Skip to content

Commit

Permalink
Changed: Expose a unix server socket
Browse files Browse the repository at this point in the history
So clients can connect without having to call am if the plugin is
already running.
  • Loading branch information
tareksander authored and Grimler91 committed Dec 23, 2021
1 parent 5ca50e0 commit 4c8296e
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 1 deletion.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
<service
android:name=".SchedulerJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".KeepAliveService"
android:description="@string/keep_alive_service"
android:enabled="true"
android:exported="false" />
</application>

</manifest>
217 changes: 217 additions & 0 deletions app/src/main/java/com/termux/api/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package com.termux.api;

import android.app.Application;
import android.content.Intent;
import android.net.LocalServerSocket;
import android.net.LocalSocket;

import com.termux.api.util.TermuxApiLogger;

import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class App extends Application
{
public static final String LISTEN_ADDRESS = "com.termux.api://listen";
private static final Pattern EXTRA_STRING = Pattern.compile("(-e|--es|--esa) +([^ ]+) +\"(.*?)(?<!\\\\)\"", Pattern.DOTALL);
private static final Pattern EXTRA_BOOLEAN = Pattern.compile("--ez +([^ ]+) +([^ ]+)");
private static final Pattern EXTRA_INT = Pattern.compile("--ei +([^ ]+) +(-?[0-9]+)");
private static final Pattern EXTRA_FLOAT = Pattern.compile("--ef +([^ ]+) +(-?[0-9]+(?:\\.[0-9]+))");
private static final Pattern EXTRA_INT_LIST = Pattern.compile("--eia +([^ ]+) +(-?[0-9]+(?:,-?[0-9]+)*)");
private static final Pattern EXTRA_LONG_LIST = Pattern.compile("--ela +([^ ]+) +(-?[0-9]+(?:,-?[0-9]+)*)");
private static final Pattern EXTRA_UNSUPPORTED = Pattern.compile("--e[^izs ] +[^ ]+ +[^ ]+");


@Override
public void onCreate() {
super.onCreate();
new Thread(() -> {
try (LocalServerSocket listen = new LocalServerSocket(LISTEN_ADDRESS)) {
while (true) {
try (LocalSocket con = listen.accept();
DataInputStream in = new DataInputStream(con.getInputStream());
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(con.getOutputStream()))) {
// only accept connections from Termux programs
if (con.getPeerCredentials().getUid() != getApplicationInfo().uid) {
continue;
}
try {
//System.out.println("connection");
int length = in.readUnsignedShort();
byte[] b = new byte[length];
in.readFully(b);
String cmdline = new String(b, StandardCharsets.UTF_8);

//System.out.println(cmdline.replaceAll("--es socket_input \".*?\"","").replaceAll("--es socket_output \".*?\"",""));
HashMap<String, String> stringExtras = new HashMap<>();
HashMap<String, String[]> stringArrayExtras = new HashMap<>();
HashMap<String, Boolean> booleanExtras = new HashMap<>();
HashMap<String, Integer> intExtras = new HashMap<>();
HashMap<String, Float> floatExtras = new HashMap<>();
HashMap<String, int[]> intArrayExtras = new HashMap<>();
HashMap<String, long[]> longArrayExtras = new HashMap<>();
boolean err = false;

// extract and remove the string extras first, so another argument embedded in a string isn't counted as an argument
Matcher m = EXTRA_STRING.matcher(cmdline);
while (m.find()) {
String option = m.group(1);
if ("-e".equals(option) || "--es".equals(option)) {
// unescape "
stringExtras.put(m.group(2), Objects.requireNonNull(m.group(3)).replaceAll("\\\\\"","\""));
} else {
// split the list
String[] list = Objects.requireNonNull(m.group(3)).split("(?<!\\\\),");
for (int i = 0; i<list.length; i++) {
/// unescape the ","
list[i] = list[i].replaceFirst("\\\\,", ",");
}
stringArrayExtras.put(m.group(2), list);
}

}
cmdline = m.replaceAll("");

m = EXTRA_BOOLEAN.matcher(cmdline);
while (m.find()) {
booleanExtras.put(m.group(1), Boolean.parseBoolean(m.group(2)));
}
cmdline = m.replaceAll("");

m = EXTRA_INT.matcher(cmdline);
while (m.find()) {
try {
intExtras.put(m.group(1), Integer.parseInt(Objects.requireNonNull(m.group(2))));
} catch (NumberFormatException e) {
String msg = "Invalid integer extra: " + m.group(0) + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
break;
}
}
cmdline = m.replaceAll("");

m = EXTRA_FLOAT.matcher(cmdline);
while (m.find()) {
try {
floatExtras.put(m.group(1), Float.parseFloat(Objects.requireNonNull(m.group(2))));
} catch (NumberFormatException e) {
String msg = "Invalid float extra: " + m.group(0) + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
break;
}
}
cmdline = m.replaceAll("");

m = EXTRA_INT_LIST.matcher(cmdline);
while (m.find()) {
try {
String[] parts = Objects.requireNonNull(m.group(2)).split(",");
int[] ints = new int[parts.length];
for (int i = 0; i<parts.length; i++) {
ints[i] = Integer.parseInt(parts[i]);
}
intArrayExtras.put(m.group(1), ints);
} catch (NumberFormatException e) {
String msg = "Invalid int array extra: " + m.group(0) + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
break;
}
}
cmdline = m.replaceAll("");

m = EXTRA_LONG_LIST.matcher(cmdline);
while (m.find()) {
try {
String[] parts = Objects.requireNonNull(m.group(2)).split(",");
long[] longs = new long[parts.length];
for (int i = 0; i<parts.length; i++) {
longs[i] = Long.parseLong(parts[i]);
}
longArrayExtras.put(m.group(1), longs);
} catch (NumberFormatException e) {
String msg = "Invalid long array extra: " + m.group(0) + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
break;
}
}
cmdline = m.replaceAll("");

m = EXTRA_UNSUPPORTED.matcher(cmdline);
if (m.find()) {
String msg = "Unsupported argument type: " + m.group(0) + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
}
cmdline = m.replaceAll("");

// check if there are any non-whitespace characters left after parsing all the options
cmdline = cmdline.replaceAll("\\s", "");
if (! "".equals(cmdline)) {
String msg = "Unsupported options: " + cmdline + "\n";
TermuxApiLogger.info(msg);
out.write(msg);
err = true;
}

if (err) {
out.flush();
continue;
}

// construct the intent with the extras
Intent i = new Intent(getApplicationContext(), TermuxApiReceiver.class);
for (Map.Entry<String, String> e : stringExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, String[]> e : stringArrayExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, Integer> e : intExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, Boolean> e : booleanExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, Float> e : floatExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, int[]> e : intArrayExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
for (Map.Entry<String, long[]> e : longArrayExtras.entrySet()) {
i.putExtra(e.getKey(), e.getValue());
}
getApplicationContext().sendOrderedBroadcast(i, null);
// send a null byte as a sign that the arguments have been successfully received, parsed and the broadcast receiver is called
con.getOutputStream().write(0);
con.getOutputStream().flush();
} catch (Exception e) {
TermuxApiLogger.error("Error parsing arguments", e);
out.write("Exception in the plugin\n");
out.flush();
}
}
}
} catch (Exception e) {
TermuxApiLogger.error("Error listening for connections", e);
}
}).start();
}

}
21 changes: 21 additions & 0 deletions app/src/main/java/com/termux/api/KeepAliveService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.termux.api;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import androidx.annotation.Nullable;

public class KeepAliveService extends Service
{
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_STICKY;
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
<string name="app_name">&TERMUX_API_APP_NAME;</string>
<string name="share_file_chooser_title">Share with</string>
<string name="grant_permission">Grant permission</string>

<string name="keep_alive_service">This service keeps Termux:API running in the background for faster startup of termux-* commands.</string>
<string name="permission_description">This app needs the following permission(s):\n</string>
</resources>

0 comments on commit 4c8296e

Please sign in to comment.