diff options
Diffstat (limited to 'mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils')
11 files changed, 2370 insertions, 0 deletions
diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/CloneUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/CloneUtils.java new file mode 100644 index 000000000..67f70ee07 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/CloneUtils.java @@ -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() { + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/DateUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/DateUtils.java new file mode 100644 index 000000000..e337923d3 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/DateUtils.java @@ -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(); + } + + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/HttpClientUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/HttpClientUtils.java new file mode 100644 index 000000000..3c7769c2a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/HttpClientUtils.java @@ -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) { + } + } + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Idn.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Idn.java new file mode 100644 index 000000000..a2b5bd036 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Idn.java @@ -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); +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/JdkIdn.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/JdkIdn.java new file mode 100644 index 000000000..53d46fe23 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/JdkIdn.java @@ -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); + } + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Punycode.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Punycode.java new file mode 100644 index 000000000..fa4872c41 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Punycode.java @@ -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); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Rfc3492Idn.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Rfc3492Idn.java new file mode 100644 index 000000000..ccf980050 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/Rfc3492Idn.java @@ -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(); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIBuilder.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIBuilder.java new file mode 100644 index 000000000..e41958f7f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIBuilder.java @@ -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; + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIUtils.java new file mode 100644 index 000000000..73619c90a --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URIUtils.java @@ -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 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() { + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URLEncodedUtils.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URLEncodedUtils.java new file mode 100644 index 000000000..97465401f --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/URLEncodedUtils.java @@ -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); + } + +} diff --git a/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/package-info.java b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/package-info.java new file mode 100644 index 000000000..7f5671654 --- /dev/null +++ b/mobile/android/thirdparty/ch/boye/httpclientandroidlib/client/utils/package-info.java @@ -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; |