root / src / org / gss_project / gss / server / rest / RequestUtil.java @ 1206:292dec4eae08
History | View | Annotate | Download (14.4 kB)
1 |
/*
|
---|---|
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more
|
3 |
* contributor license agreements. See the NOTICE file distributed with
|
4 |
* this work for additional information regarding copyright ownership.
|
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0
|
6 |
* (the "License"); you may not use this file except in compliance with
|
7 |
* the License. You may obtain a copy of the License at
|
8 |
*
|
9 |
* http://www.apache.org/licenses/LICENSE-2.0
|
10 |
*
|
11 |
* Unless required by applicable law or agreed to in writing, software
|
12 |
* distributed under the License is distributed on an "AS IS" BASIS,
|
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14 |
* See the License for the specific language governing permissions and
|
15 |
* limitations under the License.
|
16 |
*/
|
17 |
package org.gss_project.gss.server.rest; |
18 |
|
19 |
import java.io.UnsupportedEncodingException; |
20 |
import java.text.SimpleDateFormat; |
21 |
import java.util.Map; |
22 |
import java.util.TimeZone; |
23 |
|
24 |
|
25 |
/**
|
26 |
* General purpose request parsing and encoding utility methods.
|
27 |
*
|
28 |
* @author Craig R. McClanahan
|
29 |
* @author Tim Tye
|
30 |
* @version $Revision$ $Date$
|
31 |
*/
|
32 |
|
33 |
public final class RequestUtil { |
34 |
|
35 |
|
36 |
/**
|
37 |
* The DateFormat to use for generating readable dates in cookies.
|
38 |
*/
|
39 |
private static SimpleDateFormat format = |
40 |
new SimpleDateFormat(" EEEE, dd-MMM-yy kk:mm:ss zz"); |
41 |
|
42 |
static {
|
43 |
format.setTimeZone(TimeZone.getTimeZone("GMT")); |
44 |
} |
45 |
|
46 |
|
47 |
/**
|
48 |
* Filter the specified message string for characters that are sensitive
|
49 |
* in HTML. This avoids potential attacks caused by including JavaScript
|
50 |
* codes in the request URL that is often reported in error messages.
|
51 |
*
|
52 |
* @param message The message string to be filtered
|
53 |
* @return the filtered string
|
54 |
*/
|
55 |
public static String filter(String message) { |
56 |
|
57 |
if (message == null) |
58 |
return null; |
59 |
|
60 |
char content[] = new char[message.length()]; |
61 |
message.getChars(0, message.length(), content, 0); |
62 |
StringBuffer result = new StringBuffer(content.length + 50); |
63 |
for (int i = 0; i < content.length; i++) |
64 |
switch (content[i]) {
|
65 |
case '<': |
66 |
result.append("<");
|
67 |
break;
|
68 |
case '>': |
69 |
result.append(">");
|
70 |
break;
|
71 |
case '&': |
72 |
result.append("&");
|
73 |
break;
|
74 |
case '"': |
75 |
result.append(""");
|
76 |
break;
|
77 |
default:
|
78 |
result.append(content[i]); |
79 |
} |
80 |
return result.toString();
|
81 |
|
82 |
} |
83 |
|
84 |
|
85 |
/**
|
86 |
* Normalize a relative URI path that may have relative values ("/./",
|
87 |
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
|
88 |
* useful only for normalizing application-generated paths. It does not
|
89 |
* try to perform security checks for malicious input.
|
90 |
*
|
91 |
* @param path Relative path to be normalized
|
92 |
* @return the normalized string
|
93 |
*/
|
94 |
public static String normalize(String path) { |
95 |
return normalize(path, true); |
96 |
} |
97 |
|
98 |
/**
|
99 |
* Normalize a relative URI path that may have relative values ("/./",
|
100 |
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
|
101 |
* useful only for normalizing application-generated paths. It does not
|
102 |
* try to perform security checks for malicious input.
|
103 |
*
|
104 |
* @param path Relative path to be normalized
|
105 |
* @param replaceBackSlash Should '\\' be replaced with '/'
|
106 |
*/
|
107 |
public static String normalize(String path, boolean replaceBackSlash) { |
108 |
|
109 |
if (path == null) |
110 |
return null; |
111 |
|
112 |
// Create a place for the normalized path
|
113 |
String normalized = path;
|
114 |
|
115 |
if (replaceBackSlash && normalized.indexOf('\\') >= 0) |
116 |
normalized = normalized.replace('\\', '/'); |
117 |
|
118 |
if (normalized.equals("/.")) |
119 |
return "/"; |
120 |
|
121 |
// Add a leading "/" if necessary
|
122 |
if (!normalized.startsWith("/")) |
123 |
normalized = "/" + normalized;
|
124 |
|
125 |
// Resolve occurrences of "//" in the normalized path
|
126 |
while (true) { |
127 |
int index = normalized.indexOf("//"); |
128 |
if (index < 0) |
129 |
break;
|
130 |
normalized = normalized.substring(0, index) +
|
131 |
normalized.substring(index + 1);
|
132 |
} |
133 |
|
134 |
// Resolve occurrences of "/./" in the normalized path
|
135 |
while (true) { |
136 |
int index = normalized.indexOf("/./"); |
137 |
if (index < 0) |
138 |
break;
|
139 |
normalized = normalized.substring(0, index) +
|
140 |
normalized.substring(index + 2);
|
141 |
} |
142 |
|
143 |
// Resolve occurrences of "/../" in the normalized path
|
144 |
while (true) { |
145 |
int index = normalized.indexOf("/../"); |
146 |
if (index < 0) |
147 |
break;
|
148 |
if (index == 0) |
149 |
return null; // Trying to go outside our context |
150 |
int index2 = normalized.lastIndexOf('/', index - 1); |
151 |
normalized = normalized.substring(0, index2) +
|
152 |
normalized.substring(index + 3);
|
153 |
} |
154 |
|
155 |
// Return the normalized path that we have completed
|
156 |
return normalized;
|
157 |
|
158 |
} |
159 |
|
160 |
|
161 |
/**
|
162 |
* Append request parameters from the specified String to the specified
|
163 |
* Map. It is presumed that the specified Map is not accessed from any
|
164 |
* other thread, so no synchronization is performed.
|
165 |
* <p>
|
166 |
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
|
167 |
* individually on the parsed name and value elements, rather than on
|
168 |
* the entire query string ahead of time, to properly deal with the case
|
169 |
* where the name or value includes an encoded "=" or "&" character
|
170 |
* that would otherwise be interpreted as a delimiter.
|
171 |
*
|
172 |
* @param map Map that accumulates the resulting parameters
|
173 |
* @param data Input string containing request parameters
|
174 |
* @param encoding
|
175 |
* @throws UnsupportedEncodingException
|
176 |
*
|
177 |
* @exception IllegalArgumentException if the data is malformed
|
178 |
*/
|
179 |
public static void parseParameters(Map map, String data, String encoding) |
180 |
throws UnsupportedEncodingException { |
181 |
|
182 |
if (data != null && data.length() > 0) { |
183 |
|
184 |
// use the specified encoding to extract bytes out of the
|
185 |
// given string so that the encoding is not lost. If an
|
186 |
// encoding is not specified, let it use platform default
|
187 |
byte[] bytes = null; |
188 |
try {
|
189 |
if (encoding == null) |
190 |
bytes = data.getBytes(); |
191 |
else
|
192 |
bytes = data.getBytes(encoding); |
193 |
} catch (UnsupportedEncodingException uee) { |
194 |
} |
195 |
|
196 |
parseParameters(map, bytes, encoding); |
197 |
} |
198 |
|
199 |
} |
200 |
|
201 |
|
202 |
/**
|
203 |
* Decode and return the specified URL-encoded String.
|
204 |
* When the byte array is converted to a string, the system default
|
205 |
* character encoding is used... This may be different than some other
|
206 |
* servers. It is assumed the string is not a query string.
|
207 |
*
|
208 |
* @param str The url-encoded string
|
209 |
* @return the decoded string
|
210 |
*
|
211 |
* @exception IllegalArgumentException if a '%' character is not followed
|
212 |
* by a valid 2-digit hexadecimal number
|
213 |
*/
|
214 |
public static String URLDecode(String str) { |
215 |
return URLDecode(str, null); |
216 |
} |
217 |
|
218 |
|
219 |
/**
|
220 |
* Decode and return the specified URL-encoded String. It is assumed the
|
221 |
* string is not a query string.
|
222 |
*
|
223 |
* @param str The url-encoded string
|
224 |
* @param enc The encoding to use; if null, the default encoding is used
|
225 |
* @return the decoded string
|
226 |
* @exception IllegalArgumentException if a '%' character is not followed
|
227 |
* by a valid 2-digit hexadecimal number
|
228 |
*/
|
229 |
public static String URLDecode(String str, String enc) { |
230 |
return URLDecode(str, enc, false); |
231 |
} |
232 |
|
233 |
/**
|
234 |
* Decode and return the specified URL-encoded String.
|
235 |
*
|
236 |
* @param str The url-encoded string
|
237 |
* @param enc The encoding to use; if null, the default encoding is used
|
238 |
* @param isQuery Is this a query string being processed
|
239 |
* @return the decoded string
|
240 |
* @exception IllegalArgumentException if a '%' character is not followed
|
241 |
* by a valid 2-digit hexadecimal number
|
242 |
*/
|
243 |
public static String URLDecode(String str, String enc, boolean isQuery) { |
244 |
if (str == null) |
245 |
return null; |
246 |
|
247 |
// use the specified encoding to extract bytes out of the
|
248 |
// given string so that the encoding is not lost. If an
|
249 |
// encoding is not specified, let it use platform default
|
250 |
byte[] bytes = null; |
251 |
try {
|
252 |
if (enc == null) |
253 |
bytes = str.getBytes(); |
254 |
else
|
255 |
bytes = str.getBytes(enc); |
256 |
} catch (UnsupportedEncodingException uee) {} |
257 |
|
258 |
return URLDecode(bytes, enc, isQuery);
|
259 |
|
260 |
} |
261 |
|
262 |
|
263 |
/**
|
264 |
* Decode and return the specified URL-encoded byte array. It is assumed
|
265 |
* the string is not a query string.
|
266 |
*
|
267 |
* @param bytes The url-encoded byte array
|
268 |
* @return the decoded string
|
269 |
* @exception IllegalArgumentException if a '%' character is not followed
|
270 |
* by a valid 2-digit hexadecimal number
|
271 |
*/
|
272 |
public static String URLDecode(byte[] bytes) { |
273 |
return URLDecode(bytes, null); |
274 |
} |
275 |
|
276 |
|
277 |
/**
|
278 |
* Decode and return the specified URL-encoded byte array. It is assumed
|
279 |
* the string is not a query string.
|
280 |
*
|
281 |
* @param bytes The url-encoded byte array
|
282 |
* @param enc The encoding to use; if null, the default encoding is used
|
283 |
* @return the decoded string
|
284 |
* @exception IllegalArgumentException if a '%' character is not followed
|
285 |
* by a valid 2-digit hexadecimal number
|
286 |
*/
|
287 |
public static String URLDecode(byte[] bytes, @SuppressWarnings("unused") String enc) { |
288 |
return URLDecode(bytes, null, false); |
289 |
} |
290 |
|
291 |
/**
|
292 |
* Decode and return the specified URL-encoded byte array.
|
293 |
*
|
294 |
* @param bytes The url-encoded byte array
|
295 |
* @param enc The encoding to use; if null, the default encoding is used
|
296 |
* @param isQuery Is this a query string being processed
|
297 |
* @return the decoded string
|
298 |
* @exception IllegalArgumentException if a '%' character is not followed
|
299 |
* by a valid 2-digit hexadecimal number
|
300 |
*/
|
301 |
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) { |
302 |
|
303 |
if (bytes == null) |
304 |
return null; |
305 |
|
306 |
int len = bytes.length;
|
307 |
int ix = 0; |
308 |
int ox = 0; |
309 |
while (ix < len) {
|
310 |
byte b = bytes[ix++]; // Get byte to test |
311 |
if (b == '+' && isQuery) |
312 |
b = (byte)' '; |
313 |
else if (b == '%') |
314 |
b = (byte) ((convertHexDigit(bytes[ix++]) << 4) |
315 |
+ convertHexDigit(bytes[ix++])); |
316 |
bytes[ox++] = b; |
317 |
} |
318 |
if (enc != null) |
319 |
try {
|
320 |
return new String(bytes, 0, ox, enc); |
321 |
} catch (Exception e) { |
322 |
e.printStackTrace(); |
323 |
} |
324 |
return new String(bytes, 0, ox); |
325 |
|
326 |
} |
327 |
|
328 |
|
329 |
/**
|
330 |
* Convert a byte character value to hexidecimal digit value.
|
331 |
*
|
332 |
* @param b the character value byte
|
333 |
* @return the converted value
|
334 |
*/
|
335 |
private static byte convertHexDigit( byte b ) { |
336 |
if (b >= '0' && b <= '9') return (byte)(b - '0'); |
337 |
if (b >= 'a' && b <= 'f') return (byte)(b - 'a' + 10); |
338 |
if (b >= 'A' && b <= 'F') return (byte)(b - 'A' + 10); |
339 |
return 0; |
340 |
} |
341 |
|
342 |
|
343 |
/**
|
344 |
* Put name and value pair in map. When name already exist, add value
|
345 |
* to array of values.
|
346 |
*
|
347 |
* @param map The map to populate
|
348 |
* @param name The parameter name
|
349 |
* @param value The parameter value
|
350 |
*/
|
351 |
private static void putMapEntry( Map map, String name, String value) { |
352 |
String[] newValues = null; |
353 |
String[] oldValues = (String[]) map.get(name); |
354 |
if (oldValues == null) { |
355 |
newValues = new String[1]; |
356 |
newValues[0] = value;
|
357 |
} else {
|
358 |
newValues = new String[oldValues.length + 1]; |
359 |
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); |
360 |
newValues[oldValues.length] = value; |
361 |
} |
362 |
map.put(name, newValues); |
363 |
} |
364 |
|
365 |
|
366 |
/**
|
367 |
* Append request parameters from the specified String to the specified
|
368 |
* Map. It is presumed that the specified Map is not accessed from any
|
369 |
* other thread, so no synchronization is performed.
|
370 |
* <p>
|
371 |
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
|
372 |
* individually on the parsed name and value elements, rather than on
|
373 |
* the entire query string ahead of time, to properly deal with the case
|
374 |
* where the name or value includes an encoded "=" or "&" character
|
375 |
* that would otherwise be interpreted as a delimiter.
|
376 |
*
|
377 |
* NOTE: byte array data is modified by this method. Caller beware.
|
378 |
*
|
379 |
* @param map Map that accumulates the resulting parameters
|
380 |
* @param data Input string containing request parameters
|
381 |
* @param encoding Encoding to use for converting hex
|
382 |
*
|
383 |
* @exception UnsupportedEncodingException if the data is malformed
|
384 |
*/
|
385 |
public static void parseParameters(Map map, byte[] data, String encoding) |
386 |
throws UnsupportedEncodingException { |
387 |
|
388 |
if (data != null && data.length > 0) { |
389 |
int ix = 0; |
390 |
int ox = 0; |
391 |
String key = null; |
392 |
String value = null; |
393 |
while (ix < data.length) {
|
394 |
byte c = data[ix++];
|
395 |
switch ((char) c) { |
396 |
case '&': |
397 |
value = new String(data, 0, ox, encoding); |
398 |
if (key != null) { |
399 |
putMapEntry(map, key, value); |
400 |
key = null;
|
401 |
} |
402 |
ox = 0;
|
403 |
break;
|
404 |
case '=': |
405 |
if (key == null) { |
406 |
key = new String(data, 0, ox, encoding); |
407 |
ox = 0;
|
408 |
} else
|
409 |
data[ox++] = c; |
410 |
break;
|
411 |
case '+': |
412 |
data[ox++] = (byte)' '; |
413 |
break;
|
414 |
case '%': |
415 |
data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4) |
416 |
+ convertHexDigit(data[ix++])); |
417 |
break;
|
418 |
default:
|
419 |
data[ox++] = c; |
420 |
} |
421 |
} |
422 |
//The last value does not end in '&'. So save it now.
|
423 |
if (key != null) { |
424 |
value = new String(data, 0, ox, encoding); |
425 |
putMapEntry(map, key, value); |
426 |
} |
427 |
} |
428 |
|
429 |
} |
430 |
|
431 |
|
432 |
|
433 |
} |