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