All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.resteasy.reactive.common.util.Encode Maven / Gradle / Ivy

There is a newer version: 3.17.0.CR1
Show newest version
package org.jboss.resteasy.reactive.common.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jakarta.ws.rs.core.MultivaluedMap;

/**
 * @author Bill Burke
 */
public class Encode {
    private static final String UTF_8 = StandardCharsets.UTF_8.name();

    private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");

    private static final String[] pathEncoding = new String[128];
    private static final String[] pathSegmentEncoding = new String[128];
    private static final String[] matrixParameterEncoding = new String[128];
    private static final String[] queryNameValueEncoding = new String[128];
    private static final String[] queryStringEncoding = new String[128];

    static {
        /*
         * Encode via RFC 3986. PCHAR is allowed allong with '/'
         *
         * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
         * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
         * / "*" / "+" / "," / ";" / "="
         * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
         *
         */
        for (int i = 0; i < 128; i++) {
            if (i >= 'a' && i <= 'z')
                continue;
            if (i >= 'A' && i <= 'Z')
                continue;
            if (i >= '0' && i <= '9')
                continue;
            switch ((char) i) {
                case '-':
                case '.':
                case '_':
                case '~':
                case '!':
                case '$':
                case '&':
                case '\'':
                case '(':
                case ')':
                case '*':
                case '+':
                case ',':
                case '/':
                case ';':
                case '=':
                case ':':
                case '@':
                    continue;
            }
            pathEncoding[i] = encodeString(String.valueOf((char) i));
        }
        pathEncoding[' '] = "%20";
        System.arraycopy(pathEncoding, 0, matrixParameterEncoding, 0, pathEncoding.length);
        matrixParameterEncoding[';'] = "%3B";
        matrixParameterEncoding['='] = "%3D";
        matrixParameterEncoding['/'] = "%2F"; // RESTEASY-729
        System.arraycopy(pathEncoding, 0, pathSegmentEncoding, 0, pathEncoding.length);
        pathSegmentEncoding['/'] = "%2F";
        /*
         * Encode via RFC 3986.
         *
         * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
         * space encoded as '+'
         *
         */
        for (int i = 0; i < 128; i++) {
            if (i >= 'a' && i <= 'z')
                continue;
            if (i >= 'A' && i <= 'Z')
                continue;
            if (i >= '0' && i <= '9')
                continue;
            switch ((char) i) {
                case '-':
                case '.':
                case '_':
                case '~':
                    continue;
                case '?':
                    queryNameValueEncoding[i] = "%3F";
                    continue;
                case ' ':
                    queryNameValueEncoding[i] = "+";
                    continue;
            }
            queryNameValueEncoding[i] = encodeString(String.valueOf((char) i));
        }

        /*
         * query = *( pchar / "/" / "?" )
         *
         */
        for (int i = 0; i < 128; i++) {
            if (i >= 'a' && i <= 'z')
                continue;
            if (i >= 'A' && i <= 'Z')
                continue;
            if (i >= '0' && i <= '9')
                continue;
            switch ((char) i) {
                case '-':
                case '.':
                case '_':
                case '~':
                case '!':
                case '$':
                case '&':
                case '\'':
                case '(':
                case ')':
                case '*':
                case '+':
                case ',':
                case ';':
                case '=':
                case ':':
                case '@':
                case '?':
                case '/':
                    continue;
                case ' ':
                    queryStringEncoding[i] = "%20";
                    continue;
            }
            queryStringEncoding[i] = encodeString(String.valueOf((char) i));
        }
    }

    /**
     * Keep encoded values "%..." and template parameters intact.
     *
     * @param value query string
     * @return encoded query string
     */
    public static String encodeQueryString(String value) {
        return encodeValue(value, queryStringEncoding);
    }

    /**
     * Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact.
     *
     * @param value path
     * @return encoded path
     */
    public static String encodePath(String value) {
        return encodeValue(value, pathEncoding);
    }

    /**
     * Keep encoded values "%...", matrix parameters and template parameters intact.
     *
     * @param value path segment
     * @return encoded path segment
     */
    public static String encodePathSegment(String value) {
        return encodeValue(value, pathSegmentEncoding);
    }

    /**
     * Keep encoded values "%..." and template parameters intact.
     *
     * @param value uri fragment
     * @return encoded uri fragment
     */
    public static String encodeFragment(String value) {
        return encodeValue(value, queryStringEncoding);
    }

    /**
     * Keep encoded values "%..." and template parameters intact.
     *
     * @param value matrix parameter
     * @return encoded matrix parameter
     */
    public static String encodeMatrixParam(String value) {
        return encodeValue(value, matrixParameterEncoding);
    }

    /**
     * Keep encoded values "%..." and template parameters intact.
     *
     * @param value query parameter
     * @return encoded query parameter
     */
    public static String encodeQueryParam(String value) {
        return encodeValue(value, queryNameValueEncoding);
    }

    //private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|$)");
    private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|[a-fA-F0-9]$|$|[a-fA-F0-9][^a-fA-F0-9])");
    private static final Pattern encodedChars = Pattern.compile("%([a-fA-F0-9][a-fA-F0-9])");
    private static final Pattern encodedCharsMulti = Pattern.compile("((%[a-fA-F0-9][a-fA-F0-9])+)");

    public static String decodePath(String path) {
        // FIXME: this doesn't appear to pass the TCK, because it fails to decode what it throws at it
        // also it doesn't decode slashes (it should) and it decodes + (it should not)
        //        return URLUtils.decode(path, StandardCharsets.UTF_8, false, null);
        // So let's use the Vertx decoder for now
        return URIDecoder.decodeURIComponent(path, false);
    }

    private static String decodeBytes(String enc, CharsetDecoder decoder) {
        Matcher matcher = encodedChars.matcher(enc);
        ByteBuffer bytes = ByteBuffer.allocate(enc.length() / 3);
        while (matcher.find()) {
            int b = Integer.parseInt(matcher.group(1), 16);
            bytes.put((byte) b);
        }
        bytes.flip();
        try {
            return decoder.decode(bytes).toString();
        } catch (CharacterCodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Encode '%' if it is not an encoding sequence
     *
     * @param string value to encode
     * @return encoded value
     */
    public static String encodeNonCodes(String string) {
        Matcher matcher = nonCodes.matcher(string);
        StringBuilder builder = new StringBuilder();

        // FYI: we do not use the no-arg matcher.find()
        //      coupled with matcher.appendReplacement()
        //      because the matched text may contain
        //      a second % and we must make sure we
        //      encode it (if necessary).
        int idx = 0;
        while (matcher.find(idx)) {
            int start = matcher.start();
            builder.append(string.substring(idx, start));
            builder.append("%25");
            idx = start + 1;
        }
        builder.append(string.substring(idx));
        return builder.toString();
    }

    public static boolean savePathParams(String segmentString, StringBuilder newSegment, List params) {
        boolean foundParam = false;
        // Regular expressions can have '{' and '}' characters.  Replace them to do match
        CharSequence segment = PathHelper.replaceEnclosedCurlyBracesCS(segmentString);
        Matcher matcher = PathHelper.URI_TEMPLATE_PATTERN.matcher(segment);
        int start = 0;
        while (matcher.find()) {
            newSegment.append(segment, start, matcher.start());
            foundParam = true;
            String group = matcher.group();
            // Regular expressions can have '{' and '}' characters.  Recover earlier replacement
            params.add(PathHelper.recoverEnclosedCurlyBraces(group));
            newSegment.append("_resteasy_uri_parameter");
            start = matcher.end();
        }
        newSegment.append(segment, start, segment.length());
        return foundParam;
    }

    /**
     * Keep encoded values "%..." and template parameters intact i.e. "{x}"
     *
     * @param segment value to encode
     * @param encoding encoding
     * @return encoded value
     */
    public static String encodeValue(String segment, String[] encoding) {
        ArrayList params = new ArrayList();
        boolean foundParam = false;
        StringBuilder newSegment = new StringBuilder();
        if (savePathParams(segment, newSegment, params)) {
            foundParam = true;
            segment = newSegment.toString();
        }
        String result = encodeFromArray(segment, encoding, false);
        result = encodeNonCodes(result);
        segment = result;
        if (foundParam) {
            segment = pathParamReplacement(segment, params);
        }
        return segment;
    }

    /**
     * Encode via RFC 3986. PCHAR is allowed allong with '/'
     * 

* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * * @param segment value to encode * @return encoded value */ public static String encodePathAsIs(String segment) { return encodeFromArray(segment, pathEncoding, true); } /** * Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p" * * @param segment value to encode * @return encoded value */ public static String encodePathSaveEncodings(String segment) { String result = encodeFromArray(segment, pathEncoding, false); result = encodeNonCodes(result); return result; } /** * Encode via RFC 3986. PCHAR is allowed allong with '/' *

* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * * @param segment value to encode * @return encoded value */ public static String encodePathSegmentAsIs(String segment) { return encodeFromArray(segment, pathSegmentEncoding, true); } /** * Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p" * * @param segment value to encode * @return encoded value */ public static String encodePathSegmentSaveEncodings(String segment) { String result = encodeFromArray(segment, pathSegmentEncoding, false); result = encodeNonCodes(result); return result; } /** * Encodes everything of a query parameter name or value. * * @param nameOrValue value to encode * @return encoded value */ public static String encodeQueryParamAsIs(String nameOrValue) { return encodeFromArray(nameOrValue, queryNameValueEncoding, true); } /** * Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p" * * @param segment value to encode * @return encoded value */ public static String encodeQueryParamSaveEncodings(String segment) { String result = encodeFromArray(segment, queryNameValueEncoding, false); result = encodeNonCodes(result); return result; } public static String encodeFragmentAsIs(String nameOrValue) { return encodeFromArray(nameOrValue, queryNameValueEncoding, true); } protected static String encodeFromArray(String segment, String[] encodingMap, boolean encodePercent) { StringBuilder result = new StringBuilder(); for (int i = 0; i < segment.length(); i++) { char currentChar = segment.charAt(i); if (!encodePercent && currentChar == '%') { result.append(currentChar); continue; } if (Character.isHighSurrogate(currentChar)) { String part = segment.substring(i, i + 2); result.append(URLEncoder.encode(part, StandardCharsets.UTF_8)); ++i; continue; } String encoding = encode(currentChar, encodingMap); if (encoding == null) { result.append(currentChar); } else { result.append(encoding); } } return result.toString(); } /** * @param zhar integer representation of character * @param encodingMap encoding map * @return URL encoded character */ private static String encode(int zhar, String[] encodingMap) { String encoded; if (zhar < encodingMap.length) { encoded = encodingMap[zhar]; } else { encoded = encodeString(Character.toString((char) zhar)); } return encoded; } /** * Calls URLEncoder.encode(s, "UTF-8") on given input. * * @param s string to encode * @return encoded string returned by URLEncoder.encode(s, "UTF-8") */ public static String encodeString(String s) { try { return URLEncoder.encode(s, UTF_8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static String pathParamReplacement(String segment, List params) { StringBuilder newSegment = new StringBuilder(); Matcher matcher = PARAM_REPLACEMENT.matcher(segment); int i = 0; int start = 0; while (matcher.find()) { newSegment.append(segment, start, matcher.start()); String replacement = params.get(i++); newSegment.append(replacement); start = matcher.end(); } newSegment.append(segment, start, segment.length()); segment = newSegment.toString(); return segment; } /** * decode an encoded map * * @param map map * @return decoded map */ public static MultivaluedMap decode(MultivaluedMap map) { MultivaluedMap decoded = new QuarkusMultivaluedHashMap(); for (Map.Entry> entry : map.entrySet()) { List values = entry.getValue(); for (String value : values) { try { decoded.add(URLDecoder.decode(entry.getKey(), UTF_8), URLDecoder.decode(value, UTF_8)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } } return decoded; } /** * decode an encoded map * * @param map map * @param charset charset * @return decoded map */ public static MultivaluedMap decode(MultivaluedMap map, String charset) { if (charset == null) { charset = UTF_8; } MultivaluedMap decoded = new QuarkusMultivaluedHashMap(); for (Map.Entry> entry : map.entrySet()) { List values = entry.getValue(); for (String value : values) { try { decoded.add(URLDecoder.decode(entry.getKey(), charset), URLDecoder.decode(value, charset)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } } return decoded; } public static MultivaluedMap encode(MultivaluedMap map) { MultivaluedMap decoded = new QuarkusMultivaluedHashMap(); for (Map.Entry> entry : map.entrySet()) { List values = entry.getValue(); for (String value : values) { try { decoded.add(URLEncoder.encode(entry.getKey(), UTF_8), URLEncoder.encode(value, UTF_8)); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } } return decoded; } public static String decode(String string) { try { return URLDecoder.decode(string, UTF_8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy