/*
 * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.uri;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.Buffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.PathSegment;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;

Utility class for validating, encoding and decoding components of a URI.
Author:Paul Sandoz, Marek Potociar
/** * Utility class for validating, encoding and decoding components * of a URI. * * @author Paul Sandoz * @author Marek Potociar */
public class UriComponent { // TODO rewrite to use masks and not lookup tables
The URI component type.
/** * The URI component type. */
public enum Type {
ALPHA / DIGIT / "-" / "." / "_" / "~" characters.
/** * ALPHA / DIGIT / "-" / "." / "_" / "~" characters. */
UNRESERVED,
The URI scheme component type.
/** * The URI scheme component type. */
SCHEME,
The URI authority component type.
/** * The URI authority component type. */
AUTHORITY,
The URI user info component type.
/** * The URI user info component type. */
USER_INFO,
The URI host component type.
/** * The URI host component type. */
HOST,
The URI port component type.
/** * The URI port component type. */
PORT,
The URI path component type.
/** * The URI path component type. */
PATH,
The URI path component type that is a path segment.
/** * The URI path component type that is a path segment. */
PATH_SEGMENT,
The URI path component type that is a matrix parameter.
/** * The URI path component type that is a matrix parameter. */
MATRIX_PARAM,
The URI query component type, encoded using application/x-www-form-urlencoded rules.
/** * The URI query component type, encoded using application/x-www-form-urlencoded rules. */
QUERY,
The URI query component type that is a query parameter, encoded using application/x-www-form-urlencoded rules (space character is encoded as +).
/** * The URI query component type that is a query parameter, encoded using * application/x-www-form-urlencoded rules (space character is encoded * as {@code +}). */
QUERY_PARAM,
The URI query component type that is a query parameter, encoded using application/x-www-form-urlencoded (space character is encoded as %20).
/** * The URI query component type that is a query parameter, encoded using * application/x-www-form-urlencoded (space character is encoded as * {@code %20}). */
QUERY_PARAM_SPACE_ENCODED,
The URI fragment component type.
/** * The URI fragment component type. */
FRAGMENT, } private UriComponent() { }
Validates the legal characters of a percent-encoded string that represents a URI component type.
Params:
  • s – the encoded string.
  • t – the URI component type identifying the legal characters.
Throws:
/** * Validates the legal characters of a percent-encoded string that * represents a URI component type. * * @param s the encoded string. * @param t the URI component type identifying the legal characters. * @throws IllegalArgumentException if the encoded string contains illegal * characters. */
public static void validate(final String s, final Type t) { validate(s, t, false); }
Validates the legal characters of a percent-encoded string that represents a URI component type.
Params:
  • s – the encoded string.
  • t – the URI component type identifying the legal characters.
  • template – true if the encoded string contains URI template variables
Throws:
/** * Validates the legal characters of a percent-encoded string that * represents a URI component type. * * @param s the encoded string. * @param t the URI component type identifying the legal characters. * @param template true if the encoded string contains URI template variables * @throws IllegalArgumentException if the encoded string contains illegal * characters. */
public static void validate(final String s, final Type t, final boolean template) { final int i = _valid(s, t, template); if (i > -1) { throw new IllegalArgumentException(LocalizationMessages.URI_COMPONENT_INVALID_CHARACTER(s, t, s.charAt(i), i)); } }
Validates the legal characters of a percent-encoded string that represents a URI component type.
Params:
  • s – the encoded string.
  • t – the URI component type identifying the legal characters.
Returns:true if the encoded string is valid, otherwise false.
/** * Validates the legal characters of a percent-encoded string that * represents a URI component type. * * @param s the encoded string. * @param t the URI component type identifying the legal characters. * @return true if the encoded string is valid, otherwise false. */
public static boolean valid(final String s, final Type t) { return valid(s, t, false); }
Validates the legal characters of a percent-encoded string that represents a URI component type.
Params:
  • s – the encoded string.
  • t – the URI component type identifying the legal characters.
  • template – true if the encoded string contains URI template variables
Returns:true if the encoded string is valid, otherwise false.
/** * Validates the legal characters of a percent-encoded string that * represents a URI component type. * * @param s the encoded string. * @param t the URI component type identifying the legal characters. * @param template true if the encoded string contains URI template variables * @return true if the encoded string is valid, otherwise false. */
public static boolean valid(final String s, final Type t, final boolean template) { return _valid(s, t, template) == -1; } private static int _valid(final String s, final Type t, final boolean template) { final boolean[] table = ENCODING_TABLES[t.ordinal()]; for (int i = 0; i < s.length(); i++) { final char c = s.charAt(i); if ((c < 0x80 && c != '%' && !table[c]) || c >= 0x80) { if (!template || (c != '{' && c != '}')) { return i; } } } return -1; }
Contextually encodes the characters of string that are either non-ASCII characters or are ASCII characters that must be percent-encoded using the UTF-8 encoding. Percent-encoded characters will be recognized and not double encoded.
Params:
  • s – the string to be encoded.
  • t – the URI component type identifying the ASCII characters that must be percent-encoded.
Returns:the encoded string.
/** * Contextually encodes the characters of string that are either non-ASCII * characters or are ASCII characters that must be percent-encoded using the * UTF-8 encoding. Percent-encoded characters will be recognized and not * double encoded. * * @param s the string to be encoded. * @param t the URI component type identifying the ASCII characters that * must be percent-encoded. * @return the encoded string. */
public static String contextualEncode(final String s, final Type t) { return _encode(s, t, false, true); }
Contextually encodes the characters of string that are either non-ASCII characters or are ASCII characters that must be percent-encoded using the UTF-8 encoding. Percent-encoded characters will be recognized and not double encoded.
Params:
  • s – the string to be encoded.
  • t – the URI component type identifying the ASCII characters that must be percent-encoded.
  • template – true if the encoded string contains URI template variables
Returns:the encoded string.
/** * Contextually encodes the characters of string that are either non-ASCII * characters or are ASCII characters that must be percent-encoded using the * UTF-8 encoding. Percent-encoded characters will be recognized and not * double encoded. * * @param s the string to be encoded. * @param t the URI component type identifying the ASCII characters that * must be percent-encoded. * @param template true if the encoded string contains URI template variables * @return the encoded string. */
public static String contextualEncode(final String s, final Type t, final boolean template) { return _encode(s, t, template, true); }
Encodes the characters of string that are either non-ASCII characters or are ASCII characters that must be percent-encoded using the UTF-8 encoding.
Params:
  • s – the string to be encoded.
  • t – the URI component type identifying the ASCII characters that must be percent-encoded.
Returns:the encoded string.
/** * Encodes the characters of string that are either non-ASCII characters * or are ASCII characters that must be percent-encoded using the * UTF-8 encoding. * * @param s the string to be encoded. * @param t the URI component type identifying the ASCII characters that * must be percent-encoded. * @return the encoded string. */
public static String encode(final String s, final Type t) { return _encode(s, t, false, false); }
Encodes the characters of string that are either non-ASCII characters or are ASCII characters that must be percent-encoded using the UTF-8 encoding.
Params:
  • s – the string to be encoded.
  • t – the URI component type identifying the ASCII characters that must be percent-encoded.
  • template – true if the encoded string contains URI template variables
Returns:the encoded string.
/** * Encodes the characters of string that are either non-ASCII characters * or are ASCII characters that must be percent-encoded using the * UTF-8 encoding. * * @param s the string to be encoded. * @param t the URI component type identifying the ASCII characters that * must be percent-encoded. * @param template true if the encoded string contains URI template variables * @return the encoded string. */
public static String encode(final String s, final Type t, final boolean template) { return _encode(s, t, template, false); }
Encodes a string with template parameters names present, specifically the characters '{' and '}' will be percent-encoded.
Params:
  • s – the string with zero or more template parameters names
Returns:the string with encoded template parameters names.
/** * Encodes a string with template parameters names present, specifically the * characters '{' and '}' will be percent-encoded. * * @param s the string with zero or more template parameters names * @return the string with encoded template parameters names. */
public static String encodeTemplateNames(String s) { int i = s.indexOf('{'); if (i != -1) { s = s.replace("{", "%7B"); } i = s.indexOf('}'); if (i != -1) { s = s.replace("}", "%7D"); } return s; } private static String _encode(final String s, final Type t, final boolean template, final boolean contextualEncode) { final boolean[] table = ENCODING_TABLES[t.ordinal()]; boolean insideTemplateParam = false; StringBuilder sb = null; for (int offset = 0, codePoint; offset < s.length(); offset += Character.charCount(codePoint)) { codePoint = s.codePointAt(offset); if (codePoint < 0x80 && table[codePoint]) { if (sb != null) { sb.append((char) codePoint); } } else { if (template) { boolean leavingTemplateParam = false; if (codePoint == '{') { insideTemplateParam = true; } else if (codePoint == '}') { insideTemplateParam = false; leavingTemplateParam = true; } if (insideTemplateParam || leavingTemplateParam) { if (sb != null) { sb.append(Character.toChars(codePoint)); } continue; } } if (contextualEncode && codePoint == '%' && offset + 2 < s.length() && isHexCharacter(s.charAt(offset + 1)) && isHexCharacter(s.charAt(offset + 2))) { if (sb != null) { sb.append('%').append(s.charAt(offset + 1)).append(s.charAt(offset + 2)); } offset += 2; continue; } if (sb == null) { sb = new StringBuilder(); sb.append(s.substring(0, offset)); } if (codePoint < 0x80) { if (codePoint == ' ' && (t == Type.QUERY_PARAM)) { sb.append('+'); } else { appendPercentEncodedOctet(sb, (char) codePoint); } } else { appendUTF8EncodedCharacter(sb, codePoint); } } } return (sb == null) ? s : sb.toString(); } private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private static void appendPercentEncodedOctet(final StringBuilder sb, final int b) { sb.append('%'); sb.append(HEX_DIGITS[b >> 4]); sb.append(HEX_DIGITS[b & 0x0F]); } private static void appendUTF8EncodedCharacter(final StringBuilder sb, final int codePoint) { final CharBuffer chars = CharBuffer.wrap(Character.toChars(codePoint)); final ByteBuffer bytes = UTF_8_CHARSET.encode(chars); while (bytes.hasRemaining()) { appendPercentEncodedOctet(sb, bytes.get() & 0xFF); } } private static final String[] SCHEME = {"0-9", "A-Z", "a-z", "+", "-", "."}; private static final String[] UNRESERVED = {"0-9", "A-Z", "a-z", "-", ".", "_", "~"}; private static final String[] SUB_DELIMS = {"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="}; private static final boolean[][] ENCODING_TABLES = initEncodingTables(); private static boolean[][] initEncodingTables() { final boolean[][] tables = new boolean[Type.values().length][]; final List<String> l = new ArrayList<String>(); l.addAll(Arrays.asList(SCHEME)); tables[Type.SCHEME.ordinal()] = initEncodingTable(l); l.clear(); l.addAll(Arrays.asList(UNRESERVED)); tables[Type.UNRESERVED.ordinal()] = initEncodingTable(l); l.addAll(Arrays.asList(SUB_DELIMS)); tables[Type.HOST.ordinal()] = initEncodingTable(l); tables[Type.PORT.ordinal()] = initEncodingTable(Arrays.asList("0-9")); l.add(":"); tables[Type.USER_INFO.ordinal()] = initEncodingTable(l); l.add("@"); tables[Type.AUTHORITY.ordinal()] = initEncodingTable(l); tables[Type.PATH_SEGMENT.ordinal()] = initEncodingTable(l); tables[Type.PATH_SEGMENT.ordinal()][';'] = false; tables[Type.MATRIX_PARAM.ordinal()] = tables[Type.PATH_SEGMENT.ordinal()].clone(); tables[Type.MATRIX_PARAM.ordinal()]['='] = false; l.add("/"); tables[Type.PATH.ordinal()] = initEncodingTable(l); tables[Type.QUERY.ordinal()] = initEncodingTable(l); tables[Type.QUERY.ordinal()]['!'] = false; tables[Type.QUERY.ordinal()]['*'] = false; tables[Type.QUERY.ordinal()]['\''] = false; tables[Type.QUERY.ordinal()]['('] = false; tables[Type.QUERY.ordinal()][')'] = false; tables[Type.QUERY.ordinal()][';'] = false; tables[Type.QUERY.ordinal()][':'] = false; tables[Type.QUERY.ordinal()]['@'] = false; tables[Type.QUERY.ordinal()]['$'] = false; tables[Type.QUERY.ordinal()][','] = false; tables[Type.QUERY.ordinal()]['/'] = false; tables[Type.QUERY.ordinal()]['?'] = false; tables[Type.QUERY_PARAM.ordinal()] = Arrays.copyOf( tables[Type.QUERY.ordinal()], tables[Type.QUERY.ordinal()].length); tables[Type.QUERY_PARAM.ordinal()]['='] = false; tables[Type.QUERY_PARAM.ordinal()]['+'] = false; tables[Type.QUERY_PARAM.ordinal()]['&'] = false; tables[Type.QUERY_PARAM_SPACE_ENCODED.ordinal()] = tables[Type.QUERY_PARAM.ordinal()]; tables[Type.FRAGMENT.ordinal()] = tables[Type.QUERY.ordinal()]; return tables; } private static boolean[] initEncodingTable(final List<String> allowed) { final boolean[] table = new boolean[0x80]; for (final String range : allowed) { if (range.length() == 1) { table[range.charAt(0)] = true; } else if (range.length() == 3 && range.charAt(1) == '-') { for (int i = range.charAt(0); i <= range.charAt(2); i++) { table[i] = true; } } } return table; } private static final Charset UTF_8_CHARSET = Charset.forName("UTF-8");
Decodes characters of a string that are percent-encoded octets using UTF-8 decoding (if needed).

It is assumed that the string is valid according to an (unspecified) URI component type. If a sequence of contiguous percent-encoded octets is not a valid UTF-8 character then the octets are replaced with '\uFFFD'.

If the URI component is of type HOST then any "%" found between "[]" is left alone. It is an IPv6 literal with a scope_id.

If the URI component is of type QUERY_PARAM then any "+" is decoded as as ' '.

Params:
  • s – the string to be decoded.
  • t – the URI component type, may be null.
Throws:
Returns:the decoded string.
/** * Decodes characters of a string that are percent-encoded octets using * UTF-8 decoding (if needed). * <p/> * It is assumed that the string is valid according to an (unspecified) URI * component type. If a sequence of contiguous percent-encoded octets is * not a valid UTF-8 character then the octets are replaced with '\uFFFD'. * <p/> * If the URI component is of type HOST then any "%" found between "[]" is * left alone. It is an IPv6 literal with a scope_id. * <p/> * If the URI component is of type QUERY_PARAM then any "+" is decoded as * as ' '. * <p/> * * @param s the string to be decoded. * @param t the URI component type, may be null. * @return the decoded string. * @throws IllegalArgumentException if a malformed percent-encoded octet is * detected */
public static String decode(final String s, final Type t) { if (s == null) { throw new IllegalArgumentException(); } final int n = s.length(); if (n == 0) { return s; } // If there are no percent-escaped octets if (s.indexOf('%') < 0) { // If there are no '+' characters for query param if (t == Type.QUERY_PARAM) { if (s.indexOf('+') < 0) { return s; } } else { return s; } } else { // Malformed percent-escaped octet at the end if (n < 2) { throw new IllegalArgumentException(LocalizationMessages.URI_COMPONENT_ENCODED_OCTET_MALFORMED(1)); } // Malformed percent-escaped octet at the end if (s.charAt(n - 2) == '%') { throw new IllegalArgumentException(LocalizationMessages.URI_COMPONENT_ENCODED_OCTET_MALFORMED(n - 2)); } } if (t == null) { return decode(s, n); } switch (t) { case HOST: return decodeHost(s, n); case QUERY_PARAM: return decodeQueryParam(s, n); default: return decode(s, n); } }
Decode the query component of a URI.

Query parameter names in the returned map are always decoded. Decoding of query parameter values can be controlled using the decode parameter flag.

Params:
  • u – the URI.
  • decode – true if the returned query parameter values of the query component should be in decoded form.
Returns:the multivalued map of query parameters.
/** * Decode the query component of a URI. * <p> * Query parameter names in the returned map are always decoded. Decoding of query parameter * values can be controlled using the {@code decode} parameter flag. * </p> * * @param u the URI. * @param decode {@code true} if the returned query parameter values of the query component * should be in decoded form. * @return the multivalued map of query parameters. */
public static MultivaluedMap<String, String> decodeQuery(final URI u, final boolean decode) { return decodeQuery(u.getRawQuery(), decode); }
Decode the query component of a URI.

Query parameter names in the returned map are always decoded. Decoding of query parameter values can be controlled using the decode parameter flag.

Params:
  • q – the query component in encoded form.
  • decode – true if the returned query parameter values of the query component should be in decoded form.
Returns:the multivalued map of query parameters.
/** * Decode the query component of a URI. * <p> * Query parameter names in the returned map are always decoded. Decoding of query parameter * values can be controlled using the {@code decode} parameter flag. * </p> * * @param q the query component in encoded form. * @param decode {@code true} if the returned query parameter values of the query component * should be in decoded form. * @return the multivalued map of query parameters. */
public static MultivaluedMap<String, String> decodeQuery(final String q, final boolean decode) { return decodeQuery(q, true, decode); }
Decode the query component of a URI.

Decoding of query parameter names and values can be controlled using the decodeNames and decodeValues parameter flags.

Params:
  • q – the query component in encoded form.
  • decodeNames – true if the returned query parameter names of the query component should be in decoded form.
  • decodeValues – true if the returned query parameter values of the query component should be in decoded form.
Returns:the multivalued map of query parameters.
/** * Decode the query component of a URI. * <p> * Decoding of query parameter names and values can be controlled using the {@code decodeNames} * and {@code decodeValues} parameter flags. * </p> * * @param q the query component in encoded form. * @param decodeNames {@code true} if the returned query parameter names of the query component * should be in decoded form. * @param decodeValues {@code true} if the returned query parameter values of the query component * should be in decoded form. * @return the multivalued map of query parameters. */
public static MultivaluedMap<String, String> decodeQuery(final String q, final boolean decodeNames, final boolean decodeValues) { final MultivaluedMap<String, String> queryParameters = new MultivaluedStringMap(); if (q == null || q.length() == 0) { return queryParameters; } int s = 0; do { final int e = q.indexOf('&', s); if (e == -1) { decodeQueryParam(queryParameters, q.substring(s), decodeNames, decodeValues); } else if (e > s) { decodeQueryParam(queryParameters, q.substring(s, e), decodeNames, decodeValues); } s = e + 1; } while (s > 0 && s < q.length()); return queryParameters; } @SuppressWarnings("StatementWithEmptyBody") private static void decodeQueryParam(final MultivaluedMap<String, String> params, final String param, final boolean decodeNames, final boolean decodeValues) { try { final int equals = param.indexOf('='); if (equals > 0) { params.add((decodeNames) ? URLDecoder.decode(param.substring(0, equals), "UTF-8") : param.substring(0, equals), (decodeValues) ? URLDecoder.decode(param.substring(equals + 1), "UTF-8") : param.substring(equals + 1)); } else if (equals == 0) { // no key declared, ignore } else if (param.length() > 0) { params.add((decodeNames) ? URLDecoder.decode(param, "UTF-8") : param, ""); } } catch (final UnsupportedEncodingException ex) { // This should never occur throw new IllegalArgumentException(ex); } } private static final class PathSegmentImpl implements PathSegment { private static final PathSegment EMPTY_PATH_SEGMENT = new PathSegmentImpl("", false); private final String path; private final MultivaluedMap<String, String> matrixParameters; PathSegmentImpl(final String path, final boolean decode) { this(path, decode, new MultivaluedStringMap()); } PathSegmentImpl(final String path, final boolean decode, final MultivaluedMap<String, String> matrixParameters) { this.path = (decode) ? UriComponent.decode(path, UriComponent.Type.PATH_SEGMENT) : path; this.matrixParameters = matrixParameters; } @Override public String getPath() { return path; } @Override public MultivaluedMap<String, String> getMatrixParameters() { return matrixParameters; } @Override public String toString() { return path; } }
Decode the path component of a URI as path segments.
Params:
  • u – the URI. If the path component is an absolute path component then the leading '/' is ignored and is not considered a delimiator of a path segment.
  • decode – true if the path segments of the path component should be in decoded form.
Returns:the list of path segments.
/** * Decode the path component of a URI as path segments. * * @param u the URI. If the path component is an absolute path component * then the leading '/' is ignored and is not considered a delimiator * of a path segment. * @param decode true if the path segments of the path component * should be in decoded form. * @return the list of path segments. */
public static List<PathSegment> decodePath(final URI u, final boolean decode) { String rawPath = u.getRawPath(); if (rawPath != null && rawPath.length() > 0 && rawPath.charAt(0) == '/') { rawPath = rawPath.substring(1); } return decodePath(rawPath, decode); }
Decode the path component of a URI as path segments.

Any '/' character in the path is considered to be a deliminator between two path segments. Thus if the path is '/' then the path segment list will contain two empty path segments. If the path is "//" then the path segment list will contain three empty path segments. If the path is "/a/" the path segment list will consist of the following path segments in order: "", "a" and "".

Params:
  • path – the path component in encoded form.
  • decode – true if the path segments of the path component should be in decoded form.
Returns:the list of path segments.
/** * Decode the path component of a URI as path segments. * <p> * Any '/' character in the path is considered to be a deliminator * between two path segments. Thus if the path is '/' then the path segment * list will contain two empty path segments. If the path is "//" then * the path segment list will contain three empty path segments. If the path * is "/a/" the path segment list will consist of the following path * segments in order: "", "a" and "". * </p> * * @param path the path component in encoded form. * @param decode true if the path segments of the path component * should be in decoded form. * @return the list of path segments. */
public static List<PathSegment> decodePath(final String path, final boolean decode) { final List<PathSegment> segments = new LinkedList<PathSegment>(); if (path == null) { return segments; } int s; int e = -1; do { s = e + 1; e = path.indexOf('/', s); if (e > s) { decodePathSegment(segments, path.substring(s, e), decode); } else if (e == s) { segments.add(PathSegmentImpl.EMPTY_PATH_SEGMENT); } } while (e != -1); if (s < path.length()) { decodePathSegment(segments, path.substring(s), decode); } else { segments.add(PathSegmentImpl.EMPTY_PATH_SEGMENT); } return segments; }
Decode the path segment and add it to the list of path segments.
Params:
  • segments – mutable list of path segments.
  • segment – path segment to be decoded.
  • decode – true if the path segment should be in a decoded form.
/** * Decode the path segment and add it to the list of path segments. * * @param segments mutable list of path segments. * @param segment path segment to be decoded. * @param decode {@code true} if the path segment should be in a decoded form. */
public static void decodePathSegment(final List<PathSegment> segments, final String segment, final boolean decode) { final int colon = segment.indexOf(';'); if (colon != -1) { segments.add(new PathSegmentImpl((colon == 0) ? "" : segment.substring(0, colon), decode, decodeMatrix(segment, decode))); } else { segments.add(new PathSegmentImpl(segment, decode)); } }
Decode the matrix component of a URI path segment.
Params:
  • pathSegment – the path segment component in encoded form.
  • decode – true if the matrix parameters of the path segment component should be in decoded form.
Returns:the multivalued map of matrix parameters.
/** * Decode the matrix component of a URI path segment. * * @param pathSegment the path segment component in encoded form. * @param decode true if the matrix parameters of the path segment component * should be in decoded form. * @return the multivalued map of matrix parameters. */
public static MultivaluedMap<String, String> decodeMatrix(final String pathSegment, final boolean decode) { final MultivaluedMap<String, String> matrixMap = new MultivaluedStringMap(); // Skip over path segment int s = pathSegment.indexOf(';') + 1; if (s == 0 || s == pathSegment.length()) { return matrixMap; } do { final int e = pathSegment.indexOf(';', s); if (e == -1) { decodeMatrixParam(matrixMap, pathSegment.substring(s), decode); } else if (e > s) { decodeMatrixParam(matrixMap, pathSegment.substring(s, e), decode); } s = e + 1; } while (s > 0 && s < pathSegment.length()); return matrixMap; } @SuppressWarnings("StatementWithEmptyBody") private static void decodeMatrixParam(final MultivaluedMap<String, String> params, final String param, final boolean decode) { final int equals = param.indexOf('='); if (equals > 0) { params.add(UriComponent.decode(param.substring(0, equals), UriComponent.Type.MATRIX_PARAM), (decode) ? UriComponent.decode(param.substring(equals + 1), UriComponent.Type.MATRIX_PARAM) : param .substring(equals + 1)); } else if (equals == 0) { // no key declared, ignore } else if (param.length() > 0) { params.add(UriComponent.decode(param, UriComponent.Type.MATRIX_PARAM), ""); } } private static String decode(final String s, final int n) { final StringBuilder sb = new StringBuilder(n); ByteBuffer bb = null; for (int i = 0; i < n; ) { final char c = s.charAt(i++); if (c != '%') { sb.append(c); } else { bb = decodePercentEncodedOctets(s, i, bb); i = decodeOctets(i, bb, sb); } } return sb.toString(); } private static String decodeQueryParam(final String s, final int n) { final StringBuilder sb = new StringBuilder(n); ByteBuffer bb = null; for (int i = 0; i < n; ) { final char c = s.charAt(i++); if (c != '%') { if (c != '+') { sb.append(c); } else { sb.append(' '); } } else { bb = decodePercentEncodedOctets(s, i, bb); i = decodeOctets(i, bb, sb); } } return sb.toString(); } private static String decodeHost(final String s, final int n) { final StringBuilder sb = new StringBuilder(n); ByteBuffer bb = null; boolean betweenBrackets = false; for (int i = 0; i < n; ) { final char c = s.charAt(i++); if (c == '[') { betweenBrackets = true; } else if (betweenBrackets && c == ']') { betweenBrackets = false; } if (c != '%' || betweenBrackets) { sb.append(c); } else { bb = decodePercentEncodedOctets(s, i, bb); i = decodeOctets(i, bb, sb); } } return sb.toString(); }
Decode a continuous sequence of percent encoded octets.

Assumes the index, i, starts that the first hex digit of the first percent-encoded octet.
/** * Decode a continuous sequence of percent encoded octets. * <p/> * Assumes the index, i, starts that the first hex digit of the first * percent-encoded octet. */
private static ByteBuffer decodePercentEncodedOctets(final String s, int i, ByteBuffer bb) { if (bb == null) { bb = ByteBuffer.allocate(1); } else { ((Buffer) bb).clear(); } while (true) { // Decode the hex digits bb.put((byte) (decodeHex(s, i++) << 4 | decodeHex(s, i++))); // Finish if at the end of the string if (i == s.length()) { break; } // Finish if no more percent-encoded octets follow if (s.charAt(i++) != '%') { break; } // Check if the byte buffer needs to be increased in size if (((Buffer) bb).position() == bb.capacity()) { ((Buffer) bb).flip(); // Create a new byte buffer with the maximum number of possible // octets, hence resize should only occur once final ByteBuffer bb_new = ByteBuffer.allocate(s.length() / 3); bb_new.put(bb); bb = bb_new; } } ((Buffer) bb).flip(); return bb; }
Decodes octets to characters using the UTF-8 decoding and appends the characters to a StringBuffer.
Returns:the index to the next unchecked character in the string to decode
/** * Decodes octets to characters using the UTF-8 decoding and appends * the characters to a StringBuffer. * * @return the index to the next unchecked character in the string to decode */
private static int decodeOctets(final int i, final ByteBuffer bb, final StringBuilder sb) { // If there is only one octet and is an ASCII character if (((Buffer) bb).limit() == 1 && (bb.get(0) & 0xFF) < 0x80) { // Octet can be appended directly sb.append((char) bb.get(0)); return i + 2; } else { // final CharBuffer cb = UTF_8_CHARSET.decode(bb); sb.append(cb.toString()); return i + ((Buffer) bb).limit() * 3 - 1; } } private static int decodeHex(final String s, final int i) { final int v = decodeHex(s.charAt(i)); if (v == -1) { throw new IllegalArgumentException(LocalizationMessages.URI_COMPONENT_ENCODED_OCTET_INVALID_DIGIT(i, s.charAt(i))); } return v; } private static final int[] HEX_TABLE = initHexTable(); private static int[] initHexTable() { final int[] table = new int[0x80]; Arrays.fill(table, -1); for (char c = '0'; c <= '9'; c++) { table[c] = c - '0'; } for (char c = 'A'; c <= 'F'; c++) { table[c] = c - 'A' + 10; } for (char c = 'a'; c <= 'f'; c++) { table[c] = c - 'a' + 10; } return table; } private static int decodeHex(final char c) { return (c < 128) ? HEX_TABLE[c] : -1; }
Checks whether the character c is hexadecimal character.
Params:
  • c – Any character
Returns:The is c is a hexadecimal character (e.g. 0, 5, a, A, f, ...)
/** * Checks whether the character {@code c} is hexadecimal character. * * @param c Any character * @return The is {@code c} is a hexadecimal character (e.g. 0, 5, a, A, f, ...) */
public static boolean isHexCharacter(final char c) { return c < 128 && HEX_TABLE[c] != -1; }
Return the Request-Uri representation as defined by HTTP spec. For example:
<Method> <Request-URI> HTTP/<Version> (e.g. GET /auth;foo=bar/hello?foo=bar HTTP/1.1)
Params:
  • uri – uri to obtain Request-Uri from.
Returns:Request-Uri representation or null if uri is not provided.
/** * Return the {@code Request-Uri} representation as defined by HTTP spec. For example: * <pre>&lt;Method> &lt;Request-URI> HTTP/&lt;Version> (e.g. GET /auth;foo=bar/hello?foo=bar HTTP/1.1)</pre> * * @param uri uri to obtain {@code Request-Uri} from. * @return {@code Request-Uri} representation or {@code null} if {@code uri} is not provided. */
public static String fullRelativeUri(final URI uri) { if (uri == null) { return null; } final String query = uri.getRawQuery(); return uri.getRawPath() + (query != null && query.length() > 0 ? "?" + query : ""); } }