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

Commit a30b9887 authored by Michael Enoma's avatar Michael Enoma 👽
Browse files

Server Resonse / Notes CLient

parent bc62277a
Loading
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
@@ -41,6 +41,66 @@ import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;


import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.SslErrorHandler;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import foundation.e.cert4android.CustomCertManager;
import foundation.e.cert4android.IOnCertificateDecision;
import butterknife.BindView;
import butterknife.ButterKnife;
import foundation.e.notes.R;
import foundation.e.notes.persistence.NoteSQLiteOpenHelper;
import foundation.e.notes.persistence.NoteServerSyncHelper;
import foundation.e.notes.util.ExceptionHandler;
import foundation.e.notes.util.NotesClientUtil;
import foundation.e.notes.util.NotesClientUtil.LoginStatus;

import androidx.annotation.ColorInt;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;

import java.io.ByteArrayInputStream;
import java.net.URLDecoder;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import static android.os.Process.killProcess;
import static android.os.Process.myPid;

public class ImportAccountActivity extends AppCompatActivity {


@@ -111,6 +171,12 @@ public class ImportAccountActivity extends AppCompatActivity {
            }
        });

        binding.addButton1.setOnClickListener((v) -> {
            binding.addButton1.setEnabled(false);
            binding.status.setVisibility(View.GONE);

        }


    }

+189 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.shared.util;

import androidx.annotation.WorkerThread;
import android.util.Base64;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;

import foundation.e.cert4android.CustomCertManager;
import foundation.e.notes.model.CloudNote;
import foundation.e.notes.BuildConfig;
import foundation.e.notes.model.CloudNote;
import foundation.e.notes.util.ServerResponse.NoteResponse;
import foundation.e.notes.util.ServerResponse.NotesResponse;

@WorkerThread
public class NotesClient {

    /**
     * This entity class is used to return relevant data of the HTTP reponse.
     */
    public static class ResponseData {
        private final String content;
        private final String etag;
        private final long lastModified;

        public ResponseData(String content, String etag, long lastModified) {
            this.content = content;
            this.etag = etag;
            this.lastModified = lastModified;
        }

        public String getContent() {
            return content;
        }

        public String getETag() {
            return etag;
        }

        public long getLastModified() {
            return lastModified;
        }
    }

    public static final String METHOD_GET = "GET";
    public static final String METHOD_PUT = "PUT";
    public static final String METHOD_POST = "POST";
    public static final String METHOD_DELETE = "DELETE";
    public static final String JSON_ID = "id";
    public static final String JSON_TITLE = "title";
    public static final String JSON_CONTENT = "content";
    public static final String JSON_FAVORITE = "favorite";
    public static final String JSON_CATEGORY = "category";
    public static final String JSON_ETAG = "etag";
    public static final String JSON_MODIFIED = "modified";
    private static final String application_json = "application/json";
    private String url = "";
    private String username = "";
    private String password = "";

    public NotesClient(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    public NotesResponse getNotes(CustomCertManager ccm, long lastModified, String lastETag) throws JSONException, IOException {
        String url = "notes";
        if (lastModified > 0) {
            url += "?pruneBefore=" + lastModified;
        }
        return new NotesResponse(requestServer(ccm, url, METHOD_GET, null, lastETag));
    }

    /**
     * Fetches a Note by ID from Server
     *
     * @param id long - ID of the wanted note
     * @return Requested Note
     * @throws JSONException
     * @throws IOException
     */
    @SuppressWarnings("unused")
    public NoteResponse getNoteById(CustomCertManager ccm, long id) throws JSONException, IOException {
        return new NoteResponse(requestServer(ccm, "notes/" + id, METHOD_GET, null, null));
    }

    private NoteResponse putNote(CustomCertManager ccm, CloudNote note, String path, String method) throws JSONException, IOException {
        JSONObject paramObject = new JSONObject();
        paramObject.accumulate(JSON_CONTENT, note.getContent());
        paramObject.accumulate(JSON_MODIFIED, note.getModified().getTimeInMillis() / 1000);
        paramObject.accumulate(JSON_FAVORITE, note.isFavorite());
        paramObject.accumulate(JSON_CATEGORY, note.getCategory());
        return new NoteResponse(requestServer(ccm, path, method, paramObject, null));
    }


    /**
     * Creates a Note on the Server
     *
     * @param note {@link CloudNote} - the new Note
     * @return Created Note including generated Title, ID and lastModified-Date
     * @throws JSONException
     * @throws IOException
     */
    public NoteResponse createNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException {
        return putNote(ccm, note, "notes", METHOD_POST);
    }

    public NoteResponse editNote(CustomCertManager ccm, CloudNote note) throws JSONException, IOException {
        return putNote(ccm, note, "notes/" + note.getRemoteId(), METHOD_PUT);
    }

    public void deleteNote(CustomCertManager ccm, long noteId) throws IOException {
        this.requestServer(ccm, "notes/" + noteId, METHOD_DELETE, null, null);
    }

    /**
     * Request-Method for POST, PUT with or without JSON-Object-Parameter
     *
     * @param target Filepath to the wanted function
     * @param method GET, POST, DELETE or PUT
     * @param params JSON Object which shall be transferred to the server.
     * @return Body of answer
     * @throws MalformedURLException
     * @throws IOException
     */
    private ResponseData requestServer(CustomCertManager ccm, String target, String method, JSONObject params, String lastETag)
            throws IOException {
        StringBuffer result = new StringBuffer();
        // setup connection
        String targetURL = url + "index.php/apps/notes/api/v0.2/" + target;
        HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL);
        con.setRequestMethod(method);
        con.setRequestProperty(
                "Authorization",
                "Basic " + Base64.encodeToString((username + ":" + password).getBytes(), Base64.NO_WRAP));
        // https://github.com/square/retrofit/issues/805#issuecomment-93426183
        con.setRequestProperty( "Connection", "Close");
        con.setRequestProperty("User-Agent", "nextcloud-notes/" + BuildConfig.VERSION_NAME + " (Android)");
        if (lastETag != null && METHOD_GET.equals(method)) {
            con.setRequestProperty("If-None-Match", lastETag);
        }
        con.setConnectTimeout(10 * 1000); // 10 seconds
        Log.d(getClass().getSimpleName(), method + " " + targetURL);
        // send request data (optional)
        byte[] paramData = null;
        if (params != null) {
            paramData = params.toString().getBytes();
            Log.d(getClass().getSimpleName(), "Params: " + params);
            con.setFixedLengthStreamingMode(paramData.length);
            con.setRequestProperty("Content-Type", application_json);
            con.setDoOutput(true);
            OutputStream os = con.getOutputStream();
            os.write(paramData);
            os.flush();
            os.close();
        }
        // read response data
        int responseCode = con.getResponseCode();
        Log.d(getClass().getSimpleName(), "HTTP response code: " + responseCode);

        if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
            throw new ServerResponse.NotModifiedException();
        }

        BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String line;
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }
        // create response object
        String etag = con.getHeaderField("ETag");
        long lastModified = con.getHeaderFieldDate("Last-Modified", 0) / 1000;
        Log.i(getClass().getSimpleName(), "Result length:  " + result.length() + (paramData == null ? "" : "; Request length: " + paramData.length));
        Log.d(getClass().getSimpleName(), "ETag: " + etag + "; Last-Modified: " + lastModified + " (" + con.getHeaderField("Last-Modified") + ")");
        // return these header fields since they should only be saved after successful processing the result!
        return new ResponseData(result.toString(), etag, lastModified);
    }
}
+149 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.shared.util;


import androidx.annotation.StringRes;
import android.util.Base64;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;

import foundation.e.cert4android.CustomCertManager;
import foundation.e.notes.R;

/**
 * Utils for Validation etc
 * Created by stefan on 25.09.15.
 */
public class NotesClientUtil {

    public enum LoginStatus {
        OK(0),
        AUTH_FAILED(R.string.error_username_password_invalid),
        CONNECTION_FAILED(R.string.error_io),
        NO_NETWORK(R.string.error_no_network),
        JSON_FAILED(R.string.error_json),
        SERVER_FAILED(R.string.error_server);

        @StringRes
        public final int str;

        LoginStatus(@StringRes int str) {
            this.str = str;
        }
    }

    /**
     * Checks if the given url String starts with http:// or https://
     *
     * @param url String
     * @return true, if the given String is only http
     */
    public static boolean isHttp(String url) {
        return url != null && url.length() > 4 && url.startsWith("http") && url.charAt(4) != 's';
    }

    /**
     * Strips the api part from the path of a given url, handles trailing slash and missing protocol
     *
     * @param url String
     * @return formatted URL
     */
    public static String formatURL(String url) {
        if (!url.endsWith("/")) {
            url += "/";
        }
        if (!url.startsWith("http://") && !url.startsWith("https://")) {
            url = "https://" + url;
        }
        String[] replacements = new String[]{"notes/", "v0.2/", "api/", "notes/", "apps/", "index.php/"};
        for (String replacement : replacements) {
            if (url.endsWith(replacement)) {
                url = url.substring(0, url.length() - replacement.length());
            }
        }
        return url;
    }

    /**
     * @param url      String
     * @param username String
     * @param password String
     * @return Username and Password are a valid Login-Combination for the given URL.
     */
    public static LoginStatus isValidLogin(CustomCertManager ccm, String url, String username, String password) {
        try {
            String targetURL = url + "index.php/apps/notes/api/v0.2/notes";
            HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, targetURL);
            con.setRequestMethod("GET");
            con.setRequestProperty(
                    "Authorization",
                    "Basic "
                            + new String(Base64.encode((username + ":"
                            + password).getBytes(), Base64.NO_WRAP)));
            con.setConnectTimeout(10 * 1000); // 10 seconds
            con.connect();

            Log.v(NotesClientUtil.class.getSimpleName(), "Establishing connection to server");
            if (con.getResponseCode() == 200) {
                Log.v(NotesClientUtil.class.getSimpleName(), "" + con.getResponseMessage());
                StringBuilder result = new StringBuilder();
                BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
                String line;
                while ((line = rd.readLine()) != null) {
                    result.append(line);
                }
                Log.v(NotesClientUtil.class.getSimpleName(), result.toString());
                new JSONArray(result.toString());
                return LoginStatus.OK;
            } else if (con.getResponseCode() >= 401 && con.getResponseCode() <= 403) {
                return LoginStatus.AUTH_FAILED;
            } else {
                return LoginStatus.SERVER_FAILED;
            }
        } catch (MalformedURLException | SocketTimeoutException  e) {
            Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e);
            return LoginStatus.CONNECTION_FAILED;
        } catch (IOException e) {
            Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e);
            return LoginStatus.CONNECTION_FAILED;
        } catch (JSONException e) {
            Log.e(NotesClientUtil.class.getSimpleName(), "Exception", e);
            return LoginStatus.JSON_FAILED;
        }
    }

    /**
     * Pings a server and checks if there is a installed ownCloud instance
     *
     * @param url String URL to server
     * @return true if there is a installed instance, false if not
     */
    public static boolean isValidURL(CustomCertManager ccm, String url) {
        StringBuilder result = new StringBuilder();
        try {
            HttpURLConnection con = SupportUtil.getHttpURLConnection(ccm, url + "status.php");
            con.setRequestMethod(NotesClient.METHOD_GET);
            con.setConnectTimeout(10 * 1000); // 10 seconds
            BufferedReader rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String line;
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
            JSONObject response = new JSONObject(result.toString());
            return response.getBoolean("installed");
        } catch (IOException | JSONException | NullPointerException e) {
            return false;
        }
    }

}
+101 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.shared.util;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import foundation.e.notes.model.CloudNote;
import foundation.e.notes.model.CloudNote;

/**
 * Provides entity classes for handling server responses with a single note ({@link NoteResponse}) or a list of notes ({@link NotesResponse}).
 */
public class ServerResponse {

    public static class NotModifiedException extends IOException {
    }

    public static class NoteResponse extends ServerResponse {
        public NoteResponse(NotesClient.ResponseData response) {
            super(response);
        }

        public CloudNote getNote() throws JSONException {
            return getNoteFromJSON(new JSONObject(getContent()));
        }
    }

    public static class NotesResponse extends ServerResponse {
        public NotesResponse(NotesClient.ResponseData response) {
            super(response);
        }

        public List<CloudNote> getNotes() throws JSONException {
            List<CloudNote> notesList = new ArrayList<>();
            JSONArray notes = new JSONArray(getContent());
            for (int i = 0; i < notes.length(); i++) {
                JSONObject json = notes.getJSONObject(i);
                notesList.add(getNoteFromJSON(json));
            }
            return notesList;
        }
    }


    private final NotesClient.ResponseData response;

    public ServerResponse(NotesClient.ResponseData response) {
        this.response = response;
    }

    protected String getContent() {
        return response.getContent();
    }

    public String getETag() {
        return response.getETag();
    }

    public long getLastModified() {
        return response.getLastModified();
    }

    protected CloudNote getNoteFromJSON(JSONObject json) throws JSONException {
        long id = 0;
        String title = "";
        String content = "";
        Calendar modified = null;
        boolean favorite = false;
        String category = null;
        String etag = null;
        if (!json.isNull(NotesClient.JSON_ID)) {
            id = json.getLong(NotesClient.JSON_ID);
        }
        if (!json.isNull(NotesClient.JSON_TITLE)) {
            title = json.getString(NotesClient.JSON_TITLE);
        }
        if (!json.isNull(NotesClient.JSON_CONTENT)) {
            content = json.getString(NotesClient.JSON_CONTENT);
        }
        if (!json.isNull(NotesClient.JSON_MODIFIED)) {
            modified = GregorianCalendar.getInstance();
            modified.setTimeInMillis(json.getLong(NotesClient.JSON_MODIFIED) * 1000);
        }
        if (!json.isNull(NotesClient.JSON_FAVORITE)) {
            favorite = json.getBoolean(NotesClient.JSON_FAVORITE);
        }
        if (!json.isNull(NotesClient.JSON_CATEGORY)) {
            category = json.getString(NotesClient.JSON_CATEGORY);
        }
        if (!json.isNull(NotesClient.JSON_ETAG)) {
            etag = json.getString(NotesClient.JSON_ETAG);
        }
        return new CloudNote(id, modified, title, content, favorite, category, etag);
    }
}