001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.server.hmux;
031:
032: import com.caucho.util.Alarm;
033: import com.caucho.util.CharBuffer;
034: import com.caucho.util.L10N;
035: import com.caucho.util.LruCache;
036: import com.caucho.util.QDate;
037: import com.caucho.vfs.FilesystemPath;
038: import com.caucho.vfs.Path;
039: import com.caucho.vfs.ReadStream;
040: import com.caucho.vfs.StreamImpl;
041: import com.caucho.vfs.WriteStream;
042: import com.caucho.xml.XmlParser;
043:
044: import org.xml.sax.Attributes;
045: import org.xml.sax.SAXException;
046:
047: import java.io.IOException;
048: import java.util.ArrayList;
049: import java.util.Map;
050:
051: /**
052: * The HTTP scheme. Currently it supports GET and POST.
053: *
054: * <p>TODO: support WEBDAV, enabling the full Path API.
055: */
056: public class HmuxPath extends FilesystemPath {
057: protected static L10N L = new L10N(HmuxPath.class);
058:
059: protected static LruCache<String, CacheEntry> _cache = new LruCache<String, CacheEntry>(
060: 1024);
061:
062: protected String _host;
063: protected int _port;
064: protected String _query;
065:
066: protected String _virtualHost;
067:
068: protected CacheEntry _cacheEntry;
069:
070: /**
071: * Creates a new HTTP root path with a host and a port.
072: *
073: * @param host the target host
074: * @param port the target port, if zero, uses port 80.
075: */
076: public HmuxPath(String host, int port) {
077: super (null, "/", "/");
078:
079: _root = this ;
080: _host = host;
081: _port = port == 0 ? 80 : port;
082: }
083:
084: /**
085: * Creates a new HTTP sub path.
086: *
087: * @param root the HTTP filesystem root
088: * @param userPath the argument to the calling lookup()
089: * @param newAttributes any attributes passed to http
090: * @param path the full normalized path
091: * @param query any query string
092: */
093: HmuxPath(FilesystemPath root, String userPath,
094: Map<String, Object> newAttributes, String path, String query) {
095: super (root, userPath, path);
096:
097: _host = ((HmuxPath) root)._host;
098: _port = ((HmuxPath) root)._port;
099: _query = query;
100:
101: if (newAttributes != null) {
102: _virtualHost = (String) newAttributes.get("host");
103: }
104: }
105:
106: /**
107: * Overrides the default lookup to parse the host and port
108: * before parsing the path.
109: *
110: * @param userPath the path passed in by the user
111: * @param newAttributes attributes passed by the user
112: *
113: * @return the final path.
114: */
115: public Path lookupImpl(String userPath,
116: Map<String, Object> newAttributes) {
117: String newPath;
118:
119: if (userPath == null)
120: return _root.fsWalk(getPath(), newAttributes, "/");
121:
122: int length = userPath.length();
123: int colon = userPath.indexOf(':');
124: int slash = userPath.indexOf('/');
125:
126: // parent handles scheme:xxx
127: if (colon != -1 && (colon < slash || slash == -1))
128: return super .lookupImpl(userPath, newAttributes);
129:
130: // //hostname
131: if (slash == 0 && length > 1 && userPath.charAt(1) == '/')
132: return schemeWalk(userPath, newAttributes, userPath, 0);
133:
134: // /path
135: else if (slash == 0)
136: newPath = normalizePath("/", userPath, 0, '/');
137:
138: // path
139: else
140: newPath = normalizePath(_pathname, userPath, 0, '/');
141:
142: // XXX: does missing root here cause problems with restrictions?
143: return _root.fsWalk(userPath, newAttributes, newPath);
144: }
145:
146: /**
147: * Walk down the path starting from the portion immediately following
148: * the scheme. i.e. schemeWalk is responsible for parsing the host and
149: * port from the URL.
150: *
151: * @param userPath the user's passed in path
152: * @param attributes the attributes for the new path
153: * @param uri the normalized full uri
154: * @param offset offset into the uri to start processing, i.e. after the
155: * scheme.
156: *
157: * @return the looked-up path.
158: */
159: protected Path schemeWalk(String userPath,
160: Map<String, Object> attributes, String uri, int offset) {
161: int length = uri.length();
162:
163: if (length < 2 + offset || uri.charAt(offset) != '/'
164: || uri.charAt(offset + 1) != '/')
165: throw new RuntimeException(L.l("bad scheme in `{0}'", uri));
166:
167: CharBuffer buf = CharBuffer.allocate();
168: int i = 2 + offset;
169: int ch = 0;
170: for (; i < length && (ch = uri.charAt(i)) != ':' && ch != '/'
171: && ch != '?'; i++) {
172: buf.append((char) ch);
173: }
174:
175: String host = buf.close();
176: if (host.length() == 0)
177: throw new RuntimeException(L.l("bad host in `{0}'", uri));
178:
179: int port = 0;
180: if (ch == ':') {
181: for (i++; i < length && (ch = uri.charAt(i)) >= '0'
182: && ch <= '9'; i++) {
183: port = 10 * port + uri.charAt(i) - '0';
184: }
185: }
186:
187: if (port == 0)
188: port = 80;
189:
190: HmuxPath root = create(host, port);
191:
192: return root.fsWalk(userPath, attributes, uri.substring(i));
193: }
194:
195: /**
196: * Scans the path portion of the URI, i.e. everything after the
197: * host and port.
198: *
199: * @param userPath the user's supplied path
200: * @param attributes the attributes for the new path
201: * @param uri the full uri for the new path.
202: *
203: * @return the found path.
204: */
205: public Path fsWalk(String userPath, Map<String, Object> attributes,
206: String uri) {
207: String path;
208: String query = null;
209: int queryIndex = uri.indexOf('?');
210: if (queryIndex >= 0) {
211: path = uri.substring(0, queryIndex);
212: query = uri.substring(queryIndex + 1);
213: } else
214: path = uri;
215:
216: if (path.length() == 0)
217: path = "/";
218:
219: return create(_root, userPath, attributes, path, query);
220: }
221:
222: protected HmuxPath create(String host, int port) {
223: return new HmuxPath(host, port);
224: }
225:
226: protected HmuxPath create(FilesystemPath root, String userPath,
227: Map<String, Object> newAttributes, String path, String query) {
228: return new HmuxPath(root, userPath, newAttributes, path, query);
229: }
230:
231: /**
232: * Returns the scheme, http.
233: */
234: public String getScheme() {
235: return "http";
236: }
237:
238: /**
239: * Returns a full URL for the path.
240: */
241: public String getURL() {
242: int port = getPort();
243:
244: return (getScheme() + "://" + getHost()
245: + (port == 80 ? "" : ":" + getPort()) + getPath() + (_query == null ? ""
246: : "?" + _query));
247: }
248:
249: /**
250: * Returns the host part of the url.
251: */
252: public String getHost() {
253: return _host;
254: }
255:
256: /**
257: * Returns the port part of the url.
258: */
259: public int getPort() {
260: return _port;
261: }
262:
263: /**
264: * Returns the user's path.
265: */
266: public String getUserPath() {
267: return _userPath;
268: }
269:
270: /**
271: * Returns the virtual host, if any.
272: */
273: public String getVirtualHost() {
274: return _virtualHost;
275: }
276:
277: /**
278: * Returns the query string.
279: */
280: public String getQuery() {
281: return _query;
282: }
283:
284: /**
285: * Returns the last modified time.
286: */
287: public long getLastModified() {
288: return getCache().lastModified;
289: }
290:
291: /**
292: * Returns the file's length
293: */
294: public long getLength() {
295: return getCache().length;
296: }
297:
298: /**
299: * Returns true if the file exists.
300: */
301: public boolean exists() {
302: return getCache().lastModified >= 0;
303: }
304:
305: /**
306: * Returns true if the file exists.
307: */
308: public boolean isFile() {
309: return !getPath().endsWith("/") && getCache().lastModified >= 0;
310: }
311:
312: /**
313: * Returns true if the file is readable.
314: */
315: public boolean canRead() {
316: return isFile();
317: }
318:
319: /**
320: * Returns the last modified time.
321: */
322: public boolean isDirectory() {
323: return getPath().endsWith("/") && getCache().lastModified >= 0;
324: }
325:
326: /**
327: * @return The contents of this directory or null if the path does not
328: * refer to a directory.
329: */
330: public String[] list() throws IOException {
331: try {
332: HmuxStream stream = (HmuxStream) openReadWriteImpl();
333: stream.setMethod("PROPFIND");
334: stream.setAttribute("Depth", "1");
335:
336: WriteStream os = new WriteStream(stream);
337: os.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
338: os.println("<propfind xmlns=\"DAV:\"><prop>");
339: os.println("<resourcetype/>");
340: os.println("</prop></propfind>");
341: os.flush();
342:
343: ReadStream is = new ReadStream(stream);
344:
345: ListHandler handler = new ListHandler(getPath());
346: XmlParser parser = new XmlParser();
347: parser.setContentHandler(handler);
348:
349: parser.parse(is);
350:
351: is.close();
352: os.close();
353: stream.close();
354:
355: ArrayList<String> names = handler.getNames();
356: String[] list = new String[names.size()];
357: names.toArray(list);
358:
359: return list;
360: } catch (Exception e) {
361: throw new IOException(L
362: .l("list() is not supported by this server"));
363: }
364: }
365:
366: protected CacheEntry getCache() {
367: if (_cacheEntry == null) {
368: synchronized (_cache) {
369: _cacheEntry = _cache.get(getPath());
370: if (_cacheEntry == null) {
371: _cacheEntry = new CacheEntry();
372: _cache.put(getPath(), _cacheEntry);
373: }
374: }
375: }
376:
377: long now = Alarm.getCurrentTime();
378: synchronized (_cacheEntry) {
379: try {
380: if (_cacheEntry.expires > now)
381: return _cacheEntry;
382:
383: HmuxStreamWrapper stream = (HmuxStreamWrapper) openReadImpl();
384: stream.setHead(true);
385: stream.setSocketTimeout(120000);
386:
387: String status = (String) stream.getAttribute("status");
388: if (status.equals("200")) {
389: String lastModified = (String) stream
390: .getAttribute("last-modified");
391:
392: _cacheEntry.lastModified = 0;
393: if (lastModified != null) {
394: QDate date = QDate.getGlobalDate();
395: synchronized (date) {
396: _cacheEntry.lastModified = date
397: .parseDate(lastModified);
398: }
399: }
400:
401: String length = (String) stream
402: .getAttribute("content-length");
403: _cacheEntry.length = 0;
404: if (length != null) {
405: _cacheEntry.length = Integer.parseInt(length);
406: }
407: } else
408: _cacheEntry.lastModified = -1;
409:
410: _cacheEntry.expires = now + 5000;
411:
412: stream.close();
413: return _cacheEntry;
414: } catch (Exception e) {
415: _cacheEntry.lastModified = -1;
416: _cacheEntry.expires = now + 5000;
417:
418: return _cacheEntry;
419: }
420: }
421: }
422:
423: /**
424: * Returns a read stream for a GET request.
425: */
426: public StreamImpl openReadImpl() throws IOException {
427: return HmuxStream.openRead(this );
428: }
429:
430: /**
431: * Returns a read/write pair for a POST request.
432: */
433: public StreamImpl openReadWriteImpl() throws IOException {
434: return HmuxStream.openReadWrite(this );
435: }
436:
437: @Override
438: protected Path cacheCopy() {
439: return new HmuxPath(getRoot(), getUserPath(), null, getPath(),
440: _query);
441: }
442:
443: /**
444: * Returns the string form of the http path.
445: */
446: public String toString() {
447: return getURL();
448: }
449:
450: /**
451: * Returns a hashCode for the path.
452: */
453: public int hashCode() {
454: return 65537 * super .hashCode() + 37 * _host.hashCode() + _port;
455: }
456:
457: /**
458: * Overrides equals to test for equality with an HTTP path.
459: */
460: public boolean equals(Object o) {
461: if (!(o instanceof HmuxPath))
462: return false;
463:
464: HmuxPath test = (HmuxPath) o;
465:
466: if (!_host.equals(test._host))
467: return false;
468: else if (_port != test._port)
469: return false;
470: else if (_query != null && !_query.equals(test._query))
471: return false;
472: else if (_query == null && test._query != null)
473: return false;
474: else
475: return true;
476: }
477:
478: static class CacheEntry {
479: long lastModified;
480: long length;
481: boolean canRead;
482: long expires;
483: }
484:
485: static class ListHandler extends org.xml.sax.helpers.DefaultHandler {
486: String _prefix;
487: ArrayList<String> _names = new ArrayList<String>();
488: boolean _inHref;
489:
490: ListHandler(String prefix) {
491: _prefix = prefix;
492: }
493:
494: ArrayList<String> getNames() {
495: return _names;
496: }
497:
498: public void startElement(String uri, String localName,
499: String qName, Attributes attributes) {
500: if (localName.equals("href"))
501: _inHref = true;
502: }
503:
504: public void characters(char[] data, int offset, int length)
505: throws SAXException {
506: if (!_inHref)
507: return;
508:
509: String href = new String(data, offset, length).trim();
510: if (!href.startsWith(_prefix))
511: return;
512:
513: href = href.substring(_prefix.length());
514: if (href.startsWith("/"))
515: href = href.substring(1);
516:
517: int p = href.indexOf('/');
518: if (href.equals("") || p == 0)
519: return;
520:
521: if (p < 0)
522: _names.add(href);
523: else
524: _names.add(href.substring(0, p));
525: }
526:
527: public void endElement(String uri, String localName,
528: String qName) throws SAXException {
529: if (localName.equals("href"))
530: _inHref = false;
531: }
532: }
533: }
|