001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.http.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024/**
025 * Represents an HTTP <c>Forwarded</c> header.
026 *
027 * <p>
028 * Disclose original information of a client connecting to a web server through an HTTP proxy.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
032 * </ul>
033 *
034 * @since 9.2.1
035 */
036public class Forwarded extends HttpStringHeader {
037
038   /** The header name */
039   public static final String NAME = "Forwarded";
040
041   /**
042    * Constructor with an eager value.
043    *
044    * @param value The header value. May be <jk>null</jk>.
045    */
046   public Forwarded(String value) {
047      super(NAME, value);
048   }
049
050   /**
051    * Constructor with a lazy value supplier.
052    *
053    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
054    */
055   public Forwarded(Supplier<String> valueSupplier) {
056      super(NAME, valueSupplier);
057   }
058
059   /**
060    * Static factory method with an eager value.
061    *
062    * @param value The header value. May be <jk>null</jk>.
063    * @return A new instance. Never <jk>null</jk>.
064    */
065   public static Forwarded of(String value) {
066      return new Forwarded(value);
067   }
068
069   /**
070    * Static factory method with a lazy value supplier.
071    *
072    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
073    * @return A new instance. Never <jk>null</jk>.
074    */
075   public static Forwarded of(Supplier<String> valueSupplier) {
076      return new Forwarded(valueSupplier);
077   }
078
079   /**
080    * Creates a new empty {@link Builder}.
081    *
082    * @return A new builder. Never <jk>null</jk>.
083    */
084   public static Builder create() {
085      return new Builder();
086   }
087
088   //------------------------------------------------------------------------------------------------------------------
089   // Inner types
090   //------------------------------------------------------------------------------------------------------------------
091
092   /**
093    * Fluent builder for assembling an HTTP <c>Forwarded</c> header value.
094    *
095    * <p>
096    * Composes the forwarding information defined by
097    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7239'>RFC 7239</a> into the
098    * <c>key=value;key=value, key=value</c> wire format: <c>;</c>-separated parameters within a single forwarded
099    * element (one proxy hop) and <c>,</c>-separated elements across hops.  Typed setters cover the four registered
100    * parameters (<c>by</c>, <c>for</c>, <c>host</c>, <c>proto</c>); {@link #param(String, String)} is a generic escape
101    * hatch.  Values are emitted as bare tokens when possible and quoted automatically when they are not valid tokens
102    * (e.g. IPv6 node identifiers such as <c>"[2001:db8::1]:8080"</c>).  Call {@link #next()} to start a new hop.
103    *
104    * <h5 class='section'>Example:</h5>
105    * <p class='bjava'>
106    *    String <jv>value</jv> = Forwarded.<jsm>create</jsm>()
107    *       .forValue(<js>"192.0.2.60"</js>)
108    *       .proto(<js>"http"</js>)
109    *       .by(<js>"203.0.113.43"</js>)
110    *       .next()
111    *       .forValue(<js>"198.51.100.17"</js>)
112    *       .build();
113    *    <jc>// =&gt; "for=192.0.2.60;proto=http;by=203.0.113.43, for=198.51.100.17"</jc>
114    * </p>
115    *
116    * <h5 class='section'>See Also:</h5><ul>
117    *    <li class='jc'>{@link Forwarded}
118    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7239'>RFC 7239 - Forwarded HTTP Extension</a>
119    * </ul>
120    *
121    * @since 10.0.0
122    */
123   public static class Builder {
124
125      private final List<Map<String,String>> elements = l();
126      private Map<String,String> current;
127
128      /**
129       * Constructor.
130       */
131      public Builder() {
132         current = m();
133         elements.add(current);
134      }
135
136      /**
137       * Sets the <c>by</c> parameter (the interface where the request came in to the proxy) on the current element.
138       *
139       * @param value The node identifier. Must not be <jk>null</jk>.
140       * @return This object.
141       */
142      public Builder by(String value) {
143         return param("by", value);
144      }
145
146      /**
147       * Sets the <c>for</c> parameter (the client that initiated the request) on the current element.
148       *
149       * @param value The node identifier. Must not be <jk>null</jk>.
150       * @return This object.
151       */
152      public Builder forValue(String value) {
153         return param("for", value);
154      }
155
156      /**
157       * Sets the <c>host</c> parameter (the original <c>Host</c> request header) on the current element.
158       *
159       * @param value The host. Must not be <jk>null</jk>.
160       * @return This object.
161       */
162      public Builder host(String value) {
163         return param("host", value);
164      }
165
166      /**
167       * Sets the <c>proto</c> parameter (the protocol used to make the request) on the current element.
168       *
169       * @param value The protocol (e.g. <js>"http"</js>, <js>"https"</js>). Must not be <jk>null</jk>.
170       * @return This object.
171       */
172      public Builder proto(String value) {
173         return param("proto", value);
174      }
175
176      /**
177       * Generic escape hatch for setting any forwarded parameter by name on the current element.
178       *
179       * <p>
180       * Replaces any previously-registered value for the same parameter while preserving its original position.  The
181       * value is emitted as a bare token when valid, otherwise as an escaped quoted-string.
182       *
183       * @param name The parameter name. Must not be <jk>null</jk> or blank.
184       * @param value The parameter value. Must not be <jk>null</jk>.
185       * @return This object.
186       * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank.
187       */
188      public Builder param(String name, String value) {
189         reqnn("name", name);
190         reqnn("value", value);
191         var n = name.trim();
192         if (n.isEmpty())
193            throw iaex("forwarded parameter name must not be blank");
194         current.put(n, value);
195         return this;
196      }
197
198      /**
199       * Commits the current forwarded element and begins a new one (the next proxy hop).
200       *
201       * @return This object.
202       */
203      public Builder next() {
204         current = m();
205         elements.add(current);
206         return this;
207      }
208
209      private static boolean isToken(String value) {
210         if (value.isEmpty())
211            return false;
212         for (var i = 0; i < value.length(); i++) {
213            var c = value.charAt(i);
214            var ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
215               || "!#$%&'*+-.^_`|~".indexOf(c) >= 0;
216            if (! ok)
217               return false;
218         }
219         return true;
220      }
221
222      private static String render(String value) {
223         if (isToken(value))
224            return value;
225         var sb = new StringBuilder(value.length() + 2);
226         sb.append('"');
227         for (var i = 0; i < value.length(); i++) {
228            var c = value.charAt(i);
229            if (c == '\\' || c == '"')
230               sb.append('\\');
231            sb.append(c);
232         }
233         sb.append('"');
234         return sb.toString();
235      }
236
237      /**
238       * Builds the rendered <c>Forwarded</c> header value.
239       *
240       * <p>
241       * Empty elements (hops with no parameters) are omitted.  Returns an empty string when nothing has been
242       * registered.
243       *
244       * @return The header value. Never <jk>null</jk>.
245       */
246      public String build() {
247         var rendered = new ArrayList<String>(elements.size());
248         for (var e : elements) {
249            if (e.isEmpty())
250               continue;
251            var parts = new ArrayList<String>(e.size());
252            e.forEach((k, v) -> parts.add(k + "=" + render(v)));
253            rendered.add(String.join(";", parts));
254         }
255         return String.join(", ", rendered);
256      }
257
258      /**
259       * Builds a {@link Forwarded} header bean directly from this builder.
260       *
261       * @return A {@link Forwarded} carrying {@link #build()}. Never <jk>null</jk>.
262       */
263      public Forwarded toHeader() {
264         return Forwarded.of(build());
265      }
266
267      @Override
268      public String toString() {
269         return build();
270      }
271   }
272}