Savarese.Org
BareHTTP 1.0.2 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%  (411/516)82%  (93/113)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class HTTPSession100% (1/1)91%  (10/11)78%  (360/459)80%  (74/93)
reportError (String): void 0%   (0/1)0%   (0/44)0%   (0/2)
parseRequest (): boolean 100% (1/1)80%  (97/121)81%  (22/27)
processRequest (): void 100% (1/1)84%  (98/117)67%  (16/24)
<static initializer> 100% (1/1)84%  (64/76)71%  (10/14)
HTTPSession (String, InputStream, OutputStream): void 100% (1/1)100% (44/44)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,2012 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 *     https://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="https://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  OutputStream 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      }
138    } catch(MalformedPatternException e) {
139      // This should happen only during development.
140      throw new RuntimeException(e);
141    }
142 
143    try {
144      MimeTypes.load(HTTPSession.class.getResourceAsStream("mime.properties"));
145    } catch(IOException ioe) {
146      throw new RuntimeException(ioe);
147    }
148  }
149 
150  boolean parseRequest() throws IOException {
151    String line;
152    int pattern;
153    MatchResult match;
154    String lastHeader = null;
155 
156    request.reset();
157 
158  loop:
159    while(true) {
160      line = input.readLine();
161 
162      if(line == null) {
163        break;
164      }
165 
166 
167      for(pattern = 0; pattern < PATTERN.length; ++pattern) {
168        if(matcher.matches(line, PATTERN[pattern])) {
169           break;
170        }
171      }
172      
173      match = matcher.getMatch();
174 
175      switch(pattern) {
176      case Request.SIMPLE_REQUEST:
177        request.setType(pattern);
178        request.setURI(documentRoot + match.group(1));
179        request.setVersion("0.9");
180        break loop;
181      case Request.GET_REQUEST:
182      case Request.HEAD_REQUEST:
183        request.setType(pattern);
184        request.setURI(documentRoot + match.group(1));
185        request.setVersion(match.group(2));
186        break;
187      case Request.HEADER:
188        lastHeader = match.group(1);
189        request.setHeaderValue(lastHeader, match.group(2));
190        break;
191      case Request.HEADER_CONTINUATION:
192        request.setHeaderValue(lastHeader,
193                               request.getHeaderValue(lastHeader) +
194                               match.group(1));
195        break;
196      case Request.END_OF_REQUEST:
197        break loop;
198      default:
199        reportError(NOT_IMPLEMENTED_STATUS);
200        return false;
201      }
202    }
203 
204    return true;
205  }
206 
207  void processRequest() throws IOException {
208    int type = request.getType();
209 
210    switch(type) {
211    case Request.SIMPLE_REQUEST:
212    case Request.GET_REQUEST:
213    case Request.HEAD_REQUEST:
214      File file = new File(request.getURI());
215 
216      if(file.isDirectory()) {
217        file = new File(file, "index.html");
218      }
219 
220      if(!file.exists()) {
221        reportError(NOT_FOUND_STATUS);
222        return;
223      }
224 
225      if(!validateFile(file)) {
226        reportError(FORBIDDEN_STATUS);
227        return;
228      }
229 
230      if(type != Request.SIMPLE_REQUEST) {
231        output.write((HTTP_VERSION + " " + OK_STATUS + "\r\nDate: " +
232                      getDateHeader() + "\r\n" +
233                      DEFAULT_HEADERS + "Last-Modified: " +
234                      getDateHeader(file.lastModified()) + "\r\n" +
235                      "Content-Length: " + file.length() + "\r\n" +
236                      "Content-Type: " + getContentType(file) + 
237                      "\r\n\r\n").getBytes());
238      }
239 
240      if(type == Request.HEAD_REQUEST) {
241        return;
242      }
243 
244      FileInputStream input = new FileInputStream(file);
245      byte[] buffer = new byte[1024];
246      int bytes;
247 
248      while((bytes = input.read(buffer, 0, buffer.length)) != -1) {
249        output.write(buffer, 0, bytes);
250      }
251      input.close();
252      break;
253    default:
254      reportError(NOT_IMPLEMENTED_STATUS);
255      return;
256    }
257  }
258 
259  String getContentType(File file) throws IOException {
260    String path = file.getCanonicalPath();
261    int index = path.lastIndexOf('.') + 1;
262    String type = MimeTypes.getProperty(path.substring(index));
263 
264    if(type == null) {
265      type = "application/octet-stream";
266    }
267 
268    return type;
269  }
270 
271  /**
272   * Makes a feeble attempt to confine the file to a tree
273   * rooted at the server's document directory.
274   */
275  boolean validateFile(File file) throws IOException {
276    return (file.getCanonicalPath().startsWith(documentRoot));
277    //return (file.getPath().startsWith(documentRoot));
278  }
279 
280  void closeSession() throws IOException {
281    output.flush();
282    output.close();
283    input.close();
284  }
285 
286 
287  String getDateHeader(long time) {
288    return dateFormat.format(new Date(time));
289  }
290 
291  String getDateHeader() {
292    return getDateHeader(System.currentTimeMillis());
293  }
294 
295  void reportError(String statusLine) throws IOException {
296    output.write((HTTP_VERSION + " " + statusLine + 
297                  "\r\nDate: " + getDateHeader() + "\r\n" +
298                  DEFAULT_HEADERS + "\r\n" +
299                  "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n" +
300                  "<HTML><HEAD>\r\n" +
301                  "<TITLE>" + statusLine + "</TITLE>\r\n" +
302                  "</HEAD><BODY>\r\n" +
303                  "<H1>" + statusLine + "</H1>\r\n" +
304                  "</BODY></HTML>\r\n").getBytes());
305  }
306 
307  /**
308   * Creates an HTTPSession rooted at the specified document directory
309   * and communicating via the specified streams.
310   *
311   * @param documentRoot The fully qualified directory pathname to
312   * serve as the document root.
313   * @param in The InputStream via which the client submits its request.
314   * @param out The OutputStram via which the HTTPSession sends its reply.
315   */
316  public HTTPSession(String documentRoot, InputStream in, OutputStream out)
317    throws IOException
318  {
319    input   = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
320    output  = out;
321    matcher = new Perl5Matcher();
322    request = new Request();
323    dateFormat = new SimpleDateFormat(DATE_FORMAT); 
324    dateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
325    this.documentRoot = documentRoot;
326  }
327 
328  /**
329   * Executes the HTTP conversation and closes the session after
330   * satisfying the first request.
331   */
332  public void execute() throws IOException {
333    if(parseRequest()) {
334      processRequest();
335    }
336    closeSession();
337  }
338 
339}
340 

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