package com.koushikdutta.async.http.server; import android.util.Log; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.FilteredDataEmitter; import java.nio.ByteBuffer; public class BoundaryEmitter extends FilteredDataEmitter { private byte[] boundary; public void setBoundary(String boundary) { this.boundary = ("\r\n--" + boundary).getBytes(); } public String getBoundary() { if (boundary == null) return null; return new String(boundary, 4, boundary.length - 4); } public String getBoundaryStart() { assert boundary != null; return new String(boundary, 2, boundary.length - 2); } public String getBoundaryEnd() { assert boundary != null; return getBoundaryStart() + "--\r\n"; } protected void onBoundaryStart() { } protected void onBoundaryEnd() { } // >= 0 matching // -1 matching - (start of boundary end) or \r (boundary start) // -2 matching - (end of boundary end) // -3 matching \r after boundary // -4 matching \n after boundary // the state starts out having already matched \r\n /* Content-Type: multipart/form-data; boundary=----------------------------bc3c801ac760 ------------------------------bc3c801ac760 Content-Disposition: form-data; name="my-file"; filename="foo" Content-Type: application/octet-stream foo <---------------- the newline is NOT PART OF THE PAYLOAD ------------------------------bc3c801ac760-- */ int state = 2; @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { // System.out.println(bb.getString()); // System.out.println("chunk: " + bb.remaining()); // System.out.println("state: " + state); // if we were in the middle of a potential match, let's throw that // at the beginning of the buffer and process it too. if (state > 0) { ByteBuffer b = ByteBuffer.wrap(boundary, 0, state).duplicate(); bb.addFirst(b); state = 0; } int last = 0; byte[] buf = new byte[bb.remaining()]; bb.get(buf); for (int i = 0; i < buf.length; i++) { if (state >= 0) { if (buf[i] == boundary[state]) { state++; if (state == boundary.length) state = -1; } else if (state > 0) { // let's try matching again one byte after the start // of last match occurrence i -= state; state = 0; } } else if (state == -1) { if (buf[i] == '\r') { state = -4; int len = i - last - boundary.length; if (last != 0 || len != 0) { ByteBuffer b = ByteBuffer.wrap(buf, last, len); ByteBufferList list = new ByteBufferList(); list.add(b); super.onDataAvailable(this, list); } // System.out.println("bstart"); onBoundaryStart(); } else if (buf[i] == '-') { state = -2; } else { report(new Exception("Invalid multipart/form-data. Expected \r or -")); return; } } else if (state == -2) { if (buf[i] == '-') { state = -3; } else { report(new Exception("Invalid multipart/form-data. Expected -")); return; } } else if (state == -3) { if (buf[i] == '\r') { state = -4; ByteBuffer b = ByteBuffer.wrap(buf, last, i - last - boundary.length - 2); ByteBufferList list = new ByteBufferList(); list.add(b); super.onDataAvailable(this, list); // System.out.println("bend"); onBoundaryEnd(); } else { report(new Exception("Invalid multipart/form-data. Expected \r")); return; } } else if (state == -4) { if (buf[i] == '\n') { last = i + 1; state = 0; } else { report(new Exception("Invalid multipart/form-data. Expected \n")); } } else { assert false; report(new Exception("Invalid multipart/form-data. Unknown state?")); } } if (last < buf.length) { // System.out.println("amount left at boundary: " + (buf.length - last)); // System.out.println("State: " + state); // System.out.println(state); int keep = Math.max(state, 0); ByteBuffer b = ByteBuffer.wrap(buf, last, buf.length - last - keep); ByteBufferList list = new ByteBufferList(); list.add(b); super.onDataAvailable(this, list); } } }