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-2015 ForgeRock AS. 017 */ 018 019package org.forgerock.http.protocol; 020 021 022import static org.forgerock.http.util.Uris.formDecodeParameterNameOrValue; 023import static org.forgerock.http.util.Uris.formEncodeParameterNameOrValue; 024import static org.forgerock.http.util.Uris.urlDecodeQueryParameterNameOrValue; 025import static org.forgerock.http.util.Uris.urlEncodeQueryParameterNameOrValue; 026 027import java.io.IOException; 028import java.net.URISyntaxException; 029import java.util.LinkedHashMap; 030import java.util.List; 031 032import org.forgerock.http.header.ContentLengthHeader; 033import org.forgerock.http.header.ContentTypeHeader; 034import org.forgerock.http.util.MultiValueMap; 035 036/** 037 * Form fields, a case-sensitive multi-string-valued map. The form can be read 038 * from and written to request objects as query parameters (GET) and request 039 * entities (POST). 040 */ 041public class Form extends MultiValueMap<String, String> { 042 043 /** 044 * Constructs a new, empty form object. 045 */ 046 public Form() { 047 super(new LinkedHashMap<String, List<String>>()); 048 } 049 050 /** 051 * Parses a form URL-encoded string containing form parameters and stores them in 052 * this object. Malformed name-value pairs (missing the "=" delimiter) are 053 * simply ignored. 054 * 055 * @param s the form URL-encoded string to parse. 056 * @return this form object. 057 * @deprecated use {@link #fromFormString(String)} instead. 058 */ 059 @Deprecated 060 public Form fromString(String s) { 061 return fromFormString(s); 062 } 063 064 /** 065 * Parses a form URL-encoded string containing form parameters and stores them in 066 * this object. Malformed name-value pairs (missing the "=" delimiter) are 067 * simply ignored. 068 * 069 * @param s the form URL-encoded string to parse. 070 * @return this form object. 071 */ 072 public Form fromFormString(final String s) { 073 for (String param : s.split("&")) { 074 String[] nv = param.split("=", 2); 075 if (nv.length == 2) { 076 add(formDecodeParameterNameOrValue(nv[0]), formDecodeParameterNameOrValue(nv[1])); 077 } 078 } 079 return this; 080 } 081 082 /** 083 * Parses a URL-encoded query string containing form parameters and stores them in 084 * this object. Malformed name-value pairs (missing the "=" delimiter) are 085 * simply ignored. 086 * 087 * @param s the URL-encoded query string to parse. 088 * @return this form object. 089 */ 090 public Form fromQueryString(final String s) { 091 for (String param : s.split("&")) { 092 String[] nv = param.split("=", 2); 093 if (nv.length == 2) { 094 add(urlDecodeQueryParameterNameOrValue(nv[0]), urlDecodeQueryParameterNameOrValue(nv[1])); 095 } 096 } 097 return this; 098 } 099 100 /** 101 * Returns this form in a form URL-encoded string. 102 * 103 * @return the form URL-encoded form. 104 * @deprecated use {@link #toFormString()} instead. 105 */ 106 @Deprecated 107 @Override 108 public String toString() { 109 return toFormString(); 110 } 111 112 /** 113 * Returns this form in a form URL-encoded string. 114 * 115 * @return the form URL-encoded form. 116 */ 117 public String toFormString() { 118 StringBuilder sb = new StringBuilder(); 119 for (String name : keySet()) { 120 for (String value : get(name)) { 121 if (sb.length() > 0) { 122 sb.append('&'); 123 } 124 sb.append(formEncodeParameterNameOrValue(name)) 125 .append('=') 126 .append(formEncodeParameterNameOrValue(value)); 127 } 128 } 129 return sb.toString(); 130 } 131 132 /** 133 * Returns this form in a URL-encoded query string. 134 * 135 * @return the URL-encoded query string. 136 */ 137 public String toQueryString() { 138 StringBuilder sb = new StringBuilder(); 139 for (String name : keySet()) { 140 for (String value : get(name)) { 141 if (sb.length() > 0) { 142 sb.append('&'); 143 } 144 sb.append(urlEncodeQueryParameterNameOrValue(name)) 145 .append('=') 146 .append(urlEncodeQueryParameterNameOrValue(value)); 147 } 148 } 149 return sb.toString(); 150 } 151 152 /** 153 * Parses the query parameters of a request and stores them in this object. 154 * The object is not cleared beforehand, so this adds to any fields already 155 * in place. 156 * 157 * @param request the request to be parsed. 158 * @return this form object. 159 */ 160 public Form fromRequestQuery(Request request) { 161 String query = request.getUri().getRawQuery(); 162 if (query != null) { 163 fromQueryString(query); 164 } 165 return this; 166 } 167 168 /** 169 * Sets a request URI with query parameters. This overwrites any query 170 * parameters that may already exist in the request URI. 171 * 172 * @param request the request to set query parameters to. 173 */ 174 public void toRequestQuery(Request request) { 175 try { 176 request.getUri().setRawQuery(toQueryString()); 177 } catch (URISyntaxException use) { 178 throw new IllegalArgumentException(use); 179 } 180 } 181 182 /** 183 * Appends the form as additional query parameters on an existing request 184 * URI. This leaves any existing query parameters intact. 185 * 186 * @param request the request to append query parameters to. 187 */ 188 public void appendRequestQuery(Request request) { 189 StringBuilder sb = new StringBuilder(); 190 String uriQuery = request.getUri().getRawQuery(); 191 if (uriQuery != null && uriQuery.length() > 0) { 192 sb.append(uriQuery); 193 } 194 String toAppend = toQueryString(); 195 if (toAppend != null && toAppend.length() > 0) { 196 if (sb.length() > 0) { 197 sb.append('&'); 198 } 199 sb.append(toAppend); 200 } 201 String newQuery = sb.toString(); 202 if (newQuery.length() == 0) { 203 newQuery = null; 204 } 205 try { 206 request.getUri().setRawQuery(newQuery); 207 } catch (URISyntaxException use) { 208 throw new IllegalArgumentException(use); 209 } 210 } 211 212 /** 213 * Parses the URL-encoded form entity of a request and stores them in this 214 * object. The object is not cleared beforehand, so this adds to any fields 215 * already in place. 216 * 217 * @param request 218 * the request to be parsed. 219 * @return this form object. 220 * @throws IOException 221 * if an I/O exception occurs. 222 */ 223 public Form fromRequestEntity(Request request) throws IOException { 224 if (request != null 225 && request.getEntity() != null 226 && "application/x-www-form-urlencoded".equalsIgnoreCase(request.getHeaders() 227 .getFirst(ContentTypeHeader.class))) { 228 fromFormString(request.getEntity().getString()); 229 } 230 return this; 231 } 232 233 /** 234 * Populates a request with the necessary headers and entity for the form to 235 * be submitted as a POST with application/x-www-form-urlencoded content 236 * type. This overwrites any entity that may already be in the request. 237 * 238 * @param request the request to add the form entity to. 239 */ 240 public void toRequestEntity(Request request) { 241 String form = toFormString(); 242 request.setMethod("POST"); 243 request.getHeaders().put(ContentTypeHeader.NAME, "application/x-www-form-urlencoded"); 244 request.getHeaders().put(ContentLengthHeader.NAME, form.length()); 245 request.getEntity().setString(form); 246 } 247}