package com.koushikdutta.async.http; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.DataSink; import com.koushikdutta.async.LineEmitter; import com.koushikdutta.async.LineEmitter.StringCallback; import com.koushikdutta.async.NullDataCallback; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.ContinuationCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.future.Continuation; import com.koushikdutta.async.http.libcore.RawHeaders; import com.koushikdutta.async.http.server.BoundaryEmitter; import java.io.File; import java.util.ArrayList; import java.util.UUID; public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpRequestBody<Multimap> { LineEmitter liner; RawHeaders formData; ByteBufferList last; String lastName; public interface MultipartCallback { public void onPart(Part part); } @Override public void parse(DataEmitter emitter, final CompletedCallback completed) { setDataEmitter(emitter); setEndCallback(completed); } void handleLast() { if (last == null) return; if (formData == null) formData = new RawHeaders(); formData.add(lastName, last.peekString()); lastName = null; last = null; } public String getField(String name) { if (formData == null) return null; return formData.get(name); } @Override protected void onBoundaryEnd() { super.onBoundaryEnd(); handleLast(); } @Override protected void onBoundaryStart() { final RawHeaders headers = new RawHeaders(); liner = new LineEmitter(); liner.setLineCallback(new StringCallback() { @Override public void onStringAvailable(String s) { if (!"\r".equals(s)){ headers.addLine(s); } else { handleLast(); liner = null; setDataCallback(null); Part part = new Part(headers); if (mCallback != null) mCallback.onPart(part); if (getDataCallback() == null) { if (part.isFile()) { setDataCallback(new NullDataCallback()); return; } lastName = part.getName(); last = new ByteBufferList(); setDataCallback(new DataCallback() { @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { bb.get(last); } }); } } } }); setDataCallback(liner); } public static final String CONTENT_TYPE = "multipart/form-data"; public MultipartFormDataBody(String contentType, String[] values) { for (String value: values) { String[] splits = value.split("="); if (splits.length != 2) continue; if (!"boundary".equals(splits[0])) continue; setBoundary(splits[1]); return; } report(new Exception ("No boundary found for multipart/form-data")); } MultipartCallback mCallback; public void setMultipartCallback(MultipartCallback callback) { mCallback = callback; } public MultipartCallback getMultipartCallback() { return mCallback; } int written; @Override public void write(AsyncHttpRequest request, final DataSink sink, final CompletedCallback completed) { if (mParts == null) { sink.end(); return; } Continuation c = new Continuation(new CompletedCallback() { @Override public void onCompleted(Exception ex) { completed.onCompleted(ex); // if (ex == null) // sink.end(); // else // sink.close(); } }); for (final Part part: mParts) { c.add(new ContinuationCallback() { @Override public void onContinue(Continuation continuation, CompletedCallback next) throws Exception { part.getRawHeaders().setStatusLine(getBoundaryStart()); byte[] bytes = part.getRawHeaders().toHeaderString().getBytes(); com.koushikdutta.async.Util.writeAll(sink, bytes, next); written += bytes.length; } }) .add(new ContinuationCallback() { @Override public void onContinue(Continuation continuation, CompletedCallback next) throws Exception { written += part.length(); part.write(sink, next); } }) .add(new ContinuationCallback() { @Override public void onContinue(Continuation continuation, CompletedCallback next) throws Exception { byte[] bytes = "\r\n".getBytes(); com.koushikdutta.async.Util.writeAll(sink, bytes, next); written += bytes.length; } }); } c.add(new ContinuationCallback() { @Override public void onContinue(Continuation continuation, CompletedCallback next) throws Exception { byte[] bytes = (getBoundaryEnd() + "\r\n").getBytes(); com.koushikdutta.async.Util.writeAll(sink, bytes, next); written += bytes.length; assert written == totalToWrite; } }); c.start(); } @Override public String getContentType() { if (getBoundary() == null) { setBoundary("----------------------------" + UUID.randomUUID().toString().replace("-", "")); } return CONTENT_TYPE + "; boundary=" + getBoundary(); } @Override public boolean readFullyOnRequest() { return false; } int totalToWrite; @Override public int length() { if (getBoundary() == null) { setBoundary("----------------------------" + UUID.randomUUID().toString().replace("-", "")); } int length = 0; for (final Part part: mParts) { part.getRawHeaders().setStatusLine(getBoundaryStart()); if (part.length() == -1) return -1; length += part.length() + part.getRawHeaders().toHeaderString().getBytes().length + "\r\n".length(); } length += (getBoundaryEnd() + "\r\n").getBytes().length; return totalToWrite = length; } public MultipartFormDataBody() { } public void addFilePart(String name, File file) { addPart(new FilePart(name, file)); } public void addStringPart(String name, String value) { addPart(new StringPart(name, value)); } private ArrayList<Part> mParts; public void addPart(Part part) { if (mParts == null) mParts = new ArrayList<Part>(); mParts.add(part); } @Override public Multimap get() { return new Multimap(formData); } }