Update nanohttpd

pull/586/head
FongMi 2 years ago
parent 713180b365
commit ef808b4f14
  1. 2
      app/build.gradle
  2. 3
      app/proguard-rules.pro
  3. 64
      app/src/main/java/com/fongmi/android/tv/server/Nano.java
  4. 81
      app/src/main/java/com/fongmi/android/tv/server/process/Action.java
  5. 27
      app/src/main/java/com/fongmi/android/tv/server/process/Cache.java
  6. 45
      app/src/main/java/com/fongmi/android/tv/server/process/Local.java
  7. 13
      app/src/main/java/com/fongmi/android/tv/server/process/Media.java
  8. 9
      app/src/main/java/com/fongmi/android/tv/server/process/Process.java
  9. 34
      app/src/main/java/com/fongmi/android/tv/server/process/Proxy.java
  10. 1
      catvod/src/main/java/com/github/catvod/utils/Path.java

@ -99,6 +99,7 @@ dependencies {
implementation project(':youtube')
implementation project(':jianpian')
implementation project(':forcetech')
implementation project(':nanohttpd')
pythonImplementation project(':chaquo')
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.media:media:1.7.0'
@ -125,7 +126,6 @@ dependencies {
implementation 'org.fourthline.cling:cling-core:2.1.2-SNAPSHOT'
implementation 'org.fourthline.cling:cling-support:2.1.2-SNAPSHOT'
implementation 'org.greenrobot:eventbus:3.3.1'
implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation('org.simpleframework:simple-xml:2.7.1') { exclude group: 'stax', module: 'stax-api' exclude group: 'xpp3', module: 'xpp3' }
implementation(ext: 'aar', name: 'dlna-core-release', group: 'fongmi', version: 'release')
implementation(ext: 'aar', name: 'dlna-dmc-release', group: 'fongmi', version: 'release')

@ -55,9 +55,6 @@
# Jianpian
-keep class com.p2p.** { *; }
# Nano
-keep class fi.iki.elonen.** { *; }
# QuickJS
-keep class com.fongmi.quickjs.method.** { *; }

@ -1,28 +1,32 @@
package com.fongmi.android.tv.server;
import com.fongmi.android.tv.api.config.LiveConfig;
import com.fongmi.android.tv.api.config.VodConfig;
import com.fongmi.android.tv.bean.Device;
import com.fongmi.android.tv.server.process.Action;
import com.fongmi.android.tv.server.process.Cache;
import com.fongmi.android.tv.server.process.Local;
import com.fongmi.android.tv.server.process.Media;
import com.fongmi.android.tv.server.process.Process;
import com.fongmi.android.tv.server.process.Proxy;
import com.github.catvod.utils.Asset;
import com.github.catvod.utils.Util;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.NanoHTTPD;
import org.nanohttpd.protocols.http.request.Method;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.response.Status;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import fi.iki.elonen.NanoHTTPD;
public class Nano extends NanoHTTPD {
private static final String INDEX = "index.html";
private List<Process> process;
public Nano(int port) {
@ -36,72 +40,52 @@ public class Nano extends NanoHTTPD {
process.add(new Cache());
process.add(new Local());
process.add(new Media());
process.add(new Proxy());
}
public static Response success() {
return success("OK");
public static Response ok() {
return ok("OK");
}
public static Response success(String text) {
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, text);
public static Response ok(String text) {
return Response.newFixedLengthResponse(Status.OK, MIME_PLAINTEXT, text);
}
public static Response error(String text) {
return error(Response.Status.INTERNAL_ERROR, text);
return error(Status.INTERNAL_ERROR, text);
}
public static Response error(Response.IStatus status, String text) {
return newFixedLengthResponse(status, MIME_PLAINTEXT, text);
public static Response error(Status status, String text) {
return Response.newFixedLengthResponse(status, MIME_PLAINTEXT, text);
}
@Override
public Response serve(IHTTPSession session) {
public Response handle(IHTTPSession session) {
String url = session.getUri().trim();
Map<String, String> files = new HashMap<>();
if (session.getMethod() == Method.POST) parse(session, files);
if (url.contains("?")) url = url.substring(0, url.indexOf('?'));
if (url.startsWith("/proxy")) return proxy(session);
if (url.startsWith("/tvbus")) return success(LiveConfig.getResp());
if (url.startsWith("/device")) return success(Device.get().toString());
if (url.startsWith("/license")) return success(new String(Util.decode(url.substring(9), Util.URL_SAFE)));
if (url.startsWith("/tvbus")) return ok(LiveConfig.getResp());
if (url.startsWith("/device")) return ok(Device.get().toString());
if (url.startsWith("/license")) return ok(new String(Util.decode(url.substring(9), Util.URL_SAFE)));
for (Process process : process) if (process.isRequest(session, url)) return process.doResponse(session, url, files);
return getAssets(url.substring(1));
}
private void parse(IHTTPSession session, Map<String, String> files) {
String ct = session.getHeaders().get("content-type");
if (ct != null && ct.toLowerCase().contains("multipart/form-data") && !ct.toLowerCase().contains("charset=")) {
Matcher matcher = Pattern.compile("[ |\t]*(boundary[ |\t]*=[ |\t]*['|\"]?[^\"^'^;^,]*['|\"]?)", Pattern.CASE_INSENSITIVE).matcher(ct);
String boundary = matcher.find() ? matcher.group(1) : null;
if (boundary != null) session.getHeaders().put("content-type", "multipart/form-data; charset=utf-8; " + boundary);
}
try {
session.parseBody(files);
} catch (Exception ignored) {
}
}
private Response proxy(IHTTPSession session) {
try {
Map<String, String> params = session.getParms();
params.putAll(session.getHeaders());
Object[] rs = VodConfig.get().proxyLocal(params);
if (rs[0] instanceof Response) return (Response) rs[0];
Response response = newChunkedResponse(Response.Status.lookup((Integer) rs[0]), (String) rs[1], (InputStream) rs[2]);
if (rs.length > 3 && rs[3] != null) for (Map.Entry<String, String> entry : ((Map<String, String>) rs[3]).entrySet()) response.addHeader(entry.getKey(), entry.getValue());
return response;
} catch (Exception e) {
return error(e.getMessage());
}
}
private Response getAssets(String path) {
try {
if (path.isEmpty()) path = "index.html";
if (path.isEmpty()) path = INDEX;
InputStream is = Asset.open(path);
return newFixedLengthResponse(Response.Status.OK, getMimeTypeForFile(path), is, is.available());
return Response.newFixedLengthResponse(Status.OK, getMimeTypeForFile(path), is, is.available());
} catch (Exception e) {
return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, null);
return Response.newFixedLengthResponse(Status.NOT_FOUND, MIME_HTML, null, 0);
}
}
}

@ -19,54 +19,42 @@ import com.fongmi.android.tv.utils.Notify;
import com.github.catvod.net.OkHttp;
import com.github.catvod.utils.Path;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Response;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import fi.iki.elonen.NanoHTTPD;
import okhttp3.FormBody;
public class Action implements Process {
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
public boolean isRequest(IHTTPSession session, String path) {
return "/action".equals(path);
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path, Map<String, String> files) {
public Response doResponse(IHTTPSession session, String path, Map<String, String> files) {
Map<String, String> params = session.getParms();
switch (Objects.requireNonNullElse(params.get("do"), "")) {
case "search":
onSearch(params);
return Nano.success();
case "push":
onPush(params);
return Nano.success();
case "setting":
onSetting(params);
return Nano.success();
case "file":
onFile(params);
return Nano.success();
case "refresh":
onRefresh(params);
return Nano.success();
case "cast":
onCast(params);
return Nano.success();
case "sync":
onSync(params);
return Nano.success();
default:
return Nano.error(null);
}
String param = params.get("do");
if ("file".equals(param)) onFile(params);
else if ("push".equals(param)) onPush(params);
else if ("cast".equals(param)) onCast(params);
else if ("sync".equals(param)) onSync(params);
else if ("search".equals(param)) onSearch(params);
else if ("setting".equals(param)) onSetting(params);
else if ("refresh".equals(param)) onRefresh(params);
return Nano.ok();
}
private void onSearch(Map<String, String> params) {
String word = params.get("word");
if (TextUtils.isEmpty(word)) return;
ServerEvent.search(word);
private void onFile(Map<String, String> params) {
String path = params.get("path");
if (TextUtils.isEmpty(path)) return;
if (path.endsWith(".apk")) FileUtil.openFile(Path.local(path));
else if (path.endsWith(".srt") || path.endsWith(".ssa") || path.endsWith(".ass")) RefreshEvent.subtitle(path);
else ServerEvent.setting(path);
}
private void onPush(Map<String, String> params) {
@ -75,6 +63,12 @@ public class Action implements Process {
ServerEvent.push(url);
}
private void onSearch(Map<String, String> params) {
String word = params.get("word");
if (TextUtils.isEmpty(word)) return;
ServerEvent.search(word);
}
private void onSetting(Map<String, String> params) {
String text = params.get("text");
String name = params.get("name");
@ -82,29 +76,12 @@ public class Action implements Process {
ServerEvent.setting(text, name);
}
private void onFile(Map<String, String> params) {
String path = params.get("path");
if (TextUtils.isEmpty(path)) return;
if (path.endsWith(".apk")) FileUtil.openFile(Path.local(path));
else if (path.endsWith(".srt") || path.endsWith(".ssa") || path.endsWith(".ass")) RefreshEvent.subtitle(path);
else ServerEvent.setting(path);
}
private void onRefresh(Map<String, String> params) {
String type = params.get("type");
String path = params.get("path");
if (TextUtils.isEmpty(type)) return;
switch (type) {
case "detail":
RefreshEvent.detail();
break;
case "player":
RefreshEvent.player();
break;
case "subtitle":
RefreshEvent.subtitle(path);
break;
}
if ("detail".equals(type)) RefreshEvent.detail();
else if ("player".equals(type)) RefreshEvent.player();
else if ("subtitle".equals(type)) RefreshEvent.subtitle(path);
}
private void onCast(Map<String, String> params) {

@ -5,15 +5,15 @@ import android.text.TextUtils;
import com.fongmi.android.tv.server.Nano;
import com.github.catvod.utils.Prefers;
import java.util.Map;
import java.util.Objects;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Response;
import fi.iki.elonen.NanoHTTPD;
import java.util.Map;
public class Cache implements Process {
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
public boolean isRequest(IHTTPSession session, String path) {
return "/cache".equals(path);
}
@ -22,21 +22,14 @@ public class Cache implements Process {
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path, Map<String, String> files) {
public Response doResponse(IHTTPSession session, String path, Map<String, String> files) {
Map<String, String> params = session.getParms();
String action = params.get("do");
String rule = params.get("rule");
String key = params.get("key");
switch (Objects.requireNonNullElse(params.get("do"), "")) {
case "get":
return Nano.success(Prefers.getString(getKey(rule, key)));
case "set":
Prefers.put(getKey(rule, key), params.get("value"));
return Nano.success();
case "del":
Prefers.remove(getKey(rule, key));
return Nano.success();
default:
return Nano.error(null);
}
if ("get".equals(action)) return Nano.ok(Prefers.getString(getKey(rule, key)));
if ("set".equals(action)) Prefers.put(getKey(rule, key), params.get("value"));
if ("del".equals(action)) Prefers.remove(getKey(rule, key));
return Nano.ok();
}
}

@ -7,6 +7,11 @@ import com.google.common.net.HttpHeaders;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.NanoHTTPD;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.response.Status;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -16,8 +21,6 @@ import java.util.Date;
import java.util.Locale;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
public class Local implements Process {
private final SimpleDateFormat format;
@ -27,12 +30,12 @@ public class Local implements Process {
}
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
public boolean isRequest(IHTTPSession session, String path) {
return path.startsWith("/file") || path.startsWith("/upload") || path.startsWith("/newFolder") || path.startsWith("/delFolder") || path.startsWith("/delFile");
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path, Map<String, String> files) {
public Response doResponse(IHTTPSession session, String path, Map<String, String> files) {
if (path.startsWith("/file")) return getFile(session.getHeaders(), path);
if (path.startsWith("/upload")) return upload(session.getParms(), files);
if (path.startsWith("/newFolder")) return newFolder(session.getParms());
@ -40,7 +43,7 @@ public class Local implements Process {
return null;
}
private NanoHTTPD.Response getFile(Map<String, String> headers, String path) {
private Response getFile(Map<String, String> headers, String path) {
try {
File file = Path.root(path.substring(5));
if (file.isDirectory()) return getFolder(file);
@ -51,7 +54,7 @@ public class Local implements Process {
}
}
private NanoHTTPD.Response upload(Map<String, String> params, Map<String, String> files) {
private Response upload(Map<String, String> params, Map<String, String> files) {
String path = params.get("path");
for (String k : files.keySet()) {
String fn = params.get(k);
@ -59,29 +62,29 @@ public class Local implements Process {
if (fn.toLowerCase().endsWith(".zip")) FileUtil.extractZip(temp, Path.root(path));
else Path.copy(temp, Path.root(path, fn));
}
return Nano.success();
return Nano.ok();
}
private NanoHTTPD.Response newFolder(Map<String, String> params) {
private Response newFolder(Map<String, String> params) {
String path = params.get("path");
String name = params.get("name");
Path.root(path, name).mkdirs();
return Nano.success();
return Nano.ok();
}
private NanoHTTPD.Response delFolder(Map<String, String> params) {
private Response delFolder(Map<String, String> params) {
String path = params.get("path");
Path.clear(Path.root(path));
return Nano.success();
return Nano.ok();
}
private NanoHTTPD.Response getFolder(File root) {
private Response getFolder(File root) {
File[] list = root.listFiles();
JsonObject info = new JsonObject();
info.addProperty("parent", root.equals(Path.root()) ? "." : root.getParent().replace(Path.rootPath(), ""));
if (list == null || list.length == 0) {
info.add("files", new JsonArray());
return Nano.success(info.toString());
return Nano.ok(info.toString());
}
Arrays.sort(list, (o1, o2) -> {
if (o1.isDirectory() && o2.isFile()) return -1;
@ -97,10 +100,10 @@ public class Local implements Process {
obj.addProperty("dir", file.isDirectory() ? 1 : 0);
files.add(obj);
}
return Nano.success(info.toString());
return Nano.ok(info.toString());
}
private NanoHTTPD.Response getFile(Map<String, String> header, File file, String mime) throws Exception {
private Response getFile(Map<String, String> header, File file, String mime) throws Exception {
long startFrom = 0;
long endAt = -1;
String range = header.get("range");
@ -117,7 +120,7 @@ public class Local implements Process {
}
}
}
NanoHTTPD.Response res;
Response res;
long fileLen = file.length();
String ifRange = header.get("if-range");
String etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode());
@ -126,7 +129,7 @@ public class Local implements Process {
boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null && ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag));
if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) {
if (headerIfNoneMatchPresentAndMatching) {
res = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NOT_MODIFIED, mime, "");
res = Response.newFixedLengthResponse(Status.NOT_MODIFIED, mime, "");
res.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
res.addHeader(HttpHeaders.ETAG, etag);
} else {
@ -135,7 +138,7 @@ public class Local implements Process {
if (newLen < 0) newLen = 0;
FileInputStream fis = new FileInputStream(file);
fis.skip(startFrom);
res = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.PARTIAL_CONTENT, mime, fis, newLen);
res = Response.newFixedLengthResponse(Status.PARTIAL_CONTENT, mime, fis, newLen);
res.addHeader(HttpHeaders.CONTENT_LENGTH, newLen + "");
res.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startFrom + "-" + endAt + "/" + fileLen);
res.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
@ -143,16 +146,16 @@ public class Local implements Process {
}
} else {
if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) {
res = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
res = Response.newFixedLengthResponse(Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
res.addHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + fileLen);
res.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
res.addHeader(HttpHeaders.ETAG, etag);
} else if (headerIfNoneMatchPresentAndMatching && (!headerIfRangeMissingOrMatching || range == null)) {
res = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NOT_MODIFIED, mime, "");
res = Response.newFixedLengthResponse(Status.NOT_MODIFIED, mime, "");
res.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
res.addHeader(HttpHeaders.ETAG, etag);
} else {
res = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, mime, new FileInputStream(file), (int) file.length());
res = Response.newFixedLengthResponse(Status.OK, mime, new FileInputStream(file), (int) file.length());
res.addHeader(HttpHeaders.CONTENT_LENGTH, fileLen + "");
res.addHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
res.addHeader(HttpHeaders.ETAG, etag);

@ -9,21 +9,22 @@ import com.fongmi.android.tv.server.Nano;
import com.fongmi.android.tv.server.Server;
import com.google.gson.JsonObject;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Response;
import java.util.Map;
import java.util.Objects;
import fi.iki.elonen.NanoHTTPD;
public class Media implements Process {
@Override
public boolean isRequest(NanoHTTPD.IHTTPSession session, String path) {
public boolean isRequest(IHTTPSession session, String path) {
return "/media".equals(path);
}
@Override
public NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path, Map<String, String> files) {
if (isNull()) return Nano.success("{}");
public Response doResponse(IHTTPSession session, String path, Map<String, String> files) {
if (isNull()) return Nano.ok("{}");
JsonObject result = new JsonObject();
result.addProperty("url", getUrl());
result.addProperty("state", getState());
@ -33,7 +34,7 @@ public class Media implements Process {
result.addProperty("artwork", getArtUri());
result.addProperty("duration", getDuration());
result.addProperty("position", getPosition());
return Nano.success(result.toString());
return Nano.ok(result.toString());
}
private Players getPlayer() {

@ -1,12 +1,13 @@
package com.fongmi.android.tv.server.process;
import java.util.Map;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Response;
import fi.iki.elonen.NanoHTTPD;
import java.util.Map;
public interface Process {
boolean isRequest(NanoHTTPD.IHTTPSession session, String path);
boolean isRequest(IHTTPSession session, String path);
NanoHTTPD.Response doResponse(NanoHTTPD.IHTTPSession session, String path, Map<String, String> files);
Response doResponse(IHTTPSession session, String path, Map<String, String> files);
}

@ -0,0 +1,34 @@
package com.fongmi.android.tv.server.process;
import com.fongmi.android.tv.api.config.VodConfig;
import com.fongmi.android.tv.server.Nano;
import org.nanohttpd.protocols.http.IHTTPSession;
import org.nanohttpd.protocols.http.response.Response;
import org.nanohttpd.protocols.http.response.Status;
import java.io.InputStream;
import java.util.Map;
public class Proxy implements Process {
@Override
public boolean isRequest(IHTTPSession session, String path) {
return "/proxy".equals(path);
}
@Override
public Response doResponse(IHTTPSession session, String path, Map<String, String> files) {
try {
Map<String, String> params = session.getParms();
params.putAll(session.getHeaders());
Object[] rs = VodConfig.get().proxyLocal(params);
if (rs[0] instanceof Response) return (Response) rs[0];
Response response = Response.newChunkedResponse(Status.lookup((Integer) rs[0]), (String) rs[1], (InputStream) rs[2]);
if (rs.length > 3 && rs[3] != null) for (Map.Entry<String, String> entry : ((Map<String, String>) rs[3]).entrySet()) response.addHeader(entry.getKey(), entry.getValue());
return response;
} catch (Exception e) {
return Nano.error(e.getMessage());
}
}
}

@ -223,6 +223,7 @@ public class Path {
public static File create(File file) throws Exception {
try {
if (file.getParentFile() != null) mkdir(file.getParentFile());
if (!file.canWrite()) file.setWritable(true);
if (!file.exists()) file.createNewFile();
Shell.exec("chmod 777 " + file);

Loading…
Cancel
Save