org.glassfish.jersey.message.internal.HttpHeaderReader Maven / Gradle / Ivy
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.message.internal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
/**
* An abstract pull-based reader of HTTP headers.
*
* @author Paul Sandoz
* @author Marek Potociar (marek.potociar at oracle.com)
*/
public abstract class HttpHeaderReader {
public enum Event {
Token, QuotedString, Comment, Separator, Control
}
public abstract boolean hasNext();
public abstract boolean hasNextSeparator(char separator, boolean skipWhiteSpace);
public abstract Event next() throws ParseException;
public abstract Event next(boolean skipWhiteSpace) throws ParseException;
public abstract Event next(boolean skipWhiteSpace, boolean preserveBackslash) throws ParseException;
public abstract String nextSeparatedString(char startSeparator, char endSeparator) throws ParseException;
public abstract Event getEvent();
public abstract String getEventValue();
public abstract String getRemainder();
public abstract int getIndex();
public String nextToken() throws ParseException {
Event e = next(false);
if (e != Event.Token) {
throw new ParseException("Next event is not a Token", getIndex());
}
return getEventValue();
}
public char nextSeparator() throws ParseException {
Event e = next(false);
if (e != Event.Separator) {
throw new ParseException("Next event is not a Separator", getIndex());
}
return getEventValue().charAt(0);
}
public void nextSeparator(char c) throws ParseException {
Event e = next(false);
if (e != Event.Separator) {
throw new ParseException("Next event is not a Separator", getIndex());
}
if (c != getEventValue().charAt(0)) {
throw new ParseException("Expected separator '" + c + "' instead of '"
+ getEventValue().charAt(0) + "'", getIndex());
}
}
public String nextQuotedString() throws ParseException {
Event e = next(false);
if (e != Event.QuotedString) {
throw new ParseException("Next event is not a Quoted String", getIndex());
}
return getEventValue();
}
public String nextTokenOrQuotedString() throws ParseException {
return nextTokenOrQuotedString(false);
}
public String nextTokenOrQuotedString(boolean preserveBackslash) throws ParseException {
Event e = next(false, preserveBackslash);
if (e != Event.Token && e != Event.QuotedString) {
throw new ParseException("Next event is not a Token or a Quoted String, "
+ getEventValue(), getIndex());
}
return getEventValue();
}
public static HttpHeaderReader newInstance(String header) {
return new HttpHeaderReaderImpl(header);
}
public static HttpHeaderReader newInstance(String header, boolean processComments) {
return new HttpHeaderReaderImpl(header, processComments);
}
public static Date readDate(String date) throws ParseException {
return HttpDateFormat.readDate(date);
}
public static int readQualityFactor(String q) throws ParseException {
if (q == null || q.length() == 0) {
throw new ParseException("Quality value cannot be null or an empty String", 0);
}
int index = 0;
final int length = q.length();
if (length > 5) {
throw new ParseException("Quality value is greater than the maximum length, 5", 0);
}
// Parse the whole number and decimal point
final char wholeNumber;
char c = wholeNumber = q.charAt(index++);
if (c == '0' || c == '1') {
if (index == length) {
return (c - '0') * 1000;
}
c = q.charAt(index++);
if (c != '.') {
throw new ParseException("Error parsing Quality value: a decimal place is expected rather than '"
+ c + "'", index);
}
if (index == length) {
return (c - '0') * 1000;
}
} else if (c == '.') {
// This is not conformant to the HTTP specification but some implementations
// do this, for example HttpURLConnection.
if (index == length) {
throw new ParseException("Error parsing Quality value: a decimal numeral is expected after the decimal point", index);
}
} else {
throw new ParseException("Error parsing Quality value: a decimal numeral '0' or '1' is expected rather than '"
+ c + "'", index);
}
// Parse the fraction
int value = 0;
int exponent = 100;
while (index < length) {
c = q.charAt(index++);
if (c >= '0' && c <= '9') {
value += (c - '0') * exponent;
exponent /= 10;
} else {
throw new ParseException("Error parsing Quality value: a decimal numeral is expected rather than '"
+ c + "'", index);
}
}
if (wholeNumber == '1') {
if (value > 0) {
throw new ParseException("The Quality value, " + q + ", is greater than 1", index);
}
return Quality.DEFAULT_QUALITY;
} else {
return value;
}
}
public static int readQualityFactorParameter(HttpHeaderReader reader) throws ParseException {
int q = -1;
while (reader.hasNext()) {
reader.nextSeparator(';');
// Ignore a ';' with no parameters
if (!reader.hasNext()) {
return Quality.DEFAULT_QUALITY;
}
// Get the parameter name
String name = reader.nextToken();
reader.nextSeparator('=');
// Get the parameter value
String value = reader.nextTokenOrQuotedString();
if (q == -1 && name.equalsIgnoreCase(Qualified.QUALITY_PARAMETER_NAME)) {
q = readQualityFactor(value);
}
}
return (q == -1) ? Quality.DEFAULT_QUALITY : q;
}
public static Map readParameters(HttpHeaderReader reader) throws ParseException {
return readParameters(reader, false);
}
public static Map readParameters(HttpHeaderReader reader, boolean fileNameFix) throws ParseException {
Map m = null;
while (reader.hasNext()) {
reader.nextSeparator(';');
while (reader.hasNextSeparator(';', true)) {
reader.next();
}
// Ignore a ';' with no parameters
if (!reader.hasNext()) {
break;
}
// Get the parameter name
String name = reader.nextToken();
reader.nextSeparator('=');
// Get the parameter value
String value;
// fix for http://java.net/jira/browse/JERSEY-759
if ("filename".equalsIgnoreCase(name) && fileNameFix) {
value = reader.nextTokenOrQuotedString(true);
value = value.substring(value.lastIndexOf('\\') + 1);
} else {
value = reader.nextTokenOrQuotedString(false);
}
if (m == null) {
m = new LinkedHashMap();
}
// Lower case the parameter name
m.put(name.toLowerCase(), value);
}
return m;
}
public static Map readCookies(String header) {
return CookiesParser.parseCookies(header);
}
public static Cookie readCookie(String header) {
return CookiesParser.parseCookie(header);
}
public static NewCookie readNewCookie(String header) {
return CookiesParser.parseNewCookie(header);
}
private static final ListElementCreator MATCHING_ENTITY_TAG_CREATOR =
new ListElementCreator() {
@Override
public MatchingEntityTag create(HttpHeaderReader reader) throws ParseException {
return MatchingEntityTag.valueOf(reader);
}
};
public static Set readMatchingEntityTag(String header) throws ParseException {
if (header.equals("*")) {
return MatchingEntityTag.ANY_MATCH;
}
HttpHeaderReader reader = new HttpHeaderReaderImpl(header);
Set l = new HashSet(1);
HttpHeaderListAdapter adapter = new HttpHeaderListAdapter(reader);
while (reader.hasNext()) {
l.add(MATCHING_ENTITY_TAG_CREATOR.create(adapter));
adapter.reset();
if (reader.hasNext()) {
reader.next();
}
}
return l;
}
private static final ListElementCreator MEDIA_TYPE_CREATOR =
new ListElementCreator() {
@Override
public MediaType create(HttpHeaderReader reader) throws ParseException {
return MediaTypeProvider.valueOf(reader);
}
};
public static List readMediaTypes(List l, String header) throws ParseException {
return HttpHeaderReader.readList(
l,
MEDIA_TYPE_CREATOR,
header);
}
private static final ListElementCreator ACCEPTABLE_MEDIA_TYPE_CREATOR =
new ListElementCreator() {
@Override
public AcceptableMediaType create(HttpHeaderReader reader) throws ParseException {
return AcceptableMediaType.valueOf(reader);
}
};
private static final Comparator ACCEPTABLE_MEDIA_TYPE_COMPARATOR = new Comparator() {
@Override
public int compare(AcceptableMediaType o1, AcceptableMediaType o2) {
int i = o2.getQuality() - o1.getQuality();
if (i != 0) {
return i;
}
return MediaTypes.MEDIA_TYPE_COMPARATOR.compare(o1, o2);
}
};
public static List readAcceptMediaType(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(
ACCEPTABLE_MEDIA_TYPE_COMPARATOR,
ACCEPTABLE_MEDIA_TYPE_CREATOR,
header);
}
private static final ListElementCreator QUALITY_SOURCE_MEDIA_TYPE_CREATOR =
new ListElementCreator() {
@Override
public QualitySourceMediaType create(HttpHeaderReader reader) throws ParseException {
return QualitySourceMediaType.valueOf(reader);
}
};
public static List readQualitySourceMediaType(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(
MediaTypes.QUALITY_SOURCE_MEDIA_TYPE_COMPARATOR,
QUALITY_SOURCE_MEDIA_TYPE_CREATOR,
header);
}
public static List readQualitySourceMediaType(String[] header) throws ParseException {
if (header.length < 2) {
return readQualitySourceMediaType(header[0]);
}
StringBuilder sb = new StringBuilder();
for (String h : header) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(h);
}
return readQualitySourceMediaType(sb.toString());
}
public static List readAcceptMediaType(String header,
final List priorityMediaTypes) throws ParseException {
return HttpHeaderReader.readAcceptableList(
new Comparator() {
@Override
public int compare(AcceptableMediaType o1, AcceptableMediaType o2) {
boolean q_o1_set = false;
int q_o1 = Quality.DEFAULT_QUALITY * Quality.DEFAULT_QUALITY;
boolean q_o2_set = false;
int q_o2 = Quality.DEFAULT_QUALITY * Quality.DEFAULT_QUALITY;
for (QualitySourceMediaType m : priorityMediaTypes) {
if (!q_o1_set && MediaTypes.typeEqual(o1, m)) {
q_o1 = o1.getQuality() * m.getQualitySource();
q_o1_set = true;
} else if (!q_o2_set && MediaTypes.typeEqual(o2, m)) {
q_o2 = o2.getQuality() * m.getQualitySource();
q_o2_set = true;
}
}
int i = q_o2 - q_o1;
if (i != 0) {
return i;
}
i = o2.getQuality() - o1.getQuality();
if (i != 0) {
return i;
}
return MediaTypes.MEDIA_TYPE_COMPARATOR.compare(o1, o2);
}
},
ACCEPTABLE_MEDIA_TYPE_CREATOR,
header);
}
private static final ListElementCreator ACCEPTABLE_TOKEN_CREATOR =
new ListElementCreator() {
@Override
public AcceptableToken create(HttpHeaderReader reader) throws ParseException {
return new AcceptableToken(reader);
}
};
public static List readAcceptToken(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(ACCEPTABLE_TOKEN_CREATOR, header);
}
private static final ListElementCreator LANGUAGE_CREATOR =
new ListElementCreator() {
@Override
public AcceptableLanguageTag create(HttpHeaderReader reader) throws ParseException {
return new AcceptableLanguageTag(reader);
}
};
public static List readAcceptLanguage(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(LANGUAGE_CREATOR, header);
}
private static final Comparator QUALITY_COMPARATOR = new Comparator() {
@Override
public int compare(Qualified o1, Qualified o2) {
return o2.getQuality() - o1.getQuality();
}
};
public static List readAcceptableList(
ListElementCreator c,
String header) throws ParseException {
List l = readList(c, header);
Collections.sort(l, QUALITY_COMPARATOR);
return l;
}
public static List readAcceptableList(
Comparator comparator,
ListElementCreator c,
String header) throws ParseException {
List l = readList(c, header);
Collections.sort(l, comparator);
return l;
}
public static List readStringList(String header) throws ParseException {
return readList(new ListElementCreator() {
@Override
public String create(HttpHeaderReader reader) throws ParseException {
reader.hasNext();
return reader.nextToken();
}
}, header);
}
public static interface ListElementCreator {
T create(HttpHeaderReader reader) throws ParseException;
}
public static List readList(ListElementCreator c,
String header) throws ParseException {
return readList(new ArrayList(), c, header);
}
public static List readList(List l, ListElementCreator c,
String header) throws ParseException {
HttpHeaderReader reader = new HttpHeaderReaderImpl(header);
HttpHeaderListAdapter adapter = new HttpHeaderListAdapter(reader);
while (reader.hasNext()) {
l.add(c.create(adapter));
adapter.reset();
if (reader.hasNext()) {
reader.next();
}
}
return l;
}
}