Initial commit
[mustard2:mustard2.git] / src / org / mustard2 / util / HttpManager.java
1 /*
2  * MUSTARD: Android's Client for StatusNet
3  * 
4  * Copyright (C) 2009-2010 macno.org, Michele Azzolari
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  * 
20  */
21
22 package org.mustard2.util;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Iterator;
34
35 import javax.xml.parsers.DocumentBuilder;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.ParserConfigurationException;
38
39 import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
40 import oauth.signpost.exception.OAuthCommunicationException;
41 import oauth.signpost.exception.OAuthExpectationFailedException;
42 import oauth.signpost.exception.OAuthMessageSignerException;
43
44 import org.apache.http.Header;
45 import org.apache.http.HttpException;
46 import org.apache.http.HttpHost;
47 import org.apache.http.HttpRequest;
48 import org.apache.http.HttpRequestInterceptor;
49 import org.apache.http.HttpResponse;
50 import org.apache.http.HttpVersion;
51 import org.apache.http.NameValuePair;
52 import org.apache.http.auth.AuthScope;
53 import org.apache.http.auth.AuthState;
54 import org.apache.http.auth.Credentials;
55 import org.apache.http.auth.UsernamePasswordCredentials;
56 import org.apache.http.client.ClientProtocolException;
57 import org.apache.http.client.CredentialsProvider;
58 import org.apache.http.client.entity.UrlEncodedFormEntity;
59 import org.apache.http.client.methods.HttpDelete;
60 import org.apache.http.client.methods.HttpGet;
61 import org.apache.http.client.methods.HttpPost;
62 import org.apache.http.client.methods.HttpUriRequest;
63 import org.apache.http.client.protocol.ClientContext;
64 import org.apache.http.conn.ClientConnectionManager;
65 import org.apache.http.conn.scheme.PlainSocketFactory;
66 import org.apache.http.conn.scheme.Scheme;
67 import org.apache.http.conn.scheme.SchemeRegistry;
68 import org.apache.http.entity.InputStreamEntity;
69 import org.apache.http.entity.mime.MIME;
70 import org.apache.http.entity.mime.MultipartEntity;
71 import org.apache.http.entity.mime.content.AbstractContentBody;
72 import org.apache.http.entity.mime.content.StringBody;
73 import org.apache.http.impl.auth.BasicScheme;
74 import org.apache.http.impl.client.BasicCredentialsProvider;
75 import org.apache.http.impl.client.DefaultHttpClient;
76 import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
77 import org.apache.http.params.BasicHttpParams;
78 import org.apache.http.params.HttpConnectionParams;
79 import org.apache.http.params.HttpParams;
80 import org.apache.http.params.HttpProtocolParams;
81 import org.apache.http.protocol.ExecutionContext;
82 import org.apache.http.protocol.HTTP;
83 import org.apache.http.protocol.HttpContext;
84 import org.apache.http.util.EntityUtils;
85 import org.json.JSONArray;
86 import org.json.JSONException;
87 import org.json.JSONObject;
88 import org.mustard2.android.AuthException;
89 import org.mustard2.android.MustardApp;
90 import org.mustard2.android.MustardException;
91 import org.mustard2.android.R;
92 import org.w3c.dom.Document;
93 import org.xml.sax.SAXException;
94
95 import android.content.Context;
96 import android.content.pm.PackageInfo;
97 import android.content.pm.PackageManager;
98 import android.content.pm.PackageManager.NameNotFoundException;
99 import android.util.Log;
100
101 public class HttpManager {
102
103         public static final String GET = "GET";
104         public static final String POST = "POST";
105         public static final String DELETE = "DELETE";
106
107         private static final Integer DEFAULT_POST_REQUEST_TIMEOUT = 20000;
108         
109         //private AuthScope mAuthScope;
110         private DefaultHttpClient mClient;
111
112         private String mHost;
113         private CommonsHttpOAuthConsumer consumer ;
114         private Context mContext;
115         
116         public HttpManager() {
117                 this(null);
118         }
119         
120         public HttpManager(Context context) {
121                 mContext=context;
122                 HttpParams params = getHttpParams();
123         SchemeRegistry schemeRegistry = new SchemeRegistry();
124         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
125             schemeRegistry.register(new Scheme("https", UntrustedSSLSocketFactory.getSocketFactory(), 443));
126         ClientConnectionManager manager = new ThreadSafeClientConnManager(params, schemeRegistry);
127         mClient = new DefaultHttpClient(manager,params);
128         }
129         
130         public HttpParams getHttpParams() {
131         final HttpParams params = new BasicHttpParams();
132         HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
133         HttpProtocolParams.setContentCharset(params, "UTF-8");
134
135         HttpConnectionParams.setStaleCheckingEnabled(params, false);
136 //        HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
137         HttpConnectionParams.setSoTimeout(params, 20 * 1000);
138         HttpConnectionParams.setSocketBufferSize(params, 2*8192);
139
140 //        HttpClientParams.setRedirecting(params, true);
141
142                 HttpProtocolParams.setUserAgent(params, getUserAgent());
143         HttpProtocolParams.setUseExpectContinue(params,false);
144         
145         return params;
146         
147         }
148         
149     private String getUserAgent() {
150         if(mContext != null) {
151                 try {
152                         // Read package name and version number from manifest
153                         PackageManager manager = mContext.getPackageManager();
154                         PackageInfo info = manager.getPackageInfo(mContext.getPackageName(), 0);
155                         return String.format(mContext.getString(R.string.template_user_agent),
156                                         mContext.getString(R.string.app_name), info.versionName);
157
158                 } catch(NameNotFoundException e) {
159                         Log.e("Mustard", "Couldn't find package information in PackageManager", e);
160                 }
161         }
162         return "Mustard/1.0";
163     }
164     
165         public HttpManager(Context context,String host) {
166                 mContext=context;
167                 HttpParams params = getHttpParams();
168         SchemeRegistry schemeRegistry = new SchemeRegistry();
169         schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
170             schemeRegistry.register(new Scheme("https", UntrustedSSLSocketFactory.getSocketFactory(), 443));
171         ClientConnectionManager manager = new ThreadSafeClientConnManager(params, schemeRegistry);
172         mClient = new DefaultHttpClient(manager,params);
173                 mHost=host;
174         }
175
176         public void setHost(String host) {
177                 mHost=host;
178         }
179         
180         public void setCredentials(String username, String password) {
181
182                 Credentials defaultcreds = new UsernamePasswordCredentials(username, password);
183                 String host=AuthScope.ANY_HOST;
184                 if (mHost!=null)
185                         host=mHost;
186                 BasicCredentialsProvider cP = new BasicCredentialsProvider(); 
187                 cP.setCredentials(new AuthScope(host, AuthScope.ANY_PORT, AuthScope.ANY_REALM), defaultcreds);
188                 mClient.setCredentialsProvider(cP);
189                 mClient.addRequestInterceptor(preemptiveAuth, 0);
190                 
191         }
192
193         public void setOAuthConsumer(CommonsHttpOAuthConsumer consumer ) {
194                 this.consumer=consumer;
195         }
196         
197         public CommonsHttpOAuthConsumer getOAuthConsumer() {
198                 return consumer;
199         }
200         
201         public JSONObject getJsonObject(String url) throws IOException,MustardException,AuthException {
202                 return getJsonObject(url,GET,null);
203         }
204         
205         public JSONObject getJsonObject(String url, String httpMethod) throws IOException,MustardException,AuthException {
206                 return getJsonObject(url,httpMethod,null);
207         }
208         
209         public JSONObject getJsonObject(String url, String httpMethod,
210                         ArrayList<NameValuePair> params) throws IOException,MustardException,AuthException {
211                 JSONObject json = null;
212                 try {
213                         json = new JSONObject(StreamUtil.toString(getInputStream(url,httpMethod,params)));
214                 } catch (JSONException e) {
215                         throw new MustardException(998,"Non json response: " + e.toString());
216                 }
217                 return json;
218         }
219
220         public JSONObject getJsonObject(String url, ArrayList<NameValuePair> params,String attachmentParam, File attachment) throws IOException,MustardException,AuthException {
221                 JSONObject json = null;
222                 try {
223                         json = new JSONObject(StreamUtil.toString(requestData(url,params,attachmentParam,attachment)));
224                 } catch (JSONException e) {
225                         throw new MustardException(998,"Non json response: " + e.toString());
226                 }
227                 return json;
228         }
229         
230         public JSONArray getJsonArray(String url) throws IOException,MustardException,AuthException {
231                 return getJsonArray(url,GET,null);
232         }
233         
234         public JSONArray getJsonArray(String url, String httpMethod) 
235                                                                         throws IOException,MustardException,AuthException {
236                 return getJsonArray(url,httpMethod,null);
237         }
238         
239         public JSONArray getJsonArray(String url, String httpMethod,
240                         ArrayList<NameValuePair> params) throws IOException,MustardException,AuthException {
241                 JSONArray json = null;
242                 InputStream is = null;
243                 try {
244                         is = getInputStream(url,httpMethod,params);
245                         json = new JSONArray(StreamUtil.toString(is));
246                 } catch (JSONException e) {
247                         throw new MustardException(998,"Non json response: " + e.toString());
248                 } finally {
249                         if(is != null) {
250                                 try { is.close();} catch (Exception e){}
251                         }
252                 }
253                 return json;
254         }
255
256         public InputStream getInputStream(String url, String httpMethod,ArrayList<NameValuePair> params) throws IOException,MustardException,AuthException {
257                 return getInputStream(url, httpMethod,params,0);
258         }
259         
260         private HashMap<String,String> mHeaders;
261         
262         public void setExtraHeaders(HashMap<String,String> headers) {
263                 mHeaders = headers;
264         }
265         
266         public InputStream getInputStream(String url, String httpMethod,ArrayList<NameValuePair> params, int loop) throws IOException,MustardException,AuthException {
267
268                 URI uri;
269
270                 try {
271                         uri = new URI(url);
272                 } catch (URISyntaxException e) {
273                         throw new IOException("Invalid URL.");
274                 }
275                 if (MustardApp.DEBUG) Log.d("HTTPManager","Requesting " + uri);
276                 HttpUriRequest method;
277
278                 if (POST.equals(httpMethod)) {
279                         HttpPost post = new HttpPost(uri);
280                         if(params != null)
281                                 post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
282                         method = post;
283                 } else if (DELETE.equals(httpMethod)) {
284                         method = new HttpDelete(uri);
285                 } else {
286                         method = new HttpGet(uri);
287                 }
288
289                 if (mHeaders != null) {
290                         Iterator<String> headKeys = mHeaders.keySet().iterator();
291                         while(headKeys.hasNext()) {
292                                 String key = headKeys.next();
293                                 method.setHeader(key, mHeaders.get(key));
294                         }
295                 }
296                 if (consumer != null) {
297                         try {
298                                 consumer.sign(method);
299                         } catch (OAuthMessageSignerException e) {
300                                  
301                         }catch (OAuthExpectationFailedException e) {
302                                 
303                         } catch (OAuthCommunicationException e) {
304                                 
305                         }
306                 }
307
308                 HttpResponse response;
309                 try {
310                         response = mClient.execute(method);
311                 } catch (ClientProtocolException e) {
312                         throw new IOException("HTTP protocol error.");
313                 }
314
315                 int statusCode = response.getStatusLine().getStatusCode();
316 //              Log.d("HttpManager", url + " >> " + statusCode);
317                 if (statusCode == 401) {
318                         throw new AuthException(401,"Unauthorized");
319                 } else if (statusCode == 403 || statusCode == 406) {
320                         try {
321                                 JSONObject json = null;
322                                 try {
323                                         json = new JSONObject(StreamUtil.toString(response.getEntity().getContent()));
324                                 } catch (JSONException e) {
325                                         throw new MustardException(998,"Non json response: " + e.toString());
326                                 }
327                                 throw new MustardException(statusCode, json.getString("error"));
328                         } catch (IllegalStateException e) {
329                                 throw new IOException("Could not parse error response.");
330                         } catch (JSONException e) {
331                                 throw new IOException("Could not parse error response.");
332                         }
333                 } else if (statusCode == 404) {
334                         // User/Group or page not found
335                         throw new MustardException(404,"Not found: " + url);
336                 } else if ( (statusCode == 301 || statusCode == 302 || statusCode == 303) && GET.equals(httpMethod) && loop < 3) {
337 //                      Log.v("HttpManager", "Got : " + statusCode);
338                         Header hLocation = response.getLastHeader("Location");
339                         if (hLocation != null) {
340                                 Log.v("HttpManager", "Got : " + hLocation.getValue());
341                                 return getInputStream(hLocation.getValue(), httpMethod,params, loop+1);
342                         }
343                         else throw new MustardException(statusCode,"Too many redirect: " + url);
344                 } else if (statusCode != 200) {
345                         throw new MustardException(999,"Unmanaged response code: " + statusCode);
346                 }
347
348                 return response.getEntity().getContent();
349         }
350         
351         
352         private static final String IMAGE_MIME_JPG = "image/jpeg";
353         private static final String IMAGE_MIME_PNG = "image/png";
354         
355         private InputStream requestData(String url, ArrayList<NameValuePair> params, String attachmentParam, File attachment) 
356                         throws IOException,MustardException,AuthException {
357
358                 URI uri;
359
360                 try {
361                         uri = new URI(url);
362                 } catch (URISyntaxException e) {
363                         throw new IOException("Invalid URL.");
364                 }
365                 if (MustardApp.DEBUG) Log.d("HTTPManager","Requesting " + uri);
366                 
367                 HttpPost post = new HttpPost(uri);
368                 
369                 HttpResponse response;
370
371                 // create the multipart request and add the parts to it 
372                 MultipartEntity requestContent = new MultipartEntity(); 
373                 long len = attachment.length();
374                 
375                 InputStream ins = new FileInputStream(attachment);
376                 InputStreamEntity ise = new InputStreamEntity(ins, -1L); 
377                 byte[] data = EntityUtils.toByteArray(ise);
378                  
379                 String IMAGE_MIME = attachment.getName().toLowerCase().endsWith("png") ? IMAGE_MIME_PNG : IMAGE_MIME_JPG;
380                 requestContent.addPart(attachmentParam, new ByteArrayBody(data, IMAGE_MIME, attachment.getName()));
381
382                 if (params != null) {
383                         for (NameValuePair param : params) {
384                                 len += param.getValue().getBytes().length;
385                                 requestContent.addPart(param.getName(), new StringBody(param.getValue()));
386                         }
387                 }
388                 post.setEntity(requestContent); 
389                 
390                 Log.d("Mustard","Length: " + len);
391                 
392                 if (mHeaders != null) {
393                         Iterator<String> headKeys = mHeaders.keySet().iterator();
394                         while(headKeys.hasNext()) {
395                                 String key = headKeys.next();
396                                 post.setHeader(key, mHeaders.get(key));
397                         }
398                 }
399
400                 if (consumer != null) {
401                         try {
402                                 consumer.sign(post);
403                         } catch (OAuthMessageSignerException e) {
404                                  
405                         }catch (OAuthExpectationFailedException e) {
406                                 
407                         } catch (OAuthCommunicationException e) {
408                                 
409                         }
410                 }
411                 
412                 try {
413                         mClient.getParams().setIntParameter(HttpConnectionParams.CONNECTION_TIMEOUT, DEFAULT_POST_REQUEST_TIMEOUT);
414                         mClient.getParams().setIntParameter(HttpConnectionParams.SO_TIMEOUT, DEFAULT_POST_REQUEST_TIMEOUT);
415                         response = mClient.execute(post);
416                 } catch (ClientProtocolException e) {
417                         e.printStackTrace();
418                         throw new IOException("HTTP protocol error.");
419                 }
420
421                 int statusCode = response.getStatusLine().getStatusCode();
422
423                 
424                 if (statusCode == 401) {
425                         throw new AuthException(401,"Unauthorized: " + url);
426                 } else if (statusCode == 403 || statusCode == 406) {
427                         try {
428                                 JSONObject json = null;
429                                 try {
430                                         json = new JSONObject(StreamUtil.toString(response.getEntity().getContent()));
431                                 } catch (JSONException e) {
432                                         throw new MustardException(998,"Non json response: " + e.toString());
433                                 }
434                                 throw new MustardException(statusCode, json.getString("error"));
435                         } catch (IllegalStateException e) {
436                                 throw new IOException("Could not parse error response.");
437                         } catch (JSONException e) {
438                                 throw new IOException("Could not parse error response.");
439                         }
440                 } else if (statusCode != 200) {
441                         Log.e("Mustard", response.getStatusLine().getReasonPhrase());
442                         throw new MustardException(999,"Unmanaged response code: " + statusCode);
443                 }
444
445                 return response.getEntity().getContent();
446         }
447
448         public String getResponseAsString(String url)
449                 throws IOException,MustardException,AuthException {
450                 return getResponseAsString(url,GET,null);
451         }
452         
453         public String getResponseAsString(String url, String httpMethod)
454                 throws IOException,MustardException,AuthException {
455                 return getResponseAsString(url,httpMethod,null);
456         }
457         
458         public String getResponseAsString(String url, String httpMethod,
459                         ArrayList<NameValuePair> params) throws IOException,MustardException,AuthException {
460                 return StreamUtil.toString(getInputStream(url,httpMethod,params)); 
461         }
462         
463         public String getResponseAsString(String url, ArrayList<NameValuePair> params,String attachmentParam, File attachment) throws IOException,MustardException,AuthException {
464                 return StreamUtil.toString(requestData(url,params,attachmentParam,attachment));
465         }
466         
467         public Document getDocument(String url) throws IOException,MustardException,AuthException {
468                 return getDocument(url, GET, null);
469         }
470
471         public Document getDocument(String url, String httpMethod,ArrayList<NameValuePair> params) throws IOException,MustardException,AuthException {
472                 Document  dom = null;
473                 InputStream is = null;
474                 try {
475                         is = getInputStream(url,httpMethod,params);
476                         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
477                 
478                 DocumentBuilder builder = factory.newDocumentBuilder();
479                 dom = builder.parse(is);
480                 } catch(ParserConfigurationException e) {
481                         e.printStackTrace();
482                         throw new MustardException(980,"Parser exception: " + e.getMessage());
483                 } catch(SAXException e) {
484                         e.printStackTrace();
485                         throw new MustardException(981,"Parser exception: " + e.getMessage());
486                 } finally {
487                         if(is != null) {
488                                 try { is.close();} catch (Exception e){}
489                         }
490                 }
491                 return dom;
492         }
493         
494         
495         private HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
496             
497             public void process(
498                     final HttpRequest request, 
499                     final HttpContext context) throws HttpException, IOException {
500                 
501                 AuthState authState = (AuthState) context.getAttribute(
502                         ClientContext.TARGET_AUTH_STATE);
503                 CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
504                         ClientContext.CREDS_PROVIDER);
505                 HttpHost targetHost = (HttpHost) context.getAttribute(
506                         ExecutionContext.HTTP_TARGET_HOST);
507                 
508                 // If not auth scheme has been initialized yet
509                 if (authState.getAuthScheme() == null) {
510                     AuthScope authScope = new AuthScope(
511                             targetHost.getHostName(), 
512                             targetHost.getPort());
513                     // Obtain credentials matching the target host
514                     Credentials creds = credsProvider.getCredentials(authScope);
515                     // If found, generate BasicScheme preemptively
516                     if (creds != null) {
517                         authState.setAuthScheme(new BasicScheme());
518                         authState.setCredentials(creds);
519                     }
520                 }
521             }
522             
523         };
524         
525         private  class ByteArrayBody extends AbstractContentBody {
526
527                 private final byte[] bytes;
528                 private final String fileName;
529
530                 public ByteArrayBody(byte[] bytes, String mimeType, String fileName) {
531                         super(mimeType);
532                         this.bytes = bytes;
533                         this.fileName = fileName;
534                 }
535
536                 public String getFilename() {
537                         return fileName;
538                 }
539
540                 @Override
541                 public void writeTo(OutputStream out) throws IOException {
542                         out.write(bytes);
543                 }
544
545                 public String getCharset() {
546                         return null;
547                 }
548
549                 public long getContentLength() {
550                         return bytes.length;
551                 }
552
553                 public String getTransferEncoding() {
554                         return MIME.ENC_BINARY;
555                 }
556
557         }
558
559 }