1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.http;
17
18 import io.netty.util.internal.ObjectUtil;
19
20 import java.io.UnsupportedEncodingException;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.net.URLEncoder;
24 import java.nio.charset.Charset;
25 import java.nio.charset.UnsupportedCharsetException;
26
27 /**
28 * Creates an URL-encoded URI from a path string and key-value parameter pairs.
29 * This encoder is for one time use only. Create a new instance for each URI.
30 *
31 * <pre>
32 * {@link QueryStringEncoder} encoder = new {@link QueryStringEncoder}("/hello");
33 * encoder.addParam("recipient", "world");
34 * assert encoder.toString().equals("/hello?recipient=world");
35 * </pre>
36 * @see QueryStringDecoder
37 */
38 public class QueryStringEncoder {
39
40 private final String charsetName;
41 private final StringBuilder uriBuilder;
42 private boolean hasParams;
43
44 /**
45 * Creates a new encoder that encodes a URI that starts with the specified
46 * path string. The encoder will encode the URI in UTF-8.
47 */
48 public QueryStringEncoder(String uri) {
49 this(uri, HttpConstants.DEFAULT_CHARSET);
50 }
51
52 /**
53 * Creates a new encoder that encodes a URI that starts with the specified
54 * path string in the specified charset.
55 */
56 public QueryStringEncoder(String uri, Charset charset) {
57 uriBuilder = new StringBuilder(uri);
58 charsetName = charset.name();
59 }
60
61 /**
62 * Adds a parameter with the specified name and value to this encoder.
63 */
64 public void addParam(String name, String value) {
65 ObjectUtil.checkNotNull(name, "name");
66 if (hasParams) {
67 uriBuilder.append('&');
68 } else {
69 uriBuilder.append('?');
70 hasParams = true;
71 }
72 appendComponent(name, charsetName, uriBuilder);
73 if (value != null) {
74 uriBuilder.append('=');
75 appendComponent(value, charsetName, uriBuilder);
76 }
77 }
78
79 /**
80 * Returns the URL-encoded URI object which was created from the path string
81 * specified in the constructor and the parameters added by
82 * {@link #addParam(String, String)} method.
83 */
84 public URI toUri() throws URISyntaxException {
85 return new URI(toString());
86 }
87
88 /**
89 * Returns the URL-encoded URI which was created from the path string
90 * specified in the constructor and the parameters added by
91 * {@link #addParam(String, String)} method.
92 */
93 @Override
94 public String toString() {
95 return uriBuilder.toString();
96 }
97
98 private static void appendComponent(String s, String charset, StringBuilder sb) {
99 try {
100 s = URLEncoder.encode(s, charset);
101 } catch (UnsupportedEncodingException ignored) {
102 throw new UnsupportedCharsetException(charset);
103 }
104 // replace all '+' with "%20"
105 int idx = s.indexOf('+');
106 if (idx == -1) {
107 sb.append(s);
108 return;
109 }
110 sb.append(s, 0, idx).append("%20");
111 int size = s.length();
112 idx++;
113 for (; idx < size; idx++) {
114 char c = s.charAt(idx);
115 if (c != '+') {
116 sb.append(c);
117 } else {
118 sb.append("%20");
119 }
120 }
121 }
122 }