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.http.client.utils;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.Reader;
34  import java.net.URI;
35  import java.nio.ByteBuffer;
36  import java.nio.CharBuffer;
37  import java.nio.charset.Charset;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.BitSet;
41  import java.util.Collections;
42  import java.util.List;
43  import java.util.Scanner;
44  
45  import org.apache.http.Consts;
46  import org.apache.http.Header;
47  import org.apache.http.HeaderElement;
48  import org.apache.http.HttpEntity;
49  import org.apache.http.NameValuePair;
50  import org.apache.http.entity.ContentType;
51  import org.apache.http.message.BasicNameValuePair;
52  import org.apache.http.message.ParserCursor;
53  import org.apache.http.message.TokenParser;
54  import org.apache.http.protocol.HTTP;
55  import org.apache.http.util.Args;
56  import org.apache.http.util.CharArrayBuffer;
57  
58  /**
59   * A collection of utilities for encoding URLs.
60   *
61   * @since 4.0
62   */
63  public class URLEncodedUtils {
64  
65      /**
66       * The default HTML form content type.
67       */
68      public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
69  
70      private static final char QP_SEP_A = '&';
71      private static final char QP_SEP_S = ';';
72      private static final String NAME_VALUE_SEPARATOR = "=";
73      private static final char PATH_SEPARATOR = '/';
74  
75      private static final BitSet PATH_SEPARATORS     = new BitSet(256);
76      static {
77          PATH_SEPARATORS.set(PATH_SEPARATOR);
78      }
79  
80      /**
81       * @deprecated 4.5 Use {@link #parse(URI, Charset)}
82       */
83      @Deprecated
84      public static List <NameValuePair> parse(final URI uri, final String charsetName) {
85          return parse(uri, charsetName != null ? Charset.forName(charsetName) : null);
86      }
87  
88      /**
89       * Returns a list of {@link NameValuePair}s URI query parameters.
90       * By convention, {@code '&'} and {@code ';'} are accepted as parameter separators.
91       *
92       * @param uri input URI.
93       * @param charset parameter charset.
94       * @return list of query parameters.
95       *
96       * @since 4.5
97       */
98      public static List <NameValuePair> parse(final URI uri, final Charset charset) {
99          Args.notNull(uri, "URI");
100         final String query = uri.getRawQuery();
101         if (query != null && !query.isEmpty()) {
102             return parse(query, charset);
103         }
104         return createEmptyList();
105     }
106 
107     /**
108      * Returns a list of {@link NameValuePair NameValuePairs} as parsed from an {@link HttpEntity}.
109      * The encoding is taken from the entity's Content-Encoding header.
110      * <p>
111      * This is typically used while parsing an HTTP POST.
112      *
113      * @param entity
114      *            The entity to parse
115      * @return a list of {@link NameValuePair} as built from the URI's query portion.
116      * @throws IOException
117      *             If there was an exception getting the entity's data.
118      */
119     public static List <NameValuePair> parse(
120             final HttpEntity entity) throws IOException {
121         Args.notNull(entity, "HTTP entity");
122         final ContentType contentType = ContentType.get(entity);
123         if (contentType == null || !contentType.getMimeType().equalsIgnoreCase(CONTENT_TYPE)) {
124             return createEmptyList();
125         }
126         final long len = entity.getContentLength();
127         Args.check(len <= Integer.MAX_VALUE, "HTTP entity is too large");
128         final Charset charset = contentType.getCharset() != null ? contentType.getCharset() : HTTP.DEF_CONTENT_CHARSET;
129         final InputStream inStream = entity.getContent();
130         if (inStream == null) {
131             return createEmptyList();
132         }
133         final CharArrayBuffer buf;
134         try {
135             buf = new CharArrayBuffer(len > 0 ? (int) len : 1024);
136             final Reader reader = new InputStreamReader(inStream, charset);
137             final char[] tmp = new char[1024];
138             int l;
139             while((l = reader.read(tmp)) != -1) {
140                 buf.append(tmp, 0, l);
141             }
142 
143         } finally {
144             inStream.close();
145         }
146         if (buf.isEmpty()) {
147             return createEmptyList();
148         }
149         return parse(buf, charset, QP_SEP_A);
150     }
151 
152     /**
153      * Returns true if the entity's Content-Type header is
154      * {@code application/x-www-form-urlencoded}.
155      */
156     public static boolean isEncoded(final HttpEntity entity) {
157         Args.notNull(entity, "HTTP entity");
158         final Header h = entity.getContentType();
159         if (h != null) {
160             final HeaderElement[] elems = h.getElements();
161             if (elems.length > 0) {
162                 final String contentType = elems[0].getName();
163                 return contentType.equalsIgnoreCase(CONTENT_TYPE);
164             }
165         }
166         return false;
167     }
168 
169     /**
170      * Adds all parameters within the Scanner to the list of {@code parameters}, as encoded by
171      * {@code encoding}. For example, a scanner containing the string {@code a=1&b=2&c=3} would add the
172      * {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the list of parameters. By convention, {@code '&'} and
173      * {@code ';'} are accepted as parameter separators.
174      *
175      * @param parameters
176      *            List to add parameters to.
177      * @param scanner
178      *            Input that contains the parameters to parse.
179      * @param charset
180      *            Encoding to use when decoding the parameters.
181      *
182      * @deprecated (4.4) use {@link #parse(String, java.nio.charset.Charset)}
183      */
184     @Deprecated
185     public static void parse(
186             final List<NameValuePair> parameters,
187             final Scanner scanner,
188             final String charset) {
189         parse(parameters, scanner, "[" + QP_SEP_A + QP_SEP_S + "]", charset);
190     }
191 
192     /**
193      * Adds all parameters within the Scanner to the list of
194      * {@code parameters}, as encoded by {@code encoding}. For
195      * example, a scanner containing the string {@code a=1&b=2&c=3} would
196      * add the {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the
197      * list of parameters.
198      *
199      * @param parameters
200      *            List to add parameters to.
201      * @param scanner
202      *            Input that contains the parameters to parse.
203      * @param parameterSepartorPattern
204      *            The Pattern string for parameter separators, by convention {@code "[&;]"}
205      * @param charset
206      *            Encoding to use when decoding the parameters.
207      *
208      * @deprecated (4.4) use {@link #parse(org.apache.http.util.CharArrayBuffer, java.nio.charset.Charset, char...)}
209      */
210     @Deprecated
211     public static void parse(
212             final List <NameValuePair> parameters,
213             final Scanner scanner,
214             final String parameterSepartorPattern,
215             final String charset) {
216         scanner.useDelimiter(parameterSepartorPattern);
217         while (scanner.hasNext()) {
218             final String name;
219             final String value;
220             final String token = scanner.next();
221             final int i = token.indexOf(NAME_VALUE_SEPARATOR);
222             if (i != -1) {
223                 name = decodeFormFields(token.substring(0, i).trim(), charset);
224                 value = decodeFormFields(token.substring(i + 1).trim(), charset);
225             } else {
226                 name = decodeFormFields(token.trim(), charset);
227                 value = null;
228             }
229             parameters.add(new BasicNameValuePair(name, value));
230         }
231     }
232 
233     /**
234      * Returns a list of {@link NameValuePair}s URI query parameters.
235      * By convention, {@code '&'} and {@code ';'} are accepted as parameter separators.
236      *
237      * @param s URI query component.
238      * @param charset charset to use when decoding the parameters.
239      * @return list of query parameters.
240      *
241      * @since 4.2
242      */
243     public static List<NameValuePair> parse(final String s, final Charset charset) {
244         if (s == null) {
245             return createEmptyList();
246         }
247         final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
248         buffer.append(s);
249         return parse(buffer, charset, QP_SEP_A, QP_SEP_S);
250     }
251 
252     /**
253      * Returns a list of {@link NameValuePair NameValuePairs} as parsed from the given string using the given character
254      * encoding.
255      *
256      * @param s input text.
257      * @param charset parameter charset.
258      * @param separators parameter separators.
259      * @return list of query parameters.
260      *
261      * @since 4.3
262      */
263     public static List<NameValuePair> parse(final String s, final Charset charset, final char... separators) {
264         if (s == null) {
265             return createEmptyList();
266         }
267         final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
268         buffer.append(s);
269         return parse(buffer, charset, separators);
270     }
271 
272     /**
273      * Returns a list of {@link NameValuePair}s parameters.
274      *
275      * @param buf
276      *            text to parse.
277      * @param charset
278      *            Encoding to use when decoding the parameters.
279      * @param separators
280      *            element separators.
281      * @return a list of {@link NameValuePair} as built from the URI's query portion.
282      *
283      * @since 4.4
284      */
285     public static List<NameValuePair> parse(
286             final CharArrayBuffer buf, final Charset charset, final char... separators) {
287         Args.notNull(buf, "Char array buffer");
288         final TokenParser tokenParser = TokenParser.INSTANCE;
289         final BitSet delimSet = new BitSet();
290         for (final char separator: separators) {
291             delimSet.set(separator);
292         }
293         final ParserCursor cursor = new ParserCursor(0, buf.length());
294         final List<NameValuePair> list = new ArrayList<NameValuePair>();
295         while (!cursor.atEnd()) {
296             delimSet.set('=');
297             final String name = tokenParser.parseToken(buf, cursor, delimSet);
298             String value = null;
299             if (!cursor.atEnd()) {
300                 final int delim = buf.charAt(cursor.getPos());
301                 cursor.updatePos(cursor.getPos() + 1);
302                 if (delim == '=') {
303                     delimSet.clear('=');
304                     value = tokenParser.parseToken(buf, cursor, delimSet);
305                     if (!cursor.atEnd()) {
306                         cursor.updatePos(cursor.getPos() + 1);
307                     }
308                 }
309             }
310             if (!name.isEmpty()) {
311                 list.add(new BasicNameValuePair(
312                         decodeFormFields(name, charset),
313                         decodeFormFields(value, charset)));
314             }
315         }
316         return list;
317     }
318 
319     static List<String> splitSegments(final CharSequence s, final BitSet separators) {
320         final ParserCursor cursor = new ParserCursor(0, s.length());
321         // Skip leading separator
322         if (cursor.atEnd()) {
323             return Collections.emptyList();
324         }
325         if (separators.get(s.charAt(cursor.getPos()))) {
326             cursor.updatePos(cursor.getPos() + 1);
327         }
328         final List<String> list = new ArrayList<String>();
329         final StringBuilder buf = new StringBuilder();
330         for (;;) {
331             if (cursor.atEnd()) {
332                 list.add(buf.toString());
333                 break;
334             }
335             final char current = s.charAt(cursor.getPos());
336             if (separators.get(current)) {
337                 list.add(buf.toString());
338                 buf.setLength(0);
339             } else {
340                 buf.append(current);
341             }
342             cursor.updatePos(cursor.getPos() + 1);
343         }
344         return list;
345     }
346 
347     static List<String> splitPathSegments(final CharSequence s) {
348         return splitSegments(s, PATH_SEPARATORS);
349     }
350 
351     /**
352      * Returns a list of URI path segments.
353      *
354      * @param s URI path component.
355      * @param charset parameter charset.
356      * @return list of segments.
357      *
358      * @since 4.5
359      */
360     public static List<String> parsePathSegments(final CharSequence s, final Charset charset) {
361         Args.notNull(s, "Char sequence");
362         final List<String> list = splitPathSegments(s);
363         for (int i = 0; i < list.size(); i++) {
364             list.set(i, urlDecode(list.get(i), charset != null ? charset : Consts.UTF_8, false));
365         }
366         return list;
367     }
368 
369     /**
370      * Returns a list of URI path segments.
371      *
372      * @param s URI path component.
373      * @return list of segments.
374      *
375      * @since 4.5
376      */
377     public static List<String> parsePathSegments(final CharSequence s) {
378         return parsePathSegments(s, Consts.UTF_8);
379     }
380 
381     /**
382      * Returns a string consisting of joint encoded path segments.
383      *
384      * @param segments the segments.
385      * @param charset parameter charset.
386      * @return URI path component
387      *
388      * @since 4.5
389      */
390     public static String formatSegments(final Iterable<String> segments, final Charset charset) {
391         Args.notNull(segments, "Segments");
392         final StringBuilder result = new StringBuilder();
393         for (final String segment : segments) {
394             result.append(PATH_SEPARATOR).append(urlEncode(segment, charset, PATHSAFE, false));
395         }
396         return result.toString();
397     }
398 
399     /**
400      * Returns a string consisting of joint encoded path segments.
401      *
402      * @param segments the segments.
403      * @return URI path component
404      *
405      * @since 4.5
406      */
407     public static String formatSegments(final String... segments) {
408         return formatSegments(Arrays.asList(segments), Consts.UTF_8);
409     }
410 
411     /**
412      * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
413      * list of parameters in an HTTP PUT or HTTP POST.
414      *
415      * @param parameters  The parameters to include.
416      * @param charset The encoding to use.
417      * @return An {@code application/x-www-form-urlencoded} string
418      */
419     public static String format(
420             final List <? extends NameValuePair> parameters,
421             final String charset) {
422         return format(parameters, QP_SEP_A, charset);
423     }
424 
425     /**
426      * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
427      * list of parameters in an HTTP PUT or HTTP POST.
428      *
429      * @param parameters  The parameters to include.
430      * @param parameterSeparator The parameter separator, by convention, {@code '&'} or {@code ';'}.
431      * @param charset The encoding to use.
432      * @return An {@code application/x-www-form-urlencoded} string
433      *
434      * @since 4.3
435      */
436     public static String format(
437             final List <? extends NameValuePair> parameters,
438             final char parameterSeparator,
439             final String charset) {
440         final StringBuilder result = new StringBuilder();
441         for (final NameValuePair parameter : parameters) {
442             final String encodedName = encodeFormFields(parameter.getName(), charset);
443             final String encodedValue = encodeFormFields(parameter.getValue(), charset);
444             if (result.length() > 0) {
445                 result.append(parameterSeparator);
446             }
447             result.append(encodedName);
448             if (encodedValue != null) {
449                 result.append(NAME_VALUE_SEPARATOR);
450                 result.append(encodedValue);
451             }
452         }
453         return result.toString();
454     }
455 
456     /**
457      * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
458      * list of parameters in an HTTP PUT or HTTP POST.
459      *
460      * @param parameters  The parameters to include.
461      * @param charset The encoding to use.
462      * @return An {@code application/x-www-form-urlencoded} string
463      *
464      * @since 4.2
465      */
466     public static String format(
467             final Iterable<? extends NameValuePair> parameters,
468             final Charset charset) {
469         return format(parameters, QP_SEP_A, charset);
470     }
471 
472     /**
473      * Returns a String that is suitable for use as an {@code application/x-www-form-urlencoded}
474      * list of parameters in an HTTP PUT or HTTP POST.
475      *
476      * @param parameters  The parameters to include.
477      * @param parameterSeparator The parameter separator, by convention, {@code '&'} or {@code ';'}.
478      * @param charset The encoding to use.
479      * @return An {@code application/x-www-form-urlencoded} string
480      *
481      * @since 4.3
482      */
483     public static String format(
484             final Iterable<? extends NameValuePair> parameters,
485             final char parameterSeparator,
486             final Charset charset) {
487         Args.notNull(parameters, "Parameters");
488         final StringBuilder result = new StringBuilder();
489         for (final NameValuePair parameter : parameters) {
490             final String encodedName = encodeFormFields(parameter.getName(), charset);
491             final String encodedValue = encodeFormFields(parameter.getValue(), charset);
492             if (result.length() > 0) {
493                 result.append(parameterSeparator);
494             }
495             result.append(encodedName);
496             if (encodedValue != null) {
497                 result.append(NAME_VALUE_SEPARATOR);
498                 result.append(encodedValue);
499             }
500         }
501         return result.toString();
502     }
503 
504     /**
505      * Unreserved characters, i.e. alphanumeric, plus: {@code _ - ! . ~ ' ( ) *}
506      * <p>
507      *  This list is the same as the {@code unreserved} list in
508      *  <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
509      */
510     private static final BitSet UNRESERVED   = new BitSet(256);
511     /**
512      * Punctuation characters: , ; : $ & + =
513      * <p>
514      * These are the additional characters allowed by userinfo.
515      */
516     private static final BitSet PUNCT        = new BitSet(256);
517     /** Characters which are safe to use in userinfo,
518      * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation */
519     private static final BitSet USERINFO     = new BitSet(256);
520     /** Characters which are safe to use in a path,
521      * i.e. {@link #UNRESERVED} plus {@link #PUNCT}uation plus / @ */
522     private static final BitSet PATHSAFE     = new BitSet(256);
523     /** Characters which are safe to use in a query or a fragment,
524      * i.e. {@link #RESERVED} plus {@link #UNRESERVED} */
525     private static final BitSet URIC     = new BitSet(256);
526 
527     /**
528      * Reserved characters, i.e. {@code ;/?:@&=+$,[]}
529      * <p>
530      *  This list is the same as the {@code reserved} list in
531      *  <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>
532      *  as augmented by
533      *  <a href="http://www.ietf.org/rfc/rfc2732.txt">RFC 2732</a>
534      */
535     private static final BitSet RESERVED     = new BitSet(256);
536 
537 
538     /**
539      * Safe characters for x-www-form-urlencoded data, as per java.net.URLEncoder and browser behaviour,
540      * i.e. alphanumeric plus {@code "-", "_", ".", "*"}
541      */
542     private static final BitSet URLENCODER   = new BitSet(256);
543 
544     private static final BitSet PATH_SPECIAL = new BitSet(256);
545 
546     static {
547         // unreserved chars
548         // alpha characters
549         for (int i = 'a'; i <= 'z'; i++) {
550             UNRESERVED.set(i);
551         }
552         for (int i = 'A'; i <= 'Z'; i++) {
553             UNRESERVED.set(i);
554         }
555         // numeric characters
556         for (int i = '0'; i <= '9'; i++) {
557             UNRESERVED.set(i);
558         }
559         UNRESERVED.set('_'); // these are the charactes of the "mark" list
560         UNRESERVED.set('-');
561         UNRESERVED.set('.');
562         UNRESERVED.set('*');
563         URLENCODER.or(UNRESERVED); // skip remaining unreserved characters
564         UNRESERVED.set('!');
565         UNRESERVED.set('~');
566         UNRESERVED.set('\'');
567         UNRESERVED.set('(');
568         UNRESERVED.set(')');
569         // punct chars
570         PUNCT.set(',');
571         PUNCT.set(';');
572         PUNCT.set(':');
573         PUNCT.set('$');
574         PUNCT.set('&');
575         PUNCT.set('+');
576         PUNCT.set('=');
577         // Safe for userinfo
578         USERINFO.or(UNRESERVED);
579         USERINFO.or(PUNCT);
580 
581         // URL path safe
582         PATHSAFE.or(UNRESERVED);
583         PATHSAFE.set(';'); // param separator
584         PATHSAFE.set(':'); // RFC 2396
585         PATHSAFE.set('@');
586         PATHSAFE.set('&');
587         PATHSAFE.set('=');
588         PATHSAFE.set('+');
589         PATHSAFE.set('$');
590         PATHSAFE.set(',');
591 
592         PATH_SPECIAL.or(PATHSAFE);
593         PATH_SPECIAL.set('/');
594 
595         RESERVED.set(';');
596         RESERVED.set('/');
597         RESERVED.set('?');
598         RESERVED.set(':');
599         RESERVED.set('@');
600         RESERVED.set('&');
601         RESERVED.set('=');
602         RESERVED.set('+');
603         RESERVED.set('$');
604         RESERVED.set(',');
605         RESERVED.set('['); // added by RFC 2732
606         RESERVED.set(']'); // added by RFC 2732
607 
608         URIC.or(RESERVED);
609         URIC.or(UNRESERVED);
610     }
611 
612     private static final int RADIX = 16;
613 
614     private static List<NameValuePair> createEmptyList() {
615         return new ArrayList<NameValuePair>(0);
616     }
617 
618     private static String urlEncode(
619             final String content,
620             final Charset charset,
621             final BitSet safechars,
622             final boolean blankAsPlus) {
623         if (content == null) {
624             return null;
625         }
626         final StringBuilder buf = new StringBuilder();
627         final ByteBuffer bb = charset.encode(content);
628         while (bb.hasRemaining()) {
629             final int b = bb.get() & 0xff;
630             if (safechars.get(b)) {
631                 buf.append((char) b);
632             } else if (blankAsPlus && b == ' ') {
633                 buf.append('+');
634             } else {
635                 buf.append("%");
636                 final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
637                 final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
638                 buf.append(hex1);
639                 buf.append(hex2);
640             }
641         }
642         return buf.toString();
643     }
644 
645     /**
646      * Decode/unescape a portion of a URL, to use with the query part ensure {@code plusAsBlank} is true.
647      *
648      * @param content the portion to decode
649      * @param charset the charset to use
650      * @param plusAsBlank if {@code true}, then convert '+' to space (e.g. for www-url-form-encoded content), otherwise leave as is.
651      * @return encoded string
652      */
653     private static String urlDecode(
654             final String content,
655             final Charset charset,
656             final boolean plusAsBlank) {
657         if (content == null) {
658             return null;
659         }
660         final ByteBuffer bb = ByteBuffer.allocate(content.length());
661         final CharBuffer cb = CharBuffer.wrap(content);
662         while (cb.hasRemaining()) {
663             final char c = cb.get();
664             if (c == '%' && cb.remaining() >= 2) {
665                 final char uc = cb.get();
666                 final char lc = cb.get();
667                 final int u = Character.digit(uc, 16);
668                 final int l = Character.digit(lc, 16);
669                 if (u != -1 && l != -1) {
670                     bb.put((byte) ((u << 4) + l));
671                 } else {
672                     bb.put((byte) '%');
673                     bb.put((byte) uc);
674                     bb.put((byte) lc);
675                 }
676             } else if (plusAsBlank && c == '+') {
677                 bb.put((byte) ' ');
678             } else {
679                 bb.put((byte) c);
680             }
681         }
682         bb.flip();
683         return charset.decode(bb).toString();
684     }
685 
686     /**
687      * Decode/unescape www-url-form-encoded content.
688      *
689      * @param content the content to decode, will decode '+' as space
690      * @param charset the charset to use
691      * @return encoded string
692      */
693     private static String decodeFormFields (final String content, final String charset) {
694         if (content == null) {
695             return null;
696         }
697         return urlDecode(content, charset != null ? Charset.forName(charset) : Consts.UTF_8, true);
698     }
699 
700     /**
701      * Decode/unescape www-url-form-encoded content.
702      *
703      * @param content the content to decode, will decode '+' as space
704      * @param charset the charset to use
705      * @return encoded string
706      */
707     private static String decodeFormFields (final String content, final Charset charset) {
708         if (content == null) {
709             return null;
710         }
711         return urlDecode(content, charset != null ? charset : Consts.UTF_8, true);
712     }
713 
714     /**
715      * Encode/escape www-url-form-encoded content.
716      * <p>
717      * Uses the {@link #URLENCODER} set of characters, rather than
718      * the {@link #UNRESERVED} set; this is for compatibilty with previous
719      * releases, URLEncoder.encode() and most browsers.
720      *
721      * @param content the content to encode, will convert space to '+'
722      * @param charset the charset to use
723      * @return encoded string
724      */
725     private static String encodeFormFields(final String content, final String charset) {
726         if (content == null) {
727             return null;
728         }
729         return urlEncode(content, charset != null ? Charset.forName(charset) : Consts.UTF_8, URLENCODER, true);
730     }
731 
732     /**
733      * Encode/escape www-url-form-encoded content.
734      * <p>
735      * Uses the {@link #URLENCODER} set of characters, rather than
736      * the {@link #UNRESERVED} set; this is for compatibilty with previous
737      * releases, URLEncoder.encode() and most browsers.
738      *
739      * @param content the content to encode, will convert space to '+'
740      * @param charset the charset to use
741      * @return encoded string
742      */
743     private static String encodeFormFields (final String content, final Charset charset) {
744         if (content == null) {
745             return null;
746         }
747         return urlEncode(content, charset != null ? charset : Consts.UTF_8, URLENCODER, true);
748     }
749 
750     /**
751      * Encode a String using the {@link #USERINFO} set of characters.
752      * <p>
753      * Used by URIBuilder to encode the userinfo segment.
754      *
755      * @param content the string to encode, does not convert space to '+'
756      * @param charset the charset to use
757      * @return the encoded string
758      */
759     static String encUserInfo(final String content, final Charset charset) {
760         return urlEncode(content, charset, USERINFO, false);
761     }
762 
763     /**
764      * Encode a String using the {@link #URIC} set of characters.
765      * <p>
766      * Used by URIBuilder to encode the query and fragment segments.
767      *
768      * @param content the string to encode, does not convert space to '+'
769      * @param charset the charset to use
770      * @return the encoded string
771      */
772     static String encUric(final String content, final Charset charset) {
773         return urlEncode(content, charset, URIC, false);
774     }
775 
776     /**
777      * Encode a String using the {@link #PATH_SPECIAL} set of characters.
778      * <p>
779      * Used by URIBuilder to encode path segments.
780      *
781      * @param content the string to encode, does not convert space to '+'
782      * @param charset the charset to use
783      * @return the encoded string
784      */
785     static String encPath(final String content, final Charset charset) {
786         return urlEncode(content, charset, PATH_SPECIAL, false);
787     }
788 
789 }