All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.muserver.ForwardedHeader Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
package io.muserver;

import java.util.*;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

/**
 * 

Represents a Forwarded header as described by RFC-7239.

*/ public class ForwardedHeader { private final String by; private final String forValue; private final String host; private final String proto; private final Map extensions; /** * Creates a new Forwarded Header. All values are optional. * @param by The address of the node proxying the request, or null * @param forValue The address of the client that initiated the request, or null * @param host The host that the client used, or null * @param proto The protocol the client used, or null * @param extensions Any extensions, or null */ public ForwardedHeader(String by, String forValue, String host, String proto, Map extensions) { this.by = by; this.forValue = forValue; this.host = host; this.proto = proto; this.extensions = extensions == null ? emptyMap() : extensions; } /** * @return The interface where the request came in to the proxy server (e.g. the IP address of the reverse * proxy that forwarded this request), or null if not specified. */ public String by() { return by; } /** * @return The interface where the request came in to the proxy server, e.g. the IP address of the client * that originated the request), or null if not specified. */ public String forValue() { return forValue; } /** * @return The Host request header field as received by the proxy (e.g. the hostname used on the original * request), or null if not specified. */ public String host() { return host; } /** * @return Indicates which protocol was used to make the request (typically "http" or "https"), or * null if not specified. */ public String proto() { return proto; } /** * @return Values not covered by by, for, host, or proto. */ public Map extensions() { return extensions; } private enum State {PARAM_NAME, PARAM_VALUE;} /** *

Parses the value of a Forwarded header into an object.

*

Where multiple reverse proxies have resulted in multiple Forwarded headers, the first * value in the list should contain the information of the original request.

*

Null or blank strings return an empty list.

* @param input The value to parse * @return A list of ForwardedHeader objects * @throws IllegalArgumentException The value cannot be parsed */ public static List fromString(String input) { if (input == null || input.trim().isEmpty()) { return emptyList(); } StringBuilder buffer = new StringBuilder(); List results = new ArrayList<>(); int i = 0; while (i < input.length()) { LinkedHashMap extensions = null; State state = State.PARAM_NAME; String paramName = null; String by = null; String forValue = null; String host = null; String proto = null; boolean isQuotedString = false; headerValueLoop: for (; i < input.length(); i++) { char c = input.charAt(i); if (state == State.PARAM_NAME) { if (c == ',' && buffer.length() == 0) { i++; // a semi-colon without an parameter, like "something;" break headerValueLoop; } else if (c == '=') { paramName = buffer.toString(); buffer.setLength(0); state = State.PARAM_VALUE; } else if (ParseUtils.isTChar(c)) { buffer.append(c); } else if (ParseUtils.isOWS(c)) { if (buffer.length() > 0) { throw new IllegalArgumentException("Got whitespace in parameter name while in " + state + " - header was " + buffer); } } else { throw new IllegalArgumentException("Got ascii " + ((int)c) + " while in " + state); } } else { boolean isFirst = !isQuotedString && buffer.length() == 0; if (isFirst && ParseUtils.isOWS(c)) { // ignore it } else if (isFirst && c == '"') { isQuotedString = true; } else { if (isQuotedString) { char lastChar = input.charAt(i - 1); if (c == '\\') { // don't append } else if (lastChar == '\\') { buffer.append(c); } else if (c == '"') { // this is the end, but we'll update on the next go isQuotedString = false; } else { buffer.append(c); } } else { if (c == ';') { String val = buffer.toString(); switch (paramName.toLowerCase()) { case "by": by = val; break; case "for": forValue = val; break; case "host": host = val; break; case "proto": proto = val; break; default: if (extensions == null) { extensions = new LinkedHashMap<>(); // keeps insertion order } extensions.put(paramName, val); break; } buffer.setLength(0); paramName = null; state = State.PARAM_NAME; } else if (c == ',') { i++; break headerValueLoop; } else if (ParseUtils.isVChar(c)) { buffer.append(c); } else if (ParseUtils.isOWS(c)) { // ignore it } else { throw new IllegalArgumentException("Got character code " + ((int) c) + " (" + c + ") while parsing parameter value"); } } } } } switch (state) { case PARAM_VALUE: String val = buffer.toString(); switch (paramName.toLowerCase()) { case "by": by = val; break; case "for": forValue = val; break; case "host": host = val; break; case "proto": proto = val; break; default: if (extensions == null) { extensions = new LinkedHashMap<>(); // keeps insertion order } extensions.put(paramName, val); break; } buffer.setLength(0); break; default: if (buffer.length() > 0) { throw new IllegalArgumentException("Unexpected ending point at state " + state + " for " + input); } } results.add(new ForwardedHeader(by, forValue, host, proto, extensions)); } return results; } /** * Converts the HeaderValue into a string, suitable for printing in an HTTP header. * * @return A String, such as "some-value" or "content-type:text/html;charset=UTF-8" */ public String toString() { StringBuilder sb = new StringBuilder(); appendString(sb, "by", by); appendString(sb, "for", forValue); appendString(sb, "host", host); appendString(sb, "proto", proto); this.extensions().forEach((key, value) -> appendString(sb, key, value)); return sb.toString(); } /** * Converts a list of headers into a single string that can be put into a Forwarded header field. * @param headers The headers to serialise * @return An RFC-7239 compliant Forwarded header value. */ public static String toString(List headers) { StringBuilder sb = new StringBuilder(); for (ForwardedHeader header : headers) { if (sb.length() > 0) { sb.append(", "); } sb.append(header.toString()); } return sb.toString(); } private static void appendString(StringBuilder sb, String key, String value) { if (value == null) { return; } if (sb.length() > 0) { sb.append(';'); } sb.append(key).append('=').append(ParseUtils.quoteIfNeeded(value)); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ForwardedHeader that = (ForwardedHeader) o; return Objects.equals(by, that.by) && Objects.equals(forValue, that.forValue) && Objects.equals(host, that.host) && Objects.equals(proto, that.proto) && extensions.equals(that.extensions); } @Override public int hashCode() { return Objects.hash(by, forValue, host, proto, extensions); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy