Statistics
| Branch: | Tag: | Revision:

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("&lt;");
67
                break;
68
            case '>':
69
                result.append("&gt;");
70
                break;
71
            case '&':
72
                result.append("&amp;");
73
                break;
74
            case '"':
75
                result.append("&quot;");
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
}