Skip to content
Marc Larue edited this page Apr 19, 2016 · 5 revisions
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import se.rupy.http.Deploy;
import se.rupy.http.Event;
import se.rupy.http.Input;
import se.rupy.http.Output;
import se.rupy.http.Query;
import se.rupy.http.Root;
import se.rupy.http.Service;
import se.rupy.http.User;

/*
 * This service only supports one file at the time.
 */
public class Upload extends Service {
	private static int SIZE = 1024;

	public String path() { return "/upload"; }
	public void filter(Event event) throws Event, Exception {
		Output out = event.output();
		if(event.query().method() == Query.POST) {
			Item item = new Item();
			item.path = "file";
			item = save(event, item);
			out.println("File deployed!");
		}
		else {
			out.print("<form enctype=\"multipart/form-data\" name=\"upload\" action=\"upload\" method=\"post\">");
			out.print("<input name=\"file\" type=\"file\"/>");
			out.print("<input type=\"submit\">");
			out.print("</form>");
		}
	}

	public static Item save(Event event, Item item) throws Event, Exception {
		String type = event.query().header("content-type");
		String boundary = "--" + unquote(type.substring(type.indexOf("boundary=") + 9));

		Input in = event.input();
		String line = in.line();

		while(line != null) {
			/*
			 * find boundary
			 */

			if(line.equals(boundary + "--")) {
				User.redirect(event, "/");
			}

			if(line.equals(boundary)) {
				line = in.line();

				/*
				 * read headers; parse filename and content-type
				 */

				while(line != null && !line.equals("")) {
					int colon = line.indexOf(":");

					if (colon > -1) {
						String name = line.substring(0, colon).toLowerCase();
						String value = line.substring(colon + 1).trim();

						if(name.equals("content-disposition")) {
							item.name = unpath(unquote(value.substring(value.indexOf("filename=") + 9).trim()));
						}

						if(name.equals("content-type")) {
							item.type = value;
						}
					}

					line = in.line();
				}

				if(item.name == null || item.name.length() == 0) {
					User.redirect(event, "/");
				}

				/*
				 * create path and file
				 */

				String home = "app/" + Root.host();
				
				java.io.File path = new java.io.File(home + "/" + item.path);

				if(!path.exists()) {
					path.mkdirs();
				}

				item.file = new java.io.File(home + "/" + item.path + "/" + item.name);
				FileOutputStream out = new FileOutputStream(item.file);

				/*
				 * stream data
				 */

				Boundary bound = new Boundary();
				bound.value = boundary.getBytes();

				byte[] data = new byte[SIZE];
				int read = in.read(data);

				while(read > -1) {
					try {
						out.write(data, 0, bound.find(read, data, out));
					}
					catch(Boundary.EOB eob) {
						out.write(data, 0, eob.index - 2); // remove the last trailing \r\n
						out.flush();
						out.close();

						// only handles one file for now, 
						// need to rewind the stream for 
						// multiple files.
						return item;
					}

					read = in.read(data);
				}

				throw new IOException("Boundary not found. (trailing)");
			}

			line = in.line();
		}

		throw new IOException("Boundary not found. (initing)");
	}

	static String unquote(String value) {
		int index = value.lastIndexOf("\"");

		if(index > -1) {
			return value.substring(1, index);
		}

		return value;
	}

	static String unpath(String value) {
		int index = Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'));

		if(index > -1) {
			return value.substring(index + 1);
		}

		return value;
	}

	public static class Item {
		String name;
		String path;
		String type;
		java.io.File file;

		public String toString() {
			return name + " " + path + " " + type;
		}
	}

	/*
	 * The multipart protocol was poorly designed, so we have to check 
	 * for the boundary for every byte and handle things if the boundary 
	 * is between buffers.
	 * 
	 * TODO: An alternative way is to write everything from headers onwards 
	 * to a file and then search for the boundary starting at the end of the 
	 * file, and crop the boundary.
	 */
	public static class Boundary {
		byte[] value;
		int index; // remember boundary index between finds

		int find(int read, byte[] data, OutputStream out) throws EOB, IOException {
			int wrap = index;

			for(int i = 0; i < read; i++) {
				if(data[i] == value[index]) { 		// maybe boundary
					if(index == value.length - 1) { // boundary
						int start = i - value.length + 1;
						throw new EOB(start > -1 ? start : 0);
					}
					index++;
				}
				else { 			   // not boundary
					if(wrap > 0) { // write non-boundary from last find
						out.write(value, 0, wrap);
						wrap = 0;
					}
					index = 0;
				}
			}

			return read - index;
		}

		public static class EOB extends Throwable {
			int index;

			public EOB(int index) {
				this.index = index;
			}
		}
	}
}
Clone this wiki locally