Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 3cc5ca08 authored by Tobias Kaminsky's avatar Tobias Kaminsky Committed by GitHub
Browse files

Merge pull request #166 from desperateCoder/master

Multipart File Uploads via Retrofit
parents 1007810e c536bc32
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -144,7 +144,7 @@ dependencies {

    implementation 'commons-io:commons-io:2.6'

    implementation 'com.squareup.retrofit2:retrofit:2.6.2'
    implementation 'com.squareup.retrofit2:retrofit:2.7.1'
    implementation 'com.squareup.okhttp3:okhttp:3.12.8' // 3.13+ requires Lollipop, but our minSdkVersion is 14

    spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1'
+33 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ package com.nextcloud.android.sso.aidl;

import androidx.core.util.ObjectsCompat;

import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
@@ -41,10 +42,25 @@ public class NextcloudRequest implements Serializable {
    private String token;
    private String packageName;
    private String accountName;
    private transient InputStream bodyAsStream = null;
    private boolean followRedirects;

    private NextcloudRequest() { }

    public NextcloudRequest(NextcloudRequest ncr) {
        this.method = ncr.method;
        this.requestBody = ncr.requestBody;
        this.url = ncr.url;
        this.token = ncr.token;
        this.packageName = ncr.packageName;
        this.accountName = ncr.accountName;
        this.followRedirects = ncr.followRedirects;
        header = new HashMap<>(ncr.header);
        parameter = new HashMap<>(ncr.parameter);
        bodyAsStream = ncr.bodyAsStream;

    }

    public static class Builder implements Serializable {

        private static final long serialVersionUID = 2121321432424242L; //assign a long value
@@ -55,6 +71,10 @@ public class NextcloudRequest implements Serializable {
            ncr = new NextcloudRequest();
        }

        public Builder(Builder cloneSource) {
            ncr = new NextcloudRequest(cloneSource.ncr);
        }

        public NextcloudRequest build() {
            return ncr;
        }
@@ -78,6 +98,10 @@ public class NextcloudRequest implements Serializable {
            ncr.requestBody = requestBody;
            return this;
        }
        public Builder setRequestBodyAsStream(InputStream requestBody) {
            ncr.bodyAsStream = requestBody;
            return this;
        }

        public Builder setUrl(String url) {
            ncr.url = url;
@@ -94,6 +118,8 @@ public class NextcloudRequest implements Serializable {
            return this;
        }



        /**
         * Default value: true
         * @param followRedirects
@@ -153,6 +179,13 @@ public class NextcloudRequest implements Serializable {
        return this.followRedirects;
    }

    public InputStream getBodyAsStream() {
        return bodyAsStream;
    }

    public void setBodyAsStream(InputStream bodyAsStream) {
        this.bodyAsStream = bodyAsStream;
    }

    @Override
    public boolean equals(Object obj) {
+4 −14
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import androidx.annotation.NonNull;

import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.nextcloud.android.sso.Constants;
@@ -31,8 +33,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import androidx.annotation.NonNull;

import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;

public class AidlNetworkRequest extends NetworkRequest {
@@ -219,22 +219,12 @@ public class AidlNetworkRequest extends NetworkRequest {
        InputStream is = new ByteArrayInputStream(baos.toByteArray());

        ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is,
                new IThreadListener() {
                    @Override
                    public void onThreadFinished(Thread thread) {
                        Log.d(TAG, "copy data from service finished");
                    }
                });
                thread -> Log.d(TAG, "copy data from service finished"));

        ParcelFileDescriptor requestBodyParcelFileDescriptor = null;
        if(requestBodyInputStream != null) {
            requestBodyParcelFileDescriptor = ParcelFileDescriptorUtil.pipeFrom(requestBodyInputStream,
                    new IThreadListener() {
                        @Override
                        public void onThreadFinished(Thread thread) {
                            Log.d(TAG, "copy data from service finished");
                        }
                    });
                    thread -> Log.d(TAG, "copy data from service finished"));
        }

        ParcelFileDescriptor output;
+2 −2
Original line number Diff line number Diff line
@@ -116,11 +116,11 @@ public class NextcloudAPI {
     * @throws Exception or SSOException
     */
     public InputStream performNetworkRequest(NextcloudRequest request) throws Exception {
        return networkRequest.performNetworkRequest(request, null);
        return networkRequest.performNetworkRequest(request, request.getBodyAsStream());
    }

    public Response performRequestV2(final @NonNull Type type, NextcloudRequest request) throws Exception {
        Log.d(TAG, "performRequest() called with: type = [" + type + "], request = [" + request + "]");
        Log.d(TAG, "performRequestV2() called with: type = [" + type + "], request = [" + request + "]");

        Response result = null;
        Response response = performNetworkRequestV2(request);
+73 −34
Original line number Diff line number Diff line
@@ -2,18 +2,16 @@ package com.nextcloud.android.sso.api;

import android.util.Log;

import androidx.annotation.Nullable;

import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.helper.Okhttp3Helper;
import com.nextcloud.android.sso.helper.ReactivexHelper;
import com.nextcloud.android.sso.helper.Retrofit2Helper;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
@@ -27,12 +25,14 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import androidx.annotation.Nullable;
import io.reactivex.Completable;
import io.reactivex.Observable;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okio.Buffer;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
@@ -40,9 +40,13 @@ import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.HEAD;
import retrofit2.http.HTTP;
import retrofit2.http.Header;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Part;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.Streaming;
@@ -66,6 +70,8 @@ public class NextcloudRetrofitServiceMethod<T> {
    private Map<String, String> queryParameters;

    private final NextcloudRequest.Builder requestBuilder;
    private boolean isMultipart = false;
    private boolean isFormEncoded = false;


    public NextcloudRetrofitServiceMethod(String apiEndpoint, Method method) {
@@ -100,7 +106,7 @@ public class NextcloudRetrofitServiceMethod<T> {
            throw new InvalidParameterException("Expected: " + parameterAnnotationsArray.length + " params - were: " + args.length);
        }

        NextcloudRequest.Builder rBuilder = cloneSerializable(requestBuilder);
        NextcloudRequest.Builder rBuilder = new NextcloudRequest.Builder(requestBuilder);


        Map<String, String> parameters = new HashMap<>();
@@ -108,10 +114,14 @@ public class NextcloudRetrofitServiceMethod<T> {
        // Copy all static query params into parameters array
        parameters.putAll(this.queryParameters);

        MultipartBody.Builder multipartBuilder = null;
        if (isMultipart) {
            multipartBuilder = new MultipartBody.Builder();
        }

        // Build/parse dynamic parameters
        for(int i = 0; i < parameterAnnotationsArray.length; i++) {
            Annotation annotation = parameterAnnotationsArray[i][0];

            if(annotation instanceof Query) {
                parameters.put(((Query)annotation).value(), String.valueOf(args[i]));
            } else if(annotation instanceof Body) {
@@ -121,13 +131,9 @@ public class NextcloudRetrofitServiceMethod<T> {
                String url = rBuilder.build().getUrl();
                rBuilder.setUrl(url.replace(varName, String.valueOf(args[i])));
            } else if(annotation instanceof Header) {
                Map<String, List<String>> headers = rBuilder.build().getHeader();
                List<String> arg = new ArrayList<>();
                if(args[i] != null) {
                    arg.add(String.valueOf(args[i]));
                    headers.put(((Header) annotation).value(), arg);
                }
                rBuilder.setHeader(headers);
                Object value = args[i];
                String key =((Header) annotation).value();
                addHeader(rBuilder, key, value);
            } else if(annotation instanceof FieldMap) {
                if(args[i] != null) {
                    Map<String, Object> fieldMap = (HashMap<String, Object>) args[i];
@@ -140,11 +146,24 @@ public class NextcloudRetrofitServiceMethod<T> {
                    String field = args[i].toString();
                    parameters.put(((Field)annotation).value(), field);
                }
            } else if(annotation instanceof Part) {
                if (args[i] instanceof MultipartBody.Part){
                    multipartBuilder.addPart((MultipartBody.Part) args[i]);
                } else {
                    throw new IllegalArgumentException("Only MultipartBody.Part type is supported as a @Part");
                }
            } else {
                throw new UnsupportedOperationException("don't know this type yet.. [" + annotation + "]");
            }
        }

        // include multipart body as stream, set header
        if (isMultipart) {
            MultipartBody multipartBody = multipartBuilder.build();
            addHeader(rBuilder, "Content-Type", MultipartBody.FORM+"; boundary="+multipartBody.boundary());
            rBuilder.setRequestBodyAsStream(bodyToStream(multipartBody));
        }

        NextcloudRequest request = rBuilder
                .setParameter(parameters)
                .build();
@@ -176,6 +195,30 @@ public class NextcloudRetrofitServiceMethod<T> {
        return nextcloudAPI.performRequest(this.returnType, request);
    }

    private void addHeader(NextcloudRequest.Builder rBuilder, String key, Object value) {
        if (key == null || value == null) {
            Log.d(TAG, "WARNING: Header not set - key or value missing! Key: " + key + " | Value: " + value);
            return;
        }
        Map<String, List<String>> headers = rBuilder.build().getHeader();
        List<String> arg = new ArrayList<>();
        arg.add(String.valueOf(value));
        headers.put(key, arg);
        rBuilder.setHeader(headers);
    }

    private static InputStream bodyToStream(final RequestBody request){
        try {
            final RequestBody copy = request;
            final Buffer buffer = new Buffer();
            copy.writeTo(buffer);
            return buffer.inputStream();
        }
        catch (final IOException e) {
            throw new IllegalStateException("failed to build request-body", e);
        }
    }

    private void parseMethodAnnotation(Annotation annotation) {
        if (annotation instanceof DELETE) {
            parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
@@ -185,6 +228,21 @@ public class NextcloudRetrofitServiceMethod<T> {
            parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
        } else if (annotation instanceof PUT) {
            parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
        } else if (annotation instanceof HEAD) {
            parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        } else if (annotation instanceof HTTP) {
            HTTP http = (HTTP) annotation;
            parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
        } else if (annotation instanceof Multipart) {
            if (isFormEncoded) {
                throw methodError(method, "Only one encoding annotation is allowed.");
            }
            isMultipart = true;
        } else if (annotation instanceof FormUrlEncoded) {
            if (isMultipart) {
                throw methodError(method, "Only one encoding annotation is allowed.");
            }
            isFormEncoded = true;
        } else if (annotation instanceof Streaming) {
            Log.v(TAG, "streaming interface");
        } else if (annotation instanceof retrofit2.http.Headers) {
@@ -289,23 +347,4 @@ public class NextcloudRetrofitServiceMethod<T> {
                + "."
                + method.getName(), cause);
    }

    private static <T extends Serializable> T cloneSerializable(T o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream( baos );
        oos.writeObject(o);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()) );
        T res  = null;
        try {
            res = (T) ois.readObject();
        } catch (ClassNotFoundException e) {
            // Can't happen as we just clone an object..
            Log.e(TAG, "ClassNotFoundException", e);
        }
        ois.close();

        return res;
    }
}
Loading