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