HttpUploadConnection.java

  1package eu.siacs.conversations.http;
  2
  3import android.os.PowerManager;
  4import android.util.Log;
  5import android.util.Pair;
  6
  7import java.io.FileInputStream;
  8import java.io.FileNotFoundException;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.io.OutputStream;
 12import java.net.HttpURLConnection;
 13import java.net.URL;
 14import java.util.Arrays;
 15import java.util.HashMap;
 16import java.util.List;
 17import java.util.Scanner;
 18
 19import javax.net.ssl.HttpsURLConnection;
 20
 21import eu.siacs.conversations.Config;
 22import eu.siacs.conversations.entities.Account;
 23import eu.siacs.conversations.entities.DownloadableFile;
 24import eu.siacs.conversations.entities.Message;
 25import eu.siacs.conversations.entities.Transferable;
 26import eu.siacs.conversations.persistance.FileBackend;
 27import eu.siacs.conversations.services.AbstractConnectionManager;
 28import eu.siacs.conversations.services.XmppConnectionService;
 29import eu.siacs.conversations.utils.Checksum;
 30import eu.siacs.conversations.utils.CryptoHelper;
 31import eu.siacs.conversations.utils.WakeLockHelper;
 32
 33public class HttpUploadConnection implements Transferable {
 34
 35	static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
 36			"Authorization",
 37			"Cookie",
 38			"Expires"
 39	);
 40
 41	private final HttpConnectionManager mHttpConnectionManager;
 42	private final XmppConnectionService mXmppConnectionService;
 43	private final SlotRequester mSlotRequester;
 44	private final Method method;
 45	private final boolean mUseTor;
 46	private boolean canceled = false;
 47	private boolean delayed = false;
 48	private DownloadableFile file;
 49	private Message message;
 50	private String mime;
 51	private SlotRequester.Slot slot;
 52	private byte[] key = null;
 53
 54	private long transmitted = 0;
 55
 56	public HttpUploadConnection(Method method, HttpConnectionManager httpConnectionManager) {
 57		this.method = method;
 58		this.mHttpConnectionManager = httpConnectionManager;
 59		this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
 60		this.mSlotRequester = new SlotRequester(this.mXmppConnectionService);
 61		this.mUseTor = mXmppConnectionService.useTorToConnect();
 62	}
 63
 64	@Override
 65	public boolean start() {
 66		return false;
 67	}
 68
 69	@Override
 70	public int getStatus() {
 71		return STATUS_UPLOADING;
 72	}
 73
 74	@Override
 75	public long getFileSize() {
 76		return file == null ? 0 : file.getExpectedSize();
 77	}
 78
 79	@Override
 80	public int getProgress() {
 81		if (file == null) {
 82			return 0;
 83		}
 84		return (int) ((((double) transmitted) / file.getExpectedSize()) * 100);
 85	}
 86
 87	@Override
 88	public void cancel() {
 89		this.canceled = true;
 90	}
 91
 92	private void fail(String errorMessage) {
 93		mHttpConnectionManager.finishUploadConnection(this);
 94		message.setTransferable(null);
 95		mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, errorMessage);
 96	}
 97
 98	public void init(Message message, boolean delay) {
 99		this.message = message;
100		final Account account = message.getConversation().getAccount();
101		this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
102		if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
103			this.mime = "application/pgp-encrypted";
104		} else {
105			this.mime = this.file.getMimeType();
106		}
107		this.delayed = delay;
108		if (Config.ENCRYPT_ON_HTTP_UPLOADED
109				|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
110				|| message.getEncryption() == Message.ENCRYPTION_OTR) {
111			this.key = new byte[44];
112			mXmppConnectionService.getRNG().nextBytes(this.key);
113			this.file.setKeyAndIv(this.key);
114		}
115
116		final String md5;
117
118		if (method == Method.P1_S3) {
119			try {
120				md5 = Checksum.md5(AbstractConnectionManager.upgrade(file, new FileInputStream(file)));
121			} catch (Exception e) {
122				Log.d(Config.LOGTAG, account.getJid().asBareJid()+": unable to calculate md5()", e);
123				fail(e.getMessage());
124				return;
125			}
126		} else {
127			md5 = null;
128		}
129
130		this.file.setExpectedSize(file.getSize() + (file.getKey() != null ? 16 : 0));
131		message.resetFileParams();
132		this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() {
133			@Override
134			public void success(SlotRequester.Slot slot) {
135				if (!canceled) {
136					HttpUploadConnection.this.slot = slot;
137					new Thread(HttpUploadConnection.this::upload).start();
138				}
139			}
140
141			@Override
142			public void failure(String message) {
143				fail(message);
144			}
145		});
146		message.setTransferable(this);
147		mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
148	}
149
150	private void upload() {
151		OutputStream os = null;
152		InputStream fileInputStream = null;
153		HttpURLConnection connection = null;
154		PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid());
155		try {
156			fileInputStream = new FileInputStream(file);
157			final int expectedFileSize = (int) file.getExpectedSize();
158			final int readTimeout = (expectedFileSize / 2048) + Config.SOCKET_TIMEOUT; //assuming a minimum transfer speed of 16kbit/s
159			wakeLock.acquire(readTimeout);
160			Log.d(Config.LOGTAG, "uploading to " + slot.getPutUrl().toString()+ " w/ read timeout of "+readTimeout+"s");
161			if (mUseTor || message.getConversation().getAccount().isOnion()) {
162				connection = (HttpURLConnection) slot.getPutUrl().openConnection(HttpConnectionManager.getProxy());
163			} else {
164				connection = (HttpURLConnection) slot.getPutUrl().openConnection();
165			}
166			if (connection instanceof HttpsURLConnection) {
167				mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
168			}
169			connection.setUseCaches(false);
170			connection.setRequestMethod("PUT");
171			connection.setFixedLengthStreamingMode(expectedFileSize);
172			connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
173			if(slot.getHeaders() != null) {
174				for(HashMap.Entry<String,String> entry : slot.getHeaders().entrySet()) {
175					connection.setRequestProperty(entry.getKey(),entry.getValue());
176				}
177			}
178			connection.setDoOutput(true);
179			connection.setDoInput(true);
180			connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
181			connection.setReadTimeout(readTimeout * 1000);
182			connection.connect();
183			final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
184			os = connection.getOutputStream();
185			transmitted = 0;
186			int count;
187			byte[] buffer = new byte[4096];
188			while (((count = innerInputStream.read(buffer)) != -1) && !canceled) {
189				transmitted += count;
190				os.write(buffer, 0, count);
191				mHttpConnectionManager.updateConversationUi(false);
192			}
193			os.flush();
194			os.close();
195			int code = connection.getResponseCode();
196			InputStream is = connection.getErrorStream();
197			if (is != null) {
198				try (Scanner scanner = new Scanner(is)) {
199					scanner.useDelimiter("\\Z");
200					Log.d(Config.LOGTAG, "body: " + scanner.next());
201				}
202			}
203			if (code == 200 || code == 201) {
204				Log.d(Config.LOGTAG, "finished uploading file");
205				final URL get;
206				if (key != null) {
207					if (method == Method.P1_S3) {
208						get = new URL(slot.getGetUrl().toString()+"#"+CryptoHelper.bytesToHex(key));
209					} else {
210						get = CryptoHelper.toAesGcmUrl(new URL(slot.getGetUrl().toString() + "#" + CryptoHelper.bytesToHex(key)));
211					}
212				} else {
213					get = slot.getGetUrl();
214				}
215				mXmppConnectionService.getFileBackend().updateFileParams(message, get);
216				mXmppConnectionService.getFileBackend().updateMediaScanner(file);
217				message.setTransferable(null);
218				message.setCounterpart(message.getConversation().getJid().asBareJid());
219				mXmppConnectionService.resendMessage(message, delayed);
220			} else {
221				Log.d(Config.LOGTAG,"http upload failed because response code was "+code);
222				fail("http upload failed because response code was "+code);
223			}
224		} catch (Exception e) {
225			e.printStackTrace();
226			Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
227			fail(e.getMessage());
228		} finally {
229			FileBackend.close(fileInputStream);
230			FileBackend.close(os);
231			if (connection != null) {
232				connection.disconnect();
233			}
234			WakeLockHelper.release(wakeLock);
235		}
236	}
237}