|
|
|
|
@ -1,18 +1,33 @@ |
|
|
|
|
package com.fongmi.android.tv.server; |
|
|
|
|
|
|
|
|
|
import android.os.Environment; |
|
|
|
|
|
|
|
|
|
import com.fongmi.android.tv.R; |
|
|
|
|
import com.fongmi.android.tv.api.ApiConfig; |
|
|
|
|
import com.fongmi.android.tv.server.process.InputRequestProcess; |
|
|
|
|
import com.fongmi.android.tv.server.process.RawRequestProcess; |
|
|
|
|
import com.fongmi.android.tv.server.process.RequestProcess; |
|
|
|
|
import com.fongmi.android.tv.utils.FileUtil; |
|
|
|
|
import com.google.gson.JsonArray; |
|
|
|
|
import com.google.gson.JsonObject; |
|
|
|
|
|
|
|
|
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; |
|
|
|
|
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; |
|
|
|
|
|
|
|
|
|
import java.io.BufferedInputStream; |
|
|
|
|
import java.io.File; |
|
|
|
|
import java.io.FileInputStream; |
|
|
|
|
import java.io.FileNotFoundException; |
|
|
|
|
import java.io.FileOutputStream; |
|
|
|
|
import java.io.InputStream; |
|
|
|
|
import java.io.OutputStream; |
|
|
|
|
import java.text.SimpleDateFormat; |
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.Arrays; |
|
|
|
|
import java.util.Date; |
|
|
|
|
import java.util.HashMap; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Locale; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.regex.Matcher; |
|
|
|
|
import java.util.regex.Pattern; |
|
|
|
|
@ -22,18 +37,20 @@ import fi.iki.elonen.NanoHTTPD; |
|
|
|
|
public class Nano extends NanoHTTPD { |
|
|
|
|
|
|
|
|
|
private List<RequestProcess> processes; |
|
|
|
|
private final SimpleDateFormat format; |
|
|
|
|
private Listener listener; |
|
|
|
|
|
|
|
|
|
public Nano(int port) { |
|
|
|
|
super(port); |
|
|
|
|
addRequestProcess(); |
|
|
|
|
format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void addRequestProcess() { |
|
|
|
|
processes = new ArrayList<>(); |
|
|
|
|
processes.add(new InputRequestProcess(this)); |
|
|
|
|
processes.add(new RawRequestProcess("/", R.raw.index, NanoHTTPD.MIME_HTML)); |
|
|
|
|
processes.add(new RawRequestProcess("/index.html", R.raw.index, NanoHTTPD.MIME_HTML)); |
|
|
|
|
processes.add(new RawRequestProcess("/", R.raw.index, MIME_HTML)); |
|
|
|
|
processes.add(new RawRequestProcess("/index.html", R.raw.index, MIME_HTML)); |
|
|
|
|
processes.add(new RawRequestProcess("/ui.css", R.raw.ui, "text/css")); |
|
|
|
|
processes.add(new RawRequestProcess("/style.css", R.raw.style, "text/css")); |
|
|
|
|
processes.add(new RawRequestProcess("/script.js", R.raw.script, "application/x-javascript")); |
|
|
|
|
@ -51,55 +68,159 @@ public class Nano extends NanoHTTPD { |
|
|
|
|
@Override |
|
|
|
|
public Response serve(IHTTPSession session) { |
|
|
|
|
String url = session.getUri().trim(); |
|
|
|
|
Map<String, String> files = new HashMap<>(); |
|
|
|
|
if (url.contains("?")) url = url.substring(0, url.indexOf('?')); |
|
|
|
|
if (session.getMethod() == Method.POST) parseBody(session); |
|
|
|
|
if (session.getMethod() == Method.POST) parseBody(session, files); |
|
|
|
|
for (RequestProcess process : processes) { |
|
|
|
|
if (process.isRequest(session, url)) { |
|
|
|
|
return process.doResponse(session, url); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (session.getMethod() == Method.GET) { |
|
|
|
|
if (url.equals("/proxy")) { |
|
|
|
|
Map<String, String> params = session.getParms(); |
|
|
|
|
if (params.containsKey("do")) { |
|
|
|
|
Object[] rs = ApiConfig.get().proxyLocal(params); |
|
|
|
|
try { |
|
|
|
|
int code = (int) rs[0]; |
|
|
|
|
String mime = (String) rs[1]; |
|
|
|
|
InputStream stream = rs[2] != null ? (InputStream) rs[2] : null; |
|
|
|
|
return NanoHTTPD.newChunkedResponse(Response.Status.lookup(code), mime, stream); |
|
|
|
|
} catch (Exception e) { |
|
|
|
|
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if (url.startsWith("/file")) { |
|
|
|
|
try { |
|
|
|
|
return NanoHTTPD.newChunkedResponse(Response.Status.OK, "application/octet-stream", new FileInputStream(FileUtil.getLocal(url.substring(1)))); |
|
|
|
|
} catch (FileNotFoundException e) { |
|
|
|
|
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, e.getMessage()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
switch (session.getMethod()) { |
|
|
|
|
case GET: |
|
|
|
|
if (url.startsWith("/file")) return doFile(url); |
|
|
|
|
else if (url.startsWith("/proxy")) return doProxy(session.getParms()); |
|
|
|
|
break; |
|
|
|
|
case POST: |
|
|
|
|
if (url.startsWith("/upload")) return doUpload(session.getParms(), files); |
|
|
|
|
else if (url.startsWith("/newFolder")) return doNewFolder(session.getParms()); |
|
|
|
|
else if (url.startsWith("/delFolder") || url.startsWith("/delFile")) return doDelFolder(session.getParms()); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
return processes.get(0).doResponse(session, ""); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void parseBody(IHTTPSession session) { |
|
|
|
|
Map<String, String> files = new HashMap<>(); |
|
|
|
|
private void parseBody(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 { |
|
|
|
|
String hd = session.getHeaders().get("content-type"); |
|
|
|
|
if (hd == null) return; |
|
|
|
|
if (hd.toLowerCase().contains("multipart/form-data") && !hd.toLowerCase().contains("charset=")) { |
|
|
|
|
Matcher matcher = Pattern.compile("[ |\t]*(boundary[ |\t]*=[ |\t]*['|\"]?[^\"^'^;^,]*['|\"]?)", Pattern.CASE_INSENSITIVE).matcher(hd); |
|
|
|
|
String boundary = matcher.find() ? matcher.group(1) : null; |
|
|
|
|
if (boundary != null) session.getHeaders().put("content-type", "multipart/form-data; charset=utf-8; " + boundary); |
|
|
|
|
} |
|
|
|
|
session.parseBody(files); |
|
|
|
|
} catch (Exception ignored) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Response doFile(String url) { |
|
|
|
|
try { |
|
|
|
|
String path = url.substring(6); |
|
|
|
|
File file = FileUtil.getRootFile(path); |
|
|
|
|
if (file.isFile()) return newChunkedResponse(Response.Status.OK, "application/octet-stream", new FileInputStream(file)); |
|
|
|
|
else return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, listFiles(file)); |
|
|
|
|
} catch (FileNotFoundException e) { |
|
|
|
|
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Response doProxy(Map<String, String> params) { |
|
|
|
|
try { |
|
|
|
|
Object[] rs = ApiConfig.get().proxyLocal(params); |
|
|
|
|
return newChunkedResponse(Response.Status.lookup((Integer) rs[0]), (String) rs[1], (InputStream) rs[2]); |
|
|
|
|
} catch (Exception e) { |
|
|
|
|
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "500"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Response doUpload(Map<String, String> params, Map<String, String> files) { |
|
|
|
|
String path = params.get("path"); |
|
|
|
|
for (String k : files.keySet()) { |
|
|
|
|
if (k.startsWith("files-")) { |
|
|
|
|
String fn = params.get(k); |
|
|
|
|
String tmpFile = files.get(k); |
|
|
|
|
File tmp = new File(tmpFile); |
|
|
|
|
String root = Environment.getExternalStorageDirectory().getAbsolutePath(); |
|
|
|
|
File file = new File(root + "/" + path + "/" + fn); |
|
|
|
|
if (file.exists()) |
|
|
|
|
file.delete(); |
|
|
|
|
if (tmp.exists()) { |
|
|
|
|
if (fn.toLowerCase().endsWith(".zip")) { |
|
|
|
|
try { |
|
|
|
|
unzip(tmp, root + "/" + path); |
|
|
|
|
} catch (Exception e) { |
|
|
|
|
e.printStackTrace(); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
FileUtil.copy(tmp, file); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (tmp.exists()) |
|
|
|
|
tmp.delete(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Response doNewFolder(Map<String, String> params) { |
|
|
|
|
String path = params.get("path"); |
|
|
|
|
String name = params.get("name"); |
|
|
|
|
FileUtil.getRootFile(path + File.separator + name).mkdirs(); |
|
|
|
|
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Response doDelFolder(Map<String, String> params) { |
|
|
|
|
String path = params.get("path"); |
|
|
|
|
FileUtil.clearDir(FileUtil.getRootFile(path)); |
|
|
|
|
return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private String getParent(File root) { |
|
|
|
|
if (root.getAbsolutePath().equals(FileUtil.getRootPath())) return "."; |
|
|
|
|
return root.getParentFile().getAbsolutePath().replace(FileUtil.getRootPath() + File.separator, "").replace(FileUtil.getRootPath(), ""); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private String listFiles(File root) { |
|
|
|
|
File[] list = root.listFiles(); |
|
|
|
|
String parent = getParent(root); |
|
|
|
|
JsonObject info = new JsonObject(); |
|
|
|
|
info.addProperty("parent", parent); |
|
|
|
|
if (list == null || list.length == 0) { |
|
|
|
|
info.add("files", new JsonArray()); |
|
|
|
|
return info.toString(); |
|
|
|
|
} |
|
|
|
|
Arrays.sort(list, (o1, o2) -> { |
|
|
|
|
if (o1.isDirectory() && o2.isFile()) return -1; |
|
|
|
|
return o1.isFile() && o2.isDirectory() ? 1 : o1.getName().compareTo(o2.getName()); |
|
|
|
|
}); |
|
|
|
|
JsonArray files = new JsonArray(); |
|
|
|
|
info.add("files", files); |
|
|
|
|
for (File file : list) { |
|
|
|
|
JsonObject obj = new JsonObject(); |
|
|
|
|
obj.addProperty("name", file.getName()); |
|
|
|
|
obj.addProperty("path", file.getAbsolutePath().replace(FileUtil.getRootPath() + File.separator, "")); |
|
|
|
|
obj.addProperty("time", format.format(new Date(file.lastModified()))); |
|
|
|
|
obj.addProperty("dir", file.isDirectory() ? 1 : 0); |
|
|
|
|
files.add(obj); |
|
|
|
|
} |
|
|
|
|
return info.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void unzip(File file, String path) throws Exception { |
|
|
|
|
try (ZipArchiveInputStream is = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(file)))) { |
|
|
|
|
ZipArchiveEntry entry; |
|
|
|
|
while ((entry = is.getNextZipEntry()) != null) { |
|
|
|
|
if (entry.isDirectory()) { |
|
|
|
|
new File(path, entry.getName()).mkdirs(); |
|
|
|
|
} else { |
|
|
|
|
extractFile(is, path + File.separator + entry.getName()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void extractFile(InputStream is, String path) { |
|
|
|
|
try (OutputStream out = new FileOutputStream(path)) { |
|
|
|
|
int len; |
|
|
|
|
byte[] buf = new byte[2048]; |
|
|
|
|
while ((len = is.read(buf)) > 0) out.write(buf, 0, len); |
|
|
|
|
} catch (Exception e) { |
|
|
|
|
e.printStackTrace(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static Response createPlainTextResponse(Response.IStatus status, String text) { |
|
|
|
|
return newFixedLengthResponse(status, NanoHTTPD.MIME_PLAINTEXT, text); |
|
|
|
|
return newFixedLengthResponse(status, MIME_PLAINTEXT, text); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public interface Listener { |
|
|
|
|
|