diff --git a/app/build.gradle b/app/build.gradle index ef7bf465a..4aaaca261 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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') diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 757064042..fd9b17367 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -55,9 +55,6 @@ # Jianpian -keep class com.p2p.** { *; } -# Nano --keep class fi.iki.elonen.** { *; } - # QuickJS -keep class com.fongmi.quickjs.method.** { *; } diff --git a/app/src/main/java/com/fongmi/android/tv/server/Nano.java b/app/src/main/java/com/fongmi/android/tv/server/Nano.java index 63f4a003d..0700b58b3 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/Nano.java +++ b/app/src/main/java/com/fongmi/android/tv/server/Nano.java @@ -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; 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 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 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 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 entry : ((Map) 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); } } } diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Action.java b/app/src/main/java/com/fongmi/android/tv/server/process/Action.java index 4322bae5f..59feec795 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Action.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Action.java @@ -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 files) { + public Response doResponse(IHTTPSession session, String path, Map files) { Map 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 params) { - String word = params.get("word"); - if (TextUtils.isEmpty(word)) return; - ServerEvent.search(word); + private void onFile(Map 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 params) { @@ -75,6 +63,12 @@ public class Action implements Process { ServerEvent.push(url); } + private void onSearch(Map params) { + String word = params.get("word"); + if (TextUtils.isEmpty(word)) return; + ServerEvent.search(word); + } + private void onSetting(Map 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 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 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 params) { diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Cache.java b/app/src/main/java/com/fongmi/android/tv/server/process/Cache.java index 24e044425..da3589deb 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Cache.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Cache.java @@ -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 files) { + public Response doResponse(IHTTPSession session, String path, Map files) { Map 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(); } } diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Local.java b/app/src/main/java/com/fongmi/android/tv/server/process/Local.java index 24ffd72f2..7fb7ba1df 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Local.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Local.java @@ -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 files) { + public Response doResponse(IHTTPSession session, String path, Map 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 headers, String path) { + private Response getFile(Map 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 params, Map files) { + private Response upload(Map params, Map 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 params) { + private Response newFolder(Map 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 params) { + private Response delFolder(Map 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 header, File file, String mime) throws Exception { + private Response getFile(Map 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); diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Media.java b/app/src/main/java/com/fongmi/android/tv/server/process/Media.java index b85a5c958..0d16318b8 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Media.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Media.java @@ -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 files) { - if (isNull()) return Nano.success("{}"); + public Response doResponse(IHTTPSession session, String path, Map 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() { diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Process.java b/app/src/main/java/com/fongmi/android/tv/server/process/Process.java index 5c94d5ab4..e90159fdd 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Process.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Process.java @@ -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 files); + Response doResponse(IHTTPSession session, String path, Map files); } diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Proxy.java b/app/src/main/java/com/fongmi/android/tv/server/process/Proxy.java new file mode 100644 index 000000000..ca904bceb --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Proxy.java @@ -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 files) { + try { + Map 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 entry : ((Map) rs[3]).entrySet()) response.addHeader(entry.getKey(), entry.getValue()); + return response; + } catch (Exception e) { + return Nano.error(e.getMessage()); + } + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Path.java b/catvod/src/main/java/com/github/catvod/utils/Path.java index 598670169..295118abb 100644 --- a/catvod/src/main/java/com/github/catvod/utils/Path.java +++ b/catvod/src/main/java/com/github/catvod/utils/Path.java @@ -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);