All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.restassured.internal.http.URIBuilder Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * Copyright 2019 the original author or authors.
 *
 * Licensed 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 io.restassured.internal.http;

import io.restassured.config.EncoderConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;

import java.io.UnsupportedEncodingException;
import java.net.*;
import java.nio.charset.Charset;
import java.util.*;

import static io.restassured.config.EncoderConfig.encoderConfig;

/**
 * This class implements a mutable URI.  All set, add
 * and remove methods affect this class' internal URI
 * representation.  All mutator methods support chaining, e.g.
 * 
 * new URIBuilder("http://www.google.com/")
 *   .setScheme( "https" )
 *   .setPort( 443 )
 *   .setPath( "some/path" )
 *   .toString();
 * 
* A slightly more 'Groovy' version would be: *
 * new URIBuilder('http://www.google.com/').with {
 *    scheme = 'https'
 *    port = 443
 *    path = 'some/path'
 *    query = [p1:1, p2:'two']
 * }.toString()
 * 
* * @author Tom Nichols * @author Johan Haleby */ public class URIBuilder implements Cloneable { private static final String PARAMETER_SEPARATOR = "&"; private static final String NAME_VALUE_SEPARATOR = "="; private static final String PLUS = "+"; private static final String PERCENTAGE_20 = "%20"; protected URI base; private String enc; private final boolean isUrlEncodingEnabled; /** * @param uri * @throws IllegalArgumentException if uri is null */ public URIBuilder(URI uri, boolean urlEncodingEnabled, EncoderConfig config) throws IllegalArgumentException { Validate.notNull(uri, "uri cannot be null"); Validate.notNull(config, "encoder config cannot be null"); this.base = uri; this.enc = config.defaultQueryParameterCharset(); this.isUrlEncodingEnabled = urlEncodingEnabled; } /** * Utility method to convert a number of type to a URI instance. * * @param uri a {@link URI}, {@link URL} or any object that produces a * valid URI string from its toString() result. * @return a valid URI parsed from the given object * @throws URISyntaxException */ public static URI convertToURI(Object uri) throws URISyntaxException { if (uri instanceof URI) return (URI) uri; if (uri instanceof URL) return ((URL) uri).toURI(); if (uri instanceof URIBuilder) return ((URIBuilder) uri).toURI(); return new URI(uri.toString()); // assume any other object type produces a valid URI string } /** * Set the URI scheme, AKA the 'protocol.' e.g. * setScheme('https') * * @throws URISyntaxException if the given scheme contains illegal characters. */ public URIBuilder setScheme(String scheme) throws URISyntaxException { this.base = new URI(scheme, base.getUserInfo(), base.getHost(), base.getPort(), base.getPath(), base.getQuery(), base.getFragment()); return this; } public URIBuilder setPort(int port) throws URISyntaxException { this.base = new URI(base.getScheme(), base.getUserInfo(), base.getHost(), port, base.getPath(), base.getQuery(), base.getFragment()); return this; } public URIBuilder setHost(String host) throws URISyntaxException { this.base = new URI(base.getScheme(), base.getUserInfo(), host, base.getPort(), base.getPath(), base.getQuery(), base.getFragment()); return this; } /** * Set the path component of this URI. The value may be absolute or * relative to the current path. * e.g.
     *   def uri = new URIBuilder( 'http://localhost/p1/p2?a=1' )
     * 

* uri.path = '/p3/p2' * assert uri.toString() == 'http://localhost/p3/p2?a=1' *

* uri.path = 'p2a' * assert uri.toString() == 'http://localhost/p3/p2a?a=1' *

* uri.path = '../p4' * assert uri.toString() == 'http://localhost/p4?a=1&b=2&c=3#frag' *

     *
     * @param path the path portion of this URI, relative to the current URI.
     * @return this URIBuilder instance, for method chaining.
     * @throws URISyntaxException if the given path contains characters that
     *                            cannot be converted to a valid URI
     */
    public URIBuilder setPath(String path) throws URISyntaxException {
        /* Passing the path string in the URI constructor will
                     * double-escape path parameters and goober things up.  So we have
                     * to create a full path+query+fragment and use URI#resolve() to
                     * create the new URI. */
        StringBuilder sb = new StringBuilder();
        if (path != null) sb.append(path);
        String query = base.getQuery();
        if (query != null) {
            sb.append('?');
            sb.append(query);
        }
        String frag = base.getRawFragment();
        if (frag != null) sb.append('#').append(frag);
        this.base = base.resolve(sb.toString());
        return this;
    }

    /* TODO null/ zero-size check if this is ever made public */
    protected URIBuilder setQueryNVP(List nvp) throws URISyntaxException {
        /* Passing the query string in the URI constructor will
           * double-escape query parameters and goober things up.  So we have
           * to create a full path+query+fragment and use URI#resolve() to
           * create the new URI.  */
        StringBuilder sb = new StringBuilder();
        String path = base.getRawPath();
        if (path != null) sb.append(path);
        sb.append('?');
        sb.append(format(nvp, isUrlEncodingEnabled, enc));
        String frag = base.getRawFragment();
        if (frag != null) sb.append('#').append(frag);
        this.base = base.resolve(sb.toString());

        return this;
    }

    /**
     * Set the query portion of the URI.  For query parameters with multiple
     * values, put the values in a list like so:
     * 
uri.query = [ p1:'val1', p2:['val2', 'val3'] ]
     * // will produce a query string of ?p1=val1&p2=val2&p2=val3
* * @param params a Map of parameters that will be transformed into the query string * @return this URIBuilder instance, for method chaining. * @throws URISyntaxException */ public URIBuilder setQuery(Map params) throws URISyntaxException { if (params != null && params.size() >= 1) { List nvp = new ArrayList(params.size()); for (Object key : params.keySet()) { Object value = params.get(key); if (value instanceof List) { for (Object val : (List) value) nvp.add(new BasicNameValuePairWithNoValueSupport(key.toString(), val)); } else nvp.add(new BasicNameValuePairWithNoValueSupport(key.toString(), value)); } this.setQueryNVP(nvp); } return this; } /** * Get the query string as a map for convenience. If any parameter contains * multiple values (e.g. p1=one&p1=two) both values will be * inserted into a list for that parameter key ([p1 : ['one','two']] * ). Note that this is not a "live" map. Therefore, you cannot * call *
 uri.query.a = 'BCD'
* You will not modify the query string but instead the generated map of * parameters. Instead, you need to use {@link #removeQueryParam(String)} * first, then call {@link #setQuery(Map)} which will set the entire query string. * * @return a map of String name/value pairs representing the URI's query * string. */ public Map getQuery() { Map params = new TreeMap(); List pairs = this.getQueryNVP(); for (NameValuePair pair : pairs) { String key = pair.getName(); Object existing = params.get(key); if (existing == null) params.put(key, pair.getValue()); else if (existing instanceof List) ((List) existing).add(pair.getValue()); else { List vals = new ArrayList(2); vals.add((String) existing); vals.add(pair.getValue()); params.put(key, vals); } } return params; } protected List getQueryNVP() { List nvps = parse(this.base); List newList = new ArrayList(); if (nvps != null) newList.addAll(nvps); return newList; } /** * Indicates if the given parameter is already part of this URI's query * string. * * @param name the query parameter name * @return true if the given parameter name is found in the query string of * the URI. */ public boolean hasQueryParam(String name) { return getQuery().get(name) != null; } /** * Remove the given query parameter from this URI's query string. * * @param param the query name to remove * @return this URIBuilder instance, for method chaining. * @throws URISyntaxException */ public URIBuilder removeQueryParam(String param) throws URISyntaxException { List params = getQueryNVP(); NameValuePair found = null; for (NameValuePair nvp : params) // BOO linear search. Assume the list is small. if (nvp.getName().equals(param)) { found = nvp; break; } if (found == null) throw new IllegalArgumentException("Param '" + param + "' not found"); params.remove(found); this.setQueryNVP(params); return this; } protected URIBuilder addQueryParams(List nvp) throws URISyntaxException { List params = getQueryNVP(); params.addAll(nvp); this.setQueryNVP(params); return this; } /** * Add these parameters to the URIBuilder's existing query string. * Parameters may be passed either as a single map argument, or as a list * of named arguments. e.g. *
 uriBuilder.addQueryParams( [one:1,two:2] )
     * uriBuilder.addQueryParams( three : 3 ) 
*

* If any of the parameters already exist in the URI query, these values * will not replace them. Multiple values for the same * query parameter may be added by putting them in a list. See * {@link #setQuery(Map)}. * * @param params parameters to add to the existing URI query (if any). * @return this URIBuilder instance, for method chaining. * @throws URISyntaxException */ @SuppressWarnings("unchecked") public URIBuilder addQueryParams(Map params) throws URISyntaxException { List nvp = new ArrayList(); for (Object key : params.keySet()) { Object value = params.get(key); if (value instanceof List) { for (Object val : (List) value) nvp.add(new BasicNameValuePairWithNoValueSupport(key.toString(), val)); } else nvp.add(new BasicNameValuePairWithNoValueSupport(key.toString(), value)); } this.addQueryParams(nvp); return this; } /** * The document fragment, without a preceeding '#' * * @param fragment * @return this URIBuilder instance, for method chaining. * @throws URISyntaxException if the given value contains illegal characters. */ public URIBuilder setFragment(String fragment) throws URISyntaxException { this.base = new URI(base.getScheme(), base.getUserInfo(), base.getHost(), base.getPort(), base.getPath(), base.getQuery(), fragment); return this; } /** * Print this builder's URI representation. */ @Override public String toString() { return base.toString(); } /** * Convenience method to convert this object to a URL instance. * * @return this builder as a URL * @throws MalformedURLException if the underlying URI does not represent a * valid URL. */ public URL toURL() throws MalformedURLException { return base.toURL(); } /** * Convenience method to convert this object to a URI instance. * * @return this builder's underlying URI representation */ public URI toURI() { return this.base; } /** * Implementation of Groovy's as operator, to allow type * conversion. * * @param type URL, URL, or String. * @return a representation of this URIBuilder instance in the given type * @throws MalformedURLException if type is URL and this * URIBuilder instance does not represent a valid URL. */ public Object asType(Class type) throws MalformedURLException { if (type == URI.class) return this.toURI(); if (type == URL.class) return this.toURL(); if (type == String.class) return this.toString(); throw new ClassCastException("Cannot cast instance of URIBuilder to class " + type); } /** * Create a copy of this URIBuilder instance. */ @Override protected URIBuilder clone() { return new URIBuilder(this.base, this.isUrlEncodingEnabled, encoderConfig().defaultQueryParameterCharset(this.enc)); } /** * Determine if this URIBuilder is equal to another URIBuilder instance. * * @return if obj is a URIBuilder instance whose underlying * URI implementation is equal to this one's. * @see URI#equals(Object) */ @Override public boolean equals(Object obj) { if (!(obj instanceof URIBuilder)) return false; return this.base.equals(((URIBuilder) obj).toURI()); } /** * Returns a String that is suitable for use as an application/x-www-form-urlencoded * list of parameters in an HTTP PUT or HTTP POST. *

* This is a copy of {@link URLEncodedUtils#format(java.util.List, String)} that also handles {@link BasicNameValuePairWithNoValueSupport}. *

* * @param parameters The parameters to include. * @param encoding The encoding to use. */ private static String format( final List parameters, final boolean isUrlEncodingEnabled, final String encoding) { final StringBuilder result = new StringBuilder(); for (final NameValuePair parameter : parameters) { if (result.length() > 0) result.append(PARAMETER_SEPARATOR); final String encodedName = isUrlEncodingEnabled ? encode(parameter.getName(), encoding) : parameter.getName(); result.append(encodedName); if (hasValue(parameter)) { final String value = parameter.getValue(); final String encodedValue = value != null ? isUrlEncodingEnabled ? encode(value, encoding) : value : ""; result.append(NAME_VALUE_SEPARATOR); result.append(encodedValue); } } return result.toString(); } private static boolean hasValue(NameValuePair parameter) { if (!(parameter instanceof BasicNameValuePairWithNoValueSupport)) { return true; } return ((BasicNameValuePairWithNoValueSupport) parameter).hasValue(); } // Copy of the private method in URLEncodedUtils public static String encode(final String content, final String encoding) { try { String encoded = URLEncoder.encode(content, encoding != null ? encoding : Charset.defaultCharset().toString()); // We replace spaces encoded as "+" to %20 because some server (such as Scalatra) doesn't decode "+" correctly. encoded = StringUtils.replace(encoded, PLUS, PERCENTAGE_20); return encoded; } catch (UnsupportedEncodingException problem) { throw new IllegalArgumentException(problem); } } /** * Adds all parameters within the Scanner to the list of * parameters, as encoded by encoding. For * example, a scanner containing the string a=1&b=2&c=3 would * add the {@link NameValuePair NameValuePairs} a=1, b=2, and c=3 to the * list of parameters. *

* Note that this method has been copied from {@link URLEncodedUtils#parse(java.util.List, java.util.Scanner, String)} but it doesn't do URL decoding. *

*/ private List parse(URI uri) { List parameters = new ArrayList(); final String query = uri.getRawQuery(); if (query != null && query.length() > 0) { final Scanner scanner = new Scanner(query); scanner.useDelimiter(PARAMETER_SEPARATOR); while (scanner.hasNext()) { String name; String value = null; String token = scanner.next(); int i = token.indexOf(NAME_VALUE_SEPARATOR); if (i != -1) { name = token.substring(0, i).trim(); value = token.substring(i + 1).trim(); } else { name = token.trim(); } parameters.add(new BasicNameValuePair(name, value)); } } return parameters; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy