All Implemented Interfaces:
Runnable, EventDispatcher
Direct Known Subclasses:
I2PTunnelHTTPBidirServer

public class I2PTunnelHTTPServerextends I2PTunnelServer
HTTP server tunnel that filters headers and provides compression.

Extends I2PTunnelServer to filter HTTP headers from client to server, replacing the Host header with configured value. Compresses HTTP message body and sets Content-Encoding: x-i2p-gzip when browser requests Accept-Encoding: x-i2p-gzip.

Features include POST/PUT rate limiting, referer and user-agent rejection, inproxy detection, HTTP blocklist support, keepalive management, and response header security filtering.

  • Field Details

    • DEFAULT_POST_BAN_TIME

      public static final int DEFAULT_POST_BAN_TIME
      Post throttle window (seconds).
      See Also:
    • DEFAULT_POST_MAX

      public static final int DEFAULT_POST_MAX
      Default max posts per window.
      See Also:
    • DEFAULT_POST_TOTAL_BAN_TIME

      public static final int DEFAULT_POST_TOTAL_BAN_TIME
      Default total ban time for excessive posts.
      See Also:
    • DEFAULT_POST_TOTAL_MAX

      public static final int DEFAULT_POST_TOTAL_MAX
      Default max total posts before ban.
      See Also:
    • DEFAULT_POST_WINDOW

      public static final int DEFAULT_POST_WINDOW
      Default post rate-limit window (seconds).
      See Also:
    • DEFAULT_KEEPALIVE

      private static final boolean DEFAULT_KEEPALIVE
      Default keepalive setting for HTTP persistent connections.
      See Also:
    • OPT_POST_BAN_TIME

      public static final String OPT_POST_BAN_TIME
      Config key for post ban time.
      See Also:
    • OPT_POST_MAX

      public static final String OPT_POST_MAX
      Config key for max posts.
      See Also:
    • OPT_POST_TOTAL_BAN_TIME

      public static final String OPT_POST_TOTAL_BAN_TIME
      Config key for post total ban time.
      See Also:
    • OPT_POST_TOTAL_MAX

      public static final String OPT_POST_TOTAL_MAX
      Config key for max total posts.
      See Also:
    • OPT_POST_WINDOW

      public static final String OPT_POST_WINDOW
      Config key for post check time window.
      See Also:
    • DNS_TIMEOUT_MS

      private static final int DNS_TIMEOUT_MS
      Max wait for DNS resolution of non-I2P hostnames in the HTTP proxy.
      See Also:
    • REQUEST_ERROR_LOG_INTERVAL_MS

      static final long REQUEST_ERROR_LOG_INTERVAL_MS
      Minimum interval between per-client "Request error" warnings. A single torrent client reconnecting without sending a request produced 1000+ read timeouts in one rotation, which buried every other warning.
      See Also:
    • MAX_TRACKED_ERROR_CLIENTS

      static final int MAX_TRACKED_ERROR_CLIENTS
      Cap on distinct clients tracked for warning rate limiting, so a flood from many distinct peers cannot grow this map without bound.
      See Also:
    • _lastErrorLogTime

      private static final Map<String,Long> _lastErrorLogTime
      Last time each client was logged for a request error, for rate limiting. Keyed on the base32 destination; bounded by MAX_TRACKED_ERROR_CLIENTS.
    • _dnsResolver

      private static final ExecutorService _dnsResolver
      Dedicated executor for non-blocking DNS lookups with bounded timeout.
    • _ioPools

      private static final ConcurrentHashMap<Object, ThreadPoolExecutor> _ioPools
      Per-tunnel I/O pools for Server→Client data transfer, keyed by this server instance so a saturated dest cannot consume every IO thread. Sized as shares of TunnelControllerGroup.getIOTransferThreads(). Separate from the runner pool so pinned runner threads are never consumed by slow downloads.
    • _ioExecutorLock

      private static final Object _ioExecutorLock
      Lock for creating the shared IO executor.
    • ioTransferThreads

      static volatile int ioTransferThreads
      Default I/O transfer threads (global budget); Tuner adjusts via TunnelControllerGroup. Sized for concurrent body transfers across many server tunnels (e.g. 16 eepsites x floor 8 = 128 threads when fully loaded).
    • IO_THREADS_MIN

      static final int IO_THREADS_MIN
      Absolute floor for the global I/O budget. Two transfers minimum so a quiet router can still serve a page and its assets; the per-tunnel floor is derived from the budget in ioPoolFloor().
      See Also:
    • IO_THREADS_MAX

      static final int IO_THREADS_MAX
      Maximum global I/O budget (Tuner max must match). @since 0.9.71+.
    • IO_TRANSFER_RATES

      private static final long[] IO_TRANSFER_RATES
      Sample periods for the I/O transfer load stat. Five minutes is included because a body transfer holds its thread for the whole transfer, so the per-minute view is noisy while the five-minute one shows the trend the operator needs against the pool's ceiling.
    • STALL_WINDOW_FLOOR_MS

      static final long STALL_WINDOW_FLOOR_MS
      Floor for the configured stall window, ms. A window below this is shorter than any pause in a bulk transfer over a congested path and aborts healthy downloads; bodies that have moved megabytes scale up further via computeStallWindowMs(long, long).
      See Also:
    • ioStallTimeoutMs

      static volatile long ioStallTimeoutMs
      Current stall window, adjustable by the Tuner between the floor and max.
    • _stallEventCount

      static final AtomicLong _stallEventCount
      Monotonically increasing counter of stall events detected by runOnIO or the Sender. Used by the Tuner as the observed signal for stall timeout adjustment.
    • _bodyAbortCount

      private static final AtomicLong _bodyAbortCount
      Bodies aborted because the I/O pool was saturated. A response aborted here reaches the client as a truncated body, which is otherwise indistinguishable from a socket close or a read timeout, so the counter is what lets the cause be told apart in the log and on the console.
    • SHED_LOG_THROTTLE_MS

      private static final long SHED_LOG_THROTTLE_MS
      Minimum interval between shed warnings for this listener.
      See Also:
    • _lastShedLog

      private volatile long _lastShedLog
      When the last overload shed was logged.
    • BULK_STALL_1M

      private static final long BULK_STALL_1M
      Progress thresholds for computeStallWindowMs(long, long). @since 0.9.71+.
      See Also:
    • BULK_STALL_8M

      private static final long BULK_STALL_8M
      Body size where the bulk-transfer stall begins.
      See Also:
    • BULK_STALL_32M

      private static final long BULK_STALL_32M
      Body size where the bulk-transfer stall is at full delay.
      See Also:
    • MAX_STALL_WINDOW_MS

      static final long MAX_STALL_WINDOW_MS
      Hard ceiling on a progress-scaled window, ms. Above the top progress tier (8x) so a raised base window still cannot push a body past ten minutes without a stall.
      See Also:
    • STALL_SIDE_INDETERMINATE

      static final String STALL_SIDE_INDETERMINATE
      Stall side reported when both stamps are equally stale. @since 0.9.71+.
      See Also:
    • STALL_SIDE_SOURCE

      static final String STALL_SIDE_SOURCE
      Stall side reported when the source moved nothing and only a read blocked.
      See Also:
    • STALL_SIDE_DESTINATION

      static final String STALL_SIDE_DESTINATION
      Stall side reported when the destination moved nothing.
      See Also:
    • STALL_SIDE_INDETERMINATE_MAX_BYTES

      private static final long STALL_SIDE_INDETERMINATE_MAX_BYTES
      Below this many bytes a body has moved too little for a "moved nothing, and only a read is blocked" claim, so equally stale stamps are reported as destination anyway. @since 0.9.71+.
      See Also:
    • OPT_REJECT_INPROXY

      public static final String OPT_REJECT_INPROXY
      Config key to reject requests from inproxy.
      See Also:
    • OPT_REJECT_REFERER

      public static final String OPT_REJECT_REFERER
      Config key to reject requests with Referer header.
      See Also:
    • OPT_REJECT_USER_AGENTS

      public static final String OPT_REJECT_USER_AGENTS
      Config key to reject specific User-Agents.
      See Also:
    • OPT_USER_AGENTS

      public static final String OPT_USER_AGENTS
      Config key for User-Agent reject list.
      See Also:
    • OPT_KEEPALIVE

      public static final String OPT_KEEPALIVE
      Config key to enable keepalive (legacy short form; see PROP_KEEPALIVE).
      See Also:
    • PROP_KEEPALIVE

      public static final String PROP_KEEPALIVE
      Config key to enable HTTP persistent connections; live via optionsUpdated. Falls back to the legacy OPT_KEEPALIVE short key when unset. Key: tunnel.N.option.i2ptunnel.server.keepalive.
      See Also:
    • PROP_KEEPALIVE_TIMEOUT

      public static final String PROP_KEEPALIVE_TIMEOUT
      Config key for the idle keepalive wait before closing a persistent connection, in ms. Replaces the hard-coded 130s wait so a browser that goes away does not pin a handler thread for over two minutes. Key: tunnel.N.option.i2ptunnel.server.keepAliveTimeout.
      See Also:
    • PROP_HEADER_TIMEOUT

      public static final String PROP_HEADER_TIMEOUT
      Config key for the first-request header read timeout, in ms. Key: tunnel.N.option.i2ptunnel.server.headerTimeout.
      See Also:
    • DEFAULT_KEEPALIVE_TIMEOUT_MS

      public static final long DEFAULT_KEEPALIVE_TIMEOUT_MS
      Default idle keepalive wait (ms); short enough to free the handler thread, long enough for a browser to reuse the connection.
      See Also:
    • DEFAULT_HEADER_TIMEOUT_MS

      public static final long DEFAULT_HEADER_TIMEOUT_MS
      Default first-request header timeout (ms). 30s matches the pre-config hard-coded value and gives slow eepsites time to start responding before the 408 kill fires (which aborts Range-resume downloads mid-body).
      See Also:
    • MIN_KEEPALIVE_TIMEOUT_MS

      public static final long MIN_KEEPALIVE_TIMEOUT_MS
      Bounds for PROP_KEEPALIVE_TIMEOUT (ms).
      See Also:
    • MAX_KEEPALIVE_TIMEOUT_MS

      public static final long MAX_KEEPALIVE_TIMEOUT_MS
      Ceiling for PROP_KEEPALIVE_TIMEOUT (ms).
      See Also:
    • MIN_HEADER_TIMEOUT_MS

      public static final long MIN_HEADER_TIMEOUT_MS
      Bounds for PROP_HEADER_TIMEOUT (ms).
      See Also:
    • MAX_HEADER_TIMEOUT_MS

      public static final long MAX_HEADER_TIMEOUT_MS
      Ceiling for PROP_HEADER_TIMEOUT (ms).
      See Also:
    • OPT_ADD_RESPONSE_HEADER_ALLOW

      public static final String OPT_ADD_RESPONSE_HEADER_ALLOW
      Config key to add Allow response header.
      See Also:
    • OPT_ADD_RESPONSE_HEADER_CACHE_CONTROL

      public static final String OPT_ADD_RESPONSE_HEADER_CACHE_CONTROL
      Config key to add Cache-Control response header.
      See Also:
    • OPT_ADD_RESPONSE_HEADER_NOSNIFF

      public static final String OPT_ADD_RESPONSE_HEADER_NOSNIFF
      Config key to add X-Content-Type-Options: nosniff.
      See Also:
    • OPT_ADD_RESPONSE_HEADER_REFERRER_POLICY

      public static final String OPT_ADD_RESPONSE_HEADER_REFERRER_POLICY
      Config key to add Referrer-Policy response header.
      See Also:
    • _spoofHost

      private String _spoofHost
      The hostname to inject as the Host header, or null for passthrough.
    • HASH_HEADER

      private static final String HASH_HEADER
      Client request headers to remove.
      See Also:
    • DEST64_HEADER

      private static final String DEST64_HEADER
      Header carrying the server's destination in base64.
      See Also:
    • DEST32_HEADER

      private static final String DEST32_HEADER
      Header carrying the server's destination in base32.
      See Also:
    • PROXY_CONN_HEADER

      private static final String PROXY_CONN_HEADER
      The Proxy-Connection header.
      See Also:
    • PRIORITY_HEADER

      private static final String PRIORITY_HEADER
      Header carrying the browser's request priority.
      See Also:
    • SEC_GPC_HEADER

      private static final String SEC_GPC_HEADER
      Header carrying the Global Privacy Control signal.
      See Also:
    • X_REAL_IP_HEADER

      private static final String X_REAL_IP_HEADER
      Header a proxy uses to name the client address.
      See Also:
    • CLIENT_SKIPHEADERS

      private static final Set<String> CLIENT_SKIPHEADERS
      Headers stripped from the client's request.
    • SERVER_SKIPHEADERS

      private static final Set<String> SERVER_SKIPHEADERS
      Headers stripped from the server's response.
    • AGE_HEADER

      private static final String AGE_HEADER
      Age header, stripped for anonymity.
      See Also:
    • ALT_SVC_HEADER

      private static final String ALT_SVC_HEADER
      Alt-Svc header pointing elsewhere, stripped.
      See Also:
    • DATE_HEADER

      private static final String DATE_HEADER
      Date header, replaced by the proxy.
      See Also:
    • EXPIRES_HEADER

      private static final String EXPIRES_HEADER
      Expires header, stripped as it conflicts with cache-control.
      See Also:
    • PRAGMA_HEADER

      private static final String PRAGMA_HEADER
      Obsolete Pragma header, stripped.
      See Also:
    • REFERER_HEADER

      private static final String REFERER_HEADER
      Referer header, stripped as it does not belong in responses.
      See Also:
    • SERVER_HEADER

      private static final String SERVER_HEADER
      Server header naming the web server, stripped.
      See Also:
    • STRICT_TRANSPORT_SECURITY_HEADER

      private static final String STRICT_TRANSPORT_SECURITY_HEADER
      HSTS header, useless through the proxy, stripped.
      See Also:
    • VIA_HEADER

      private static final String VIA_HEADER
      Via header listing proxies, stripped for anonymity.
      See Also:
    • X_CACHE_HEADER

      private static final String X_CACHE_HEADER
      X-Cache header revealing cache state, stripped.
      See Also:
    • X_CACHE_HITS_HEADER

      private static final String X_CACHE_HITS_HEADER
      X-Cache-Hits header revealing cache state, stripped.
      See Also:
    • X_CLOUD_TRACE_CONTEXT_HEADER

      private static final String X_CLOUD_TRACE_CONTEXT_HEADER
      Google Cloud trace header, stripped.
      See Also:
    • X_CONTEXTID_HEADER

      private static final String X_CONTEXTID_HEADER
      X-Contextid header, stripped.
      See Also:
    • X_GOOG_GENERATION_HEADER

      private static final String X_GOOG_GENERATION_HEADER
      Google Cloud Storage generation header, stripped.
      See Also:
    • X_GOOG_HASH_HEADER

      private static final String X_GOOG_HASH_HEADER
      Google Cloud Storage hash header, stripped.
      See Also:
    • X_GUPLOADER_UPLOADID_HEADER

      private static final String X_GUPLOADER_UPLOADID_HEADER
      Google upload id header, stripped.
      See Also:
    • X_HACKER_HEADER

      private static final String X_HACKER_HEADER
      Wordpress X-Hacker header, stripped.
      See Also:
    • X_NANANANA_HEADER

      private static final String X_NANANANA_HEADER
      Wordpress Batcache header, stripped.
      See Also:
    • X_PANTHEON_STYX_HOSTNAME_HEADER

      private static final String X_PANTHEON_STYX_HOSTNAME_HEADER
      Pantheon hosting host name, stripped for anonymity.
      See Also:
    • X_POWERED_BY_HEADER

      private static final String X_POWERED_BY_HEADER
      X-Powered-By header naming the framework, stripped.
      See Also:
    • X_RUNTIME_HEADER

      private static final String X_RUNTIME_HEADER
      Rails runtime header, stripped.
      See Also:
    • X_SERVED_BY_HEADER

      private static final String X_SERVED_BY_HEADER
      Host that served the response, stripped for anonymity.
      See Also:
    • X_STYX_REQ_ID_HEADER

      private static final String X_STYX_REQ_ID_HEADER
      Request id on Pantheon responses, stripped.
      See Also:
    • PROXY_HEADER

      private static final String PROXY_HEADER
      Proxy header, checked against the httpoxy attack. See also: httpoxy
      See Also:
    • NEWLINE_SPLIT

      private static final Pattern NEWLINE_SPLIT
      Pattern splitting a request line at CRLF.
    • SPACE_SPLIT

      private static final Pattern SPACE_SPLIT
      Pattern splitting a request line at spaces.
    • SLASH_SPLIT

      private static final Pattern SLASH_SPLIT
      Pattern splitting a path at slashes.
    • _headerTimeoutMs

      private volatile long _headerTimeoutMs
      Live first-request header timeout (ms); updated by optionsUpdated. Was a hard-coded 30s constant.
    • _keepAliveTimeoutMs

      private volatile long _keepAliveTimeoutMs
      Live idle keepalive wait (ms); updated by optionsUpdated. Was a hard-coded 130s wait.
    • _keepAlive

      private volatile boolean _keepAlive
      Whether HTTP persistent connections are accepted; updated by optionsUpdated.
    • HEADER_FINISH_TIMEOUT

      private static final long HEADER_FINISH_TIMEOUT
      Grace period for remaining header lines after the first line, on top of the initial header timeout (slowloris bound).
      See Also:
    • START_INTERVAL

      private static final long START_INTERVAL
      Min time before socket error is escalated to ERROR level.
      See Also:
    • MAX_LINE_LENGTH

      private static final int MAX_LINE_LENGTH
      Longest request header line accepted.
      See Also:
    • MAX_HEADERS

      private static final int MAX_HEADERS
      Ridiculously long, just to prevent OOM DOS.
      See Also:
    • MAX_TOTAL_HEADER_SIZE

      private static final int MAX_TOTAL_HEADER_SIZE
      Includes request, just to prevent OOM DOS.
      See Also:
    • DEFAULT_HTTP_READ_TIMEOUT

      private static final long DEFAULT_HTTP_READ_TIMEOUT
      Default read timeout for the server socket, -1 for none. It does not apply to header reads; forever keeps a large response from timing out, and the web server presumably implements its own timeout for POST requests.
      See Also:
    • SERVER_READ_TIMEOUT_GET

      private static final int SERVER_READ_TIMEOUT_GET
      Read timeout while reading a GET request, relatively short; POST and CONNECT get a long failsafe timeout since the user may be uploading a massive file.
      See Also:
    • SERVER_READ_TIMEOUT_MEDIUM

      private static final int SERVER_READ_TIMEOUT_MEDIUM
      Read timeout for medium uploads.
      See Also:
    • SERVER_READ_TIMEOUT_POST

      private static final int SERVER_READ_TIMEOUT_POST
      Read timeout while reading a POST body.
      See Also:
    • _startedOn

      private long _startedOn
      Timestamp when the tunnel started, used for error-level escalation timing.
    • _postThrottler

      private ConnThrottler _postThrottler
      POST/PUT throttler for rate limiting.
    • HTTP_BLOCKLIST_CLIENT_LIMIT

      private static final int HTTP_BLOCKLIST_CLIENT_LIMIT
      Max clients tracked by the HTTP blocklist.
      See Also:
    • _blocklistManager

      private BlocklistManager _blocklistManager
      HTTP blocklist manager.
    • ERR_NOT_FOUND

      static final String ERR_NOT_FOUND
      404 Not Found error response.
      See Also:
    • ERR_UNAVAILABLE

      private static final String ERR_UNAVAILABLE
      503 Service Unavailable error response.
      See Also:
    • ERR_DENIED

      private static final String ERR_DENIED
      429 Too Many Requests error response.
      See Also:
    • ERR_FORBIDDEN

      static final String ERR_FORBIDDEN
      403 Forbidden error response.
      See Also:
    • ERR_REQUEST_URI_TOO_LONG

      private static final String ERR_REQUEST_URI_TOO_LONG
      414 Request URI too long error response.
      See Also:
    • ERR_HEADERS_TOO_LARGE

      private static final String ERR_HEADERS_TOO_LARGE
      431 Request header fields too large error response.
      See Also:
    • ERR_REQUEST_TIMEOUT

      protected static final String ERR_REQUEST_TIMEOUT
      HTTP 408 Request Timeout error response. Includes a meta refresh to retry after 5 seconds.
      See Also:
    • ERR_BAD_REQUEST

      private static final String ERR_BAD_REQUEST
      400 Bad Request error response.
      See Also:
    • CONN_NONE

      static final int CONN_NONE
      Connection header absent.
      See Also:
    • CONN_KEEPALIVE

      static final int CONN_KEEPALIVE
      Connection: keep-alive.
      See Also:
    • CONN_UPGRADE

      static final int CONN_UPGRADE
      Connection: upgrade (websocket).
      See Also:
    • CONN_CLOSE

      static final int CONN_CLOSE
      Connection: close or unknown value.
      See Also:
    • MIN_TO_COMPRESS

      private static final int MIN_TO_COMPRESS
      Minimum response size in bytes before gzip compression is applied.
      See Also:
  • Constructor Details

    • I2PTunnelHTTPServer

      public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel)
      Create an HTTP tunnel server with a private key string.
      Parameters:
      host - the local address to bind to
      port - the local port
      privData - the private key data as a Base64 string
      spoofHost - the hostname to spoof in the Host header, or null
      l - the logging instance
      notifyThis - the event dispatcher for notifications
      tunnel - the parent tunnel configuration
    • I2PTunnelHTTPServer

      public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel)
      Create an HTTP tunnel server from a private key file.
      Parameters:
      host - the local address to bind to
      port - the local port
      privkey - the private key file
      privkeyname - the name for the private key
      spoofHost - the hostname to spoof in the Host header, or null
      l - the logging instance
      notifyThis - the event dispatcher for notifications
      tunnel - the parent tunnel configuration
    • I2PTunnelHTTPServer

      public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel)
      Create an HTTP tunnel server from a private key stream.
      Parameters:
      host - the local address to bind to
      port - the local port
      privData - the input stream containing the private key data
      privkeyname - the name for the private key
      spoofHost - the hostname to spoof in the Host header, or null
      l - the logging instance
      notifyThis - the event dispatcher for notifications
      tunnel - the parent tunnel configuration
  • Method Details

    • shouldLogRequestError

      static boolean shouldLogRequestError(String peerB32, long now)
      Decide whether a request-error warning should be emitted for a client. First occurrence for a client is always logged; subsequent ones only after REQUEST_ERROR_LOG_INTERVAL_MS. Evicts the oldest entries once the tracking map is full, so a burst of distinct peers cannot grow it without bound.
      Parameters:
      peerB32 - client base32, may be null
      now - current time in ms
      Returns:
      true if the caller should log at WARN
    • evictOldestErrorClients

      private static void evictOldestErrorClients(long now)
      Free space in the tracking map, guaranteeing it never exceeds MAX_TRACKED_ERROR_CLIENTS. First drops clients whose last warning is older than the log interval. If that is not enough (a burst of distinct peers inside one interval leaves nothing stale), the single oldest entry is evicted so the bound always holds. This runs only on the overflow path, never on the normal per-request check.
      Parameters:
      now - current time in ms
    • resetRequestErrorLimiter

      static void resetRequestErrorLimiter()
      Test seam: clear the request-error rate limiting state.
    • trackedErrorClients

      static int trackedErrorClients()
      Test seam: number of clients currently tracked.
      Returns:
      tracked client count
    • maxIOThreads

      public static int maxIOThreads()
      Heap-derived ceiling on the I/O transfer budget.

      Each I/O thread holds 16 KB of buffer plus a 16 KB BufferedInputStream and is blocked for the whole of a body transfer, so a thread costs roughly an order of magnitude more than a runner-pool task, which finishes in microseconds. Costing the divisor accordingly: 4 MB per thread rather than the 2 MB the runner pool uses. At 8 GB that is 2048 threads, adding roughly 190 MB to a 1.2 GB footprint — comfortable, and still large headroom over the few hundred concurrent transfers a 40 K-client tracker produces at a 30-minute announce interval.

      The ceiling is burst headroom, not residency: allowCoreThreadTimeOut with a 30-second keepalive reaps threads half a minute after their last task, so an idle pool costs nothing whatever the ceiling says.

      Returns:
      the memory-feasible I/O thread ceiling, in [1024, 16384]
    • getBodyAbortCount

      public static long getBodyAbortCount()
      Count of bodies dropped because the I/O pool had no free worker.
      Returns:
      bodies aborted due to I/O pool saturation since router start
    • getIOExecutor

      ThreadPoolExecutor getIOExecutor()
      Get (creating on first use) this tunnel's private I/O pool for data transfer. The pool is a share of the global ioTransferThreads budget so one tunnel's bulk downloads cannot starve another's.
      Returns:
      non-null, this tunnel's I/O executor
    • growRunnerPoolForSubmit

      private void growRunnerPoolForSubmit()
      Grow this tunnel's runner pool if work is backing up, immediately before a submit.

      The fast path is one atomic load of the queue depth, so a request that finds a free pool pays nothing. Only when the queue holds work is getActiveCount() consulted, because that takes the pool's mainLock and would otherwise be a per-request monitor acquisition on a busy tunnel.

      Growing here rather than in the accept loop is deliberate: the accept loop owns the server handler pool, and the runner pool is only reachable from a request that has already been parsed.

    • reportRunnerShed

      private void reportRunnerShed(String tunnelId, String peerB32)
      Report a runner-pool rejection to the Tuner and the log.

      The shed stat is the point of this method. It is the unambiguous overload signal I2PTunnelClientRunnerMaxParam grows on, and the server paths that reject never wrote it: i2ptunnel.clientConnectionShed read "No lifetime events" on a live router that was rejecting about 1.7 requests a second. Without the write the Tuner reads such a tunnel as idle and decays its runner budget toward the floor while the tunnel rejects at its share.

      The active-thread sample is taken here because this is the only instant the pool is observably pinned; the accept-loop sample is taken before the work starts and reads low. Closing the connection is left to the caller, which knows what it opened.

      Parameters:
      tunnelId - the tunnel identifier for the log line
      peerB32 - the client's base32 address for the log line
    • ioQueueCap

      static int ioQueueCap(int threads)
      Maximum queued body tasks for an I/O pool with threads workers. The queue is bounded so a saturated pool cannot park an unbounded number of sockets with their browser and I2P streams open; overflow runs the body on the handler thread instead (see handOffBody(ThreadPoolExecutor, I2PTunnelHTTPServer.Sender, String)), which preserves a response whose headers were already sent. Package-visible for tests.
      Parameters:
      threads - pool worker count (already floored)
      Returns:
      queue capacity, at least 32
    • createIOExecutor

      static ThreadPoolExecutor createIOExecutor(int threads)
      Create a fixed-size I/O transfer pool. Package-visible for tests.
      Parameters:
      threads - fixed core/max worker count
      Returns:
      non-null executor with a bounded queue (ioQueueCap(int))
    • rebalanceIOPools

      private static void rebalanceIOPools()
      Rebalance every live I/O pool from the global ioTransferThreads budget. Must run under _ioExecutorLock.
    • resizeIOExecutor

      private static void resizeIOExecutor(ThreadPoolExecutor ex, int newThreads)
      Resize an I/O pool, preserving core == max.
      Parameters:
      ex - the pool; ignored if null or shut down
      newThreads - the new fixed size
    • ioPoolStopped

      private void ioPoolStopped()
      Deregister this tunnel's I/O pool on stop so its budget share is freed.
    • rebalanceAllIOPools

      static void rebalanceAllIOPools()
      Trigger a full I/O-pool rebalance after a global budget change (Tuner).
    • getIOTransferActiveCount

      public static int getIOTransferActiveCount()
      Get the saturation pressure summed across all live I/O pools: running workers plus queued transfers. Used by the Tuner as the reactive growth signal (queued bodies count as pressure so a backlog grows the budget on the next cycle, not only when workers are full).
      Returns:
      active + queued transfers across pools, or 0 if none created
    • getIOMaxPoolUtilization

      public static double getIOMaxPoolUtilization()
      The busiest pool's utilization: its active plus queued transfers divided by its present maximum, as a rate that may exceed 1.0 when the queue is backing up past the pool's ceiling.

      The growth signal must be per-pool, not a global sum. Ten pools sharing one budget means the summed pressure divided by the budget reads low even when one pool is saturated — a tracker under load can sit at its allocation while the global ratio stays below the growth step. This returns the busiest pool's own utilization against its own maximum, so a saturated pool is visible regardless of how quiet the others are.

      Returns:
      the busiest pool's utilization rate, or 0.0 when no pools exist
    • ioPoolFloor

      static int ioPoolFloor()
      The floor for one I/O pool, proportional to the budget and pool count.

      A fixed floor scales linearly with tunnel count and can consume the whole budget before any load-based allocation — ten pools at a floor of 8 leave 16 of 96 threads for load. The floor is a quarter of one pool's fair share, so it shrinks as pools multiply and always leaves the majority of the budget for pressure-based distribution.

      Returns:
      the per-pool floor, never below IO_THREADS_MIN
    • setIOTransferThreads

      public static void setIOTransferThreads(int val)
      Set the I/O transfer pool size (called by Tuner via reflection). Clamped to the absolute bounds only: the per-pool floor is derived from the budget it would clamp against, so using it here would make the setter path-dependent. A set could never lower the budget below a quarter of its previous value. The per-pool floor is enforced where it is used, at allocation in rebalanceIOPools().
      Parameters:
      val - new pool size (clamped to [IO_THREADS_MIN, IO_THREADS_MAX])
    • getIOTransferThreads

      public static int getIOTransferThreads()
      Get the current I/O transfer pool size.
      Returns:
      current pool size
    • setIOStallTimeoutMs

      public static void setIOStallTimeoutMs(long val)
      Set the I/O stall timeout in ms (called by Tuner via reflection).

      The floor matches the Tuner's own floor: a window below STALL_WINDOW_FLOOR_MS is shorter than any pause in a bulk transfer, so it aborts healthy downloads rather than freeing threads. Bulk bodies get a larger window still via computeStallWindowMs(long, long).

      Parameters:
      val - new timeout in ms (clamped to [STALL_WINDOW_FLOOR_MS, 300000])
    • getIOStallTimeoutMs

      public static long getIOStallTimeoutMs()
      Get the current I/O stall timeout in ms.
      Returns:
      current stall timeout
    • getStallEventCount

      public static long getStallEventCount()
      Get the total number of stall events detected by Sender since router start. Each stall event means a Server→Client transfer was interrupted because no data arrived for ioStallTimeoutMs. Used by the Tuner as a real-time signal for stall timeout adjustment.
      Returns:
      monotonically increasing stall event count
    • sampleIOLoad

      private static void sampleIOLoad(ThreadPoolExecutor ioPool)
      Publish a pool's load for the operator-facing view on /stats and the graphs page.

      Explicitly *not* the Tuner's input, despite the name matching its param: I2PTunnelServerIOTransferParam.getObservedStat reads the live getIOTransferActiveCount() total instead, on the fast five-second cycle, so the ceiling reacts to load in seconds rather than to a rate window. An earlier revision of this javadoc claimed this stat unblocked that policy — it does not, and the frozen pool it was meant to explain was caused by rebalanceIOPools() splitting the budget equally across every pool, which left a busy tracker at the 8-thread floor alongside nine idle eepsites. Load-weighting that rebalance is the fix; this stat is how an operator sees the same pressure over time.

      Parameters:
      ioPool - the pool to sample; ignored if null or shut down
    • handOffBody

      static boolean handOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc) throws IOException
      Hand off the response body to the I/O transfer pool, or run it inline when the pool is null or shut down.
      Parameters:
      pool - the I/O executor to submit to; null or shut down forces an inline run
      s - the Sender to run; null does nothing and reports false
      desc - descriptive name for error messages
      Returns:
      true if the body was submitted asynchronously, false if it ran inline
      Throws:
      IOException - part of the handoff API's signature; the body reports every failure through the Sender rather than throwing
    • handOffBody

      static boolean handOffBody(ThreadPoolExecutor pool, I2PTunnelHTTPServer.Sender s, String desc, Runnable onCompletion) throws IOException
      As handOffBody(ThreadPoolExecutor, Sender, String) with a completion callback invoked after the Sender finishes (success, failure, or abort), on the pool worker when submitted and on the caller otherwise. The callback always runs, so a caller can rely on it for connection teardown no matter which path was taken.
      Parameters:
      pool - the I/O executor to submit to; null or shut down forces an inline run
      s - the Sender to run; null runs the callback only and reports false
      desc - descriptive name for error messages
      onCompletion - may be null; must not throw
      Returns:
      true if the body was submitted asynchronously, false if it ran inline or was aborted
      Throws:
      IOException - part of the handoff API's signature; the body reports every failure through the Sender rather than throwing
    • isBodyWatchdogStalled

      static boolean isBodyWatchdogStalled(long lastReadNanos, long nowNanos, long stallNs)
      Decide whether an async body handoff has stalled. Pure decision helper for the body watchdog: true when the Sender's last-read stamp is at least the stall window old. A task that has not started yet (stamp 0) never stalls — queueing behind a healthy long transfer is normal — and a disabled window (<= 0) never stalls.
      Parameters:
      lastReadNanos - Sender#getLastReadNanos(); 0 or negative if the worker has not started the copy yet
      nowNanos - the current System.nanoTime(), same base as lastReadNanos
      stallNs - stall window in nanoseconds; <= 0 disables
      Returns:
      true if the watchdog should close the streams and finish
    • computeStallWindowMs

      static long computeStallWindowMs(long baseMs, long bytesTransferred)
      Stall window for a body that has already moved bytesTransferred.

      The configured window is right for a small response but wrong for a bulk one: over a congested path a 45MB transfer routinely pauses for many seconds between copy steps, and a single fixed window either kills the transfer or, if sized for the bulk case, pins a worker for minutes behind a dead peer. The response Content-Length is not available here (it is never parsed, and gzip and chunked bodies have no useful declared length anyway), so the window scales with progress instead: a body that has already moved megabytes has demonstrably earned patience.

      Parameters:
      baseMs - configured window, ioStallTimeoutMs
      bytesTransferred - bytes already moved by the body
      Returns:
      the window to apply, ms
    • classifyStalledSide

      static String classifyStalledSide(long readAgeMs, long writeAgeMs, long bytesTransferred)
      Which side of a stalled body transfer stopped making progress.

      Read and write stamps are updated either side of the same copy step, so a body blocked mid-write ages both equally: the read that fed the blocked write is stamped, then the write never completes to stamp itself. The two therefore look identical to a body blocked mid-read, and only the progress counter separates them -- a body that has moved meaningful bytes and then gone quiet is sitting in a blocked write, because a blocked read would have had nothing to hand the previous write.

      Reporting the ambiguous case as the source is actively misleading: a 45MB download that stalled after 3.2MB was logged as "local backend silent" while the same backend served the whole file in 4ms on a fresh connection, sending every diagnosis to the wrong layer.

      Parameters:
      readAgeMs - age of the last-read stamp
      writeAgeMs - age of the last-write stamp
      bytesTransferred - bytes already moved by the body
      Returns:
      one of STALL_SIDE_SOURCE, STALL_SIDE_DESTINATION, or STALL_SIDE_INDETERMINATE
    • closeQuietly

      private static void closeQuietly(Closeable c)
      Close a stream, ignoring failures. Used by stall/teardown paths where the close is best-effort (the stream may already be dead) and the goal is only to unblock a thread parked in read or write.
      Parameters:
      c - stream to close; ignored if null
    • setupI2PTunnelHTTPServer

      private void setupI2PTunnelHTTPServer(String spoofHost)
      Common initialization for all constructors. Sets the spoofed host, creates stats, and initializes the blocklist manager.
      Parameters:
      spoofHost - the hostname to spoof in the Host header, or null for no spoofing
    • startRunning

      public void startRunning()
      Start the tunnel and initialize timing and throttling.
      Overrides:
      startRunning in class I2PTunnelServer
    • setupPostThrottle

      private void setupPostThrottle()
      Initialize or update the POST/PUT throttler from tunnel options. Creates a new ConnThrottler if limits are configured, or updates existing.
    • parseKeepAlive

      static boolean parseKeepAlive(Properties opts)
      Parse the keepalive enable flag from tunnel options. Prefers PROP_KEEPALIVE; falls back to the legacy OPT_KEEPALIVE short key; defaults to true when unset.
      Parameters:
      opts - the client options; may be null
      Returns:
      true if persistent connections are enabled
    • parseKeepAliveTimeout

      static long parseKeepAliveTimeout(Properties opts)
      Parse and clamp the idle keepalive timeout (ms) from tunnel options. Unparseable values fall back to the default; out-of-range values clamp.
      Parameters:
      opts - the client options; may be null
      Returns:
      timeout in ms within [MIN_KEEPALIVE_TIMEOUT_MS, MAX_KEEPALIVE_TIMEOUT_MS]
    • parseHeaderTimeout

      static long parseHeaderTimeout(Properties opts)
      Parse and clamp the first-request header timeout (ms) from tunnel options.
      Parameters:
      opts - the client options; may be null
      Returns:
      timeout in ms within [MIN_HEADER_TIMEOUT_MS, MAX_HEADER_TIMEOUT_MS]
    • parseTimeoutMs

      private static long parseTimeoutMs(Properties opts, String key, long dflt, long min, long max)
      Shared timeout parser: default on missing/unparseable, clamp to [min, max].
      Parameters:
      opts - the options; may be null
      key - the property key
      dflt - default when unset or not a number
      min - inclusive lower bound
      max - inclusive upper bound
      Returns:
      the parsed and clamped timeout in ms
    • isKeepAlive

      public boolean isKeepAlive()
      Whether a client may reuse one connection for several requests.
      Returns:
      whether HTTP persistent connections are currently enabled
    • setKeepAlive

      public void setKeepAlive(boolean on)
      Enable/disable HTTP persistent connections without a tunnel restart.
      Parameters:
      on - true to accept persistent connections
    • getKeepAliveTimeout

      public long getKeepAliveTimeout()
      How long an idle keepalive connection is held before it is probed.
      Returns:
      idle keepalive wait in ms
    • setKeepAliveTimeout

      public void setKeepAliveTimeout(long ms)
      Set the idle keepalive wait, clamped to the configured bounds.
      Parameters:
      ms - timeout in ms
    • getHeaderTimeout

      public long getHeaderTimeout()
      How long the first request's headers may take to arrive.
      Returns:
      first-request header timeout in ms
    • setHeaderTimeout

      public void setHeaderTimeout(long ms)
      Set the first-request header timeout, clamped to the configured bounds.
      Parameters:
      ms - timeout in ms
    • getIntOption

      private int getIntOption(String opt, int dflt)
      Get an integer option from the tunnel client options.
      Parameters:
      opt - the option key
      dflt - the default value if not set or invalid
      Returns:
      the option value, or dflt
    • close

      public boolean close(boolean forced)
      Close the tunnel and stop the POST throttler.
      Overrides:
      close in class I2PTunnelServer
      Parameters:
      forced - true to force close
      Returns:
      true if closed successfully
    • optionsUpdated

      public void optionsUpdated(I2PTunnel tunnel)
      Called when tunnel options are updated. Re-initializes the POST throttler and updates the spoofed host.
      Overrides:
      optionsUpdated in class I2PTunnelServer
      Parameters:
      tunnel - tunnel that was updated; ignored unless it is the tunnel we serve
    • getTunnelLogId

      private String getTunnelLogId(Destination localDest)
      Short human-readable identifier for this HTTP server tunnel, e.g. "[skank / 8charB32]", matching the outbound tunnel log format "[nickname / b32prefix]" so related warnings can be correlated across the router and the i2ptunnel logs. A trailing ".i2p" on the configured nickname is stripped to match that format.
      Parameters:
      localDest - this tunnel's own destination, may be null
      Returns:
      the "[nickname / prefix]" label, never null
    • blockingHandle

      protected void blockingHandle(I2PSocket socket)
      Called by the thread pool of I2PSocket handlers. Handles a single client connection through the HTTP proxy lifecycle: read headers, validate, apply spoofing, compress, and forward.
      Overrides:
      blockingHandle in class I2PTunnelServer
      Parameters:
      socket - the incoming I2P socket from the client
      See Also:
    • readRequestHeaders

      static Map<String, List<String>> readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId) throws IOException
      Read the request headers with the default timeouts. Delegates to readRequestHeaders(I2PSocket, StringBuilder, int, String, I2PAppContext, Log, String, long, long).
      Parameters:
      socket - the client socket
      command - buffer for the request line
      requestCount - the number of requests already handled on this connection (0 = first)
      peerB32 - the client's base32 for logging
      ctx - the I2P app context
      log - the logging instance
      tunnelId - "[nickname / b32prefix]" label for this server tunnel
      Returns:
      the parsed headers, or null if the request failed and the client was notified
      Throws:
      IOException - on other I/O errors, propagated to the caller
    • readRequestHeaders

      static Map<String, List<String>> readRequestHeaders(I2PSocket socket, StringBuilder command, int requestCount, String peerB32, I2PAppContext ctx, Log log, String tunnelId, long headerTimeout, long keepAliveTimeout) throws IOException
      Read the request headers, handling the specific errors that can occur by sending the appropriate error response to the client. The 5 error handlers share this shape: send error, log, close, return null.
      Parameters:
      socket - the client socket
      command - buffer for the request line
      requestCount - the number of requests already handled on this connection (0 = first)
      peerB32 - the client's base32 for logging
      ctx - the I2P app context
      log - the logging instance
      tunnelId - "[nickname / b32prefix]" label for this server tunnel
      headerTimeout - first-request header timeout (ms)
      keepAliveTimeout - idle keepalive wait for subsequent requests (ms)
      Returns:
      the parsed headers, or null if the request failed and the client was notified
      Throws:
      IOException - on other I/O errors, propagated to the caller
    • getConnectionType

      static int getConnectionType(Map<String, List<String>> headers)
      Classify the request's Connection header value.
      Parameters:
      headers - the parsed request headers, searched for a Connection entry
      Returns:
      CONN_NONE, CONN_KEEPALIVE, CONN_UPGRADE, or CONN_CLOSE
    • isKeepAliveRequest

      static boolean isKeepAliveRequest(String cmd)
      Whether the request can keep the connection alive: GET or HEAD with HTTP/1.1.
      Parameters:
      cmd - the trimmed request line
      Returns:
      true if keepalive may continue
    • hasGzipEncoding

      static boolean hasGzipEncoding(String enc)
      Whether the Accept-Encoding value includes x-i2p-gzip.
      Parameters:
      enc - an Accept-Encoding header value, or null
      Returns:
      true if it includes x-i2p-gzip
    • isGzipAllowed

      static boolean isGzipAllowed(Properties opts)
      Whether the tunnel allows gzip responses, from the tunnel options.
      Parameters:
      opts - the client options
      Returns:
      true unless PROP_TUN_GZIP is set to false
    • handlePort443

      private boolean handlePort443(I2PSocket socket) throws IOException
      Handle port 443 (SSL passthrough). If no target is configured for port 443, reset the socket. Otherwise, set a long timeout and forward directly to the server via I2PTunnelRunner.
      Parameters:
      socket - the incoming I2P socket
      Returns:
      true if the socket was handled (port 443), false otherwise
      Throws:
      IOException - if socket operations fail
    • rejectConnection

      protected void rejectConnection(I2PSocket socket)
      Refuse an inbound connection when the gate cap is exceeded: send a 503 (SSL port 443 resets instead) and close. The base implementation then logs the rate-limited warning.
      Overrides:
      rejectConnection in class I2PTunnelServer
      Parameters:
      socket - the accepted but undelivered I2PSocket to reject
    • rejectConnection

      protected void rejectConnection(I2PSocket socket, boolean queueFull)
      Refuse an inbound connection (queue-full or connection-cap): send a 503 (SSL port 443 resets instead) and close, then log the accurate reason.
      Overrides:
      rejectConnection in class I2PTunnelServer
      Parameters:
      socket - the accepted but undelivered I2PSocket to reject
      queueFull - true if the handler queue gate rejected, false for the connection cap
    • validateRequestHost

      private void validateRequestHost(Map<String, List<String>> headers, I2PSocket socket, String peerB32) throws IOException
      Validate the request hostname against local/loopback/private addresses. If the hostname resolves to a private or loopback address, the peer is blocklisted and the socket is closed. If DNS resolves to 0.0.0.0 (blocked DNS), a 403 is sent. DNS is resolved asynchronously with a bounded timeout to avoid blocking the shared client thread pool on slow or unreachable DNS servers.
      Parameters:
      headers - the request headers map (to extract Host header)
      socket - the incoming I2P socket
      peerB32 - the peer's base32 address for logging/blocklisting
      Throws:
      IOException - if blocklist writing fails
    • isInproxyRejection

      boolean isInproxyRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts)
      Reject requests that contain forwarded-for headers (inproxy detection).
      Parameters:
      headers - the request headers
      socket - the incoming I2P socket
      peerB32 - the peer's base32 address for logging
      opts - the tunnel client options
      Returns:
      true if the request was rejected and the socket closed
    • isRefererRejection

      boolean isRefererRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts)
      Reject requests with absolute Referer URIs.
      Parameters:
      headers - the request headers
      socket - the incoming I2P socket
      peerB32 - the peer's base32 address for logging
      opts - the tunnel client options
      Returns:
      true if the request was rejected and the socket closed
    • isUserAgentRejection

      boolean isUserAgentRejection(Map<String, List<String>> headers, I2PSocket socket, String peerB32, Properties opts)
      Reject requests with blacklisted User-Agent strings.
      Parameters:
      headers - the request headers
      socket - the incoming I2P socket
      peerB32 - the peer's base32 address for logging
      opts - the tunnel client options
      Returns:
      true if the request was rejected and the socket closed
    • isPostThrottled

      boolean isPostThrottled(StringBuilder command, Hash peerHash, I2PSocket socket, String peerB32)
      Check POST/PUT throttling for this peer.
      Parameters:
      command - the request command string (first line)
      peerHash - the peer's hash for throttling lookup
      socket - the incoming I2P socket
      peerB32 - the peer's base32 address for logging
      Returns:
      true if the request was throttled and the socket closed
    • recordInitialTiming

      private void recordInitialTiming(long afterAccept, long afterHeaders, long afterSocket, long afterHandle, int requestCount, String peerB32)
      Record timing stats for the first request in a keepalive cycle.
      Parameters:
      afterAccept - clock time right after accept
      afterHeaders - clock time after reading headers
      afterSocket - clock time after creating the server socket
      afterHandle - clock time after dispatching the request
      requestCount - the request number (only logs for 0)
      peerB32 - the peer's base32 address for logging
    • applySpoofedHost

      private void applySpoofedHost(I2PSocket socket, Map<String, List<String>> headers, Properties opts)
      Apply port-specific spoofed host header. If no port-specific spoof is configured for the socket's port, falls back to the default _spoofHost.
      Parameters:
      socket - the incoming I2P socket
      headers - the request header map to modify
      opts - the tunnel client options
    • shouldKeepalive

      private boolean shouldKeepalive(AtomicInteger waiter, long afterAccept, int requestCount)
      Check whether the keepalive waiter indicates we should stay alive.
      Parameters:
      waiter - the AtomicInteger set by CompressedRequestor
      afterAccept - clock time right after accept (for debug logging)
      requestCount - the request number (for debug logging)
      Returns:
      true if the waiter returned keepalive-eligible (value 2)
    • sendError

      private static void sendError(I2PSocket socket, String resp) throws IOException
      Send an error response to the client. For port 443 (SSL), resets the socket instead since we can't send plaintext error messages.
      Parameters:
      socket - the client socket
      resp - the HTTP error response string
      Throws:
      IOException - if writing the response fails
    • sendErrorAndClose

      private static void sendErrorAndClose(I2PSocket socket)
      Send a 503 and close the socket, ignoring any failures.
      Parameters:
      socket - the socket to respond on
    • extractRequestUrl

      static String extractRequestUrl(String headers)
      Extract the request URL from the client's headers for logging.

      Package-visible and static so a test can drive it with a hostile request line: the URL is entirely attacker-controlled, and this method used to call substring(0, 12) on any host merely containing "b32.i2p", so a request for "/b32.i2p" aborted the handler with StringIndexOutOfBoundsException.

      Parameters:
      headers - the raw request headers, may be null
      Returns:
      a human-readable request description, never null
    • addEntry

      static void addEntry(Map<String, List<String>> headers, String key, String value)
      Add an entry to the multimap. If the key already exists, appends the value to the existing list.
      Parameters:
      headers - the header multimap to modify
      key - the header name
      value - the header value to add
    • setEntry

      static void setEntry(Map<String, List<String>> headers, String key, String value)
      Remove the other matching entries and set this entry as the only one.
      Parameters:
      headers - the header multimap to modify
      key - the header name
      value - the header value to set (replaces all existing values for this key)
    • getEntryOrNull

      private static String getEntryOrNull(Map<String, List<String>> headers, String key)
      Get the first matching entry in the multimap.
      Parameters:
      headers - the header multimap to search
      key - the header name to look up
      Returns:
      the first matching entry or null if not present
    • readHeaders

      public static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException
      From I2P to server: socket non-null, in null. From server to I2P: socket null, in non-null.

      Note: This does not handle RFC 2616 header line splitting, which is obsoleted in RFC 7230.

      Parameters:
      socket - if null, use in as InputStream
      in - if null, use socket.getInputStream() as InputStream
      command - out parameter, first line
      skipHeaders - MUST be lower case
      ctx - I2P app context for clock and timeout
      initialTimeout - timeout for the first line read
      Returns:
      the parsed header multimap
      Throws:
      SocketTimeoutException - if timeout is reached before newline
      EOFException - if EOF is reached before newline
      I2PTunnelHTTPServer.LineTooLongException - if one header too long, or too many headers, or total size too big
      I2PTunnelHTTPServer.RequestTooLongException - if too long
      I2PTunnelHTTPServer.BadRequestException - on bad headers
      IOException - on other errors in the underlying stream
    • readHeaders

      public static Map<String, List<String>> readHeaders(I2PSocket socket, InputStream in, StringBuilder command, String[] skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException
      Read headers from the input stream, using an array of skip headers.
      Parameters:
      socket - if non-null, read from socket; otherwise read from in
      in - if non-null and socket is null, read from this stream
      command - output buffer for the first request line
      skipHeaders - array of lower-case header names to filter out
      ctx - I2P app context for clock and timeout
      initialTimeout - timeout for the first line read
      Returns:
      the parsed header multimap
      Throws:
      IOException - on I/O errors
    • readHeadersInternal

      private static Map<String, List<String>> readHeadersInternal(I2PSocket socket, InputStream in, StringBuilder command, Set<String> skipHeaders, I2PAppContext ctx, long initialTimeout) throws IOException
      Read and parse HTTP headers, filtering out specified headers and enforcing size limits.
      Parameters:
      socket - if non-null, read from socket; otherwise read from in
      in - if non-null and socket is null, read from this stream
      command - output buffer for the first request line
      skipHeaders - set of lower-case header names to filter out
      ctx - I2P app context for clock and timeout
      initialTimeout - timeout for the first line read
      Returns:
      the parsed header multimap
      Throws:
      SocketTimeoutException - if the first line read exceeds initialTimeout
      EOFException - on unexpected EOF
      I2PTunnelHTTPServer.LineTooLongException - if a line, total, or count exceeds limits
      I2PTunnelHTTPServer.RequestTooLongException - if the request line is too long
      I2PTunnelHTTPServer.BadRequestException - on malformed headers
      IOException - on other I/O errors
    • readLine

      private static void readLine(I2PSocket socket, StringBuilder buf, long timeout) throws IOException
      Read a line terminated by newline, with a total read timeout.

      Warning - strips \n but not \r Warning - 8KB line length limit as of 0.7.13, @throws IOException if exceeded Warning - not UTF-8.

      Parameters:
      socket - the socket to read the line from
      buf - StringBuilder the line is appended to, without its newline
      timeout - throws SocketTimeoutException immediately if zero or negative
      Throws:
      SocketTimeoutException - if timeout is reached before newline
      EOFException - if EOF is reached before newline
      I2PTunnelHTTPServer.LineTooLongException - if too long
      IOException - on other errors in the underlying stream
    • processBlocklist

      private void processBlocklist(I2PSocket socket, StringBuilder command) throws IOException
      Check the request command against the HTTP blocklist. If matched, logs the blocked destination, closes the socket, and throws BadRequestException.
      Parameters:
      socket - the incoming I2P socket to close if blocked
      command - the request command string to check
      Throws:
      I2PTunnelHTTPServer.BadRequestException - if the request matches a blocklist entry
      IOException - if closing the socket fails