package de.thedevstack.conversationsplus.http; import android.app.PendingIntent; import android.os.PowerManager; import android.util.Pair; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.Scanner; import javax.net.ssl.HttpsURLConnection; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.StreamUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.services.AbstractConnectionManager; import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredContentException; import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredElementException; import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException; import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUpload; import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadRequestSlotPacketGenerator; import de.thedevstack.conversationsplus.xmpp.httpupload.HttpUploadSlot; import de.thedevstack.conversationsplus.xmpp.httpupload.SlotPacketParser; import de.thedevstack.conversationsplus.xmpp.httpupload.SlotRequestPacket; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; @Deprecated public class HttpUploadConnection implements Transferable { private HttpConnectionManager mHttpConnectionManager; private XmppConnectionService mXmppConnectionService; private boolean canceled = false; private boolean delayed = false; private Account account; private DownloadableFile file; private Message message; private String mime; private URL mGetUrl; private URL mPutUrl; private byte[] key = null; private long transmitted = 0; private InputStream mFileInputStream; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; this.mXmppConnectionService = XmppConnectionServiceAccessor.xmppConnectionService; } @Override public boolean start() { return false; } @Override public int getStatus() { return STATUS_UPLOADING; } @Override public long getFileSize() { return file == null ? 0 : file.getExpectedSize(); } @Override public int getProgress() { if (file == null) { return 0; } return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } @Override public void cancel() { this.canceled = true; } private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); StreamUtil.close(mFileInputStream); } public void init(Message message, boolean delay) { this.message = message; this.message.setHttpUploaded(true); this.message.setNoDownloadable(); this.account = message.getConversation().getAccount(); this.file = FileBackend.getFile(message, false); this.mime = this.file.getMimeType(); this.delayed = delay; if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; ConversationsPlusApplication.getSecureRandom().nextBytes(this.key); this.file.setKeyAndIv(this.key); } Pair pair; try { pair = AbstractConnectionManager.createInputStream(file, true); } catch (FileNotFoundException e) { fail(); return; } this.file.setExpectedSize(pair.second); this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(HttpUpload.NAMESPACE); IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(host, file.getName(), file.getSize(), mime); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { try { HttpUploadSlot slot = SlotPacketParser.parseGetAndPutUrl(packet); mGetUrl = new URL(slot.getGetUrl()); mPutUrl = new URL(slot.getPutUrl()); if (!canceled) { new Thread(new FileUploader()).start(); } } catch (XmppException e) { Logging.e("httpupload", e.getMessage()); fail(); } catch (MalformedURLException e) { Logging.e("httpupload", "malformed url retrieved from slot", e); fail(); } } }); message.setTransferable(this); MessageUtil.markMessage(message, Message.STATUS_UNSEND); } private class FileUploader implements Runnable { @Override public void run() { this.upload(); } private void upload() { OutputStream os = null; InputStream errorStream = null; HttpURLConnection connection = null; PowerManager.WakeLock wakeLock = ConversationsPlusApplication.createPartialWakeLock("http_upload_"+message.getUuid()); try { wakeLock.acquire(); Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); connection = (HttpURLConnection) mPutUrl.openConnection(); if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion()); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); transmitted = 0; int count = -1; byte[] buffer = new byte[4096]; while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; os.write(buffer, 0, count); mXmppConnectionService.updateConversationUi(); } os.flush(); os.close(); mFileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { Logging.d(Config.LOGTAG, "finished uploading file"); if (key != null) { mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); } MessageUtil.updateFileParams(message, mGetUrl); FileBackend.updateMediaScanner(file, mXmppConnectionService); message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { mXmppConnectionService.getPgpEngine().encrypt(message, new UiCallback() { @Override public void success(Message message) { mXmppConnectionService.resendMessage(message,delayed); } @Override public void error(int errorCode, Message object) { fail(); } @Override public void userInputRequried(PendingIntent pi, Message object) { fail(); } }); } else { mXmppConnectionService.resendMessage(message, delayed); } } else { errorStream = connection.getErrorStream(); Logging.e("httpupload", "file upload failed: http code (" + code + ") " + new Scanner(errorStream).useDelimiter("\\A").next()); fail(); } } catch (IOException e) { errorStream = (null != connection) ? connection.getErrorStream() : null; String httpResponseMessage = null; if (null != errorStream) { httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next(); } Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); fail(); } finally { StreamUtil.close(os); StreamUtil.close(errorStream); if (connection != null) { connection.disconnect(); } wakeLock.release(); } } } }