org.restexpress.url.QueryStringParser Maven / Gradle / Ivy
/*
* 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 org.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