/* * Sewing: a Simple framework for Embedded-OSGi Web Development * Copyright (C) 2009 Bug Labs * Email: bballantine@buglabs.net * Site: http://www.buglabs.net * * This library 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 library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.buglabs.osgi.sewing.pub.util; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.osgi.service.log.LogService; import com.buglabs.osgi.sewing.LogManager; /** * Static methods for helping us parse a string list of paramaters typically * pulled off the request object in a get request or form post * * @author brian * */ public class RequestHelper { private static final String CONTENT_TYPE = "Content-Type"; private static final String MULTIPART_TYPE = "multipart/form-data"; private static final String BOUNDARY_KEY = "boundary"; private static final String ENCODING = "ISO-8859-1"; public static RequestParameters parseParams(HttpServletRequest req) { RequestParameters params = new RequestParameters(); // do the querystring String querystring = req.getQueryString(); if (querystring != null) params = parseParamString(querystring); String requestBody = ""; try { requestBody = RequestReader.read(req); } catch (IOException e) { LogManager.log(LogService.LOG_DEBUG, "Failed to read body.", e); } params = parseParamString(requestBody, params); // since we've pulled the data off the input stream // it makes sense to make the raw body available params.put(RequestParameters.REQUEST_BODY_PARAM_KEY, requestBody); return params; } /** * parse a param string into a RequestParameters object * * @param param_string * @return */ static public RequestParameters parseParamString(String param_string) { return parseParamString(param_string, new RequestParameters()); } /** * Parse a param string, adding it to an existing parameters object * * @param param_string * @param param_map * @return */ static public RequestParameters parseParamString(String param_string, RequestParameters param_map) { param_string = param_string.trim(); String k = "", v = "", tmp_key = ""; char mode = 'k'; int len = param_string.length(); for (int n = 0; n != len; ++n) { char c = param_string.charAt(n); if (c == '&') { tmp_key = unescape(k); if (param_map.get(tmp_key) == null) param_map.put(tmp_key, unescape(v)); else param_map.put(tmp_key, param_map.get(tmp_key) + "," + unescape(v)); k = v = ""; mode = 'k'; } else if (c == '=') { mode = 'v'; } else { if (mode == 'k') { k += c; } else if (mode == 'v') { v += c; } } if (n == len - 1 && k.length() > 0 && v.length() > 0) { tmp_key = unescape(k); if (param_map.get(tmp_key) == null) param_map.put(tmp_key, unescape(v)); else param_map.put(tmp_key, param_map.get(tmp_key) + "," + unescape(v)); } } return param_map; } static private String unescape(String s) { StringBuffer sbuf = new StringBuffer(); int l = s.length(); int ch = -1; int b, sumb = 0; for (int i = 0, more = -1; i < l; i++) { /* Get next byte b from URL segment s */ switch (ch = s.charAt(i)) { case '%': ch = s.charAt(++i); int hb = (Character.isDigit((char) ch) ? ch - '0' : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF; ch = s.charAt(++i); int lb = (Character.isDigit((char) ch) ? ch - '0' : 10 + Character.toLowerCase((char) ch) - 'a') & 0xF; b = (hb << 4) | lb; break; case '+': b = ' '; break; default: b = ch; } /* Decode byte b as UTF-8, sumb collects incomplete chars */ if ((b & 0xc0) == 0x80) { // 10xxxxxx (continuation byte) sumb = (sumb << 6) | (b & 0x3f); // Add 6 bits to sumb if (--more == 0) sbuf.append((char) sumb); // Add char to sbuf } else if ((b & 0x80) == 0x00) { // 0xxxxxxx (yields 7 bits) sbuf.append((char) b); // Store in sbuf } else if ((b & 0xe0) == 0xc0) { // 110xxxxx (yields 5 bits) sumb = b & 0x1f; more = 1; // Expect 1 more byte } else if ((b & 0xf0) == 0xe0) { // 1110xxxx (yields 4 bits) sumb = b & 0x0f; more = 2; // Expect 2 more bytes } else if ((b & 0xf8) == 0xf0) { // 11110xxx (yields 3 bits) sumb = b & 0x07; more = 3; // Expect 3 more bytes } else if ((b & 0xfc) == 0xf8) { // 111110xx (yields 2 bits) sumb = b & 0x03; more = 4; // Expect 4 more bytes } else /*if ((b & 0xfe) == 0xfc)*/{ // 1111110x (yields 1 bit) sumb = b & 0x01; more = 5; // Expect 5 more bytes } /* No need to test if the UTF-8 encoding is well-formed */ } return sbuf.toString(); } /** * Does the request come from a multipart post? * * @param req * @return */ public static boolean isMultipart(HttpServletRequest req) { String[] contentType = StringUtil.split(req.getHeader(CONTENT_TYPE), ";"); return (contentType.length > 0 && contentType[0].trim().equals(MULTIPART_TYPE)); } /** * Parse a multipart form * * @param req * @return */ public static RequestParameters parseMultipart(HttpServletRequest req) { RequestParameters params = new RequestParameters(); String boundary = getMultipartBoundary(req); try { params = readMultipart(boundary, req); } catch (IOException e) { LogManager.log(LogService.LOG_ERROR, "Failed to read multi-part body.", e); } return params; } /** * Get's the item boundary for a multipart post from the header * * @param req * @return */ public static String getMultipartBoundary(HttpServletRequest req) { // if multipart, Content-Type will look something like: // multipart/form-data; boundary=-----------------1372285795046453821504463 String[] contentType = StringUtil.split(req.getHeader(CONTENT_TYPE), ";"); // Extra check while we're here if (contentType.length < 1 || !contentType[0].trim().equals(MULTIPART_TYPE)) { return null; } // Scan to the "boundary" item int x = 1; while (x < contentType.length && !contentType[x].trim().startsWith(BOUNDARY_KEY)) x++; if (x == contentType.length) return null; // second string in item is boundaryItem // return it if it's found String[] boundaryItem = StringUtil.split(contentType[x].trim(), "="); if (boundaryItem.length == 2 && boundaryItem[0].trim().equals(BOUNDARY_KEY)) return "--" + boundaryItem[1].trim(); // default, boundary not found return null; } /** * Read from a BufferedReader representing a multipart form post into a * RequestParameters object * * @param boundary * separator between multipart chunks * @param reader * * @return * @throws IOException */ private static RequestParameters readMultipart(String boundary, HttpServletRequest req) throws IOException { RequestParameters params = new RequestParameters(); String requestBody = RequestReader.read(req); String[] chunks = StringUtil.split(requestBody, boundary); for (int i = 0; i < chunks.length; i++) { // last boundary ends in --, after split, will be last item if (chunks[i].trim().equals("--")) continue; params.add(readMultipartChunk(chunks[i])); } return params; } /** * Reads a multipart chunk (which is a string) and returns a * RequestParameters object with one parameter representing the chunk * * @param chunk * a string from the multipart post representing one item * @return */ private static RequestParameters readMultipartChunk(String chunk) { String[] lines = StringUtil.split(chunk, "\n"); // Go through the chunk and pull out data String name = null, val = null, filename = null, contentType = null; boolean dataRead = false; for (int i = 0; i < lines.length; i++) { String line = lines[i].trim(); // content-disposition is usually the first line of the chunk // it has useful stuff in it like field name and filename if (line.toLowerCase().startsWith("content-disposition")) { name = getName(line); filename = getFilename(line); dataRead = true; } // content-type only appears with files if (line.toLowerCase().startsWith("content-type")) { contentType = getContentType(line); } // a blank line separates the header stuff found above // from the data // dataRead just means we've already found the header stuff if (dataRead && line.length() == 0) { val = getVal(lines, i + 1); break; // data will be last thing } } return createParamsFromData(name, val, filename, contentType); } /** * Take in all the stuff that might make up a Request Parameter and create a * new RequestParameters object with only one param * * @param name * param name * @param val * the value as string (could be the contents of a file) * @param filename * if it's a file, this will be set * @param contentType * if it's a file, this will be the content type * @return a new RequestParameters object w/ one item */ private static RequestParameters createParamsFromData(String name, String val, String filename, String contentType) { RequestParameters params = new RequestParameters(); // Something we can save if (name != null && val != null) { // it's not a file if fields are null // if filename length is 0, it's a file who's form // field wasn't filled out, so we store the empty value if (filename == null || contentType == null || filename.length() == 0) { params.put(name, val.trim()); } else { // it's a file try { params.setFile(new FormFile(filename, contentType, val.getBytes(ENCODING))); } catch (UnsupportedEncodingException e) { LogManager.log(LogService.LOG_ERROR, "Failed to set file.", e); } } } return params; } /** * puts the lines from the mulitpart chunk together into a piece of data * * @param lines * an array of lines from the mulitpart chunk * @param i * which line to start writing to data * @return the final composed data as a string */ private static String getVal(String[] lines, int i) { StringBuffer sbuf = new StringBuffer(); // append all but the last line while (i < lines.length - 1) { sbuf.append(lines[i]); sbuf.append('\n'); i++; } sbuf.append(lines[i].trim()); // last line w/o line break return sbuf.toString(); } /** * Extract param name from a line * * @param line * will look something like this: Content-Disposition: form-data; * name="myfile"; filename="test.jpg" * @return */ private static String getName(String line) { String val = getValFrom(line, "name=", ";"); // remove quotes from front and back return (val == null) ? null : val.substring(1, val.length() - 1); } /** * Extract content type from a line * * @param line * will look something like this: Content-Type: image/jpeg * @return */ private static String getContentType(String line) { return getValFrom(line.toLowerCase().trim(), "Content-Type: ".toLowerCase(), ";"); } /** * Extract filename from line * * @param line * will look something like this: Content-Disposition: form-data; * name="myfile"; filename="test.jpg" * * @return */ private static String getFilename(String line) { String val = getValFrom(line, "filename=", ";"); // remove quotes from front and back return (val == null) ? null : val.substring(1, val.length() - 1); } /** * Get a string from line between prefix & suffix * * @param line * @param prefix * @param suffix * @return */ private static String getValFrom(String line, String prefix, String suffix) { int start = line.indexOf(prefix); if (start == -1) return null; start = start + prefix.length(); if (start >= line.length()) return null; int end = line.indexOf(suffix, start); String out; if (end < 0) out = line.substring(start); else out = line.substring(start, end); return out; } /** * This is a copy of kgilmer's split function from * com.buglabs.util.StringUtil This function was copied over for the purpose * of trying to remove Buglabs framework code dependencies so it could * potentially run in any osgi env. * * @author bballantine * */ private static class StringUtil { /** * custom string splitting function as CDC/Foundation does not include * String.split(); * * @param s * Input String * @param seperator * @return */ public static String[] split(String s, String seperator) { if (s == null || seperator == null || s.length() == 0 || seperator.length() == 0) { return (new String[0]); } List tokens = new ArrayList(); String token; int index_a = 0; int index_b = 0; while (true) { index_b = s.indexOf(seperator, index_a); if (index_b == -1) { token = s.substring(index_a); if (token.length() > 0) { tokens.add(token); } break; } token = s.substring(index_a, index_b); if (token.length() >= 0) { tokens.add(token); } index_a = index_b + seperator.length(); } String[] str_array = new String[tokens.size()]; for (int i = 0; i < str_array.length; i++) { str_array[i] = (String) (tokens.get(i)); } return str_array; } } }