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}