/*
* ContentDispositionParser.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* 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 org.simpleframework.http.parse;
import org.simpleframework.http.ContentDisposition;
import org.simpleframework.util.parse.ParseBuffer;
import org.simpleframework.util.parse.Parser;
/**
* The <code>ContentDispositionParser</code> object is used to represent a
* parser used to parse the Content-Disposition header. Its used when there is a
* multipart form upload to the server and allows the server to determine the
* individual part types.
*
* @author Niall Gallagher
*/
public class ContentDispositionParser extends Parser implements
ContentDisposition {
/**
* This is the buffer used to acquire values from the header.
*/
private ParseBuffer skip;
/**
* This is used to capture the name of the file if it is provided.
*/
private ParseBuffer file;
/**
* This is used to capture the name of the part if it is provided.
*/
private ParseBuffer name;
/**
* This is used to determine if the disposition is a file or form.
*/
private boolean form;
/**
* Constructor for the <code>ContentDispositionParser</code> object. This is
* used to create a parser that can parse a disposition header which is
* typically sent as part of a multipart upload. It can be used to determine
* the type of the upload.
*/
public ContentDispositionParser() {
this.file = new ParseBuffer();
this.name = new ParseBuffer();
this.skip = new ParseBuffer();
}
/**
* Constructor for the <code>ContentDispositionParser</code> object. This is
* used to create a parser that can parse a disposition header which is
* typically sent as part of a multipart upload. It can be used to determine
* the type of the upload.
*
* @param text
* this is the header value that is to be parsed
*/
public ContentDispositionParser(String text) {
this();
this.parse(text);
}
/**
* This method is used to acquire the file name of the part. This is used
* when the part represents a text parameter rather than a file. However,
* this can also be used with a file part.
*
* @return this returns the file name of the associated part
*/
@Override
public String getFileName() {
return this.file.toString();
}
/**
* This method is used to acquire the name of the part. Typically this is
* used when the part represents a text parameter rather than a file.
* However, this can also be used with a file part.
*
* @return this returns the name of the associated part
*/
@Override
public String getName() {
return this.name.toString();
}
/**
* This method is used to determine the type of a part. Typically a part is
* either a text parameter or a file. If this is true then the content
* represented by the associated part is a file.
*
* @return this returns true if the associated part is a file
*/
@Override
public boolean isFile() {
return !this.form || (this.file.length() > 0);
}
/**
* This will initialize the <code>Parser</code> when it is ready to parse a
* new <code>String</code>. This will reset the parser to a ready state.
* This method is invoked by the parser before the parse method is invoked,
* it is used to pack the contents of the header and clear any previous
* tokens used.
*/
@Override
protected void init() {
if (this.count > 0) {
this.pack();
}
this.clear();
}
/**
* This is used to clear all previously collected tokens. This allows the
* parser to be reused when there are multiple source strings to be parsed.
* Clearing of the tokens is performed when the parser is initialized.
*/
protected void clear() {
this.file.clear();
this.name.clear();
this.form = false;
this.off = 0;
}
/**
* This is the method that should be implemented to read the buffer. This
* method will extract the type from the header and the tries to extract the
* optional parameters if they are in the header. The optional parts are the
* file name and name.
*/
@Override
protected void parse() {
this.type();
this.parameters();
}
/**
* This is used to remove all whitespace characters from the
* <code>String</code> excluding the whitespace within literals. The
* definition of a literal can be found in RFC 2616.
* <p>
* The definition of a literal for RFC 2616 is anything between 2 quotes but
* excuding quotes that are prefixed with the backward slash character.
*/
private void pack() {
char old = this.buf[0];
int len = this.count;
int seek = 0;
int pos = 0;
while (seek < len) {
char ch = this.buf[seek++];
if ((ch == '"') && (old != '\\')) { /* qd-text */
this.buf[pos++] = ch;
while (seek < len) {
old = this.buf[seek - 1];
ch = this.buf[seek++];
this.buf[pos++] = ch;
if ((ch == '"') && (old != '\\')) { /* qd-text */
break;
}
}
} else if (!this.space(ch)) {
old = this.buf[seek - 1];
this.buf[pos++] = old;
}
}
this.count = pos;
}
/**
* This is used to determine the type of the disposition header. This will
* allow the parser to determine it the header represents form data or a
* file upload. Once it determines the type of the upload header it sets an
* internal flag which can be used.
*/
private void type() {
if (this.skip("form-data")) {
this.form = true;
} else if (this.skip("file")) {
this.form = false;
}
}
/**
* This will read the parameters from the header value. This will search for
* the <code>filename</code> parameter within the set of parameters which
* are given to the type. The <code>filename</code> param and the the
* <code>name</code> are tokenized by this method.
*/
private void parameters() {
while (this.skip(";")) {
if (this.skip("filename=")) {
this.value(this.file);
} else {
if (this.skip("name=")) {
this.value(this.name);
} else {
this.parameter();
}
}
}
}
/**
* This will read the parameters from the header value. This will search for
* the <code>filename</code> parameter within the set of parameters which
* are given to the type. The <code>filename</code> param and the the
* <code>name</code> are tokenized by this method.
*/
private void parameter() {
this.name();
this.off++;
this.value(this.skip);
}
/**
* This will simply read all characters from the buffer before the first '='
* character. This represents a parameter name (see RFC 2616 for token). The
* parameter name is not buffered it is simply read from the buffer. This
* will not cause an <code>IndexOutOfBoundsException</code> as each offset
* is checked before it is acccessed.
*/
private void name() {
while (this.off < this.count) {
if (this.buf[this.off] == '=') {
break;
}
this.off++;
}
}
/**
* This is used to read a parameters value from the buf. This will read all
* <code>char</code>'s upto but excluding the first terminal
* <code>char</code> encountered from the off within the buf, or if the
* value is a literal it will read a literal from the buffer (literal is any
* data between quotes except if the quote is prefixed with a backward slash
* character).
*
* @param value
* this is the parse buffer to append the value to
*/
private void value(ParseBuffer value) {
if (this.quote(this.buf[this.off])) {
char quote = this.buf[this.off];
for (this.off++; this.off < this.count;) {
if (quote == this.buf[this.off]) {
if (this.buf[++this.off - 2] != '\\') {
break;
}
}
value.append(this.buf[this.off++]);
}
} else {
while (this.off < this.count) {
if (this.buf[this.off] == ';') {
break;
}
value.append(this.buf[this.off]);
this.off++;
}
}
}
/**
* This method is used to determine if the specified character is a quote
* character. The quote character is typically used as a boundary for the
* values within the header. This accepts a single or double quote.
*
* @param ch
* the character to determine if it is a quotation
*
* @return true if the character provided is a quotation character
*/
private boolean quote(char ch) {
return (ch == '\'') || (ch == '"');
}
}