Savarese.Org
BareHTTP 1.0.1 Unit Test Coverage
[all classes][org.savarese.barehttp]

COVERAGE SUMMARY FOR SOURCE FILE [HTTPSession.java]

nameclass, %method, %block, %line, %
HTTPSession.java100% (2/2)90%  (18/20)80%  (412/516)82%  (93/113)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class HTTPSession100% (1/1)91%  (10/11)79%  (361/459)80%  (74/93)
reportError (String): void 0%   (0/1)0%   (0/43)0%   (0/2)
parseRequest (): boolean 100% (1/1)80%  (97/121)81%  (22/27)
processRequest (): void 100% (1/1)84%  (97/116)67%  (16/24)
<static initializer> 100% (1/1)84%  (64/76)71%  (10/14)
HTTPSession (String, InputStream, OutputStream): void 100% (1/1)100% (46/46)100% (9/9)
closeSession (): void 100% (1/1)100% (10/10)100% (4/4)
execute (): void 100% (1/1)100% (8/8)100% (4/4)
getContentType (File): String 100% (1/1)100% (21/21)100% (6/6)
getDateHeader (): String 100% (1/1)100% (4/4)100% (1/1)
getDateHeader (long): String 100% (1/1)100% (8/8)100% (1/1)
validateFile (File): boolean 100% (1/1)100% (6/6)100% (1/1)
     
class HTTPSession$Request100% (1/1)89%  (8/9)89%  (51/57)95%  (19/20)
getHeaderValue (String): String 0%   (0/1)0%   (0/6)0%   (0/1)
HTTPSession$Request (HTTPSession): void 100% (1/1)100% (13/13)100% (4/4)
getType (): int 100% (1/1)100% (3/3)100% (1/1)
getURI (): String 100% (1/1)100% (3/3)100% (1/1)
reset (): void 100% (1/1)100% (13/13)100% (5/5)
setHeaderValue (String, String): void 100% (1/1)100% (7/7)100% (2/2)
setType (int): void 100% (1/1)100% (4/4)100% (2/2)
setURI (String): void 100% (1/1)100% (4/4)100% (2/2)
setVersion (String): void 100% (1/1)100% (4/4)100% (2/2)

1/* Copyright 2001,2006,2010 Daniel F. Savarese
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.savarese.org/software/ApacheLicense-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 
16package org.savarese.barehttp;
17 
18import java.io.*;
19import java.net.*;
20import java.text.*;
21import java.util.*;
22 
23import org.apache.oro.text.regex.*;
24 
25/**
26 * An HTTPSession executes an HTTP conversation via an InputStream and
27 * OutputSream.  It processes only one request and then terminates the
28 * session.  Only HTTP/0.9, 1.0, and 1.1 GET requests and HTTP/1.0 and
29 * 1.1 HEAD requests are supported.  The canonical pathname of served
30 * files is checked against the document root.  If the file path does
31 * not fall under the document root, an HTTP "403 Forbidden Resource"
32 * error is returned.
33 *
34 * @author <a href="http://www.savarese.org/">Daniel F. Savarese</a>
35 */
36public class HTTPSession {
37 
38  final class Request {
39    static final int UNKNOWN_REQUEST     = -1;
40    static final int SIMPLE_REQUEST      = 0;
41    static final int GET_REQUEST         = 1;
42    static final int HEAD_REQUEST        = 2;
43    static final int HEADER              = 3;
44    static final int HEADER_CONTINUATION = 4;
45    static final int END_OF_REQUEST      = 5;
46    static final String DEFAULT_VERSION  = "1.0";
47 
48    int type;
49    HashMap<String,String> headers;
50    String version;
51    String uri;
52 
53    Request() {
54      headers = new HashMap<String,String>();
55      reset();
56    }
57 
58    void setVersion(String version) {
59      this.version = version;
60    }
61 
62    // unused
63    // String getVersion() { return version; }
64 
65    
66    void setType(int type) {
67      this.type = type;
68    }
69 
70    int getType() { return type; }
71 
72    void setHeaderValue(String field, String value) {
73      headers.put(field, value);
74    }
75 
76    String getHeaderValue(String field) {
77      return headers.get(field);
78    }
79 
80    void setURI(String uri) throws IOException {
81      this.uri = uri;
82    }
83 
84    String getURI() { return uri; }
85 
86    void reset() { 
87      headers.clear();
88      type    = UNKNOWN_REQUEST;
89      version = DEFAULT_VERSION;
90      uri     = null;
91    }
92  }
93 
94  static final String URL_PATTERN_STRING = "([^ ?]*)(?:\\?[^ ]*)?";
95 
96  static final String[] PATTERN_STRING = {
97    "^GET " + URL_PATTERN_STRING + "$",
98    "^GET " + URL_PATTERN_STRING + " HTTP/(\\d+\\.\\d+)$",
99    "^HEAD " + URL_PATTERN_STRING + " HTTP/(\\d+\\.\\d+)$",
100    "^(\\S+): (.*)$",
101    "^ (.*)$",
102    "^$"
103  };
104 
105  static final String HTTP_VERSION  = "HTTP/1.0";
106  static final String DEFAULT_HEADERS =
107    "Server: BareHTTP @version@ (Java)\r\n" +
108    "Allow: GET, HEAD\r\n" +
109    "Connection: close\r\n";
110 
111  static final String OK_STATUS              = "200 OK";
112  static final String FORBIDDEN_STATUS       = "403 Forbidden Resource";
113  static final String NOT_FOUND_STATUS       = "404 Resource Not Found";
114  static final String NOT_IMPLEMENTED_STATUS = "501 Not Implemented";
115 
116  static final Pattern[] PATTERN;
117  static final String DATE_FORMAT = "EEE, d MMM yyyy hh:mm:ss z";
118  static final Properties MimeTypes = new Properties();
119 
120  BufferedReader input;
121  OutputStreamWriter output;
122  Perl5Matcher matcher;
123  Request request;
124  SimpleDateFormat dateFormat;
125  String documentRoot;
126 
127  static {
128    Perl5Compiler compiler = new Perl5Compiler();
129 
130    PATTERN = new Perl5Pattern[PATTERN_STRING.length];
131 
132    try {
133      for(int i = 0; i < PATTERN.length; ++i)
134        PATTERN[i] =
135          compiler.compile(PATTERN_STRING[i],
136                           Perl5Compiler.READ_ONLY_MASK);
137    } catch(MalformedPatternException e) {
138      // This should happen only during development.
139      throw new RuntimeException(e);
140    }
141 
142    try {
143      MimeTypes.load(HTTPSession.class.getResourceAsStream("mime.properties"));
144    } catch(IOException ioe) {
145      throw new RuntimeException(ioe);
146    }
147  }
148 
149  boolean parseRequest() throws IOException {
150    String line;
151    int pattern;
152    MatchResult match;
153    String lastHeader = null;
154 
155    request.reset();
156 
157  loop:
158    while(true) {
159      line = input.readLine();
160 
161      if(line == null)
162        break;
163 
164 
165      for(pattern = 0; pattern < PATTERN.length; ++pattern)
166        if(matcher.matches(line, PATTERN[pattern]))
167           break;
168      
169      match = matcher.getMatch();
170 
171      switch(pattern) {
172      case Request.SIMPLE_REQUEST:
173        request.setType(pattern);
174        request.setURI(documentRoot + match.group(1));
175        request.setVersion("0.9");
176        break loop;
177      case Request.GET_REQUEST:
178      case Request.HEAD_REQUEST:
179        request.setType(pattern);
180        request.setURI(documentRoot + match.group(1));
181        request.setVersion(match.group(2));
182        break;
183      case Request.HEADER:
184        lastHeader = match.group(1);
185        request.setHeaderValue(lastHeader, match.group(2));
186        break;
187      case Request.HEADER_CONTINUATION:
188        request.setHeaderValue(lastHeader,
189                               request.getHeaderValue(lastHeader) +
190                               match.group(1));
191        break;
192      case Request.END_OF_REQUEST:
193        break loop;
194      default:
195        reportError(NOT_IMPLEMENTED_STATUS);
196        return false;
197      }
198    }
199 
200    return true;
201  }
202 
203  void processRequest() throws IOException {
204    int type = request.getType();
205 
206    switch(type) {
207    case Request.SIMPLE_REQUEST:
208    case Request.GET_REQUEST:
209    case Request.HEAD_REQUEST:
210      File file = new File(request.getURI());
211 
212      if(file.isDirectory())
213        file = new File(file, "index.html");
214 
215      if(!file.exists()) {
216        reportError(NOT_FOUND_STATUS);
217        return;
218      }
219 
220      if(!validateFile(file)) {
221        reportError(FORBIDDEN_STATUS);
222        return;
223      }
224 
225      if(type != Request.SIMPLE_REQUEST)
226        output.write(HTTP_VERSION + " " + OK_STATUS + "\r\nDate: " +
227                   getDateHeader() + "\r\n" +
228                   DEFAULT_HEADERS + "Last-Modified: " +
229                   getDateHeader(file.lastModified()) + "\r\n" +
230                   "Content-Length: " + file.length() + "\r\n" +
231                   "Content-Type: " + getContentType(file) + 
232                   "\r\n\r\n");
233 
234      if(type == Request.HEAD_REQUEST)
235        return;
236 
237      FileReader input = new FileReader(file);
238      char[] buffer = new char[1024];
239      int chars;
240 
241      while((chars = input.read(buffer, 0, buffer.length))
242            != -1)
243        output.write(buffer, 0, chars);
244      input.close();
245      break;
246    default:
247      reportError(NOT_IMPLEMENTED_STATUS);
248      return;
249    }
250  }
251 
252  String getContentType(File file) throws IOException {
253    String path = file.getCanonicalPath();
254    int index = path.lastIndexOf('.') + 1;
255    String type = MimeTypes.getProperty(path.substring(index));
256 
257    if(type == null)
258      type = "application/octet-stream";
259 
260    return type;
261  }
262 
263  /**
264   * Makes a feeble attempt to confine the file to a tree
265   * rooted at the server's document directory.
266   */
267  boolean validateFile(File file) throws IOException {
268    return (file.getCanonicalPath().startsWith(documentRoot));
269    //return (file.getPath().startsWith(documentRoot));
270  }
271 
272  void closeSession() throws IOException {
273    output.flush();
274    output.close();
275    input.close();
276  }
277 
278 
279  String getDateHeader(long time) {
280    return dateFormat.format(new Date(time));
281  }
282 
283  String getDateHeader() {
284    return getDateHeader(System.currentTimeMillis());
285  }
286 
287  void reportError(String statusLine) throws IOException {
288    output.write(HTTP_VERSION + " " + statusLine + 
289                 "\r\nDate: " + getDateHeader() + "\r\n" +
290                 DEFAULT_HEADERS + "\r\n" +
291    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n" +
292    "<HTML><HEAD>\r\n" +
293    "<TITLE>" + statusLine + "</TITLE>\r\n" +
294    "</HEAD><BODY>\r\n" +
295    "<H1>" + statusLine + "</H1>\r\n" +
296    "</BODY></HTML>\r\n");
297  }
298 
299  /**
300   * Creates an HTTPSession rooted at the specified document directory
301   * and communicating via the specified streams.
302   *
303   * @param documentRoot The fully qualified directory pathname to
304   * serve as the document root.
305   * @param in The InputStream via which the client submits its request.
306   * @param out The OutputStram via which the HTTPSession sends its reply.
307   */
308  public HTTPSession(String documentRoot, InputStream in, OutputStream out)
309    throws IOException
310  {
311    input   = new BufferedReader(new InputStreamReader(in));
312    output  = new OutputStreamWriter(out);
313    matcher = new Perl5Matcher();
314    request = new Request();
315    dateFormat = new SimpleDateFormat(DATE_FORMAT); 
316    dateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
317    this.documentRoot = documentRoot;
318  }
319 
320  /**
321   * Executes the HTTP conversation and closes the session after
322   * satisfying the first request.
323   */
324  public void execute() throws IOException {
325    if(parseRequest())
326      processRequest();
327    closeSession();
328  }
329 
330}
331 

[all classes][org.savarese.barehttp]
Savarese.Org