001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.war.webdav;
034:
035: import com.flexive.shared.FxSharedUtils;
036: import com.flexive.war.filter.FxRequestWrapper;
037:
038: import javax.servlet.http.HttpServletRequest;
039: import javax.servlet.http.HttpServletResponse;
040: import java.text.SimpleDateFormat;
041: import java.util.Date;
042: import java.util.Hashtable;
043:
044: /**
045: * Static WebDav helper functions
046: *
047: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
048: */
049: public class FxWebDavUtils {
050: private static final String WEBDAV_METHOD_PROPFIND = "PROPFIND";
051: private static final String WEBDAV_METHOD_PROPPATCH = "PROPPATCH";
052: private static final String WEBDAV_METHOD_MKCOL = "MKCOL";
053: private static final String WEBDAV_METHOD_COPY = "COPY";
054: private static final String WEBDAV_METHOD_MOVE = "MOVE";
055: private static final String WEBDAV_METHOD_LOCK = "LOCK";
056: private static final String WEBDAV_METHOD_UNLOCK = "UNLOCK";
057: private static final String WEBDAV_METHOD_OPTIONS = "OPTIONS";
058: private static final String HTTP_HEADER_LAST_MODIFIED = "Last-Modified";
059: private static final String HTTP_MODIFIED_AT_FORMATER_TXT = "EEE, dd MMM yyyy HH:mm:ss z";
060: private static SimpleDateFormat HTTP_MODIFIED_AT_FORMATER = new SimpleDateFormat(
061: HTTP_MODIFIED_AT_FORMATER_TXT);
062:
063: /**
064: * Determine if the given request is supposed to be a WebDav request by inspecting the context path (=/webdav)
065: *
066: * @param request the request to examine
067: * @return if this request is supposed to be a WebDav request
068: */
069: public static boolean isWebDavRequest(
070: final HttpServletRequest request) {
071: // Already resolved?
072: if (request instanceof FxRequestWrapper) {
073: ((FxRequestWrapper) request).isWebdavRequest();
074: }
075: // Resolve it
076: if (request.getServletPath() == null)
077: return false;
078: String requestUriNoContext = request.getRequestURI().substring(
079: request.getContextPath().length());
080: return (requestUriNoContext.length() == 7 && requestUriNoContext
081: .equalsIgnoreCase("/webdav"))
082: || (requestUriNoContext.length() >= 8 && requestUriNoContext
083: .substring(0, 8).equalsIgnoreCase("/webdav/"));
084: }
085:
086: /**
087: * Check if this request is a WebDav method dealing with properties
088: *
089: * @param request the request to check
090: * @return request is a WebDav method dealing with properties
091: */
092: public static boolean isWebDavPropertyMethod(
093: HttpServletRequest request) {
094: return request.getMethod().equals(WEBDAV_METHOD_PROPFIND)
095: || request.getMethod().equals(WEBDAV_METHOD_PROPPATCH)
096: || (request.getHeader("USER-AGENT") != null && request
097: .getHeader("USER-AGENT").indexOf("neon/0.") > 0);
098: }
099:
100: /**
101: * Check if the given method is WebDav related
102: *
103: * @param method the method (request.getMethod())
104: * @return method is WebDav related
105: */
106: public static boolean isWebDavMethod(String method) {
107: return method.equals(FxWebDavUtils.WEBDAV_METHOD_PROPFIND)
108: || method.equals(FxWebDavUtils.WEBDAV_METHOD_PROPPATCH)
109: || method.equals(FxWebDavUtils.WEBDAV_METHOD_MKCOL)
110: || method.equals(FxWebDavUtils.WEBDAV_METHOD_COPY)
111: || method.equals(FxWebDavUtils.WEBDAV_METHOD_MOVE)
112: || method.equals(FxWebDavUtils.WEBDAV_METHOD_LOCK)
113: || method.equals(FxWebDavUtils.WEBDAV_METHOD_OPTIONS)
114: || method.equals(FxWebDavUtils.WEBDAV_METHOD_UNLOCK);
115: }
116:
117: /**
118: * Decode and return the specified URL-encoded String.
119: * When the byte array is converted to a string, the system default
120: * character encoding is used... This may be different than some other
121: * servers.
122: *
123: * @param str The url-encoded string
124: * @throws IllegalArgumentException if a '%' character is not followed
125: * by a valid 2-digit hexadecimal number
126: */
127: public static String URLDecode(String str) {
128:
129: return URLDecode(str, null);
130:
131: }
132:
133: /**
134: * Decode and return the specified URL-encoded String.
135: *
136: * @param str The url-encoded string
137: * @param enc The encoding to use; if null, the default encoding is used
138: * @throws IllegalArgumentException if a '%' character is not followed
139: * by a valid 2-digit hexadecimal number
140: */
141: public static String URLDecode(String str, String enc) {
142:
143: if (str == null)
144: return (null);
145:
146: // use the specified encoding to extract bytes out of the
147: // given string so that the encoding is not lost. If an
148: // encoding is not specified, let it use platform default
149: byte[] bytes = null;
150: if (enc == null) {
151: bytes = FxSharedUtils.getBytes(str);
152: } else {
153: bytes = FxSharedUtils.getBytes(str);
154: }
155:
156: return URLDecode(bytes, enc);
157:
158: }
159:
160: /**
161: * Decode and return the specified URL-encoded byte array.
162: *
163: * @param bytes The url-encoded byte array
164: * @throws IllegalArgumentException if a '%' character is not followed
165: * by a valid 2-digit hexadecimal number
166: */
167: public static String URLDecode(byte[] bytes) {
168: return URLDecode(bytes, null);
169: }
170:
171: /**
172: * Decode and return the specified URL-encoded byte array.
173: *
174: * @param bytes The url-encoded byte array
175: * @param enc The encoding to use; if null, the default encoding is used
176: * @throws IllegalArgumentException if a '%' character is not followed
177: * by a valid 2-digit hexadecimal number
178: */
179: public static String URLDecode(byte[] bytes, String enc) {
180:
181: if (bytes == null)
182: return (null);
183:
184: int len = bytes.length;
185: int ix = 0;
186: int ox = 0;
187: while (ix < len) {
188: byte b = bytes[ix++]; // Get byte to test
189: if (b == '+') {
190: b = (byte) ' ';
191: } else if (b == '%') {
192: b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + convertHexDigit(bytes[ix++]));
193: }
194: bytes[ox++] = b;
195: }
196: if (enc != null) {
197: try {
198: return new String(bytes, 0, ox, enc);
199: } catch (Exception e) {
200: e.printStackTrace();
201: }
202: }
203: return new String(bytes, 0, ox);
204:
205: }
206:
207: /**
208: * Convert a byte character value to hexidecimal digit value.
209: *
210: * @param b the character value byte
211: */
212: private static byte convertHexDigit(byte b) {
213: if ((b >= '0') && (b <= '9'))
214: return (byte) (b - '0');
215: if ((b >= 'a') && (b <= 'f'))
216: return (byte) (b - 'a' + 10);
217: if ((b >= 'A') && (b <= 'F'))
218: return (byte) (b - 'A' + 10);
219: return 0;
220: }
221:
222: private static final char[] hexadecimal = { '0', '1', '2', '3',
223: '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
224:
225: // --------------------------------------------------------- Public Methods
226:
227: /**
228: * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
229: *
230: * @param binaryData Array containing the digest
231: * @return Encoded MD5, or null if encoding failed
232: */
233: public static String MD5encode(byte[] binaryData) {
234:
235: if (binaryData.length != 16)
236: return null;
237:
238: char[] buffer = new char[32];
239:
240: for (int i = 0; i < 16; i++) {
241: int low = binaryData[i] & 0x0f;
242: int high = (binaryData[i] & 0xf0) >> 4;
243: buffer[i * 2] = hexadecimal[high];
244: buffer[i * 2 + 1] = hexadecimal[low];
245: }
246:
247: return new String(buffer);
248:
249: }
250:
251: /**
252: * Get the WebDav path that is appended to the servlet
253: *
254: * @param request
255: * @return Dav path
256: */
257: public static String getDavPath(HttpServletRequest request) {
258: String result = request.getRequestURI();
259: int ctxPathLen = request.getContextPath() == null ? 0 : request
260: .getContextPath().length();
261: if (ctxPathLen > 0) {
262: result = result.substring(ctxPathLen);
263: }
264: result = result.substring(request.getServletPath().length());
265: if (result.startsWith("/")) {
266: result = result.substring(1);
267: }
268: if (result.endsWith("/")) {
269: result = result.substring(0, result.length() - 1);
270: }
271: return FxWebDavUtils.URLDecode(result, "UTF8");
272: }
273:
274: /**
275: * Return a context-relative path, beginning with a "/", that represents
276: * the canonical version of the specified path after ".." and "." elements
277: * are resolved out. If the specified path attempts to go outside the
278: * boundaries of the current context (i.e. too many ".." path elements
279: * are present), return <code>null</code> instead.
280: *
281: * @param path Path to be normalized
282: */
283: public static String normalize(String path) {
284:
285: if (path == null)
286: return null;
287:
288: // Create a place for the normalized path
289: String normalized = path;
290:
291: if (normalized.equals("/."))
292: return "/";
293:
294: // Normalize the slashes and add leading slash if necessary
295: if (normalized.indexOf('\\') >= 0)
296: normalized = normalized.replace('\\', '/');
297: if (!normalized.startsWith("/"))
298: normalized = "/" + normalized;
299:
300: // Resolve occurrences of "//" in the normalized path
301: while (true) {
302: int index = normalized.indexOf("//");
303: if (index < 0)
304: break;
305: normalized = normalized.substring(0, index)
306: + normalized.substring(index + 1);
307: }
308:
309: // Resolve occurrences of "/./" in the normalized path
310: while (true) {
311: int index = normalized.indexOf("/./");
312: if (index < 0)
313: break;
314: normalized = normalized.substring(0, index)
315: + normalized.substring(index + 2);
316: }
317:
318: // Resolve occurrences of "/../" in the normalized path
319: while (true) {
320: int index = normalized.indexOf("/../");
321: if (index < 0)
322: break;
323: if (index == 0)
324: return (null); // Trying to go outside our context
325: int index2 = normalized.lastIndexOf('/', index - 1);
326: normalized = normalized.substring(0, index2)
327: + normalized.substring(index + 3);
328: }
329:
330: // Return the normalized path that we have completed
331: return (normalized);
332: }
333:
334: /**
335: * Decode a given path, removing all url encodings, etc. and normalizes it
336: *
337: * @param request the current request
338: * @param path the path to decode
339: * @return decoded path
340: */
341: public static String decodePath(HttpServletRequest request,
342: String path) {
343: // Remove url encoding from destination
344: path = FxWebDavUtils.URLDecode(path, "UTF8");
345:
346: int protocolIndex = path.indexOf("://");
347: if (protocolIndex >= 0) {
348: // if the Destination URL contains the protocol, we can safely
349: // trim everything upto the first "/" character after "://"
350: int firstSeparator = path.indexOf("/", protocolIndex + 4);
351: if (firstSeparator < 0) {
352: path = "/";
353: } else {
354: path = path.substring(firstSeparator);
355: }
356: } else {
357: if ((request.getServerName() != null)
358: && (path.startsWith(request.getServerName()))) {
359: path = path.substring(request.getServerName().length());
360: }
361: int portIndex = path.indexOf(":");
362: if (portIndex >= 0) {
363: path = path.substring(portIndex);
364: }
365: if (path.startsWith(":")) {
366: int firstSeparator = path.indexOf("/");
367: if (firstSeparator < 0) {
368: path = "/";
369: } else {
370: path = path.substring(firstSeparator);
371: }
372: }
373: }
374: // Normalize destination path (remove '.' and '..')
375: path = FxWebDavUtils.normalize(path);
376: if (request.getContextPath() != null
377: && path.startsWith(request.getContextPath()))
378: path = path.substring(request.getContextPath().length());
379:
380: if (request.getPathInfo() != null
381: && request.getServletPath() != null
382: && path.startsWith(request.getServletPath()))
383: path = path.substring(request.getServletPath().length());
384: return path;
385: }
386:
387: private static Hashtable<String, String> contentType_mapping = null;
388:
389: /**
390: * Get the content type mapping for a given file extension
391: *
392: * @param extension
393: * @return the mapping
394: */
395: public static synchronized String getContentTypeMapping(
396: String extension) {
397: if (contentType_mapping == null) {
398: contentType_mapping = new Hashtable<String, String>(50);
399: // Image section
400: contentType_mapping.put("gif", "image/gif");
401: contentType_mapping.put("jpg", "image/jpeg");
402: contentType_mapping.put("jpeg", "image/jpeg");
403: contentType_mapping.put("jpe", "image/jpeg");
404: contentType_mapping.put("ief", "image/ief");
405: contentType_mapping.put("tiff", "image/tiff");
406: contentType_mapping.put("tif", "image/tiff");
407: contentType_mapping.put("bmp", "image/bitmap");
408: // Text section
409: contentType_mapping.put("htm", "text/html");
410: contentType_mapping.put("html", "text/html");
411: contentType_mapping.put("css", "text/css");
412: contentType_mapping.put("txt", "text/plain");
413: contentType_mapping.put("text", "text/plain");
414: contentType_mapping.put("jsp", "text/html");
415: contentType_mapping.put("js", "text/javascript");
416: contentType_mapping.put("rtx", "text/richtext");
417: contentType_mapping.put("sgm", "text/richtext");
418: contentType_mapping.put("sgml", "text/x-sgml");
419: contentType_mapping.put("etx", "text/x-setext");
420: // Video section
421: contentType_mapping.put("mpg", "video/mpeg");
422: contentType_mapping.put("mpeg", "video/mpeg");
423: contentType_mapping.put("mpe", "video/mpeg");
424: contentType_mapping.put("mov", "video/quicktime");
425: contentType_mapping.put("qt", "video/quicktime");
426: contentType_mapping.put("avi", "video/x-msvideo");
427: contentType_mapping.put("movie", "video/x-sgi-movie");
428: // Audio section
429: contentType_mapping.put("m3u", "audio/m3u");
430: contentType_mapping.put("mp3", "audio/mp3");
431: contentType_mapping.put("wav", "audio/wave");
432: contentType_mapping.put("au", "audio/basic");
433: contentType_mapping.put("aif", "audio/x-aiff");
434: contentType_mapping.put("aiff", "audio/x-aiff");
435: contentType_mapping.put("aifc", "audio/x-aiff");
436: contentType_mapping.put("snd", "audio/basic");
437: contentType_mapping.put("midi", "audio/x-midi");
438: contentType_mapping.put("mid", "audio/x-midi");
439: contentType_mapping.put("ram", "audio/x-pn-realaudio");
440: contentType_mapping.put("ra", "audio/x-pn-realaudio");
441: contentType_mapping.put("rpm",
442: "audio/x-pn-realaudio-plugin");
443: // Application section
444: contentType_mapping.put("doc", "application/msword");
445: contentType_mapping.put("dot", "application/msword");
446: contentType_mapping.put("pdf", "application/pdf");
447: contentType_mapping.put("rtf", "application/rtf");
448: contentType_mapping.put("xls", "application/excel");
449: contentType_mapping.put("xla", "application/excel");
450: contentType_mapping.put("ppt", "application/powerpoint");
451: contentType_mapping.put("pot", "application/powerpoint");
452: contentType_mapping.put("pps", "application/powerpoint");
453: contentType_mapping.put("ppz", "application/powerpoint");
454: contentType_mapping.put("ai", "application/postscript");
455: contentType_mapping.put("eps", "application/postscript");
456: contentType_mapping.put("ps", "application/postscript");
457: contentType_mapping.put("tar", "application/x-tar");
458: contentType_mapping.put("zip", "application/x-compressed");
459: contentType_mapping.put("sh", "application/x-sh");
460: contentType_mapping.put("csh", "application/x-csh");
461: contentType_mapping.put("latex", "application/x-latex");
462: contentType_mapping.put("ustar", "application/x-ustar");
463: contentType_mapping.put("shar", "application/x-shar");
464: contentType_mapping.put("mif", "application/mif");
465: contentType_mapping.put("com", "application/octet-stream");
466: contentType_mapping.put("exe", "application/octet-stream");
467: contentType_mapping.put("bin", "application/octet-stream");
468: contentType_mapping.put("dll", "application/octet-stream");
469: contentType_mapping
470: .put("class", "application/octet-stream");
471: contentType_mapping.put("jar", "application/octet-stream");
472: contentType_mapping.put("hlp", "application/mshelp");
473: contentType_mapping.put("chm", "application/mshelp");
474: contentType_mapping.put("pm6", "application/pagemaker");
475: // Others section
476: contentType_mapping.put("wrl", "x-world/x-vrml");
477: }
478: String type = contentType_mapping.get(extension.toLowerCase());
479: if (type == null)
480: type = "application/octet-string";
481: return type;
482: }
483:
484: /**
485: * Sets the modified at date in the response to the given date.
486: *
487: * @param date the date to use
488: */
489: public static void setModifiedAtDate(HttpServletResponse response,
490: Date date) {
491: response.setHeader(HTTP_HEADER_LAST_MODIFIED,
492: buildModifiedAtDate(date));
493: }
494:
495: /**
496: * Returns the modified-at date for a http response with
497: * the given time.
498: * <p/>
499: * This function uses the HTTP standard time format ("Sat, 07 Apr 2001 00:58:08 GMT")
500: *
501: * @return the modified-at date with the current time
502: */
503: public static String buildModifiedAtDate(Date date) {
504: // HTTP standard time format: "Sat, 07 Apr 2001 00:58:08 GMT"
505: // Apache server SSI format: Saturday, 08-Sep-2001 21:46:40 EDT
506: return HTTP_MODIFIED_AT_FORMATER.format(date);
507: }
508: }
|