All Implemented Interfaces:
Runnable, EventDispatcher
Direct Known Subclasses:
I2PTunnelConnectClient, I2PTunnelHTTPClient

public abstract class I2PTunnelHTTPClientBaseextends I2PTunnelClientBase implements Runnable
Common things for HTTPClient and ConnectClient Retrofit over them in 0.8.2.
  • Field Details

    • PROXYNONCE_BYTES

      private static final int PROXYNONCE_BYTES
      Length of the proxy nonce, in bytes.
      See Also:
    • SHA256_BYTES

      private static final int SHA256_BYTES
      Length of a SHA-256 hash, in bytes.
      See Also:
    • NONCE_BYTES

      private static final int NONCE_BYTES
      Nonce size in bytes (date + SHA256).
      See Also:
    • MAX_NONCE_AGE

      private static final long MAX_NONCE_AGE
      Age after which a used nonce is forgotten.
      See Also:
    • MAX_NONCE_COUNT

      private static final int MAX_NONCE_COUNT
      Nonces tracked per user.
      See Also:
    • PROP_USE_OUTPROXY_PLUGIN

      public static final String PROP_USE_OUTPROXY_PLUGIN
      Property to use local outproxy plugin.
      See Also:
    • PROP_SSL_OUTPROXIES

      public static final String PROP_SSL_OUTPROXIES
      Property for SSL outproxies.
      See Also:
    • SLASH

      private static final String SLASH
      The platform file separator.
    • INITIAL_SO_TIMEOUT

      protected static final int INITIAL_SO_TIMEOUT
      This is a standard soTimeout, not a total timeout. We have no slowloris protection on the client side. See I2PTunnelHTTPServer or SAM's ReadLine if we need that.
      See Also:
    • SHED_MAX_REFRESH

      static final int SHED_MAX_REFRESH
      How many meta-refresh pages to serve before the final 503.
      See Also:
    • SHED_REFRESH_SECONDS

      static final int SHED_REFRESH_SECONDS
      Refresh delay in seconds for the intermediate shed page.
      See Also:
    • SHED_READ_TIMEOUT_MS

      static final int SHED_READ_TIMEOUT_MS
      Short SO_TIMEOUT while reading the shed request head (never on the hot path).
      See Also:
    • BROWSER_READ_TIMEOUT

      protected static final int BROWSER_READ_TIMEOUT
      Socket read timeout for a browser fetch, four hours as a failsafe.
      See Also:
    • ERR_AUTH1

      private static final String ERR_AUTH1
      Headers of the proxy authentication error response.
      See Also:
    • ERR_AUTH2

      private static final String ERR_AUTH2
      Body of the proxy authentication error response.
      See Also:
    • _proxyList

      protected final List<String> _proxyList
      List of configured outproxies.
    • ERR_NO_OUTPROXY

      protected static final String ERR_NO_OUTPROXY
      Error response when no outproxy is configured.
      See Also:
    • ERR_DESTINATION_UNKNOWN

      protected static final String ERR_DESTINATION_UNKNOWN
      Error response when destination is not found.
      See Also:
    • SUCCESS_RESPONSE

      protected static final String SUCCESS_RESPONSE
      HTTP 200 response for established CONNECT tunnels.
      See Also:
    • _proxyNonce

      private final byte[] _proxyNonce
      The nonce this tunnel issues with.
    • _nonces

      Used nonces, by user name.
    • _nonceCleanCounter

      private final AtomicInteger _nonceCleanCounter
      Counter scheduling the nonce cleanup.
    • _proxyCache

      private final Map<String,String> _proxyCache
      Selected HTTP proxy, by clearnet host.
    • _lastFailedProxy

      private String _lastFailedProxy
      The last HTTP proxy that failed.
    • _proxySSLCache

      private final Map<String,String> _proxySSLCache
      Selected HTTPS proxy, by clearnet host.
    • _lastFailedSSLProxy

      private String _lastFailedSSLProxy
      The last HTTPS proxy that failed.
    • _haveIDN

      private static final boolean _haveIDN
      Available as of Java 6 and Android API 9.
    • DEFAULT_READ_TIMEOUT

      protected static final int DEFAULT_READ_TIMEOUT
      -1 (forever) as of 0.9.36, so that large POSTs won't timeout on the read side.
      See Also:
    • __requestId

      protected static final AtomicLong __requestId
      Counter for unique request IDs.
    • PROP_AUTH

      public static final String PROP_AUTH
      All auth.
      See Also:
    • PROP_USER

      public static final String PROP_USER
      Proxy username property.
      See Also:
    • PROP_PW

      public static final String PROP_PW
      Proxy password property.
      See Also:
    • PROP_PW_PREFIX

      public static final String PROP_PW_PREFIX
      Additional users may be added with proxyPassword.user=pw.
      See Also:
    • PROP_OUTPROXY_AUTH

      public static final String PROP_OUTPROXY_AUTH
      Outproxy auth property.
      See Also:
    • PROP_OUTPROXY_USER

      public static final String PROP_OUTPROXY_USER
      Outproxy username property.
      See Also:
    • PROP_OUTPROXY_PW

      public static final String PROP_OUTPROXY_PW
      Outproxy password property.
      See Also:
    • PROP_OUTPROXY_USER_PREFIX

      public static final String PROP_OUTPROXY_USER_PREFIX
      Passwords for specific outproxies may be added with outproxyUsername.fooproxy.i2p=user and outproxyPassword.fooproxy.i2p=pw.
      See Also:
    • PROP_OUTPROXY_PW_PREFIX

      public static final String PROP_OUTPROXY_PW_PREFIX
      Outproxy password property prefix.
      See Also:
    • PROP_PROXY_DIGEST_PREFIX

      public static final String PROP_PROXY_DIGEST_PREFIX
      New style MD5 auth.
      See Also:
    • PROP_PROXY_DIGEST_SUFFIX

      public static final String PROP_PROXY_DIGEST_SUFFIX
      MD5 digest suffix.
      See Also:
    • PROP_PROXY_DIGEST_SHA256_SUFFIX

      public static final String PROP_PROXY_DIGEST_SHA256_SUFFIX
      SHA-256 digest suffix.
      See Also:
    • BASIC_AUTH

      public static final String BASIC_AUTH
      Basic auth type constant.
      See Also:
    • DIGEST_AUTH

      public static final String DIGEST_AUTH
      Digest auth type constant.
      See Also:
    • MAX_LOG_VALUE

      private static final int MAX_LOG_VALUE
      Maximum length of an attacker-supplied value (e.g. a username) echoed to the log.
      See Also:
    • AUTH_REDACTED_KEYS

      private static final Set<String> AUTH_REDACTED_KEYS
      Digest arguments whose values are credential material and are replaced wholesale before logging. The response hash is replayable inside its nonce window. The username and nonce pair identifies the credential being targeted; cnonce is the client half of the nonce.
    • BUNDLE_NAME

      private static final String BUNDLE_NAME
      These strings go in the jar, not the war.
      See Also:
  • Constructor Details

    • I2PTunnelHTTPClientBase

      public I2PTunnelHTTPClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName, I2PTunnel tunnel) throws IllegalArgumentException
      Create a new HTTP client tunnel.
      Parameters:
      localPort - the local port to bind to
      ownDest - whether to use our own destination
      l - the Logging instance this tunnel's status messages are sent to
      notifyThis - event dispatcher for notifications
      handlerName - the handler name
      tunnel - the parent I2PTunnel instance
      Throws:
      IllegalArgumentException
    • I2PTunnelHTTPClientBase

      public I2PTunnelHTTPClientBase(int localPort, Logging l, I2PSocketManager sktMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) throws IllegalArgumentException
      This constructor always starts the tunnel (ignoring the i2cp.delayOpen option). It is used to add a client to an existing socket manager.
      Parameters:
      localPort - the local port to bind to
      l - the Logging instance this tunnel's status messages are sent to
      sktMgr - the existing socket manager
      tunnel - the parent I2PTunnel instance
      notifyThis - event dispatcher for notifications
      clientId - the client identifier
      Throws:
      IllegalArgumentException
  • Method Details

    • getPrefix

      protected String getPrefix(long requestId)
      Generates a unique log prefix for request tracking.

      This method creates a consistent prefix string that includes the request ID for correlating log entries across multiple threads and connections.

      Parameters:
      requestId - the unique identifier for this request
      Returns:
      a formatted string suitable for logging
    • selectProxy

      protected String selectProxy(String host)
      Selects an appropriate outproxy for the given host from the proxy pool.

      The selection is random, but sticky per host: the first chosen proxy for a host is cached and reused. Failed proxies are remembered and temporarily skipped.

      Parameters:
      host - the target hostname to route through the outproxy (may be used for stickiness)
      Returns:
      the selected proxy destination string (Base32 address), or null if no proxies configured
      See Also:
    • selectSSLProxy

      protected String selectSSLProxy(String host)
      Selects an SSL-capable outproxy for HTTPS connections.

      This method is similar to selectProxy(String) but only considers outproxies configured for SSL support (via i2ptunnel.httpclient.SSLOutproxies).

      Unlike selectProxy(), we parse the option on the fly so it can be changed without restart. ConnectClient should use selectProxy().

      Parameters:
      host - the target hostname (may be used for proxy stickiness)
      Returns:
      the selected SSL proxy destination, or null if none configured
      See Also:
    • selectProxy

      private String selectProxy(String host, Object lock, List<String> proxies, String lastFailed, Map<String,String> cache, String logPrefix)
      Select an outproxy for the given host with load balancing. Failed proxies are remembered and temporarily skipped.
      Parameters:
      host - the target hostname (may be used for proxy stickiness)
      lock - the monitor guarding the proxy state
      proxies - the configured outproxies
      lastFailed - the last failed proxy, or null
      cache - clearnet host to proxy stickiness cache
      logPrefix - the message prefix
      Returns:
      the selected proxy destination string, or null if none configured
    • noteProxyResult

      protected void noteProxyResult(String proxy, String host, boolean isSSL, boolean ok)
      Update the cache and note if failed.
      Parameters:
      proxy - the outproxy name to record, or null to note nothing
      host - clearnet hostname targeted
      isSSL - set to FALSE for ConnectClient
      ok - success or failure
    • getRealm

      protected abstract String getRealm()
      Get the authentication realm string.
      Returns:
      realm string
    • optionsUpdated

      public void optionsUpdated(I2PTunnel tunnel)
      Update the outproxy list then call super.
      Overrides:
      optionsUpdated in class I2PTunnelClientBase
      Parameters:
      tunnel - the tunnel whose client options were updated
    • isDigestAuthRequired

      protected boolean isDigestAuthRequired()
      Checks if HTTP Digest authentication is required by the outproxy.

      This method checks the configured authentication requirements and determines whether the current request requires digest authentication.

      Returns:
      true if digest authentication is required, false otherwise
    • writeShedResponse

      protected void writeShedResponse(Socket s)
      Write a real HTTP status on a shed connection so the browser never sees an empty response: HTML GET navigations get a cookie-capped meta-refresh page (up to SHED_MAX_REFRESH times), everything else gets a 503. Falls back to a plain 503 if the request head cannot be read in time.
      Overrides:
      writeShedResponse in class I2PTunnelClientBase
      Parameters:
      s - the accepted socket being shed; never null
    • parseShedAttempt

      static int parseShedAttempt(String cookie)
      How many shed meta-refresh attempts the request's Cookie header already used.
      Parameters:
      cookie - the full Cookie header value, or null
      Returns:
      parsed attempt count, or 0 when absent/unparseable
    • shouldMetaRefresh

      static boolean shouldMetaRefresh(String method, String accept, int attempt)
      Whether a shed connection should get a meta-refresh page instead of a 503. Only HTML navigations qualify: GET with an Accept header that asks for text/html, and fewer than SHED_MAX_REFRESH prior attempts.
      Parameters:
      method - HTTP method from the request line, or null
      accept - Accept header value, or null
      attempt - prior attempts from the shed cookie
      Returns:
      true to emit a meta-refresh page
    • buildShedRefreshResponse

      static String buildShedRefreshResponse(int nextAttempt)
      Intermediate shed page: unquoted meta refresh (no nested quotes in the tag) plus a Set-Cookie bump so the browser retries at most SHED_MAX_REFRESH times before the final 503.
      Parameters:
      nextAttempt - the attempt count to store in the cookie (1-based after increment)
      Returns:
      a complete HTTP/1.1 200 response with HTML body
    • buildShed503Response

      static String buildShed503Response()
      Final shed response after refresh budget is exhausted (or non-HTML).
      Returns:
      a complete HTTP/1.1 503 response
    • authorize

      protected I2PTunnelHTTPClientBase.AuthResult authorize(Socket s, long requestId, String method, String authorization)
      Authorization Ref: RFC 2617 If the socket is an InternalSocket, no auth required.
      Parameters:
      s - the client socket
      requestId - the unique request identifier for logging
      method - GET, POST, etc.
      authorization - may be null, the full auth line e.g. "Basic lskjlksjf"
      Returns:
      AUTH_GOOD when no authorization is required or the supplied credentials were accepted, otherwise AUTH_BAD when they were rejected or AUTH_BAD_REQ when the auth line was malformed
    • validateDigest

      private I2PTunnelHTTPClientBase.AuthResult validateDigest(String method, Map<String,String> args, Socket s)
      Verify all of it. Ref: RFC 2617.
      Parameters:
      method - the HTTP method from the request line, hashed as part of the digest
      args - the parsed Authorization header parameters, keyed by name
      s - just to log the IP on failure
      Returns:
      AUTH_GOOD if everything checks out, otherwise AUTH_BAD, AUTH_STALE or AUTH_BAD_REQ
    • summarizeAuthorization

      static String summarizeAuthorization(String authorization)
      Summarize an Authorization header for logging without disclosing credential material. Basic auth carries a decodable user:password pair, so only the scheme and the credential byte count are logged. The scheme is attacker-supplied too, so anything with line breaks (log injection) or an implausible length is replaced.
      Parameters:
      authorization - the full header value e.g. "Basic dXNlcjpwYXNz" (no "Proxy-Authorization:" prefix), may be null
      Returns:
      e.g. "Basic (13 bytes)", never the credentials; "null" if null and "malformed" for a hostile scheme
    • sanitizeLogValue

      static String sanitizeLogValue(String value)
      Strip line breaks and cap the length of an attacker-supplied value before it is written to the log, so a hostile username cannot forge extra log lines.
      Parameters:
      value - the raw value, may be null
      Returns:
      the sanitized value, empty string if null
    • sanitizeAuthArgs

      static Map<String,String> sanitizeAuthArgs(Map<String,String> args)
      Copy digest request arguments into a form safe to log: credential material is redacted, and every remaining value is stripped of line breaks and capped, so a hostile upstream cannot forge extra log lines or grow the log without bound.

      Non-secret protocol parameters (qop, nc, algorithm, realm, uri) are kept because they are what makes a failed handshake diagnosable, but they are attacker-controlled, so they pass through sanitizeLogValue(String) rather than being logged raw.

      Parameters:
      args - the parsed digest arguments, may be null
      Returns:
      a sanitized copy, empty map if null
    • addrAndPort

      static String addrAndPort(Socket s)
      Format the remote address as IP:PORT for ban and failure logging. IPv6 addresses are bracketed so the port separator is unambiguous.
      Parameters:
      s - the socket, may be null
      Returns:
      e.g. "127.0.0.1:4444" or "[::1]:1234", "unknown" if unavailable
    • isBlockedLocalAddress

      public static boolean isBlockedLocalAddress(String host)
      Determine whether a proxy request target is a loopback, private, link-local, shared, or otherwise non-routable address that must not be reached through the proxy. Non-.i2p hosts are handed to the outproxy plugin, which opens a raw socket from this machine, so a request for 127.0.0.1 or 10.x would otherwise reach the router itself or the LAN.

      The host is parsed as an address rather than matched with string prefixes, so every address in a blocked block is caught (not only 10.0.x.x or 172.16.x.x) while a name that merely looks similar (10.0.example.com) is not. Bracketed IPv6 literals, zone ids, and v4-mapped forms are normalized first.

      Public so the SOCKS4a and SOCKS5 servers, which hand non-.i2p targets to the same outproxy plugin, apply one policy rather than a narrower copy of it.

      Parameters:
      host - the request target host, without port, may be null
      Returns:
      true if the host must not be proxied to
    • isBlockedIPv4Address

      public static boolean isBlockedIPv4Address(String host)
      Determine whether an IPv4 host is in a blocked non-routable block: loopback, "this network", RFC 1918 private, link-local, carrier-grade NAT, benchmarking, documentation, multicast, and reserved space.

      A host made only of digits and dots that is not a strict dotted quad (127.1, 2130706433) is an ambiguous address literal rather than a name, so it is blocked too; such a host cannot be a resolvable name, as no top-level label may be all-numeric.

      Parameters:
      host - the IPv4 host, lowercase, no brackets, may be null
      Returns:
      true if blocked
    • parseDottedQuad

      private static int[] parseDottedQuad(String host)
      Parse a strict dotted quad into four octets.
      Parameters:
      host - lowercase host, digits and dots
      Returns:
      the octets, or null if not exactly four in-range groups
    • parseIPv6Groups

      private static int[] parseIPv6Groups(String s)
      Parse an IPv6 address into its eight 16-bit groups, expanding "::" and converting an embedded IPv4 tail.
      Parameters:
      s - the address, lowercase, without brackets or zone id
      Returns:
      the groups, or null if the address does not parse
    • parseHexGroup

      private static int parseHexGroup(String s)
      Parse one IPv6 group of up to four hex digits.
      Parameters:
      s - the group text
      Returns:
      the value, or -1 if empty, too long, or non-hex
    • getNonce

      private String getNonce()
      The Base 64 of 40 bytes: (now, sha256 of (now, proxy nonce)).
      Returns:
      the Base 64 digest, which is also registered as a valid nonce
    • verifyNonce

      private I2PTunnelHTTPClientBase.AuthResult verifyNonce(String b64, String ncs)
      Verify the Base 64 of 40 bytes: (now, sha256 of (now, proxy nonce)) and the nonce count.
      Parameters:
      b64 - nonce non-null
      ncs - nonce count string non-null
      Returns:
      AUTH_GOOD if the nonce is valid and the count is new, otherwise AUTH_BAD or AUTH_STALE
    • cleanNonces

      private void cleanNonces()
      Remove expired nonces from map.
    • getAuthError

      protected String getAuthError(boolean isStale)
      What to send if digest auth fails.
      Parameters:
      isStale - true if the previous nonce was stale
      Returns:
      the HTTP error response string
    • parseArgs

      private static Map<String,String> parseArgs(String args)
      Modified from LoadClientAppsJob. All keys are mapped to lower case. Ref: RFC 2617.
      Parameters:
      args - non-null, starting after "Digest " or "Basic "
      Returns:
      the parsed key-value pairs, with the keys lower-cased
    • getErrorPage

      protected String getErrorPage(String base, String backup)
      Load the error page header file, e.g. foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht, or the backup byte array on fail.

      .ht files must be UTF-8 encoded and use \r\n terminators so the HTTP headers are conformant. We can't use FileUtil.readFile(String, String, OutputStream) because it strips \r.

      Parameters:
      base - the error page base name
      backup - fallback string if the file cannot be read
      Returns:
      non-null
    • getErrorPage

      protected static String getErrorPage(I2PAppContext ctx, String base, String backup)
      Load the error page header file, e.g. foo => errordir/foo-header_xx.ht for lang xx, or errordir/foo-header.ht, or the backup byte array on fail.

      .ht files must be UTF-8 encoded and use \r\n terminators so the HTTP headers are conformant. We can't use FileUtil.readFile(String, String, OutputStream) because it strips \r.

      Parameters:
      ctx - the I2P application context
      base - the error page base name
      backup - fallback string if the file cannot be read
      Returns:
      non-null
    • readFile

      private static String readFile(I2PAppContext ctx, File file) throws IOException
      Reads a small text file into a string.
      Parameters:
      ctx - the app context
      file - the file to read
      Returns:
      the file contents
      Throws:
      IOException - if the file cannot be read
    • handleClientException

      protected void handleClientException(Exception ex, OutputStream out, String targetRequest, boolean usingWWWProxy, String wwwProxy, long requestId)
      Handle an exception from a client connection.
      Parameters:
      ex - may be null
      out - the output stream for the error response
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
      requestId - the request identifier for logging
    • handleI2PSocketException

      protected void handleI2PSocketException(I2PSocketException ise, OutputStream out, String targetRequest, boolean usingWWWProxy, String wwwProxy)
      Generate an error page based on the status code in our custom exception.
      Parameters:
      ise - may be null
      out - the output stream for the error response
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
    • writeErrorMessage

      protected void writeErrorMessage(String errMessage, OutputStream out, String targetRequest, boolean usingWWWProxy, String wwwProxy) throws IOException
      No jump servers or extra message.
      Parameters:
      errMessage - the error header
      out - the output stream
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
      Throws:
      IOException - on write error
    • writeErrorMessage

      protected void writeErrorMessage(String errMessage, OutputStream out, String targetRequest, boolean usingWWWProxy, String wwwProxy, String jumpServers) throws IOException
      No extra message.
      Parameters:
      errMessage - the error header
      out - the output stream
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
      jumpServers - comma- or space-separated list, or null
      Throws:
      IOException - on write error
    • writeErrorMessage

      protected void writeErrorMessage(String errMessage, String extraMessage, OutputStream out, String targetRequest, boolean usingWWWProxy, String wwwProxy) throws IOException
      No jump servers.
      Parameters:
      errMessage - the error header
      extraMessage - extra message or null, will be HTML-escaped
      out - the output stream
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
      Throws:
      IOException - on write error
    • writeErrorMessage

      protected void writeErrorMessage(String errMessage, String extraMessage, OutputStream outs, String targetRequest, boolean usingWWWProxy, String wwwProxy, String jumpServers) throws IOException
      Write the complete error message to the output stream.
      Parameters:
      errMessage - the error header
      extraMessage - extra message or null, will be HTML-escaped
      outs - the output stream
      targetRequest - the original request URI
      usingWWWProxy - whether a WWW outproxy is in use
      wwwProxy - the outproxy destination
      jumpServers - comma- or space-separated list, or null
      Throws:
      IOException - on write error
    • decodeIDNURI

      private static String decodeIDNURI(String uri)
      Decodes Internationalized Domain Names in a URI for display.

      This method converts punycode-encoded hostnames (xn--) in a URI back to their Unicode representation for human-readable display. If IDN support is unavailable or the URI contains no punycode, the original URI is returned unchanged.

      Parameters:
      uri - the URI string that may contain encoded hostnames
      Returns:
      the URI with decoded hostname, or the original URI on error
    • decodeIDNHost

      public static String decodeIDNHost(String host)
      Decode a hostname for display. Returns original string on any error.
      Parameters:
      host - the hostname to decode
      Returns:
      the decoded hostname, or the original on error
    • writeFooter

      public static void writeFooter(OutputStream out) throws IOException
      Write the closing HTML footer and flush it down the stream.

      Public only for LocalHTTPServer, not for general use.

      Parameters:
      out - the output stream to write to
      Throws:
      IOException - on write error
    • writeFooter

      public static void writeFooter(Writer out) throws IOException
      Write the closing HTML footer and flush it down the writer.

      Public only for LocalHTTPServer, not for general use.

      Parameters:
      out - the writer to write to
      Throws:
      IOException - on write error
    • getFooter

      private static String getFooter()
      Returns the HTML footer added to error pages.
      Returns:
      the footer text
    • _t

      protected String _t(String key)
      Look up a translation key in this class's resource bundle.
      Parameters:
      key - the translation key
      Returns:
      the translated string
    • _t

      protected String _t(String key, Object o)
      Translate with one parameter {0}.
      Parameters:
      key - the translation key
      o - the parameter to insert
      Returns:
      the translated string
    • _t

      protected String _t(String key, Object o, Object o2)
      Translate with two parameters {0} and {1}.
      Parameters:
      key - the translation key
      o - the first parameter to insert
      o2 - the second parameter to insert
      Returns:
      the translated string