001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009 Sun Microsystems Inc.
015 * Portions Copyright 2010–2011 ApexIdentity Inc.
016 * Portions Copyright 2011-2014 ForgeRock AS.
017 */
018
019package org.forgerock.openig.http;
020
021import static org.forgerock.openig.el.Functions.*;
022
023import java.io.IOException;
024import java.net.URISyntaxException;
025import java.util.LinkedHashMap;
026import java.util.List;
027
028import org.forgerock.openig.util.MultiValueMap;
029
030/**
031 * Form fields, a case-sensitive multi-string-valued map. The form can be read
032 * from and written to request objects as query parameters (GET) and request
033 * entities (POST).
034 */
035public class Form extends MultiValueMap<String, String> {
036
037    /**
038     * Constructs a new, empty form object.
039     */
040    public Form() {
041        super(new LinkedHashMap<String, List<String>>());
042    }
043
044    /**
045     * Parses a URL-encoded string containing form parameters and stores them in
046     * this object. Malformed name-value pairs (missing the "=" delimiter) are
047     * simply ignored.
048     *
049     * @param s the URL-encoded string to parse.
050     * @return this form object.
051     */
052    public Form fromString(String s) {
053        for (String param : s.split("&")) {
054            String[] nv = param.split("=", 2);
055            if (nv.length == 2) {
056                add(urlDecode(nv[0]), urlDecode(nv[1]));
057            }
058        }
059        return this;
060    }
061
062    /**
063     * Returns this form in a URL-encoded format string.
064     *
065     * @return the URL-encoded form.
066     */
067    @Override
068    public String toString() {
069        StringBuilder sb = new StringBuilder();
070        for (String name : keySet()) {
071            for (String value : get(name)) {
072                if (sb.length() > 0) {
073                    sb.append('&');
074                }
075                sb.append(urlEncode(name)).append('=').append(urlEncode(value));
076            }
077        }
078        return sb.toString();
079    }
080
081    /**
082     * Parses the query parameters of a request and stores them in this object.
083     * The object is not cleared beforehand, so this adds to any fields already
084     * in place.
085     *
086     * @param request the request to be parsed.
087     * @return this form object.
088     */
089    public Form fromRequestQuery(Request request) {
090        String query = request.getUri().getRawQuery();
091        if (query != null) {
092            fromString(query);
093        }
094        return this;
095    }
096
097    /**
098     * Sets a request URI with query parameters. This overwrites any query
099     * parameters that may already exist in the request URI.
100     *
101     * @param request the request to set query parameters to.
102     */
103    public void toRequestQuery(Request request) {
104        try {
105            request.getUri().setRawQuery(toString());
106        } catch (URISyntaxException use) {
107            throw new IllegalArgumentException(use);
108        }
109    }
110
111    /**
112     * Appends the form as additional query parameters on an existing request
113     * URI. This leaves any existing query parameters intact.
114     *
115     * @param request the request to append query parameters to.
116     */
117    public void appendRequestQuery(Request request) {
118        StringBuilder sb = new StringBuilder();
119        String uriQuery = request.getUri().getRawQuery();
120        if (uriQuery != null && uriQuery.length() > 0) {
121            sb.append(uriQuery);
122        }
123        String toAppend = toString();
124        if (toAppend != null && toAppend.length() > 0) {
125            if (sb.length() > 0) {
126                sb.append('&');
127            }
128            sb.append(toAppend);
129        }
130        String newQuery = sb.toString();
131        if (newQuery.length() == 0) {
132            newQuery = null;
133        }
134        try {
135            request.getUri().setRawQuery(newQuery);
136        } catch (URISyntaxException use) {
137            throw new IllegalArgumentException(use);
138        }
139    }
140
141    /**
142     * Parses the URL-encoded form entity of a request and stores them in this
143     * object. The object is not cleared beforehand, so this adds to any fields
144     * already in place.
145     *
146     * @param request
147     *            the request to be parsed.
148     * @return this form object.
149     * @throws IOException
150     *             if an I/O exception occurs.
151     */
152    public Form fromRequestEntity(Request request) throws IOException {
153        if (request != null
154                && request.getEntity() != null
155                && "application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeaders()
156                        .getFirst("Content-Type"))) {
157            fromString(request.getEntity().getString());
158        }
159        return this;
160    }
161
162    /**
163     * Populates a request with the necessary headers and entity for the form to
164     * be submitted as a POST with application/x-www-form-urlencoded content
165     * type. This overwrites any entity that may already be in the request.
166     *
167     * @param request the request to add the form entity to.
168     */
169    public void toRequestEntity(Request request) {
170        String form = toString();
171        request.setMethod("POST");
172        request.getHeaders().putSingle("Content-Type", "application/x-www-form-urlencoded");
173        request.getHeaders().putSingle("Content-Length", Integer.toString(form.length()));
174        request.getEntity().setString(form);
175    }
176}