package org.lobobrowser.security; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import org.javatuples.Pair; import org.lobobrowser.ua.UserAgentContext.Request; import org.lobobrowser.ua.UserAgentContext.RequestKind; public class PermissionSystem { static enum Permission { Allow, Deny, Undecided; public boolean isDecided() { return this != Undecided; } } static final class PermissionResult { final Permission permission; final boolean isDefault; public PermissionResult(final Permission permission, final boolean isDefault) { this.permission = permission; this.isDefault = isDefault; } public PermissionResult makeDefault() { return new PermissionResult(permission, true); } @Override public String toString() { return permission.name() + (isDefault ? "" : "*"); } } // private static Permission DefaultFallbackPermisson = Permission.Undecided; // private static PermissionResult DefaultFallbackPermissonResult = new PermissionResult(DefaultFallbackPermisson, true); private final List<PermissionBoard> boards = new LinkedList<>(); private final RequestRuleStore store; final String frameHost; public PermissionSystem(final String frameHost, final RequestRuleStore store) { this.frameHost = frameHost; this.store = store; final PermissionBoard defaultBoard = new PermissionBoard("*", Optional.empty()); final PermissionBoard frameBoard = new PermissionBoard("*." + frameHost, Optional.of(defaultBoard)); boards.add(defaultBoard); boards.add(frameBoard); } public boolean isRequestPermitted(final Request request) { final String protocol = request.url.getProtocol(); if ("http".equals(protocol) || "https".equals(protocol) || "data".equals(protocol)) { return getLastBoard().isRequestPermitted(request); } else if ("about".equals(protocol)) { return request.url.toString().equals("about:blank"); } else { // Constrain all other protocols. Especially worrying is the file:// protocol // ... for issue #18 return false; } } public List<PermissionBoard> getBoards() { return boards; } public PermissionBoard getLastBoard() { return boards.get(boards.size() - 1); } public void dump() { boards.get(0).dump(); getLastBoard().dump(); } public String getPermissionsAsString() { StringBuilder permissions = new StringBuilder(); for (PermissionBoard board : boards) { permissions.append(board.getBoardPermissionsAsString()); } return permissions.toString(); } static <T> Stream<T> streamOpt(final Optional<T> opt) { if (opt.isPresent()) { return Stream.of(opt.get()); } else { return Stream.empty(); } } public class PermissionBoard { private final Map<String, PermissionRow> requestHostMap = new HashMap<>(); private final Optional<PermissionRow> headerRowOpt; private final Optional<PermissionBoard> fallbackBoardOpt; final String hostPattern; public PermissionBoard(final String hostPattern, final Optional<PermissionBoard> fallbackBoardOpt) { final boolean gpCellCanBeUndecidable = fallbackBoardOpt.isPresent(); final Pair<Permission, Permission[]> headerPermissions = store.getPermissions(hostPattern, ""); final PermissionRow headerRow = new PermissionRow("", headerPermissions, Optional.empty(), fallbackBoardOpt.map(b -> b.headerRowOpt .get()), gpCellCanBeUndecidable); this.headerRowOpt = Optional.of(headerRow); this.fallbackBoardOpt = fallbackBoardOpt; this.hostPattern = hostPattern; } public PermissionRow getRow(final String requestHost) { final PermissionRow row = requestHostMap.get(requestHost); if (row == null) { final Optional<PermissionRow> fallbackRow = fallbackBoardOpt.map(b -> b.getRow(requestHost)); // final PermissionRow newRow = new PermissionRow(Permission.Undecided, getEmptyPermissions(), headerRowOpt, fallbackRow); final Pair<Permission, Permission[]> permissions = store.getPermissions(hostPattern, requestHost); final PermissionRow newRow = new PermissionRow(requestHost, permissions, headerRowOpt, fallbackRow, true); requestHostMap.put(requestHost, newRow); return newRow; } else { return row; } } public boolean isRequestPermitted(final Request request) { final String requestHost = request.url.getHost().toLowerCase(); final PermissionRow row = getRow(requestHost); return row.isRequestPermitted(request); } public void dump() { System.out.print(String.format("\n%30s", "all")); headerRowOpt.ifPresent(r -> r.dump()); System.out.println("\n ---------------------------------"); requestHostMap.forEach((host, row) -> { System.out.print(String.format("%30s", host)); row.dump(); System.out.println(""); }); } public String getBoardPermissionsAsString() { StringBuilder permissionState = new StringBuilder(); permissionState.append(headerRowOpt.isPresent() ? headerRowOpt.get().getRowPermissionsAsString() : ""); requestHostMap.forEach((host, row) -> { permissionState.append(row.getRowPermissionsAsString()); }); return permissionState.toString(); } public int getRowCount() { return requestHostMap.size(); } public List<Entry<String, PermissionRow>> getRows() { return requestHostMap.entrySet().stream().collect(Collectors.toList()); } public PermissionRow getHeaderRow() { return headerRowOpt.get(); } public class PermissionRow { private final PermissionCell hostCell; private final PermissionCell[] requestCells = new PermissionCell[RequestKind.numKinds()]; private final String requestHost; public PermissionRow(final String requestHost, final Pair<Permission, Permission[]> initialRequestPermissions, final Optional<PermissionRow> headerRowOpt, final Optional<PermissionRow> fallbackRowOpt, final boolean hostCanBeUndecidable) { this.requestHost = requestHost; final List<PermissionCell> hostParentList = streamOpt(headerRowOpt.map(r -> r.hostCell)).collect(Collectors.toList()); final Permission hostPermission = initialRequestPermissions.getValue0(); final Permission[] kindPermissions = initialRequestPermissions.getValue1(); hostCell = new PermissionCell(Optional.empty(), hostPermission, hostParentList, fallbackRowOpt.map(r -> r.hostCell), Optional.empty(), hostCanBeUndecidable); IntStream.range(0, requestCells.length).forEach(i -> { final LinkedList<PermissionCell> parentCells = makeParentCells(headerRowOpt, i); final Optional<PermissionCell> grandParentCellOpt = headerRowOpt.map(r -> r.hostCell); final Optional<PermissionCell> fallbackCellOpt = fallbackRowOpt.map(r -> r.requestCells[i]); final Optional<RequestKind> kindOpt = Optional.of(RequestKind.forOrdinal(i)); requestCells[i] = new PermissionCell(kindOpt, kindPermissions[i], parentCells, fallbackCellOpt, grandParentCellOpt, true); }); } private LinkedList<PermissionCell> makeParentCells(final Optional<PermissionRow> headerRowOpt, final int i) { final LinkedList<PermissionCell> parentCells = new LinkedList<>(); parentCells.add(hostCell); headerRowOpt.ifPresent(headerRow -> { parentCells.add(headerRow.requestCells[i]); }); return parentCells; } public boolean isRequestPermitted(final Request request) { return getPermission(request).permission == Permission.Allow; } public PermissionResult getPermission(final Request request) { final int requestOrdinal = request.kind.ordinal(); return requestCells[requestOrdinal].getEffectivePermission(); } public void dump() { Arrays.stream(requestCells).forEach(c -> { final PermissionResult permissionResult = c.getEffectivePermission(); final String permStr = permissionResult.permission.toString().substring(0, 1); final String permFmtStr = permissionResult.isDefault ? " " + permStr + " " : "[" + permStr + "]"; System.out.print(String.format(" %s%d", permFmtStr, c.parentCells.size())); }); } public String getRowPermissionsAsString() { StringBuilder rowPermissionState = new StringBuilder(); Arrays.stream(requestCells).forEach(c -> { final PermissionResult permissionResult = c.getEffectivePermission(); final String permStr = permissionResult.permission.toString().substring(0, 1); final String permFmtStr = permissionResult.isDefault ? " " + permStr + " " : "[" + permStr + "]"; rowPermissionState.append(permFmtStr); }); return rowPermissionState.toString(); } public PermissionCell getHostCell() { return hostCell; } public PermissionCell getRequestCell(final int i) { return requestCells[i]; } public class PermissionCell { private Permission myPermission; private final List<PermissionCell> parentCells; private final Optional<PermissionCell> grandParentCellOpt; private final Optional<PermissionCell> fallbackCellOpt; private final Optional<RequestKind> kindOpt; final boolean canBeUndecidable; public PermissionCell(final Optional<RequestKind> kind, final Permission startingPermission, final List<PermissionCell> parentCells, final Optional<PermissionCell> fallbackCellOpt, final Optional<PermissionCell> grandParentCellOpt, final boolean canBeUndecidable) { myPermission = startingPermission; this.parentCells = parentCells; this.fallbackCellOpt = fallbackCellOpt; this.grandParentCellOpt = grandParentCellOpt; this.kindOpt = kind; this.canBeUndecidable = canBeUndecidable; } public PermissionResult getEffectivePermission() { if (myPermission.isDecided()) { return new PermissionResult(myPermission, false); } else { final Permission firstCutPermission = computeFirstCutPermission(); if (firstCutPermission.isDecided()) { return new PermissionResult(firstCutPermission, true); } else { assert (parentCells.size() > 0); return parentCells.get(0).getEffectivePermission().makeDefault(); } } } private Permission computeFirstCutPermission() { if (parentExistsFor(Permission.Deny)) { return Permission.Deny; } else if (parentExistsFor(Permission.Allow)) { return Permission.Allow; } else { final Optional<PermissionResult> gpPermissionOpt = grandParentCellOpt.map(gp -> gp.getEffectivePermission()); final boolean gpDefault = gpPermissionOpt.map(gp -> gp.isDefault).orElse(true); if (gpDefault) { return fallbackCellOpt.map(c -> c.getEffectivePermission().permission).orElse(Permission.Undecided); } else { return gpPermissionOpt.get().permission; } } } private boolean parentExistsFor(final Permission permission) { return parentCells.stream().anyMatch(cell -> { final PermissionResult effectivePermission = cell.getEffectivePermission(); return (effectivePermission.permission == permission) && !effectivePermission.isDefault; }); } public void setPermission(final Permission permission) { myPermission = permission; store.storePermissions(hostPattern, requestHost, kindOpt, permission); } } } } static Permission[] getEmptyPermissions() { final Map<RequestKind, Permission> emptyPermissionMap = new HashMap<>(); final Permission[] emptyPermissions = flatten(emptyPermissionMap); return emptyPermissions; } public static Permission[] flatten(final Map<RequestKind, Permission> permissionMap) { final int numKinds = RequestKind.numKinds(); final Permission[] permissions = new Permission[numKinds]; IntStream.range(0, numKinds).forEach(i -> { permissions[i] = permissionMap.getOrDefault(RequestKind.forOrdinal(i), Permission.Undecided); }); return permissions; } }