com.tinspx.util.net.Headers.java Source code

Java tutorial

Introduction

Here is the source code for com.tinspx.util.net.Headers.java

Source

/* Copyright (C) 2013-2014 Ian Teune <ian.teune@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.tinspx.util.net;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import static com.google.common.base.Preconditions.*;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ForwardingList;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
import com.google.common.primitives.Longs;
import com.tinspx.util.collect.Listenable;
import com.tinspx.util.collect.Predicated;
import com.tinspx.util.io.CAWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;

/**
 * Wraps a {@link ListMultimap} as collection of headers. The iteration order of
 * the headers is preserved. All polling methods of this class
 * ({@link #get(String) get}, {@link #contains(String) contains}, etc...) are
 * all case insensitive. However, the multimap and map return from
 * {@link #asMultimap()} and {@link #asMap()} represent the raw header strings
 * and <i>are</i> case sensitive.
 * <p>
 * {@link #parseContentType(CharSequence) parseContentType} and
 * {@link #parseMediaType(CharSequence) parseMediaType} can be used to parse
 * {@code Content-Type} headers.
 * 
 * @author Ian
 */
@NotThreadSafe
@ToString(of = { "normalize", "headers" })
@EqualsAndHashCode(of = "headers")
public class Headers implements Iterable<Map.Entry<String, String>> {

    /**
     * raw header values.
     */
    private ListMultimap<String, String> headers;
    /**
     * maps a canonical name to its actual raw header names.
     */
    @Nullable
    @VisibleForTesting
    SetMultimap<String, String> canons;
    /**
     * true when {@link #canons} needs to be rebuilt, typically because
     * {@link #headers} was directly modified.
     */
    private boolean rebuildCanons;
    /**
     * same as {@link #headers}, except with canonical names.
     */
    @Nullable
    @VisibleForTesting
    ListMultimap<String, String> canonHeaders;
    /**
     * true when {@link #canonHeaders} needs to be rebuilt, typically because
     * {@link #headers} was directly modified.
     */
    private boolean rebuildHeaders;
    /**
     * a modifiable view of {@link #headers} returned to the user. does not
     * allow null keys or values.
     */
    private @Nullable ListMultimap<String, String> view;

    private ListMultimap<String, String> view() {
        if (view == null) {
            view = Predicated.listMultimap(Listenable.listMultimap(headers, true, new Listenable.Modification() {
                @Override
                public void onModify(Object src, Listenable.Event type) {
                    rebuildCanons = true;
                    rebuildHeaders = true;
                }
            }), Predicates.notNull(), Predicates.notNull(), true);
        }
        return view;
    }

    /**
     * if true, header names are {@link #normalize(String) normalized}.
     */
    private boolean normalize;

    public Headers() {
        this.headers = LinkedListMultimap.create();
    }

    public Headers(Headers headers) {
        normalize = headers.normalize;
        this.headers = LinkedListMultimap.create(Math.max(8, headers.headers.keySet().size()));
        addAll(headers.headers.entries());
    }

    private ListMultimap<String, String> canonHeaders() {
        if (canonHeaders == null || rebuildHeaders) {
            if (canonHeaders != null) {
                canonHeaders.clear();
            } else {
                canonHeaders = LinkedListMultimap.create();
            }
            for (Map.Entry<String, String> h : headers.entries()) {
                canonHeaders.put(canonicalize(h.getKey()), h.getValue());
            }
            rebuildHeaders = false;
        }
        return canonHeaders;
    }

    private SetMultimap<String, String> canons() {
        if (canons == null || rebuildCanons) {
            if (canons != null) {
                canons.clear();
            } else {
                canons = HashMultimap.create();
            }
            for (String name : headers.keySet()) {
                canons.put(canonicalize(name), name);
            }
            rebuildCanons = false;
        }
        return canons;
    }

    /**
     * Determines if header names are {@link #normalize(String) normalized}.
     * Defaults to {@code false}. Note that headers will not be normalized if
     * they are added directly to the underlying {@link #asMultimap() multimap}
     * or {@link #asMap() map}, regardless of this setting.
     */
    public boolean isNormalize() {
        return normalize;
    }

    /**
     * Sets whether header names are {@link #normalize(String) normalized}.
     * Defaults to {@code false}. Note that headers will not be normalized if
     * they are added directly to the underlying {@link #asMultimap() multimap},
     * regardless of this setting.
     */
    public Headers setNormalize(boolean normalize) {
        if (normalize && !this.normalize && !headers.isEmpty()) {
            final ListMultimap<String, String> h = LinkedListMultimap.create(Math.max(8, headers.keySet().size()));
            for (Map.Entry<String, String> e : headers.entries()) {
                h.put(normalize(e.getKey()), e.getValue());
            }
            headers = h;
            view = null;
            rebuildCanons = true;
            rebuildHeaders = true;
        }
        this.normalize = normalize;
        return this;
    }

    /**
     * Returns a modifiable {@code ListMultimap} view of the headers.
     */
    public ListMultimap<String, String> asMultimap() {
        return view();
    }

    /**
     * Returns a modifiable {@code Map} view of the headers. Note that this map
     * is acquired through {@link Multimap#asMap()}, and therefore the map does
     * not support {@code put} or {@code putAll}, nor do its entries support
     * {@link java.util.Map.Entry#setValue(Object) setValue}.
     */
    public Map<String, List<String>> asMap() {
        return Multimaps.asMap(view());
    }

    @Override
    public Iterator<Map.Entry<String, String>> iterator() {
        return view().entries().iterator();
    }

    public boolean isEmpty() {
        return headers.isEmpty();
    }

    /**
     * Removes all headers associated with the specified header {@code name}.
     */
    public Headers removeAll(@Nullable String name) {
        if (name == null) {
            return this;
        }
        name = canonicalize(name);
        for (String header : canons().get(name)) {
            headers.removeAll(header);
        }
        canons().removeAll(name);
        if (canonHeaders != null && !rebuildHeaders) {
            canonHeaders.removeAll(name);
        }
        return this;
    }

    /**
     * Removes the header {@code name/value} pair. Other values associated with
     * {@code name} are not removed.
     */
    public Headers remove(@Nullable String name, @Nullable String value) {
        if (name == null || value == null) {
            return this;
        }
        name = canonicalize(name);
        final Set<String> singleton = Collections.singleton(value);
        for (String header : canons().get(name)) {
            headers.get(header).removeAll(singleton);
        }
        if (canonHeaders != null && !rebuildHeaders) {
            canonHeaders.get(name).removeAll(singleton);
        }
        return this;
    }

    /**
     * Replaces all existing headers with the specified {@code name} with
     * {@code value}. Equivalent to {@code removeAll(name).add(name, value)},
     * except iteration order will be preserved if possible.
     * 
     * @throws NullPointerException if either {@code name} or {@code value}
     * is {@code null}
     */
    public Headers set(@NonNull String name, @NonNull String value) {
        if (normalize) {
            name = normalize(name);
        }
        final Set<String> singleton = Collections.singleton(value);
        headers.replaceValues(name, singleton);
        final String cn = canonicalize(name);
        for (String header : canons().get(cn)) {
            if (!header.equals(name)) {
                headers.removeAll(header);
            }
        }
        canons().replaceValues(cn, Collections.singleton(name));
        if (canonHeaders != null && !rebuildHeaders) {
            canonHeaders.replaceValues(cn, singleton);
        }
        return this;
    }

    /**
     * Adds the specified header {@code name/value} pair.
     * 
     * @throws NullPointerException if either {@code name} or {@code value}
     * is {@code null}
     */
    public Headers add(@NonNull String name, @NonNull String value) {
        if (normalize) {
            name = normalize(name);
        }
        headers.put(name, value);
        String cn = null;
        if (canons != null && !rebuildCanons) {
            canons.put(cn = canonicalize(name), name);
        }
        if (canonHeaders != null && !rebuildHeaders) {
            canonHeaders.put(cn != null ? cn : canonicalize(name), value);
        }
        return this;
    }

    public Headers addAll(@NonNull Headers headers) {
        return addAll(headers.headers);
    }

    public Headers addAll(@NonNull Map<String, ? extends Iterable<String>> headers) {
        for (Map.Entry<String, ? extends Iterable<String>> e : headers.entrySet()) {
            addAll(e.getKey(), e.getValue());
        }
        return this;
    }

    public Headers addAllFrom(@NonNull Map<String, String> headers) {
        return addAll(headers.entrySet());
    }

    public Headers addAll(@NonNull Multimap<String, String> headers) {
        return addAll(headers.entries());
    }

    private Headers addAll(Iterable<Map.Entry<String, String>> entries) {
        for (Map.Entry<String, String> h : entries) {
            add(h.getKey(), h.getValue());
        }
        return this;
    }

    public Headers addAll(@NonNull String name, String... values) {
        return addAll(name, Arrays.asList(values));
    }

    public Headers addAll(@NonNull String name, Iterable<String> values) {
        for (String value : values) {
            add(name, value);
        }
        return this;
    }

    /**
     * Returns all values mapped to the specified header {@code name}. The
     * returned list is a "live" view of the headers mapped to {@code name}, so
     * any values added or removed with the given header {@code name} will be
     * reflected in the returned list. However, the returned list is <i>not</i>
     * modifiable.
     */
    public List<String> get(@Nullable String name) {
        if (name == null) {
            return Collections.emptyList();
        }
        final List<String> delegate = canonHeaders().get(canonicalize(name));
        return Collections.unmodifiableList(new ForwardingList<String>() {
            @Override
            protected List<String> delegate() {
                if (rebuildHeaders) {
                    canonHeaders();
                }
                return delegate;
            }
        });
    }

    /**
     * same as {@link #get(String)}, except not wrapped in an
     * unmodifiable/forwarding list.
     */
    private List<String> getImpl(@Nullable String name) {
        if (name == null) {
            return Collections.emptyList();
        }
        return canonHeaders().get(canonicalize(name));
    }

    /**
     * Returns the <i>first</i> header value mapped to {@code name}, or the
     * the empty {@code String} if there are none.
     */
    public String first(@Nullable String name) {
        final List<String> h = getImpl(name);
        return h.isEmpty() ? "" : Strings.nullToEmpty(h.get(0));
    }

    /**
     * Returns the <i>last</i> header value mapped to {@code name}, or the
     * the empty {@code String} if there are none.
     */
    public String last(@Nullable String name) {
        final List<String> h = getImpl(name);
        return h.isEmpty() ? "" : Strings.nullToEmpty(h.get(h.size() - 1));
    }

    /**
     * Returns the number of header values associated with the header
     * {@code name}.
     */
    public int count(@Nullable String name) {
        return getImpl(name).size();
    }

    /**
     * Returns the total number of headers. Equivalent to
     * {@code asMultimap().size()}.
     */
    public int size() {
        return headers.size();
    }

    /**
     * Determines if there is at least one value associated with the specified
     * header {@code name}.
     */
    public boolean contains(@Nullable String name) {
        if (name == null) {
            return false;
        }
        return headers.containsKey(name) || canonHeaders().containsKey(canonicalize(name));
    }

    public boolean contains(@Nullable String name, @Nullable String value) {
        if (name == null || value == null) {
            return false;
        }
        return headers.containsEntry(name, value) || canonHeaders().containsEntry(canonicalize(name), value);
    }

    public boolean containsAny(String... names) {
        return containsAny(Arrays.asList(names));
    }

    public boolean containsAny(Iterable<String> names) {
        for (String name : names) {
            if (contains(name)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsAll(String... names) {
        return containsAll(Arrays.asList(names));
    }

    public boolean containsAll(Iterable<String> names) {
        for (String name : names) {
            if (!contains(name)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Determines if the headers contains the specified name/value pair.
     * However, {@code valueFunction} is used to transform all header values
     * before checking for equality. {@code valueFunction} is applied to
     * {@code value} before searching.
     */
    public boolean contains(@Nullable String name, @Nullable String value,
            @NonNull Function<? super String, ? extends String> valueFunction) {
        if (name == null) {
            //can't check value==null as the function will affect the nullity
            //of the header values
            return false;
        }
        value = valueFunction.apply(value);
        for (final String hvalue : getImpl(name)) {
            if (Objects.equal(value, valueFunction.apply(hvalue))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determines if the headers contain a value mapped to {@code name} that
     * satisfies {@code valuePredicate}.
     */
    public boolean contains(@Nullable String name, @NonNull Predicate<? super String> valuePredicate) {
        if (name == null) {
            return false;
        }
        for (final String value : getImpl(name)) {
            if (valuePredicate.apply(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Removes the specified name/value pair. However, {@code valueFunction} is
     * used to transform all header values before checking for equality, which
     * may result in multiple headers being removed. {@code valueFunction} is
     * applied to {@code value} before searching.
     */
    public Headers remove(@Nullable String name, @Nullable String value,
            @NonNull Function<? super String, ? extends String> valueFunction) {
        if (name == null) {
            //can't check value==null as the function will affect the nullity
            //of the header values
            return this;
        }
        value = valueFunction.apply(value);
        for (final String header : canons().get(canonicalize(name))) {
            final List<String> values = headers.get(header);
            if (values.isEmpty()) {
                continue;
            }
            final Iterator<String> viter = values.iterator();
            while (viter.hasNext()) {
                if (Objects.equal(value, valueFunction.apply(viter.next()))) {
                    viter.remove();
                    rebuildHeaders = true;
                }
            }
        }
        return this;
    }

    /**
     * Removes all headers mapped to {@code name} that satisfy
     * {@code valuePredicate}.
     */
    public Headers remove(@Nullable String name, @NonNull Predicate<? super String> valuePredicate) {
        if (name == null) {
            return this;
        }
        for (final String header : canons().get(canonicalize(name))) {
            final List<String> values = headers.get(header);
            if (values.isEmpty()) {
                continue;
            }
            if (Iterables.removeIf(values, valuePredicate)) {
                rebuildHeaders = true;
            }
        }
        return this;
    }

    /**
     * Returns the value of the {@code Content-Length} header, or 0 if the
     * header is missing or invalid. Will not throw a
     * {@code NumberFormatException}.
     * 
     * @see #parseContentLength()
     */
    public long contentLength() {
        final String header = last(HttpHeaders.CONTENT_LENGTH).trim();
        if (header.isEmpty()) {
            return 0;
        } else {
            final Long len = Longs.tryParse(header);
            return len != null ? Math.max(0, len) : 0;
        }
    }

    /**
     * Parses a the value of the {@code Content-Length} header, throwing a
     * {@code NumberFormatException} if missing or invalid.
     * 
     * @see #contentLength()
     */
    public long parseContentLength() {
        final long len = Long.parseLong(last(HttpHeaders.CONTENT_LENGTH).trim());
        if (len < 0) {
            throw new NumberFormatException("Content-Length cannot be negative: " + len);
        }
        return len;
    }

    /**
     * Returns the value of the {@code Content-Type} header or the empty string
     * if missing.
     */
    public String contentType() {
        return last(HttpHeaders.CONTENT_TYPE);
    }

    /**
     * Sets the value of the {@code Content-Type} header.
     */
    public Headers contentType(@NonNull String contentType) {
        return set(HttpHeaders.CONTENT_TYPE, contentType);
    }

    /**
     * <i>very</i> leniently parses a Content-Type header as defined by
     * <a href="http://tools.ietf.org/html/rfc2045#section-5.1">RFC 2045</a>
     * and returns it as a {@link MediaType}.
     * <p>
     * See {@link #parseContentType(CharSequence)} for more
     * information on how {@code contentType} is parsed. If the type/subtype of
     * the media type is not available in {@code contentType}, this method will
     * throw an {@code IllegalArgumentException}.
     *
     * @param contentType the Content-Type header field to parse
     * @return the parsed {@code MediaType}
     * @throws IllegalArgumentException if the type/subtype information is not
     * present in {@code contentType}
     */
    public static MediaType parseMediaType(CharSequence contentType) {
        LenientContentType lct = new LenientContentType(contentType);
        lct.parse();
        return lct.convert();
    }

    /**
     * <i>very</i> leniently parses a Content-Type header as defined by
     * <a href="http://tools.ietf.org/html/rfc2045#section-5.1">RFC 2045</a>.
     * <p>
     * And when I say leniently, I mean this method will try to parse any mess
     * thrown at it. The {@code contentType} does not have to have the leading
     * string "Content-Type:". The field name does not have to be Content-Type
     * and may be missing the trailing colon as long as there is some other
     * delimiting characters. The media type may have no type or subtype or may
     * be missing all together. There may be any number of parameters, and every
     * parameter may be invalid. Invalid parameters are skipped/ignored.
     * Whitespace and comments are skipped/ignored anywhere delimiters are
     * allowed. Improperly nested/terminated comments will cause the all
     * subsequent characters to be skipped. Inside of quoted-string values,
     * "CRLF LWSP-char" linear-white-space that requires line folding is
     * stripped from the value unless the "CRLF LWSP-char" linear-white-space is
     * quoted with a preceding backslash ("\"), in which case "CRLF" is kept but
     * the "LWSP-char" char is stripped.
     * <p>
     * All tokens (type, subtype, attribute) are normalized to lowercase.
     * attribute values are case-sensitive and are not lowercased except for the
     * "charset" attribute (this is done to mimick behavior of guava
     * {@link MediaType}).
     * <p>
     * Most properties of the returned {@link ContentType} are optional due to
     * the leniency of this method. This method will never fail and always
     * returns a non-null value. However, the returned {@code ContentType}
     * may be empty.
     * 
     * @param contentType the Content-Type header field to parse
     * @return the parsed {@code ContentType}
     */
    public static ContentType parseContentType(CharSequence contentType) {
        LenientContentType lct = new LenientContentType(contentType);
        lct.parse();
        return lct;
    }

    /**
     * Represents a Content-Type as defined by
     * <a href="http://tools.ietf.org/html/rfc2045#section-5.1">RFC 2045</a>.
     * This interface is intended for parsing bad Content-Type header fields
     * and all parts of the Content-Type are optional.
     */
    public interface ContentType {
        /**
         * Returns the original Content-Type string source used to parse this
         * {@code ContentType}.
         */
        CharSequence source();

        Optional<String> type();

        Optional<String> subtype();

        ImmutableListMultimap<String, String> parameters();

        /**
         * If both {@link #type()} and {@link #subtype()} are present, a
         * {@link MediaType} is returned with the type/subtype and all
         * parameters of this {@code ContentType}.
         */
        Optional<MediaType> asMediaType();
    }

    static final CharMatcher TSPECIALS = CharMatcher.anyOf("()<>@,;:\\\"/[]?=");

    static final CharMatcher TOKEN = CharMatcher.ASCII.and(CharMatcher.JAVA_ISO_CONTROL.negate())
            .and(CharMatcher.isNot(' ')).and(TSPECIALS.negate());

    /**
     * <i>very</i> lenient Content-Type or media type parsing.
     * <p>
     * <a href="http://tools.ietf.org/html/rfc822#section-3.3">RFC 822</a>
     * <a href="http://tools.ietf.org/html/rfc2045#section-5.1">RFC 2045</a>
     * <a href="http://tools.ietf.org/html/rfc2046">RFC 2046</a>
     */
    static class LenientContentType implements ContentType {
        final CharSequence source;
        int pos;
        final int len;
        String type;
        String subtype;
        Multimap<String, String> params;
        ImmutableListMultimap<String, String> parameters;
        CAWriter buffer;
        boolean immediateParam;

        public LenientContentType(CharSequence input) {
            this.source = checkNotNull(input);
            this.len = input.length();
        }

        private CAWriter getBuffer() {
            if (buffer == null) {
                buffer = new CAWriter();
            } else {
                buffer.reset();
            }
            return buffer;
        }

        void parse() {
            if (pos >= len) {
                return;
            }
            if (tryParseContentType() || tryParseFieldName()) {
                parseTypes();
            }
            if (immediateParam) {
                parseParameter();
            }
            while (pos < len) {
                if (source.charAt(pos++) == ';') {
                    parseParameter();
                }
            }
            buffer = null;
        }

        /**
         * attempts to parse a single parameter in form attribute=value
         */
        void parseParameter() {
            parseDelimiter();
            int start = pos, i = start;
            for (; i < len && TOKEN.matches(source.charAt(i)); i++) {
                /*parse attr*/}
            //attr may be empty String
            String attr = source.subSequence(start, i).toString();
            pos = i;
            parseDelimiter();
            if (pos >= len || source.charAt(pos) != '=') {
                //no =, only add value if there is an attr name
                if (!attr.isEmpty()) {
                    addParam(attr, "");
                }
                return;
            }
            pos++;
            parseDelimiter();
            if (pos >= len) {
                addParam(attr, "");
                return;
            }
            String value;
            if (source.charAt(pos) == '"') {
                value = parseString();
            } else {
                start = i = pos;
                for (; i < len && TOKEN.matches(source.charAt(i)); i++) {
                    /*parse value*/}
                //value may be empty String
                value = source.subSequence(start, i).toString();
                pos = i;
            }
            addParam(attr, value);
        }

        private void addParam(String attr, String value) {
            if (params == null) {
                params = ArrayListMultimap.create();
            }
            attr = normalizeToken(attr);
            params.put(attr, normalizeParameterValue(attr, checkNotNull(value)));
        }

        /**
         * parses a quoted String defined by quoted-string in RFC 822. unquoted
         * "CRLF LWSP-char" is stripped, quoted "CRLF LWSP-char" writes
         * CRLF and strips the single LWSP-char. does not require CR to be
         * escaped.
         */
        String parseString() {
            assert source.charAt(pos) == '"';
            pos++;
            CAWriter buf = getBuffer();
            while (pos < len) {
                boolean append = true;
                char c = source.charAt(pos++);
                if (c == '"') {
                    return buf.toString();
                } else if (c == '\\') {
                    if (pos < len) {
                        c = source.charAt(pos++);
                        if (c == '\r' && pos < len && source.charAt(pos) == '\n') {
                            int p = pos + 1;
                            if (p < len) {
                                char lwspc = source.charAt(p);
                                if (lwspc == ' ' || lwspc == '\t') {
                                    buf.write('\r');
                                    buf.write('\n');
                                    append = false;
                                    pos = p + 1;
                                }
                            }
                        }
                    }
                } else if (c == '\r') {
                    if (pos < len && source.charAt(pos) == '\n') {
                        int p = pos + 1;
                        if (p < len) {
                            char lwspc = source.charAt(p);
                            if (lwspc == ' ' || lwspc == '\t') {
                                append = false;
                                pos = p + 1;
                            }
                        }
                    }
                }
                if (append) {
                    buf.append(c);
                }
            }
            return buf.toString();
        }

        /**
         * attempts to parse the type/subtype
         */
        void parseTypes() {
            if (pos >= len) {
                return;
            }
            parseDelimiter();
            if (pos >= len) {
                return;
            }
            int start = pos, i = start;
            for (; i < len && TOKEN.matches(source.charAt(i)); i++) {
                /*parse type*/}
            if (i > start) {
                type = normalizeToken(source.subSequence(start, i).toString());
                pos = i;
            }
            parseDelimiter();
            if (pos >= len || source.charAt(pos) != '/') {
                return;
            }
            pos++;
            parseDelimiter();
            start = i = pos;
            for (; i < len && TOKEN.matches(source.charAt(i)); i++) {
                /*parse subtype*/}
            if (i > start) {
                subtype = normalizeToken(source.subSequence(start, i).toString());
                pos = i;
            }
        }

        static final char[] CT = "content-type".toCharArray();

        /**
         * attempts to parse the leading "Content-Type:" string
         */
        boolean tryParseContentType() {
            if (pos >= len) {
                return false;
            }
            parseDelimiter();
            char c = source.charAt(pos);
            if (c != 'C' && c != 'c') {
                return false;
            }
            if (len - pos < CT.length) {
                return false;
            }
            for (int i = pos + 1, k = 1; k < CT.length; i++, k++) {
                if (Character.toLowerCase(source.charAt(i)) != CT[k]) {
                    return false;
                }
            }
            pos += CT.length;
            if (pos < len && source.charAt(pos) == ':') {
                pos++;
                return true;
            }
            parseDelimiter();
            if (pos < len && source.charAt(pos) == ':') {
                pos++;
            }
            return true;
        }

        /**
         * attempts to parse any field name
         * 
         * @return true if the type/subtype should be parsed, false to skip
         * this and go straight to parsing parameters
         */
        boolean tryParseFieldName() {
            int lastToken = -1; //start index of the last token encountered
            boolean inToken = false, //true when currently inside a token
                    parseTypes = true, //true if type/subtype should be parsed
                    broken = false; //true if the for loop was broken out of
            for (int p = pos; p < len; /*p incremented in loop*/) {
                char c = source.charAt(p);
                if (TOKEN.matches(c)) {
                    if (!inToken) {
                        inToken = true;
                        lastToken = p;
                    }
                    p++;
                } else if (c == ':') {
                    broken = true;
                    pos = p + 1;
                    break;
                } else if (c == '/' || c == ';') {
                    broken = true;
                    if (lastToken >= 0) {
                        pos = lastToken;
                    } else {
                        pos = p; //can't increment as c could be ';'
                        parseTypes = false;
                    }
                    break;
                } else if (c == '=') {
                    broken = true;
                    parseTypes = false;
                    immediateParam = true;
                    if (lastToken >= 0) {
                        pos = lastToken;
                    }
                    break;
                } else {
                    inToken = false;
                    pos = p;
                    parseDelimiter();
                    p = pos == p ? p + 1 : pos;
                }
            }
            if (!broken && lastToken >= 0) {
                pos = lastToken;
            }
            return parseTypes;
        }

        /**
         * skips white-space and comments
         */
        void parseDelimiter() {
            while (pos < len) {
                char c = source.charAt(pos);
                if (CharMatcher.WHITESPACE.matches(c)) {
                    pos++;
                } else if (c == '(') {
                    parseComment();
                } else {
                    break;
                }
            }
        }

        /**
         * parses a single comment, the current char must be '('
         * 
         * @return true if the comment ended correctly
         */
        boolean parseComment() {
            assert source.charAt(pos) == '(';
            pos++;
            int level = 1;
            while (pos < len) {
                char c = source.charAt(pos++);
                if (c == ')') {
                    if (--level == 0) {
                        return true;
                    }
                } else if (c == '(') {
                    level++;
                } else if (c == '\\') {
                    pos++;
                }
            }
            return false;
        }

        @Override
        public CharSequence source() {
            return source;
        }

        @Override
        public Optional<String> type() {
            return Optional.fromNullable(type);
        }

        @Override
        public Optional<String> subtype() {
            return Optional.fromNullable(subtype);
        }

        @Override
        public ImmutableListMultimap<String, String> parameters() {
            if (parameters == null) {
                if (params != null) {
                    parameters = ImmutableListMultimap.copyOf(params);
                    params = null;
                } else {
                    parameters = ImmutableListMultimap.of();
                }
            }
            return parameters;
        }

        @Override
        public Optional<MediaType> asMediaType() {
            if (type != null && subtype != null) {
                MediaType mt = MediaType.create(type, subtype);
                return Optional.of(parameters().isEmpty() ? mt : mt.withParameters(parameters()));
            } else {
                return Optional.absent();
            }
        }

        MediaType convert() {
            if (type != null && subtype != null) {
                MediaType mt = MediaType.create(type, subtype);
                return parameters().isEmpty() ? mt : mt.withParameters(parameters());
            } else {
                throw new IllegalStateException(String.format("missing type/subtype; %s", this));
            }
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this).add("source", source).add("type", type).add("subtype", subtype)
                    .add("parameters", parameters()).toString();
        }
    }

    //normalizeToken and normalizeParameterValue are the same as guava MediaType
    static String normalizeToken(String token) {
        return Ascii.toLowerCase(token);
    }

    static String normalizeParameterValue(String attribute, String value) {
        return "charset".equals(attribute) ? Ascii.toLowerCase(value) : value;
    }

    private static final ImmutableSet<String> HTTP_HEADERS = ImmutableSet.of(HttpHeaders.CACHE_CONTROL,
            HttpHeaders.CONTENT_LENGTH, HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE, HttpHeaders.PRAGMA,
            HttpHeaders.VIA, HttpHeaders.WARNING, HttpHeaders.ACCEPT, HttpHeaders.ACCEPT_CHARSET,
            HttpHeaders.ACCEPT_ENCODING, HttpHeaders.ACCEPT_LANGUAGE, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS,
            HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.AUTHORIZATION, HttpHeaders.CONNECTION,
            HttpHeaders.COOKIE, HttpHeaders.EXPECT, HttpHeaders.FROM, HttpHeaders.FOLLOW_ONLY_WHEN_PRERENDER_SHOWN,
            HttpHeaders.HOST, HttpHeaders.IF_MATCH, HttpHeaders.IF_MODIFIED_SINCE, HttpHeaders.IF_NONE_MATCH,
            HttpHeaders.IF_RANGE, HttpHeaders.IF_UNMODIFIED_SINCE, HttpHeaders.LAST_EVENT_ID,
            HttpHeaders.MAX_FORWARDS, HttpHeaders.ORIGIN, HttpHeaders.PROXY_AUTHORIZATION, HttpHeaders.RANGE,
            HttpHeaders.REFERER, HttpHeaders.TE, HttpHeaders.UPGRADE, HttpHeaders.USER_AGENT,
            HttpHeaders.ACCEPT_RANGES, HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
            HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
            HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
            HttpHeaders.ACCESS_CONTROL_MAX_AGE, HttpHeaders.AGE, HttpHeaders.ALLOW, HttpHeaders.CONTENT_DISPOSITION,
            HttpHeaders.CONTENT_ENCODING, HttpHeaders.CONTENT_LANGUAGE, HttpHeaders.CONTENT_LOCATION,
            HttpHeaders.CONTENT_MD5, HttpHeaders.CONTENT_RANGE, HttpHeaders.CONTENT_SECURITY_POLICY,
            HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, HttpHeaders.ETAG, HttpHeaders.EXPIRES,
            HttpHeaders.LAST_MODIFIED, HttpHeaders.LINK, HttpHeaders.LOCATION, HttpHeaders.P3P,
            HttpHeaders.PROXY_AUTHENTICATE, HttpHeaders.REFRESH, HttpHeaders.RETRY_AFTER, HttpHeaders.SERVER,
            HttpHeaders.SET_COOKIE, HttpHeaders.SET_COOKIE2, HttpHeaders.STRICT_TRANSPORT_SECURITY,
            HttpHeaders.TIMING_ALLOW_ORIGIN, HttpHeaders.TRAILER, HttpHeaders.TRANSFER_ENCODING, HttpHeaders.VARY,
            HttpHeaders.WWW_AUTHENTICATE, HttpHeaders.DNT, HttpHeaders.X_CONTENT_TYPE_OPTIONS,
            HttpHeaders.X_DO_NOT_TRACK, HttpHeaders.X_FORWARDED_FOR, HttpHeaders.X_FORWARDED_PROTO,
            HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.X_POWERED_BY, HttpHeaders.PUBLIC_KEY_PINS,
            HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, HttpHeaders.X_REQUESTED_WITH, HttpHeaders.X_USER_IP,
            HttpHeaders.X_XSS_PROTECTION);

    private static String canonicalize(String name) {
        name = name.trim();
        while (name.endsWith(":")) {
            name = name.substring(0, name.length() - 1).trim();
        }
        return Ascii.toLowerCase(name);
    }

    private static final Map<String, String> HEADER_MAP = Maps.newHashMap();
    static {
        for (String name : HTTP_HEADERS) {
            HEADER_MAP.put(canonicalize(name), name);
        }
    }

    /**
     * Returns all headers found in {@link HttpHeaders} as an
     * {@code ImmutableSet}.
     */
    public static ImmutableSet<String> httpHeaders() {
        return HTTP_HEADERS;
    }

    /**
     * Normalizes the header name to standard name found in {@link HttpHeaders}.
     * If {@code headerName} does not match any header in {@code HttpHeaders},
     * it is returned as is.
     * 
     * @param headerName the header name to normalize
     * @return the normalized header or null if {@code headerName} is null
     */
    public static String normalize(@Nullable String headerName) {
        if (headerName == null || HTTP_HEADERS.contains(headerName)) {
            return headerName;
        }
        return MoreObjects.firstNonNull(HEADER_MAP.get(canonicalize(headerName)), headerName);
    }

    private static final Function<String, String> NORMALIZE = new Function<String, String>() {
        @Override
        public String apply(String input) {
            return normalize(input);
        }
    };

    public static Function<String, String> normalize() {
        return NORMALIZE;
    }
}