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.nio.charset.*; 022import java.util.*; 023import java.util.function.*; 024 025/** 026 * Represents an HTTP <c>Proxy-Authorization</c> header. 027 * 028 * <p> 029 * Authorization credentials for connecting to a proxy. 030 * 031 * <h5 class='section'>See Also:</h5><ul> 032 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 033 * </ul> 034 * 035 * @since 9.2.1 036 */ 037@SuppressWarnings({ 038 "java:S1192" // Duplicated "value" literals are HTTP header component keys; a constant would obscure the header grammar. 039}) 040public class ProxyAuthorization extends HttpStringHeader { 041 042 /** The header name */ 043 public static final String NAME = "Proxy-Authorization"; 044 045 /** 046 * Constructor with an eager value. 047 * 048 * @param value The header value. May be <jk>null</jk>. 049 */ 050 public ProxyAuthorization(String value) { 051 super(NAME, value); 052 } 053 054 /** 055 * Constructor with a lazy value supplier. 056 * 057 * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>. 058 */ 059 public ProxyAuthorization(Supplier<String> valueSupplier) { 060 super(NAME, valueSupplier); 061 } 062 063 /** 064 * Static factory method with an eager value. 065 * 066 * @param value The header value. May be <jk>null</jk>. 067 * @return A new instance. Never <jk>null</jk>. 068 */ 069 public static ProxyAuthorization of(String value) { 070 return new ProxyAuthorization(value); 071 } 072 073 /** 074 * Static factory method with a lazy value supplier. 075 * 076 * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>. 077 * @return A new instance. Never <jk>null</jk>. 078 */ 079 public static ProxyAuthorization of(Supplier<String> valueSupplier) { 080 return new ProxyAuthorization(valueSupplier); 081 } 082 083 /** 084 * Creates a new empty {@link Builder}. 085 * 086 * @return A new builder. Never <jk>null</jk>. 087 */ 088 public static Builder create() { 089 return new Builder(); 090 } 091 092 /** 093 * Creates a {@link Builder} pre-populated with <c>Basic</c> credentials (base64 of <c>user:password</c>). 094 * 095 * @param user The username. Must not be <jk>null</jk>. 096 * @param password The password. Must not be <jk>null</jk>. 097 * @return A new builder. Never <jk>null</jk>. 098 */ 099 public static Builder basic(String user, String password) { 100 return create().basic(user, password); 101 } 102 103 /** 104 * Creates a {@link Builder} pre-populated with <c>Bearer</c> credentials. 105 * 106 * @param token The bearer token. Must not be <jk>null</jk>. 107 * @return A new builder. Never <jk>null</jk>. 108 */ 109 public static Builder bearer(String token) { 110 return create().bearer(token); 111 } 112 113 /** 114 * Creates a {@link Builder} pre-populated with a <c>Digest</c> scheme. 115 * 116 * @return A new builder. Never <jk>null</jk>. 117 */ 118 public static Builder digest() { 119 return create().digest(); 120 } 121 122 //------------------------------------------------------------------------------------------------------------------ 123 // Inner types 124 //------------------------------------------------------------------------------------------------------------------ 125 126 /** 127 * Fluent builder for assembling an HTTP <c>Proxy-Authorization</c> credentials value. 128 * 129 * <p> 130 * Composes the <c>scheme 1*SP ( token68 / #auth-param )</c> credentials form defined by 131 * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235#section-4.4'>RFC 7235 ยง4.4</a>. The 132 * {@link #basic(String, String)} and {@link #bearer(String)} presets render the opaque <c>token68</c> form; the 133 * {@link #digest()} preset enables the typed Digest auth-params, which apply the correct quoting (quoted-string vs 134 * token). {@link #param(String, String)} is a generic escape hatch so new params never require an API change. 135 * Setting a scheme preset or {@link #token(String)} clears any previously-registered auth-params and vice versa, so 136 * a built value carries either a token68 or an auth-param list, never both. 137 * 138 * <h5 class='section'>Example:</h5> 139 * <p class='bjava'> 140 * String <jv>value</jv> = ProxyAuthorization.<jsm>create</jsm>() 141 * .digest() 142 * .username(<js>"Mufasa"</js>) 143 * .realm(<js>"http-auth@example.org"</js>) 144 * .uri(<js>"/dir/index.html"</js>) 145 * .qop(<js>"auth"</js>) 146 * .nc(<js>"00000001"</js>) 147 * .response(<js>"8ca523f5e9506fed4657c9700eebdbec"</js>) 148 * .build(); 149 * <jc>// => "Digest username=\"Mufasa\", realm=\"...\", uri=\"/dir/index.html\", qop=auth, nc=00000001, response=\"...\""</jc> 150 * </p> 151 * 152 * <h5 class='section'>See Also:</h5><ul> 153 * <li class='jc'>{@link ProxyAuthorization} 154 * <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7235'>RFC 7235 - HTTP/1.1 Authentication</a> 155 * </ul> 156 * 157 * @since 10.0.0 158 */ 159 public static class Builder { 160 161 private String scheme; 162 private String token68; 163 private final Map<String,String> params = m(); 164 165 /** 166 * Sets the authentication scheme (e.g. <js>"Basic"</js>, <js>"Bearer"</js>, <js>"Digest"</js>). 167 * 168 * @param value The scheme name. Must not be <jk>null</jk> or blank. 169 * @return This object. 170 * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank. 171 */ 172 public Builder scheme(String value) { 173 reqnn("value", value); 174 var v = value.trim(); 175 if (v.isEmpty()) 176 throw iaex("scheme must not be blank"); 177 scheme = v; 178 return this; 179 } 180 181 /** 182 * Sets <c>Basic</c> credentials by base64-encoding <c>user:password</c> as the token68 form. 183 * 184 * @param user The username. Must not be <jk>null</jk>. 185 * @param password The password. Must not be <jk>null</jk>. 186 * @return This object. 187 */ 188 public Builder basic(String user, String password) { 189 reqnn("user", user); 190 reqnn("password", password); 191 var encoded = Base64.getEncoder().encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8)); 192 return scheme("Basic").token(encoded); 193 } 194 195 /** 196 * Sets <c>Bearer</c> credentials with the supplied token as the token68 form. 197 * 198 * @param token The bearer token. Must not be <jk>null</jk>. 199 * @return This object. 200 */ 201 public Builder bearer(String token) { 202 return scheme("Bearer").token(token); 203 } 204 205 /** 206 * Sets the scheme to <c>Digest</c> (typed Digest auth-params can then be chained). 207 * 208 * @return This object. 209 */ 210 public Builder digest() { 211 return scheme("Digest"); 212 } 213 214 /** 215 * Sets the opaque <c>token68</c> credentials form, clearing any auth-params. 216 * 217 * @param value The token68 value. Must not be <jk>null</jk>. 218 * @return This object. 219 */ 220 public Builder token(String value) { 221 reqnn("value", value); 222 params.clear(); 223 token68 = value; 224 return this; 225 } 226 227 /** 228 * Sets the <c>username</c> auth-param (rendered as a quoted-string). 229 * 230 * @param value The username. Must not be <jk>null</jk>. 231 * @return This object. 232 */ 233 public Builder username(String value) { 234 return quotedParam("username", value); 235 } 236 237 /** 238 * Sets the <c>realm</c> auth-param (rendered as a quoted-string). 239 * 240 * @param value The realm. Must not be <jk>null</jk>. 241 * @return This object. 242 */ 243 public Builder realm(String value) { 244 return quotedParam("realm", value); 245 } 246 247 /** 248 * Sets the <c>nonce</c> auth-param (rendered as a quoted-string). 249 * 250 * @param value The server nonce echoed back from the challenge. Must not be <jk>null</jk>. 251 * @return This object. 252 */ 253 public Builder nonce(String value) { 254 return quotedParam("nonce", value); 255 } 256 257 /** 258 * Sets the <c>uri</c> auth-param (rendered as a quoted-string). 259 * 260 * @param value The effective request URI. Must not be <jk>null</jk>. 261 * @return This object. 262 */ 263 public Builder uri(String value) { 264 return quotedParam("uri", value); 265 } 266 267 /** 268 * Sets the <c>response</c> auth-param (rendered as a quoted-string). 269 * 270 * @param value The computed digest response. Must not be <jk>null</jk>. 271 * @return This object. 272 */ 273 public Builder response(String value) { 274 return quotedParam("response", value); 275 } 276 277 /** 278 * Sets the <c>cnonce</c> auth-param (rendered as a quoted-string). 279 * 280 * @param value The client nonce. Must not be <jk>null</jk>. 281 * @return This object. 282 */ 283 public Builder cnonce(String value) { 284 return quotedParam("cnonce", value); 285 } 286 287 /** 288 * Sets the <c>opaque</c> auth-param (rendered as a quoted-string). 289 * 290 * @param value The opaque value echoed back from the challenge. Must not be <jk>null</jk>. 291 * @return This object. 292 */ 293 public Builder opaque(String value) { 294 return quotedParam("opaque", value); 295 } 296 297 /** 298 * Sets the <c>algorithm</c> auth-param (rendered as a bare token, unquoted). 299 * 300 * @param value The algorithm (e.g. <js>"MD5"</js>, <js>"SHA-256"</js>). Must not be <jk>null</jk>. 301 * @return This object. 302 */ 303 public Builder algorithm(String value) { 304 return tokenParam("algorithm", value); 305 } 306 307 /** 308 * Sets the <c>qop</c> auth-param (rendered as a bare token, unquoted; a single value in credentials). 309 * 310 * @param value The quality-of-protection token (e.g. <js>"auth"</js>). Must not be <jk>null</jk>. 311 * @return This object. 312 */ 313 public Builder qop(String value) { 314 return tokenParam("qop", value); 315 } 316 317 /** 318 * Sets the <c>nc</c> (nonce-count) auth-param (rendered as a bare token, unquoted). 319 * 320 * @param value The 8-digit hex nonce-count (e.g. <js>"00000001"</js>). Must not be <jk>null</jk>. 321 * @return This object. 322 */ 323 public Builder nc(String value) { 324 return tokenParam("nc", value); 325 } 326 327 /** 328 * Generic escape hatch for setting any auth-param by name, rendered as a quoted-string. 329 * 330 * <p> 331 * Replaces any previously-registered value for the same param while preserving its original position, and 332 * clears any previously-set {@link #token(String) token68}. 333 * 334 * @param name The auth-param name. Must not be <jk>null</jk> or blank. 335 * @param value The auth-param value (quoted automatically). Must not be <jk>null</jk>. 336 * @return This object. 337 * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank. 338 */ 339 public Builder param(String name, String value) { 340 return quotedParam(name, value); 341 } 342 343 private Builder quotedParam(String name, String value) { 344 reqnn("value", value); 345 token68 = null; 346 params.put(cleanName(name), q(value)); 347 return this; 348 } 349 350 private Builder tokenParam(String name, String value) { 351 reqnn("value", value); 352 token68 = null; 353 params.put(cleanName(name), value); 354 return this; 355 } 356 357 private static String cleanName(String name) { 358 reqnn("name", name); 359 var n = name.trim(); 360 if (n.isEmpty()) 361 throw iaex("auth-param name must not be blank"); 362 return n; 363 } 364 365 private static String q(String value) { 366 var sb = new StringBuilder(value.length() + 2); 367 sb.append('"'); 368 for (var i = 0; i < value.length(); i++) { 369 var c = value.charAt(i); 370 if (c == '\\' || c == '"') 371 sb.append('\\'); 372 sb.append(c); 373 } 374 sb.append('"'); 375 return sb.toString(); 376 } 377 378 /** 379 * Builds the rendered <c>Proxy-Authorization</c> credentials value. 380 * 381 * <p> 382 * Returns an empty string when no scheme has been registered. 383 * 384 * @return The header value. Never <jk>null</jk>. 385 */ 386 public String build() { 387 if (scheme == null) 388 return ""; 389 var sb = new StringBuilder(scheme); 390 if (token68 != null) { 391 sb.append(' ').append(token68); 392 } else if (! params.isEmpty()) { 393 sb.append(' '); 394 var parts = new ArrayList<String>(params.size()); 395 params.forEach((k, v) -> parts.add(k + "=" + v)); 396 sb.append(String.join(", ", parts)); 397 } 398 return sb.toString(); 399 } 400 401 /** 402 * Builds a {@link ProxyAuthorization} header bean directly from this builder. 403 * 404 * @return A {@link ProxyAuthorization} carrying {@link #build()}. Never <jk>null</jk>. 405 */ 406 public ProxyAuthorization toHeader() { 407 return ProxyAuthorization.of(build()); 408 } 409 410 @Override 411 public String toString() { 412 return build(); 413 } 414 } 415}