diff --git a/AndroidManifest.xml b/AndroidManifest.xml index cf9d9cc813..b06617ec3d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -96,8 +96,7 @@ tools:replace="android:allowBackup" android:allowBackup="false" android:theme="@style/TextSecure.LightTheme" - android:largeHeap="true" - android:networkSecurityConfig="@xml/local_audio_server_config"> + android:largeHeap="true"> - - - 127.0.0.1 - - diff --git a/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java b/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java deleted file mode 100644 index e12ba08c54..0000000000 --- a/src/org/thoughtcrime/securesms/attachments/AttachmentServer.java +++ /dev/null @@ -1,370 +0,0 @@ -package org.thoughtcrime.securesms.attachments; - - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.util.Hex; -import org.thoughtcrime.securesms.util.Util; - -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.security.MessageDigest; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.StringTokenizer; - -/** - * @author Stefan "frostymarvelous" Froelich - */ -public class AttachmentServer implements Runnable { - - private static final String TAG = AttachmentServer.class.getSimpleName(); - - private final Context context; - private final Attachment attachment; - private final ServerSocket socket; - private final int port; - private final String auth; - - private volatile boolean isRunning; - - public AttachmentServer(Context context, Attachment attachment) - throws IOException - { - try { - this.context = context; - this.attachment = attachment; - this.socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1})); - this.port = socket.getLocalPort(); - this.auth = Hex.toStringCondensed(Util.getSecretBytes(16)); - - this.socket.setSoTimeout(5000); - } catch (UnknownHostException e) { - throw new AssertionError(e); - } - } - - public Uri getUri() { - return Uri.parse(String.format(Locale.ROOT, "http://127.0.0.1:%d/%s", port, auth)); - } - - public void start() { - isRunning = true; - new Thread(this).start(); - } - - public void stop() { - isRunning = false; - } - - @Override - public void run() { - while (isRunning) { - Socket client = null; - - try { - client = socket.accept(); - - if (client != null) { - StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client, "/" + auth); - - if (task.processRequest()) { - task.execute(); - } - } - - } catch (SocketTimeoutException e) { - Log.w(TAG, e); - } catch (IOException e) { - Log.e(TAG, "Error connecting to client", e); - } finally { - try {if (client != null) client.close();} catch (IOException e) {} - } - } - - Log.d(TAG, "Proxy interrupted. Shutting down."); - } - - - private class StreamToMediaPlayerTask { - - private final @NonNull Socket client; - private final @NonNull String auth; - - private long cbSkip; - private Properties parameters; - private Properties request; - private Properties requestHeaders; -// private String filePath; - - public StreamToMediaPlayerTask(@NonNull Socket client, @NonNull String auth) { - this.client = client; - this.auth = auth; - } - - public boolean processRequest() throws IOException { - InputStream is = client.getInputStream(); - final int bufferSize = 8192; - byte[] buffer = new byte[bufferSize]; - int splitByte = 0; - int readLength = 0; - - { - int read = is.read(buffer, 0, bufferSize); - while (read > 0) { - readLength += read; - splitByte = findHeaderEnd(buffer, readLength); - if (splitByte > 0) - break; - read = is.read(buffer, readLength, bufferSize - readLength); - } - } - - // Create a BufferedReader for parsing the header. - ByteArrayInputStream hbis = new ByteArrayInputStream(buffer, 0, readLength); - BufferedReader hin = new BufferedReader(new InputStreamReader(hbis)); - - request = new Properties(); - parameters = new Properties(); - requestHeaders = new Properties(); - - try { - decodeHeader(hin, request, parameters, requestHeaders); - } catch (InterruptedException e1) { - Log.e(TAG, "Exception: " + e1.getMessage()); - e1.printStackTrace(); - } - - for (Map.Entry e : requestHeaders.entrySet()) { - Log.i(TAG, "Header: " + e.getKey() + " : " + e.getValue()); - } - - String range = requestHeaders.getProperty("range"); - - if (range != null) { - Log.i(TAG, "range is: " + range); - range = range.substring(6); - int charPos = range.indexOf('-'); - if (charPos > 0) { - range = range.substring(0, charPos); - } - cbSkip = Long.parseLong(range); - Log.i(TAG, "range found!! " + cbSkip); - } - - if (!"GET".equals(request.get("method"))) { - Log.e(TAG, "Only GET is supported: " + request.get("method")); - return false; - } - - String receivedAuth = request.getProperty("uri"); - - if (receivedAuth == null || !MessageDigest.isEqual(receivedAuth.getBytes(), auth.getBytes())) { - Log.w(TAG, "Bad auth token!"); - return false; - } - -// filePath = request.getProperty("uri"); - - return true; - } - - protected void execute() throws IOException { - InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri()); - long fileSize = attachment.getSize(); - - String headers = ""; - if (cbSkip > 0) {// It is a seek or skip request if there's a Range - // header - headers += "HTTP/1.1 206 Partial Content\r\n"; - headers += "Content-Type: " + attachment.getContentType() + "\r\n"; - headers += "Accept-Ranges: bytes\r\n"; - headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n"; - headers += "Content-Range: bytes " + cbSkip + "-" + (fileSize - 1) + "/" + fileSize + "\r\n"; - headers += "Connection: Keep-Alive\r\n"; - headers += "\r\n"; - } else { - headers += "HTTP/1.1 200 OK\r\n"; - headers += "Content-Type: " + attachment.getContentType() + "\r\n"; - headers += "Accept-Ranges: bytes\r\n"; - headers += "Content-Length: " + fileSize + "\r\n"; - headers += "Connection: Keep-Alive\r\n"; - headers += "\r\n"; - } - - Log.i(TAG, "headers: " + headers); - - OutputStream output = null; - byte[] buff = new byte[64 * 1024]; - try { - output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024); - output.write(headers.getBytes()); - - inputStream.skip(cbSkip); -// dataSource.skipFully(data, cbSkip);//try to skip as much as possible - - // Loop as long as there's stuff to send and client has not closed - int cbRead; - while (!client.isClosed() && (cbRead = inputStream.read(buff, 0, buff.length)) != -1) { - output.write(buff, 0, cbRead); - } - } - catch (SocketException socketException) { - Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly"); - } - catch (Exception e) { - Log.e(TAG, "Exception thrown from streaming task:"); - Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); - } - - // Cleanup - try { - if (output != null) { - output.close(); - } - client.close(); - } - catch (IOException e) { - Log.e(TAG, "IOException while cleaning up streaming task:"); - Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); - e.printStackTrace(); - } - } - - /** - * Find byte index separating header from body. It must be the last byte of - * the first two sequential new lines. - **/ - private int findHeaderEnd(final byte[] buf, int rlen) { - int splitbyte = 0; - while (splitbyte + 3 < rlen) { - if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' - && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') - return splitbyte + 4; - splitbyte++; - } - return 0; - } - - - /** - * Decodes the sent headers and loads the data into java Properties' key - - * value pairs - **/ - private void decodeHeader(BufferedReader in, Properties pre, - Properties parms, Properties header) throws InterruptedException { - try { - // Read the request line - String inLine = in.readLine(); - if (inLine == null) - return; - StringTokenizer st = new StringTokenizer(inLine); - if (!st.hasMoreTokens()) - Log.e(TAG, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); - - String method = st.nextToken(); - pre.put("method", method); - - if (!st.hasMoreTokens()) - Log.e(TAG, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); - - String uri = st.nextToken(); - - // Decode parameters from the URI - int qmi = uri.indexOf('?'); - if (qmi >= 0) { - decodeParms(uri.substring(qmi + 1), parms); - uri = decodePercent(uri.substring(0, qmi)); - } else - uri = decodePercent(uri); - - // If there's another token, it's protocol version, - // followed by HTTP headers. Ignore version but parse headers. - // NOTE: this now forces header names lowercase since they are - // case insensitive and vary by client. - if (st.hasMoreTokens()) { - String line = in.readLine(); - while (line != null && line.trim().length() > 0) { - int p = line.indexOf(':'); - if (p >= 0) - header.put(line.substring(0, p).trim().toLowerCase(), - line.substring(p + 1).trim()); - line = in.readLine(); - } - } - - pre.put("uri", uri); - } catch (IOException ioe) { - Log.e(TAG, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); - } - } - - /** - * Decodes parameters in percent-encoded URI-format ( e.g. - * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given - * Properties. NOTE: this doesn't support multiple identical keys due to the - * simplicity of Properties -- if you need multiples, you might want to - * replace the Properties with a Hashtable of Vectors or such. - */ - private void decodeParms(String parms, Properties p) - throws InterruptedException { - if (parms == null) - return; - - StringTokenizer st = new StringTokenizer(parms, "&"); - while (st.hasMoreTokens()) { - String e = st.nextToken(); - int sep = e.indexOf('='); - if (sep >= 0) - p.put(decodePercent(e.substring(0, sep)).trim(), - decodePercent(e.substring(sep + 1))); - } - } - - /** - * Decodes the percent encoding scheme.
- * For example: "an+example%20string" -> "an example string" - */ - private String decodePercent(String str) throws InterruptedException { - try { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - switch (c) { - case '+': - sb.append(' '); - break; - case '%': - sb.append((char) Integer.parseInt( - str.substring(i + 1, i + 3), 16)); - i += 2; - break; - default: - sb.append(c); - break; - } - } - return sb.toString(); - } catch (Exception e) { - Log.e(TAG, "BAD REQUEST: Bad percent-encoding."); - return null; - } - } - } -} \ No newline at end of file