/*
* 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 com.sun.jersey.core.header.reader;
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;
import com.sun.jersey.core.header.AcceptableLanguageTag;
import com.sun.jersey.core.header.AcceptableMediaType;
import com.sun.jersey.core.header.AcceptableToken;
import com.sun.jersey.core.header.HttpDateFormat;
import com.sun.jersey.core.header.MatchingEntityTag;
import com.sun.jersey.core.header.MediaTypes;
import com.sun.jersey.core.header.QualityFactor;
import com.sun.jersey.core.header.QualitySourceMediaType;
import com.sun.jersey.core.impl.provider.header.MediaTypeProvider;
/**
* A pull-based reader of HTTP headers.
*
* @author Paul.Sandoz@Sun.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 QualityFactor.DEFAULT_QUALITY_FACTOR;
} 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 QualityFactor.DEFAULT_QUALITY_FACTOR;
// Get the parameter name
String name = reader.nextToken();
reader.nextSeparator('=');
// Get the parameter value
String value = reader.nextTokenOrQuotedString();
if (q == -1 && name.equalsIgnoreCase(QualityFactor.QUALITY_FACTOR)) {
q = readQualityFactor(value);
}
}
return (q == -1) ? QualityFactor.DEFAULT_QUALITY_FACTOR : q;
}
public static Map<String, String> readParameters(HttpHeaderReader reader) throws ParseException {
return readParameters(reader, false);
}
public static Map<String, String> readParameters(HttpHeaderReader reader, boolean fileNameFix) throws ParseException {
Map<String, String> 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<String, String>();
// Lower case the parameter name
m.put(name.toLowerCase(), value);
}
return m;
}
public static Map<String, Cookie> 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<MatchingEntityTag> MATCHING_ENTITY_TAG_CREATOR =
new ListElementCreator<MatchingEntityTag>() {
public MatchingEntityTag create(HttpHeaderReader reader) throws ParseException {
return MatchingEntityTag.valueOf(reader);
}
};
public static Set<MatchingEntityTag> readMatchingEntityTag(String header) throws ParseException {
if (header.equals("*"))
return MatchingEntityTag.ANY_MATCH;
HttpHeaderReader reader = new HttpHeaderReaderImpl(header);
Set<MatchingEntityTag> l = new HashSet<MatchingEntityTag>(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<MediaType> MEDIA_TYPE_CREATOR =
new ListElementCreator<MediaType>() {
public MediaType create(HttpHeaderReader reader) throws ParseException {
return MediaTypeProvider.valueOf(reader);
}
};
public static List<MediaType> readMediaTypes(List<MediaType> l, String header) throws ParseException {
return HttpHeaderReader.readList(
l,
MEDIA_TYPE_CREATOR,
header);
}
private static final ListElementCreator<AcceptableMediaType> ACCEPTABLE_MEDIA_TYPE_CREATOR =
new ListElementCreator<AcceptableMediaType>() {
public AcceptableMediaType create(HttpHeaderReader reader) throws ParseException {
return AcceptableMediaType.valueOf(reader);
}
};
private static final Comparator<AcceptableMediaType> ACCEPTABLE_MEDIA_TYPE_COMPARATOR
= new Comparator<AcceptableMediaType>() {
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<AcceptableMediaType> readAcceptMediaType(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(
ACCEPTABLE_MEDIA_TYPE_COMPARATOR,
ACCEPTABLE_MEDIA_TYPE_CREATOR,
header);
}
private static final ListElementCreator<QualitySourceMediaType> QUALITY_SOURCE_MEDIA_TYPE_CREATOR =
new ListElementCreator<QualitySourceMediaType>() {
public QualitySourceMediaType create(HttpHeaderReader reader) throws ParseException {
return QualitySourceMediaType.valueOf(reader);
}
};
public static List<QualitySourceMediaType> readQualitySourceMediaType(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(
MediaTypes.QUALITY_SOURCE_MEDIA_TYPE_COMPARATOR,
QUALITY_SOURCE_MEDIA_TYPE_CREATOR,
header);
}
public static List<QualitySourceMediaType> 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<AcceptableMediaType> readAcceptMediaType(String header,
final List<QualitySourceMediaType> priorityMediaTypes) throws ParseException {
return HttpHeaderReader.readAcceptableList(
new Comparator<AcceptableMediaType>() {
public int compare(AcceptableMediaType o1, AcceptableMediaType o2) {
boolean q_o1_set = false;
int q_o1 = QualitySourceMediaType.DEFAULT_QUALITY_SOURCE_FACTOR * QualitySourceMediaType.DEFAULT_QUALITY_SOURCE_FACTOR;
boolean q_o2_set = false;
int q_o2 = QualitySourceMediaType.DEFAULT_QUALITY_SOURCE_FACTOR * QualitySourceMediaType.DEFAULT_QUALITY_SOURCE_FACTOR;
for (QualitySourceMediaType m : priorityMediaTypes) {
if (!q_o1_set && MediaTypes.typeEquals(o1, m)) {
q_o1 = o1.getQuality() * m.getQualitySource();
q_o1_set = true;
} else if (!q_o2_set && MediaTypes.typeEquals(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<AcceptableToken> ACCEPTABLE_TOKEN_CREATOR =
new ListElementCreator<AcceptableToken>() {
public AcceptableToken create(HttpHeaderReader reader) throws ParseException {
return new AcceptableToken(reader);
}
};
public static List<AcceptableToken> readAcceptToken(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(ACCEPTABLE_TOKEN_CREATOR, header);
}
private static final ListElementCreator<AcceptableLanguageTag> LANGUAGE_CREATOR =
new ListElementCreator<AcceptableLanguageTag>() {
public AcceptableLanguageTag create(HttpHeaderReader reader) throws ParseException {
return new AcceptableLanguageTag(reader);
}
};
public static List<AcceptableLanguageTag> readAcceptLanguage(String header) throws ParseException {
return HttpHeaderReader.readAcceptableList(LANGUAGE_CREATOR, header);
}
private static final Comparator<QualityFactor> QUALITY_COMPARATOR = new Comparator<QualityFactor>() {
public int compare(QualityFactor o1, QualityFactor o2) {
return o2.getQuality() - o1.getQuality();
}
};
public static <T extends QualityFactor> List<T> readAcceptableList(
ListElementCreator<T> c,
String header) throws ParseException {
List<T> l = readList(c, header);
Collections.sort(l, QUALITY_COMPARATOR);
return l;
}
public static <T> List<T> readAcceptableList(
Comparator<T> comparator,
ListElementCreator<T> c,
String header) throws ParseException {
List<T> l = readList(c, header);
Collections.sort(l, comparator);
return l;
}
public static interface ListElementCreator<T> {
T create(HttpHeaderReader reader) throws ParseException;
}
public static <T> List<T> readList(ListElementCreator<T> c,
String header) throws ParseException {
return readList(new ArrayList<T>(), c, header);
}
public static <T> List<T> readList(List<T> l, ListElementCreator<T> 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;
}
}