Merge remote-tracking branch 'origin/jetty-8'
Conflicts: jetty-util/src/main/java/org/eclipse/jetty/util/B64Code.java jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
This commit is contained in:
commit
1762ab7de5
|
@ -336,11 +336,33 @@ public class B64Code
|
|||
if (encoded==null)
|
||||
return null;
|
||||
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);
|
||||
decode(encoded, bout);
|
||||
return bout.toByteArray();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Base 64 decode as described in RFC 2045.
|
||||
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
|
||||
* @param encoded String to decode.
|
||||
* @param output stream for decoded bytes
|
||||
* @return byte array containing the decoded form of the input.
|
||||
* @throws IllegalArgumentException if the input is not a valid
|
||||
* B64 encoding.
|
||||
*/
|
||||
static public void decode (String encoded, ByteArrayOutputStream bout)
|
||||
{
|
||||
if (encoded==null)
|
||||
return;
|
||||
|
||||
if (bout == null)
|
||||
throw new IllegalArgumentException("No outputstream for decoded bytes");
|
||||
|
||||
int ci=0;
|
||||
byte nibbles[] = new byte[4];
|
||||
int s=0;
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);
|
||||
|
||||
|
||||
while (ci<encoded.length())
|
||||
{
|
||||
char c=encoded.charAt(ci++);
|
||||
|
@ -375,8 +397,9 @@ public class B64Code
|
|||
|
||||
}
|
||||
|
||||
return bout.toByteArray();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public static void encode(int value,Appendable buf) throws IOException
|
||||
{
|
||||
|
|
|
@ -20,15 +20,14 @@ package org.eclipse.jetty.util;
|
|||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -502,11 +501,13 @@ public class MultiPartInputStreamParser
|
|||
|
||||
// Read each part
|
||||
boolean lastPart=false;
|
||||
String contentDisposition=null;
|
||||
String contentType=null;
|
||||
String contentTransferEncoding=null;
|
||||
|
||||
outer:while(!lastPart)
|
||||
{
|
||||
String contentDisposition=null;
|
||||
String contentType=null;
|
||||
String contentTransferEncoding=null;
|
||||
|
||||
MultiMap headers = new MultiMap();
|
||||
while(true)
|
||||
{
|
||||
|
@ -577,13 +578,21 @@ public class MultiPartInputStreamParser
|
|||
continue;
|
||||
}
|
||||
|
||||
//Have a new Part
|
||||
MultiPart part = new MultiPart(name, filename);
|
||||
part.setHeaders(headers);
|
||||
part.setContentType(contentType);
|
||||
_parts.add(name, part);
|
||||
part.open();
|
||||
|
||||
InputStream partInput = null;
|
||||
if ("base64".equalsIgnoreCase(contentTransferEncoding))
|
||||
{
|
||||
_in = new Base64InputStream(_in);
|
||||
partInput = new Base64InputStream((ReadLineInputStream)_in);
|
||||
}
|
||||
else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
|
||||
{
|
||||
_in = new FilterInputStream(_in)
|
||||
partInput = new FilterInputStream(_in)
|
||||
{
|
||||
@Override
|
||||
public int read() throws IOException
|
||||
|
@ -604,17 +613,10 @@ public class MultiPartInputStreamParser
|
|||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
partInput = _in;
|
||||
|
||||
|
||||
|
||||
//Have a new Part
|
||||
MultiPart part = new MultiPart(name, filename);
|
||||
part.setHeaders(headers);
|
||||
part.setContentType(contentType);
|
||||
_parts.add(name, part);
|
||||
|
||||
part.open();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
int state=-2;
|
||||
|
@ -626,33 +628,38 @@ public class MultiPartInputStreamParser
|
|||
while(true)
|
||||
{
|
||||
int b=0;
|
||||
while((c=(state!=-2)?state:_in.read())!=-1)
|
||||
while((c=(state!=-2)?state:partInput.read())!=-1)
|
||||
{
|
||||
total ++;
|
||||
if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
|
||||
throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
|
||||
|
||||
state=-2;
|
||||
|
||||
// look for CR and/or LF
|
||||
if(c==13||c==10)
|
||||
{
|
||||
if(c==13)
|
||||
{
|
||||
_in.mark(1);
|
||||
int tmp=_in.read();
|
||||
partInput.mark(1);
|
||||
int tmp=partInput.read();
|
||||
if (tmp!=10)
|
||||
_in.reset();
|
||||
partInput.reset();
|
||||
else
|
||||
state=tmp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// look for boundary
|
||||
|
||||
// Look for boundary
|
||||
if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
|
||||
{
|
||||
b++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is not a boundary
|
||||
// Got a character not part of the boundary, so we don't have the boundary marker.
|
||||
// Write out as many chars as we matched, then the char we're looking at.
|
||||
if(cr)
|
||||
part.write(13);
|
||||
|
||||
|
@ -667,7 +674,8 @@ public class MultiPartInputStreamParser
|
|||
part.write(c);
|
||||
}
|
||||
}
|
||||
// check partial boundary
|
||||
|
||||
// Check for incomplete boundary match, writing out the chars we matched along the way
|
||||
if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
|
||||
{
|
||||
if(cr)
|
||||
|
@ -680,15 +688,18 @@ public class MultiPartInputStreamParser
|
|||
part.write(byteBoundary,0,b);
|
||||
b=-1;
|
||||
}
|
||||
// boundary match
|
||||
|
||||
// Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
|
||||
if(b>0||c==-1)
|
||||
{
|
||||
|
||||
if(b==byteBoundary.length)
|
||||
lastPart=true;
|
||||
if(state==10)
|
||||
state=-2;
|
||||
break;
|
||||
}
|
||||
|
||||
// handle CR LF
|
||||
if(cr)
|
||||
part.write(13);
|
||||
|
@ -764,14 +775,15 @@ public class MultiPartInputStreamParser
|
|||
|
||||
private static class Base64InputStream extends InputStream
|
||||
{
|
||||
BufferedReader _in;
|
||||
ReadLineInputStream _in;
|
||||
String _line;
|
||||
byte[] _buffer;
|
||||
int _pos;
|
||||
|
||||
public Base64InputStream (InputStream in)
|
||||
|
||||
public Base64InputStream(ReadLineInputStream rlis)
|
||||
{
|
||||
_in = new BufferedReader(new InputStreamReader(in));
|
||||
_in = rlis;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -779,18 +791,29 @@ public class MultiPartInputStreamParser
|
|||
{
|
||||
if (_buffer==null || _pos>= _buffer.length)
|
||||
{
|
||||
_line = _in.readLine();
|
||||
//Any CR and LF will be consumed by the readLine() call.
|
||||
//We need to put them back into the bytes returned from this
|
||||
//method because the parsing of the multipart content uses them
|
||||
//as markers to determine when we've reached the end of a part.
|
||||
_line = _in.readLine();
|
||||
if (_line==null)
|
||||
return -1;
|
||||
return -1; //nothing left
|
||||
if (_line.startsWith("--"))
|
||||
_buffer=(_line+"\r\n").getBytes();
|
||||
_buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
|
||||
else if (_line.length()==0)
|
||||
_buffer="\r\n".getBytes();
|
||||
_buffer="\r\n".getBytes(); //blank line
|
||||
else
|
||||
_buffer=B64Code.decode(_line);
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
|
||||
B64Code.decode(_line, baos);
|
||||
baos.write(13);
|
||||
baos.write(10);
|
||||
_buffer = baos.toByteArray();
|
||||
}
|
||||
|
||||
_pos=0;
|
||||
}
|
||||
|
||||
return _buffer[_pos++];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -724,6 +724,98 @@ public class MultiPartInputStreamTest
|
|||
assertEquals(5, p.getSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64EncodedContent () throws Exception
|
||||
{
|
||||
String contentWithEncodedPart =
|
||||
"--AaB03x\r\n"+
|
||||
"Content-disposition: form-data; name=\"other\"\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"other" + "\r\n"+
|
||||
"--AaB03x\r\n"+
|
||||
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+
|
||||
"Content-Transfer-Encoding: base64\r\n"+
|
||||
"Content-Type: application/octet-stream\r\n"+
|
||||
"\r\n"+
|
||||
B64Code.encode("hello jetty") + "\r\n"+
|
||||
"--AaB03x\r\n"+
|
||||
"Content-disposition: form-data; name=\"final\"\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"the end" + "\r\n"+
|
||||
"--AaB03x--\r\n";
|
||||
|
||||
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
|
||||
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
|
||||
_contentType,
|
||||
config,
|
||||
_tmpDir);
|
||||
mpis.setDeleteOnExit(true);
|
||||
Collection<Part> parts = mpis.getParts();
|
||||
assertEquals(3, parts.size());
|
||||
|
||||
Part p1 = mpis.getPart("other");
|
||||
assertNotNull(p1);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
IO.copy(p1.getInputStream(), baos);
|
||||
assertEquals("other", baos.toString("US-ASCII"));
|
||||
|
||||
Part p2 = mpis.getPart("stuff");
|
||||
assertNotNull(p2);
|
||||
baos = new ByteArrayOutputStream();
|
||||
IO.copy(p2.getInputStream(), baos);
|
||||
assertEquals("hello jetty", baos.toString("US-ASCII"));
|
||||
|
||||
Part p3 = mpis.getPart("final");
|
||||
assertNotNull(p3);
|
||||
baos = new ByteArrayOutputStream();
|
||||
IO.copy(p3.getInputStream(), baos);
|
||||
assertEquals("the end", baos.toString("US-ASCII"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuotedPrintableEncoding () throws Exception
|
||||
{
|
||||
String contentWithEncodedPart =
|
||||
"--AaB03x\r\n"+
|
||||
"Content-disposition: form-data; name=\"other\"\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"other" + "\r\n"+
|
||||
"--AaB03x\r\n"+
|
||||
"Content-disposition: form-data; name=\"stuff\"; filename=\"stuff.txt\"\r\n"+
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n"+
|
||||
"Content-Type: text/plain\r\n"+
|
||||
"\r\n"+
|
||||
"truth=3Dbeauty" + "\r\n"+
|
||||
"--AaB03x--\r\n";
|
||||
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
|
||||
MultiPartInputStreamParser mpis = new MultiPartInputStreamParser(new ByteArrayInputStream(contentWithEncodedPart.getBytes()),
|
||||
_contentType,
|
||||
config,
|
||||
_tmpDir);
|
||||
mpis.setDeleteOnExit(true);
|
||||
Collection<Part> parts = mpis.getParts();
|
||||
assertEquals(2, parts.size());
|
||||
|
||||
Part p1 = mpis.getPart("other");
|
||||
assertNotNull(p1);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
IO.copy(p1.getInputStream(), baos);
|
||||
assertEquals("other", baos.toString("US-ASCII"));
|
||||
|
||||
Part p2 = mpis.getPart("stuff");
|
||||
assertNotNull(p2);
|
||||
baos = new ByteArrayOutputStream();
|
||||
IO.copy(p2.getInputStream(), baos);
|
||||
assertEquals("truth=beauty", baos.toString("US-ASCII"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private String createMultipartRequestString(String filename)
|
||||
{
|
||||
int length = filename.length();
|
||||
|
|
Loading…
Reference in New Issue