aboutsummaryrefslogtreecommitdiff
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
parenta3bfeb15064ab85900e28f0f3f84b88e99c9a466 (diff)
WIP
-rw-r--r--.gitignore1
-rw-r--r--AndroidManifest.xml31
-rw-r--r--HelloWorld.java34
-rw-r--r--Makefile48
-rw-r--r--res/layout/activity_directory.xml45
-rw-r--r--res/layout/activity_login.xml93
-rw-r--r--res/layout/activity_main.xml22
-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
13 files changed, 791 insertions, 57 deletions
diff --git a/.gitignore b/.gitignore
index 49a5af1..b7e7bd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
*.unsigned
*.aligned
R.java
+password.secret
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 242f46d..51c6197 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,15 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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/>.
+-->
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="helloworld"
+ package="org.slcl"
versionCode="0"
versionName="0">
- <uses-sdk android:minSdkVersion="19"/>
- <application android:label="Hello World">
- <activity android:name=".HelloWorld">
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-sdk android:minSdkVersion="9"/>
+ <application android:label="slcl" android:debuggable="true">
+ <activity android:name=".Main">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".LoginActivity"/>
+ <activity android:name=".Directory"/>
</application>
</manifest>
diff --git a/HelloWorld.java b/HelloWorld.java
deleted file mode 100644
index 4f93a65..0000000
--- a/HelloWorld.java
+++ /dev/null
@@ -1,34 +0,0 @@
-import helloworld.R;
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-import java.io.OutputStream;
-import java.net.URL;
-import javax.net.ssl.HttpsURLConnection;
-
-public class HelloWorld extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
- try {
- Test t = new Test();
- URL url = new URL("https://slcl.privatedns.org");
- HttpsURLConnection c = (HttpsURLConnection) url.openConnection();
-
- t.f();
-
- c.setFollowRedirects(true);
-
- OutputStream os = c.getOutputStream();
- System.out.println("Response code:" + c.getResponseCode());
- } catch (Exception e) {
- System.out.println("Exception: " + e.getMessage());
- }
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- TextView text = (TextView)findViewById(R.id.my_text);
- text.setText("Hello, world!");
- }
-}
diff --git a/Makefile b/Makefile
index 76f8162..471ca21 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,28 @@
.POSIX:
.SUFFIXES: .apk .unsigned .aligned
-PROJECT = helloworld
+PROJECT = org.slcl
ANDROIDSDK = /usr/lib/android-sdk
DX = $(ANDROIDSDK)/build-tools/debian/dx
MANIFEST = AndroidManifest.xml
PLATFORM = $(ANDROIDSDK)/platforms/android-23/android.jar
OBJECTS = \
- HelloWorld.class \
- Test.class
+ src/org/slcl/core/Cookie.class \
+ src/org/slcl/core/Login.class \
+ src/org/slcl/Directory.class \
+ src/org/slcl/InternalFile.class \
+ src/org/slcl/Main.class
RESOURCES = \
+ res/layout/activity_directory.xml \
res/layout/activity_main.xml
-R = helloworld/R.java
+R = src/org/slcl/R.java
+PASS = password.secret
+DNAME = "cn=slcl, ou=slcl, o=slcl, c=ES"
# Resources:
# https://www.hanshq.net/command-line-android.html
+# https://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string
+# https://stuff.mit.edu/afs/sipb/project/android/docs/guide/topics/resources/layout-resource.html#layoutvalues
all: $(PROJECT).apk
@@ -24,20 +32,23 @@ $(PROJECT).apk: keystore.jks
apksigner sign \
--ks keystore.jks \
--ks-key-alias androidkey \
- --ks-pass pass:android \
- --key-pass pass:android \
+ --ks-pass file:$(PASS) \
--out $@ $<
-keystore.jks:
- keytool \
+keystore.jks: $(PASS)
+ (for i in 1 2; do cat $(PASS); done; echo) \
+ | keytool \
-genkeypair \
+ -dname $(DNAME) \
-keystore $@ \
-alias androidkey \
- -validity 10000 \
-keyalg RSA \
- -keysize 4096 \
- -storepass android \
- -keypass android
+ -keysize 4096
+
+$(PASS):
+ (LC_ALL=C \
+ tr -dc 'A-Za-z0-9!"#$$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' \
+ < /dev/urandom | head -c 100; echo) > $(PASS)
.unsigned.aligned:
zipalign -f -p 4 $< $@
@@ -45,19 +56,20 @@ keystore.jks:
$(PROJECT).unsigned: dex/classes.dex $(MANIFEST)
aapt package -f -F $@ -I $(PLATFORM) -M $(MANIFEST) -S res dex
-dex/classes.dex: $(OBJECTS) $(MANIFEST)
+dex/classes.dex: .build $(MANIFEST)
mkdir -p dex
- $(DX) --dex --output=$@ $(OBJECTS)
+ $(DX) --dex --min-sdk-version=9 --output=$@ src
-$(OBJECTS): $(OBJECTS:.class=.java) $(R)
- javac -bootclasspath $(PLATFORM) -source 1.7 $(OBJECTS:.class=.java)
+.build: $(OBJECTS:.class=.java) $(R)
+ javac -bootclasspath $(PLATFORM) -source 1.7 -sourcepath src $(OBJECTS:.class=.java)
+ touch $@
$(R): $(MANIFEST) $(RESOURCES)
- aapt package -f -m -S res -M $(MANIFEST) -I $(PLATFORM) -J .
+ aapt package -f -m -S res -M $(MANIFEST) -I $(PLATFORM) -J src
clean:
rm -f $(R) $(OBJECTS) $(PROJECT).unsigned \
$(PROJECT).aligned $(PROJECT).apk.idsig dex/classes.dex
distclean: clean
- rm -rf dex/ *.apk keystore.jks
+ rm -rf dex/ *.apk keystore.jks $(PASS)
diff --git a/res/layout/activity_directory.xml b/res/layout/activity_directory.xml
new file mode 100644
index 0000000..cd6db5b
--- /dev/null
+++ b/res/layout/activity_directory.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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/>.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="slcl, a simple and lightweight cloud"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="URL:"/>
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/activity_login.xml b/res/layout/activity_login.xml
new file mode 100644
index 0000000..835a828
--- /dev/null
+++ b/res/layout/activity_login.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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/>.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="slcl, a simple and lightweight cloud"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="URL:"/>
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="HTTPS"
+ android:checked="true"
+ android:id="@+id/https"/>
+
+ <EditText
+ android:layout_width="250sp"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:id="@+id/url">
+ <requestFocus/>
+ </EditText>
+
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Username:"/>
+
+ <EditText
+ android:layout_width="250sp"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:id="@+id/username">
+ </EditText>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password:"/>
+
+ <EditText
+ android:layout_width="250sp"
+ android:layout_height="wrap_content"
+ android:id="@+id/password"
+ android:inputType="textPassword"/>
+
+ <Button android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Login"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/error"/>
+</LinearLayout>
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
index 2f67c50..1f3d7ae 100644
--- a/res/layout/activity_main.xml
+++ b/res/layout/activity_main.xml
@@ -1,4 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ 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/>.
+-->
+
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
@@ -9,5 +28,6 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/my_text"/>
+ android:text="slcl, a simple and lightweight cloud"/>
+
</LinearLayout>
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);
+ }
+ }
+}