/**
* Copyright 2012 Charles du Jeu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of the AjaXplorer Java Client
* More info on http://ajaxplorer.info/
*/
package info.ajaxplorer.client.http;
import info.ajaxplorer.client.model.Server;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.naming.AuthenticationException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.util.EncodingUtils;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class RestRequest {
String httpUser;
String httpPassword;
Boolean serversNeedsCredentials = false;
String authStep = "";
Boolean loginStateChanged = false;
MessageListener handler;
public boolean throwAuthExceptions = false;
public boolean throwIOExceptions = false;
public static String STATUS_REFRESHING_AUTH = "refreshing_auth";
public static String STATUS_LOADING_DATA = "loading_data";
public static String STATUS_PARSING_RESPONSE = "parsing_response";
public static String AUTH_ERROR_NOSERVER = "nocurrent_server";
public static String AUTH_ERROR_LOCKEDOUT = "locked_out";
public static String AUTH_ERROR_LOGIN_FAILED = "login_failed";
public static String IO_CONNECTION_ERROR = "Error_Connection";
public long getMaxUploadSize() {
double maxUpload = 60 * 1024 * 1024;
Map<String, String> caps = RestStateHolder.getInstance().getServer().getRemoteCapacities(this);
if(caps!=null && caps.containsKey(Server.capacity_UPLOAD_LIMIT)) {
try{
double doubl = new Double( caps.get(Server.capacity_UPLOAD_LIMIT));
maxUpload = (long) doubl;
}catch (NumberFormatException e) {}
}
maxUpload = Math.min(maxUpload, 60*1024*1024);
return (long)maxUpload;
}
public void setHandler(MessageListener handler) {
this.handler = handler;
}
public void clearHandler() {
this.handler = null;
}
public void setTimeout(int timeoutMillis){
HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), timeoutMillis);
}
public int getTimeout(){
return HttpConnectionParams.getConnectionTimeout(httpClient.getParams());
}
protected AjxpHttpClient httpClient;
protected RestStateHolder.ServerStateListener internalListener;
public RestRequest() {
RestStateHolder state = RestStateHolder.getInstance();
internalListener = new RestStateHolder.ServerStateListener() {
public void onServerChange(Server newServer, Server oldServer) {
// we clear cookies if newServer and OldServer have the same host names (aliases) but different users credentials.
initWithServer(newServer);
if(oldServer != null && AjxpHttpClient.cookieStore != null){
String currentHost = oldServer.getHost();
String currentUser = oldServer.getUser();
if(newServer.getUri() != null && newServer.getUri().toString() != null && !newServer.getUri().toString().contains(EndPointResolverApi.SERVER_URL_RESOLUTION)) {
if(newServer.getHost().equals(currentHost) && !newServer.getUser().equals(currentUser)){
AjxpHttpClient.cookieStore.clear();
}
return;
}
String oldAlias = null;
String newAlias = null;
try{
oldAlias = oldServer.getServerNode().getPropertyValue("Resolution_Alias");
newAlias = newServer.getServerNode().getPropertyValue("Resolution_Alias");
}catch(Exception e){}
if(newAlias == null || oldAlias == null){
return;
}
String serverURI = newServer.getUri().toString();
if(serverURI != null && serverURI.contains(EndPointResolverApi.SERVER_URL_RESOLUTION) && !newAlias.equals(oldAlias)) {
AjxpHttpClient.cookieStore.clear();
}
}
}
};
state.registerStateListener(internalListener);
this.initWithServer(state.getServer());
}
public void release(){
RestStateHolder.getInstance().unRegisterStateListener(internalListener);
}
private void initWithServer(Server server){
boolean trustSSL = (server != null && server.shouldTrustSSL());
if(httpClient != null){
httpClient.destroy();
}
httpClient = new AjxpHttpClient(trustSSL);
if(server != null){
setHttpUser(server.getUser());
setHttpPassword(server.getPassword());
refreshCredentials();
}
}
private CountingMultipartRequestEntity.ProgressListener uploadListener;
public void setUploadProgressListener(CountingMultipartRequestEntity.ProgressListener uploadList){
this.uploadListener = uploadList;
}
private HttpResponse issueRequest(URI uri) throws Exception{
return this.issueRequest(uri, null, null, null, null);
}
private HttpResponse issueRequest(URI uri, Map<String,String> postParameters) throws Exception{
return this.issueRequest(uri, postParameters, null, null, null);
}
private HttpResponse issueRequest(URI uri, Map<String, String> postParameters, File file, String fileName, AjxpFileBody fileBody)
throws Exception {
return this.issueRequest(uri, postParameters, file, fileName, fileBody, false);
}
private HttpResponse issueRequest(URI uri, Map<String, String> postParameters, File file, String fileName, AjxpFileBody fileBody,
boolean skipAuth) throws Exception {
URI originalUri = new URI(uri.toString());
if(EndPointResolverApi.checkResolutionRequired(uri))
{
EndPointResolverApi endPointResolverApi = null;
String uri_server ="";
Server server = RestStateHolder.getInstance().getServer();
String resolverName = server.getServerNode().getPropertyValue("resolver_class");
if (resolverName != null && !resolverName.equals("")) {
Class<?> classe = null;
try {
classe = Class.forName(resolverName);
}catch(Exception e) {
String msg = e.getMessage();
System.out.println(msg);
}
Constructor<?> ctor = classe.getConstructor();
endPointResolverApi = (EndPointResolverApi) ctor.newInstance();
}
if(endPointResolverApi == null) {
endPointResolverApi = new EndPointResolverApi();
}
uri_server = endPointResolverApi.resolveServer(server, this,uri);
originalUri = new URI(uri_server);
uri = new URI(uri.toString().replace("RequestResolution", uri_server));
RestStateHolder.getInstance().getServer().setUrl(uri_server);
// RestStateHolder.getInstance().getServer().setId(Server.slugifyId(RestStateHolder.getInstance().getServer().getUser(),
// RestStateHolder.getInstance().getServer().getServerNode().getPropertyValue("server_url")));
RestStateHolder.getInstance().notifyServerChanged(RestStateHolder.getInstance().getServer());
// TEST WEIRD, SHOULD BE SET BY THE NOTIFIER.... ????
AjxpAPI.getInstance().setServer(RestStateHolder.getInstance().getServer());
}
if(RestStateHolder.getInstance().getSECURE_TOKEN() != null){
uri = new URI(uri.toString().concat("&secure_token=" + RestStateHolder.getInstance().getSECURE_TOKEN()));
}
HttpResponse response = null;
try {
HttpRequestBase request;
if(postParameters != null || file != null){
request = new HttpPost();
if (file != null) {
if(fileBody == null){
if(fileName == null) fileName = file.getName();
fileBody = new AjxpFileBody(file, fileName);
// set upload chunk size
fileBody.setUploadChunkSize(RestStateHolder.getInstance().getFileUploadChunkSize());
long maxUpload = getMaxUploadSize();
if(maxUpload > 0 && maxUpload < file.length()){
fileBody.chunkIntoPieces((int)maxUpload);
if(uploadListener != null){
uploadListener.partTransferred(fileBody.getCurrentIndex(), fileBody.getTotalChunks());
}
}
}else{
if(uploadListener != null){
uploadListener.partTransferred(fileBody.getCurrentIndex() , fileBody.getTotalChunks());
}
}
MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
reqEntity.addPart("userfile_0", fileBody);
if(fileName != null && !EncodingUtils.getAsciiString(EncodingUtils.getBytes(fileName, "US-ASCII")).equals(fileName)){
reqEntity.addPart("urlencoded_filename", new StringBody(java.net.URLEncoder.encode(fileName, "UTF-8")));
}
if(fileBody != null && !fileBody.getFilename().equals(fileBody.getRootFilename())){
reqEntity.addPart("appendto_urlencoded_part", new StringBody(java.net.URLEncoder.encode(fileBody.getRootFilename(), "UTF-8")));
}
if (postParameters != null) {
Iterator<Map.Entry<String, String>> it = postParameters.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String, String> entry = it.next();
reqEntity.addPart(entry.getKey(), new StringBody(entry.getValue()));
}
}
if(uploadListener != null){
CountingMultipartRequestEntity countingEntity = new CountingMultipartRequestEntity(reqEntity, uploadListener);
((HttpPost)request).setEntity(countingEntity);
}else{
((HttpPost)request).setEntity(reqEntity);
}
}else{
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(postParameters.size());
Iterator<Map.Entry<String, String>> it = postParameters.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String, String> entry = it.next();
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
((HttpPost)request).setEntity(new UrlEncodedFormEntity(nameValuePairs));
}
}else{
request = new HttpGet();
}
request.setURI(uri);
if(this.httpUser.length()> 0 && this.httpPassword.length()> 0 ){
request.addHeader("Ajxp-Force-Login", "true");
}
response = httpClient.executeInContext(request);
if (!skipAuth && isAuthenticationRequested(response)) {
sendMessageToHandler(MessageListener.MESSAGE_WHAT_STATE, STATUS_REFRESHING_AUTH);
this.discardResponse(response);
this.authenticate();
if(loginStateChanged){
// RELOAD
loginStateChanged = false;
sendMessageToHandler(MessageListener.MESSAGE_WHAT_STATE, STATUS_LOADING_DATA);
if(fileBody != null) fileBody.resetChunkIndex();
return this.issueRequest(originalUri, postParameters, file, fileName, fileBody);
}
}else if(fileBody != null && fileBody.isChunked() && !fileBody.allChunksUploaded()){
this.discardResponse(response);
this.issueRequest(originalUri, postParameters, file, fileName, fileBody);
}
} catch (ClientProtocolException e) {
error(e);
} catch (IOException e) {
errorConnection(e);
} catch (AuthenticationException e){
if(this.throwAuthExceptions) throw e;
else error(e);
} catch (SAXException e){
error(e);
} catch (Exception e) {
sendMessageToHandler(MessageListener.MESSAGE_WHAT_ERROR, e.getMessage());
e.printStackTrace();
}finally{
uploadListener = null;
}
return response;
}
public HttpResponse getHttpResponse(URI uri) throws Exception{
return this.issueRequest(uri);
}
private void authenticate() throws AuthenticationException{
loginStateChanged = false;
AjxpAPI API = AjxpAPI.getInstance();
try{
if(authStep.equals("RENEW-TOKEN")){
JSONObject jObject = this.getJSonContent(API.getGetSecureTokenUri());
RestStateHolder.getInstance().setSECURE_TOKEN(jObject.getString("SECURE_TOKEN"));
loginStateChanged = true;
}else{
String seed = this.getStringContent(API.getGetLoginSeedUri());
if(seed != null) seed = seed.trim();
if(seed.indexOf("captcha") > -1){
throw new AuthenticationException(AUTH_ERROR_LOCKEDOUT);
}
if(!RestStateHolder.getInstance().isServerSet()){
throw new AuthenticationException(AUTH_ERROR_NOSERVER);
}
String user = RestStateHolder.getInstance().getServer().getUser();
String password = RestStateHolder.getInstance().getServer().getPassword();
if(!seed.trim().equals("-1")){
password = RestRequest.md5(password) + seed;
password = RestRequest.md5(password);
}
Map<String, String> loginPass = new HashMap<String, String>();
loginPass.put("userid", user);
loginPass.put("password", password);
loginPass.put("login_seed", seed);
Document doc = this.getDocumentContent(API.makeLoginUri(),loginPass);
if(doc.getElementsByTagName("logging_result").getLength() > 0){
String result = doc.getElementsByTagName("logging_result").item(0).getAttributes().getNamedItem("value").getNodeValue();
if(result.equals("1")){
//Log.d("RestRequest Authentication", "LOGGING SUCCEED! REFRESHING TOKEN");
String newToken = doc.getElementsByTagName("logging_result").item(0).getAttributes().getNamedItem("secure_token").getNodeValue();
RestStateHolder.getInstance().setSECURE_TOKEN(newToken);
loginStateChanged = true;
}else{
//Log.d("RestRequest Authentication", "Login Failed");
throw new AuthenticationException(AUTH_ERROR_LOGIN_FAILED);
}
}
}
}catch(AuthenticationException e){
throw e;
}catch(Exception e){
throw new AuthenticationException(e.getMessage());
}
}
private boolean isAuthenticationRequested(HttpResponse response) throws Exception {
Header[] heads = response.getHeaders("Content-type");
boolean xml = false;
for(int i=0;i<heads.length;i++){
if(heads[i].getValue().contains("text/xml")) xml = true;
}
if(!xml || loginStateChanged) return false;
try{
HttpEntity ent = response.getEntity();
Document doc;
if(ent.getClass() == XMLDocEntity.class){
doc = ((XMLDocEntity)ent).getDoc();
((XMLDocEntity)ent).toLogger();
}else{
XMLDocEntity docEntity = new XMLDocEntity(ent);
doc = docEntity.getDoc();
ent.consumeContent();// Make sure to clear resources
response.setEntity(docEntity);
docEntity.toLogger();
}
if(doc.getElementsByTagName("ajxp_registry_part").getLength() > 0
&& doc.getDocumentElement().getAttribute("xPath").equals("user/repositories")
&& doc.getElementsByTagName("repositories").getLength() == 0){
//Log.d("RestRequest Authentication", "EMPTY REGISTRY : AUTH IS REQUIRED");
this.authStep = "LOG-USER";
return true;
}
if(doc.getElementsByTagName("message").getLength() > 0){
if(doc.getElementsByTagName("message").item(0).getFirstChild().getNodeValue().trim().contains("You are not allowed to access this resource.")){
//Log.d("RestRequest Authentication", "REQUIRE_AUTH TAG : TOKEN IS REQUIRED");
this.authStep = "RENEW-TOKEN";
return true;
}
}
if(doc.getElementsByTagName("require_auth").getLength() > 0){
//Log.d("RestRequest Authentication", "REQUIRE_AUTH TAG : AUTH IS REQUIRED");
if(doc.getElementsByTagName("message").getLength() > 0) {
throw new Exception(doc.getElementsByTagName("message").item(0).getFirstChild().getNodeValue().trim());
}
this.authStep = "LOG-USER";
return true;
}
}catch (Exception e) {
error(e);
/*
* sendMessageToHandler(MessageListener.MESSAGE_WHAT_ERROR,
* e.getMessage());
* e.printStackTrace();
*/
}
return false;
}
public String getStringContent(URI uri) throws Exception {
return this.getStringContent(uri, null, null, null);
}
public String getContentString(URI uri, Map<String, String> map) throws Exception{
return this.getStringContent(uri, map, null, null);
}
public String getStringContent(URI uri, Map<String, String> parameters) throws Exception {
return this.getStringContent(uri, parameters, null, null);
}
public String getStringContent(URI uri, Map<String, String> parameters, File file, String fileName) throws Exception {
BufferedReader in = null;
try {
HttpResponse response = this.issueRequest(uri, parameters, file, fileName, null);
if(response == null){
throw new Exception("Empty Http response");
}
HttpEntity e = response.getEntity();
if(e.getClass() == XMLDocEntity.class){
Document doc = ((XMLDocEntity)e).getDoc();
return doc.toString();
}else{
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
String page = sb.toString();
return page;
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public HttpEntity getNotConsumedResponseEntity(URI uri, Map<String, String> params, File uploadFile, boolean skipAuth) throws Exception {
HttpResponse response = this.issueRequest(uri, params, uploadFile, null, null, skipAuth);
StatusLine status = response.getStatusLine();
if(status.getStatusCode() != 200){
throw new Exception("Status code :" + status.getStatusCode());
}
return response.getEntity();
}
public HttpEntity getNotConsumedResponseEntity(URI uri, Map<String, String> params, File uploadFile) throws Exception {
return this.getNotConsumedResponseEntity(uri, params, uploadFile, false);
}
public HttpEntity getNotConsumedResponseEntity(URI uri, Map<String, String> params) throws Exception{
return this.getNotConsumedResponseEntity(uri, params, null);
}
public Document getDocumentContent(URI uri) throws Exception {
return getDocumentContent(uri, null);
}
public Document getDocumentContent(URI uri, Map<String, String> postParams) throws Exception {
if(EndPointResolverApi.checkResolutionRequired(uri))
{
EndPointResolverApi endPointResolverApi = null;//new EndPointResolverApi();
Server server = RestStateHolder.getInstance().getServer();
String resolverName = server.getServerNode().getPropertyValue("resolver_class");
if (resolverName != null && !resolverName.equals("")) {
Class<?> classe = null;
try {
classe = Class.forName(resolverName);
}catch(Exception e) {
String msg = e.getMessage();
System.out.println(msg);
}
Constructor<?> ctor = classe.getConstructor();
endPointResolverApi = (EndPointResolverApi) ctor.newInstance();
}
if(endPointResolverApi == null) {
endPointResolverApi = new EndPointResolverApi();
}
String uri_server = endPointResolverApi.resolveServer(RestStateHolder.getInstance().getServer(), this,uri);
uri = new URI(uri.toString().replace("RequestResolution", uri_server));
RestStateHolder.getInstance().getServer().setUrl(uri_server);
// RestStateHolder.getInstance().getServer().setId(Server.slugifyId(RestStateHolder.getInstance().getServer().getUser(),
// RestStateHolder.getInstance().getServer().getServerNode().getPropertyValue("server_url")));
RestStateHolder.getInstance().notifyServerChanged(RestStateHolder.getInstance().getServer());
}
HttpResponse response = this.issueRequest(uri, postParams);
HttpEntity ent = response.getEntity();
Document doc;
sendMessageToHandler(MessageListener.MESSAGE_WHAT_STATE, STATUS_PARSING_RESPONSE);
if(ent.getClass() == XMLDocEntity.class){
doc = ((XMLDocEntity)ent).getDoc();
}else{
XMLDocEntity docEntity = new XMLDocEntity(ent);
ent.consumeContent();
doc = docEntity.getDoc();
response.setEntity(docEntity);
}
return doc;
}
public JSONObject getJSonContent(URI uri) throws Exception {
return new JSONObject(this.getStringContent(uri));
}
public JSONObject getJSonContent(URI uri, Map<String, String> parameters) throws ParseException, Exception {
return new JSONObject(this.getStringContent(uri, parameters, null, null));
}
/**
* @param uri
* Target uri that might require http basic auth
* @return true or false depending if the target uri requires http basic
* auth
*/
public Boolean credentialsRequired(URI uri) {
Boolean credentialsRequired = false;
try {
int status = getStatusCodeForRequest(uri);
if (status == 401) {
credentialsRequired = true;
serversNeedsCredentials = true;
}
} catch (Exception e) {
e.printStackTrace();
}
return credentialsRequired;
}
public Boolean credentialsRequired() {
return serversNeedsCredentials;
}
public void parseDocumentMessage(Document doc){
try{
parseDocumentMessage(doc, false);
}catch(Exception e){}
}
public void parseDocumentMessage(Document doc, boolean throwOnError) throws Exception{
if(doc.getElementsByTagName("message").getLength() > 0){
Node node = doc.getElementsByTagName("message").item(0);
String type = node.getAttributes().getNamedItem("type").getNodeValue();
String message = node.getTextContent();
if(type.equalsIgnoreCase("error")){
if(throwOnError){
throw new Exception(message);
}
sendMessageToHandler(MessageListener.MESSAGE_WHAT_ERROR, message);
}else{
sendMessageToHandler(MessageListener.MESSAGE_WHAT_STATE, message);
}
}
}
public String getHttpUser() {
return httpUser;
}
public void setHttpUser(String httpUser) {
this.httpUser = httpUser;
//setAuthenticator();
}
public String getHttpPassword() {
return httpPassword;
}
public void setHttpPassword(String httpPassword) {
this.httpPassword = httpPassword;
//setAuthenticator();
}
public void refreshCredentials(){
this.getHttpClient().refreshCredentials(httpUser, httpPassword);
}
/*
public void setAuthenticator() {
if (httpPassword != null && httpUser != null) {
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(httpUser, httpPassword.toCharArray());
}
});
}
}
*/
public int getStatusCodeForRequest(URI uri) {
HttpResponse response;
try {
response = issueRequest(uri);
if (response != null) {
int status = response.getStatusLine().getStatusCode();
// we need to read the whole response-body stream if we want to
// avoid problems with the SingleConnexionManager
discardResponse(response);
return status;
} else {
return -1;
}
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
public void discardResponse(HttpResponse response) {
try {
BufferedReader in = null;
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer("");
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
} catch (IllegalStateException e){
// Silent, was already consumed
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized AjxpHttpClient getHttpClient() {
return httpClient;
}
public static final String md5(final String s) {
try {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest
.getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String h = Integer.toHexString(0xFF & messageDigest[i]);
while (h.length() < 2)
h = "0" + h;
hexString.append(h);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
protected void error(Exception e) throws Exception{
if(handler != null) handler.sendMessage(MessageListener.MESSAGE_WHAT_ERROR, e.getMessage());
else {
e.printStackTrace();
throw e;
}
}
protected void errorConnection(Exception e) throws Exception{
if(this.throwIOExceptions) throw new Exception(IO_CONNECTION_ERROR);
else if(handler != null)
{
handler.sendMessage(MessageListener.MESSAGE_WHAT_ERROR, IO_CONNECTION_ERROR);
}
else {
e.printStackTrace();
throw e;
}
}
protected void sendMessageToHandler(int messageType, Object obj){
if(handler == null) return;
handler.sendMessage(messageType, obj);
}
}