/*
 * Copyright (c) 2011-2017 Contributors to the Eclipse Foundation
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 */

package io.vertx.core.http.impl.headers;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import io.netty.util.HashingStrategy;
import io.vertx.core.MultiMap;
import io.vertx.core.http.impl.HttpUtils;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Consumer;

import static io.netty.handler.codec.http.HttpConstants.*;
import static io.netty.util.AsciiString.*;

Author:Julien Viet
/** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */
public final class VertxHttpHeaders extends HttpHeaders implements MultiMap { @Override public MultiMap setAll(MultiMap headers) { return set0(headers); } @Override public MultiMap setAll(Map<String, String> headers) { return set0(headers.entrySet()); } @Override public int size() { return names().size(); } private final VertxHttpHeaders.MapEntry[] entries = new VertxHttpHeaders.MapEntry[16]; private final VertxHttpHeaders.MapEntry head = new VertxHttpHeaders.MapEntry(); public VertxHttpHeaders() { head.before = head.after = head; } @Override public VertxHttpHeaders add(CharSequence name, CharSequence value) { int h = AsciiString.hashCode(name); int i = h & 0x0000000F; add0(h, i, name, value); return this; } @Override public VertxHttpHeaders add(CharSequence name, Object value) { return add(name, (CharSequence)value); } @Override public HttpHeaders add(String name, Object value) { return add((CharSequence) name, (CharSequence) value); } @Override public VertxHttpHeaders add(String name, String strVal) { return add((CharSequence) name, strVal); } @Override public VertxHttpHeaders add(CharSequence name, Iterable values) { int h = AsciiString.hashCode(name); int i = h & 0x0000000F; for (Object vstr: values) { add0(h, i, name, (String) vstr); } return this; } @Override public VertxHttpHeaders add(String name, Iterable values) { return add((CharSequence) name, values); } @Override public MultiMap addAll(MultiMap headers) { return addAll(headers.entries()); } @Override public MultiMap addAll(Map<String, String> map) { return addAll(map.entrySet()); } private MultiMap addAll(Iterable<Map.Entry<String, String>> headers) { for (Map.Entry<String, String> entry: headers) { add(entry.getKey(), entry.getValue()); } return this; } @Override public VertxHttpHeaders remove(CharSequence name) { Objects.requireNonNull(name, "name"); int h = AsciiString.hashCode(name); int i = h & 0x0000000F; remove0(h, i, name); return this; } @Override public VertxHttpHeaders remove(final String name) { return remove((CharSequence) name); } @Override public VertxHttpHeaders set(CharSequence name, CharSequence value) { return set0(name, value); } @Override public VertxHttpHeaders set(String name, String value) { return set((CharSequence)name, value); } @Override public VertxHttpHeaders set(String name, Object value) { return set((CharSequence)name, (CharSequence) value); } @Override public VertxHttpHeaders set(CharSequence name, Object value) { return set(name, (CharSequence)value); } @Override public VertxHttpHeaders set(CharSequence name, Iterable values) { Objects.requireNonNull(values, "values"); int h = AsciiString.hashCode(name); int i = h & 0x0000000F; remove0(h, i, name); for (Object v: values) { if (v == null) { break; } add0(h, i, name, (CharSequence) v); } return this; } @Override public VertxHttpHeaders set(String name, Iterable values) { return set((CharSequence) name, values); } @Override public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { int h = AsciiString.hashCode(name); int i = h & 0x0000000F; VertxHttpHeaders.MapEntry e = entries[i]; HashingStrategy<CharSequence> strategy = ignoreCase ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER; while (e != null) { CharSequence key = e.key; if (e.hash == h && (name == key || AsciiString.contentEqualsIgnoreCase(name, key))) { if (strategy.equals(value, e.getValue())) { return true; } } e = e.next; } return false; } @Override public boolean contains(String name, String value, boolean ignoreCase) { return contains((CharSequence) name, value, ignoreCase); } @Override public boolean contains(CharSequence name) { return get0(name) != null; } @Override public boolean contains(String name) { return contains((CharSequence) name); } @Override public String get(CharSequence name) { Objects.requireNonNull(name, "name"); CharSequence ret = get0(name); return ret != null ? ret.toString() : null; } @Override public String get(String name) { return get((CharSequence) name); } @Override public List<String> getAll(CharSequence name) { Objects.requireNonNull(name, "name"); LinkedList<String> values = new LinkedList<>(); int h = AsciiString.hashCode(name); int i = h & 0x0000000F; VertxHttpHeaders.MapEntry e = entries[i]; while (e != null) { CharSequence key = e.key; if (e.hash == h && (name == key || AsciiString.contentEqualsIgnoreCase(name, key))) { values.addFirst(e.getValue().toString()); } e = e.next; } return values; } @Override public List<String> getAll(String name) { return getAll((CharSequence) name); } @Override public void forEach(Consumer<? super Map.Entry<String, String>> action) { VertxHttpHeaders.MapEntry e = head.after; while (e != head) { action.accept(new AbstractMap.SimpleEntry<>(e.key.toString(), e.value.toString())); e = e.after; } } @Override public List<Map.Entry<String, String>> entries() { List<Map.Entry<String, String>> all = new ArrayList<>(size()); VertxHttpHeaders.MapEntry e = head.after; while (e != head) { final MapEntry f = e; all.add(new Map.Entry<String, String>() { @Override public String getKey() { return f.key.toString(); } @Override public String getValue() { return f.value.toString(); } @Override public String setValue(String value) { return f.setValue(value).toString(); } @Override public String toString() { return getKey() + ": " + getValue(); } }); e = e.after; } return all; } @Override public Iterator<Map.Entry<String, String>> iterator() { return entries().iterator(); } @Override public boolean isEmpty() { return head == head.after; } @Override public Set<String> names() { Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); VertxHttpHeaders.MapEntry e = head.after; while (e != head) { names.add(e.getKey().toString()); e = e.after; } return names; } @Override public VertxHttpHeaders clear() { for (int i = 0; i < entries.length; i ++) { entries[i] = null; } head.before = head.after = head; return this; } public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry: this) { sb.append(entry).append('\n'); } return sb.toString(); } @Override public Integer getInt(CharSequence name) { throw new UnsupportedOperationException(); } @Override public int getInt(CharSequence name, int defaultValue) { throw new UnsupportedOperationException(); } @Override public Short getShort(CharSequence name) { throw new UnsupportedOperationException(); } @Override public short getShort(CharSequence name, short defaultValue) { throw new UnsupportedOperationException(); } @Override public Long getTimeMillis(CharSequence name) { throw new UnsupportedOperationException(); } @Override public long getTimeMillis(CharSequence name, long defaultValue) { throw new UnsupportedOperationException(); } @Override public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() { return new Iterator<Map.Entry<CharSequence, CharSequence>>() { VertxHttpHeaders.MapEntry current = head.after; @Override public boolean hasNext() { return current != head; } @Override public Map.Entry<CharSequence, CharSequence> next() { Map.Entry<CharSequence, CharSequence> next = current; current = current.after; return next; } }; } @Override public HttpHeaders addInt(CharSequence name, int value) { throw new UnsupportedOperationException(); } @Override public HttpHeaders addShort(CharSequence name, short value) { throw new UnsupportedOperationException(); } @Override public HttpHeaders setInt(CharSequence name, int value) { return set(name, Integer.toString(value)); } @Override public HttpHeaders setShort(CharSequence name, short value) { throw new UnsupportedOperationException(); } public void encode(ByteBuf buf) { VertxHttpHeaders.MapEntry current = head.after; while (current != head) { encoderHeader(current.key, current.value, buf); current = current.after; } } private static final int COLON_AND_SPACE_SHORT = (COLON << 8) | SP; static final int CRLF_SHORT = (CR << 8) | LF; static void encoderHeader(CharSequence name, CharSequence value, ByteBuf buf) { final int nameLen = name.length(); final int valueLen = value.length(); final int entryLen = nameLen + valueLen + 4; buf.ensureWritable(entryLen); int offset = buf.writerIndex(); writeAscii(buf, offset, name); offset += nameLen; ByteBufUtil.setShortBE(buf, offset, COLON_AND_SPACE_SHORT); offset += 2; writeAscii(buf, offset, value); offset += valueLen; ByteBufUtil.setShortBE(buf, offset, CRLF_SHORT); offset += 2; buf.writerIndex(offset); } private static void writeAscii(ByteBuf buf, int offset, CharSequence value) { if (value instanceof AsciiString) { ByteBufUtil.copy((AsciiString) value, 0, buf, offset, value.length()); } else { buf.setCharSequence(offset, value, CharsetUtil.US_ASCII); } } private static final class MapEntry implements Map.Entry<CharSequence, CharSequence> { final int hash; final CharSequence key; CharSequence value; VertxHttpHeaders.MapEntry next; VertxHttpHeaders.MapEntry before, after; MapEntry() { this.hash = -1; this.key = null; this.value = null; } MapEntry(int hash, CharSequence key, CharSequence value) { this.hash = hash; this.key = key; this.value = value; } void remove() { before.after = after; after.before = before; } void addBefore(VertxHttpHeaders.MapEntry e) { after = e; before = e.before; before.after = this; after.before = this; } @Override public CharSequence getKey() { return key; } @Override public CharSequence getValue() { return value; } @Override public CharSequence setValue(CharSequence value) { Objects.requireNonNull(value, "value"); if (!io.vertx.core.http.HttpHeaders.DISABLE_HTTP_HEADERS_VALIDATION) { HttpUtils.validateHeaderValue(value); } CharSequence oldValue = this.value; this.value = value; return oldValue; } @Override public String toString() { return getKey() + ": " + getValue(); } } private void remove0(int h, int i, CharSequence name) { VertxHttpHeaders.MapEntry e = entries[i]; if (e == null) { return; } for (;;) { CharSequence key = e.key; if (e.hash == h && (name == key || AsciiString.contentEqualsIgnoreCase(name, key))) { e.remove(); VertxHttpHeaders.MapEntry next = e.next; if (next != null) { entries[i] = next; e = next; } else { entries[i] = null; return; } } else { break; } } for (;;) { VertxHttpHeaders.MapEntry next = e.next; if (next == null) { break; } CharSequence key = next.key; if (next.hash == h && (name == key || AsciiString.contentEqualsIgnoreCase(name, key))) { e.next = next.next; next.remove(); } else { e = next; } } } private void add0(int h, int i, final CharSequence name, final CharSequence value) { if (!io.vertx.core.http.HttpHeaders.DISABLE_HTTP_HEADERS_VALIDATION) { HttpUtils.validateHeader(name, value); } // Update the hash table. VertxHttpHeaders.MapEntry e = entries[i]; VertxHttpHeaders.MapEntry newEntry; entries[i] = newEntry = new VertxHttpHeaders.MapEntry(h, name, value); newEntry.next = e; // Update the linked list. newEntry.addBefore(head); } private VertxHttpHeaders set0(final CharSequence name, final CharSequence strVal) { int h = AsciiString.hashCode(name); int i = h & 0x0000000F; remove0(h, i, name); if (strVal != null) { // test me add0(h, i, name, strVal); } return this; } private CharSequence get0(CharSequence name) { int h = AsciiString.hashCode(name); int i = h & 0x0000000F; VertxHttpHeaders.MapEntry e = entries[i]; CharSequence value = null; while (e != null) { CharSequence key = e.key; if (e.hash == h && (name == key || AsciiString.contentEqualsIgnoreCase(name, key))) { value = e.getValue(); } e = e.next; } return value; } private MultiMap set0(Iterable<Map.Entry<String, String>> map) { clear(); for (Map.Entry<String, String> entry: map) { add(entry.getKey(), entry.getValue()); } return this; } }