add blacklist support
authorNiki Roo <niki@nikiroo.be>
Wed, 13 May 2020 17:12:50 +0000 (19:12 +0200)
committerNiki Roo <niki@nikiroo.be>
Wed, 13 May 2020 17:12:50 +0000 (19:12 +0200)
src/be/nikiroo/fanfix/bundles/Config.java
src/be/nikiroo/fanfix/library/RemoteLibrary.java
src/be/nikiroo/fanfix/library/RemoteLibraryServer.java
src/be/nikiroo/fanfix/library/WebLibraryServer.java

index c96ed22a93e47cf91374e525f38011866f6413fe..86744b45b8d169d4d66e2e7dfa884d1d72f479a5 100644 (file)
@@ -102,9 +102,12 @@ public enum Config {
        @Meta(description = "Allow write access to the clients (download story, move story...) without RW subkeys", //
        format = Format.BOOLEAN, def = "true")
        SERVER_RW, //
-       @Meta(description = "If not empty, only the EXACT listed sources will be available for clients without BL subkeys",//
+       @Meta(description = "If not empty, only the EXACT listed sources will be available for clients without a WL subkey",//
        array = true, format = Format.STRING, def = "")
        SERVER_WHITELIST, //
+       @Meta(description = "Those sources will not be available for clients without a BL subkey",//
+       array = true, format = Format.STRING, def = "")
+       SERVER_BLACKLIST, //
        @Meta(description = "The subkeys that the server will allow, including the modes\nA subkey is used as a login for HTTP (clear text protocol) and HTTPS modes", //
        array = true, format = Format.STRING, def = "")
        SERVER_ALLOWED_SUBKEYS, //
index 9fa8c66190174bd1a92536b995d181a16bc8744a..83bb2124ecaaf9eb1bf66d92366670609c493610 100644 (file)
@@ -62,7 +62,7 @@ public class RemoteLibrary extends BasicLibrary {
         * Create a {@link RemoteLibrary} linked to the given server.
         * <p>
         * Note that the key is structured:
-        * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>|<b>wl</b>)(|<b>rw</b>)</tt>
+        * <tt><b><i>xxx</i></b>(|<b><i>yyy</i></b>(|<b>wl</b>)(|<b>bl</b>)(|<b>rw</b>)</tt>
         * <p>
         * Note that anything before the first pipe (<tt>|</tt>) character is
         * considered to be the encryption key, anything after that character is
@@ -77,16 +77,16 @@ public class RemoteLibrary extends BasicLibrary {
         * <li><b><i>yyy</i></b>: the secondary key</li>
         * <li><b>rw</b>: flag to allow read and write access if it is not the
         * default on this server</li>
-        * <li><b>wl</b>: flag to allow access to all the stories (bypassing the
-        * whitelist if it exists)</li>
+        * <li><b>bl</b>: flag to bypass the blacklist (if it exists)</li>
+        * <li><b>wl</b>: flag to bypass the whitelist if it exists</li>
         * </ul>
         * <p>
         * Some examples:
         * <ul>
         * <li><b>my_key</b>: normal connection, will take the default server
         * options</li>
-        * <li><b>my_key|agzyzz|wl</b>: will ask to bypass the white list (if it
-        * exists)</li>
+        * <li><b>my_key|agzyzz|wl|bl</b>: will ask to bypass the black list and the
+        * white list (if it exists)</li>
         * <li><b>my_key|agzyzz|rw</b>: will ask read-write access (if the default
         * is read-only)</li>
         * <li><b>my_key|agzyzz|wl|rw</b>: will ask both read-write access and white
index c150a01b6f8a14f68279fc4bb7358f3374a85e10..e54f105d6bf9233c4f1c4383c948630b57b79555 100644 (file)
@@ -37,7 +37,8 @@ import be.nikiroo.utils.serial.server.ServerObject;
  * "r/w")</li>
  * <li>GET_METADATA *: will return the metadata of all the stories in the
  * library (array)</li> *
- * <li>GET_METADATA [luid]: will return the metadata of the story of LUID luid</li>
+ * <li>GET_METADATA [luid]: will return the metadata of the story of LUID
+ * luid</li>
  * <li>GET_STORY [luid]: will return the given story if it exists (or NULL if
  * not)</li>
  * <li>SAVE_STORY [luid]: save the story (that must be sent just after the
@@ -61,6 +62,7 @@ public class RemoteLibraryServer extends ServerObject {
        private Map<Long, String> commands = new HashMap<Long, String>();
        private Map<Long, Long> times = new HashMap<Long, Long>();
        private Map<Long, Boolean> wls = new HashMap<Long, Boolean>();
+       private Map<Long, Boolean> bls = new HashMap<Long, Boolean>();
        private Map<Long, Boolean> rws = new HashMap<Long, Boolean>();
 
        /**
@@ -79,7 +81,7 @@ public class RemoteLibraryServer extends ServerObject {
                                                .getInteger(Config.SERVER_PORT),
                                Instance.getInstance().getConfig()
                                                .getString(Config.SERVER_KEY));
-               
+
                setTraceHandler(Instance.getInstance().getTraceHandler());
        }
 
@@ -91,6 +93,7 @@ public class RemoteLibraryServer extends ServerObject {
                // defaults are positive (as previous versions without the feature)
                boolean rw = true;
                boolean wl = true;
+               boolean bl = true;
 
                String subkey = "";
                String command = "";
@@ -110,18 +113,26 @@ public class RemoteLibraryServer extends ServerObject {
                        }
                }
 
-               List<String> whitelist = Instance.getInstance().getConfig().getList(Config.SERVER_WHITELIST);
+               List<String> whitelist = Instance.getInstance().getConfig()
+                               .getList(Config.SERVER_WHITELIST);
                if (whitelist == null) {
                        whitelist = new ArrayList<String>();
                }
+               List<String> blacklist = Instance.getInstance().getConfig()
+                               .getList(Config.SERVER_BLACKLIST);
+               if (blacklist == null) {
+                       blacklist = new ArrayList<String>();
+               }
 
                if (whitelist.isEmpty()) {
                        wl = false;
                }
 
-               rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW, rw);
+               rw = Instance.getInstance().getConfig().getBoolean(Config.SERVER_RW,
+                               rw);
                if (!subkey.isEmpty()) {
-                       List<String> allowed = Instance.getInstance().getConfig().getList(Config.SERVER_ALLOWED_SUBKEYS);
+                       List<String> allowed = Instance.getInstance().getConfig()
+                                       .getList(Config.SERVER_ALLOWED_SUBKEYS);
                        if (allowed.contains(subkey)) {
                                if ((subkey + "|").contains("|rw|")) {
                                        rw = true;
@@ -130,10 +141,14 @@ public class RemoteLibraryServer extends ServerObject {
                                        wl = false; // |wl| = bypass whitelist
                                        whitelist = new ArrayList<String>();
                                }
+                               if ((subkey + "|").contains("|bl|")) {
+                                       bl = false; // |bl| = bypass blacklist
+                                       blacklist = new ArrayList<String>();
+                               }
                        }
                }
 
-               String mode = display(wl, rw);
+               String mode = display(wl, bl, rw);
 
                String trace = mode + "[ " + command + "] ";
                for (Object arg : args) {
@@ -144,20 +159,21 @@ public class RemoteLibraryServer extends ServerObject {
 
                Object rep = null;
                try {
-                       rep = doRequest(action, command, args, rw, whitelist);
+                       rep = doRequest(action, command, args, rw, whitelist, blacklist);
                } catch (IOException e) {
                        rep = new RemoteLibraryException(e, true);
                }
 
                commands.put(id, command);
                wls.put(id, wl);
+               bls.put(id, bl);
                rws.put(id, rw);
                times.put(id, (new Date().getTime() - start));
 
                return rep;
        }
 
-       private String display(boolean whitelist, boolean rw) {
+       private String display(boolean whitelist, boolean blacklist, boolean rw) {
                String mode = "";
                if (!rw) {
                        mode += "RO: ";
@@ -165,6 +181,9 @@ public class RemoteLibraryServer extends ServerObject {
                if (whitelist) {
                        mode += "WL: ";
                }
+               if (blacklist) {
+                       mode += "BL: ";
+               }
 
                return mode;
        }
@@ -172,27 +191,28 @@ public class RemoteLibraryServer extends ServerObject {
        @Override
        protected void onRequestDone(long id, long bytesReceived, long bytesSent) {
                boolean whitelist = wls.get(id);
+               boolean blacklist = bls.get(id);
                boolean rw = rws.get(id);
                wls.remove(id);
+               bls.remove(id);
                rws.remove(id);
 
                String rec = StringUtils.formatNumber(bytesReceived) + "b";
                String sent = StringUtils.formatNumber(bytesSent) + "b";
                long now = System.currentTimeMillis();
-               System.out.println(StringUtils.fromTime(now)
-                               + ": "
+               System.out.println(StringUtils.fromTime(now) + ": "
                                + String.format("%s[>%s]: (%s sent, %s rec) in %d ms",
-                                               display(whitelist, rw), commands.get(id), sent, rec,
-                                               times.get(id)));
+                                               display(whitelist, blacklist, rw), commands.get(id),
+                                               sent, rec, times.get(id)));
 
                commands.remove(id);
                times.remove(id);
        }
 
        private Object doRequest(ConnectActionServerObject action, String command,
-                       Object[] args, boolean rw, List<String> whitelist)
-                       throws NoSuchFieldException, NoSuchMethodException,
-                       ClassNotFoundException, IOException {
+                       Object[] args, boolean rw, List<String> whitelist,
+                       List<String> blacklist) throws NoSuchFieldException,
+                       NoSuchMethodException, ClassNotFoundException, IOException {
                if ("PING".equals(command)) {
                        return rw ? "r/w" : "r/o";
                } else if ("GET_METADATA".equals(command)) {
@@ -201,13 +221,15 @@ public class RemoteLibraryServer extends ServerObject {
                        if ("*".equals(args[0])) {
                                Progress pg = createPgForwarder(action);
 
-                               for (MetaData meta : Instance.getInstance().getLibrary().getMetas(pg)) {
+                               for (MetaData meta : Instance.getInstance().getLibrary()
+                                               .getMetas(pg)) {
                                        metas.add(removeCover(meta));
                                }
 
                                forcePgDoneSent(pg);
                        } else {
-                               MetaData meta = Instance.getInstance().getLibrary().getInfo((String) args[0]);
+                               MetaData meta = Instance.getInstance().getLibrary()
+                                               .getInfo((String) args[0]);
                                MetaData light;
                                if (meta.getCover() == null) {
                                        light = meta;
@@ -219,43 +241,38 @@ public class RemoteLibraryServer extends ServerObject {
                                metas.add(light);
                        }
 
-                       if (!whitelist.isEmpty()) {
-                               for (int i = 0; i < metas.size(); i++) {
-                                       if (!whitelist.contains(metas.get(i).getSource())) {
-                                               metas.remove(i);
-                                               i--;
-                                       }
+                       for (int i = 0; i < metas.size(); i++) {
+                               if (!isAllowed(metas.get(i), whitelist, blacklist)) {
+                                       metas.remove(i);
+                                       i--;
                                }
                        }
 
                        return metas.toArray(new MetaData[0]);
+
                } else if ("GET_STORY".equals(command)) {
-                       MetaData meta = Instance.getInstance().getLibrary().getInfo((String) args[0]);
-                       if (meta == null) {
+                       MetaData meta = Instance.getInstance().getLibrary()
+                                       .getInfo((String) args[0]);
+                       if (meta == null || !isAllowed(meta, whitelist, blacklist)) {
                                return null;
                        }
 
-                       if (!whitelist.isEmpty()) {
-                               if (!whitelist.contains(meta.getSource())) {
-                                       return null;
-                               }
-                       }
-
                        meta = meta.clone();
                        meta.setCover(null);
 
                        action.send(meta);
                        action.rec();
 
-                       Story story = Instance.getInstance().getLibrary().getStory((String) args[0], null);
+                       Story story = Instance.getInstance().getLibrary()
+                                       .getStory((String) args[0], null);
                        for (Object obj : breakStory(story)) {
                                action.send(obj);
                                action.rec();
                        }
                } else if ("SAVE_STORY".equals(command)) {
                        if (!rw) {
-                               throw new RemoteLibraryException("Read-Only remote library: "
-                                               + args[0], false);
+                               throw new RemoteLibraryException(
+                                               "Read-Only remote library: " + args[0], false);
                        }
 
                        List<Object> list = new ArrayList<Object>();
@@ -269,54 +286,64 @@ public class RemoteLibraryServer extends ServerObject {
                        }
 
                        Story story = rebuildStory(list);
-                       Instance.getInstance().getLibrary().save(story, (String) args[0], null);
+                       Instance.getInstance().getLibrary().save(story, (String) args[0],
+                                       null);
                        return story.getMeta().getLuid();
                } else if ("IMPORT".equals(command)) {
                        if (!rw) {
-                               throw new RemoteLibraryException("Read-Only remote library: "
-                                               + args[0], false);
+                               throw new RemoteLibraryException(
+                                               "Read-Only remote library: " + args[0], false);
                        }
 
                        Progress pg = createPgForwarder(action);
-                       MetaData meta = Instance.getInstance().getLibrary().imprt(new URL((String) args[0]), pg);
+                       MetaData meta = Instance.getInstance().getLibrary()
+                                       .imprt(new URL((String) args[0]), pg);
                        forcePgDoneSent(pg);
                        return meta.getLuid();
                } else if ("DELETE_STORY".equals(command)) {
                        if (!rw) {
-                               throw new RemoteLibraryException("Read-Only remote library: "
-                                               + args[0], false);
+                               throw new RemoteLibraryException(
+                                               "Read-Only remote library: " + args[0], false);
                        }
 
                        Instance.getInstance().getLibrary().delete((String) args[0]);
                } else if ("GET_COVER".equals(command)) {
-                       return Instance.getInstance().getLibrary().getCover((String) args[0]);
+                       return Instance.getInstance().getLibrary()
+                                       .getCover((String) args[0]);
                } else if ("GET_CUSTOM_COVER".equals(command)) {
                        if ("SOURCE".equals(args[0])) {
-                               return Instance.getInstance().getLibrary().getCustomSourceCover((String) args[1]);
+                               return Instance.getInstance().getLibrary()
+                                               .getCustomSourceCover((String) args[1]);
                        } else if ("AUTHOR".equals(args[0])) {
-                               return Instance.getInstance().getLibrary().getCustomAuthorCover((String) args[1]);
+                               return Instance.getInstance().getLibrary()
+                                               .getCustomAuthorCover((String) args[1]);
                        } else {
                                return null;
                        }
                } else if ("SET_COVER".equals(command)) {
                        if (!rw) {
-                               throw new RemoteLibraryException("Read-Only remote library: "
-                                               + args[0] + ", " + args[1], false);
+                               throw new RemoteLibraryException(
+                                               "Read-Only remote library: " + args[0] + ", " + args[1],
+                                               false);
                        }
 
                        if ("SOURCE".equals(args[0])) {
-                               Instance.getInstance().getLibrary().setSourceCover((String) args[1], (String) args[2]);
+                               Instance.getInstance().getLibrary()
+                                               .setSourceCover((String) args[1], (String) args[2]);
                        } else if ("AUTHOR".equals(args[0])) {
-                               Instance.getInstance().getLibrary().setAuthorCover((String) args[1], (String) args[2]);
+                               Instance.getInstance().getLibrary()
+                                               .setAuthorCover((String) args[1], (String) args[2]);
                        }
                } else if ("CHANGE_STA".equals(command)) {
                        if (!rw) {
-                               throw new RemoteLibraryException("Read-Only remote library: " + args[0] + ", " + args[1], false);
+                               throw new RemoteLibraryException(
+                                               "Read-Only remote library: " + args[0] + ", " + args[1],
+                                               false);
                        }
 
                        Progress pg = createPgForwarder(action);
-                       Instance.getInstance().getLibrary().changeSTA((String) args[0], (String) args[1], (String) args[2],
-                                       (String) args[3], pg);
+                       Instance.getInstance().getLibrary().changeSTA((String) args[0],
+                                       (String) args[1], (String) args[2], (String) args[3], pg);
                        forcePgDoneSent(pg);
                } else if ("EXIT".equals(command)) {
                        if (!rw) {
@@ -423,17 +450,16 @@ public class RemoteLibraryServer extends ServerObject {
                        int min = (Integer) a[0 + offset];
                        int max = (Integer) a[1 + offset];
                        int progress = (Integer) a[2 + offset];
-                       
+
                        Object meta = null;
                        if (a.length > (3 + offset)) {
                                meta = a[3 + offset];
                        }
-                       
+
                        String name = null;
                        if (a.length > (4 + offset)) {
                                name = a[4 + offset] == null ? "" : a[4 + offset].toString();
                        }
-                       
 
                        if (min >= 0 && min <= max) {
                                pg.setName(name);
@@ -477,22 +503,20 @@ public class RemoteLibraryServer extends ServerObject {
                        public void progress(Progress progress, String name) {
                                Object meta = pg.get("meta");
                                if (meta instanceof MetaData) {
-                                       meta = removeCover((MetaData)meta);
+                                       meta = removeCover((MetaData) meta);
                                }
-                               
+
                                int min = pg.getMin();
                                int max = pg.getMax();
-                               int rel = min
-                                               + (int) Math.round(pg.getRelativeProgress()
-                                                               * (max - min));
-                               
+                               int rel = min + (int) Math
+                                               .round(pg.getRelativeProgress() * (max - min));
+
                                boolean samePg = p[0] == min && p[1] == max && p[2] == rel;
-                               
+
                                // Do not re-send the same value twice over the wire,
                                // unless more than 2 seconds have elapsed (to maintain the
                                // connection)
-                               if (!samePg || !same(pMeta[0], meta)
-                                               || !same(pName[0], name) //
+                               if (!samePg || !same(pMeta[0], meta) || !same(pName[0], name) //
                                                || (new Date().getTime() - lastTime[0] > 2000)) {
                                        p[0] = min;
                                        p[1] = max;
@@ -517,7 +541,7 @@ public class RemoteLibraryServer extends ServerObject {
 
                return pg;
        }
-       
+
        private boolean same(Object obj1, Object obj2) {
                if (obj1 == null || obj2 == null)
                        return obj1 == null && obj2 == null;
@@ -537,7 +561,7 @@ public class RemoteLibraryServer extends ServerObject {
                        }
                }
        }
-       
+
        private MetaData removeCover(MetaData meta) {
                MetaData light = null;
                if (meta != null) {
@@ -548,7 +572,20 @@ public class RemoteLibraryServer extends ServerObject {
                                light.setCover(null);
                        }
                }
-               
+
                return light;
        }
+
+       private boolean isAllowed(MetaData meta, List<String> whitelist,
+                       List<String> blacklist) {
+               if (!whitelist.isEmpty() && !whitelist.contains(meta.getSource())) {
+                       return false;
+               }
+
+               if (blacklist.contains(meta.getSource())) {
+                       return false;
+               }
+
+               return true;
+       }
 }
index 8a874010257072cc93018f92583c1d82620fedfd..c4be6633dc563949a5f146a86225017d43b987c4 100644 (file)
@@ -51,16 +51,18 @@ public class WebLibraryServer implements Runnable {
                private boolean success;
                private boolean rw;
                private boolean wl;
+               private boolean bl;
                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) {
+                               boolean success, boolean rw, boolean wl, boolean bl) {
                        this.success = success;
                        this.rw = rw;
                        this.wl = wl;
+                       this.bl = bl;
                        this.wookie = CookieUtils.generateCookie(who + key, 0);
 
                        String opts = "";
@@ -68,6 +70,8 @@ public class WebLibraryServer implements Runnable {
                                opts += "|rw";
                        if (!wl)
                                opts += "|wl";
+                       if (!bl)
+                               opts += "|bl";
 
                        this.token = wookie + "~"
                                        + CookieUtils.generateCookie(wookie + subkey + opts, 0)
@@ -101,6 +105,7 @@ public class WebLibraryServer implements Runnable {
 
                                                                this.rw = opts.contains("|rw");
                                                                this.wl = !opts.contains("|wl");
+                                                               this.bl = !opts.contains("|bl");
                                                        }
                                                }
                                        }
@@ -124,6 +129,10 @@ public class WebLibraryServer implements Runnable {
                        return wl;
                }
 
+               public boolean isBl() {
+                       return bl;
+               }
+
                public String getToken() {
                        return token;
                }
@@ -144,6 +153,9 @@ public class WebLibraryServer implements Runnable {
        private long maxStoryCacheSize;
        private TraceHandler tracer = new TraceHandler();
 
+       private List<String> whitelist;
+       private List<String> blacklist;
+
        public WebLibraryServer(boolean secure) throws IOException {
                Integer port = Instance.getInstance().getConfig()
                                .getInteger(Config.SERVER_PORT);
@@ -158,6 +170,11 @@ public class WebLibraryServer implements Runnable {
 
                setTraceHandler(Instance.getInstance().getTraceHandler());
 
+               whitelist = Instance.getInstance().getConfig()
+                               .getList(Config.SERVER_WHITELIST, new ArrayList<String>());
+               blacklist = Instance.getInstance().getConfig()
+                               .getList(Config.SERVER_BLACKLIST, new ArrayList<String>());
+
                SSLServerSocketFactory ssf = null;
                if (secure) {
                        String keystorePath = Instance.getInstance().getConfig()
@@ -211,30 +228,19 @@ public class WebLibraryServer implements Runnable {
                                        cookies.put(cookie, session.getCookies().read(cookie));
                                }
 
-                               List<String> whitelist = Instance.getInstance().getConfig()
-                                               .getList(Config.SERVER_WHITELIST);
-                               if (whitelist == null) {
-                                       whitelist = new ArrayList<String>();
-                               }
-
                                LoginResult login = null;
                                Map<String, String> params = session.getParms();
                                String who = session.getRemoteHostName()
                                                + session.getRemoteIpAddress();
                                if (params.get("login") != null) {
                                        login = login(who, params.get("password"),
-                                                       params.get("login"), whitelist);
+                                                       params.get("login"));
                                } else {
                                        String token = cookies.get("token");
-                                       login = login(who, token, Instance.getInstance().getConfig()
-                                                       .getList(Config.SERVER_ALLOWED_SUBKEYS));
+                                       login = login(who, token);
                                }
 
                                if (login.isSuccess()) {
-                                       if (!login.isWl()) {
-                                               whitelist.clear();
-                                       }
-
                                        // refresh token
                                        session.getCookies().set(new Cookie("token",
                                                        login.getToken(), "30; path=/"));
@@ -267,13 +273,13 @@ public class WebLibraryServer implements Runnable {
                                if (rep == null) {
                                        try {
                                                if (uri.equals("/")) {
-                                                       rep = root(session, cookies, whitelist);
+                                                       rep = root(session, cookies, login);
                                                } else if (uri.startsWith(LIST_URL)) {
-                                                       rep = getList(uri, whitelist);
+                                                       rep = getList(uri, login);
                                                } else if (uri.startsWith(STORY_URL_BASE)) {
-                                                       rep = getStoryPart(uri, whitelist);
+                                                       rep = getStoryPart(uri, login);
                                                } else if (uri.startsWith(VIEWER_URL_BASE)) {
-                                                       rep = getViewer(cookies, uri, whitelist);
+                                                       rep = getViewer(cookies, uri, login);
                                                } else if (uri.equals("/logout")) {
                                                        session.getCookies().delete("token");
                                                        cookies.remove("token");
@@ -313,24 +319,6 @@ public class WebLibraryServer implements Runnable {
                                }
 
                                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());
                        }
                };
 
@@ -385,35 +373,33 @@ public class WebLibraryServer implements Runnable {
                this.tracer = tracer;
        }
 
-       private LoginResult login(String who, String token, List<String> subkeys) {
+       private LoginResult login(String who, String token) {
+               List<String> subkeys = Instance.getInstance().getConfig().getList(
+                               Config.SERVER_ALLOWED_SUBKEYS, new ArrayList<String>());
                String realKey = Instance.getInstance().getConfig()
-                               .getString(Config.SERVER_KEY);
-               realKey = realKey == null ? "" : realKey;
+                               .getString(Config.SERVER_KEY, "");
+
                return new LoginResult(token, who, realKey, subkeys);
        }
 
        // allow rw/wl
-       private LoginResult login(String who, String key, String subkey,
-                       List<String> whitelist) {
+       private LoginResult 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 LoginResult(null, null, null, false, false, false,
+                                       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);
@@ -427,12 +413,16 @@ public class WebLibraryServer implements Runnable {
                                if ((subkey + "|").contains("|wl|")) {
                                        wl = false; // |wl| = bypass whitelist
                                }
+                               if ((subkey + "|").contains("|bl|")) {
+                                       bl = false; // |bl| = bypass blacklist
+                               }
                        } else {
-                               return new LoginResult(null, null, null, false, false, false);
+                               return new LoginResult(null, null, null, false, false, false,
+                                               false);
                        }
                }
 
-               return new LoginResult(who, key, subkey, true, rw, wl);
+               return new LoginResult(who, key, subkey, true, rw, wl, bl);
        }
 
        private Response loginPage(LoginResult login, String uri) {
@@ -465,13 +455,11 @@ public class WebLibraryServer implements Runnable {
                                NanoHTTPD.MIME_HTML, builder.toString());
        }
 
-       protected Response getList(String uri, List<String> whitelist)
+       protected Response getList(String uri, LoginResult login)
                        throws IOException {
                if (uri.equals("/list/luids")) {
-                       BasicLibrary lib = Instance.getInstance().getLibrary();
-                       List<MetaData> metas = lib.getList().filter(whitelist, null, null);
                        List<JSONObject> jsons = new ArrayList<JSONObject>();
-                       for (MetaData meta : metas) {
+                       for (MetaData meta : metas(login)) {
                                jsons.add(JsonIO.toJson(meta));
                        }
 
@@ -485,10 +473,9 @@ public class WebLibraryServer implements Runnable {
        }
 
        private Response root(IHTTPSession session, Map<String, String> cookies,
-                       List<String> whitelist) throws IOException {
+                       LoginResult login) throws IOException {
                BasicLibrary lib = Instance.getInstance().getLibrary();
-               MetaResultList result = lib.getList();
-               result = new MetaResultList(result.filter(whitelist, null, null));
+               MetaResultList result = new MetaResultList(metas(login));
                StringBuilder builder = new StringBuilder();
 
                appendPreHtml(builder, true);
@@ -662,7 +649,7 @@ public class WebLibraryServer implements Runnable {
        // /story/luid/cover <-- image
        // /story/luid/metadata <-- json
        // /story/luid/json <-- json, whole chapter (no images)
-       private Response getStoryPart(String uri, List<String> whitelist) {
+       private Response getStoryPart(String uri, LoginResult login) {
                String[] cover = uri.split("/");
                int off = 2;
 
@@ -709,24 +696,24 @@ public class WebLibraryServer implements Runnable {
                InputStream in = null;
                try {
                        if ("cover".equals(chapterStr)) {
-                               Image img = getCover(luid, whitelist);
+                               Image img = getCover(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();
@@ -769,7 +756,7 @@ public class WebLibraryServer implements Runnable {
        }
 
        private Response getViewer(Map<String, String> cookies, String uri,
-                       List<String> whitelist) {
+                       LoginResult login) {
                String[] cover = uri.split("/");
                int off = 2;
 
@@ -812,7 +799,7 @@ public class WebLibraryServer implements Runnable {
                }
 
                try {
-                       Story story = story(luid, whitelist);
+                       Story story = story(luid, login);
                        if (story == null) {
                                return NanoHTTPD.newFixedLengthResponse(Status.NOT_FOUND,
                                                NanoHTTPD.MIME_PLAINTEXT, "Story not found");
@@ -1082,20 +1069,41 @@ public class WebLibraryServer implements Runnable {
                                .replace("{luid}", luid);
        }
 
-       private MetaData meta(String luid, List<String> whitelist)
-                       throws IOException {
+       private boolean isAllowed(MetaData meta, LoginResult login) {
+               if (login.isWl() && !whitelist.isEmpty()
+                               && !whitelist.contains(meta.getSource())) {
+                       return false;
+               }
+               if (login.isBl() && blacklist.contains(meta.getSource())) {
+                       return false;
+               }
+
+               return true;
+       }
+
+       private List<MetaData> metas(LoginResult login) throws IOException {
+               BasicLibrary lib = Instance.getInstance().getLibrary();
+               List<MetaData> metas = new ArrayList<MetaData>();
+               for (MetaData meta : lib.getList().getMetas()) {
+                       if (isAllowed(meta, login)) {
+                               metas.add(meta);
+                       }
+               }
+
+               return metas;
+       }
+
+       private MetaData meta(String luid, LoginResult login) throws IOException {
                BasicLibrary lib = Instance.getInstance().getLibrary();
                MetaData meta = lib.getInfo(luid);
-               if (!whitelist.isEmpty() && !whitelist.contains(meta.getSource())) {
+               if (!isAllowed(meta, login))
                        return null;
-               }
 
                return meta;
        }
 
-       private Image getCover(String luid, List<String> whitelist)
-                       throws IOException {
-               MetaData meta = meta(luid, whitelist);
+       private Image getCover(String luid, LoginResult login) throws IOException {
+               MetaData meta = meta(luid, login);
                if (meta != null) {
                        BasicLibrary lib = Instance.getInstance().getLibrary();
                        return lib.getCover(meta.getLuid());
@@ -1105,22 +1113,19 @@ public class WebLibraryServer implements Runnable {
        }
 
        // NULL if not whitelist OK or if not found
-       private Story story(String luid, List<String> whitelist)
-                       throws IOException {
+       private Story story(String luid, LoginResult 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);