Path: blob/master/src/java.base/share/classes/sun/net/www/MessageHeader.java
41159 views
/*1* Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*-26* news stream opener27*/2829package sun.net.www;3031import java.io.*;32import java.util.Collections;33import java.util.*;3435/** An RFC 844 or MIME message header. Includes methods36for parsing headers from incoming streams, fetching37values, setting values, and printing headers.38Key values of null are legal: they indicate lines in39the header that don't have a valid key, but do have40a value (this isn't legal according to the standard,41but lines like this are everywhere). */42public43class MessageHeader {44private String keys[];45private String values[];46private int nkeys;4748public MessageHeader () {49grow();50}5152public MessageHeader (InputStream is) throws java.io.IOException {53parseHeader(is);54}5556/**57* Returns list of header names in a comma separated list58*/59public synchronized String getHeaderNamesInList() {60StringJoiner joiner = new StringJoiner(",");61for (int i=0; i<nkeys; i++) {62joiner.add(keys[i]);63}64return joiner.toString();65}6667/**68* Reset a message header (all key/values removed)69*/70public synchronized void reset() {71keys = null;72values = null;73nkeys = 0;74grow();75}7677/**78* Find the value that corresponds to this key.79* It finds only the first occurrence of the key.80* @param k the key to find.81* @return null if not found.82*/83public synchronized String findValue(String k) {84if (k == null) {85for (int i = nkeys; --i >= 0;)86if (keys[i] == null)87return values[i];88} else89for (int i = nkeys; --i >= 0;) {90if (k.equalsIgnoreCase(keys[i]))91return values[i];92}93return null;94}9596// return the location of the key97public synchronized int getKey(String k) {98for (int i = nkeys; --i >= 0;)99if ((keys[i] == k) ||100(k != null && k.equalsIgnoreCase(keys[i])))101return i;102return -1;103}104105public synchronized String getKey(int n) {106if (n < 0 || n >= nkeys) return null;107return keys[n];108}109110public synchronized String getValue(int n) {111if (n < 0 || n >= nkeys) return null;112return values[n];113}114115/** Deprecated: Use multiValueIterator() instead.116*117* Find the next value that corresponds to this key.118* It finds the first value that follows v. To iterate119* over all the values of a key use:120* <pre>121* for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {122* ...123* }124* </pre>125*/126public synchronized String findNextValue(String k, String v) {127boolean foundV = false;128if (k == null) {129for (int i = nkeys; --i >= 0;)130if (keys[i] == null)131if (foundV)132return values[i];133else if (values[i] == v)134foundV = true;135} else136for (int i = nkeys; --i >= 0;)137if (k.equalsIgnoreCase(keys[i]))138if (foundV)139return values[i];140else if (values[i] == v)141foundV = true;142return null;143}144145/**146* Removes bare Negotiate and Kerberos headers when an "NTLM ..."147* appears. All Performed on headers with key being k.148* @return true if there is a change149*/150public boolean filterNTLMResponses(String k) {151boolean found = false;152for (int i=0; i<nkeys; i++) {153if (k.equalsIgnoreCase(keys[i])154&& values[i] != null && values[i].length() > 5155&& values[i].substring(0, 5).equalsIgnoreCase("NTLM ")) {156found = true;157break;158}159}160if (found) {161int j = 0;162for (int i=0; i<nkeys; i++) {163if (k.equalsIgnoreCase(keys[i]) && (164"Negotiate".equalsIgnoreCase(values[i]) ||165"Kerberos".equalsIgnoreCase(values[i]))) {166continue;167}168if (i != j) {169keys[j] = keys[i];170values[j] = values[i];171}172j++;173}174if (j != nkeys) {175nkeys = j;176return true;177}178}179return false;180}181182class HeaderIterator implements Iterator<String> {183int index = 0;184int next = -1;185String key;186boolean haveNext = false;187Object lock;188189public HeaderIterator (String k, Object lock) {190key = k;191this.lock = lock;192}193public boolean hasNext () {194synchronized (lock) {195if (haveNext) {196return true;197}198while (index < nkeys) {199if (key.equalsIgnoreCase (keys[index])) {200haveNext = true;201next = index++;202return true;203}204index ++;205}206return false;207}208}209public String next() {210synchronized (lock) {211if (haveNext) {212haveNext = false;213return values [next];214}215if (hasNext()) {216return next();217} else {218throw new NoSuchElementException ("No more elements");219}220}221}222public void remove () {223throw new UnsupportedOperationException ("remove not allowed");224}225}226227/**228* return an Iterator that returns all values of a particular229* key in sequence230*/231public Iterator<String> multiValueIterator (String k) {232return new HeaderIterator (k, this);233}234235public synchronized Map<String, List<String>> getHeaders() {236return getHeaders(null);237}238239public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {240return filterAndAddHeaders(excludeList, null);241}242243public synchronized Map<String, List<String>> filterAndAddHeaders(244String[] excludeList, Map<String, List<String>> include) {245boolean skipIt = false;246Map<String, List<String>> m = new HashMap<>();247for (int i = nkeys; --i >= 0;) {248if (excludeList != null) {249// check if the key is in the excludeList.250// if so, don't include it in the Map.251for (int j = 0; j < excludeList.length; j++) {252if ((excludeList[j] != null) &&253(excludeList[j].equalsIgnoreCase(keys[i]))) {254skipIt = true;255break;256}257}258}259if (!skipIt) {260List<String> l = m.get(keys[i]);261if (l == null) {262l = new ArrayList<>();263m.put(keys[i], l);264}265l.add(values[i]);266} else {267// reset the flag268skipIt = false;269}270}271272if (include != null) {273for (Map.Entry<String,List<String>> entry: include.entrySet()) {274List<String> l = m.get(entry.getKey());275if (l == null) {276l = new ArrayList<>();277m.put(entry.getKey(), l);278}279l.addAll(entry.getValue());280}281}282283for (String key : m.keySet()) {284m.put(key, Collections.unmodifiableList(m.get(key)));285}286287return Collections.unmodifiableMap(m);288}289290/** Check if a line of message header looks like a request line.291* This method does not perform a full validation but simply292* returns false if the line does not end with 'HTTP/[1-9].[0-9]'293* @param line the line to check.294* @return true if the line might be a request line.295*/296private static boolean isRequestline(String line) {297String k = line.trim();298int i = k.lastIndexOf(' ');299if (i <= 0) return false;300int len = k.length();301if (len - i < 9) return false;302303char c1 = k.charAt(len-3);304char c2 = k.charAt(len-2);305char c3 = k.charAt(len-1);306if (c1 < '1' || c1 > '9') return false;307if (c2 != '.') return false;308if (c3 < '0' || c3 > '9') return false;309310return (k.substring(i+1, len-3).equalsIgnoreCase("HTTP/"));311}312313/** Prints the key-value pairs represented by this314header. Also prints the RFC required blank line315at the end. Omits pairs with a null key. Omits316colon if key-value pair is the requestline. */317public void print(PrintStream p) {318// no synchronization: use cloned arrays instead.319String[] k; String[] v; int n;320synchronized (this) { n = nkeys; k = keys.clone(); v = values.clone(); }321print(n, k, v, p);322}323324325/** Prints the key-value pairs represented by this326header. Also prints the RFC required blank line327at the end. Omits pairs with a null key. Omits328colon if key-value pair is the requestline. */329private static void print(int nkeys, String[] keys, String[] values, PrintStream p) {330for (int i = 0; i < nkeys; i++)331if (keys[i] != null) {332StringBuilder sb = new StringBuilder(keys[i]);333if (values[i] != null) {334sb.append(": " + values[i]);335} else if (i != 0 || !isRequestline(keys[i])) {336sb.append(":");337}338p.print(sb.append("\r\n"));339}340p.print("\r\n");341p.flush();342}343344/** Adds a key value pair to the end of the345header. Duplicates are allowed */346public synchronized void add(String k, String v) {347grow();348keys[nkeys] = k;349values[nkeys] = v;350nkeys++;351}352353/** Prepends a key value pair to the beginning of the354header. Duplicates are allowed */355public synchronized void prepend(String k, String v) {356grow();357for (int i = nkeys; i > 0; i--) {358keys[i] = keys[i-1];359values[i] = values[i-1];360}361keys[0] = k;362values[0] = v;363nkeys++;364}365366/** Overwrite the previous key/val pair at location 'i'367* with the new k/v. If the index didn't exist before368* the key/val is simply tacked onto the end.369*/370371public synchronized void set(int i, String k, String v) {372grow();373if (i < 0) {374return;375} else if (i >= nkeys) {376add(k, v);377} else {378keys[i] = k;379values[i] = v;380}381}382383384/** grow the key/value arrays as needed */385386private void grow() {387if (keys == null || nkeys >= keys.length) {388String[] nk = new String[nkeys + 4];389String[] nv = new String[nkeys + 4];390if (keys != null)391System.arraycopy(keys, 0, nk, 0, nkeys);392if (values != null)393System.arraycopy(values, 0, nv, 0, nkeys);394keys = nk;395values = nv;396}397}398399/**400* Remove the key from the header. If there are multiple values under401* the same key, they are all removed.402* Nothing is done if the key doesn't exist.403* After a remove, the other pairs' order are not changed.404* @param k the key to remove405*/406public synchronized void remove(String k) {407if(k == null) {408for (int i = 0; i < nkeys; i++) {409while (keys[i] == null && i < nkeys) {410for(int j=i; j<nkeys-1; j++) {411keys[j] = keys[j+1];412values[j] = values[j+1];413}414nkeys--;415}416}417} else {418for (int i = 0; i < nkeys; i++) {419while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {420for(int j=i; j<nkeys-1; j++) {421keys[j] = keys[j+1];422values[j] = values[j+1];423}424nkeys--;425}426}427}428}429430/** Sets the value of a key. If the key already431exists in the header, it's value will be432changed. Otherwise a new key/value pair will433be added to the end of the header. */434public synchronized void set(String k, String v) {435for (int i = nkeys; --i >= 0;)436if (k.equalsIgnoreCase(keys[i])) {437values[i] = v;438return;439}440add(k, v);441}442443/** Set's the value of a key only if there is no444* key with that value already.445*/446447public synchronized void setIfNotSet(String k, String v) {448if (findValue(k) == null) {449add(k, v);450}451}452453/** Convert a message-id string to canonical form (strips off454leading and trailing {@literal <>s}) */455public static String canonicalID(String id) {456if (id == null)457return "";458int st = 0;459int len = id.length();460boolean substr = false;461int c;462while (st < len && ((c = id.charAt(st)) == '<' ||463c <= ' ')) {464st++;465substr = true;466}467while (st < len && ((c = id.charAt(len - 1)) == '>' ||468c <= ' ')) {469len--;470substr = true;471}472return substr ? id.substring(st, len) : id;473}474475/** Parse a MIME header from an input stream. */476public void parseHeader(InputStream is) throws java.io.IOException {477synchronized (this) {478nkeys = 0;479}480mergeHeader(is);481}482483/** Parse and merge a MIME header from an input stream. */484@SuppressWarnings("fallthrough")485public void mergeHeader(InputStream is) throws java.io.IOException {486if (is == null)487return;488char s[] = new char[10];489int firstc = is.read();490while (firstc != '\n' && firstc != '\r' && firstc >= 0) {491int len = 0;492int keyend = -1;493int c;494boolean inKey = firstc > ' ';495s[len++] = (char) firstc;496parseloop:{497while ((c = is.read()) >= 0) {498switch (c) {499case ':':500if (inKey && len > 0)501keyend = len;502inKey = false;503break;504case '\t':505c = ' ';506/*fall through*/507case ' ':508inKey = false;509break;510case '\r':511case '\n':512firstc = is.read();513if (c == '\r' && firstc == '\n') {514firstc = is.read();515if (firstc == '\r')516firstc = is.read();517}518if (firstc == '\n' || firstc == '\r' || firstc > ' ')519break parseloop;520/* continuation */521c = ' ';522break;523}524if (len >= s.length) {525char ns[] = new char[s.length * 2];526System.arraycopy(s, 0, ns, 0, len);527s = ns;528}529s[len++] = (char) c;530}531firstc = -1;532}533while (len > 0 && s[len - 1] <= ' ')534len--;535String k;536if (keyend <= 0) {537k = null;538keyend = 0;539} else {540k = String.copyValueOf(s, 0, keyend);541if (keyend < len && s[keyend] == ':')542keyend++;543while (keyend < len && s[keyend] <= ' ')544keyend++;545}546String v;547if (keyend >= len)548v = new String();549else550v = String.copyValueOf(s, keyend, len - keyend);551add(k, v);552}553}554555public synchronized String toString() {556String result = super.toString() + nkeys + " pairs: ";557for (int i = 0; i < keys.length && i < nkeys; i++) {558result += "{"+keys[i]+": "+values[i]+"}";559}560return result;561}562}563564565