/**
* 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.internal;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.ext.html.FormData;
import org.restlet.representation.Representation;
import org.restlet.util.Series;
/**
* Form reader.
*
* @author Jerome Louvel
*/
public class FormReader {
/** The encoding to use, decoding is enabled, see {@link #decoding}. */
private volatile CharacterSet characterSet;
/** Indicates if the entries should be decoded. */
private volatile boolean decoding;
/** The separator character used between entries. */
private volatile char separator;
/** The form stream. */
private volatile InputStream stream;
/**
* Constructor.<br>
* In case the representation does not define a character set, the UTF-8
* character set is used.
*
* @param representation
* The web form content.
* @throws IOException
* if the stream of the representation could not be opened.
*/
public FormReader(Representation representation) throws IOException {
this.decoding = true;
this.stream = representation.getStream();
this.separator = '&';
if (representation.getCharacterSet() != null) {
this.characterSet = representation.getCharacterSet();
} else {
this.characterSet = CharacterSet.UTF_8;
}
}
/**
* Constructor. Will leave the parsed data encoded.
*
* @param queryString
* The query string.
*/
public FormReader(String queryString, char separator) {
this.decoding = false;
// [ifndef gwt] line
this.stream = new ByteArrayInputStream(queryString.getBytes());
// [ifdef gwt] line uncomment
// this.stream = new
// org.restlet.engine.io.StringInputStream(queryString);
this.characterSet = null;
this.separator = separator;
}
/**
* Constructor.
*
* @param queryString
* The query string.
* @param characterSet
* The supported character encoding. Set to null to leave the
* data encoded.
*/
public FormReader(String queryString, CharacterSet characterSet,
char separator) {
this.decoding = true;
// [ifndef gwt] line
this.stream = new ByteArrayInputStream(queryString.getBytes());
// [ifdef gwt] line uncomment
// this.stream = new
// org.restlet.engine.io.StringInputStream(queryString);
this.characterSet = characterSet;
this.separator = separator;
}
/**
* Adds the entries into a given series.
*
* @param entries
* The target series of entries.
*/
public void addEntries(Series<FormData> entries) {
boolean readNext = true;
FormData entry = null;
if (this.stream != null) {
// Let's read all form data entries
try {
while (readNext) {
entry = readNextEntry();
if (entry != null) {
// Add parsed entry to the form
entries.add(entry);
} else {
// Last entry parsed
readNext = false;
}
}
} catch (IOException ioe) {
Context.getCurrentLogger()
.log(Level.WARNING,
"Unable to parse a form entry. Skipping the remaining entries.",
ioe);
}
try {
this.stream.close();
} catch (IOException ioe) {
Context.getCurrentLogger().log(Level.WARNING,
"Unable to close the form input stream", ioe);
}
}
}
/**
* Reads all the entries.
*
* @return The form read.
* @throws IOException
* If the entries could not be read.
*/
public Series<FormData> read() throws IOException {
Series<FormData> result = new Series<FormData>(FormData.class);
FormData entry = readNextEntry();
while (entry != null) {
result.add(entry);
entry = readNextEntry();
}
this.stream.close();
return result;
}
/**
* Reads the entries whose name is a key in the given map. If a matching
* entry is found, its value is put in the map. If multiple values are
* found, a list is created and set in the map.
*
* @param entries
* The entries map controlling the reading.
* @throws IOException
* If the entries could not be read.
*/
@SuppressWarnings("unchecked")
public void readEntries(Map<String, Object> entries) throws IOException {
FormData entry = readNextEntry();
Object currentValue = null;
while (entry != null) {
if (entries.containsKey(entry.getName())) {
currentValue = entries.get(entry.getName());
if (currentValue != null) {
List<Object> values = null;
if (currentValue instanceof List) {
// Multiple values already found for this entry
values = (List<Object>) currentValue;
} else {
// Second value found for this entry
// Create a list of values
values = new ArrayList<Object>();
values.add(currentValue);
entries.put(entry.getName(), values);
}
if (entry.getValue() == null) {
values.add(Series.EMPTY_VALUE);
} else {
values.add(entry.getValue());
}
} else {
if (entry.getValue() == null) {
entries.put(entry.getName(), Series.EMPTY_VALUE);
} else {
entries.put(entry.getName(), entry.getValue());
}
}
}
entry = readNextEntry();
}
this.stream.close();
}
/**
* Reads the entries with the given name. If multiple values are found, a
* list is returned created.
*
* @param name
* The entry name to match.
* @return The entry value or list of values.
* @throws IOException
* If the entry could not be read.
*/
@SuppressWarnings("unchecked")
public Object readEntry(String name) throws IOException {
FormData entry = readNextEntry();
Object result = null;
while (entry != null) {
if (entry.getName().equals(name)) {
if (result != null) {
List<Object> values = null;
if (result instanceof List) {
// Multiple values already found for this entry
values = (List<Object>) result;
} else {
// Second value found for this entry
// Create a list of values
values = new ArrayList<Object>();
values.add(result);
result = values;
}
if (entry.getValue() == null) {
values.add(Series.EMPTY_VALUE);
} else {
values.add(entry.getValue());
}
} else {
if (entry.getValue() == null) {
result = Series.EMPTY_VALUE;
} else {
result = entry.getValue();
}
}
}
entry = readNextEntry();
}
this.stream.close();
return result;
}
/**
* Reads the first entry with the given name.
*
* @param name
* The entry name to match.
* @return The entry value.
* @throws IOException
*/
public FormData readFirstEntry(String name) throws IOException {
FormData entry = readNextEntry();
FormData result = null;
while ((entry != null) && (result == null)) {
if (entry.getName().equals(name)) {
result = entry;
}
entry = readNextEntry();
}
this.stream.close();
return result;
}
/**
* Reads the next entry available or null.
*
* @return The next entry available or null.
* @throws IOException
* If the next entry could not be read.
*/
public FormData readNextEntry() throws IOException {
FormData result = null;
try {
boolean readingName = true;
boolean readingValue = false;
final StringBuilder nameBuffer = new StringBuilder();
final StringBuilder valueBuffer = new StringBuilder();
int nextChar = 0;
while ((result == null) && (nextChar != -1)) {
nextChar = this.stream.read();
if (readingName) {
if (nextChar == '=') {
if (nameBuffer.length() > 0) {
readingName = false;
readingValue = true;
} else {
throw new IOException(
"Empty entry name detected. Please check your form data");
}
} else if ((nextChar == this.separator) || (nextChar == -1)) {
if (nameBuffer.length() > 0) {
result = FormUtils.create(nameBuffer, null,
this.decoding, this.characterSet);
} else if (nextChar == -1) {
// Do nothing return null preference
} else {
Context.getCurrentLogger()
.fine("Empty entry name detected. Please check your form data");
}
} else {
nameBuffer.append((char) nextChar);
}
} else if (readingValue) {
if ((nextChar == this.separator) || (nextChar == -1)) {
if (valueBuffer.length() > 0) {
result = FormUtils.create(nameBuffer, valueBuffer,
this.decoding, this.characterSet);
} else {
result = FormUtils.create(nameBuffer, null,
this.decoding, this.characterSet);
}
} else {
valueBuffer.append((char) nextChar);
}
}
}
} catch (UnsupportedEncodingException uee) {
throw new IOException(
"Unsupported encoding. Please contact the administrator");
}
return result;
}
}