001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.protocols.http; 020 021import java.text.ParseException; 022import java.util.Map; 023import java.util.TreeMap; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import static org.jdrupes.httpcodec.protocols.http.HttpConstants.*; 028 029import org.jdrupes.httpcodec.types.Converter; 030import org.jdrupes.httpcodec.types.Converters; 031 032/** 033 * A base class for all kinds of header field values. 034 * 035 * @param <T> the type of the header field's value 036 * 037 * @see "[Message Headers](https://www.iana.org/assignments/message-headers/message-headers.xhtml)" 038 */ 039public class HttpField<T> { 040 041 // RFC 7230 3.2, 3.2.4 042 protected static final Pattern headerLinePatter = Pattern 043 .compile("^(" + TOKEN_REGEXP + "):(.*)$"); 044 045 /** @see "[RFC 7231, 5.3.2](https://tools.ietf.org/html/rfc7231#section-5.3.2)" */ 046 public static final String ACCEPT = "Accept"; 047 /** @see "[RFC 7231, 5.3.3](https://tools.ietf.org/html/rfc7231#section-5.3.3)" */ 048 public static final String ACCEPT_CHARSET = "Accept-Charset"; 049 /** @see "[RFC 7231, 5.3.4](https://tools.ietf.org/html/rfc7231#section-5.3.4)" */ 050 public static final String ACCEPT_ENCODING = "Accept-Encoding"; 051 /** @see "[RFC 7231, 5.3.5](https://tools.ietf.org/html/rfc7231#section-5.3.5)" */ 052 public static final String ACCEPT_LANGUAGE = "Accept-Language"; 053 /** @see "[RFC 7234, 5.1](https://tools.ietf.org/html/rfc7234#section-5.1)" */ 054 public static final String AGE = "Age"; 055 /** @see "[RFC 7231, 7.4.1](https://tools.ietf.org/html/rfc7231#section-7.4.1)" */ 056 public static final String ALLOW = "Allow"; 057 /** @see "[RFC 7235, 4.2](https://tools.ietf.org/html/rfc7235#section-4.2)" */ 058 public static final String AUTHORIZATION = "Authorization"; 059 /** @see "[RFC 7234, 5.2](https://tools.ietf.org/html/rfc7234#section-5.2)" */ 060 public static final String CACHE_CONTROL = "Cache-Control"; 061 /** @see "[RFC 6265, 5.4](https://tools.ietf.org/html/rfc6265#section-5.4)" */ 062 public static final String COOKIE = "Cookie"; 063 /** @see "[RFC 7230, 6.1](https://tools.ietf.org/html/rfc7230#section-6.1)" */ 064 public static final String CONNECTION = "Connection"; 065 /** @see "[RFC 7230, 3.3.2](https://tools.ietf.org/html/rfc7230#section-3.3.2)" */ 066 public static final String CONTENT_LENGTH = "Content-Length"; 067 /** @see "[RFC 7231, 3.1.4.2](https://tools.ietf.org/html/rfc7231#section-3.1.4.2)" */ 068 public static final String CONTENT_LOCATION = "Content-Location"; 069 /** @see "[RFC 7231, 3.1.1.5](https://tools.ietf.org/html/rfc7231#section-3.1.1.5)" */ 070 public static final String CONTENT_TYPE = "Content-Type"; 071 /** @see "[RFC 7231, 7.1.1.2](https://tools.ietf.org/html/rfc7231#section-7.1.1.2)" */ 072 public static final String DATE = "Date"; 073 /** @see "[RFC 7232, 2.3](https://tools.ietf.org/html/rfc7232#section-2.3)" */ 074 public static final String ETAG = "ETag"; 075 /** @see "[RFC 7231, 5.1.1](https://tools.ietf.org/html/rfc7231#section-5.1.1)" */ 076 public static final String EXPECT = "Expect"; 077 /** @see "[RFC 7234, 5.3](https://tools.ietf.org/html/rfc7234#section-5.3)" */ 078 public static final String EXPIRES = "Expires"; 079 /** @see "[RFC 7231, 5.5.1](https://tools.ietf.org/html/rfc7231#section-5.5.1)" */ 080 public static final String FROM = "From"; 081 /** @see "[RFC 7230, 5.4](https://tools.ietf.org/html/rfc7230#section-5.4)" */ 082 public static final String HOST = "Host"; 083 /** @see "[RFC 7232, 3.1](https://tools.ietf.org/html/rfc7232#section-3.1)" */ 084 public static final String IF_MATCH = "If-Match"; 085 /** @see "[RFC 7232, 3.2](https://tools.ietf.org/html/rfc7232#section-3.2)" */ 086 public static final String IF_NONE_MATCH = "If-None-Match"; 087 /** @see "[RFC 7232, 3.3](https://tools.ietf.org/html/rfc7232#section-3.3)" */ 088 public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; 089 /** @see "[RFC 7232, 3.4](https://tools.ietf.org/html/rfc7232#section-3.4)" */ 090 public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; 091 /** @see "[RFC 7232, 2.2](https://tools.ietf.org/html/rfc7232#section-2.2)" */ 092 public static final String LAST_MODIFIED = "Last-Modified"; 093 /** @see "[RFC 7231, 7.1.2](https://tools.ietf.org/html/rfc7231#section-7.1.2)" */ 094 public static final String LOCATION = "Location"; 095 /** @see "[RFC 7231, 5.1.2](https://tools.ietf.org/html/rfc7231#section-5.1.2)" */ 096 public static final String MAX_FORWARDS = "Max-Forwards"; 097 /** @see "[RFC 7234, 5.4](https://tools.ietf.org/html/rfc7234#section-5.4)" */ 098 public static final String PRAGMA = "Pragma"; 099 /** @see "[RFC 7235, 4.3](https://tools.ietf.org/html/rfc7235#section-4.3)" */ 100 public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; 101 /** @see "[RFC 7235, 4.4](https://tools.ietf.org/html/rfc7235#section-4.4)" */ 102 public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; 103 /** @see "[RFC 7231, 7.1.3](https://tools.ietf.org/html/rfc7231#section-7.1.3)" */ 104 public static final String RETRY_AFTER = "Retry-After"; 105 /** @see "[RFC 7231, 7.4.2](https://tools.ietf.org/html/rfc7231#section-7.4.2)" */ 106 public static final String SERVER = "Server"; 107 /** @see "[RFC 6265, 4.1.1](https://tools.ietf.org/html/rfc6265#section-4.1.1)" */ 108 public static final String SET_COOKIE = "Set-Cookie"; 109 /** @see "[RFC 7230, 4.3](https://tools.ietf.org/html/rfc7230#section-4.3)" */ 110 public static final String TE = "TE"; 111 /** @see "[RFC 7230, 4.4](https://tools.ietf.org/html/rfc7230#section-4.4)" */ 112 public static final String TRAILER = "Trailer"; 113 /** @see "[RFC 7230, 3.3.1](https://tools.ietf.org/html/rfc7230#section-3.3.1)" */ 114 public static final String TRANSFER_ENCODING = "Transfer-Encoding"; 115 /** @see "[RFC 7230, 6.7](https://tools.ietf.org/html/rfc7230#section-6.7)" */ 116 public static final String UPGRADE = "Upgrade"; 117 /** @see "[RFC 7231, 5.5.3](https://tools.ietf.org/html/rfc7231#section-5.5.3)" */ 118 public static final String USER_AGENT = "User-Agent"; 119 /** @see "[RFC 7231, 7.1.4](https://tools.ietf.org/html/rfc7231#section-7.1.4)" */ 120 public static final String VARY = "Vary"; 121 /** @see "[RFC 7230, 5.7.1](https://tools.ietf.org/html/rfc7230#section-5.7.1)" */ 122 public static final String VIA = "Via"; 123 /** @see "[RFC 7234, 5.5](https://tools.ietf.org/html/rfc7234#section-5.5)" */ 124 public static final String WARNING = "Warning"; 125 /** @see "[RFC 7235, 4.1](https://tools.ietf.org/html/rfc7235#section-4.1)" */ 126 public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; 127 128 private static Map<String, String> fieldNameMap = new TreeMap<>( 129 String.CASE_INSENSITIVE_ORDER); 130 131 static { 132 fieldNameMap.put(ACCEPT, ACCEPT); 133 fieldNameMap.put(ACCEPT_CHARSET, ACCEPT_CHARSET); 134 fieldNameMap.put(ACCEPT_ENCODING, ACCEPT_ENCODING); 135 fieldNameMap.put(ACCEPT_LANGUAGE, ACCEPT_LANGUAGE); 136 fieldNameMap.put(AGE, AGE); 137 fieldNameMap.put(ALLOW, ALLOW); 138 fieldNameMap.put(AUTHORIZATION, AUTHORIZATION); 139 fieldNameMap.put(CACHE_CONTROL, CACHE_CONTROL); 140 fieldNameMap.put(COOKIE, COOKIE); 141 fieldNameMap.put(CONNECTION, CONNECTION); 142 fieldNameMap.put(CONTENT_LENGTH, CONTENT_LENGTH); 143 fieldNameMap.put(CONTENT_LOCATION, CONTENT_LOCATION); 144 fieldNameMap.put(CONTENT_TYPE, CONTENT_TYPE); 145 fieldNameMap.put(DATE, DATE); 146 fieldNameMap.put(ETAG, ETAG); 147 fieldNameMap.put(EXPECT, EXPECT); 148 fieldNameMap.put(EXPIRES, EXPIRES); 149 fieldNameMap.put(FROM, FROM); 150 fieldNameMap.put(HOST, HOST); 151 fieldNameMap.put(IF_MATCH, IF_MATCH); 152 fieldNameMap.put(IF_NONE_MATCH, IF_NONE_MATCH); 153 fieldNameMap.put(IF_MODIFIED_SINCE, IF_MODIFIED_SINCE); 154 fieldNameMap.put(IF_UNMODIFIED_SINCE, IF_UNMODIFIED_SINCE); 155 fieldNameMap.put(LAST_MODIFIED, LAST_MODIFIED); 156 fieldNameMap.put(LOCATION, LOCATION); 157 fieldNameMap.put(MAX_FORWARDS, MAX_FORWARDS); 158 fieldNameMap.put(PRAGMA, PRAGMA); 159 fieldNameMap.put(PROXY_AUTHENTICATE, PROXY_AUTHENTICATE); 160 fieldNameMap.put(PROXY_AUTHORIZATION, PROXY_AUTHORIZATION); 161 fieldNameMap.put(RETRY_AFTER, RETRY_AFTER); 162 fieldNameMap.put(SERVER, SERVER); 163 fieldNameMap.put(SET_COOKIE, SET_COOKIE); 164 fieldNameMap.put(TE, TE); 165 fieldNameMap.put(TRAILER, TRAILER); 166 fieldNameMap.put(TRANSFER_ENCODING, TRANSFER_ENCODING); 167 fieldNameMap.put(UPGRADE, UPGRADE); 168 fieldNameMap.put(USER_AGENT, USER_AGENT); 169 fieldNameMap.put(VARY, VARY); 170 fieldNameMap.put(VIA, VIA); 171 fieldNameMap.put(WARNING, WARNING); 172 fieldNameMap.put(WWW_AUTHENTICATE, WWW_AUTHENTICATE); 173 } 174 175 private final String name; 176 private T value; 177 private Converter<T> converter; 178 179 /** 180 * Creates a new representation of a header field with the 181 * given value and converter. For fields with a 182 * constant definition in this class, the name is normalized. 183 * 184 * @param name the field name 185 * @param value the value 186 * @param converter the converter 187 */ 188 public HttpField(String name, T value, Converter<T> converter) { 189 this.name = fieldNameMap.getOrDefault(name, name); 190 this.converter = converter; 191 this.value = value; 192 } 193 194 /** 195 * Creates a new representation of a header field from its textual 196 * representation. For fields with a 197 * constant definition in this class, the name is normalized. 198 * 199 * @param headerLine the header line 200 * @param converter the converter 201 * @throws ParseException if an error occurs while parsing the header line 202 */ 203 public HttpField(String headerLine, Converter<T> converter) 204 throws ParseException { 205 this.converter = converter; 206 Matcher hlp = headerLinePatter.matcher(headerLine); 207 if (!hlp.matches()) { 208 throw new ParseException("Invalid header: ", 0); 209 } 210 this.name = fieldNameMap.getOrDefault(hlp.group(1), hlp.group(1)); 211 // RFC 7230 3.2.4 212 this.value = converter.fromFieldValue(hlp.group(2).trim()); 213 } 214 215 /** 216 * Returns the proper converter for the header field with the given 217 * name. Works for all well known 218 * field names, i.e. the field names defined as constants in this class. 219 * If the field name is unknown, a string converter is returned. 220 * 221 * @param fieldName 222 * the field name 223 * @return the converter 224 */ 225 public static Converter<?> lookupConverter(String fieldName) { 226 String normalizedFieldName = fieldNameMap 227 .getOrDefault(fieldName, fieldName); 228 switch (normalizedFieldName) { 229 case ACCEPT: 230 return Converters.MEDIA_RANGE; 231 case ACCEPT_LANGUAGE: 232 return Converters.LANGUAGE; 233 case AGE: 234 return Converters.LONG; 235 case ALLOW: 236 return Converters.STRING_LIST; 237 case AUTHORIZATION: 238 return Converters.CREDENTIALS; 239 case CACHE_CONTROL: 240 return Converters.CACHE_CONTROL_LIST; 241 case COOKIE: 242 return Converters.COOKIE_LIST; 243 case CONNECTION: 244 return Converters.STRING_LIST; 245 case CONTENT_LENGTH: 246 return Converters.LONG; 247 case CONTENT_LOCATION: 248 return Converters.URI_CONV; 249 case CONTENT_TYPE: 250 return Converters.MEDIA_TYPE; 251 case DATE: 252 return Converters.DATE_TIME; 253 case EXPIRES: 254 return Converters.DATE_TIME; 255 case HOST: 256 return Converters.UNQUOTED_STRING; 257 case IF_MATCH: 258 return Converters.ETAG_LIST; 259 case IF_MODIFIED_SINCE: 260 return Converters.DATE_TIME; 261 case IF_NONE_MATCH: 262 return Converters.ETAG_LIST; 263 case IF_UNMODIFIED_SINCE: 264 return Converters.DATE_TIME; 265 case LAST_MODIFIED: 266 return Converters.DATE_TIME; 267 case LOCATION: 268 return Converters.URI_CONV; 269 case MAX_FORWARDS: 270 return Converters.LONG; 271 case PRAGMA: 272 return Converters.DIRECTIVE_LIST; 273 case PROXY_AUTHENTICATE: 274 return Converters.CHALLENGE_LIST; 275 case PROXY_AUTHORIZATION: 276 return Converters.CREDENTIALS; 277 case RETRY_AFTER: 278 return Converters.DATE_TIME; 279 case SERVER: 280 return Converters.PRODUCT_DESCRIPTIONS; 281 case SET_COOKIE: 282 return Converters.SET_COOKIE; 283 case TRAILER: 284 return Converters.STRING_LIST; 285 case TRANSFER_ENCODING: 286 return Converters.STRING_LIST; 287 case UPGRADE: 288 return Converters.STRING_LIST; 289 case USER_AGENT: 290 return Converters.PRODUCT_DESCRIPTIONS; 291 case VIA: 292 return Converters.STRING_LIST; 293 case WARNING: 294 return Converters.STRING; 295 case WWW_AUTHENTICATE: 296 return Converters.CHALLENGE_LIST; 297 default: 298 return Converters.STRING; 299 } 300 } 301 302 /** 303 * Returns the header field name. 304 * 305 * @return the name 306 */ 307 public String name() { 308 return name; 309 } 310 311 /** 312 * Returns the header field's parsed value. 313 * 314 * @return the field's value 315 */ 316 public T value() { 317 return value; 318 } 319 320 /** 321 * Returns the cconverter used by this field. 322 * 323 * @return the converter 324 */ 325 public Converter<T> converter() { 326 return converter; 327 } 328 329 /** 330 * Sets the header field's value. 331 * 332 * @param value the new value 333 * @return the field for easy chaining 334 */ 335 public HttpField<T> setValue(T value) { 336 this.value = value; 337 return this; 338 } 339 340 /** 341 * Returns the string representation of this field's value. 342 * 343 * @return the field value as string 344 */ 345 public String asFieldValue() { 346 return converter.asFieldValue(value); 347 } 348 349 /** 350 * Returns the string representation of this header field as it appears in 351 * an HTTP message. Note that the returned string may span several 352 * lines (may contain CR/LF), but is has no trailing CR/LF. 353 * 354 * @return the field as it occurs in a header 355 */ 356 public String asHeaderField() { 357 return converter.asHeaderField(name(), value); 358 } 359 360 /* 361 * (non-Javadoc) 362 * 363 * @see java.lang.Object#toString() 364 */ 365 @Override 366 public String toString() { 367 StringBuilder result = new StringBuilder(); 368 result.append(getClass().getSimpleName()); 369 result.append(" ["); 370 result.append(asHeaderField().replace("\r\n", " CRLF ")); 371 result.append("]"); 372 return result.toString(); 373 } 374}