diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2024-01-07 23:49:03 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2024-05-17 23:28:51 +0200 |
| commit | 40f6d425a4429b16936cc8bb4900a23c3362a123 (patch) | |
| tree | cc60a4a6995604f095952d7c3cda83913c92b61a /src/org/slcl | |
| parent | a3bfeb15064ab85900e28f0f3f84b88e99c9a466 (diff) | |
WIP
Diffstat (limited to 'src/org/slcl')
| -rw-r--r-- | src/org/slcl/Directory.java | 48 | ||||
| -rw-r--r-- | src/org/slcl/InternalFile.java | 38 | ||||
| -rw-r--r-- | src/org/slcl/LoginActivity.java | 146 | ||||
| -rw-r--r-- | src/org/slcl/Main.java | 93 | ||||
| -rw-r--r-- | src/org/slcl/core/Cookie.java | 156 | ||||
| -rw-r--r-- | src/org/slcl/core/Login.java | 93 |
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); + } + } +} |
