From e48788e821c1c2cdab3647a0f4cce197ea626fe9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 28 Jan 2018 14:17:42 +0100 Subject: [PATCH] support new http upload namespace --- .../conversations/generator/IqGenerator.java | 8 +- .../http/HttpUploadConnection.java | 185 ++++++++++-------- .../eu/siacs/conversations/xml/Namespace.java | 2 +- 3 files changed, 102 insertions(+), 93 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 559442288..fb8813cbb 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -342,11 +342,9 @@ public class IqGenerator extends AbstractGenerator { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(host); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD); - request.addChild("filename").setContent(convertFilename(file.getName())); - request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); - if (mime != null) { - request.addChild("content-type").setContent(mime); - } + request.setAttribute("filename",convertFilename(file.getName())); + request.setAttribute("size",file.getExpectedSize()); + request.setAttribute("content-type",mime); return packet; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index b1352b0ef..e7e612242 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -11,6 +11,7 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; import javax.net.ssl.HttpsURLConnection; @@ -26,7 +27,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -43,6 +43,7 @@ public class HttpUploadConnection implements Transferable { private String mime; private URL mGetUrl; private URL mPutUrl; + private HashMap mPutHeaders; private boolean mUseTor = false; private byte[] key = null; @@ -122,103 +123,113 @@ public class HttpUploadConnection implements Transferable { this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Namespace.HTTP_UPLOAD); IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); - mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element slot = packet.findChild("slot", Namespace.HTTP_UPLOAD); - if (slot != null) { - try { - mGetUrl = new URL(slot.findChildContent("get")); - mPutUrl = new URL(slot.findChildContent("put")); - if (!canceled) { - new Thread(new FileUploader()).start(); + mXmppConnectionService.sendIqPacket(account, request, (account, packet) -> { + if (packet.getType() == IqPacket.TYPE.RESULT) { + Element slot = packet.findChild("slot", Namespace.HTTP_UPLOAD); + if (slot != null) { + try { + final Element put = slot.findChild("put"); + final Element get = slot.findChild("get"); + final String putUrl = put == null ? null : put.getAttribute("url"); + final String getUrl = get == null ? null : get.getAttribute("url"); + if (getUrl != null && putUrl != null) { + this.mGetUrl = new URL(getUrl); + this.mPutUrl = new URL(putUrl); + this.mPutHeaders = new HashMap<>(); + for(Element child : put.getChildren()) { + if ("header".equals(child.getName())) { + String name = child.getAttribute("name"); + String value = child.getContent(); + if (name != null && value != null && !name.trim().contains("\n") && !value.trim().contains("\n")) { + this.mPutHeaders.put(name.trim(),value.trim()); + } + } } - return; - } catch (MalformedURLException e) { - //fall through } + if (!canceled) { + new Thread(this::upload).start(); + } + return; + } catch (MalformedURLException e) { + //fall through } } - Log.d(Config.LOGTAG,account.getJid().toString()+": invalid response to slot request "+packet); - fail(IqParser.extractErrorMessage(packet)); } + Log.d(Config.LOGTAG,account.getJid().toString()+": invalid response to slot request "+packet); + fail(IqParser.extractErrorMessage(packet)); }); message.setTransferable(this); mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); } - private class FileUploader implements Runnable { - - @Override - public void run() { - this.upload(); - } - - private void upload() { - OutputStream os = null; - HttpURLConnection connection = null; - PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); - try { - wakeLock.acquire(); - final int expectedFileSize = (int) file.getExpectedSize(); - final int readTimeout = (expectedFileSize / 2048) + Config.SOCKET_TIMEOUT; //assuming a minimum transfer speed of 16kbit/s - Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()+ " w/ read timeout of "+readTimeout+"s"); - if (mUseTor) { - connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy()); - } else { - connection = (HttpURLConnection) mPutUrl.openConnection(); - } - if (connection instanceof HttpsURLConnection) { - mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); - } - connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode(expectedFileSize); - connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); - connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); - connection.setDoOutput(true); - connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); - connection.setReadTimeout(readTimeout * 1000); - connection.connect(); - os = connection.getOutputStream(); - transmitted = 0; - int count; - byte[] buffer = new byte[4096]; - while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { - transmitted += count; - os.write(buffer, 0, count); - mHttpConnectionManager.updateConversationUi(false); - } - os.flush(); - os.close(); - mFileInputStream.close(); - int code = connection.getResponseCode(); - if (code == 200 || code == 201) { - Log.d(Config.LOGTAG, "finished uploading file"); - if (key != null) { - mGetUrl = CryptoHelper.toAesGcmUrl(new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key))); - } - mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); - mXmppConnectionService.getFileBackend().updateMediaScanner(file); - message.setTransferable(null); - message.setCounterpart(message.getConversation().getJid().toBareJid()); - mXmppConnectionService.resendMessage(message, delayed); - } else { - Log.d(Config.LOGTAG,"http upload failed because response code was "+code); - fail("http upload failed because response code was "+code); - } - } catch (IOException e) { - e.printStackTrace(); - Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); - fail(e.getMessage()); - } finally { - FileBackend.close(mFileInputStream); - FileBackend.close(os); - if (connection != null) { - connection.disconnect(); - } - wakeLock.release(); + private void upload() { + OutputStream os = null; + HttpURLConnection connection = null; + PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid()); + try { + final int expectedFileSize = (int) file.getExpectedSize(); + final int readTimeout = (expectedFileSize / 2048) + Config.SOCKET_TIMEOUT; //assuming a minimum transfer speed of 16kbit/s + wakeLock.acquire(readTimeout); + Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()+ " w/ read timeout of "+readTimeout+"s"); + if (mUseTor) { + connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy()); + } else { + connection = (HttpURLConnection) mPutUrl.openConnection(); } + if (connection instanceof HttpsURLConnection) { + mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); + } + connection.setRequestMethod("PUT"); + connection.setFixedLengthStreamingMode(expectedFileSize); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); + connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName()); + if(mPutHeaders != null) { + for(HashMap.Entry entry : mPutHeaders.entrySet()) { + connection.setRequestProperty(entry.getKey(),entry.getValue()); + } + } + connection.setDoOutput(true); + connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); + connection.setReadTimeout(readTimeout * 1000); + connection.connect(); + os = connection.getOutputStream(); + transmitted = 0; + int count; + byte[] buffer = new byte[4096]; + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { + transmitted += count; + os.write(buffer, 0, count); + mHttpConnectionManager.updateConversationUi(false); + } + os.flush(); + os.close(); + mFileInputStream.close(); + int code = connection.getResponseCode(); + if (code == 200 || code == 201) { + Log.d(Config.LOGTAG, "finished uploading file"); + if (key != null) { + mGetUrl = CryptoHelper.toAesGcmUrl(new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key))); + } + mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); + mXmppConnectionService.getFileBackend().updateMediaScanner(file); + message.setTransferable(null); + message.setCounterpart(message.getConversation().getJid().toBareJid()); + mXmppConnectionService.resendMessage(message, delayed); + } else { + Log.d(Config.LOGTAG,"http upload failed because response code was "+code); + fail("http upload failed because response code was "+code); + } + } catch (IOException e) { + e.printStackTrace(); + Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); + fail(e.getMessage()); + } finally { + FileBackend.close(mFileInputStream); + FileBackend.close(os); + if (connection != null) { + connection.disconnect(); + } + wakeLock.release(); } } } diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 0e8af1717..f1ab61e5d 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -5,7 +5,7 @@ public final class Namespace { public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; - public static final String HTTP_UPLOAD = "urn:xmpp:http:upload"; + public static final String HTTP_UPLOAD = "urn:xmpp:http:upload:0"; public static final String STANZA_IDS = "urn:xmpp:sid:0"; public static final String MAM = "urn:xmpp:mam:2"; public static final String MAM_LEGACY = "urn:xmpp:mam:0";