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

org.keycloak.common.util.Encode Maven / Gradle / Ivy

There is a newer version: 26.0.3
Show newest version
/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed 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.
 */

package org.keycloak.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.Charset;
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;

/**
 * @author Bill Burke
 * @version $Revision: 1 $
 */
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];
   private static final String[] userInfoStringEncoding = 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;
         }
         try {
            pathEncoding[i] = URLEncoder.encode(String.valueOf((char) i), UTF_8);
         } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
         }
      }
      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 '~':
            case '?':
               continue;
            case ' ':
               queryNameValueEncoding[i] = "+";
               continue;
         }
         try {
            queryNameValueEncoding[i] = URLEncoder.encode(String.valueOf((char) i), UTF_8);
         } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
         }
      }

      /*
       * 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;
         }
         try {
            queryStringEncoding[i] = URLEncoder.encode(String.valueOf((char) i), UTF_8);
         } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
         }
      }

     /*
      * userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
      * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
      * pct-encoded   = "%" HEXDIG HEXDIG
      * 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 ':':
               continue;
            case ' ':
               userInfoStringEncoding[i] = "%20";
               continue;
         }
         userInfoStringEncoding[i] = URLEncoder.encode(String.valueOf((char) i));
      }
   }

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

   /**
    * Keep encoded values "%..." but not the template parameters.
    * @param value
    * @return
    */
   public static String encodeQueryStringNotTemplateParameters(String value) {
      return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false));
   }

   /**
    * Keep encoded values "%..." and template parameters intact.
    * @param value The user-info value to encode
    * @return The user-info encoded
    */
   public static String encodeUserInfo(String value) {
      return encodeValue(value, userInfoStringEncoding);
   }

   /**
    * Keep encoded values "%..." but not the template parameters.
    * @param value The user-info to encode
    * @return The user-info encoded
    */
   public static String encodeUserInfoNotTemplateParameters(String value) {
      return encodeNonCodes(encodeFromArray(value, userInfoStringEncoding, false));
   }

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

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

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

   /**
    * Keep encoded values "%..." but not the template parameters.
    * @param value
    * @return
    */
   public static String encodeFragmentNotTemplateParameters(String value)
   {
      return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false));
   }

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

   /**
    * Keep encoded values "%..." and template parameters intact.
    */
   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)
   {
      Matcher matcher = encodedCharsMulti.matcher(path);
      int start=0;
      StringBuilder builder = new StringBuilder();
      CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
      while (matcher.find())
      {
    	 builder.append(path, start, matcher.start());
         decoder.reset();
         String decoded = decodeBytes(matcher.group(1), decoder);
         builder.append(decoded);
         start = matcher.end();
      }
      builder.append(path, start, path.length());
      return builder.toString();
   }

   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
    * @return
    */
   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 segment, StringBuilder newSegment, List params)
   {
      boolean foundParam = false;
      // Regular expressions can have '{' and '}' characters.  Replace them to do match
      segment = PathHelper.replaceEnclosedCurlyBraces(segment);
      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
    * @param encoding
    * @return
    */
   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 along with '/'
    * 

* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" */ 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 * @return */ public static String encodePathSaveEncodings(String segment) { String result = encodeFromArray(segment, pathEncoding, false); result = encodeNonCodes(result); return result; } /** * Encode via RFC 3986. PCHAR is allowed along with '/' *

* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" */ 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 * @return */ 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 * @return */ 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 * @return */ public static String encodeQueryParamSaveEncodings(String segment) { String result = encodeFromArray(segment, queryNameValueEncoding, false); result = encodeNonCodes(result); return result; } /** * Encodes everything in user-info * * @param nameOrValue * @return */ public static String encodeUserInfoAsIs(String nameOrValue) { return encodeFromArray(nameOrValue, userInfoStringEncoding, true); } /** * Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p" * * @param segment * @return */ public static String encodeUserInfoSaveEncodings(String segment) { String result = encodeFromArray(segment, userInfoStringEncoding, 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; } 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 { try { encoded = URLEncoder.encode(Character.toString((char) zhar), UTF_8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } return encoded; } 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 * @return */ public static MultivaluedHashMap decode(MultivaluedHashMap map) { MultivaluedHashMap decoded = new MultivaluedHashMap(); 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 * @param charset * @return */ public static MultivaluedHashMap decode(MultivaluedHashMap map, String charset) { if (charset == null) { charset = UTF_8; } MultivaluedHashMap decoded = new MultivaluedHashMap(); 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 MultivaluedHashMap encode(MultivaluedHashMap map) { MultivaluedHashMap decoded = new MultivaluedHashMap(); 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); } } /** * @param string * @return URL encoded input */ public static String urlEncode(String string) { try { return URLEncoder.encode(string, UTF_8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * @param string * @return URL decoded input */ public static String urlDecode(String string) { return decode(string); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy