View Javadoc

    1   /*
    2    * Copyright 2009 Red Hat, Inc.
    3    *
    4    * Red Hat licenses this file to you under the Apache License, version 2.0
    5    * (the "License"); you may not use this file except in compliance with the
    6    * 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 org.jboss.netty.handler.codec.http;
    17
    18  import java.io.UnsupportedEncodingException;
    19  import java.net.URI;
    20  import java.net.URLDecoder;
    21  import java.nio.charset.Charset;
    22  import java.nio.charset.UnsupportedCharsetException;
    23  import java.util.ArrayList;
    24  import java.util.Collections;
    25  import java.util.LinkedHashMap;
    26  import java.util.List;
    27  import java.util.Map;
    28
    29  /**
    30   * Splits an HTTP query string into a path string and key-value parameter pairs.
    31   * This decoder is for one time use only.  Create a new instance for each URI:
    32   * <pre>
    33   * {@link QueryStringDecoder} decoder = new {@link QueryStringDecoder}("/hello?recipient=world");
    34   * assert decoder.getPath().equals("/hello");
    35   * assert decoder.getParameters().get("recipient").equals("world");
    36   * </pre>
    37   *
    38   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
    39   * @author Andy Taylor (andy.taylor@jboss.org)
    40   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
    41   * @author <a href="http://tsunanet.net/">Benoit Sigoure</a>
    42   * @version $Rev: 2302 $, $Date: 2010-06-14 20:07:44 +0900 (Mon, 14 Jun 2010) $
    43   *
    44   * @see QueryStringEncoder
    45   *
    46   * @apiviz.stereotype utility
    47   * @apiviz.has        org.jboss.netty.handler.codec.http.HttpRequest oneway - - decodes
    48   */
    49  public class QueryStringDecoder {
    50
    51      private final Charset charset;
    52      private final String uri;
    53      private String path;
    54      private Map<String, List<String>> params;
    55
    56      /**
    57       * Creates a new decoder that decodes the specified URI. The decoder will
    58       * assume that the query string is encoded in UTF-8.
    59       */
    60      public QueryStringDecoder(String uri) {
    61          this(uri, HttpCodecUtil.DEFAULT_CHARSET);
    62      }
    63
    64      /**
    65       * Creates a new decoder that decodes the specified URI encoded in the
    66       * specified charset.
    67       */
    68      public QueryStringDecoder(String uri, Charset charset) {
    69          if (uri == null) {
    70              throw new NullPointerException("uri");
    71          }
    72          if (charset == null) {
    73              throw new NullPointerException("charset");
    74          }
    75
    76          this.uri = uri;
    77          this.charset = charset;
    78      }
    79
    80      /**
    81       * @deprecated Use {@link #QueryStringDecoder(String, Charset)} instead.
    82       */
    83      @Deprecated
    84      public QueryStringDecoder(String uri, String charset) {
    85          this(uri, Charset.forName(charset));
    86      }
    87
    88      /**
    89       * Creates a new decoder that decodes the specified URI. The decoder will
    90       * assume that the query string is encoded in UTF-8.
    91       */
    92      public QueryStringDecoder(URI uri) {
    93          this(uri, HttpCodecUtil.DEFAULT_CHARSET);
    94      }
    95
    96      /**
    97       * Creates a new decoder that decodes the specified URI encoded in the
    98       * specified charset.
    99       */
    100     public QueryStringDecoder(URI uri, Charset charset){
    101         if (uri == null) {
    102             throw new NullPointerException("uri");
    103         }
    104         if (charset == null) {
    105             throw new NullPointerException("charset");
    106         }
    107
    108         this.uri = uri.toASCIIString();
    109         this.charset = charset;
    110     }
    111
    112     /**
    113      * @deprecated Use {@link #QueryStringDecoder(URI, Charset)} instead.
    114      */
    115     @Deprecated
    116     public QueryStringDecoder(URI uri, String charset){
    117         this(uri, Charset.forName(charset));
    118     }
    119
    120     /**
    121      * Returns the decoded path string of the URI.
    122      */
    123     public String getPath() {
    124         if (path == null) {
    125             int pathEndPos = uri.indexOf('?');
    126             if (pathEndPos < 0) {
    127                 path = uri;
    128             }
    129             else {
    130                 return path = uri.substring(0, pathEndPos);
    131             }
    132         }
    133         return path;
    134     }
    135
    136     /**
    137      * Returns the decoded key-value parameter pairs of the URI.
    138      */
    139     public Map<String, List<String>> getParameters() {
    140         if (params == null) {
    141             int pathLength = getPath().length();
    142             if (uri.length() == pathLength) {
    143                 return Collections.emptyMap();
    144             }
    145             params = decodeParams(uri.substring(pathLength + 1));
    146         }
    147         return params;
    148     }
    149
    150     private Map<String, List<String>> decodeParams(String s) {
    151         Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
    152         String name = null;
    153         int pos = 0; // Beginning of the unprocessed region
    154         int i;       // End of the unprocessed region
    155         char c = 0;  // Current character
    156         for (i = 0; i < s.length(); i++) {
    157             c = s.charAt(i);
    158             if (c == '=' && name == null) {
    159                 if (pos != i) {
    160                     name = decodeComponent(s.substring(pos, i), charset);
    161                 }
    162                 pos = i + 1;
    163             } else if (c == '&') {
    164                 if (name == null && pos != i) {
    165                     // We haven't seen an `=' so far but moved forward.
    166                     // Must be a param of the form '&a&' so add it with
    167                     // an empty value.
    168                     addParam(params, decodeComponent(s.substring(pos, i), charset), "");
    169                 } else if (name != null) {
    170                     addParam(params, name, decodeComponent(s.substring(pos, i), charset));
    171                     name = null;
    172                 }
    173                 pos = i + 1;
    174             }
    175         }
    176
    177         if (pos != i) {  // Are there characters we haven't dealt with?
    178             if (name == null) {     // Yes and we haven't seen any `='.
    179                 addParam(params, decodeComponent(s.substring(pos, i), charset), "");
    180             } else {                // Yes and this must be the last value.
    181                 addParam(params, name, decodeComponent(s.substring(pos, i), charset));
    182             }
    183         } else if (name != null) {  // Have we seen a name without value?
    184             addParam(params, name, "");
    185         }
    186
    187         return params;
    188     }
    189
    190     private static String decodeComponent(String s, Charset charset) {
    191         if (s == null) {
    192             return "";
    193         }
    194
    195         try {
    196             return URLDecoder.decode(s, charset.name());
    197         } catch (UnsupportedEncodingException e) {
    198             throw new UnsupportedCharsetException(charset.name());
    199         }
    200     }
    201
    202     private static void addParam(Map<String, List<String>> params, String name, String value) {
    203         List<String> values = params.get(name);
    204         if (values == null) {
    205             values = new ArrayList<String>(1);  // Often there's only 1 value.
    206             params.put(name, values);
    207         }
    208         values.add(value);
    209     }
    210 }