View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.net;
29  
30  import java.nio.ByteBuffer;
31  import java.nio.CharBuffer;
32  import java.nio.charset.Charset;
33  import java.nio.charset.StandardCharsets;
34  import java.util.BitSet;
35  
36  /**
37   * Percent-encoding mechanism defined in RFC 3986
38   *
39   * @since 5.1
40   */
41  public class PercentCodec {
42  
43      static final BitSet GEN_DELIMS = new BitSet(256);
44      static final BitSet SUB_DELIMS = new BitSet(256);
45      static final BitSet UNRESERVED = new BitSet(256);
46      static final BitSet URIC = new BitSet(256);
47  
48      static {
49          GEN_DELIMS.set(':');
50          GEN_DELIMS.set('/');
51          GEN_DELIMS.set('?');
52          GEN_DELIMS.set('#');
53          GEN_DELIMS.set('[');
54          GEN_DELIMS.set(']');
55          GEN_DELIMS.set('@');
56  
57          SUB_DELIMS.set('!');
58          SUB_DELIMS.set('$');
59          SUB_DELIMS.set('&');
60          SUB_DELIMS.set('\'');
61          SUB_DELIMS.set('(');
62          SUB_DELIMS.set(')');
63          SUB_DELIMS.set('*');
64          SUB_DELIMS.set('+');
65          SUB_DELIMS.set(',');
66          SUB_DELIMS.set(';');
67          SUB_DELIMS.set('=');
68  
69          for (int i = 'a'; i <= 'z'; i++) {
70              UNRESERVED.set(i);
71          }
72          for (int i = 'A'; i <= 'Z'; i++) {
73              UNRESERVED.set(i);
74          }
75          // numeric characters
76          for (int i = '0'; i <= '9'; i++) {
77              UNRESERVED.set(i);
78          }
79          UNRESERVED.set('-');
80          UNRESERVED.set('.');
81          UNRESERVED.set('_');
82          UNRESERVED.set('~');
83          URIC.or(SUB_DELIMS);
84          URIC.or(UNRESERVED);
85      }
86  
87      private static final int RADIX = 16;
88  
89      static void encode(final StringBuilder buf, final CharSequence content, final Charset charset,
90                         final BitSet safechars, final boolean blankAsPlus) {
91          if (content == null) {
92              return;
93          }
94          final CharBuffer cb = CharBuffer.wrap(content);
95          final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb);
96          while (bb.hasRemaining()) {
97              final int b = bb.get() & 0xff;
98              if (safechars.get(b)) {
99                  buf.append((char) b);
100             } else if (blankAsPlus && b == ' ') {
101                 buf.append("+");
102             } else {
103                 buf.append("%");
104                 final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
105                 final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
106                 buf.append(hex1);
107                 buf.append(hex2);
108             }
109         }
110     }
111 
112     static void encode(final StringBuilder buf, final CharSequence content, final Charset charset, final boolean blankAsPlus) {
113         encode(buf, content, charset, UNRESERVED, blankAsPlus);
114     }
115 
116     public static void encode(final StringBuilder buf, final CharSequence content, final Charset charset) {
117         encode(buf, content, charset, UNRESERVED, false);
118     }
119 
120     public static String encode(final CharSequence content, final Charset charset) {
121         if (content == null) {
122             return null;
123         }
124         final StringBuilder buf = new StringBuilder();
125         encode(buf, content, charset, UNRESERVED, false);
126         return buf.toString();
127     }
128 
129     static String decode(final CharSequence content, final Charset charset, final boolean plusAsBlank) {
130         if (content == null) {
131             return null;
132         }
133         final ByteBuffer bb = ByteBuffer.allocate(content.length());
134         final CharBuffer cb = CharBuffer.wrap(content);
135         while (cb.hasRemaining()) {
136             final char c = cb.get();
137             if (c == '%' && cb.remaining() >= 2) {
138                 final char uc = cb.get();
139                 final char lc = cb.get();
140                 final int u = Character.digit(uc, RADIX);
141                 final int l = Character.digit(lc, RADIX);
142                 if (u != -1 && l != -1) {
143                     bb.put((byte) ((u << 4) + l));
144                 } else {
145                     bb.put((byte) '%');
146                     bb.put((byte) uc);
147                     bb.put((byte) lc);
148                 }
149             } else if (plusAsBlank && c == '+') {
150                 bb.put((byte) ' ');
151             } else {
152                 bb.put((byte) c);
153             }
154         }
155         bb.flip();
156         return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString();
157     }
158 
159     public static String decode(final CharSequence content, final Charset charset) {
160         return decode(content, charset, false);
161     }
162 
163 }