X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Flibrary%2FWebLibraryServer.java;h=22f36e91f5729a7bfef72fe9c98d2a7fa157a6ec;hp=b4a6e4bb25f3776c819a43e9235f58b5c63b7788;hb=cab46763df87b1aacf51a9c064a3f5fc49642df9;hpb=34718cf9dc943306279713aec140a197fb27629a diff --git a/src/be/nikiroo/fanfix/library/WebLibraryServer.java b/src/be/nikiroo/fanfix/library/WebLibraryServer.java index b4a6e4b..22f36e9 100644 --- a/src/be/nikiroo/fanfix/library/WebLibraryServer.java +++ b/src/be/nikiroo/fanfix/library/WebLibraryServer.java @@ -1,156 +1,79 @@ package be.nikiroo.fanfix.library; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.security.KeyStore; +import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLServerSocketFactory; - import org.json.JSONArray; import org.json.JSONObject; import be.nikiroo.fanfix.Instance; import be.nikiroo.fanfix.bundles.Config; -import be.nikiroo.fanfix.bundles.UiConfig; import be.nikiroo.fanfix.data.Chapter; import be.nikiroo.fanfix.data.JsonIO; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Paragraph; import be.nikiroo.fanfix.data.Paragraph.ParagraphType; import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.web.WebLibraryServerIndex; -import be.nikiroo.fanfix.reader.TextOutput; -import be.nikiroo.utils.CookieUtils; -import be.nikiroo.utils.IOUtils; import be.nikiroo.utils.Image; +import be.nikiroo.utils.LoginResult; import be.nikiroo.utils.NanoHTTPD; -import be.nikiroo.utils.NanoHTTPD.IHTTPSession; import be.nikiroo.utils.NanoHTTPD.Response; import be.nikiroo.utils.NanoHTTPD.Response.Status; -import be.nikiroo.utils.TraceHandler; -import be.nikiroo.utils.Version; - -public class WebLibraryServer implements Runnable { - static private String VIEWER_URL_BASE = "/view/story/"; - static private String VIEWER_URL = VIEWER_URL_BASE + "{luid}/{chap}/{para}"; - static private String STORY_URL_BASE = "/story/"; - static private String STORY_URL = STORY_URL_BASE + "{luid}/{chap}/{para}"; - static private String STORY_URL_COVER = STORY_URL_BASE + "{luid}/cover"; - static private String LIST_URL = "/list/"; - - private class LoginResult { - private boolean success; - private boolean rw; - private boolean wl; - private String wookie; - private String token; - private boolean badLogin; - private boolean badToken; - - public LoginResult(String who, String key, String subkey, - boolean success, boolean rw, boolean wl) { - this.success = success; - this.rw = rw; - this.wl = wl; - this.wookie = CookieUtils.generateCookie(who + key, 0); - - String opts = ""; - if (rw) - opts += "|rw"; - if (!wl) - opts += "|wl"; - - this.token = wookie + "~" - + CookieUtils.generateCookie(wookie + subkey + opts, 0) - + "~" + opts; - this.badLogin = !success; - } - - public LoginResult(String token, String who, String key, - List subkeys) { - - if (token != null) { - String hashes[] = token.split("~"); - if (hashes.length >= 2) { - String wookie = hashes[0]; - String rehashed = hashes[1]; - String opts = hashes.length > 2 ? hashes[2] : ""; - - if (CookieUtils.validateCookie(who + key, wookie)) { - if (subkeys == null) { - subkeys = new ArrayList(); - } - subkeys = new ArrayList(subkeys); - subkeys.add(""); - - for (String subkey : subkeys) { - if (CookieUtils.validateCookie( - wookie + subkey + opts, rehashed)) { - this.wookie = wookie; - this.token = token; - this.success = true; - - this.rw = opts.contains("|rw"); - this.wl = !opts.contains("|wl"); - } - } - } - } +import be.nikiroo.utils.Progress; - this.badToken = !success; - } +public class WebLibraryServer extends WebLibraryServerHtml { + class WLoginResult extends LoginResult { + public WLoginResult(boolean badLogin, boolean badCookie) { + super(badLogin, badCookie); + } - // No token -> no bad token + public WLoginResult(String who, String key, String subkey, boolean rw, + boolean wl, boolean bl) { + super(who, key, subkey, (rw ? "|rw" : "") + (wl ? "|wl" : "") + + (bl ? "|bl" : "") + "|"); } - public boolean isSuccess() { - return success; + public WLoginResult(String cookie, String who, String key, + List subkeys) { + super(cookie, who, key, subkeys, + subkeys == null || subkeys.isEmpty()); } public boolean isRw() { - return rw; + return getOption().contains("|rw|"); } public boolean isWl() { - return wl; - } - - public String getToken() { - return token; + return getOption().contains("|wl|"); } - public boolean isBadLogin() { - return badLogin; - } - - public boolean isBadToken() { - return badToken; + public boolean isBl() { + return getOption().contains("|bl|"); } } - private NanoHTTPD server; private Map storyCache = new HashMap(); private LinkedList storyCacheOrder = new LinkedList(); private long storyCacheSize = 0; private long maxStoryCacheSize; - private TraceHandler tracer = new TraceHandler(); + + private List whitelist; + private List blacklist; + + private Map imprts = new HashMap(); + + private boolean exiting; public WebLibraryServer(boolean secure) throws IOException { - Integer port = Instance.getInstance().getConfig() - .getInteger(Config.SERVER_PORT); - if (port == null) { - throw new IOException( - "Cannot start web server: port not specified"); - } + super(secure); int cacheMb = Instance.getInstance().getConfig() .getInteger(Config.SERVER_MAX_CACHE_MB, 100); @@ -158,196 +81,10 @@ public class WebLibraryServer implements Runnable { setTraceHandler(Instance.getInstance().getTraceHandler()); - SSLServerSocketFactory ssf = null; - if (secure) { - String keystorePath = Instance.getInstance().getConfig() - .getString(Config.SERVER_SSL_KEYSTORE, ""); - String keystorePass = Instance.getInstance().getConfig() - .getString(Config.SERVER_SSL_KEYSTORE_PASS); - - if (secure && keystorePath.isEmpty()) { - throw new IOException( - "Cannot start a secure web server: no keystore.jks file povided"); - } - - if (!keystorePath.isEmpty()) { - File keystoreFile = new File(keystorePath); - try { - KeyStore keystore = KeyStore - .getInstance(KeyStore.getDefaultType()); - InputStream keystoreStream = new FileInputStream( - keystoreFile); - try { - keystore.load(keystoreStream, - keystorePass.toCharArray()); - KeyManagerFactory keyManagerFactory = KeyManagerFactory - .getInstance(KeyManagerFactory - .getDefaultAlgorithm()); - keyManagerFactory.init(keystore, - keystorePass.toCharArray()); - ssf = NanoHTTPD.makeSSLSocketFactory(keystore, - keyManagerFactory); - } finally { - keystoreStream.close(); - } - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - } - } - - server = new NanoHTTPD(port) { - @Override - public Response serve(final IHTTPSession session) { - super.serve(session); - - String query = session.getQueryParameterString(); // a=a%20b&dd=2 - Method method = session.getMethod(); // GET, POST.. - String uri = session.getUri(); // /home.html - - // need them in real time (not just those sent by the UA) - Map cookies = new HashMap(); - for (String cookie : session.getCookies()) { - cookies.put(cookie, session.getCookies().read(cookie)); - } - - List whitelist = Instance.getInstance().getConfig() - .getList(Config.SERVER_WHITELIST); - if (whitelist == null) { - whitelist = new ArrayList(); - } - - LoginResult login = null; - Map params = session.getParms(); - String who = session.getRemoteHostName() - + session.getRemoteIpAddress(); - if (params.get("login") != null) { - login = login(who, params.get("password"), - params.get("login"), whitelist); - } else { - String token = cookies.get("token"); - login = login(who, token, Instance.getInstance().getConfig() - .getList(Config.SERVER_ALLOWED_SUBKEYS)); - } - - if (login.isSuccess()) { - if (!login.isWl()) { - whitelist.clear(); - } - - // refresh token - session.getCookies().set(new Cookie("token", - login.getToken(), "30; path=/")); - - // set options - String optionName = params.get("optionName"); - if (optionName != null && !optionName.isEmpty()) { - String optionNo = params.get("optionNo"); - String optionValue = params.get("optionValue"); - if (optionNo != null || optionValue == null - || optionValue.isEmpty()) { - session.getCookies().delete(optionName); - cookies.remove(optionName); - } else { - session.getCookies().set(new Cookie(optionName, - optionValue, "; path=/")); - cookies.put(optionName, optionValue); - } - } - } - - Response rep = null; - if (!login.isSuccess() && (uri.equals("/") // - || uri.startsWith(STORY_URL_BASE) // - || uri.startsWith(VIEWER_URL_BASE) // - || uri.startsWith(LIST_URL))) { - rep = loginPage(login, uri); - } - - if (rep == null) { - try { - if (uri.equals("/")) { - rep = root(session, cookies, whitelist); - } else if (uri.startsWith(LIST_URL)) { - rep = getList(uri, whitelist); - } else if (uri.startsWith(STORY_URL_BASE)) { - rep = getStoryPart(uri, whitelist); - } else if (uri.startsWith(VIEWER_URL_BASE)) { - rep = getViewer(cookies, uri, whitelist); - } else if (uri.equals("/logout")) { - session.getCookies().delete("token"); - cookies.remove("token"); - rep = loginPage(login, uri); - } else { - if (uri.startsWith("/")) - uri = uri.substring(1); - InputStream in = IOUtils.openResource( - WebLibraryServerIndex.class, uri); - if (in != null) { - String mimeType = MIME_PLAINTEXT; - if (uri.endsWith(".css")) { - mimeType = "text/css"; - } else if (uri.endsWith(".html")) { - mimeType = "text/html"; - } else if (uri.endsWith(".js")) { - mimeType = "text/javascript"; - } - rep = newChunkedResponse(Status.OK, mimeType, - in); - } else { - getTraceHandler().trace("404: " + uri); - } - } - - if (rep == null) { - rep = newFixedLengthResponse(Status.NOT_FOUND, - NanoHTTPD.MIME_PLAINTEXT, "Not Found"); - } - } catch (Exception e) { - Instance.getInstance().getTraceHandler().error( - new IOException("Cannot process web request", - e)); - rep = newFixedLengthResponse(Status.INTERNAL_ERROR, - NanoHTTPD.MIME_PLAINTEXT, "An error occured"); - } - } - - return rep; - - // Get status: for story, use "luid" + active map of current - // luids - // map must use a addRef/removeRef and delete at 0 - - // http://localhost:2000/?token=ok - - // - // MetaData meta = new MetaData(); - // meta.setTitle("Title"); - // meta.setLuid("000"); - // - // JSONObject json = new JSONObject(); - // json.put("", MetaData.class.getName()); - // json.put("title", meta.getTitle()); - // json.put("luid", meta.getLuid()); - // - // return newFixedLengthResponse(json.toString()); - } - }; - - if (ssf != null) { - getTraceHandler().trace("Install SSL on the web server..."); - server.makeSecure(ssf, null); - getTraceHandler().trace("Done."); - } - } - - @Override - public void run() { - try { - server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); - } catch (IOException e) { - tracer.error(new IOException("Cannot start the web server", e)); - } + whitelist = Instance.getInstance().getConfig() + .getList(Config.SERVER_WHITELIST, new ArrayList()); + blacklist = Instance.getInstance().getConfig() + .getList(Config.SERVER_BLACKLIST, new ArrayList()); } /** @@ -362,116 +99,121 @@ public class WebLibraryServer implements Runnable { new Thread(this).start(); } - /** - * The traces handler for this {@link WebLibraryServer}. - * - * @return the traces handler - */ - public TraceHandler getTraceHandler() { - return tracer; - } + @Override + protected Response stop(WLoginResult login) { + if (!login.isRw()) { + return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, + NanoHTTPD.MIME_PLAINTEXT, "Exit not allowed"); + } - /** - * The traces handler for this {@link WebLibraryServer}. - * - * @param tracer - * the new traces handler - */ - public void setTraceHandler(TraceHandler tracer) { - if (tracer == null) { - tracer = new TraceHandler(false, false, false); + if (exiting) { + return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, + NanoHTTPD.MIME_PLAINTEXT, "Server is already exiting..."); } - this.tracer = tracer; + exiting = true; + Instance.getInstance().getTraceHandler().trace("Exiting"); + + boolean ok; + do { + synchronized (imprts) { + ok = imprts.isEmpty(); + } + if (!ok) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Instance.getInstance().getTraceHandler() + .trace("Waiting to exit..."); + } + } + } while (!ok); + + doStop(); + + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + } + + Instance.getInstance().getTraceHandler() + .trace("Exit timeout: force-quit"); + System.exit(0); + } + }, "Exit program after timeout of 1500 ms").start(); + + return NanoHTTPD.newFixedLengthResponse(Status.OK, + NanoHTTPD.MIME_PLAINTEXT, "Exited"); + } + + @Override + protected WLoginResult login(boolean badLogin, boolean badCookie) { + return new WLoginResult(false, false); } - private LoginResult login(String who, String token, List subkeys) { + @Override + protected WLoginResult login(String who, String cookie) { + List subkeys = Instance.getInstance().getConfig() + .getList(Config.SERVER_ALLOWED_SUBKEYS); String realKey = Instance.getInstance().getConfig() .getString(Config.SERVER_KEY); - realKey = realKey == null ? "" : realKey; - return new LoginResult(token, who, realKey, subkeys); + + return new WLoginResult(cookie, who, realKey, subkeys); } // allow rw/wl - private LoginResult login(String who, String key, String subkey, - List whitelist) { + @Override + protected WLoginResult login(String who, String key, String subkey) { String realKey = Instance.getInstance().getConfig() - .getString(Config.SERVER_KEY); + .getString(Config.SERVER_KEY, ""); // I don't like NULLs... - realKey = realKey == null ? "" : realKey; key = key == null ? "" : key; subkey = subkey == null ? "" : subkey; if (!realKey.equals(key)) { - return new LoginResult(null, null, null, false, false, false); + return new WLoginResult(true, false); } - // defaults are positive (as previous versions without the feature) + // defaults are true (as previous versions without the feature) boolean rw = true; boolean wl = true; - - if (whitelist.isEmpty()) { - wl = false; - } + boolean bl = true; rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW, rw); - if (!subkey.isEmpty()) { - List allowed = Instance.getInstance().getConfig() - .getList(Config.SERVER_ALLOWED_SUBKEYS); - if (allowed != null && allowed.contains(subkey)) { - if ((subkey + "|").contains("|rw|")) { - rw = true; - } - if ((subkey + "|").contains("|wl|")) { - wl = false; // |wl| = bypass whitelist - } - } else { - return new LoginResult(null, null, null, false, false, false); - } - } - - return new LoginResult(who, key, subkey, true, rw, wl); - } - private Response loginPage(LoginResult login, String uri) { - StringBuilder builder = new StringBuilder(); + List allowed = Instance.getInstance().getConfig().getList( + Config.SERVER_ALLOWED_SUBKEYS, new ArrayList()); - appendPreHtml(builder, true); - - if (login.isBadLogin()) { - builder.append("
Bad login or password
"); - } else if (login.isBadToken()) { - builder.append("
Your session timed out
"); - } + if (!allowed.isEmpty()) { + if (!allowed.contains(subkey)) { + return new WLoginResult(true, false); + } - if (uri.equals("/logout")) { - uri = "/"; + if ((subkey + "|").contains("|rw|")) { + rw = true; + } + if ((subkey + "|").contains("|wl|")) { + wl = false; // |wl| = bypass whitelist + } + if ((subkey + "|").contains("|bl|")) { + bl = false; // |bl| = bypass blacklist + } } - builder.append( - "\n"); - - appendPostHtml(builder); - - return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, - NanoHTTPD.MIME_HTML, builder.toString()); + return new WLoginResult(who, key, subkey, rw, wl, bl); } - protected Response getList(String uri, List whitelist) + @Override + protected Response getList(String uri, WLoginResult login) throws IOException { - if (uri.equals("/list/luids")) { - BasicLibrary lib = Instance.getInstance().getLibrary(); - List metas = lib.getList().filter(whitelist, null, null); + if (WebLibraryUrls.LIST_URL_METADATA.equals(uri)) { List jsons = new ArrayList(); - for (MetaData meta : metas) { + for (MetaData meta : metas(login)) { jsons.add(JsonIO.toJson(meta)); } @@ -484,196 +226,23 @@ public class WebLibraryServer implements Runnable { NanoHTTPD.MIME_PLAINTEXT, null); } - private Response root(IHTTPSession session, Map cookies, - List whitelist) throws IOException { - BasicLibrary lib = Instance.getInstance().getLibrary(); - MetaResultList result = lib.getList(); - result = new MetaResultList(result.filter(whitelist, null, null)); - StringBuilder builder = new StringBuilder(); - - appendPreHtml(builder, true); - - Map params = session.getParms(); - - String filter = cookies.get("filter"); - if (params.get("optionNo") != null) - filter = null; - if (filter == null) { - filter = ""; - } - - String browser = params.get("browser") == null ? "" - : params.get("browser"); - String browser2 = params.get("browser2") == null ? "" - : params.get("browser2"); - String browser3 = params.get("browser3") == null ? "" - : params.get("browser3"); - - String filterSource = null; - String filterAuthor = null; - String filterTag = null; - - // TODO: javascript in realtime, using visible=false + hide [submit] - - builder.append("
\n"); - builder.append("\n"); - - // TODO: javascript in realtime, using visible=false + hide [submit] - builder.append("
\n"); - builder.append("\tFilter: \n"); - builder.append( - "\t\n"); - builder.append("\t\n"); - builder.append("\t"); - builder.append( - "\t\n"); - builder.append("
\n"); - builder.append("
\n"); - - builder.append("\t
"); - for (MetaData meta : result.getMetas()) { - if (!filter.isEmpty() && !meta.getTitle().toLowerCase() - .contains(filter.toLowerCase())) { - continue; - } - - // TODO Sub sources - if (filterSource != null - && !filterSource.equals(meta.getSource())) { - continue; - } - - // TODO: sub authors - if (filterAuthor != null - && !filterAuthor.equals(meta.getAuthor())) { - continue; - } - - if (filterTag != null && !meta.getTags().contains(filterTag)) { - continue; - } - - builder.append("\n"); - } - builder.append("
"); - - appendPostHtml(builder); - return NanoHTTPD.newFixedLengthResponse(builder.toString()); - } - // /story/luid/chapter/para <-- text/image // /story/luid/cover <-- image // /story/luid/metadata <-- json // /story/luid/json <-- json, whole chapter (no images) - private Response getStoryPart(String uri, List whitelist) { - String[] cover = uri.split("/"); + @Override + protected Response getStoryPart(String uri, WLoginResult login) { + String[] uriParts = uri.split("/"); int off = 2; - if (cover.length < off + 2) { + if (uriParts.length < off + 2) { return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, null); } - String luid = cover[off + 0]; - String chapterStr = cover[off + 1]; - String imageStr = cover.length < off + 3 ? null : cover[off + 2]; + String luid = uriParts[off + 0]; + String chapterStr = uriParts[off + 1]; + String imageStr = uriParts.length < off + 3 ? null : uriParts[off + 2]; // 1-based (0 = desc) int chapter = 0; @@ -709,24 +278,24 @@ public class WebLibraryServer implements Runnable { InputStream in = null; try { if ("cover".equals(chapterStr)) { - Image img = getCover(luid, whitelist); + Image img = storyCover(luid, login); if (img != null) { in = img.newInputStream(); } // TODO: get correct image type mimeType = "image/png"; } else if ("metadata".equals(chapterStr)) { - MetaData meta = meta(luid, whitelist); + MetaData meta = meta(luid, login); JSONObject json = JsonIO.toJson(meta); mimeType = "application/json"; in = new ByteArrayInputStream(json.toString().getBytes()); } else if ("json".equals(chapterStr)) { - Story story = story(luid, whitelist); + Story story = story(luid, login); JSONObject json = JsonIO.toJson(story); mimeType = "application/json"; in = new ByteArrayInputStream(json.toString().getBytes()); } else { - Story story = story(luid, whitelist); + Story story = story(luid, login); if (story != null) { if (chapter == 0) { StringBuilder builder = new StringBuilder(); @@ -768,359 +337,254 @@ public class WebLibraryServer implements Runnable { return newInputStreamResponse(mimeType, in); } - private Response getViewer(Map cookies, String uri, - List whitelist) { - String[] cover = uri.split("/"); - int off = 2; + // /story/luid/source + // /story/luid/title + // /story/luid/author + @Override + protected Response setStoryPart(String uri, String value, + WLoginResult login) throws IOException { + String[] uriParts = uri.split("/"); + int off = 2; // "" and "story" - if (cover.length < off + 2) { + if (uriParts.length < off + 2) { return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, - NanoHTTPD.MIME_PLAINTEXT, null); + NanoHTTPD.MIME_PLAINTEXT, "Invalid story part request"); } - String type = cover[off + 0]; - String luid = cover[off + 1]; - String chapterStr = cover.length < off + 3 ? null : cover[off + 2]; - String paragraphStr = cover.length < off + 4 ? null : cover[off + 3]; - - // 1-based (0 = desc) - int chapter = -1; - if (chapterStr != null) { - try { - chapter = Integer.parseInt(chapterStr); - if (chapter < 0) { - throw new NumberFormatException(); - } - } catch (NumberFormatException e) { - return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, - NanoHTTPD.MIME_PLAINTEXT, "Chapter is not valid"); - } + if (!login.isRw()) { + return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, + NanoHTTPD.MIME_PLAINTEXT, "SET story part not allowed"); } - // 1-based - int paragraph = 0; - if (paragraphStr != null) { - try { - paragraph = Integer.parseInt(paragraphStr); - if (paragraph <= 0) { - throw new NumberFormatException(); - } - } catch (NumberFormatException e) { - return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, - NanoHTTPD.MIME_PLAINTEXT, "Paragraph is not valid"); - } + if (exiting) { + return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, + NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); } - try { - Story story = story(luid, whitelist); - if (story == null) { - return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, - NanoHTTPD.MIME_PLAINTEXT, "Story not found"); - } + String luid = uriParts[off + 0]; + String type = uriParts[off + 1]; - StringBuilder builder = new StringBuilder(); - appendPreHtml(builder, false); + if (!Arrays.asList("source", "title", "author").contains(type)) { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, + "Invalid SET story part: " + type); + } - // For images documents, always go to the images if not chap 0 desc - if (story.getMeta().isImageDocument()) { - if (chapter > 0 && paragraph <= 0) - paragraph = 1; + if (meta(luid, login) != null) { + BasicLibrary lib = Instance.getInstance().getLibrary(); + if ("source".equals(type)) { + lib.changeSource(luid, value, null); + } else if ("title".equals(type)) { + lib.changeTitle(luid, value, null); + } else if ("author".equals(type)) { + lib.changeAuthor(luid, value, null); } + } - Chapter chap = null; - if (chapter <= 0) { - chap = story.getMeta().getResume(); - } else { - try { - chap = story.getChapters().get(chapter - 1); - } catch (IndexOutOfBoundsException e) { - return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, - NanoHTTPD.MIME_PLAINTEXT, "Chapter not found"); - } - } + return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); + } - String first, previous, next, last; - - StringBuilder content = new StringBuilder(); - - String disabledLeft = ""; - String disabledRight = ""; - String disabledZoomReal = ""; - String disabledZoomWidth = ""; - String disabledZoomHeight = ""; - - if (paragraph <= 0) { - first = getViewUrl(luid, 0, null); - previous = getViewUrl(luid, (Math.max(chapter - 1, 0)), null); - next = getViewUrl(luid, - (Math.min(chapter + 1, story.getChapters().size())), - null); - last = getViewUrl(luid, story.getChapters().size(), null); - - StringBuilder desc = new StringBuilder(); - - if (chapter <= 0) { - desc.append("

"); - desc.append(story.getMeta().getTitle()); - desc.append("

\n"); - desc.append("
\n"); - desc.append("\t
\n"); - desc.append("\t\t\n"); - desc.append("\t
\n"); - desc.append("\t\n"); - Map details = BasicLibrary - .getMetaDesc(story.getMeta()); - for (String key : details.keySet()) { - appendTableRow(desc, 2, key, details.get(key)); - } - desc.append("\t
\n"); - desc.append("
\n"); - desc.append("

Description

\n"); - } + @Override + protected Response getCover(String uri, WLoginResult login) + throws IOException { + String[] uriParts = uri.split("/"); + int off = 2; // "" and "cover" - content.append("
\n"); - content.append(desc); - String description = new TextOutput(false).convert(chap, - chapter > 0); - content.append(chap.getParagraphs().size() <= 0 - ? "No content provided." - : description); - content.append("
\n"); - - if (chapter <= 0) - disabledLeft = " disabled='disbaled'"; - if (chapter >= story.getChapters().size()) - disabledRight = " disabled='disbaled'"; - } else { - first = getViewUrl(luid, chapter, 1); - previous = getViewUrl(luid, chapter, - (Math.max(paragraph - 1, 1))); - next = getViewUrl(luid, chapter, - (Math.min(paragraph + 1, chap.getParagraphs().size()))); - last = getViewUrl(luid, chapter, chap.getParagraphs().size()); - - if (paragraph <= 1) - disabledLeft = " disabled='disbaled'"; - if (paragraph >= chap.getParagraphs().size()) - disabledRight = " disabled='disbaled'"; - - // First -> previous *chapter* - if (chapter > 0) - disabledLeft = ""; - first = getViewUrl(luid, (Math.max(chapter - 1, 0)), null); - if (paragraph <= 1) { - previous = first; - } + if (uriParts.length < off + 2) { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); + } - Paragraph para = null; - try { - para = chap.getParagraphs().get(paragraph - 1); - } catch (IndexOutOfBoundsException e) { - return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND, - NanoHTTPD.MIME_PLAINTEXT, - "Paragraph " + paragraph + " not found"); - } + String type = uriParts[off + 0]; + String id = uriParts[off + 1]; - if (para.getType() == ParagraphType.IMAGE) { - String zoomStyle = "max-width: 100%;"; - disabledZoomWidth = " disabled='disabled'"; - String zoomOption = cookies.get("zoom"); - if (zoomOption != null && !zoomOption.isEmpty()) { - if (zoomOption.equals("real")) { - zoomStyle = ""; - disabledZoomWidth = ""; - disabledZoomReal = " disabled='disabled'"; - } else if (zoomOption.equals("width")) { - zoomStyle = "max-width: 100%;"; - } else if (zoomOption.equals("height")) { - // see height of navbar + optionbar - zoomStyle = "max-height: calc(100% - 128px);"; - disabledZoomWidth = ""; - disabledZoomHeight = " disabled='disabled'"; - } - } + InputStream in = null; - content.append(String.format("" // - + "" - + "" - + "", // - next, // - zoomStyle, // - getStoryUrl(luid, chapter, paragraph))); - } else { - content.append(String.format("" // - + "
%s
", // - para.getContent())); - } + if ("story".equals(type)) { + Image img = storyCover(id, login); + if (img != null) { + in = img.newInputStream(); + } + } else if ("source".equals(type)) { + Image img = sourceCover(id, login); + if (img != null) { + in = img.newInputStream(); } + } else if ("author".equals(type)) { + Image img = authorCover(id, login); + if (img != null) { + in = img.newInputStream(); + } + } else { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, + "Invalid GET cover type: " + type); + } - builder.append(String.format("" // - + "\n", // - disabledRight, next, // - disabledRight, last // - )); - - builder.append(content); - - builder.append("
\n"); - builder.append(" BACK\n"); - - if (paragraph > 0) { - builder.append(String.format("" // - + "\tREAL\n"// - + "\tWIDTH\n"// - + "\tHEIGHT\n"// - + "
\n", // - disabledZoomReal, - uri + "?optionName=zoom&optionValue=real", // - disabledZoomWidth, - uri + "?optionName=zoom&optionValue=width", // - disabledZoomHeight, - uri + "?optionName=zoom&optionValue=height" // - )); - } + if (exiting) { + return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, + NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); + } - appendPostHtml(builder); - return NanoHTTPD.newFixedLengthResponse(Status.OK, - NanoHTTPD.MIME_HTML, builder.toString()); - } catch (IOException e) { - Instance.getInstance().getTraceHandler() - .error(new IOException("Cannot get image: " + uri, e)); - return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, - NanoHTTPD.MIME_PLAINTEXT, "Error when processing request"); + String type = uriParts[off + 0]; + String id = uriParts[off + 1]; + + if ("source".equals(type)) { + sourceCover(id, login, luid); + } else if ("author".equals(type)) { + authorCover(id, login, luid); + } else { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, + "Invalid SET cover type: " + type); } + + return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); } - private Response newInputStreamResponse(String mimeType, InputStream in) { - if (in == null) { - return NanoHTTPD.newFixedLengthResponse(Status.NO_CONTENT, "", - null); + @Override + protected Response imprt(String uri, String urlStr, WLoginResult login) + throws IOException { + final BasicLibrary lib = Instance.getInstance().getLibrary(); + + if (!login.isRw()) { + return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, + NanoHTTPD.MIME_PLAINTEXT, "Import not allowed"); } - return NanoHTTPD.newChunkedResponse(Status.OK, mimeType, in); - } - private String getContentOf(String file) { - InputStream in = IOUtils.openResource(WebLibraryServerIndex.class, - file); - if (in != null) { - try { - return IOUtils.readSmallStream(in); - } catch (IOException e) { - Instance.getInstance().getTraceHandler().error( - new IOException("Cannot get file: index.pre.html", e)); - } + if (exiting) { + return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, + NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); } - return ""; - } + final URL url = new URL(urlStr); + final Progress pg = new Progress(); + final String luid = lib.getNextId(); - private String getViewUrl(String luid, int chap, Integer para) { - return VIEWER_URL // - .replace("{luid}", luid) // - .replace("{chap}", Integer.toString(chap)) // - .replace("/{para}", - para == null ? "" : "/" + Integer.toString(para)); - } + synchronized (imprts) { + imprts.put(luid, pg); + } - private String getStoryUrl(String luid, int chap, Integer para) { - return STORY_URL // - .replace("{luid}", luid) // - .replace("{chap}", Integer.toString(chap)) // - .replace("{para}", para == null ? "" : Integer.toString(para)); + new Thread(new Runnable() { + @Override + public void run() { + try { + lib.imprt(url, luid, pg); + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + } finally { + synchronized (imprts) { + imprts.remove(luid); + } + } + } + }, "Import story: " + urlStr).start(); + + return NanoHTTPD.newFixedLengthResponse(Status.OK, + NanoHTTPD.MIME_PLAINTEXT, luid); } - private String getStoryUrlCover(String luid) { - return STORY_URL_COVER // - .replace("{luid}", luid); + @Override + protected Response imprtProgress(String uri, WLoginResult login) { + String[] uriParts = uri.split("/"); + int off = 2; // "" and "import" + + if (uriParts.length < off + 1) { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, "Invalid cover request"); + } + + String luid = uriParts[off + 0]; + + Progress pg = null; + synchronized (imprts) { + pg = imprts.get(luid); + } + if (pg != null) { + return NanoHTTPD.newFixedLengthResponse(Status.OK, + "application/json", JsonIO.toJson(pg).toString()); + } + + return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); } - private MetaData meta(String luid, List whitelist) + @Override + protected Response delete(String uri, WLoginResult login) throws IOException { - BasicLibrary lib = Instance.getInstance().getLibrary(); - MetaData meta = lib.getInfo(luid); - if (!whitelist.isEmpty() && !whitelist.contains(meta.getSource())) { - return null; + String[] uriParts = uri.split("/"); + int off = 2; // "" and "delete" + + if (uriParts.length < off + 1) { + return NanoHTTPD.newFixedLengthResponse(Status.BAD_REQUEST, + NanoHTTPD.MIME_PLAINTEXT, "Invalid delete request"); } - return meta; + if (!login.isRw()) { + return NanoHTTPD.newFixedLengthResponse(Status.FORBIDDEN, + NanoHTTPD.MIME_PLAINTEXT, "Delete not allowed"); + } + + if (exiting) { + return NanoHTTPD.newFixedLengthResponse(Status.SERVICE_UNAVAILABLE, + NanoHTTPD.MIME_PLAINTEXT, "Server is exiting..."); + } + + String luid = uriParts[off + 0]; + + BasicLibrary lib = Instance.getInstance().getLibrary(); + lib.delete(luid); + + return newInputStreamResponse(NanoHTTPD.MIME_PLAINTEXT, null); } - private Image getCover(String luid, List whitelist) - throws IOException { - MetaData meta = meta(luid, whitelist); - if (meta != null) { - BasicLibrary lib = Instance.getInstance().getLibrary(); - return lib.getCover(meta.getLuid()); + @Override + protected List metas(WLoginResult login) throws IOException { + BasicLibrary lib = Instance.getInstance().getLibrary(); + List metas = new ArrayList(); + for (MetaData meta : lib.getList().getMetas()) { + if (isAllowed(meta, login)) { + metas.add(meta); + } } - return null; + return metas; } // NULL if not whitelist OK or if not found - private Story story(String luid, List whitelist) - throws IOException { + @Override + protected Story story(String luid, WLoginResult login) throws IOException { synchronized (storyCache) { if (storyCache.containsKey(luid)) { Story story = storyCache.get(luid); - if (!whitelist.isEmpty() - && !whitelist.contains(story.getMeta().getSource())) { + if (!isAllowed(story.getMeta(), login)) return null; - } return story; } } Story story = null; - MetaData meta = meta(luid, whitelist); + MetaData meta = meta(luid, login); if (meta != null) { BasicLibrary lib = Instance.getInstance().getLibrary(); story = lib.getStory(luid, null); @@ -1145,94 +609,112 @@ public class WebLibraryServer implements Runnable { return story; } - private long sizeOf(Story story) { - long size = 0; - for (Chapter chap : story) { - for (Paragraph para : chap) { - if (para.getType() == ParagraphType.IMAGE) { - size += para.getContentImage().getSize(); - } else { - size += para.getContent().length(); - } - } - } + private MetaData meta(String luid, WLoginResult login) throws IOException { + BasicLibrary lib = Instance.getInstance().getLibrary(); + MetaData meta = lib.getInfo(luid); + if (!isAllowed(meta, login)) + return null; - return size; + return meta; } - private void appendPreHtml(StringBuilder builder, boolean banner) { - String favicon = "favicon.ico"; - String icon = Instance.getInstance().getUiConfig() - .getString(UiConfig.PROGRAM_ICON); - if (icon != null) { - favicon = "icon_" + icon.replace("-", "_") + ".png"; + private Image storyCover(String luid, WLoginResult login) + throws IOException { + MetaData meta = meta(luid, login); + if (meta != null) { + BasicLibrary lib = Instance.getInstance().getLibrary(); + return lib.getCover(meta.getLuid()); } - builder.append( - getContentOf("index.pre.html").replace("favicon.ico", favicon)); - - if (banner) { - builder.append("\n"); - } + return null; } - private void appendPostHtml(StringBuilder builder) { - builder.append(getContentOf("index.post.html")); - } + private Image authorCover(String author, WLoginResult login) + throws IOException { + Image img = null; - private void appendOption(StringBuilder builder, int depth, String name, - String value, String selected) { - for (int i = 0; i < depth; i++) { - builder.append("\t"); + List metas = new MetaResultList(metas(login)).filter(null, + author, null); + if (metas.size() > 0) { + BasicLibrary lib = Instance.getInstance().getLibrary(); + img = lib.getCustomAuthorCover(author); + if (img == null) + img = lib.getCover(metas.get(0).getLuid()); } - builder.append("\n"); } - private void appendTableRow(StringBuilder builder, int depth, - String... tds) { - for (int i = 0; i < depth; i++) { - builder.append("\t"); + private Image sourceCover(String source, WLoginResult login) + throws IOException { + Image img = null; + + List metas = new MetaResultList(metas(login)).filter(source, + null, null); + if (metas.size() > 0) { + BasicLibrary lib = Instance.getInstance().getLibrary(); + img = lib.getCustomSourceCover(source); + if (img == null) + img = lib.getCover(metas.get(0).getLuid()); } - int col = 1; - builder.append(""); - for (String td : tds) { - builder.append(""); - builder.append(td); - builder.append(""); + return img; + } + + private void sourceCover(String source, WLoginResult login, String luid) + throws IOException { + if (meta(luid, login) != null) { + List metas = new MetaResultList(metas(login)) + .filter(source, null, null); + if (metas.size() > 0) { + BasicLibrary lib = Instance.getInstance().getLibrary(); + lib.setSourceCover(source, luid); + } } - builder.append("\n"); } - private void appendItemA(StringBuilder builder, int depth, String link, - String name, boolean selected) { - for (int i = 0; i < depth; i++) { - builder.append("\t"); + private boolean isAllowed(MetaData meta, WLoginResult login) { + MetaResultList one = new MetaResultList(Arrays.asList(meta)); + if (login.isWl() && !whitelist.isEmpty()) { + if (one.filter(whitelist, null, null).isEmpty()) { + return false; + } + } + if (login.isBl() && !blacklist.isEmpty()) { + if (!one.filter(blacklist, null, null).isEmpty()) { + return false; + } } - builder.append(""); - builder.append(name); - builder.append("\n"); + + return size; } public static void main(String[] args) throws IOException {