package org.witness.informacam.transport; import info.guardianproject.onionkit.ui.OrbotHelper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.witness.informacam.InformaCam; import org.witness.informacam.R; import org.witness.informacam.informa.InformaService; import org.witness.informacam.models.Model; import org.witness.informacam.models.organizations.IRepository; import org.witness.informacam.models.transport.ITransportData; import org.witness.informacam.models.transport.ITransportStub; import org.witness.informacam.utils.Constants.Codes; import org.witness.informacam.utils.Constants.Logger; import org.witness.informacam.utils.Constants.Models; import org.witness.informacam.utils.Constants.Models.IMedia.MimeType; import android.annotation.SuppressLint; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Message; import android.support.v4.app.NotificationCompat; import android.util.Log; public class Transport extends IntentService { ITransportStub transportStub; IRepository repository; final String repoName; InformaCam informaCam; protected NotificationCompat.Builder mBuilder; protected NotificationManager mNotifyManager; public final static int NOTIFY_ID = 7777; protected final static String LOG = "InformaTRANSPORT"; private final static String URL_USE_TOR_STRING = ".onion"; //if you see this in the url string, use the local Tor proxy public Transport(String name) { super(name); this.repoName = name; informaCam = InformaCam.getInstance(); } protected boolean init() throws IOException { return init(true); } protected boolean init(boolean requiresTor) { repository = transportStub.getRepository(repoName); if(requiresTor) { int transportRequirements = checkTransportRequirements(); if(transportRequirements == -1) { transportStub.numTries++; } else { transportStub.numTries = (Models.ITransportStub.MAX_TRIES + 1); Logger.d(LOG, "ACTUALLY NO ORBOT"); finishUnsuccessfully(transportRequirements); // Prompt to start up/install orbot here. stopSelf(); return false; } } mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mBuilder = new NotificationCompat.Builder(this); mBuilder.setContentTitle(getString(R.string.app_name) + " Upload") .setContentText("Upload in progress to: " + repoName) .setTicker("Upload in progress") .setSmallIcon(android.R.drawable.ic_menu_upload); mBuilder.setProgress(100, 0, false); Intent intent = new Intent (this, InformaService.class); intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pend = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pend); // Displays the progress bar for the first time. mNotifyManager.notify(NOTIFY_ID, mBuilder.build()); return true; } protected void send() {} protected void finishSuccessfully() throws InstantiationException, IllegalAccessException { transportStub.resultCode = Models.ITransportStub.ResultCodes.OK; if(transportStub.associatedNotification != null) { transportStub.associatedNotification.taskComplete = true; informaCam.updateNotification(transportStub.associatedNotification, informaCam.h); } switch(transportStub.callbackCode) { case Models.ITransportStub.CallbackCodes.UPDATE_ORGANIZATION_HAS_KEY: Logger.d(LOG, "ALSO MARKING KEY AS RECEIVED"); transportStub.organization.keyReceived = true; transportStub.organization.save(); break; } stopSelf(); } protected void finishUnsuccessfully() { finishUnsuccessfully(-1); } protected void finishUnsuccessfully(int transportRequirements) { try { if(mBuilder != null) { mBuilder .setContentText("FAILED upload to: " + repository.asset_root) .setTicker("FAILED upload to: " + repository.asset_root); mBuilder.setAutoCancel(true); mBuilder.setProgress(0, 0, false); // Displays the progress bar for the first time. mNotifyManager.notify(NOTIFY_ID, mBuilder.build()); } if(informaCam.getEventListener() != null) { Message message = new Message(); Bundle data = new Bundle(); if(transportRequirements == -1) { data.putInt(Codes.Extras.MESSAGE_CODE, Codes.Messages.Transport.GENERAL_FAILURE); data.putString(Codes.Extras.GENERAL_FAILURE, informaCam.getString(R.string.informacam_could_not_send)); } else { data.putInt(Codes.Extras.MESSAGE_CODE, transportRequirements); } message.setData(data); informaCam.getEventListener().onUpdate(message); } if(transportStub.associatedNotification != null) { transportStub.associatedNotification.canRetry = true; transportStub.associatedNotification.save(); informaCam.transportManifest.add(transportStub); } } catch (Exception e) { Log.e("Transport Error","problem initing transport",e); } } public int checkTransportRequirements () { if(repository != null && repository.asset_root != null && repository.asset_root.toLowerCase().contains(URL_USE_TOR_STRING)) { OrbotHelper oh = new OrbotHelper(this); if(!oh.isOrbotInstalled()) { return Codes.Messages.Transport.ORBOT_UNINSTALLED; } else if(!oh.isOrbotRunning()) { return Codes.Messages.Transport.ORBOT_NOT_RUNNING; } } return -1; } /** protected void resend() { if(transportStub.numTries <= Models.ITransportStub.MAX_TRIES) { Logger.d(LOG, "POST FAILED. Trying again."); init(); } else { finishUnsuccessfully(); stopSelf(); } } * @throws IOException */ @SuppressLint("DefaultLocale") protected Object doPost(ITransportData fileData, String urlString) throws IOException { boolean useTorProxy = false; if (urlString.toLowerCase().contains(URL_USE_TOR_STRING)) useTorProxy = true; HttpURLConnection http = buildConnection(urlString, useTorProxy); InputStream inFile = informaCam.ioService.getStream(fileData.assetPath, fileData.storageType); int fixedLength = (int) inFile.available(); http.setDoOutput(true); http.setRequestMethod("POST"); http.setRequestProperty("Content-Type", fileData.mimeType); http.setRequestProperty("Content-Disposition", "attachment; filename=\"" + fileData.assetName + "\""); http.setRequestProperty("Connection", "Keep-Alive"); http.setRequestProperty("Cache-Control", "no-cache"); http.setFixedLengthStreamingMode(fixedLength); http.setUseCaches(false); http.connect(); DataOutputStream outNet = new DataOutputStream(http.getOutputStream()); int DEFAULT_BUFFER_SIZE = 1024*4; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int count = 0; int n = 0; while (-1 != (n = inFile.read(buffer))) { outNet.write(buffer, 0, n); count += n; updateProgress (count, fixedLength); } inFile.close(); outNet.close(); BufferedInputStream is = new BufferedInputStream(http.getInputStream()); if(http.getResponseCode() > -1) { return(parseResponse(is)); } return null; } private void updateProgress (int count, int total) { int percentage = (int)(((float)count/(float)total)*100f); mBuilder.setProgress(100, percentage, false); mBuilder.setContentText("Upload in progress to: " + repoName + ' ' + percentage + '%'); // Displays the progress bar for the first time. mNotifyManager.notify(NOTIFY_ID, mBuilder.build()); } protected Object doPost(Model postData, ITransportData fileData, String urlString) throws IOException { // multipart boolean useTorProxy = false; if (urlString.toLowerCase().contains(URL_USE_TOR_STRING)) useTorProxy = true; HttpURLConnection http = buildConnection(urlString, useTorProxy); String boundary = "==11==22==44==99==InformaCam=="; String hyphens = "--"; String lineEnd = "\n"; long bytesWritten = 0; List<StringBuffer> contentBuffer = new ArrayList<StringBuffer>(); StringBuffer sb = new StringBuffer(); sb.append((hyphens + boundary + lineEnd)); sb.append(("Content-type: application/json; charset=UTF-8" + lineEnd + lineEnd)); sb.append((postData.asJson().toString() + lineEnd + lineEnd)); sb.append((hyphens + boundary + lineEnd)); sb.append(("Content-type: " + fileData.mimeType + lineEnd + lineEnd)); bytesWritten += sb.toString().getBytes().length; contentBuffer.add(sb); sb = new StringBuffer(); sb.append((lineEnd + lineEnd)); sb.append((hyphens + boundary + hyphens)); bytesWritten += sb.toString().getBytes().length; contentBuffer.add(sb); InputStream in = informaCam.ioService.getStream(fileData.assetPath, fileData.storageType); bytesWritten += in.available(); http.setDoOutput(true); http.setRequestMethod("POST"); http.setRequestProperty("Content-Type", "multipart/related; boundary=\"" + boundary + "\""); http.setRequestProperty("Content-Length", Long.toString(bytesWritten)); http.setRequestProperty("Connection", "Keep-Alive"); http.setRequestProperty("Cache-Control", "no-cache"); int fixedLength = (int)bytesWritten; http.setFixedLengthStreamingMode((int)bytesWritten); http.setUseCaches(false); http.connect(); BufferedOutputStream out = new BufferedOutputStream(http.getOutputStream()); out.write(contentBuffer.get(0).toString().getBytes()); //Logger.d(LOG, contentBuffer.get(0).toString()); int DEFAULT_BUFFER_SIZE = 1024*4; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int count = 0; int n = 0; while (-1 != (n = in.read(buffer))) { out.write(buffer, 0, n); count += n; updateProgress (count, fixedLength); } in.close(); //Logger.d(LOG, "[... data ...]"); out.write(contentBuffer.get(1).toString().getBytes()); //Logger.d(LOG, contentBuffer.get(1).toString()); out.flush(); Log.i(LOG, "RESPONSE CODE: " + http.getResponseCode()); Log.i(LOG, "RESPONSE MSG: " + http.getResponseMessage()); int respCode = http.getResponseCode(); if(respCode > -1) { InputStream is = new BufferedInputStream(http.getInputStream()); return(parseResponse(is)); } out.close(); return null; } protected Object doPost(Model postData, String urlString) throws IOException { boolean useTorProxy = false; if (urlString.toLowerCase().contains(URL_USE_TOR_STRING)) useTorProxy = true; HttpURLConnection http = buildConnection(urlString, useTorProxy); http.setDoOutput(true); http.setRequestMethod("POST"); http.setRequestProperty("Content-Type", MimeType.JSON); http.connect(); http.getOutputStream().write(postData.asJson().toString().getBytes()); InputStream is = new BufferedInputStream(http.getInputStream()); //Logger.d(LOG, "RESPONSE CODE: " + http.getResponseCode()); //Logger.d(LOG, "RESPONSE MSG: " + http.getResponseMessage()); if(http.getResponseCode() > -1) { return(parseResponse(is)); } return null; } protected Object doPut(byte[] putData, String urlString, String mimeType) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream(putData); return doPut(in, urlString, mimeType); } protected Object doPut(InputStream in, String urlString, String mimeType) throws IOException { boolean useTorProxy = false; if (urlString.toLowerCase().contains(URL_USE_TOR_STRING)) useTorProxy = true; HttpURLConnection http = buildConnection(urlString, useTorProxy); http.setRequestMethod("PUT"); http.setRequestProperty("Content-Type", mimeType); http.setUseCaches(false); http.connect(); BufferedOutputStream out = new BufferedOutputStream(http.getOutputStream()); int DEFAULT_BUFFER_SIZE = 1024*4; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int count = 0; int n = 0; int totalAvail = in.available(); while (-1 != (n = in.read(buffer))) { out.write(buffer, 0, n); count += n; updateProgress (count, totalAvail); } in.close(); //Logger.d(LOG, "[... data ...]"); //Logger.d(LOG, contentBuffer.get(1).toString()); out.flush(); Logger.d(LOG, "RESPONSE CODE: " + http.getResponseCode()); Logger.d(LOG, "RESPONSE MSG: " + http.getResponseMessage()); if(http.getResponseCode() > -1) { InputStream is = new BufferedInputStream(http.getInputStream()); return(parseResponse(is)); } return null; } protected Object doGet(String urlString) throws IOException { return doGet(null, urlString); } @SuppressWarnings("unchecked") protected Object doGet(Model getData, String urlString) throws IOException { if(getData != null) { List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>(); Iterator<String> it = getData.asJson().keys(); while(it.hasNext()) { String key = it.next(); nameValuePair.add(new BasicNameValuePair(key, String.valueOf(getData.asJson().get(key)))); } urlString += ("?" + URLEncodedUtils.format(nameValuePair, "utf_8")); } boolean useTorProxy = false; if (urlString.toLowerCase().contains(URL_USE_TOR_STRING)) useTorProxy = true; HttpURLConnection http = buildConnection(urlString, useTorProxy); http.setRequestMethod("GET"); http.setDoOutput(false); InputStream is = new BufferedInputStream(http.getInputStream()); http.connect(); // Logger.d(LOG, "RESPONSE CODE: " + http.getResponseCode()); // Logger.d(LOG, "RESPONSE MSG: " + http.getResponseMessage()); if(http.getResponseCode() > -1) { return(parseResponse(is)); } else { try { //Logger.d(LOG, String.format(LOG, "ERROR IF PRESENT:\n%s", ((JSONObject) parseResponse(is)).toString())); } catch(Exception e) { Logger.e(LOG, e); } } return null; } protected Object parseResponse(InputStream is) { StringBuffer lastResult = new StringBuffer(); try { for(String line : IOUtils.readLines(is)) { Logger.d(LOG, line); lastResult.append(line); } transportStub.lastResult = lastResult.toString(); return lastResult.toString(); } catch (IOException e) { Logger.e(LOG, e); } return null; } protected HttpURLConnection buildConnection(String urlString, boolean useTorProxy) throws IOException { HttpURLConnection http = null; URL url = new URL(urlString == null ? repository.asset_root : urlString); // Logger.d(LOG, "URL PROTOCOL: " + url.getProtocol()); if(url.getProtocol().equals("https")) { // TODO: add memorizing trust manager } if (useTorProxy) { // Logger.d(LOG, "AND USING TOR PROXY"); Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8118)); http = (HttpURLConnection) url.openConnection(proxy); } else { http = (HttpURLConnection) url.openConnection(); } http.setUseCaches(false); return http; } @Override protected void onHandleIntent(Intent intent) { transportStub = (ITransportStub) intent.getSerializableExtra(Models.ITransportStub.TAG); //Log.d(LOG, "TRANSPORT:\n" + transportStub.asJson().toString()); if(transportStub == null) { stopSelf(); } else { try { init(); } catch (IOException ioe) { Log.e(LOG,"Unable to initiated transport",ioe); } } } }