001: // Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
002: // Released under the terms of the GNU General Public License version 2 or later.
003: package fitnesse.http;
004:
005: import java.io.BufferedInputStream;
006: import java.io.BufferedOutputStream;
007: import java.io.File;
008: import java.io.FileOutputStream;
009: import java.io.InputStream;
010: import java.io.OutputStream;
011: import java.io.UnsupportedEncodingException;
012: import java.net.URLDecoder;
013: import java.util.Collection;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.Set;
018: import java.util.regex.Matcher;
019: import java.util.regex.Pattern;
020:
021: import fitnesse.components.Base64;
022: import fitnesse.util.StreamReader;
023:
024: public class Request {
025: private static final Pattern requestLinePattern = Pattern
026: .compile("(\\p{Upper}+?) ([^\\s]+)");
027:
028: private static final Pattern requestUriPattern = Pattern
029: .compile("([^?]+)\\??(.*)");
030:
031: private static final Pattern queryStringPattern = Pattern
032: .compile("([^=]*)=?([^&]*)&?");
033:
034: private static final Pattern headerPattern = Pattern
035: .compile("([^:]*): (.*)");
036:
037: private static final Pattern boundaryPattern = Pattern
038: .compile("boundary=(.*)");
039:
040: private static final Pattern multipartHeaderPattern = Pattern
041: .compile("([^ =]+)=\\\"([^\"]*)\\\"");
042:
043: private static Collection allowedMethods = buildAllowedMethodList();
044:
045: protected StreamReader input;
046:
047: protected String requestURI;
048:
049: protected String resource;
050:
051: protected String queryString;
052:
053: protected HashMap inputs = new HashMap();
054:
055: protected HashMap headers = new HashMap();
056:
057: protected String entityBody = "";
058:
059: protected String requestLine;
060:
061: protected String authorizationUsername;
062:
063: protected String authorizationPassword;
064:
065: private boolean hasBeenParsed;
066:
067: private long bytesParsed = 0;
068:
069: public static Set buildAllowedMethodList() {
070: Set methods = new HashSet(20);
071: methods.add("GET");
072: methods.add("POST");
073: return methods;
074: }
075:
076: protected Request() {
077: }
078:
079: public Request(InputStream input) throws Exception {
080: this .input = new StreamReader(new BufferedInputStream(input));
081: }
082:
083: public void parse() throws Exception {
084: readAndParseRequestLine();
085: headers = parseHeaders(input);
086: parseEntityBody();
087: hasBeenParsed = true;
088: }
089:
090: private void readAndParseRequestLine() throws Exception {
091: requestLine = input.readLine();
092: Matcher match = requestLinePattern.matcher(requestLine);
093: checkRequestLine(match);
094: requestURI = match.group(2);
095: parseRequestUri(requestURI);
096: }
097:
098: private HashMap parseHeaders(StreamReader reader) throws Exception {
099: HashMap headers = new HashMap();
100: String line = reader.readLine();
101: while (!"".equals(line)) {
102: Matcher match = headerPattern.matcher(line);
103: if (match.find()) {
104: String key = match.group(1);
105: String value = match.group(2);
106: headers.put(key.toLowerCase(), value);
107: }
108: line = reader.readLine();
109: }
110: return headers;
111: }
112:
113: private void parseEntityBody() throws Exception {
114: if (hasHeader("Content-Length")) {
115: String contentType = (String) getHeader("Content-Type");
116: if (contentType != null
117: && contentType.startsWith("multipart/form-data")) {
118: Matcher match = boundaryPattern.matcher(contentType);
119: match.find();
120: parseMultiPartContent(match.group(1));
121: } else {
122: entityBody = input.read(getContentLength());
123: parseQueryString(entityBody);
124: }
125: }
126: }
127:
128: public int getContentLength() {
129: return Integer.parseInt((String) getHeader("Content-Length"));
130: }
131:
132: private void parseMultiPartContent(String boundary)
133: throws Exception {
134: boundary = "--" + boundary;
135:
136: int numberOfBytesToRead = getContentLength();
137: accumulateBytesReadAndReset();
138: input.readUpTo(boundary);
139: while (numberOfBytesToRead - input.numberOfBytesConsumed() > 10) {
140: input.readLine();
141: HashMap headers = parseHeaders(input);
142: String contentDisposition = (String) headers
143: .get("content-disposition");
144: Matcher matcher = multipartHeaderPattern
145: .matcher(contentDisposition);
146: while (matcher.find())
147: headers.put(matcher.group(1), matcher.group(2));
148:
149: String name = (String) headers.get("name");
150: Object value;
151: if (headers.containsKey("filename"))
152: value = createUploadedFile(headers, input, boundary);
153: else
154: value = input.readUpTo("\r\n" + boundary);
155:
156: inputs.put(name, value);
157: }
158: }
159:
160: private void accumulateBytesReadAndReset() {
161: bytesParsed += input.numberOfBytesConsumed();
162: input.resetNumberOfBytesConsumed();
163: }
164:
165: private Object createUploadedFile(HashMap headers,
166: StreamReader reader, String boundary) throws Exception {
167: String filename = (String) headers.get("filename");
168: String contentType = (String) headers.get("content-type");
169: File tempFile = File
170: .createTempFile("FitNesse", ".uploadedFile");
171: OutputStream output = new BufferedOutputStream(
172: new FileOutputStream(tempFile));
173: reader.copyBytesUpTo("\r\n" + boundary, output);
174: output.close();
175: return new UploadedFile(filename, contentType, tempFile);
176: }
177:
178: private void checkRequestLine(Matcher match) throws HttpException {
179: if (!match.find())
180: throw new HttpException(
181: "The request string is malformed and can not be parsed");
182: if (!allowedMethods.contains(match.group(1)))
183: throw new HttpException("The " + match.group(1)
184: + " method is not currently supported");
185: }
186:
187: public void parseRequestUri(String requestUri) {
188: Matcher match = requestUriPattern.matcher(requestUri);
189: match.find();
190: resource = stripLeadingSlash(match.group(1));
191: queryString = match.group(2);
192: parseQueryString(queryString);
193: }
194:
195: protected void parseQueryString(String queryString) {
196: Matcher match = queryStringPattern.matcher(queryString);
197: while (match.find()) {
198: String key = match.group(1);
199: String value = decodeContent(match.group(2));
200: inputs.put(key, value);
201: }
202: }
203:
204: public String getRequestLine() {
205: return requestLine;
206: }
207:
208: public String getRequestUri() {
209: return requestURI;
210: }
211:
212: public String getResource() {
213: return resource;
214: }
215:
216: public String getQueryString() {
217: return queryString;
218: }
219:
220: public boolean hasInput(String key) {
221: return inputs.containsKey(key);
222: }
223:
224: public Object getInput(String key) {
225: return inputs.get(key);
226: }
227:
228: public boolean hasHeader(String key) {
229: return headers.containsKey(key.toLowerCase());
230: }
231:
232: public Object getHeader(String key) {
233: return headers.get(key.toLowerCase());
234: }
235:
236: public String getBody() {
237: return entityBody;
238: }
239:
240: private String stripLeadingSlash(String url) {
241: return url.substring(1);
242: }
243:
244: public String toString() {
245: StringBuffer buffer = new StringBuffer();
246: buffer.append("--- Request Start ---").append("\n");
247: buffer.append("Request URI: ").append(requestURI).append("\n");
248: buffer.append("Resource: ").append(resource).append("\n");
249: buffer.append("Query String: ").append(queryString)
250: .append("\n");
251: buffer.append("Hearders: (" + headers.size() + ")\n");
252: addMap(headers, buffer);
253: buffer.append("Form Inputs: (" + inputs.size() + ")\n");
254: addMap(inputs, buffer);
255: buffer.append("Entity Body: ").append("\n");
256: buffer.append(entityBody).append("\n");
257: buffer.append("--- End Request ---\n");
258:
259: return buffer.toString();
260: }
261:
262: private void addMap(HashMap map, StringBuffer buffer) {
263: if (map.size() == 0) {
264: buffer.append("\tempty");
265: }
266: for (Iterator iterator = map.keySet().iterator(); iterator
267: .hasNext();) {
268: String key = (String) iterator.next();
269: String value = map.get(key) != null ? escape(map.get(key)
270: .toString()) : null;
271: buffer.append("\t" + escape(key) + " \t-->\t " + value
272: + "\n");
273: }
274: }
275:
276: private String escape(String foo) {
277: return foo.replaceAll("[\n\r]+", "|");
278: }
279:
280: public static String decodeContent(String content) {
281: String escapedContent = null;
282: try {
283: escapedContent = URLDecoder.decode(content, "UTF-8");
284: } catch (UnsupportedEncodingException e) {
285: escapedContent = "URLDecoder Error";
286: }
287: return escapedContent;
288: }
289:
290: public boolean hasBeenParsed() {
291: return hasBeenParsed;
292: }
293:
294: public String getUserpass(String headerValue) throws Exception {
295: String encodedUserpass = headerValue.substring(6);
296: return Base64.decode(encodedUserpass);
297: }
298:
299: public void getCredentials() throws Exception {
300: if (hasHeader("Authorization")) {
301: String authHeader = getHeader("Authorization").toString();
302: String userpass = getUserpass(authHeader);
303: String[] values = userpass.split(":");
304: if (values.length == 2) {
305: authorizationUsername = values[0];
306: authorizationPassword = values[1];
307: }
308: }
309: }
310:
311: public String getAuthorizationUsername() {
312: return authorizationUsername;
313: }
314:
315: public String getAuthorizationPassword() {
316: return authorizationPassword;
317: }
318:
319: public long numberOfBytesParsed() {
320: return bytesParsed + input.numberOfBytesConsumed();
321: }
322: }
|