001: /*
002: HttpdBase4J: An embeddable Java web server framework that supports HTTP, HTTPS,
003: templated content and serving content from inside a jar or archive.
004: Copyright (C) 2007 Donald Munro
005:
006: This library is free software; you can redistribute it and/or
007: modify it under the terms of the GNU Lesser General Public
008: License as published by the Free Software Foundation; either
009: version 2.1 of the License, or (at your option) any later version.
010:
011: This library is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public
017: License along with this library; if not,see http://www.gnu.org/licenses/lgpl.txt
018: */
019:
020: package net.homeip.donaldm.httpdbase4j;
021:
022: import com.sun.net.httpserver.Headers;
023: import com.sun.net.httpserver.HttpExchange;
024: import java.io.BufferedInputStream;
025: import java.io.BufferedReader;
026: import java.io.File;
027: import java.io.FileReader;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031: import java.net.URI;
032: import java.security.MessageDigest;
033: import java.security.NoSuchAlgorithmException;
034: import java.text.DateFormat;
035: import java.text.ParseException;
036: import java.text.SimpleDateFormat;
037: import java.util.Date;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Locale;
042: import java.util.Map;
043: import java.util.Set;
044: import java.util.SimpleTimeZone;
045: import java.util.regex.Matcher;
046: import java.util.regex.Pattern;
047:
048: /**
049: * Utility class providing various useful static methods
050: * @author Donald Munro
051: */
052: public class Http
053: //===============
054: {
055: static private HashMap<Integer, String> m_httpErrors = new HashMap<Integer, String>();
056:
057: static public int HTTP_CONTINUE = 100, HTTP_OK = 200,
058: HTTP_REDIRECT = 301, HTTP_UNAUTHORISED = 401,
059: HTTP_FORBIDDEN = 403, HTTP_NOTFOUND = 404,
060: HTTP_BADREQUEST = 400, HTTP_METHOD = 405,
061: HTTP_LENGTH = 411, HTTP_INTERNALERROR = 500,
062: HTTP_NOTIMPLEMENTED = 501;
063:
064: public static final String MIME_PLAINTEXT = "text/plain",
065: MIME_HTML = "text/html", MIME_XML = "text/xml",
066: MIME_BINARY = "application/octet-stream",
067: MIME_ICON = "image/x-icon";
068:
069: static private HashMap<String, String> m_mimeExtensionMap = null;
070:
071: public static final DateFormat m_dateFormats[] = {
072: //new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
073: new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",
074: Locale.US),
075: new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz",
076: Locale.US),
077: new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
078:
079: static {
080: m_httpErrors.put(new Integer(100), "Continue");
081: m_httpErrors.put(new Integer(101), "Switching Protocols");
082: m_httpErrors.put(new Integer(200), "OK");
083: m_httpErrors.put(new Integer(201), "Created");
084: m_httpErrors.put(new Integer(202), "Accepted");
085: m_httpErrors.put(new Integer(203),
086: "Non-Authoritative Information");
087: m_httpErrors.put(new Integer(204), "No Content");
088: m_httpErrors.put(new Integer(205), "Reset Content");
089: m_httpErrors.put(new Integer(206), "Partial Content");
090: m_httpErrors.put(new Integer(300), "Multiple Choices");
091: m_httpErrors.put(new Integer(301), "Moved Permanently");
092: m_httpErrors.put(new Integer(302), "Moved Temporarily");
093: m_httpErrors.put(new Integer(303), "See Other");
094: m_httpErrors.put(new Integer(304), "Not Modified");
095: m_httpErrors.put(new Integer(305), "Use Proxy");
096: m_httpErrors.put(new Integer(400), "Bad Request");
097: m_httpErrors.put(new Integer(401), "Unauthorized");
098: m_httpErrors.put(new Integer(402), "Payment Required");
099: m_httpErrors.put(new Integer(403), "Forbidden");
100: m_httpErrors.put(new Integer(404), "Not Found");
101: m_httpErrors.put(new Integer(405), "Method Not Allowed");
102: m_httpErrors.put(new Integer(406), "Not Acceptable");
103: m_httpErrors.put(new Integer(407),
104: "Proxy Authentication Required");
105: m_httpErrors.put(new Integer(408), "Request Time-out");
106: m_httpErrors.put(new Integer(409), "Conflict");
107: m_httpErrors.put(new Integer(410), "Gone");
108: m_httpErrors.put(new Integer(411), "Length Required");
109: m_httpErrors.put(new Integer(412), "Precondition Failed");
110: m_httpErrors.put(new Integer(413), "Request Entity Too Large");
111: m_httpErrors.put(new Integer(414), "Request-URI Too Large");
112: m_httpErrors.put(new Integer(415), "Unsupported Media Type");
113: m_httpErrors.put(new Integer(500), "Server Error");
114: m_httpErrors.put(new Integer(501), "Not Implemented");
115: m_httpErrors.put(new Integer(502), "Bad Gateway");
116: m_httpErrors.put(new Integer(503), "Service Unavailable");
117: m_httpErrors.put(new Integer(504), "Gateway Time-out");
118: m_httpErrors
119: .put(new Integer(505), "HTTP Version not supported");
120:
121: for (int i = 0; i < m_dateFormats.length; i++) {
122: m_dateFormats[i].setTimeZone(new SimpleTimeZone(0, "GMT"));
123: m_dateFormats[i].setLenient(true);
124: }
125: }
126:
127: /**
128: * Maps a HTTP error code onto an error description.
129: * @param code The HTTP error code
130: * @return An error description.
131: */
132: static public String getErrorMessage(int code)
133: //--------------------------------------------
134: {
135: String m = m_httpErrors.get(code);
136: if (m == null)
137: return "";
138: return m;
139: }
140:
141: static private Pattern m_extPattern = Pattern.compile(".+\\.(.+)$");
142:
143: /**
144: * @param file File to get the file extension for.
145: * @return The file extention
146: */
147: public static String getExtension(File file)
148: //------------------------------------------
149: {
150: String path = file.getAbsolutePath();
151: int p = path.lastIndexOf(File.separatorChar);
152: if (p < 0)
153: p = path.lastIndexOf('/');
154: if ((p++ >= 0) && (p < path.length()))
155: path = path.substring(p);
156: p = path.indexOf("?");
157: if (p < 0)
158: p = path.indexOf("&");
159: if (p > 0)
160: path = path.substring(0, p);
161: Matcher matcher = m_extPattern.matcher(path);
162: String ext = "";
163: if (matcher.matches())
164: ext = matcher.group(1);
165: if (!ext.startsWith("."))
166: ext = "." + ext;
167: return ext;
168: }
169:
170: static private void loadMimeMap()
171: //-------------------------------
172: {
173: m_mimeExtensionMap = new HashMap<String, String>();
174: m_mimeExtensionMap.put("html", "text/html");
175: m_mimeExtensionMap.put("htm", "text/html");
176: m_mimeExtensionMap.put("st", "text/html");
177: m_mimeExtensionMap.put("zip", "application/x-zip-compressed");
178: m_mimeExtensionMap.put("gif", "image/gif");
179: m_mimeExtensionMap.put("jpeg", "image/jpeg");
180: m_mimeExtensionMap.put("jpg", "image/jpeg");
181: m_mimeExtensionMap.put("png", "image/png");
182: m_mimeExtensionMap.put("css", "text/css");
183: m_mimeExtensionMap.put("pdf", "application/pdf");
184: m_mimeExtensionMap.put("doc", "application/msword");
185: m_mimeExtensionMap.put("gz", "application/x-gzip");
186: m_mimeExtensionMap.put("zip", "application/zip");
187: m_mimeExtensionMap.put("js", "application/x-javascript");
188: m_mimeExtensionMap.put("xml", "application/xml");
189: m_mimeExtensionMap.put("dtd", "application/xml-dtd");
190: m_mimeExtensionMap.put("txt", "text/plain");
191:
192: BufferedReader br = null;
193: File mimeTypes = null;
194: try { // TODO:HttpdBase4J-00002 Presumably Windoze must have a mime.types
195: // equivalent in the registry ??
196: if (!System.getProperty("os.name").toLowerCase().contains(
197: "windows")) {
198: mimeTypes = new File("/etc/mime.types");
199: if (mimeTypes.exists()) {
200: br = new BufferedReader(new FileReader(mimeTypes));
201: String s = br.readLine();
202: while (s != null) {
203: s = s.trim();
204: if ((s.length() > 0) && (!s.startsWith("#"))) {
205: String[] as = s.split("\\s+");
206: if (as.length >= 2) {
207: String v = as[0].trim();
208: for (int i = 1; i < as.length; i++) {
209: String k = as[i].trim();
210: if (k.length() > 0)
211: m_mimeExtensionMap.put(k, v);
212: }
213: }
214: }
215:
216: s = br.readLine();
217: }
218:
219: }
220: }
221: } catch (Exception e) {
222:
223: } finally {
224: if (br != null)
225: try {
226: br.close();
227: } catch (Exception e) {
228: }
229: }
230: m_mimeExtensionMap.put("chm", "application/chm");
231: m_mimeExtensionMap.put("djvu", "image/x.djvu");
232: m_mimeExtensionMap.put("djv", "image/x.djvu");
233: }
234:
235: /**
236: * @param r Request instance for which to look up the MIME type
237: * @return The mime type
238: */
239: static public String getMimeType(Request r)
240: //-----------------------------------------
241: {
242: String ext = r.getExtension();
243: if (m_mimeExtensionMap == null)
244: loadMimeMap();
245: if (m_mimeExtensionMap != null) {
246: if (ext.trim().startsWith("."))
247: ext = ext.substring(1);
248: return m_mimeExtensionMap.get(ext);
249: }
250: return null;
251: }
252:
253: static public String strDate(Date dte)
254: //----------------------------
255: {
256: if (dte == null)
257: return m_dateFormats[0].format(new Date());
258: return m_dateFormats[0].format(dte);
259: }
260:
261: public static final Date getDate(String date)
262: //-------------------------------------------
263: {
264: Date dte = null;
265: for (int i = 0; i < m_dateFormats.length; i++) {
266: try {
267: dte = m_dateFormats[i].parse(date);
268: break;
269:
270: } catch (ParseException e) {
271: dte = null;
272: continue;
273: }
274: }
275: return dte;
276: }
277:
278: /**
279: * Read a stream in a byte array.
280: * @param is - The stream to read
281: * @param data - The byte array into which to read the data.
282: * If null the array will be allocated
283: * @param len - The number of bytes to read
284: * @return A count of the bytes read.
285: * @throws IOException
286: */
287: static public int readStream(InputStream is, byte[] data, int len)
288: throws IOException
289: //-------------------------------------------------------------------------
290: {
291: if (len < 0)
292: return 0;
293: if (data == null)
294: data = new byte[len];
295: int p = is.read(data);
296: int cb = p;
297: while ((cb >= 0) && (p < len)) {
298: cb = is.read(data, p, len - p);
299: p += cb;
300: }
301: return p;
302: }
303:
304: /**
305: * Copy an input stream to an output stream
306: * @param is - The input stream
307: * @param os - The output stream
308: * @return The number of bytes copied
309: * @throws IOException
310: */
311: static public long readWriteStream(InputStream is, OutputStream os)
312: throws IOException
313: //----------------------------------------------------------------
314: {
315: byte[] data = new byte[4096];
316: long total = 0;
317: int cb = is.read(data, 0, 4096);
318: while (cb >= 0) {
319: total += cb;
320: os.write(data, 0, cb);
321: cb = is.read(data, 0, 4096);
322: }
323: return total;
324: }
325:
326: /**
327: * Create a String representation of an HttpExchange object.
328: * @param ex The HttpExchange object.
329: * @return A String representation of ex.
330: */
331: static protected String strExchange(HttpExchange ex)
332: //------------------------------------------------
333: {
334: StringBuffer sb = new StringBuffer();
335: if (ex != null) {
336: sb.append("Method: " + ex.getRequestMethod());
337: sb.append(Httpd.EOL);
338: Headers headerMap = ex.getRequestHeaders();
339: Set<Map.Entry<String, List<String>>> headers = headerMap
340: .entrySet();
341: for (Iterator<Map.Entry<String, List<String>>> i = headers
342: .iterator(); i.hasNext();) {
343: Map.Entry<String, List<String>> e = i.next();
344: sb.append("Header: " + e.getKey() + ": ");
345: List<String> hv = e.getValue();
346: for (Iterator<String> j = hv.iterator(); j.hasNext();)
347: sb.append(j.next() + " ");
348: sb.append(Httpd.EOL);
349: }
350: URI uri = ex.getRequestURI().normalize();
351: if (uri != null) {
352: sb.append(uri.toASCIIString());
353: sb.append("Path " + uri.getPath());
354: sb.append(Httpd.EOL);
355: sb.append("Host " + uri.getHost());
356: sb.append(Httpd.EOL);
357: sb.append("Port " + uri.getPort());
358: sb.append(Httpd.EOL);
359: sb.append("Fragment " + uri.getFragment());
360: sb.append(Httpd.EOL);
361: sb.append("Query " + uri.getQuery());
362: sb.append(Httpd.EOL);
363: sb.append("Scheme " + uri.getScheme());
364: sb.append(Httpd.EOL);
365: }
366: // InputStream is = null;
367: // sb.append("Content:"); sb.append(nl);
368: // try
369: // {
370: // is = ex.getRequestBody();
371: // int ch;
372: // while ( (ch = is.read()) != -1)
373: // sb.append(ch);
374: // }
375: // catch (Exception e)
376: // {
377: // }
378: // finally
379: // {
380: // if (is != null) try {is.close(); } catch (Exception e) {}
381: // }
382: // sb.append(nl);
383: }
384: return sb.toString();
385: }
386:
387: /**
388: * Calculate the e-tag for a list of files
389: * @param files List of files to calculate e-tag for
390: * @return A String containing the e-tag.
391: */
392: public static String eTag(DirItemInterface... files)
393: //-------------------------------------------
394: {
395: MessageDigest messageDigest = null;
396: try {
397: messageDigest = MessageDigest.getInstance("SHA-1");
398: } catch (NoSuchAlgorithmException e) {
399: Httpd.Log(Httpd.LogLevel.ERROR,
400: "Error computing ETAG hash", e);
401: return null;
402: }
403:
404: BufferedInputStream bis = null;
405: byte[] buffer = new byte[4096];
406: DirItemInterface currentFile = null;
407: try {
408: for (DirItemInterface file : files) {
409: currentFile = file;
410: InputStream is = file.getStream();
411: if (is == null)
412: continue;
413: bis = new BufferedInputStream(is);
414: while (true) {
415: int cb = bis.read(buffer);
416: if (cb < 0)
417: break;
418: messageDigest.update(buffer, 0, cb);
419: }
420: }
421: byte digest[] = messageDigest.digest();
422: StringBuffer sb = new StringBuffer(digest.length * 2 + 16);
423: for (int i = 0; i < digest.length; i++) {
424: int v = digest[i] & 0xff;
425: if (v < 16)
426: sb.append('0');
427: sb.append(Integer.toHexString(v));
428: }
429: return sb.toString();
430: } catch (IOException e) {
431: Httpd.Log(Httpd.LogLevel.ERROR,
432: "Error computing ETAG hash "
433: + ((currentFile == null) ? "" : currentFile
434: .getName()), e);
435: messageDigest.digest();
436: return null;
437: } finally {
438: if (bis != null)
439: try {
440: bis.close();
441: } catch (Exception e) {
442: }
443: }
444: }
445: }
|