com.strategicgains.restexpress.url.QueryStringParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of RestExpress Show documentation
Show all versions of RestExpress Show documentation
Internet scale, high-performance RESTful Services in Java
/*
* Copyright 2011 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.strategicgains.restexpress.url;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.jboss.netty.handler.codec.http.QueryStringEncoder;
/**
* Splits an HTTP query string into a path string and key-value parameter pairs.
* This parser is for one time use only. Create a new instance for each URI:
*
*
* {@link QueryStringParser} parser = new {@link QueryStringParser}("/hello?recipient=hello%20world&x=1;y=2");
* assert parser.getPath().equals("/hello");
* assert parser.getParameters().get("recipient").get(0).equals("hello%20world");
* assert parser.getParameters().get("x").get(0).equals("1");
* assert parser.getParameters().get("y").get(0).equals("2");
*
*
* This parser can also decode the content of an HTTP POST request whose content
* type is application/x-www-form-urlencoded:
*
*
* {@link QueryStringParser} parser = new {@link QueryStringParser}("recipient=hello%20world&x=1;y=2", false);
* ...
*
*
* But will not URL decode the individual values.
*
* HashDOS vulnerability fix
*
* As a workaround to the HashDOS vulnerability, the decoder limits the maximum number of decoded
* key-value parameter pairs, up to {@literal 1024} by default, and you can
* configure it when you construct the decoder by passing an additional integer
* parameter.
*
* Note that no exception is thrown if the number of parameters is exceeded. This process simply stops adding
* parameters over the limit.
*
*
* Based on Netty {@link QueryStringDecoder} with URL decoding removed for
* selective query-string parameter decoding.
*
*
* @see QueryStringDecoder
* @see QueryStringEncoder
*/
public class QueryStringParser
{
private static final int DEFAULT_MAX_PARAMS = 1024;
private final String uri;
private final boolean hasPath;
private final int maxParams;
private String path;
private Map> params;
private int nParams;
/**
* Creates a new parser for the given uri.
*/
public QueryStringParser(String uri)
{
this(uri, true);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringParser(String uri, boolean hasPath)
{
this(uri, hasPath, DEFAULT_MAX_PARAMS);
}
public QueryStringParser(URI uri)
{
this(uri, DEFAULT_MAX_PARAMS);
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringParser(String uri, boolean hasPath, int maxParams)
{
if (uri == null)
{
throw new NullPointerException("uri");
}
if (maxParams <= 0)
{
throw new IllegalArgumentException("maxParams: " + maxParams
+ " (expected: a positive integer)");
}
// http://en.wikipedia.org/wiki/Query_string
this.uri = uri.replace(';', '&');
this.maxParams = maxParams;
this.hasPath = hasPath;
}
/**
* Creates a new decoder that decodes the specified URI encoded in the
* specified charset.
*/
public QueryStringParser(URI uri, int maxParams)
{
if (uri == null)
{
throw new NullPointerException("uri");
}
if (maxParams <= 0)
{
throw new IllegalArgumentException("maxParams: " + maxParams
+ " (expected: a positive integer)");
}
String rawPath = uri.getRawPath();
if (rawPath != null)
{
hasPath = true;
}
else
{
rawPath = "";
hasPath = false;
}
// Also take care of cut of things like "http://localhost"
String newUri = rawPath + "?" + uri.getRawQuery();
// http://en.wikipedia.org/wiki/Query_string
this.uri = newUri.replace(';', '&');
this.maxParams = maxParams;
}
/**
* Returns the decoded path string of the URI.
*/
public String getPath()
{
if (path == null)
{
if (!hasPath)
{
return path = "";
}
int pathEndPos = uri.indexOf('?');
if (pathEndPos < 0)
{
path = uri;
}
else
{
return path = uri.substring(0, pathEndPos);
}
}
return path;
}
/**
* Returns the decoded key-value parameter pairs of the URI.
*/
public Map> getParameters()
{
if (params == null)
{
if (hasPath)
{
int pathLength = getPath().length();
if (uri.length() == pathLength)
{
return Collections.emptyMap();
}
parseParams(uri.substring(pathLength + 1));
}
else
{
if (uri.length() == 0)
{
return Collections.emptyMap();
}
parseParams(uri);
}
}
return params;
}
private void parseParams(String s)
{
Map> params = this.params = new LinkedHashMap>();
nParams = 0;
String name = null;
int pos = 0; // Beginning of the unprocessed region
int i; // End of the unprocessed region
char c = 0; // Current character
for (i = 0; i < s.length(); i++)
{
c = s.charAt(i);
if (c == '=' && name == null)
{
if (pos != i)
{
name = s.substring(pos, i);
}
pos = i + 1;
}
else if (c == '&')
{
if (name == null && pos != i)
{
// We haven't seen an `=' so far but moved forward.
// Must be a param of the form '&a&' so add it with
// an empty value.
if (!addParam(params, s.substring(pos, i), ""))
{
return;
}
}
else if (name != null)
{
if (!addParam(params, name, s.substring(pos, i)))
{
return;
}
name = null;
}
pos = i + 1;
}
}
if (pos != i)
{ // Are there characters we haven't dealt with?
if (name == null)
{ // Yes and we haven't seen any `='.
if (!addParam(params, s.substring(pos, i), ""))
{
return;
}
}
else
{ // Yes and this must be the last value.
if (!addParam(params, name, s.substring(pos, i)))
{
return;
}
}
}
else if (name != null)
{ // Have we seen a name without value?
if (!addParam(params, name, ""))
{
return;
}
}
}
private boolean addParam(Map> params, String name, String value)
{
if (nParams >= maxParams)
{
return false;
}
List values = params.get(name);
if (values == null)
{
values = new ArrayList(1); // Often there's only 1 value.
params.put(name, values);
}
values.add(value);
nParams++;
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy