All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.sdk.nio.http.common.HttpHeaders Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.sdk.nio.http.common;

import java.io.IOException;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.zodiac.sdk.toolkit.util.StringUtil;

public class HttpHeaders extends HashMap> implements Serializable {

    private static final long serialVersionUID = -6119777763403259588L;

    private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");

    private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ENGLISH);

    private static final ZoneId GMT = ZoneId.of("GMT");

    private static final DateTimeFormatter[] DATE_PARSERS = new DateTimeFormatter[] {
        DateTimeFormatter.RFC_1123_DATE_TIME,
        DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
        DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)
    };

    /**
     * Date formats with time zone as specified in the HTTP RFC to use for formatting.
     * @see Section 7.1.1.1 of RFC 7231
     */
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);

    /**
     * The HTTP {@code Accept} header field name.
     * @see Section 5.3.2 of RFC 7231
     */
    public static final String ACCEPT = "Accept";
    /**
     * The HTTP {@code Accept-Charset} header field name.
     * @see Section 5.3.3 of RFC 7231
     */
    public static final String ACCEPT_CHARSET = "Accept-Charset";
    /**
     * The HTTP {@code Accept-Encoding} header field name.
     * @see Section 5.3.4 of RFC 7231
     */
    public static final String ACCEPT_ENCODING = "Accept-Encoding";
    /**
     * The HTTP {@code Accept-Language} header field name.
     * @see Section 5.3.5 of RFC 7231
     */
    public static final String ACCEPT_LANGUAGE = "Accept-Language";
    /**
     * The HTTP {@code Accept-Ranges} header field name.
     * @see Section 5.3.5 of RFC 7233
     */
    public static final String ACCEPT_RANGES = "Accept-Ranges";
    /**
     * The CORS {@code Access-Control-Allow-Credentials} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
    /**
     * The CORS {@code Access-Control-Allow-Headers} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    /**
     * The CORS {@code Access-Control-Allow-Methods} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    /**
     * The CORS {@code Access-Control-Allow-Origin} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    /**
     * The CORS {@code Access-Control-Expose-Headers} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
    /**
     * The CORS {@code Access-Control-Max-Age} response header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
    /**
     * The CORS {@code Access-Control-Request-Headers} request header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    /**
     * The CORS {@code Access-Control-Request-Method} request header field name.
     * @see CORS W3C recommendation
     */
    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
    /**
     * The HTTP {@code Age} header field name.
     * @see Section 5.1 of RFC 7234
     */
    public static final String AGE = "Age";
    /**
     * The HTTP {@code Allow} header field name.
     * @see Section 7.4.1 of RFC 7231
     */
    public static final String ALLOW = "Allow";
    /**
     * The HTTP {@code Authorization} header field name.
     * @see Section 4.2 of RFC 7235
     */
    public static final String AUTHORIZATION = "Authorization";
    /**
     * The HTTP {@code Cache-Control} header field name.
     * @see Section 5.2 of RFC 7234
     */
    public static final String CACHE_CONTROL = "Cache-Control";
    /**
     * The HTTP {@code Connection} header field name.
     * @see Section 6.1 of RFC 7230
     */
    public static final String CONNECTION = "Connection";
    /**
     * The HTTP {@code Content-Encoding} header field name.
     * @see Section 3.1.2.2 of RFC 7231
     */
    public static final String CONTENT_ENCODING = "Content-Encoding";
    /**
     * The HTTP {@code Content-Disposition} header field name.
     * @see RFC 6266
     */
    public static final String CONTENT_DISPOSITION = "Content-Disposition";
    /**
     * The HTTP {@code Content-Language} header field name.
     * @see Section 3.1.3.2 of RFC 7231
     */
    public static final String CONTENT_LANGUAGE = "Content-Language";
    /**
     * The HTTP {@code Content-Length} header field name.
     * @see Section 3.3.2 of RFC 7230
     */
    public static final String CONTENT_LENGTH = "Content-Length";
    /**
     * The HTTP {@code Content-Location} header field name.
     * @see Section 3.1.4.2 of RFC 7231
     */
    public static final String CONTENT_LOCATION = "Content-Location";
    /**
     * The HTTP {@code Content-Range} header field name.
     * @see Section 4.2 of RFC 7233
     */
    public static final String CONTENT_RANGE = "Content-Range";
    /**
     * The HTTP {@code Content-Type} header field name.
     * @see Section 3.1.1.5 of RFC 7231
     */
    public static final String CONTENT_TYPE = "Content-Type";
    /**
     * The HTTP {@code Cookie} header field name.
     * @see Section 4.3.4 of RFC 2109
     */
    public static final String COOKIE = "Cookie";
    /**
     * The HTTP {@code Date} header field name.
     * @see Section 7.1.1.2 of RFC 7231
     */
    public static final String DATE = "Date";
    /**
     * The HTTP {@code ETag} header field name.
     * @see Section 2.3 of RFC 7232
     */
    public static final String ETAG = "ETag";
    /**
     * The HTTP {@code Expect} header field name.
     * @see Section 5.1.1 of RFC 7231
     */
    public static final String EXPECT = "Expect";
    /**
     * The HTTP {@code Expires} header field name.
     * @see Section 5.3 of RFC 7234
     */
    public static final String EXPIRES = "Expires";
    /**
     * The HTTP {@code From} header field name.
     * @see Section 5.5.1 of RFC 7231
     */
    public static final String FROM = "From";
    /**
     * The HTTP {@code Host} header field name.
     * @see Section 5.4 of RFC 7230
     */
    public static final String HOST = "Host";
    /**
     * The HTTP {@code If-Match} header field name.
     * @see Section 3.1 of RFC 7232
     */
    public static final String IF_MATCH = "If-Match";
    /**
     * The HTTP {@code If-Modified-Since} header field name.
     * @see Section 3.3 of RFC 7232
     */
    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    /**
     * The HTTP {@code If-None-Match} header field name.
     * @see Section 3.2 of RFC 7232
     */
    public static final String IF_NONE_MATCH = "If-None-Match";
    /**
     * The HTTP {@code If-Range} header field name.
     * @see Section 3.2 of RFC 7233
     */
    public static final String IF_RANGE = "If-Range";
    /**
     * The HTTP {@code If-Unmodified-Since} header field name.
     * @see Section 3.4 of RFC 7232
     */
    public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
    /**
     * The HTTP {@code Last-Modified} header field name.
     * @see Section 2.2 of RFC 7232
     */
    public static final String LAST_MODIFIED = "Last-Modified";
    /**
     * The HTTP {@code Link} header field name.
     * @see RFC 5988
     */
    public static final String LINK = "Link";
    /**
     * The HTTP {@code Location} header field name.
     * @see Section 7.1.2 of RFC 7231
     */
    public static final String LOCATION = "Location";
    /**
     * The HTTP {@code Max-Forwards} header field name.
     * @see Section 5.1.2 of RFC 7231
     */
    public static final String MAX_FORWARDS = "Max-Forwards";
    /**
     * The HTTP {@code Origin} header field name.
     * @see RFC 6454
     */
    public static final String ORIGIN = "Origin";
    /**
     * The HTTP {@code Pragma} header field name.
     * @see Section 5.4 of RFC 7234
     */
    public static final String PRAGMA = "Pragma";
    /**
     * The HTTP {@code Proxy-Authenticate} header field name.
     * @see Section 4.3 of RFC 7235
     */
    public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
    /**
     * The HTTP {@code Proxy-Authorization} header field name.
     * @see Section 4.4 of RFC 7235
     */
    public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
    /**
     * The HTTP {@code Range} header field name.
     * @see Section 3.1 of RFC 7233
     */
    public static final String RANGE = "Range";
    /**
     * The HTTP {@code Referer} header field name.
     * @see Section 5.5.2 of RFC 7231
     */
    public static final String REFERER = "Referer";
    /**
     * The HTTP {@code Retry-After} header field name.
     * @see Section 7.1.3 of RFC 7231
     */
    public static final String RETRY_AFTER = "Retry-After";
    /**
     * The HTTP {@code Server} header field name.
     * @see Section 7.4.2 of RFC 7231
     */
    public static final String SERVER = "Server";
    /**
     * The HTTP {@code Set-Cookie} header field name.
     * @see Section 4.2.2 of RFC 2109
     */
    public static final String SET_COOKIE = "Set-Cookie";
    /**
     * The HTTP {@code Set-Cookie2} header field name.
     * @see RFC 2965
     */
    public static final String SET_COOKIE2 = "Set-Cookie2";
    /**
     * The HTTP {@code TE} header field name.
     * @see Section 4.3 of RFC 7230
     */
    public static final String TE = "TE";
    /**
     * The HTTP {@code Trailer} header field name.
     * @see Section 4.4 of RFC 7230
     */
    public static final String TRAILER = "Trailer";
    /**
     * The HTTP {@code Transfer-Encoding} header field name.
     * @see Section 3.3.1 of RFC 7230
     */
    public static final String TRANSFER_ENCODING = "Transfer-Encoding";
    /**
     * The HTTP {@code Upgrade} header field name.
     * @see Section 6.7 of RFC 7230
     */
    public static final String UPGRADE = "Upgrade";
    /**
     * The HTTP {@code User-Agent} header field name.
     * @see Section 5.5.3 of RFC 7231
     */
    public static final String USER_AGENT = "User-Agent";
    /**
     * The HTTP {@code Vary} header field name.
     * @see Section 7.1.4 of RFC 7231
     */
    public static final String VARY = "Vary";
    /**
     * The HTTP {@code Via} header field name.
     * @see Section 5.7.1 of RFC 7230
     */
    public static final String VIA = "Via";
    /**
     * The HTTP {@code Warning} header field name.
     * @see Section 5.5 of RFC 7234
     */
    public static final String WARNING = "Warning";
    /**
     * The HTTP {@code WWW-Authenticate} header field name.
     * @see Section 4.1 of RFC 7235
     */
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";

    final Map> headers;

    public HttpHeaders() {
        this(new HashMap<>());
    }

    public HttpHeaders(Map> headers) {
        Objects.requireNonNull(headers, "Map must not be null");
        this.headers = headers;
    }

    public List getOrEmpty(Object headerName) {
        List values = get(headerName);
        return (values != null ? values : Collections.emptyList());
    }

    public String getFirst(String headerName) {
        return getFirstOrDefault(headerName, null);
    }

    public String getFirstOrDefault(String headerName, String defaultValue) {
        List values = getHeaderValues(headerName);
        return (values != null && !values.isEmpty() ? values.get(0) : defaultValue);
    }

    public Set> firstValueEntrySet() {
        Map firstValueMap = new HashMap<>();
        this.entrySet().forEach(e -> {
            List valueList = e.getValue();
            firstValueMap.put(e.getKey(), null == valueList || valueList.isEmpty() ? null : valueList.get(0));
        });

        return firstValueMap.entrySet();
    }

    public void add(String headerName, String headerValue) {
        List values = this.headers.computeIfAbsent(headerName, k -> new LinkedList<>());
        values.add(headerValue);
    }

    public void addAll(String key, List values) {
        this.headers.put(key, values);
    }

    public void addAll(Map> values) {
        this.headers.putAll(values);
    }

    public List put(String key, List value) {
        return this.headers.put(key, value);
    }

    public void putAll(Map> map) {
        this.headers.putAll(map);
    }

    public void set(String headerName, String headerValue) {
        List values = new LinkedList<>();
        values.add(headerValue);
        this.headers.put(headerName, values);
    }

    public void setAll(Map> values) {
        values.forEach((k, v) -> {
            List valuesList = new LinkedList<>();
            valuesList.addAll(v);
            this.headers.put(k, valuesList);
        });
    }

    public Map toSingleValueMap() {
        Map singleValueMap = new LinkedHashMap<>(this.headers.size());
        this.headers.forEach((key, values) -> {
            if (values != null && !values.isEmpty()) {
                singleValueMap.put(key, values.get(0));
            }
        });
        return singleValueMap;
    }

    public List getValuesAsList(String headerName) {
        List values = get(headerName);
        if (values != null) {
            List result = new ArrayList<>();
            for (String value : values) {
                if (value != null) {
                    Collections.addAll(result, value.split(","));
                }
            }
            return result;
        }
        return Collections.emptyList();
    }

    public List collectHeaders() {
        List headerCollection = headers.entrySet().stream().map(e -> {
            return e.getKey() + ": " + StringUtil.collectionToString(e.getValue());
        }).collect(Collectors.toList());
        return headerCollection;
    }

    @Override
    public int size() {
        return this.headers.size();
    }

    @Override
    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    @Override
    public List get(Object key) {
        return getHeaderValues(key);
    }

    @Override
    public List remove(Object key) {
        return this.headers.remove(key);
    }

    @Override
    public void clear() {
        this.headers.clear();
    }

    @Override
    public Set keySet() {
        return this.headers.keySet();
    }

    @Override
    public Collection> values() {
        return this.headers.values();
    }

    @Override
    public Set>> entrySet() {
        return this.headers.entrySet();
    }

    public void setAccept(List acceptableMediaTypes) {
        setAccept(HttpMediaType.toString(acceptableMediaTypes));
    }

    public void setAccept(HttpMediaType acceptableMediaType) {
        setAccept(HttpMediaType.toString(acceptableMediaType));
    }

    public void setAccept(String acceptableMediaType) {
        set(ACCEPT, acceptableMediaType);
    }

    public List getAccept() {
        try {
            return HttpMediaType.parseMediaTypes(get(ACCEPT));
        } catch (IOException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }

    public void setAcceptLanguage(List languages) {
        Objects.requireNonNull(languages, "LanguageRange List must not be null");
        DecimalFormat decimal = new DecimalFormat("0.0", DECIMAL_FORMAT_SYMBOLS);
        List values = languages.stream()
                .map(range ->
                        range.getWeight() == Locale.LanguageRange.MAX_WEIGHT ?
                                range.getRange() :
                                range.getRange() + ";q=" + decimal.format(range.getWeight()))
                .collect(Collectors.toList());
        set(ACCEPT_LANGUAGE, toCommaDelimitedString(values));
    }

    public List getAcceptLanguage() {
        String value = getFirst(ACCEPT_LANGUAGE);
        return (null != value && !value.isEmpty() ? Locale.LanguageRange.parse(value) : Collections.emptyList());
    }

    public void setAcceptLanguageAsLocales(List locales) {
        setAcceptLanguage(locales.stream()
                .map(locale -> new Locale.LanguageRange(locale.toLanguageTag()))
                .collect(Collectors.toList()));
    }

    public List getAcceptLanguageAsLocales() {
        List ranges = getAcceptLanguage();
        if (ranges.isEmpty()) {
            return Collections.emptyList();
        }
        return ranges.stream()
                .map(range -> Locale.forLanguageTag(range.getRange()))
                .filter(locale -> null != locale.getDisplayName() && !locale.getDisplayName().isEmpty())
                .collect(Collectors.toList());
    }

    public void setAccessControlAllowCredentials(boolean allowCredentials) {
        set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials));
    }

    public boolean getAccessControlAllowCredentials() {
        return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
    }

    public void setAccessControlAllowHeaders(List allowedHeaders) {
        set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders));
    }

    public List getAccessControlAllowHeaders() {
        return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
    }

    public void setAccessControlAllowMethods(List allowedMethods) {
        set(ACCESS_CONTROL_ALLOW_METHODS, StringUtil.collectionToString(allowedMethods));
    }

    public List getAccessControlAllowMethods() {
        List result = new ArrayList<>();
        String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
        if (value != null) {
            String[] tokens = value.split(",");
            for (String token : tokens) {
                HttpRequestMethod resolved = HttpRequestMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }
        }
        return result;
    }

    public void setAccessControlAllowOrigin(String allowedOrigin) {
        setOrRemove(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin);
    }

    public String getAccessControlAllowOrigin() {
        return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN);
    }

    public void setAccessControlExposeHeaders(List exposedHeaders) {
        set(ACCESS_CONTROL_EXPOSE_HEADERS, toCommaDelimitedString(exposedHeaders));
    }

    public List getAccessControlExposeHeaders() {
        return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
    }

    public void setAccessControlMaxAge(Duration maxAge) {
        set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge.getSeconds()));
    }

    public void setAccessControlMaxAge(long maxAge) {
        set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge));
    }

    public long getAccessControlMaxAge() {
        String value = getFirst(ACCESS_CONTROL_MAX_AGE);
        return (value != null ? Long.parseLong(value) : -1);
    }

    public void setAccessControlRequestHeaders(List requestHeaders) {
        set(ACCESS_CONTROL_REQUEST_HEADERS, toCommaDelimitedString(requestHeaders));
    }

    public List getAccessControlRequestHeaders() {
        return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);
    }

    public void setAccessControlRequestMethod(HttpRequestMethod requestMethod) {
        setOrRemove(ACCESS_CONTROL_REQUEST_METHOD, (requestMethod != null ? requestMethod.name() : null));
    }

    public HttpRequestMethod getAccessControlRequestMethod() {
        return HttpRequestMethod.resolve(getFirst(ACCESS_CONTROL_REQUEST_METHOD));
    }

    public void setAcceptCharset(List acceptableCharsets) {
        StringJoiner joiner = new StringJoiner(", ");
        for (Charset charset : acceptableCharsets) {
            joiner.add(charset.name().toLowerCase(Locale.ENGLISH));
        }
        set(ACCEPT_CHARSET, joiner.toString());
    }

    public List getAcceptCharset() {
        String value = getFirst(ACCEPT_CHARSET);
        if (value != null) {
            String[] tokens = value.split(",");
            List result = new ArrayList<>(tokens.length);
            for (String token : tokens) {
                int paramIdx = token.indexOf(';');
                String charsetName;
                if (paramIdx == -1) {
                    charsetName = token;
                } else {
                    charsetName = token.substring(0, paramIdx);
                }
                if (!charsetName.equals("*")) {
                    result.add(Charset.forName(charsetName));
                }
            }
            return result;
        } else {
            return Collections.emptyList();
        }
    }

    public void setAllow(Set allowedMethods) {
        set(ALLOW, StringUtil.collectionToString(allowedMethods));
    }

    public Set getAllow() {
        String value = getFirst(ALLOW);
        if (null != value && value.isEmpty()) {
            String[] tokens = value.split(",");
            List result = new ArrayList<>(tokens.length);
            for (String token : tokens) {
                HttpRequestMethod resolved = HttpRequestMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }
            return EnumSet.copyOf(result);
        } else {
            return EnumSet.noneOf(HttpRequestMethod.class);
        }
    }

    public void setBasicAuth(String username, String password) {
        setBasicAuth(username, password, null);
    }

    public void setBasicAuth(String username, String password, Charset charset) {
        setBasicAuth(encodeBasicAuth(username, password, charset));
    }

    public void setBasicAuth(String encodedCredentials) {
        if (null == encodedCredentials|| encodedCredentials.isEmpty()) {
            throw new IllegalArgumentException("'encodedCredentials' must not be null or blank");
        }
        set(AUTHORIZATION, "Basic " + encodedCredentials);
    }

    public void setBearerAuth(String token) {
        set(AUTHORIZATION, "Bearer " + token);
    }

    public void setCacheControl(HttpHeaderCacheControl httpHeaderCacheControl) {
        setOrRemove(CACHE_CONTROL, httpHeaderCacheControl.getHeaderValue());
    }

    public void setCacheControl(String cacheControl) {
        setOrRemove(CACHE_CONTROL, cacheControl);
    }

    public String getCacheControl() {
        return getFieldValues(CACHE_CONTROL);
    }

    public void setConnection(String connection) {
        set(CONNECTION, connection);
    }

    public void setConnection(List connection) {
        set(CONNECTION, toCommaDelimitedString(connection));
    }

    public List getConnection() {
        return getValuesAsList(CONNECTION);
    }

    public String getFirstConnection() {
        return getFirst(CONNECTION);
    }

    public void setContentDispositionFormData(String name, String filename) {
        Objects.requireNonNull(name, "Name must not be null");
        HttpHeaderContentDisposition.Builder disposition = HttpHeaderContentDisposition.builder("form-data").name(name);
        if (StringUtil.isNotBlank(filename)) {
            disposition.filename(filename);
        }
        setContentDisposition(disposition.build());
    }

    public void setContentDisposition(HttpHeaderContentDisposition httpHeaderContentDisposition) {
        set(CONTENT_DISPOSITION, httpHeaderContentDisposition.toString());
    }

    public HttpHeaderContentDisposition getContentDisposition() {
        String contentDisposition = getFirst(CONTENT_DISPOSITION);
        if (StringUtil.isNotBlank(contentDisposition)) {
            return HttpHeaderContentDisposition.parse(contentDisposition);
        }
        return HttpHeaderContentDisposition.empty();
    }

    public void setContentLanguage(Locale locale) {
        setOrRemove(CONTENT_LANGUAGE, (locale != null ? locale.toLanguageTag() : null));
    }

    public Locale getContentLanguage() {
        return getValuesAsList(CONTENT_LANGUAGE)
                .stream()
                .findFirst()
                .map(Locale::forLanguageTag)
                .orElse(null);
    }

    public void setContentLength(long contentLength) {
        set(CONTENT_LENGTH, Long.toString(contentLength));
    }

    public long getContentLength() {
        String value = getFirst(CONTENT_LENGTH);
        return (value != null ? Long.parseLong(value) : -1);
    }

    public void setContentType(HttpMediaType mediaType) {
        if (mediaType != null) {
            if (mediaType.isWildcardType()) {
                throw new IllegalArgumentException("Content-Type cannot contain wildcard type '*'");
            }
            if (mediaType.isWildcardSubtype()) {
                throw new IllegalArgumentException("Content-Type cannot contain wildcard subtype '*'");
            }
            set(CONTENT_TYPE, mediaType.toString());
        } else {
            remove(CONTENT_TYPE);
        }
    }

    public HttpMediaType getContentType() {
        String value = getFirst(CONTENT_TYPE);
        try {
            return (null != value && !value.isEmpty() ? HttpMediaType.parse(value) : null);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void setDate(ZonedDateTime date) {
        setZonedDateTime(DATE, date);
    }

    public void setDate(Instant date) {
        setInstant(DATE, date);
    }

    public void setDate(long date) {
        setDate(DATE, date);
    }

    public long getDate() {
        return getFirstDate(DATE);
    }

    public void setETag(String etag) {
        if (etag != null) {
            if (!(etag.startsWith("\"") || etag.startsWith("W/"))) {
                throw new IllegalArgumentException("Invalid ETag: does not start with W/ or \"");
            }
            if (!etag.endsWith("\"")) {
                throw new IllegalArgumentException("Invalid ETag: does not end with \"");
            }
            set(ETAG, etag);
        } else {
            remove(ETAG);
        }
    }

    public String getETag() {
        return getFirst(ETAG);
    }

    public void setExpires(ZonedDateTime expires) {
        setZonedDateTime(EXPIRES, expires);
    }

    public void setExpires(Instant expires) {
        setInstant(EXPIRES, expires);
    }

    public void setExpires(long expires) {
        setDate(EXPIRES, expires);
    }

    public long getExpires() {
        return getFirstDate(EXPIRES, false);
    }

    public void setHost(InetSocketAddress host) {
        if (host != null) {
            String value = host.getHostString();
            int port = host.getPort();
            if (port != 0) {
                value = value + ":" + port;
            }
            set(HOST, value);
        } else {
            remove(HOST, null);
        }
    }

    public InetSocketAddress getHost() {
        String value = getFirst(HOST);
        if (value == null) {
            return null;
        }

        String host = null;
        int port = 0;
        int separator = (value.startsWith("[") ? value.indexOf(':', value.indexOf(']')) : value.lastIndexOf(':'));
        if (separator != -1) {
            host = value.substring(0, separator);
            String portString = value.substring(separator + 1);
            try {
                port = Integer.parseInt(portString);
            } catch (NumberFormatException ex) {
                // ignore
            }
        }

        if (host == null) {
            host = value;
        }
        return InetSocketAddress.createUnresolved(host, port);
    }

    public void setIfMatch(String ifMatch) {
        set(IF_MATCH, ifMatch);
    }

    public void setIfMatch(List ifMatchList) {
        set(IF_MATCH, toCommaDelimitedString(ifMatchList));
    }

    public List getIfMatch() {
        return getETagValuesAsList(IF_MATCH);
    }

    public void setIfModifiedSince(ZonedDateTime ifModifiedSince) {
        setZonedDateTime(IF_MODIFIED_SINCE, ifModifiedSince.withZoneSameInstant(GMT));
    }

    public void setIfModifiedSince(Instant ifModifiedSince) {
        setInstant(IF_MODIFIED_SINCE, ifModifiedSince);
    }

    public void setIfModifiedSince(long ifModifiedSince) {
        setDate(IF_MODIFIED_SINCE, ifModifiedSince);
    }

    public long getIfModifiedSince() {
        return getFirstDate(IF_MODIFIED_SINCE, false);
    }

    public void setIfNoneMatch(String ifNoneMatch) {
        set(IF_NONE_MATCH, ifNoneMatch);
    }

    public void setIfNoneMatch(List ifNoneMatchList) {
        set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
    }

    public List getIfNoneMatch() {
        return getETagValuesAsList(IF_NONE_MATCH);
    }

    public void setIfUnmodifiedSince(ZonedDateTime ifUnmodifiedSince) {
        setZonedDateTime(IF_UNMODIFIED_SINCE, ifUnmodifiedSince.withZoneSameInstant(GMT));
    }

    public void setIfUnmodifiedSince(Instant ifUnmodifiedSince) {
        setInstant(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
    }

    public void setIfUnmodifiedSince(long ifUnmodifiedSince) {
        setDate(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
    }

    public long getIfUnmodifiedSince() {
        return getFirstDate(IF_UNMODIFIED_SINCE, false);
    }

    public void setLastModified(ZonedDateTime lastModified) {
        setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT));
    }

    public void setLastModified(Instant lastModified) {
        setInstant(LAST_MODIFIED, lastModified);
    }

    public void setLastModified(long lastModified) {
        setDate(LAST_MODIFIED, lastModified);
    }

    public long getLastModified() {
        return getFirstDate(LAST_MODIFIED, false);
    }

    public void setLocation(URI location) {
        setOrRemove(LOCATION, (location != null ? location.toASCIIString() : null));
    }

    public URI getLocation() {
        String value = getFirst(LOCATION);
        return (value != null ? URI.create(value) : null);
    }

    public void setOrigin(String origin) {
        setOrRemove(ORIGIN, origin);
    }

    public String getOrigin() {
        return getFirst(ORIGIN);
    }

    public void setPragma(String pragma) {
        setOrRemove(PRAGMA, pragma);
    }

    public String getPragma() {
        return getFirst(PRAGMA);
    }

    public void setRange(List ranges) {
        String value = HttpHeaderRange.toString(ranges);
        set(RANGE, value);
    }

    public List getRange() {
        String value = getFirst(RANGE);
        return HttpHeaderRange.parseRanges(value);
    }

    public void setUpgrade(String upgrade) {
        setOrRemove(UPGRADE, upgrade);
    }

    public String getUpgrade() {
        return getFirst(UPGRADE);
    }

    public void setVary(List requestHeaders) {
        set(VARY, toCommaDelimitedString(requestHeaders));
    }

    public List getVary() {
        return getValuesAsList(VARY);
    }

    public void setZonedDateTime(String headerName, ZonedDateTime date) {
        set(headerName, DATE_FORMATTER.format(date));
    }

    public void setInstant(String headerName, Instant date) {
        setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT));
    }

    public void setDate(String headerName, long date) {
        setInstant(headerName, Instant.ofEpochMilli(date));
    }

    public long getFirstDate(String headerName) {
        return getFirstDate(headerName, true);
    }

    private long getFirstDate(String headerName, boolean rejectInvalid) {
        ZonedDateTime zonedDateTime = getFirstZonedDateTime(headerName, rejectInvalid);
        return (zonedDateTime != null ? zonedDateTime.toInstant().toEpochMilli() : -1);
    }

    public ZonedDateTime getFirstZonedDateTime(String headerName) {
        return getFirstZonedDateTime(headerName, true);
    }


    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof HttpHeaders)) {
            return false;
        }
        return unwrap(this).equals(unwrap((HttpHeaders) other));
    }

    @Override
    public int hashCode() {
        return this.headers.hashCode();
    }

    @Override
    public String toString() {
        return formatHeaders(this.headers);
    }

    protected String getFieldValues(String headerName) {
        List headerValues = get(headerName);
        return (headerValues != null ? toCommaDelimitedString(headerValues) : null);
    }

    protected String toCommaDelimitedString(List headerValues) {
        StringJoiner joiner = new StringJoiner(", ");
        for (String val : headerValues) {
            if (val != null) {
                joiner.add(val);
            }
        }
        return joiner.toString();
    }

    protected List getETagValuesAsList(String headerName) {
        List values = get(headerName);
        if (values != null) {
            List result = new ArrayList<>();
            for (String value : values) {
                if (value != null) {
                    Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value);
                    while (matcher.find()) {
                        if ("*".equals(matcher.group())) {
                            result.add(matcher.group());
                        } else {
                            result.add(matcher.group(1));
                        }
                    }
                    if (result.isEmpty()) {
                        throw new IllegalArgumentException(
                                "Could not parse header '" + headerName + "' with value '" + value + "'");
                    }
                }
            }
            return result;
        }
        return Collections.emptyList();
    }

    private void setOrRemove(String headerName, String headerValue) {
        if (headerValue != null) {
            set(headerName, headerValue);
        } else {
            remove(headerName);
        }
    }

    private List getHeaderValues(Object headerName) {
        return this.headers.get(headerName);
    }

    private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid) {
        String headerValue = getFirst(headerName);
        if (headerValue == null) {
            // No header value sent at all
            return null;
        }
        if (headerValue.length() >= 3) {
            // Short "0" or "-1" like values are never valid HTTP date headers...
            // Let's only bother with DateTimeFormatter parsing for long enough values.

            // See https://stackoverflow.com/questions/12626699/if-modified-since-http-header-passed-by-ie9-includes-length
            int parametersIndex = headerValue.indexOf(';');
            if (parametersIndex != -1) {
                headerValue = headerValue.substring(0, parametersIndex);
            }

            for (DateTimeFormatter dateFormatter : DATE_PARSERS) {
                try {
                    return ZonedDateTime.parse(headerValue, dateFormatter);
                }
                catch (DateTimeParseException ex) {
                    // ignore
                }
            }

        }
        if (rejectInvalid) {
            throw new IllegalArgumentException("Cannot parse date value \"" + headerValue +
                    "\" for \"" + headerName + "\" header");
        }
        return null;
    }

    public static String formatHeaders(Map> headers) {
        return headers.entrySet().stream()
                .map(entry -> {
                    List values = entry.getValue();
                    return entry.getKey() + ":" + (values.size() == 1 ?
                            "\"" + values.get(0) + "\"" :
                            values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
                })
                .collect(Collectors.joining(", ", "[", "]"));
    }

    private static Map> unwrap(HttpHeaders headers) {
        while (headers.headers instanceof HttpHeaders) {
            headers = (HttpHeaders) headers.headers;
        }
        return headers.headers;
    }

    public static String encodeBasicAuth(String username, String password, Charset charset) {
        Objects.requireNonNull(username, "Username must not be null");
        Objects.requireNonNull(password, "Password must not be null");
        if (!username.contains(":")) {
            throw new IllegalArgumentException("Username must not contain a colon");
        }

        if (charset == null) {
            charset = StandardCharsets.ISO_8859_1;
        }

        CharsetEncoder encoder = charset.newEncoder();
        if (!encoder.canEncode(username) || !encoder.canEncode(password)) {
            throw new IllegalArgumentException(
                    "Username or password contains characters that cannot be encoded to " + charset.displayName());
        }

        String credentialsString = username + ":" + password;
        byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
        return new String(encodedBytes, charset);
    }

    // Package-private: used in ResponseCookie
    static String formatDate(long date) {
        Instant instant = Instant.ofEpochMilli(date);
        ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT);
        return DATE_FORMATTER.format(time);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy