aboutsummaryrefslogtreecommitdiff
path: root/src/org/slcl
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-01-07 23:49:03 +0100
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2024-05-17 23:28:51 +0200
commit40f6d425a4429b16936cc8bb4900a23c3362a123 (patch)
treecc60a4a6995604f095952d7c3cda83913c92b61a /src/org/slcl
parenta3bfeb15064ab85900e28f0f3f84b88e99c9a466 (diff)
WIP
Diffstat (limited to 'src/org/slcl')
-rw-r--r--src/org/slcl/Directory.java48
-rw-r--r--src/org/slcl/InternalFile.java38
-rw-r--r--src/org/slcl/LoginActivity.java146
-rw-r--r--src/org/slcl/Main.java93
-rw-r--r--src/org/slcl/core/Cookie.java156
-rw-r--r--src/org/slcl/core/Login.java93
6 files changed, 574 insertions, 0 deletions
diff --git a/src/org/slcl/Directory.java b/src/org/slcl/Directory.java
new file mode 100644
index 0000000..495a3f5
--- /dev/null
+++ b/src/org/slcl/Directory.java
@@ -0,0 +1,48 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl;
+
+import java.net.URLEncoder;
+import java.io.IOException;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class Directory extends Activity {
+ public static final String EXTRA_ID = "cookie";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_directory);
+
+ final Intent intent = getIntent();
+ final String cookie = intent.getStringExtra(EXTRA_ID);
+
+ System.out.println(cookie);
+ }
+}
diff --git a/src/org/slcl/InternalFile.java b/src/org/slcl/InternalFile.java
new file mode 100644
index 0000000..db41576
--- /dev/null
+++ b/src/org/slcl/InternalFile.java
@@ -0,0 +1,38 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl;
+
+import java.io.File;
+import android.content.Context;
+
+final public class InternalFile {
+ final File f;
+
+ InternalFile(final Context c, final String path)
+ {
+ final File dir = c.getFilesDir();
+
+ f = new File(dir, path);
+ }
+
+ public File getFile()
+ {
+ return f;
+ }
+}
diff --git a/src/org/slcl/LoginActivity.java b/src/org/slcl/LoginActivity.java
new file mode 100644
index 0000000..79e2ed4
--- /dev/null
+++ b/src/org/slcl/LoginActivity.java
@@ -0,0 +1,146 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl;
+
+import org.slcl.core.Cookie;
+import org.slcl.core.Login;
+import org.slcl.Directory;
+import org.slcl.InternalFile;
+import java.net.URLEncoder;
+import java.io.IOException;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public final class LoginActivity extends Activity {
+
+ private class LoginParams {
+ public LoginParams(final boolean https, String url,
+ final String username, final String password)
+ {
+ this.https = https;
+ this.username = username;
+ this.url = url;
+ this.password = password;
+ }
+
+ final boolean https;
+ final String url, username, password;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_login);
+
+ Button button = (Button)findViewById(R.id.button);
+
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final EditText url = (EditText)findViewById(R.id.url);
+ final EditText username = (EditText)findViewById(R.id.username);
+ final EditText password = (EditText)findViewById(R.id.password);
+ final CheckBox https = (CheckBox)findViewById(R.id.https);
+
+ url.setText("192.168.1.180:50683");
+ username.setText("a");
+ password.setText("a");
+ https.setChecked(false);
+
+ try {
+ final String urlstr = url.getText().toString();
+ final String encusername = URLEncoder.encode(
+ username.getText().toString(), "UTF-8");
+ final String encpassword = URLEncoder.encode(
+ password.getText().toString(), "UTF-8");
+
+ final LoginTask t = new LoginTask();
+ LoginParams p = new LoginParams(https.isChecked(),
+ urlstr, encusername, encpassword);
+
+ t.execute(p);
+ }
+ catch (final IOException e) {
+ //error.setText("Exception: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ // https://stackoverflow.com/questions/6053602/what-arguments-are-passed-into-asynctaskarg1-arg2-arg3
+ private final class LoginTask
+ extends AsyncTask<LoginParams, String, String>
+ {
+ ProgressDialog dialog;
+
+ @Override
+ protected void onPreExecute()
+ {
+ dialog = dialog.show(LoginActivity.this, "ProgressDialog",
+ "Logging into server");
+ }
+
+ @Override
+ protected String doInBackground(final LoginParams... params)
+ {
+ try {
+ final LoginParams p = params[0];
+ final Login l = new Login();
+ final String cookie = l.login(p.https, p.url, p.username,
+ p.password);
+ final InternalFile f = new InternalFile(
+ getApplicationContext(), "login");
+ final Cookie c = new Cookie(p.username, cookie);
+
+ c.store(f.getFile());
+ return "";
+ } catch (final IOException e) {
+ System.out.println(e.getMessage());
+ return e.getMessage();
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(final String... text)
+ {
+ }
+
+ @Override
+ protected void onPostExecute(final String result)
+ {
+ Intent intent = new Intent(LoginActivity.this, Directory.class);
+
+ dialog.dismiss();
+ startActivity(intent);
+ // TODO: review errors.
+ //error.setText("Exception: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/org/slcl/Main.java b/src/org/slcl/Main.java
new file mode 100644
index 0000000..8ac8c55
--- /dev/null
+++ b/src/org/slcl/Main.java
@@ -0,0 +1,93 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl;
+
+import org.slcl.LoginActivity;
+import org.slcl.Directory;
+import org.slcl.core.Cookie;
+import java.io.IOException;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.View;
+
+public final class Main extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ final LoadTask t = new LoadTask();
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ t.execute();
+ }
+
+ private final class LoadTask extends AsyncTask<String, String, Cookie>
+ {
+ ProgressDialog dialog;
+
+ @Override
+ protected void onPreExecute()
+ {
+ dialog = dialog.show(Main.this, "ProgressDialog",
+ "Retrieving credentials");
+ }
+
+ @Override
+ protected Cookie doInBackground(final String... args)
+ {
+ try {
+ final InternalFile f = new InternalFile(
+ getApplicationContext(), "login");
+ final Cookie c = new Cookie(f.getFile());
+
+ return c;
+ } catch (final IOException e) {
+ System.out.println(e.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(final String... params)
+ {
+ }
+
+ @Override
+ protected void onPostExecute(final Cookie cookie)
+ {
+ final Intent intent;
+
+ if (cookie == null) {
+ intent = new Intent(Main.this, LoginActivity.class);
+ } else {
+ intent = new Intent(Main.this, Directory.class);
+ // TODO: split cookie with ';' (expiration date, etc.).
+ intent.putExtra(Directory.EXTRA_ID, cookie.getCookie());
+ }
+
+ dialog.dismiss();
+ startActivity(intent);
+ }
+ }
+}
diff --git a/src/org/slcl/core/Cookie.java b/src/org/slcl/core/Cookie.java
new file mode 100644
index 0000000..86f64b5
--- /dev/null
+++ b/src/org/slcl/core/Cookie.java
@@ -0,0 +1,156 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl.core;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public final class Cookie {
+ final String username, cookie;
+ final CookieDate date;
+
+ private final class Results {
+ final String username, cookie;
+ final CookieDate date;
+
+ Results(String username, String cookie, final CookieDate date)
+ {
+ this.username = username;
+ this.cookie = cookie;
+ this.date = date;
+ }
+ }
+
+ public Cookie(final File f)
+ throws IOException {
+ final FileInputStream fis = new FileInputStream(f);
+ final InputStreamReader ir = new InputStreamReader(fis);
+ final BufferedReader r = new BufferedReader(ir);
+ final Results res = from_data(null, r.readLine());
+
+ username = res.username;
+ cookie = res.cookie;
+ date = res.date;
+ fis.close();
+ }
+
+ private final class CookieDate {
+ final private String FMT = "EEE, dd MMM yyyy hh:mm:ss z";
+ Date date;
+
+ public CookieDate(final String[] tokens) throws IOException
+ {
+ for (int i = 1; i < tokens.length; i++) {
+ final String[] date_tokens = tokens[i].split("=");
+
+ if (date_tokens.length != 2) {
+ continue;
+ }
+
+ final String header = date_tokens[0].trim();
+
+ if (!header.equalsIgnoreCase("Expires")) {
+ continue;
+ }
+
+ ParsePosition pos = new ParsePosition(0);
+ final SimpleDateFormat fmt = new SimpleDateFormat(FMT);
+ final Date d = fmt.parse(date_tokens[1], pos);
+
+ if (d != null) {
+ // TODO: check pos.
+ date = d;
+ return;
+ }
+ }
+
+ throw new IOException("missing expiration date");
+ }
+
+ public String toString()
+ {
+ final SimpleDateFormat fmt = new SimpleDateFormat(FMT);
+
+ return fmt.format(date, null, null).toString();
+ }
+
+ public Date getDate() {
+ return date;
+ }
+ }
+
+ private Results from_data(final String exp_username, final String data)
+ throws IOException
+ {
+ final int sep = data.indexOf('=');
+
+ if (sep <= 0) {
+ throw new IOException("expected key=value format");
+ }
+
+ final String username = data.substring(0, sep);
+
+ if (exp_username != null
+ && !username.equals(exp_username)) {
+ throw new IOException("wrong username");
+ }
+
+ final String[] tokens = data.substring(sep + 1).split(";");
+
+ if (tokens.length < 2) {
+ throw new IOException("expected at least two tokens");
+ }
+
+ return new Results(username, tokens[0], new CookieDate(tokens));
+ }
+
+ public Cookie(final String exp_username, final String data)
+ throws IOException
+ {
+ final Results r = from_data(exp_username, data);
+
+ username = r.username;
+ cookie = r.cookie;
+ date = r.date;
+ }
+
+ public void store(final File f) throws IOException
+ {
+ final FileOutputStream fos = new FileOutputStream(f);
+ final String datestr = "Expires=" + date.toString();
+
+ fos.write(cookie.getBytes());
+ fos.write(';');
+ fos.write(datestr.getBytes());
+ fos.write('\n');
+ fos.close();
+ }
+
+ public String getCookie() {
+ return cookie;
+ }
+}
diff --git a/src/org/slcl/core/Login.java b/src/org/slcl/core/Login.java
new file mode 100644
index 0000000..d5b9267
--- /dev/null
+++ b/src/org/slcl/core/Login.java
@@ -0,0 +1,93 @@
+/*
+ * slcl-android, an Android frontend for slcl
+ * Copyright (C) 2023-2024 Xavier Del Campo Romero
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.slcl.core;
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.HttpURLConnection;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+
+public final class Login {
+ public String login(final boolean https, String url, final String username,
+ final String password) throws IOException {
+
+ if (!url.startsWith("https://") && https) {
+ url = "https://" + url;
+ }
+ else if (!url.startsWith("http://")) {
+ url = "http://" + url;
+ }
+
+ url += "/login";
+
+ final String data = "username=" + username + "&password=" + password;
+
+ URL u = new URL(url);
+ HttpURLConnection c;
+
+ if (https) {
+ c = (HttpsURLConnection)u.openConnection();
+ }
+ else {
+ c = (HttpURLConnection)u.openConnection();
+ }
+
+ c.setRequestMethod("POST");
+ c.setInstanceFollowRedirects(false);
+ c.setReadTimeout(5000);
+
+ OutputStream os = c.getOutputStream();
+
+ os.write(data.getBytes());
+
+ final int code = c.getResponseCode();
+
+ switch (code)
+ {
+ case HttpURLConnection.HTTP_FORBIDDEN:
+ throw new IOException("Forbidden");
+
+ case HttpURLConnection.HTTP_UNAUTHORIZED:
+ throw new IOException("Unauthorized");
+
+ case HttpURLConnection.HTTP_SEE_OTHER:
+ {
+ final Map<String,List<String>> headers = c.getHeaderFields();
+ final List<String> cookies = headers.get("Set-Cookie");
+
+ if (cookies != null) {
+ if (cookies.size() != 1) {
+ throw new IOException("Expected only 1 cookie");
+ }
+
+ return cookies.toArray(new String[0])[0];
+ } else {
+ throw new IOException("No Cookie found");
+ }
+ }
+
+ default:
+ throw new IOException("Unexpected HTTP status " + code);
+ }
+ }
+}