/***************************************************************************
* Copyright (C) by Fabrizio Montesi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library General Public License as *
* published by the Free Software Foundation; either version 2 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.net.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import jolie.net.HttpProtocol;
import jolie.runtime.ByteArray;
import jolie.runtime.Value;
public class MultiPartFormDataParser
{
private final static String URL_DECODER_ENC = "UTF-8";
private final String boundary;
private final Value value;
private final HttpMessage message;
private final Map< String, PartProperties > partPropertiesMap = new HashMap< String, PartProperties >();
private static final Pattern parametersSplitPattern = Pattern.compile( ";" );
private static final Pattern keyValueSplitPattern = Pattern.compile( "=" );
public class PartProperties {
private String filename = null;
private void setFilename( String filename )
{
this.filename = filename;
}
public String filename()
{
return filename;
}
}
public MultiPartFormDataParser( HttpMessage message, Value value )
throws IOException
{
final String[] params = parametersSplitPattern.split( message.getProperty( "content-type" ) );
String b = null;
try {
for( String param : params ) {
param = param.trim();
if ( param.startsWith( "boundary" ) ) {
b = "--" + param.split( "=" )[1];
}
}
if ( b == null ) {
throw new IOException( "Invalid boundary in multipart/form-data http message" );
}
} catch( ArrayIndexOutOfBoundsException e ) {
throw new IOException( "Invalid boundary in multipart/form-data http message" );
}
this.value = value;
this.boundary = b;
this.message = message;
}
private PartProperties getPartProperties( String partName )
{
PartProperties ret = partPropertiesMap.get( partName );
if ( ret == null ) {
ret = new PartProperties();
partPropertiesMap.put( partName, ret );
}
return ret;
}
public Collection< Entry< String, PartProperties > > getPartPropertiesSet()
{
return partPropertiesMap.entrySet();
}
private void parsePart( String part, int offset )
throws IOException
{
boolean hasContentType;
// Split header from content
String[] hc = part.split( HttpProtocol.CRLF + HttpProtocol.CRLF );
BufferedReader reader =
new BufferedReader(
new StringReader( hc[0] ) );
String line, name = null, filename = null;
String[] params;
// Parse part header
hasContentType = false;
while( (line=reader.readLine()) != null && !line.isEmpty() ) {
params = parametersSplitPattern.split( line );
for( String param : params ) {
param = param.trim();
if ( param.startsWith( "name" ) ) {
try {
name = keyValueSplitPattern.split( param )[1];
// Names are surronded by "": cut them.
name = URLDecoder.decode( name.substring( 1, name.length() - 1 ), URL_DECODER_ENC );
} catch( ArrayIndexOutOfBoundsException e ) {
throw new IOException( "Invalid name specified in multipart form data element" );
}
} else if ( param.startsWith( "filename" ) ) {
try {
filename = keyValueSplitPattern.split( param )[1];
// Filenames are surronded by quotes "": cut them.
filename = URLDecoder.decode( filename.substring( 1, filename.length() - 1 ), URL_DECODER_ENC );
} catch( ArrayIndexOutOfBoundsException e ) {
throw new IOException( "Invalid filename specified in multipart form data element" );
}
} else if ( param.startsWith( "Content-Type" ) ) {
// TODO: parse content-type better, now it checks only if it exists or not
hasContentType = true;
}
}
}
if ( name == null ) {
throw new IOException( "Invalid multipart form data element: missing name" );
}
offset += hc[0].length() + 4;
Value child = value.getNewChild( name );
if ( hc.length > 1 ) {
if ( hasContentType == true ) {
child.setValue( new ByteArray( Arrays.copyOfRange( message.content(), offset, offset + hc[1].length() ) ) );
} else {
child.setValue( new String( Arrays.copyOfRange( message.content(), offset, offset + hc[1].length() ) ) );
}
}/* else {
value.getNewChild( name ).setValue( new ByteArray( new byte[0] ) );
}*/
if ( filename != null ) {
getPartProperties( name ).setFilename( filename );
}
}
public void parse()
throws IOException
{
String[] parts = (HttpProtocol.CRLF + new String( message.content(), "US-ASCII" )).split( boundary + "--" );
parts = (parts[0] + boundary + HttpProtocol.CRLF).split( HttpProtocol.CRLF + boundary + HttpProtocol.CRLF );
// The first one is always empty, so we start from 1
int offset = boundary.length() + 2;
for( int i = 1; i < parts.length; i++ ) {
parsePart( parts[i], offset );
offset += parts[i].length() + boundary.length() + 4;
}
}
}