Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/net/www/MessageHeader.java
38830 views
/*1* Copyright (c) 1995, 2013, 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<String, List<String>>();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<String>();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<String>();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 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}312313314/** Prints the key-value pairs represented by this315header. Also prints the RFC required blank line316at the end. Omits pairs with a null key. Omits317colon if key-value pair is the requestline. */318public synchronized void print(PrintStream p) {319for (int i = 0; i < nkeys; i++)320if (keys[i] != null) {321StringBuilder sb = new StringBuilder(keys[i]);322if (values[i] != null) {323sb.append(": " + values[i]);324} else if (i != 0 || !isRequestline(keys[i])) {325sb.append(":");326}327p.print(sb.append("\r\n"));328}329p.print("\r\n");330p.flush();331}332333/** Adds a key value pair to the end of the334header. Duplicates are allowed */335public synchronized void add(String k, String v) {336grow();337keys[nkeys] = k;338values[nkeys] = v;339nkeys++;340}341342/** Prepends a key value pair to the beginning of the343header. Duplicates are allowed */344public synchronized void prepend(String k, String v) {345grow();346for (int i = nkeys; i > 0; i--) {347keys[i] = keys[i-1];348values[i] = values[i-1];349}350keys[0] = k;351values[0] = v;352nkeys++;353}354355/** Overwrite the previous key/val pair at location 'i'356* with the new k/v. If the index didn't exist before357* the key/val is simply tacked onto the end.358*/359360public synchronized void set(int i, String k, String v) {361grow();362if (i < 0) {363return;364} else if (i >= nkeys) {365add(k, v);366} else {367keys[i] = k;368values[i] = v;369}370}371372373/** grow the key/value arrays as needed */374375private void grow() {376if (keys == null || nkeys >= keys.length) {377String[] nk = new String[nkeys + 4];378String[] nv = new String[nkeys + 4];379if (keys != null)380System.arraycopy(keys, 0, nk, 0, nkeys);381if (values != null)382System.arraycopy(values, 0, nv, 0, nkeys);383keys = nk;384values = nv;385}386}387388/**389* Remove the key from the header. If there are multiple values under390* the same key, they are all removed.391* Nothing is done if the key doesn't exist.392* After a remove, the other pairs' order are not changed.393* @param k the key to remove394*/395public synchronized void remove(String k) {396if(k == null) {397for (int i = 0; i < nkeys; i++) {398while (keys[i] == null && i < nkeys) {399for(int j=i; j<nkeys-1; j++) {400keys[j] = keys[j+1];401values[j] = values[j+1];402}403nkeys--;404}405}406} else {407for (int i = 0; i < nkeys; i++) {408while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {409for(int j=i; j<nkeys-1; j++) {410keys[j] = keys[j+1];411values[j] = values[j+1];412}413nkeys--;414}415}416}417}418419/** Sets the value of a key. If the key already420exists in the header, it's value will be421changed. Otherwise a new key/value pair will422be added to the end of the header. */423public synchronized void set(String k, String v) {424for (int i = nkeys; --i >= 0;)425if (k.equalsIgnoreCase(keys[i])) {426values[i] = v;427return;428}429add(k, v);430}431432/** Set's the value of a key only if there is no433* key with that value already.434*/435436public synchronized void setIfNotSet(String k, String v) {437if (findValue(k) == null) {438add(k, v);439}440}441442/** Convert a message-id string to canonical form (strips off443leading and trailing <>s) */444public static String canonicalID(String id) {445if (id == null)446return "";447int st = 0;448int len = id.length();449boolean substr = false;450int c;451while (st < len && ((c = id.charAt(st)) == '<' ||452c <= ' ')) {453st++;454substr = true;455}456while (st < len && ((c = id.charAt(len - 1)) == '>' ||457c <= ' ')) {458len--;459substr = true;460}461return substr ? id.substring(st, len) : id;462}463464/** Parse a MIME header from an input stream. */465public void parseHeader(InputStream is) throws java.io.IOException {466synchronized (this) {467nkeys = 0;468}469mergeHeader(is);470}471472/** Parse and merge a MIME header from an input stream. */473@SuppressWarnings("fallthrough")474public void mergeHeader(InputStream is) throws java.io.IOException {475if (is == null)476return;477char s[] = new char[10];478int firstc = is.read();479while (firstc != '\n' && firstc != '\r' && firstc >= 0) {480int len = 0;481int keyend = -1;482int c;483boolean inKey = firstc > ' ';484s[len++] = (char) firstc;485parseloop:{486while ((c = is.read()) >= 0) {487switch (c) {488case ':':489if (inKey && len > 0)490keyend = len;491inKey = false;492break;493case '\t':494c = ' ';495/*fall through*/496case ' ':497inKey = false;498break;499case '\r':500case '\n':501firstc = is.read();502if (c == '\r' && firstc == '\n') {503firstc = is.read();504if (firstc == '\r')505firstc = is.read();506}507if (firstc == '\n' || firstc == '\r' || firstc > ' ')508break parseloop;509/* continuation */510c = ' ';511break;512}513if (len >= s.length) {514char ns[] = new char[s.length * 2];515System.arraycopy(s, 0, ns, 0, len);516s = ns;517}518s[len++] = (char) c;519}520firstc = -1;521}522while (len > 0 && s[len - 1] <= ' ')523len--;524String k;525if (keyend <= 0) {526k = null;527keyend = 0;528} else {529k = String.copyValueOf(s, 0, keyend);530if (keyend < len && s[keyend] == ':')531keyend++;532while (keyend < len && s[keyend] <= ' ')533keyend++;534}535String v;536if (keyend >= len)537v = new String();538else539v = String.copyValueOf(s, keyend, len - keyend);540add(k, v);541}542}543544public synchronized String toString() {545String result = super.toString() + nkeys + " pairs: ";546for (int i = 0; i < keys.length && i < nkeys; i++) {547result += "{"+keys[i]+": "+values[i]+"}";548}549return result;550}551}552553554