src/java.base/share/classes/sun/net/www/MessageHeader.java
changeset 47216 71c04702a3d5
parent 31061 fead7d86d75f
child 57713 0211b062843d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/net/www/MessageHeader.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,522 @@
+/*
+ * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*-
+ *      news stream opener
+ */
+
+package sun.net.www;
+
+import java.io.*;
+import java.util.Collections;
+import java.util.*;
+
+/** An RFC 844 or MIME message header.  Includes methods
+    for parsing headers from incoming streams, fetching
+    values, setting values, and printing headers.
+    Key values of null are legal: they indicate lines in
+    the header that don't have a valid key, but do have
+    a value (this isn't legal according to the standard,
+    but lines like this are everywhere). */
+public
+class MessageHeader {
+    private String keys[];
+    private String values[];
+    private int nkeys;
+
+    public MessageHeader () {
+        grow();
+    }
+
+    public MessageHeader (InputStream is) throws java.io.IOException {
+        parseHeader(is);
+    }
+
+    /**
+     * Returns list of header names in a comma separated list
+     */
+    public synchronized String getHeaderNamesInList() {
+        StringJoiner joiner = new StringJoiner(",");
+        for (int i=0; i<nkeys; i++) {
+            joiner.add(keys[i]);
+        }
+        return joiner.toString();
+    }
+
+    /**
+     * Reset a message header (all key/values removed)
+     */
+    public synchronized void reset() {
+        keys = null;
+        values = null;
+        nkeys = 0;
+        grow();
+    }
+
+    /**
+     * Find the value that corresponds to this key.
+     * It finds only the first occurrence of the key.
+     * @param k the key to find.
+     * @return null if not found.
+     */
+    public synchronized String findValue(String k) {
+        if (k == null) {
+            for (int i = nkeys; --i >= 0;)
+                if (keys[i] == null)
+                    return values[i];
+        } else
+            for (int i = nkeys; --i >= 0;) {
+                if (k.equalsIgnoreCase(keys[i]))
+                    return values[i];
+            }
+        return null;
+    }
+
+    // return the location of the key
+    public synchronized int getKey(String k) {
+        for (int i = nkeys; --i >= 0;)
+            if ((keys[i] == k) ||
+                (k != null && k.equalsIgnoreCase(keys[i])))
+                return i;
+        return -1;
+    }
+
+    public synchronized String getKey(int n) {
+        if (n < 0 || n >= nkeys) return null;
+        return keys[n];
+    }
+
+    public synchronized String getValue(int n) {
+        if (n < 0 || n >= nkeys) return null;
+        return values[n];
+    }
+
+    /** Deprecated: Use multiValueIterator() instead.
+     *
+     *  Find the next value that corresponds to this key.
+     *  It finds the first value that follows v. To iterate
+     *  over all the values of a key use:
+     *  <pre>
+     *          for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
+     *              ...
+     *          }
+     *  </pre>
+     */
+    public synchronized String findNextValue(String k, String v) {
+        boolean foundV = false;
+        if (k == null) {
+            for (int i = nkeys; --i >= 0;)
+                if (keys[i] == null)
+                    if (foundV)
+                        return values[i];
+                    else if (values[i] == v)
+                        foundV = true;
+        } else
+            for (int i = nkeys; --i >= 0;)
+                if (k.equalsIgnoreCase(keys[i]))
+                    if (foundV)
+                        return values[i];
+                    else if (values[i] == v)
+                        foundV = true;
+        return null;
+    }
+
+    /**
+     * Removes bare Negotiate and Kerberos headers when an "NTLM ..."
+     * appears. All Performed on headers with key being k.
+     * @return true if there is a change
+     */
+    public boolean filterNTLMResponses(String k) {
+        boolean found = false;
+        for (int i=0; i<nkeys; i++) {
+            if (k.equalsIgnoreCase(keys[i])
+                    && values[i] != null && values[i].length() > 5
+                    && values[i].substring(0, 5).equalsIgnoreCase("NTLM ")) {
+                found = true;
+                break;
+            }
+        }
+        if (found) {
+            int j = 0;
+            for (int i=0; i<nkeys; i++) {
+                if (k.equalsIgnoreCase(keys[i]) && (
+                        "Negotiate".equalsIgnoreCase(values[i]) ||
+                        "Kerberos".equalsIgnoreCase(values[i]))) {
+                    continue;
+                }
+                if (i != j) {
+                    keys[j] = keys[i];
+                    values[j] = values[i];
+                }
+                j++;
+            }
+            if (j != nkeys) {
+                nkeys = j;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    class HeaderIterator implements Iterator<String> {
+        int index = 0;
+        int next = -1;
+        String key;
+        boolean haveNext = false;
+        Object lock;
+
+        public HeaderIterator (String k, Object lock) {
+            key = k;
+            this.lock = lock;
+        }
+        public boolean hasNext () {
+            synchronized (lock) {
+                if (haveNext) {
+                    return true;
+                }
+                while (index < nkeys) {
+                    if (key.equalsIgnoreCase (keys[index])) {
+                        haveNext = true;
+                        next = index++;
+                        return true;
+                    }
+                    index ++;
+                }
+                return false;
+            }
+        }
+        public String next() {
+            synchronized (lock) {
+                if (haveNext) {
+                    haveNext = false;
+                    return values [next];
+                }
+                if (hasNext()) {
+                    return next();
+                } else {
+                    throw new NoSuchElementException ("No more elements");
+                }
+            }
+        }
+        public void remove () {
+            throw new UnsupportedOperationException ("remove not allowed");
+        }
+    }
+
+    /**
+     * return an Iterator that returns all values of a particular
+     * key in sequence
+     */
+    public Iterator<String> multiValueIterator (String k) {
+        return new HeaderIterator (k, this);
+    }
+
+    public synchronized Map<String, List<String>> getHeaders() {
+        return getHeaders(null);
+    }
+
+    public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
+        return filterAndAddHeaders(excludeList, null);
+    }
+
+    public synchronized Map<String, List<String>> filterAndAddHeaders(
+            String[] excludeList, Map<String, List<String>>  include) {
+        boolean skipIt = false;
+        Map<String, List<String>> m = new HashMap<>();
+        for (int i = nkeys; --i >= 0;) {
+            if (excludeList != null) {
+                // check if the key is in the excludeList.
+                // if so, don't include it in the Map.
+                for (int j = 0; j < excludeList.length; j++) {
+                    if ((excludeList[j] != null) &&
+                        (excludeList[j].equalsIgnoreCase(keys[i]))) {
+                        skipIt = true;
+                        break;
+                    }
+                }
+            }
+            if (!skipIt) {
+                List<String> l = m.get(keys[i]);
+                if (l == null) {
+                    l = new ArrayList<>();
+                    m.put(keys[i], l);
+                }
+                l.add(values[i]);
+            } else {
+                // reset the flag
+                skipIt = false;
+            }
+        }
+
+        if (include != null) {
+                for (Map.Entry<String,List<String>> entry: include.entrySet()) {
+                List<String> l = m.get(entry.getKey());
+                if (l == null) {
+                    l = new ArrayList<>();
+                    m.put(entry.getKey(), l);
+                }
+                l.addAll(entry.getValue());
+            }
+        }
+
+        for (String key : m.keySet()) {
+            m.put(key, Collections.unmodifiableList(m.get(key)));
+        }
+
+        return Collections.unmodifiableMap(m);
+    }
+
+    /** Prints the key-value pairs represented by this
+        header.  Also prints the RFC required blank line
+        at the end. Omits pairs with a null key. */
+    public synchronized void print(PrintStream p) {
+        for (int i = 0; i < nkeys; i++)
+            if (keys[i] != null) {
+                p.print(keys[i] +
+                    (values[i] != null ? ": "+values[i]: "") + "\r\n");
+            }
+        p.print("\r\n");
+        p.flush();
+    }
+
+    /** Adds a key value pair to the end of the
+        header.  Duplicates are allowed */
+    public synchronized void add(String k, String v) {
+        grow();
+        keys[nkeys] = k;
+        values[nkeys] = v;
+        nkeys++;
+    }
+
+    /** Prepends a key value pair to the beginning of the
+        header.  Duplicates are allowed */
+    public synchronized void prepend(String k, String v) {
+        grow();
+        for (int i = nkeys; i > 0; i--) {
+            keys[i] = keys[i-1];
+            values[i] = values[i-1];
+        }
+        keys[0] = k;
+        values[0] = v;
+        nkeys++;
+    }
+
+    /** Overwrite the previous key/val pair at location 'i'
+     * with the new k/v.  If the index didn't exist before
+     * the key/val is simply tacked onto the end.
+     */
+
+    public synchronized void set(int i, String k, String v) {
+        grow();
+        if (i < 0) {
+            return;
+        } else if (i >= nkeys) {
+            add(k, v);
+        } else {
+            keys[i] = k;
+            values[i] = v;
+        }
+    }
+
+
+    /** grow the key/value arrays as needed */
+
+    private void grow() {
+        if (keys == null || nkeys >= keys.length) {
+            String[] nk = new String[nkeys + 4];
+            String[] nv = new String[nkeys + 4];
+            if (keys != null)
+                System.arraycopy(keys, 0, nk, 0, nkeys);
+            if (values != null)
+                System.arraycopy(values, 0, nv, 0, nkeys);
+            keys = nk;
+            values = nv;
+        }
+    }
+
+    /**
+     * Remove the key from the header. If there are multiple values under
+     * the same key, they are all removed.
+     * Nothing is done if the key doesn't exist.
+     * After a remove, the other pairs' order are not changed.
+     * @param k the key to remove
+     */
+    public synchronized void remove(String k) {
+        if(k == null) {
+            for (int i = 0; i < nkeys; i++) {
+                while (keys[i] == null && i < nkeys) {
+                    for(int j=i; j<nkeys-1; j++) {
+                        keys[j] = keys[j+1];
+                        values[j] = values[j+1];
+                    }
+                    nkeys--;
+                }
+            }
+        } else {
+            for (int i = 0; i < nkeys; i++) {
+                while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
+                    for(int j=i; j<nkeys-1; j++) {
+                        keys[j] = keys[j+1];
+                        values[j] = values[j+1];
+                    }
+                    nkeys--;
+                }
+            }
+        }
+    }
+
+    /** Sets the value of a key.  If the key already
+        exists in the header, it's value will be
+        changed.  Otherwise a new key/value pair will
+        be added to the end of the header. */
+    public synchronized void set(String k, String v) {
+        for (int i = nkeys; --i >= 0;)
+            if (k.equalsIgnoreCase(keys[i])) {
+                values[i] = v;
+                return;
+            }
+        add(k, v);
+    }
+
+    /** Set's the value of a key only if there is no
+     *  key with that value already.
+     */
+
+    public synchronized void setIfNotSet(String k, String v) {
+        if (findValue(k) == null) {
+            add(k, v);
+        }
+    }
+
+    /** Convert a message-id string to canonical form (strips off
+        leading and trailing {@literal <>s}) */
+    public static String canonicalID(String id) {
+        if (id == null)
+            return "";
+        int st = 0;
+        int len = id.length();
+        boolean substr = false;
+        int c;
+        while (st < len && ((c = id.charAt(st)) == '<' ||
+                            c <= ' ')) {
+            st++;
+            substr = true;
+        }
+        while (st < len && ((c = id.charAt(len - 1)) == '>' ||
+                            c <= ' ')) {
+            len--;
+            substr = true;
+        }
+        return substr ? id.substring(st, len) : id;
+    }
+
+    /** Parse a MIME header from an input stream. */
+    public void parseHeader(InputStream is) throws java.io.IOException {
+        synchronized (this) {
+            nkeys = 0;
+        }
+        mergeHeader(is);
+    }
+
+    /** Parse and merge a MIME header from an input stream. */
+    @SuppressWarnings("fallthrough")
+    public void mergeHeader(InputStream is) throws java.io.IOException {
+        if (is == null)
+            return;
+        char s[] = new char[10];
+        int firstc = is.read();
+        while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
+            int len = 0;
+            int keyend = -1;
+            int c;
+            boolean inKey = firstc > ' ';
+            s[len++] = (char) firstc;
+    parseloop:{
+                while ((c = is.read()) >= 0) {
+                    switch (c) {
+                      case ':':
+                        if (inKey && len > 0)
+                            keyend = len;
+                        inKey = false;
+                        break;
+                      case '\t':
+                        c = ' ';
+                      /*fall through*/
+                      case ' ':
+                        inKey = false;
+                        break;
+                      case '\r':
+                      case '\n':
+                        firstc = is.read();
+                        if (c == '\r' && firstc == '\n') {
+                            firstc = is.read();
+                            if (firstc == '\r')
+                                firstc = is.read();
+                        }
+                        if (firstc == '\n' || firstc == '\r' || firstc > ' ')
+                            break parseloop;
+                        /* continuation */
+                        c = ' ';
+                        break;
+                    }
+                    if (len >= s.length) {
+                        char ns[] = new char[s.length * 2];
+                        System.arraycopy(s, 0, ns, 0, len);
+                        s = ns;
+                    }
+                    s[len++] = (char) c;
+                }
+                firstc = -1;
+            }
+            while (len > 0 && s[len - 1] <= ' ')
+                len--;
+            String k;
+            if (keyend <= 0) {
+                k = null;
+                keyend = 0;
+            } else {
+                k = String.copyValueOf(s, 0, keyend);
+                if (keyend < len && s[keyend] == ':')
+                    keyend++;
+                while (keyend < len && s[keyend] <= ' ')
+                    keyend++;
+            }
+            String v;
+            if (keyend >= len)
+                v = new String();
+            else
+                v = String.copyValueOf(s, keyend, len - keyend);
+            add(k, v);
+        }
+    }
+
+    public synchronized String toString() {
+        String result = super.toString() + nkeys + " pairs: ";
+        for (int i = 0; i < keys.length && i < nkeys; i++) {
+            result += "{"+keys[i]+": "+values[i]+"}";
+        }
+        return result;
+    }
+}