Changeset d201a29


Ignore:
Timestamp:
May 25, 2011 1:52:18 PM (9 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
ab17bd3
Parents:
c4bbcc46
Message:
  • HTTP Proxy: Address helper refactoring, address book add form
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java

    rc4bbcc46 rd201a29  
    1919import java.util.List;
    2020import java.util.Locale;
     21import java.util.Map;
    2122import java.util.Properties;
    2223import java.util.StringTokenizer;
     24import java.util.concurrent.ConcurrentHashMap;
    2325
    2426import net.i2p.I2PAppContext;
    2527import net.i2p.I2PException;
     28import net.i2p.client.naming.NamingService;
    2629import net.i2p.client.streaming.I2PSocket;
    2730import net.i2p.client.streaming.I2PSocketManager;
     
    2932import net.i2p.data.Base32;
    3033import net.i2p.data.Base64;
     34import net.i2p.data.DataFormatException;
    3135import net.i2p.data.DataHelper;
    3236import net.i2p.data.Destination;
     
    6367public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runnable {
    6468
    65     private HashMap addressHelpers = new HashMap();
     69    /**
     70     *  Map of host name to base64 destination for destinations collected
     71     *  via address helper links
     72     */
     73    private final ConcurrentHashMap<String, String> addressHelpers = new ConcurrentHashMap(8);
     74
     75    /**
     76     *  Used to protect actions via http://proxy.i2p/
     77     */
     78    private final String _proxyNonce;
    6679
    6780    /**
     
    131144         .getBytes();
    132145
     146    private final static byte[] ERR_AHELPER_NEW =
     147        ("HTTP/1.1 409 New Address\r\n"+
     148         "Content-Type: text/html; charset=iso-8859-1\r\n"+
     149         "Cache-control: no-cache\r\n"+
     150         "\r\n"+
     151         "<html><body><H1>New Host Name with Address Helper</H1>"+
     152         "The address helper link you followed is for a new host name that is not in your address book. " +
     153         "You may either save the destination for this host name to your address book, or remember it only until your router restarts. " +
     154         "If you save it to your address book, you will not see this message again. " +
     155         "If you do not wish to visit this host, click the \"back\" button on your browser.")
     156         .getBytes();
     157
    133158    private final static byte[] ERR_BAD_PROTOCOL =
    134159        ("HTTP/1.1 403 Bad Protocol\r\n"+
     
    163188    public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
    164189        super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
     190        _proxyNonce = Long.toString(_context.random().nextLong());
    165191       // proxyList = new ArrayList();
    166192
     
    178204                               I2PTunnel tunnel) throws IllegalArgumentException {
    179205        super(localPort, ownDest, l, notifyThis, "HTTP Proxy on " + tunnel.listenHost + ':' + localPort + " #" + (++__clientId), tunnel);
     206        _proxyNonce = Long.toString(_context.random().nextLong());
    180207
    181208        //proxyList = new ArrayList(); // We won't use outside of i2p
     
    259286    }
    260287
     288    private static final String LOCAL_SERVER = "proxy.i2p";
    261289    private static final boolean DEFAULT_GZIP = true;
    262     // all default to false
     290    /** all default to false */
    263291    public static final String PROP_REFERER = "i2ptunnel.httpclient.sendReferer";
    264292    public static final String PROP_USER_AGENT = "i2ptunnel.httpclient.sendUserAgent";
    265293    public static final String PROP_VIA = "i2ptunnel.httpclient.sendVia";
    266294    public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers";
     295    public static final String PROP_DISABLE_HELPER = "i2ptunnel.httpclient.disableAddressHelper";
    267296
    268297    protected void clientConnectionRun(Socket s) {
     
    279308            String line, method = null, protocol = null, host = null, destination = null;
    280309            StringBuilder newRequest = new StringBuilder();
    281             int ahelper = 0;
     310            boolean ahelperPresent = false;
     311            boolean ahelperNew = false;
     312            String ahelperKey = null;
     313            String userAgent = null;
    282314            String authorization = null;
    283315            while ((line = reader.readLine(method)) != null) {
     
    335367                    request = request.substring(pos + 2);
    336368
     369                    // "foo.i2p/bar/baz HTTP/1.1", with any i2paddresshelper parameter removed
    337370                    targetRequest = request;
    338371
     
    381414                        host = getHostName(destination);
    382415                        line = method + ' ' + request.substring(pos);
    383                     } else if (host.toLowerCase().equals("proxy.i2p")) {
     416                    } else if (host.toLowerCase().equals(LOCAL_SERVER)) {
    384417                        // so we don't do any naming service lookups
    385418                        destination = host;
     
    388421                        // Destination gets the host name
    389422                        destination = host;
    390                         // Host becomes the destination key
     423                        // Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure
    391424                        host = getHostName(destination);
    392425
     
    395428                            // Try to find an address helper in the fragments
    396429                            // and split the request into it's component parts for rebuilding later
    397                             String ahelperKey = null;
    398430                            boolean ahelperConflict = false;
    399431
     
    405437                            fragments = fragments.substring(0, pos2);
    406438                            String initialFragments = fragments;
     439                            // FIXME split on ';' also
    407440                            fragments = fragments + "&";
    408441                            String fragment;
     
    413446
    414447                                // Fragment looks like addresshelper key
    415                                 if (fragment.startsWith("i2paddresshelper=")) {
     448                                if (fragment.startsWith("i2paddresshelper=") &&
     449                                    !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
    416450                                    pos2 = fragment.indexOf("=");
    417451                                    ahelperKey = fragment.substring(pos2 + 1);
     
    419453                                    // Key contains data, lets not ignore it
    420454                                    if (ahelperKey != null) {
     455                                        ahelperPresent = true;
    421456                                        // ahelperKey will be validated later
    422 
    423                                         // Host resolvable only with addresshelper
    424                                         if ( (host == null) || ("i2p".equals(host)) )
    425                                         {
    426                                             // Cannot check, use addresshelper key
    427                                             addressHelpers.put(destination,ahelperKey);
    428                                         } else {
    429                                             // Host resolvable from database, verify addresshelper key
    430                                             // Silently bypass correct keys, otherwise alert
    431                                             String destB64 = null;
    432                                             Destination _dest = _context.namingService().lookup(host);
    433                                             if (_dest != null)
    434                                                 destB64 = _dest.toBase64();
    435                                             if (destB64 != null && !destB64.equals(ahelperKey))
    436                                             {
     457                                        if (host == null || "i2p".equals(host)) {
     458                                            // Host lookup failed - resolvable only with addresshelper
     459                                            // Store in local HashMap unless there is conflict
     460                                            String old = addressHelpers.putIfAbsent(destination.toLowerCase(), ahelperKey);
     461                                            ahelperNew = old == null;
     462                                            if ((!ahelperNew) && !old.equals(ahelperKey)) {
    437463                                                // Conflict: handle when URL reconstruction done
    438464                                                ahelperConflict = true;
    439465                                                if (_log.shouldLog(Log.WARN))
    440                                                     _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
     466                                                    _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination +
     467                                                              "], trusted key [" + old + "], specified key [" + ahelperKey + "].");
     468                                            }
     469                                        } else {
     470                                            // If the host is resolvable from database, verify addresshelper key
     471                                            // Silently bypass correct keys, otherwise alert
     472                                            Destination hostDest = _context.namingService().lookup(destination);
     473                                            if (hostDest != null) {
     474                                                String destB64 = hostDest.toBase64();
     475                                                if (destB64 != null && !destB64.equals(ahelperKey)) {
     476                                                    // Conflict: handle when URL reconstruction done
     477                                                    ahelperConflict = true;
     478                                                    if (_log.shouldLog(Log.WARN))
     479                                                        _log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination +
     480                                                                  "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
     481                                                }
    441482                                            }
    442483                                        }
    443                                     }
     484                                    } // ahelperKey
    444485                                } else {
    445486                                    // Other fragments, just pass along
     
    454495                            // Reconstruct the request minus the i2paddresshelper GET var
    455496                            request = uriPath + urlEncoding + " " + protocolVersion;
     497                            targetRequest = request;
    456498
    457499                            // Did addresshelper key conflict?
    458                             if (ahelperConflict)
    459                             {
    460 
     500                            if (ahelperConflict) {
    461501                                if (out != null) {
    462502                                    // convert ahelperKey to b32
     
    480520                                return;
    481521                            }
    482                         }
    483 
    484                         String addressHelper = (String) addressHelpers.get(destination);
    485                         if (addressHelper != null) {
    486                             destination = addressHelper;
    487                             host = getHostName(destination);
    488                             ahelper = 1;
    489                         }
     522                        }  // end query processing
     523
     524                        String addressHelper = addressHelpers.get(destination);
     525                        if (addressHelper != null)
     526                            host = getHostName(addressHelper);
    490527
    491528                        line = method + " " + request.substring(pos);
     529                        // end of (host endsWith(".i2p"))
     530
    492531                    } else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1") ||
    493532                               host.startsWith("192.168.")) {
     
    585624                        if (_log.shouldLog(Log.INFO))
    586625                            _log.info(getPrefix(requestId) + "Setting host = " + host);
    587                     } else if (lowercaseLine.startsWith("user-agent: ") &&
    588                                !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
    589                         line = null;
    590                         continue;
     626                    } else if (lowercaseLine.startsWith("user-agent: ")) {
     627                        // save for deciding whether to offer address book form
     628                        userAgent = lowercaseLine.substring(12);
     629                        if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
     630                            line = null;
     631                            continue;
     632                        }
    591633                    } else if (lowercaseLine.startsWith("accept")) {
    592634                        // strip the accept-blah headers, as they vary dramatically from
     
    688730                _log.debug(getPrefix(requestId) + "Destination: " + destination);
    689731
    690             // Serve local proxy files (images, css linked from error pages)
    691             // Ignore all the headers
    692             // Allow without authorization
    693             if (usingInternalServer) {
    694                 serveLocalFile(out, method, targetRequest);
    695                 s.close();
    696                 return;
    697             }
    698 
    699732            // Authorization
    700733            if (!authorize(s, requestId, authorization)) {
     
    706739                }
    707740                out.write(getErrorPage("auth", ERR_AUTH));
     741                writeFooter(out);
    708742                s.close();
    709743                return;
    710744            }
    711745
     746            // Serve local proxy files (images, css linked from error pages)
     747            // Ignore all the headers
     748            if (usingInternalServer) {
     749                // disable the add form if address helper is disabled
     750                if (targetRequest.startsWith(LOCAL_SERVER + "/add?") &&
     751                    Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
     752                    out.write(ERR_HELPER_DISABLED);
     753                } else {
     754                    serveLocalFile(out, method, targetRequest, _proxyNonce);
     755                }
     756                s.close();
     757                return;
     758            }
     759
     760            // LOOKUP
    712761            // If the host is "i2p", the getHostName() lookup failed, don't try to
    713762            // look it up again as the naming service does not do negative caching
    714763            // so it will be slow.
    715 
    716764            Destination clientDest;
    717             if ("i2p".equals(host))
     765            String addressHelper = addressHelpers.get(destination.toLowerCase());
     766            if (addressHelper != null) {
     767                clientDest = _context.namingService().lookup(addressHelper);
     768                // remove bad entries
     769                if (clientDest == null)
     770                    addressHelpers.remove(destination.toLowerCase());
     771            } else if ("i2p".equals(host)) {
    718772                clientDest = null;
    719             else
     773            } else {
    720774                clientDest = _context.namingService().lookup(destination);
     775            }
    721776
    722777            if (clientDest == null) {
     
    728783                if (usingWWWProxy)
    729784                    header = getErrorPage("dnfp", ERR_DESTINATION_UNKNOWN);
    730                 else if(ahelper != 0)
     785                else if (ahelperPresent)
    731786                    header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
    732787                else if (destination.length() == 60 && destination.endsWith(".b32.i2p"))
     
    742797                return;
    743798            }
    744             String remoteID;
     799
     800            // Address helper response form
     801            // This will only load once - the second time it won't be "new"
     802            // Don't do this for eepget, which uses a user-agent of "Wget"
     803            if (ahelperNew && "GET".equals(method) &&
     804                (userAgent == null || !userAgent.startsWith("Wget")) &&
     805                !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) {
     806                writeHelperSaveForm(out, destination, ahelperKey, protocol + targetRequest);
     807                s.close();
     808                return;
     809            }
     810
     811            // Redirect to non-addresshelper URL to not clog the browser address bar
     812            // and not pass the parameter to the eepsite.
     813            // This also prevents the not-found error page from looking bad
     814            if (ahelperPresent) {
     815                String uri = protocol + targetRequest;
     816                int spc = uri.indexOf(" ");
     817                if (spc >= 0)
     818                    uri = uri.substring(0, spc);
     819                if (_log.shouldLog(Log.DEBUG))
     820                    _log.debug("Auto redirecting to " + uri);
     821                out.write(("HTTP/1.1 301 Address Helper Accepted\r\n"+
     822                          "Location: " + uri + "\r\n"+
     823                          "\r\n").getBytes("UTF-8"));
     824                s.close();
     825                return;
     826            }
    745827
    746828            Properties opts = new Properties();
     
    780862    }
    781863
     864    /** @since 0.8.7 */
     865    private void writeHelperSaveForm(OutputStream out, String destination, String ahelperKey, String targetRequest) throws IOException {
     866        if (out == null)
     867            return;
     868        // strip HTTP/1.1
     869        int protopos = targetRequest.indexOf(" ");
     870        if (protopos >= 0)
     871            targetRequest = targetRequest.substring(0, protopos);
     872        byte[] header = getErrorPage("ahelper-new", ERR_AHELPER_NEW);
     873        out.write(header);
     874        out.write(("<table><tr><td class=\"mediumtags\" align=\"right\">" + _("Host") + "</td><td class=\"mediumtags\">" + destination + "</td></tr>\n" +
     875                   "<tr><td class=\"mediumtags\" align=\"right\">" + _("Destination") + "</td><td>" +
     876                   "<textarea rows=\"1\" style=\"height: 4em; min-width: 0; min-height: 0;\" cols=\"70\" wrap=\"off\" readonly=\"readonly\" >" +
     877                   ahelperKey + "</textarea></td></tr></table>\n" +
     878                   "<hr><div class=\"formaction\">"+
     879                   "<form method=\"GET\" action=\"" + targetRequest + "\">" +
     880                   "<button type=\"submit\">" + _("Continue to {0} without saving", destination) + "</button>" +
     881                   "</form>\n<form method=\"GET\" action=\"http://" + LOCAL_SERVER + "/add\">" +
     882                   "<input type=\"hidden\" name=\"host\" value=\"" + destination + "\">\n" +
     883                   "<input type=\"hidden\" name=\"dest\" value=\"" + ahelperKey + "\">\n" +
     884                   "<input type=\"hidden\" name=\"nonce\" value=\"" + _proxyNonce + "\">\n" +
     885                   "<button type=\"submit\" name=\"router\" value=\"router\">" + _("Save {0} to router address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8"));
     886        if (_context.namingService().getName().equals("BlockfileNamingService")) {
     887            // only blockfile supports multiple books
     888            out.write(("<button type=\"submit\" name=\"master\" value=\"master\">" + _("Save {0} to master address book and continue to eepsite", destination) + "</button><br>\n").getBytes("UTF-8"));
     889            out.write(("<button type=\"submit\" name=\"private\" value=\"private\">" + _("Save {0} to private address book and continue to eepsite", destination) + "</button>\n").getBytes("UTF-8"));
     890        }
     891        out.write(("<input type=\"hidden\" name=\"url\" value=\"" + targetRequest + "\">\n" +
     892                   "</form></div></div>").getBytes());
     893        writeFooter(out);
     894    }
     895
    782896    /**
    783897     *  Read the first line unbuffered.
     
    9161030            if (targetRequest != null) {
    9171031                int protopos = targetRequest.indexOf(" ");
    918                 String uri = null;
     1032                String uri;
    9191033                if (protopos >= 0)
    9201034                    uri = targetRequest.substring(0, protopos);
     
    10211135         "\r\n"+
    10221136         "HTTP Proxy local file not found")
     1137        .getBytes();
     1138
     1139    private final static byte[] ERR_ADD =
     1140        ("HTTP/1.1 409 Bad\r\n"+
     1141         "Content-Type: text/plain\r\n"+
     1142         "\r\n"+
     1143         "Add to addressbook failed - bad parameters")
     1144        .getBytes();
     1145
     1146    private final static byte[] ERR_HELPER_DISABLED =
     1147        ("HTTP/1.1 403 Disabled\r\n"+
     1148         "Content-Type: text/plain\r\n"+
     1149         "\r\n"+
     1150         "Address helpers disabled")
    10231151        .getBytes();
    10241152
     
    10441172     *  @param targetRequest "proxy.i2p/themes/foo.png HTTP/1.1"
    10451173     */
    1046     private static void serveLocalFile(OutputStream out, String method, String targetRequest) {
     1174    private static void serveLocalFile(OutputStream out, String method, String targetRequest, String proxyNonce) {
     1175        //System.err.println("targetRequest: \"" + targetRequest + "\"");
    10471176        // a home page message for the curious...
    1048         if (targetRequest.startsWith("proxy.i2p/ ")) {
     1177        if (targetRequest.startsWith(LOCAL_SERVER + "/ ")) {
    10491178            try {
    10501179                out.write(("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nCache-Control: max-age=86400\r\n\r\nI2P HTTP proxy OK").getBytes());
     
    10541183        }
    10551184        if ((method.equals("GET") || method.equals("HEAD")) &&
    1056             targetRequest.startsWith("proxy.i2p/themes/") &&
     1185            targetRequest.startsWith(LOCAL_SERVER + "/themes/") &&
    10571186            !targetRequest.contains("..")) {
    10581187            int space = targetRequest.indexOf(' ');
    10591188            String filename = null;
    10601189            try {
    1061                 filename = targetRequest.substring(17, space); // "proxy.i2p/themes/".length
     1190                filename = targetRequest.substring(LOCAL_SERVER.length() + 8, space); // "/themes/".length
    10621191            } catch (IndexOutOfBoundsException ioobe) {}
    10631192            // theme hack
     
    10821211                    out.write("\r\nCache-Control: max-age=86400\r\n\r\n".getBytes());
    10831212                    FileUtil.readFile(filename, themesDir.getAbsolutePath(), out);
     1213                } catch (IOException ioe) {}
     1214                return;
     1215            }
     1216        }
     1217
     1218        // Add to addressbook (form submit)
     1219        // Parameters are url, host, dest, nonce, and master | router | private.
     1220        // Do the add and redirect.
     1221        if (targetRequest.startsWith(LOCAL_SERVER + "/add?")) {
     1222            int spc = targetRequest.indexOf(' ');
     1223            String query = targetRequest.substring(LOCAL_SERVER.length() + 5, spc);   // "/add?".length()
     1224            Map<String, String> opts = new HashMap(8);
     1225            StringTokenizer tok = new StringTokenizer(query, "=&;");
     1226            while (tok.hasMoreTokens()) {
     1227                String k = tok.nextToken();
     1228                if (!tok.hasMoreTokens())
     1229                    break;
     1230                String v = tok.nextToken();
     1231                opts.put(decode(k), decode(v));
     1232            }
     1233
     1234            String url = opts.get("url");
     1235            String host = opts.get("host");
     1236            String b64Dest = opts.get("dest");
     1237            String nonce = opts.get("nonce");
     1238            String book = "privatehosts.txt";
     1239            if (opts.get("master") != null)
     1240                book = "userhosts.txt";
     1241            else if (opts.get("router") != null)
     1242                book = "hosts.txt";
     1243            Destination dest = null;
     1244            if (b64Dest != null) {
     1245                try {
     1246                    dest = new Destination(b64Dest);
     1247                } catch (DataFormatException dfe) {
     1248                    System.err.println("Bad dest to save?" + b64Dest);
     1249                }
     1250            }
     1251            //System.err.println("url          : \"" + url           + "\"");
     1252            //System.err.println("host         : \"" + host          + "\"");
     1253            //System.err.println("b64dest      : \"" + b64Dest       + "\"");
     1254            //System.err.println("book         : \"" + book          + "\"");
     1255            //System.err.println("nonce        : \"" + nonce         + "\"");
     1256            if (proxyNonce.equals(nonce) && url != null && host != null && dest != null) {
     1257                try {
     1258                    NamingService ns = I2PAppContext.getGlobalContext().namingService();
     1259                    Properties nsOptions = new Properties();
     1260                    nsOptions.setProperty("list", book);
     1261                    nsOptions.setProperty("s", _("Added via address helper"));
     1262                    boolean success = ns.put(host, dest, nsOptions);
     1263                    writeRedirectPage(out, success, host, book, url);
    10841264                    return;
    10851265                } catch (IOException ioe) {}
    10861266            }
     1267            try {
     1268                out.write(ERR_ADD);
     1269                out.flush();
     1270            } catch (IOException ioe) {}
     1271            return;
    10871272        }
    10881273        try {
     
    10921277    }
    10931278
     1279    /** @since 0.8.7 */
     1280    private static void writeRedirectPage(OutputStream out, boolean success, String host, String book, String url) throws IOException {
     1281        out.write(("HTTP/1.1 200 OK\r\n"+
     1282                  "Content-Type: text/html; charset=UTF-8\r\n"+
     1283                  "\r\n"+
     1284                  "<html><head>"+
     1285                  "<title>" + _("Redirecting to {0}", host) + "</title>\n" +
     1286                  "<link rel=\"shortcut icon\" href=\"http://proxy.i2p/themes/console/images/favicon.ico\" >\n" +
     1287                  "<link href=\"http://proxy.i2p/themes/console/default/console.css\" rel=\"stylesheet\" type=\"text/css\" >\n" +
     1288                  "<meta http-equiv=\"Refresh\" content=\"1; url=" + url + "\">\n" +
     1289                  "</head><body>\n" +
     1290                  "<div class=logo>\n" +
     1291                  "<a href=\"http://127.0.0.1:7657/\" title=\"" + _("Router Console") + "\"><img src=\"http://proxy.i2p/themes/console/images/i2plogo.png\" alt=\"I2P Router Console\" border=\"0\"></a><hr>\n" +
     1292                  "<a href=\"http://127.0.0.1:7657/config\">" + _("Configuration") + "</a> <a href=\"http://127.0.0.1:7657/help.jsp\">" + _("Help") + "</a> <a href=\"http://127.0.0.1:7657/susidns/index.jsp\">" + _("Addressbook") + "</a>\n" +
     1293                  "</div>" +
     1294                  "<div class=warning id=warning>\n" +
     1295                  "<h3>" +
     1296                  (success ?
     1297                           _("Saved {0} to the {1} addressbook, redirecting now.", host, book) :
     1298                           _("Failed to save {0} to the {1} addressbook, redirecting now.", host, book)) +
     1299                  "</h3>\n<p><a href=\"" + url + "\">" +
     1300                  _("Click here if you are not redirected automatically.") +
     1301                  "</a></p></div>").getBytes("UTF-8"));
     1302        writeFooter(out);
     1303        out.flush();
     1304    }
     1305
     1306    private static String decode(String s) {
     1307        if (!s.contains("%"))
     1308            return s;
     1309        StringBuilder buf = new StringBuilder(s.length());
     1310        for (int i = 0; i < s.length(); i++) {
     1311            char c = s.charAt(i);
     1312            if (c != '%') {
     1313                buf.append(c);
     1314            } else {
     1315                try {
     1316                    buf.append((char) Integer.parseInt(s.substring(++i, (++i) + 1), 16));
     1317                } catch (IndexOutOfBoundsException ioobe) {
     1318                    break;
     1319                } catch (NumberFormatException nfe) {
     1320                    break;
     1321                }
     1322            }
     1323        }
     1324        return buf.toString();
     1325    }
    10941326
    10951327    private static final String BUNDLE_NAME = "net.i2p.i2ptunnel.web.messages";
    10961328
    10971329    /** lang in routerconsole.lang property, else current locale */
    1098     public static String _(String key) {
     1330    protected static String _(String key) {
    10991331        return Translate.getString(key, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
    11001332    }
    11011333
     1334    /** {0} */
     1335    protected static String _(String key, Object o) {
     1336        return Translate.getString(key, o, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
     1337    }
     1338
    11021339    /** {0} and {1} */
    1103     public static String _(String key, Object o, Object o2) {
     1340    protected static String _(String key, Object o, Object o2) {
    11041341        return Translate.getString(key, o, o2, I2PAppContext.getGlobalContext(), BUNDLE_NAME);
    11051342    }
  • history.txt

    rc4bbcc46 rd201a29  
     12011-05-25 zzz
     2    * CPUID: Load 64-bit libcpuid if available
     3    * HTTP Proxy: Address helper refactoring, address book add form
     4    * Naming: B32 fixes
     5    * NetDB: Increase floodfills again
     6
    172011-05-23 zzz
    28    * Console:
  • router/java/src/net/i2p/router/RouterVersion.java

    rc4bbcc46 rd201a29  
    1919    public final static String ID = "Monotone";
    2020    public final static String VERSION = CoreVersion.VERSION;
    21     public final static long BUILD = 5;
     21    public final static long BUILD = 6;
    2222
    2323    /** for example "-test" */
Note: See TracChangeset for help on using the changeset viewer.