pt 1 in reviving the android build (copied from pm 28a1, wish me luck)

This commit is contained in:
wuggy 2026-04-08 15:43:25 -07:00
commit d7788a6d6d
4249 changed files with 468189 additions and 0 deletions

View file

@ -0,0 +1,86 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import ch.boye.httpclientandroidlib.annotation.Immutable;
/**
* A collection of utilities to workaround limitations of Java clone framework.
*
* @since 4.0
*/
@Immutable
public class CloneUtils {
/**
* @since 4.3
*/
public static <T> T cloneObject(final T obj) throws CloneNotSupportedException {
if (obj == null) {
return null;
}
if (obj instanceof Cloneable) {
final Class<?> clazz = obj.getClass ();
final Method m;
try {
m = clazz.getMethod("clone", (Class[]) null);
} catch (final NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
}
try {
@SuppressWarnings("unchecked") // OK because clone() preserves the class
final T result = (T) m.invoke(obj, (Object []) null);
return result;
} catch (final InvocationTargetException ex) {
final Throwable cause = ex.getCause();
if (cause instanceof CloneNotSupportedException) {
throw ((CloneNotSupportedException) cause);
} else {
throw new Error("Unexpected exception", cause);
}
} catch (final IllegalAccessException ex) {
throw new IllegalAccessError(ex.getMessage());
}
} else {
throw new CloneNotSupportedException();
}
}
public static Object clone(final Object obj) throws CloneNotSupportedException {
return cloneObject(obj);
}
/**
* This class should not be instantiated.
*/
private CloneUtils() {
}
}

View file

@ -0,0 +1,250 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.lang.ref.SoftReference;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import ch.boye.httpclientandroidlib.annotation.Immutable;
import ch.boye.httpclientandroidlib.util.Args;
/**
* A utility class for parsing and formatting HTTP dates as used in cookies and
* other headers. This class handles dates as defined by RFC 2616 section
* 3.3.1 as well as some other common non-standard formats.
*
* @since 4.3
*/
@Immutable
public final class DateUtils {
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in ANSI C
* <code>asctime()</code> format.
*/
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
private static final String[] DEFAULT_PATTERNS = new String[] {
PATTERN_RFC1123,
PATTERN_RFC1036,
PATTERN_ASCTIME
};
private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
static {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(GMT);
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
}
/**
* Parses a date value. The formats used for parsing the date value are retrieved from
* the default http params.
*
* @param dateValue the date value to parse
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String dateValue) {
return parseDate(dateValue, null, null);
}
/**
* Parses the date value using the given date formats.
*
* @param dateValue the date value to parse
* @param dateFormats the date formats to use
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String dateValue, final String[] dateFormats) {
return parseDate(dateValue, dateFormats, null);
}
/**
* Parses the date value using the given date formats.
*
* @param dateValue the date value to parse
* @param dateFormats the date formats to use
* @param startDate During parsing, two digit years will be placed in the range
* <code>startDate</code> to <code>startDate + 100 years</code>. This value may
* be <code>null</code>. When <code>null</code> is given as a parameter, year
* <code>2000</code> will be used.
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(
final String dateValue,
final String[] dateFormats,
final Date startDate) {
Args.notNull(dateValue, "Date value");
final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
String v = dateValue;
// trim single quotes around date if present
// see issue #5279
if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
v = v.substring (1, v.length() - 1);
}
for (final String dateFormat : localDateFormats) {
final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
dateParser.set2DigitYearStart(localStartDate);
final ParsePosition pos = new ParsePosition(0);
final Date result = dateParser.parse(v, pos);
if (pos.getIndex() != 0) {
return result;
}
}
return null;
}
/**
* Formats the given date according to the RFC 1123 pattern.
*
* @param date The date to format.
* @return An RFC 1123 formatted date string.
*
* @see #PATTERN_RFC1123
*/
public static String formatDate(final Date date) {
return formatDate(date, PATTERN_RFC1123);
}
/**
* Formats the given date according to the specified pattern. The pattern
* must conform to that used by the {@link SimpleDateFormat simple date
* format} class.
*
* @param date The date to format.
* @param pattern The pattern to use for formatting the date.
* @return A formatted date string.
*
* @throws IllegalArgumentException If the given date pattern is invalid.
*
* @see SimpleDateFormat
*/
public static String formatDate(final Date date, final String pattern) {
Args.notNull(date, "Date");
Args.notNull(pattern, "Pattern");
final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
return formatter.format(date);
}
/**
* Clears thread-local variable containing {@link java.text.DateFormat} cache.
*
* @since 4.3
*/
public static void clearThreadLocal() {
DateFormatHolder.clearThreadLocal();
}
/** This class should not be instantiated. */
private DateUtils() {
}
/**
* A factory for {@link SimpleDateFormat}s. The instances are stored in a
* threadlocal way because SimpleDateFormat is not threadsafe as noted in
* {@link SimpleDateFormat its javadoc}.
*
*/
final static class DateFormatHolder {
private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
@Override
protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
return new SoftReference<Map<String, SimpleDateFormat>>(
new HashMap<String, SimpleDateFormat>());
}
};
/**
* creates a {@link SimpleDateFormat} for the requested format string.
*
* @param pattern
* a non-<code>null</code> format String according to
* {@link SimpleDateFormat}. The format is not checked against
* <code>null</code> since all paths go through
* {@link DateUtils}.
* @return the requested format. This simple dateformat should not be used
* to {@link SimpleDateFormat#applyPattern(String) apply} to a
* different pattern.
*/
public static SimpleDateFormat formatFor(final String pattern) {
final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
Map<String, SimpleDateFormat> formats = ref.get();
if (formats == null) {
formats = new HashMap<String, SimpleDateFormat>();
THREADLOCAL_FORMATS.set(
new SoftReference<Map<String, SimpleDateFormat>>(formats));
}
SimpleDateFormat format = formats.get(pattern);
if (format == null) {
format = new SimpleDateFormat(pattern, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
formats.put(pattern, format);
}
return format;
}
public static void clearThreadLocal() {
THREADLOCAL_FORMATS.remove();
}
}
}

View file

@ -0,0 +1,149 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.io.Closeable;
import java.io.IOException;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.HttpClient;
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
import ch.boye.httpclientandroidlib.util.EntityUtils;
/**
* Convenience methods for closing response and client objects.
*
* @since 4.2
*/
public class HttpClientUtils {
private HttpClientUtils() {
}
/**
* Unconditionally close a response.
* <p>
* Example Code:
*
* <pre>
* HttpResponse httpResponse = null;
* try {
* httpResponse = httpClient.execute(httpGet);
* } catch (Exception e) {
* // error handling
* } finally {
* HttpClientUtils.closeQuietly(httpResponse);
* }
* </pre>
*
* @param response
* the HttpResponse to release resources, may be null or already
* closed.
*
* @since 4.2
*/
public static void closeQuietly(final HttpResponse response) {
if (response != null) {
final HttpEntity entity = response.getEntity();
if (entity != null) {
try {
EntityUtils.consume(entity);
} catch (final IOException ex) {
}
}
}
}
/**
* Unconditionally close a response.
* <p>
* Example Code:
*
* <pre>
* HttpResponse httpResponse = null;
* try {
* httpResponse = httpClient.execute(httpGet);
* } catch (Exception e) {
* // error handling
* } finally {
* HttpClientUtils.closeQuietly(httpResponse);
* }
* </pre>
*
* @param response
* the HttpResponse to release resources, may be null or already
* closed.
*
* @since 4.3
*/
public static void closeQuietly(final CloseableHttpResponse response) {
if (response != null) {
try {
try {
EntityUtils.consume(response.getEntity());
} finally {
response.close();
}
} catch (final IOException ignore) {
}
}
}
/**
* Unconditionally close a httpClient. Shuts down the underlying connection
* manager and releases the resources.
* <p>
* Example Code:
*
* <pre>
* HttpClient httpClient = HttpClients.createDefault();
* try {
* httpClient.execute(request);
* } catch (Exception e) {
* // error handling
* } finally {
* HttpClientUtils.closeQuietly(httpClient);
* }
* </pre>
*
* @param httpClient
* the HttpClient to close, may be null or already closed.
* @since 4.2
*/
public static void closeQuietly(final HttpClient httpClient) {
if (httpClient != null) {
if (httpClient instanceof Closeable) {
try {
((Closeable) httpClient).close();
} catch (final IOException ignore) {
}
}
}
}
}

View file

@ -0,0 +1,42 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
/**
* Abstraction of international domain name (IDN) conversion.
*
* @since 4.0
*/
public interface Idn {
/**
* Converts a name from its punycode representation to Unicode.
* The name may be a single hostname or a dot-separated qualified domain name.
* @param punycode the Punycode representation
* @return the Unicode domain name
*/
String toUnicode(String punycode);
}

View file

@ -0,0 +1,71 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import ch.boye.httpclientandroidlib.annotation.Immutable;
/**
* Uses the java.net.IDN class through reflection.
*
* @since 4.0
*/
@Immutable
public class JdkIdn implements Idn {
private final Method toUnicode;
/**
*
* @throws ClassNotFoundException if java.net.IDN is not available
*/
public JdkIdn() throws ClassNotFoundException {
final Class<?> clazz = Class.forName("java.net.IDN");
try {
toUnicode = clazz.getMethod("toUnicode", String.class);
} catch (final SecurityException e) {
// doesn't happen
throw new IllegalStateException(e.getMessage(), e);
} catch (final NoSuchMethodException e) {
// doesn't happen
throw new IllegalStateException(e.getMessage(), e);
}
}
public String toUnicode(final String punycode) {
try {
return (String) toUnicode.invoke(null, punycode);
} catch (final IllegalAccessException e) {
throw new IllegalStateException(e.getMessage(), e);
} catch (final InvocationTargetException e) {
final Throwable t = e.getCause();
throw new RuntimeException(t.getMessage(), t);
}
}
}

View file

@ -0,0 +1,54 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import ch.boye.httpclientandroidlib.annotation.Immutable;
/**
* Facade that provides conversion between Unicode and Punycode domain names.
* It will use an appropriate implementation.
*
* @since 4.0
*/
@Immutable
public class Punycode {
private static final Idn impl;
static {
Idn _impl;
try {
_impl = new JdkIdn();
} catch (final Exception e) {
_impl = new Rfc3492Idn();
}
impl = _impl;
}
public static String toUnicode(final String punycode) {
return impl.toUnicode(punycode);
}
}

View file

@ -0,0 +1,141 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.util.StringTokenizer;
import ch.boye.httpclientandroidlib.annotation.Immutable;
/**
* Implementation from pseudo code in RFC 3492.
*
* @since 4.0
*/
@Immutable
public class Rfc3492Idn implements Idn {
private static final int base = 36;
private static final int tmin = 1;
private static final int tmax = 26;
private static final int skew = 38;
private static final int damp = 700;
private static final int initial_bias = 72;
private static final int initial_n = 128;
private static final char delimiter = '-';
private static final String ACE_PREFIX = "xn--";
private int adapt(final int delta, final int numpoints, final boolean firsttime) {
int d = delta;
if (firsttime) {
d = d / damp;
} else {
d = d / 2;
}
d = d + (d / numpoints);
int k = 0;
while (d > ((base - tmin) * tmax) / 2) {
d = d / (base - tmin);
k = k + base;
}
return k + (((base - tmin + 1) * d) / (d + skew));
}
private int digit(final char c) {
if ((c >= 'A') && (c <= 'Z')) {
return (c - 'A');
}
if ((c >= 'a') && (c <= 'z')) {
return (c - 'a');
}
if ((c >= '0') && (c <= '9')) {
return (c - '0') + 26;
}
throw new IllegalArgumentException("illegal digit: "+ c);
}
public String toUnicode(final String punycode) {
final StringBuilder unicode = new StringBuilder(punycode.length());
final StringTokenizer tok = new StringTokenizer(punycode, ".");
while (tok.hasMoreTokens()) {
String t = tok.nextToken();
if (unicode.length() > 0) {
unicode.append('.');
}
if (t.startsWith(ACE_PREFIX)) {
t = decode(t.substring(4));
}
unicode.append(t);
}
return unicode.toString();
}
protected String decode(final String s) {
String input = s;
int n = initial_n;
int i = 0;
int bias = initial_bias;
final StringBuilder output = new StringBuilder(input.length());
final int lastdelim = input.lastIndexOf(delimiter);
if (lastdelim != -1) {
output.append(input.subSequence(0, lastdelim));
input = input.substring(lastdelim + 1);
}
while (input.length() > 0) {
final int oldi = i;
int w = 1;
for (int k = base;; k += base) {
if (input.length() == 0) {
break;
}
final char c = input.charAt(0);
input = input.substring(1);
final int digit = digit(c);
i = i + digit * w; // FIXME fail on overflow
final int t;
if (k <= bias + tmin) {
t = tmin;
} else if (k >= bias + tmax) {
t = tmax;
} else {
t = k - bias;
}
if (digit < t) {
break;
}
w = w * (base - t); // FIXME fail on overflow
}
bias = adapt(i - oldi, output.length() + 1, (oldi == 0));
n = n + i / (output.length() + 1); // FIXME fail on overflow
i = i % (output.length() + 1);
// {if n is a basic code point then fail}
output.insert(i, (char) n);
i++;
}
return output.toString();
}
}

View file

@ -0,0 +1,490 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import ch.boye.httpclientandroidlib.Consts;
import ch.boye.httpclientandroidlib.NameValuePair;
import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
import ch.boye.httpclientandroidlib.conn.util.InetAddressUtils;
import ch.boye.httpclientandroidlib.message.BasicNameValuePair;
/**
* Builder for {@link URI} instances.
*
* @since 4.2
*/
@NotThreadSafe
public class URIBuilder {
private String scheme;
private String encodedSchemeSpecificPart;
private String encodedAuthority;
private String userInfo;
private String encodedUserInfo;
private String host;
private int port;
private String path;
private String encodedPath;
private String encodedQuery;
private List<NameValuePair> queryParams;
private String query;
private String fragment;
private String encodedFragment;
/**
* Constructs an empty instance.
*/
public URIBuilder() {
super();
this.port = -1;
}
/**
* Construct an instance from the string which must be a valid URI.
*
* @param string a valid URI in string form
* @throws URISyntaxException if the input is not a valid URI
*/
public URIBuilder(final String string) throws URISyntaxException {
super();
digestURI(new URI(string));
}
/**
* Construct an instance from the provided URI.
* @param uri
*/
public URIBuilder(final URI uri) {
super();
digestURI(uri);
}
private List <NameValuePair> parseQuery(final String query, final Charset charset) {
if (query != null && query.length() > 0) {
return URLEncodedUtils.parse(query, charset);
}
return null;
}
/**
* Builds a {@link URI} instance.
*/
public URI build() throws URISyntaxException {
return new URI(buildString());
}
private String buildString() {
final StringBuilder sb = new StringBuilder();
if (this.scheme != null) {
sb.append(this.scheme).append(':');
}
if (this.encodedSchemeSpecificPart != null) {
sb.append(this.encodedSchemeSpecificPart);
} else {
if (this.encodedAuthority != null) {
sb.append("//").append(this.encodedAuthority);
} else if (this.host != null) {
sb.append("//");
if (this.encodedUserInfo != null) {
sb.append(this.encodedUserInfo).append("@");
} else if (this.userInfo != null) {
sb.append(encodeUserInfo(this.userInfo)).append("@");
}
if (InetAddressUtils.isIPv6Address(this.host)) {
sb.append("[").append(this.host).append("]");
} else {
sb.append(this.host);
}
if (this.port >= 0) {
sb.append(":").append(this.port);
}
}
if (this.encodedPath != null) {
sb.append(normalizePath(this.encodedPath));
} else if (this.path != null) {
sb.append(encodePath(normalizePath(this.path)));
}
if (this.encodedQuery != null) {
sb.append("?").append(this.encodedQuery);
} else if (this.queryParams != null) {
sb.append("?").append(encodeUrlForm(this.queryParams));
} else if (this.query != null) {
sb.append("?").append(encodeUric(this.query));
}
}
if (this.encodedFragment != null) {
sb.append("#").append(this.encodedFragment);
} else if (this.fragment != null) {
sb.append("#").append(encodeUric(this.fragment));
}
return sb.toString();
}
private void digestURI(final URI uri) {
this.scheme = uri.getScheme();
this.encodedSchemeSpecificPart = uri.getRawSchemeSpecificPart();
this.encodedAuthority = uri.getRawAuthority();
this.host = uri.getHost();
this.port = uri.getPort();
this.encodedUserInfo = uri.getRawUserInfo();
this.userInfo = uri.getUserInfo();
this.encodedPath = uri.getRawPath();
this.path = uri.getPath();
this.encodedQuery = uri.getRawQuery();
this.queryParams = parseQuery(uri.getRawQuery(), Consts.UTF_8);
this.encodedFragment = uri.getRawFragment();
this.fragment = uri.getFragment();
}
private String encodeUserInfo(final String userInfo) {
return URLEncodedUtils.encUserInfo(userInfo, Consts.UTF_8);
}
private String encodePath(final String path) {
return URLEncodedUtils.encPath(path, Consts.UTF_8);
}
private String encodeUrlForm(final List<NameValuePair> params) {
return URLEncodedUtils.format(params, Consts.UTF_8);
}
private String encodeUric(final String fragment) {
return URLEncodedUtils.encUric(fragment, Consts.UTF_8);
}
/**
* Sets URI scheme.
*/
public URIBuilder setScheme(final String scheme) {
this.scheme = scheme;
return this;
}
/**
* Sets URI user info. The value is expected to be unescaped and may contain non ASCII
* characters.
*/
public URIBuilder setUserInfo(final String userInfo) {
this.userInfo = userInfo;
this.encodedSchemeSpecificPart = null;
this.encodedAuthority = null;
this.encodedUserInfo = null;
return this;
}
/**
* Sets URI user info as a combination of username and password. These values are expected to
* be unescaped and may contain non ASCII characters.
*/
public URIBuilder setUserInfo(final String username, final String password) {
return setUserInfo(username + ':' + password);
}
/**
* Sets URI host.
*/
public URIBuilder setHost(final String host) {
this.host = host;
this.encodedSchemeSpecificPart = null;
this.encodedAuthority = null;
return this;
}
/**
* Sets URI port.
*/
public URIBuilder setPort(final int port) {
this.port = port < 0 ? -1 : port;
this.encodedSchemeSpecificPart = null;
this.encodedAuthority = null;
return this;
}
/**
* Sets URI path. The value is expected to be unescaped and may contain non ASCII characters.
*/
public URIBuilder setPath(final String path) {
this.path = path;
this.encodedSchemeSpecificPart = null;
this.encodedPath = null;
return this;
}
/**
* Removes URI query.
*/
public URIBuilder removeQuery() {
this.queryParams = null;
this.query = null;
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
return this;
}
/**
* Sets URI query.
* <p>
* The value is expected to be encoded form data.
*
* @deprecated (4.3) use {@link #setParameters(List)} or {@link #setParameters(NameValuePair...)}
*
* @see URLEncodedUtils#parse
*/
@Deprecated
public URIBuilder setQuery(final String query) {
this.queryParams = parseQuery(query, Consts.UTF_8);
this.query = null;
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
return this;
}
/**
* Sets URI query parameters. The parameter name / values are expected to be unescaped
* and may contain non ASCII characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove custom query if present.
*
* @since 4.3
*/
public URIBuilder setParameters(final List <NameValuePair> nvps) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
} else {
this.queryParams.clear();
}
this.queryParams.addAll(nvps);
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.query = null;
return this;
}
/**
* Adds URI query parameters. The parameter name / values are expected to be unescaped
* and may contain non ASCII characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove custom query if present.
*
* @since 4.3
*/
public URIBuilder addParameters(final List <NameValuePair> nvps) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
}
this.queryParams.addAll(nvps);
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.query = null;
return this;
}
/**
* Sets URI query parameters. The parameter name / values are expected to be unescaped
* and may contain non ASCII characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove custom query if present.
*
* @since 4.3
*/
public URIBuilder setParameters(final NameValuePair... nvps) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
} else {
this.queryParams.clear();
}
for (final NameValuePair nvp: nvps) {
this.queryParams.add(nvp);
}
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.query = null;
return this;
}
/**
* Adds parameter to URI query. The parameter name and value are expected to be unescaped
* and may contain non ASCII characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove custom query if present.
*/
public URIBuilder addParameter(final String param, final String value) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
}
this.queryParams.add(new BasicNameValuePair(param, value));
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.query = null;
return this;
}
/**
* Sets parameter of URI query overriding existing value if set. The parameter name and value
* are expected to be unescaped and may contain non ASCII characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove custom query if present.
*/
public URIBuilder setParameter(final String param, final String value) {
if (this.queryParams == null) {
this.queryParams = new ArrayList<NameValuePair>();
}
if (!this.queryParams.isEmpty()) {
for (final Iterator<NameValuePair> it = this.queryParams.iterator(); it.hasNext(); ) {
final NameValuePair nvp = it.next();
if (nvp.getName().equals(param)) {
it.remove();
}
}
}
this.queryParams.add(new BasicNameValuePair(param, value));
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.query = null;
return this;
}
/**
* Clears URI query parameters.
*
* @since 4.3
*/
public URIBuilder clearParameters() {
this.queryParams = null;
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
return this;
}
/**
* Sets custom URI query. The value is expected to be unescaped and may contain non ASCII
* characters.
* <p/>
* Please note query parameters and custom query component are mutually exclusive. This method
* will remove query parameters if present.
*
* @since 4.3
*/
public URIBuilder setCustomQuery(final String query) {
this.query = query;
this.encodedQuery = null;
this.encodedSchemeSpecificPart = null;
this.queryParams = null;
return this;
}
/**
* Sets URI fragment. The value is expected to be unescaped and may contain non ASCII
* characters.
*/
public URIBuilder setFragment(final String fragment) {
this.fragment = fragment;
this.encodedFragment = null;
return this;
}
/**
* @since 4.3
*/
public boolean isAbsolute() {
return this.scheme != null;
}
/**
* @since 4.3
*/
public boolean isOpaque() {
return this.path == null;
}
public String getScheme() {
return this.scheme;
}
public String getUserInfo() {
return this.userInfo;
}
public String getHost() {
return this.host;
}
public int getPort() {
return this.port;
}
public String getPath() {
return this.path;
}
public List<NameValuePair> getQueryParams() {
if (this.queryParams != null) {
return new ArrayList<NameValuePair>(this.queryParams);
} else {
return new ArrayList<NameValuePair>();
}
}
public String getFragment() {
return this.fragment;
}
@Override
public String toString() {
return buildString();
}
private static String normalizePath(final String path) {
String s = path;
if (s == null) {
return null;
}
int n = 0;
for (; n < s.length(); n++) {
if (s.charAt(n) != '/') {
break;
}
}
if (n > 1) {
s = s.substring(n - 1);
}
return s;
}
}

View file

@ -0,0 +1,428 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Locale;
import java.util.Stack;
import ch.boye.httpclientandroidlib.HttpHost;
import ch.boye.httpclientandroidlib.annotation.Immutable;
import ch.boye.httpclientandroidlib.util.Args;
import ch.boye.httpclientandroidlib.util.TextUtils;
/**
* A collection of utilities for {@link URI URIs}, to workaround
* bugs within the class or for ease-of-use features.
*
* @since 4.0
*/
@Immutable
public class URIUtils {
/**
* Constructs a {@link URI} using all the parameters. This should be
* used instead of
* {@link URI#URI(String, String, String, int, String, String, String)}
* or any of the other URI multi-argument URI constructors.
*
* @param scheme
* Scheme name
* @param host
* Host name
* @param port
* Port number
* @param path
* Path
* @param query
* Query
* @param fragment
* Fragment
*
* @throws URISyntaxException
* If both a scheme and a path are given but the path is
* relative, if the URI string constructed from the given
* components violates RFC&nbsp;2396, or if the authority
* component of the string is present but cannot be parsed
* as a server-based authority
*
* @deprecated (4.2) use {@link URIBuilder}.
*/
@Deprecated
public static URI createURI(
final String scheme,
final String host,
final int port,
final String path,
final String query,
final String fragment) throws URISyntaxException {
final StringBuilder buffer = new StringBuilder();
if (host != null) {
if (scheme != null) {
buffer.append(scheme);
buffer.append("://");
}
buffer.append(host);
if (port > 0) {
buffer.append(':');
buffer.append(port);
}
}
if (path == null || !path.startsWith("/")) {
buffer.append('/');
}
if (path != null) {
buffer.append(path);
}
if (query != null) {
buffer.append('?');
buffer.append(query);
}
if (fragment != null) {
buffer.append('#');
buffer.append(fragment);
}
return new URI(buffer.toString());
}
/**
* A convenience method for creating a new {@link URI} whose scheme, host
* and port are taken from the target host, but whose path, query and
* fragment are taken from the existing URI. The fragment is only used if
* dropFragment is false. The path is set to "/" if not explicitly specified.
*
* @param uri
* Contains the path, query and fragment to use.
* @param target
* Contains the scheme, host and port to use.
* @param dropFragment
* True if the fragment should not be copied.
*
* @throws URISyntaxException
* If the resulting URI is invalid.
*/
public static URI rewriteURI(
final URI uri,
final HttpHost target,
final boolean dropFragment) throws URISyntaxException {
Args.notNull(uri, "URI");
if (uri.isOpaque()) {
return uri;
}
final URIBuilder uribuilder = new URIBuilder(uri);
if (target != null) {
uribuilder.setScheme(target.getSchemeName());
uribuilder.setHost(target.getHostName());
uribuilder.setPort(target.getPort());
} else {
uribuilder.setScheme(null);
uribuilder.setHost(null);
uribuilder.setPort(-1);
}
if (dropFragment) {
uribuilder.setFragment(null);
}
if (TextUtils.isEmpty(uribuilder.getPath())) {
uribuilder.setPath("/");
}
return uribuilder.build();
}
/**
* A convenience method for
* {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the
* fragment.
*/
public static URI rewriteURI(
final URI uri,
final HttpHost target) throws URISyntaxException {
return rewriteURI(uri, target, false);
}
/**
* A convenience method that creates a new {@link URI} whose scheme, host, port, path,
* query are taken from the existing URI, dropping any fragment or user-information.
* The path is set to "/" if not explicitly specified. The existing URI is returned
* unmodified if it has no fragment or user-information and has a path.
*
* @param uri
* original URI.
* @throws URISyntaxException
* If the resulting URI is invalid.
*/
public static URI rewriteURI(final URI uri) throws URISyntaxException {
Args.notNull(uri, "URI");
if (uri.isOpaque()) {
return uri;
}
final URIBuilder uribuilder = new URIBuilder(uri);
if (uribuilder.getUserInfo() != null) {
uribuilder.setUserInfo(null);
}
if (TextUtils.isEmpty(uribuilder.getPath())) {
uribuilder.setPath("/");
}
if (uribuilder.getHost() != null) {
uribuilder.setHost(uribuilder.getHost().toLowerCase(Locale.ENGLISH));
}
uribuilder.setFragment(null);
return uribuilder.build();
}
/**
* Resolves a URI reference against a base URI. Work-around for bug in
* java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>)
*
* @param baseURI the base URI
* @param reference the URI reference
* @return the resulting URI
*/
public static URI resolve(final URI baseURI, final String reference) {
return URIUtils.resolve(baseURI, URI.create(reference));
}
/**
* Resolves a URI reference against a base URI. Work-around for bugs in
* java.net.URI (e.g. <http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>)
*
* @param baseURI the base URI
* @param reference the URI reference
* @return the resulting URI
*/
public static URI resolve(final URI baseURI, final URI reference){
Args.notNull(baseURI, "Base URI");
Args.notNull(reference, "Reference URI");
URI ref = reference;
final String s = ref.toString();
if (s.startsWith("?")) {
return resolveReferenceStartingWithQueryString(baseURI, ref);
}
final boolean emptyReference = s.length() == 0;
if (emptyReference) {
ref = URI.create("#");
}
URI resolved = baseURI.resolve(ref);
if (emptyReference) {
final String resolvedString = resolved.toString();
resolved = URI.create(resolvedString.substring(0,
resolvedString.indexOf('#')));
}
return normalizeSyntax(resolved);
}
/**
* Resolves a reference starting with a query string.
*
* @param baseURI the base URI
* @param reference the URI reference starting with a query string
* @return the resulting URI
*/
private static URI resolveReferenceStartingWithQueryString(
final URI baseURI, final URI reference) {
String baseUri = baseURI.toString();
baseUri = baseUri.indexOf('?') > -1 ?
baseUri.substring(0, baseUri.indexOf('?')) : baseUri;
return URI.create(baseUri + reference.toString());
}
/**
* Removes dot segments according to RFC 3986, section 5.2.4 and
* Syntax-Based Normalization according to RFC 3986, section 6.2.2.
*
* @param uri the original URI
* @return the URI without dot segments
*/
private static URI normalizeSyntax(final URI uri) {
if (uri.isOpaque() || uri.getAuthority() == null) {
// opaque and file: URIs
return uri;
}
Args.check(uri.isAbsolute(), "Base URI must be absolute");
final String path = uri.getPath() == null ? "" : uri.getPath();
final String[] inputSegments = path.split("/");
final Stack<String> outputSegments = new Stack<String>();
for (final String inputSegment : inputSegments) {
if ((inputSegment.length() == 0)
|| (".".equals(inputSegment))) {
// Do nothing
} else if ("..".equals(inputSegment)) {
if (!outputSegments.isEmpty()) {
outputSegments.pop();
}
} else {
outputSegments.push(inputSegment);
}
}
final StringBuilder outputBuffer = new StringBuilder();
for (final String outputSegment : outputSegments) {
outputBuffer.append('/').append(outputSegment);
}
if (path.lastIndexOf('/') == path.length() - 1) {
// path.endsWith("/") || path.equals("")
outputBuffer.append('/');
}
try {
final String scheme = uri.getScheme().toLowerCase(Locale.ENGLISH);
final String auth = uri.getAuthority().toLowerCase(Locale.ENGLISH);
final URI ref = new URI(scheme, auth, outputBuffer.toString(),
null, null);
if (uri.getQuery() == null && uri.getFragment() == null) {
return ref;
}
final StringBuilder normalized = new StringBuilder(
ref.toASCIIString());
if (uri.getQuery() != null) {
// query string passed through unchanged
normalized.append('?').append(uri.getRawQuery());
}
if (uri.getFragment() != null) {
// fragment passed through unchanged
normalized.append('#').append(uri.getRawFragment());
}
return URI.create(normalized.toString());
} catch (final URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Extracts target host from the given {@link URI}.
*
* @param uri
* @return the target host if the URI is absolute or <code>null</null> if the URI is
* relative or does not contain a valid host name.
*
* @since 4.1
*/
public static HttpHost extractHost(final URI uri) {
if (uri == null) {
return null;
}
HttpHost target = null;
if (uri.isAbsolute()) {
int port = uri.getPort(); // may be overridden later
String host = uri.getHost();
if (host == null) { // normal parse failed; let's do it ourselves
// authority does not seem to care about the valid character-set for host names
host = uri.getAuthority();
if (host != null) {
// Strip off any leading user credentials
final int at = host.indexOf('@');
if (at >= 0) {
if (host.length() > at+1 ) {
host = host.substring(at+1);
} else {
host = null; // @ on its own
}
}
// Extract the port suffix, if present
if (host != null) {
final int colon = host.indexOf(':');
if (colon >= 0) {
final int pos = colon + 1;
int len = 0;
for (int i = pos; i < host.length(); i++) {
if (Character.isDigit(host.charAt(i))) {
len++;
} else {
break;
}
}
if (len > 0) {
try {
port = Integer.parseInt(host.substring(pos, pos + len));
} catch (final NumberFormatException ex) {
}
}
host = host.substring(0, colon);
}
}
}
}
final String scheme = uri.getScheme();
if (!TextUtils.isBlank(host)) {
target = new HttpHost(host, port, scheme);
}
}
return target;
}
/**
* Derives the interpreted (absolute) URI that was used to generate the last
* request. This is done by extracting the request-uri and target origin for
* the last request and scanning all the redirect locations for the last
* fragment identifier, then combining the result into a {@link URI}.
*
* @param originalURI
* original request before any redirects
* @param target
* if the last URI is relative, it is resolved against this target,
* or <code>null</code> if not available.
* @param redirects
* collection of redirect locations since the original request
* or <code>null</code> if not available.
* @return interpreted (absolute) URI
*/
public static URI resolve(
final URI originalURI,
final HttpHost target,
final List<URI> redirects) throws URISyntaxException {
Args.notNull(originalURI, "Request URI");
final URIBuilder uribuilder;
if (redirects == null || redirects.isEmpty()) {
uribuilder = new URIBuilder(originalURI);
} else {
uribuilder = new URIBuilder(redirects.get(redirects.size() - 1));
String frag = uribuilder.getFragment();
// read interpreted fragment identifier from redirect locations
for (int i = redirects.size() - 1; frag == null && i >= 0; i--) {
frag = redirects.get(i).getFragment();
}
uribuilder.setFragment(frag);
}
// read interpreted fragment identifier from original request
if (uribuilder.getFragment() == null) {
uribuilder.setFragment(originalURI.getFragment());
}
// last target origin
if (target != null && !uribuilder.isAbsolute()) {
uribuilder.setScheme(target.getSchemeName());
uribuilder.setHost(target.getHostName());
uribuilder.setPort(target.getPort());
}
return uribuilder.build();
}
/**
* This class should not be instantiated.
*/
private URIUtils() {
}
}

View file

@ -0,0 +1,628 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package ch.boye.httpclientandroidlib.client.utils;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
import ch.boye.httpclientandroidlib.Consts;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HeaderElement;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.NameValuePair;
import ch.boye.httpclientandroidlib.annotation.Immutable;
import ch.boye.httpclientandroidlib.entity.ContentType;
import ch.boye.httpclientandroidlib.message.BasicHeaderValueParser;
import ch.boye.httpclientandroidlib.message.BasicNameValuePair;
import ch.boye.httpclientandroidlib.message.ParserCursor;
import ch.boye.httpclientandroidlib.protocol.HTTP;
import ch.boye.httpclientandroidlib.util.CharArrayBuffer;
import ch.boye.httpclientandroidlib.util.EntityUtils;
/**
* A collection of utilities for encoding URLs.
*
* @since 4.0
*/
@Immutable
public class URLEncodedUtils {
/**
* The default HTML form content type.
*/
public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
private static final char QP_SEP_A = '&';
private static final char QP_SEP_S = ';';
private static final String NAME_VALUE_SEPARATOR = "=";
/**
* Returns a list of {@link NameValuePair NameValuePairs} as built from the URI's query portion. For example, a URI
* of http://example.org/path/to/file?a=1&b=2&c=3 would return a list of three NameValuePairs, one for a=1, one for
* b=2, and one for c=3. By convention, {@code '&'} and {@code ';'} are accepted as parameter separators.
* <p>
* This is typically useful while parsing an HTTP PUT.
*
* This API is currently only used for testing.
*
* @param uri
* URI to parse
* @param charset
* Charset name to use while parsing the query
* @return a list of {@link NameValuePair} as built from the URI's query portion.
*/
public static List <NameValuePair> parse(final URI uri, final String charset) {
final String query = uri.getRawQuery();
if (query != null && query.length() > 0) {
final List<NameValuePair> result = new ArrayList<NameValuePair>();
final Scanner scanner = new Scanner(query);
parse(result, scanner, QP_SEP_PATTERN, charset);
return result;
}
return Collections.emptyList();
}
/**
* Returns a list of {@link NameValuePair NameValuePairs} as parsed from an {@link HttpEntity}. The encoding is
* taken from the entity's Content-Encoding header.
* <p>
* This is typically used while parsing an HTTP POST.
*
* @param entity
* The entity to parse
* @return a list of {@link NameValuePair} as built from the URI's query portion.
* @throws IOException
* If there was an exception getting the entity's data.
*/
public static List <NameValuePair> parse(
final HttpEntity entity) throws IOException {
final ContentType contentType = ContentType.get(entity);
if (contentType != null && contentType.getMimeType().equalsIgnoreCase(CONTENT_TYPE)) {
final String content = EntityUtils.toString(entity, Consts.ASCII);
if (content != null && content.length() > 0) {
Charset charset = contentType.getCharset();
if (charset == null) {
charset = HTTP.DEF_CONTENT_CHARSET;
}
return parse(content, charset, QP_SEPS);
}
}
return Collections.emptyList();
}
/**
* Returns true if the entity's Content-Type header is
* <code>application/x-www-form-urlencoded</code>.
*/
public static boolean isEncoded(final HttpEntity entity) {
final Header h = entity.getContentType();
if (h != null) {
final HeaderElement[] elems = h.getElements();
if (elems.length > 0) {
final String contentType = elems[0].getName();
return contentType.equalsIgnoreCase(CONTENT_TYPE);
}
}
return false;
}
/**
* Adds all parameters within the Scanner to the list of <code>parameters</code>, as encoded by
* <code>encoding</code>. For example, a scanner containing the string <code>a=1&b=2&c=3</code> would add the
* {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the list of parameters. By convention, {@code '&'} and
* {@code ';'} are accepted as parameter separators.
*
* @param parameters
* List to add parameters to.
* @param scanner
* Input that contains the parameters to parse.
* @param charset
* Encoding to use when decoding the parameters.
*/
public static void parse(
final List <NameValuePair> parameters,
final Scanner scanner,
final String charset) {
parse(parameters, scanner, QP_SEP_PATTERN, charset);
}
/**
* Adds all parameters within the Scanner to the list of
* <code>parameters</code>, as encoded by <code>encoding</code>. For
* example, a scanner containing the string <code>a=1&b=2&c=3</code> would
* add the {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the
* list of parameters.
*
* @param parameters
* List to add parameters to.
* @param scanner
* Input that contains the parameters to parse.
* @param parameterSepartorPattern
* The Pattern string for parameter separators, by convention {@code "[&;]"}
* @param charset
* Encoding to use when decoding the parameters.
*/
public static void parse(
final List <NameValuePair> parameters,
final Scanner scanner,
final String parameterSepartorPattern,
final String charset) {
scanner.useDelimiter(parameterSepartorPattern);
while (scanner.hasNext()) {
String name = null;
String value = null;
final String token = scanner.next();
final int i = token.indexOf(NAME_VALUE_SEPARATOR);
if (i != -1) {
name = decodeFormFields(token.substring(0, i).trim(), charset);
value = decodeFormFields(token.substring(i + 1).trim(), charset);
} else {
name = decodeFormFields(token.trim(), charset);
}
parameters.add(new BasicNameValuePair(name, value));
}
}
/**
* Query parameter separators.
*/
private static final char[] QP_SEPS = new char[] { QP_SEP_A, QP_SEP_S };
/**
* Query parameter separator pattern.
*/
private static final String QP_SEP_PATTERN = "[" + new String(QP_SEPS) + "]";
/**
* Returns a list of {@link NameValuePair NameValuePairs} as parsed from the given string using the given character
* encoding. By convention, {@code '&'} and {@code ';'} are accepted as parameter separators.
*
* @param s
* text to parse.
* @param charset
* Encoding to use when decoding the parameters.
* @return a list of {@link NameValuePair} as built from the URI's query portion.
*
* @since 4.2
*/
public static List<NameValuePair> parse(final String s, final Charset charset) {
return parse(s, charset, QP_SEPS);
}
/**
* Returns a list of {@link NameValuePair NameValuePairs} as parsed from the given string using the given character
* encoding.
*
* @param s
* text to parse.
* @param charset
* Encoding to use when decoding the parameters.
* @param parameterSeparator
* The characters used to separate parameters, by convention, {@code '&'} and {@code ';'}.
* @return a list of {@link NameValuePair} as built from the URI's query portion.
*
* @since 4.3
*/
public static List<NameValuePair> parse(final String s, final Charset charset, final char... parameterSeparator) {
if (s == null) {
return Collections.emptyList();
}
final BasicHeaderValueParser parser = BasicHeaderValueParser.INSTANCE;
final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
buffer.append(s);
final ParserCursor cursor = new ParserCursor(0, buffer.length());
final List<NameValuePair> list = new ArrayList<NameValuePair>();
while (!cursor.atEnd()) {
final NameValuePair nvp = parser.parseNameValuePair(buffer, cursor, parameterSeparator);
if (nvp.getName().length() > 0) {
list.add(new BasicNameValuePair(
decodeFormFields(nvp.getName(), charset),
decodeFormFields(nvp.getValue(), charset)));
}
}
return list;
}
/**
* Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
* list of parameters in an HTTP PUT or HTTP POST.
*
* @param parameters The parameters to include.
* @param charset The encoding to use.
* @return An {@code application/x-www-form-urlencoded} string
*/
public static String format(
final List <? extends NameValuePair> parameters,
final String charset) {
return format(parameters, QP_SEP_A, charset);
}
/**
* Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
* list of parameters in an HTTP PUT or HTTP POST.
*
* @param parameters The parameters to include.
* @param parameterSeparator The parameter separator, by convention, {@code '&'} or {@code ';'}.
* @param charset The encoding to use.
* @return An {@code application/x-www-form-urlencoded} string
*
* @since 4.3
*/
public static String format(
final List <? extends NameValuePair> parameters,
final char parameterSeparator,
final String charset) {
final StringBuilder result = new StringBuilder();
for (final NameValuePair parameter : parameters) {
final String encodedName = encodeFormFields(parameter.getName(), charset);
final String encodedValue = encodeFormFields(parameter.getValue(), charset);
if (result.length() > 0) {
result.append(parameterSeparator);
}
result.append(encodedName);
if (encodedValue != null) {
result.append(NAME_VALUE_SEPARATOR);
result.append(encodedValue);
}
}
return result.toString();
}
/**
* Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
* list of parameters in an HTTP PUT or HTTP POST.
*
* @param parameters The parameters to include.
* @param charset The encoding to use.
* @return An {@code application/x-www-form-urlencoded} string
*
* @since 4.2
*/
public static String format(
final Iterable<? extends NameValuePair> parameters,
final Charset charset) {
return format(parameters, QP_SEP_A, charset);
}
/**
* Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
* list of parameters in an HTTP PUT or HTTP POST.
*
* @param parameters The parameters to include.
* @param parameterSeparator The parameter separator, by convention, {@code '&'} or {@code ';'}.
* @param charset The encoding to use.
* @return An {@code application/x-www-form-urlencoded} string
*
* @since 4.3
*/
public static String format(
final Iterable<? extends NameValuePair> parameters,
final char parameterSeparator,
final Charset charset) {
final StringBuilder result = new StringBuilder();
for (final NameValuePair parameter : parameters) {
final String encodedName = encodeFormFields(parameter.getName(), charset);
final String encodedValue = encodeFormFields(parameter.getValue(), charset);
if (result.length() > 0) {
result.append(parameterSeparator);
}
result.append(encodedName);
if (encodedValue != null) {
result.append(NAME_VALUE_SEPARATOR);
result.append(encodedValue);
}
}
return result.toString();
}
/**
* Unreserved characters, i.e. alphanumeric, plus: {@code _ - ! . ~ ' ( ) *}
* <p>
* This list is the same as the {@code unreserved} list in
* <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
*/
private static final BitSet UNRESERVED = new BitSet(256);
/**
* Punctuation characters: , ; : $ & + =
* <p>
* These are the additional characters allowed by userinfo.
*/
private static final BitSet PUNCT = new BitSet(256);
/** Characters which are safe to use in userinfo,
* i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */
private static final BitSet USERINFO = new BitSet(256);
/** Characters which are safe to use in a path,
* i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */
private static final BitSet PATHSAFE = new BitSet(256);
/** Characters which are safe to use in a query or a fragment,
* i.e. {@link #RESERVED} plus {@link #UNRESERVED} */
private static final BitSet URIC = new BitSet(256);
/**
* Reserved characters, i.e. {@code ;/?:@&=+$,[]}
* <p>
* This list is the same as the {@code reserved} list in
* <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
* as augmented by
* <a href="http://www.ietf.org/rfc/rfc2732.txt">RFC 2732</a>
*/
private static final BitSet RESERVED = new BitSet(256);
/**
* Safe characters for x-www-form-urlencoded data, as per java.net.URLEncoder and browser behaviour,
* i.e. alphanumeric plus {@code "-", "_", ".", "*"}
*/
private static final BitSet URLENCODER = new BitSet(256);
static {
// unreserved chars
// alpha characters
for (int i = 'a'; i <= 'z'; i++) {
UNRESERVED.set(i);
}
for (int i = 'A'; i <= 'Z'; i++) {
UNRESERVED.set(i);
}
// numeric characters
for (int i = '0'; i <= '9'; i++) {
UNRESERVED.set(i);
}
UNRESERVED.set('_'); // these are the charactes of the "mark" list
UNRESERVED.set('-');
UNRESERVED.set('.');
UNRESERVED.set('*');
URLENCODER.or(UNRESERVED); // skip remaining unreserved characters
UNRESERVED.set('!');
UNRESERVED.set('~');
UNRESERVED.set('\'');
UNRESERVED.set('(');
UNRESERVED.set(')');
// punct chars
PUNCT.set(',');
PUNCT.set(';');
PUNCT.set(':');
PUNCT.set('$');
PUNCT.set('&');
PUNCT.set('+');
PUNCT.set('=');
// Safe for userinfo
USERINFO.or(UNRESERVED);
USERINFO.or(PUNCT);
// URL path safe
PATHSAFE.or(UNRESERVED);
PATHSAFE.set('/'); // segment separator
PATHSAFE.set(';'); // param separator
PATHSAFE.set(':'); // rest as per list in 2396, i.e. : @ & = + $ ,
PATHSAFE.set('@');
PATHSAFE.set('&');
PATHSAFE.set('=');
PATHSAFE.set('+');
PATHSAFE.set('$');
PATHSAFE.set(',');
RESERVED.set(';');
RESERVED.set('/');
RESERVED.set('?');
RESERVED.set(':');
RESERVED.set('@');
RESERVED.set('&');
RESERVED.set('=');
RESERVED.set('+');
RESERVED.set('$');
RESERVED.set(',');
RESERVED.set('['); // added by RFC 2732
RESERVED.set(']'); // added by RFC 2732
URIC.or(RESERVED);
URIC.or(UNRESERVED);
}
private static final int RADIX = 16;
private static String urlEncode(
final String content,
final Charset charset,
final BitSet safechars,
final boolean blankAsPlus) {
if (content == null) {
return null;
}
final StringBuilder buf = new StringBuilder();
final ByteBuffer bb = charset.encode(content);
while (bb.hasRemaining()) {
final int b = bb.get() & 0xff;
if (safechars.get(b)) {
buf.append((char) b);
} else if (blankAsPlus && b == ' ') {
buf.append('+');
} else {
buf.append("%");
final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
buf.append(hex1);
buf.append(hex2);
}
}
return buf.toString();
}
/**
* Decode/unescape a portion of a URL, to use with the query part ensure {@code plusAsBlank} is true.
*
* @param content the portion to decode
* @param charset the charset to use
* @param plusAsBlank if {@code true}, then convert '+' to space (e.g. for www-url-form-encoded content), otherwise leave as is.
* @return encoded string
*/
private static String urlDecode(
final String content,
final Charset charset,
final boolean plusAsBlank) {
if (content == null) {
return null;
}
final ByteBuffer bb = ByteBuffer.allocate(content.length());
final CharBuffer cb = CharBuffer.wrap(content);
while (cb.hasRemaining()) {
final char c = cb.get();
if (c == '%' && cb.remaining() >= 2) {
final char uc = cb.get();
final char lc = cb.get();
final int u = Character.digit(uc, 16);
final int l = Character.digit(lc, 16);
if (u != -1 && l != -1) {
bb.put((byte) ((u << 4) + l));
} else {
bb.put((byte) '%');
bb.put((byte) uc);
bb.put((byte) lc);
}
} else if (plusAsBlank && c == '+') {
bb.put((byte) ' ');
} else {
bb.put((byte) c);
}
}
bb.flip();
return charset.decode(bb).toString();
}
/**
* Decode/unescape www-url-form-encoded content.
*
* @param content the content to decode, will decode '+' as space
* @param charset the charset to use
* @return encoded string
*/
private static String decodeFormFields (final String content, final String charset) {
if (content == null) {
return null;
}
return urlDecode(content, charset != null ? Charset.forName(charset) : Consts.UTF_8, true);
}
/**
* Decode/unescape www-url-form-encoded content.
*
* @param content the content to decode, will decode '+' as space
* @param charset the charset to use
* @return encoded string
*/
private static String decodeFormFields (final String content, final Charset charset) {
if (content == null) {
return null;
}
return urlDecode(content, charset != null ? charset : Consts.UTF_8, true);
}
/**
* Encode/escape www-url-form-encoded content.
* <p>
* Uses the {@link #URLENCODER} set of characters, rather than
* the {@link #UNRSERVED} set; this is for compatibilty with previous
* releases, URLEncoder.encode() and most browsers.
*
* @param content the content to encode, will convert space to '+'
* @param charset the charset to use
* @return encoded string
*/
private static String encodeFormFields(final String content, final String charset) {
if (content == null) {
return null;
}
return urlEncode(content, charset != null ? Charset.forName(charset) : Consts.UTF_8, URLENCODER, true);
}
/**
* Encode/escape www-url-form-encoded content.
* <p>
* Uses the {@link #URLENCODER} set of characters, rather than
* the {@link #UNRSERVED} set; this is for compatibilty with previous
* releases, URLEncoder.encode() and most browsers.
*
* @param content the content to encode, will convert space to '+'
* @param charset the charset to use
* @return encoded string
*/
private static String encodeFormFields (final String content, final Charset charset) {
if (content == null) {
return null;
}
return urlEncode(content, charset != null ? charset : Consts.UTF_8, URLENCODER, true);
}
/**
* Encode a String using the {@link #USERINFO} set of characters.
* <p>
* Used by URIBuilder to encode the userinfo segment.
*
* @param content the string to encode, does not convert space to '+'
* @param charset the charset to use
* @return the encoded string
*/
static String encUserInfo(final String content, final Charset charset) {
return urlEncode(content, charset, USERINFO, false);
}
/**
* Encode a String using the {@link #URIC} set of characters.
* <p>
* Used by URIBuilder to encode the query and fragment segments.
*
* @param content the string to encode, does not convert space to '+'
* @param charset the charset to use
* @return the encoded string
*/
static String encUric(final String content, final Charset charset) {
return urlEncode(content, charset, URIC, false);
}
/**
* Encode a String using the {@link #PATHSAFE} set of characters.
* <p>
* Used by URIBuilder to encode path segments.
*
* @param content the string to encode, does not convert space to '+'
* @param charset the charset to use
* @return the encoded string
*/
static String encPath(final String content, final Charset charset) {
return urlEncode(content, charset, PATHSAFE, false);
}
}

View file

@ -0,0 +1,31 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
/**
* Client utility classes.
*/
package ch.boye.httpclientandroidlib.client.utils;