/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.html; import java.io.IOException; import java.io.OutputStream; import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; import org.restlet.data.Parameter; import org.restlet.engine.header.ContentType; import org.restlet.engine.header.HeaderUtils; import org.restlet.engine.util.StringUtils; import org.restlet.ext.html.internal.FormUtils; import org.restlet.representation.OutputRepresentation; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.util.Series; /** * HTML form supporting either URL encoding or multipart encoding. * * @author Jerome Louvel */ public class FormDataSet extends OutputRepresentation { /** The default boundary separating multipart entries. */ private final static String DEFAULT_BOUNDARY = "---Aa1Bb2Cc3---"; /** * Creates the media type of a multipart form which must include the used * boundary. * * @param boundary * The multipart boundary. * @return The multipart media type. */ private static MediaType createMultipartMediaType(String boundary) { Series<Parameter> params = new Series<Parameter>(Parameter.class); params.add("boundary", boundary); MediaType result = new MediaType( MediaType.MULTIPART_FORM_DATA.getName(), params); return result; } /** The modifiable series of data entries. */ private final Series<FormData> entries; /** Indicates if the form is multipart encoded. */ private volatile boolean multipart; /** The boundary separating multipart entries. */ private volatile String multipartBoundary; /** * Default constructor, creates a single part form. */ public FormDataSet() { this(null, false, DEFAULT_BOUNDARY); } /** * Constructor. * * @param mediaType * The representation's mediaType. * @param multipart * Indicates if the form is multipart encoded. * @param multipartBoundary * The boundary separating multipart entries. */ private FormDataSet(MediaType mediaType, boolean multipart, String multipartBoundary) { super(mediaType); this.entries = new Series<FormData>(FormData.class); this.multipartBoundary = multipartBoundary; this.multipart = multipart; } /** * Constructor. * * @param formRepresentation * The representation to parse. */ public FormDataSet(Representation formRepresentation) { this(); if ((formRepresentation != null) && MediaType.APPLICATION_WWW_FORM.equals(formRepresentation .getMediaType())) { FormUtils.parse(this.entries, formRepresentation); } } /** * Creates a multipart form. * * @param multipartBoundary * The boundary separating multipart entries. */ public FormDataSet(String multipartBoundary) { this(createMultipartMediaType(multipartBoundary), true, multipartBoundary); } /** * Adds a new form data entry. * * @param name * The entry name. * @param value * The entry value. * @return The entry created and added to {@link #getEntries()}. */ public FormData add(String name, String value) { FormData result = getEntries().createEntry(name, value); getEntries().add(result); return result; } /** * Encodes the form using the standard HTML form encoding mechanism and the * UTF-8 character set. * * @return The encoded form. * @throws IOException */ public String encode() throws IOException { return encode(false); } /** * Encodes the form using the standard URI encoding mechanism and the UTF-8 * character set. * * @param queryString * True if the target is a query string. * @return The encoded form. * @throws IOException */ public String encode(boolean queryString) throws IOException { return encode('&', queryString); } /** * URL encodes the form. * * @param separator * The separator character to append between parameters. * @param queryString * True if the target is a query string. * @return The encoded form. * @throws IOException */ public String encode(char separator, boolean queryString) throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < getEntries().size(); i++) { if (i > 0) { sb.append(separator); } getEntries().get(i).encode(sb, queryString); } return sb.toString(); } /** * Returns the modifiable series of form entries. * * @return The modifiable series of form entries. */ public Series<FormData> getEntries() { return entries; } /** * Formats the form as a matrix path string. Uses UTF-8 as the character set * for encoding non-ASCII characters. * * @return The form as a matrix string. * @see <a href="http://www.w3.org/DesignIssues/MatrixURIs.html">Matrix URIs * by Tim Berners Lee</a> */ public String getMatrixString() { try { return encode(';', true); } catch (IOException ioe) { return null; } } /** * Returns the boundary separating multipart entries. * * @return The boundary separating multipart entries. */ public String getMultipartBoundary() { return multipartBoundary; } /** * Formats the form as a query string. Uses UTF-8 as the character set for * encoding non-ASCII characters. * * @return The form as a query string. */ public String getQueryString() { try { return encode(true); } catch (IOException ioe) { return null; } } /** * Indicates if the form is multipart encoded. * * @return True if the form is multipart encoded. */ public boolean isMultipart() { return this.multipart; } /** * Indicates if the form is multipart encoded. * * @param multipart * True if the form is multipart encoded. */ public void setMultipart(boolean multipart) { this.multipart = multipart; if (this.multipart && getMultipartBoundary() == null) { this.multipartBoundary = DEFAULT_BOUNDARY; } setMediaType(createMultipartMediaType(getMultipartBoundary())); } /** * Sets the boundary separating multipart entries. * * @param boundary * The boundary separating multipart entries. */ public void setMultipartBoundary(String boundary) { if (boundary != null) { this.multipartBoundary = boundary; setMultipart(true); } else { this.multipartBoundary = DEFAULT_BOUNDARY; } setMediaType(createMultipartMediaType(getMultipartBoundary())); } @Override public void write(OutputStream outputStream) throws IOException { if (isMultipart()) { for (FormData data : getEntries()) { // Write the boundary line outputStream.write(("--" + getMultipartBoundary()).getBytes()); HeaderUtils.writeCRLF(outputStream); if (StringUtils.isNullOrEmpty(data.getFilename()) && MediaType.TEXT_PLAIN.equals(data.getMediaType())) { // Write the content disposition header line, as a simple // form field String line = "Content-Disposition: form-data; name=\"" + data.getName() + "\""; outputStream.write(line.getBytes()); HeaderUtils.writeCRLF(outputStream); } else { // Write the content disposition header line as file String line = "Content-Disposition: form-data; name=\"" + data.getName() + "\"; filename=\"" + data.getFilename() + "\""; outputStream.write(line.getBytes()); HeaderUtils.writeCRLF(outputStream); // Write the content type header line line = "Content-Type: " + ContentType.writeHeader(data .getValueRepresentation()); outputStream.write(line.getBytes()); HeaderUtils.writeCRLF(outputStream); } // Write the data content HeaderUtils.writeCRLF(outputStream); data.getValueRepresentation().write(outputStream); HeaderUtils.writeCRLF(outputStream); } // Write the final boundary line outputStream.write(("--" + getMultipartBoundary() + "--") .getBytes()); HeaderUtils.writeCRLF(outputStream); } else { Representation formRep = new StringRepresentation(getQueryString(), MediaType.APPLICATION_WWW_FORM, null, CharacterSet.UTF_8); formRep.write(outputStream); } } }