com.caucho.vfs.HttpPath Maven / Gradle / Ivy
Show all versions of resin Show documentation
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.vfs;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import com.caucho.util.CharBuffer;
import com.caucho.util.CurrentTime;
import com.caucho.util.L10N;
import com.caucho.util.LruCache;
import com.caucho.util.QDate;
/**
* The HTTP scheme. Currently it supports GET and POST.
*
* TODO: support WEBDAV, enabling the full Path API.
*/
public class HttpPath extends FilesystemPath {
protected static L10N L = new L10N(HttpPath.class);
protected static LruCache _cache
= new LruCache(1024);
protected String _host;
protected int _port;
protected String _query;
protected String _virtualHost;
protected CacheEntry _cacheEntry;
/**
* Creates a new HTTP root path with a host and a port.
*
* @param host the target host
* @param port the target port, if zero, uses port 80.
*/
public HttpPath(String host, int port)
{
super(null, "/", "/");
_root = this;
_host = host;
_port = port == 0 ? getDefaultPort() : port;
}
protected int getDefaultPort()
{
return 80;
}
/**
* Creates a new HTTP sub path.
*
* @param root the HTTP filesystem root
* @param userPath the argument to the calling lookup()
* @param newAttributes any attributes passed to http
* @param path the full normalized path
* @param query any query string
*/
HttpPath(FilesystemPath root,
String userPath, Map newAttributes,
String path, String query)
{
super(root, userPath, path);
_host = ((HttpPath) root)._host;
_port = ((HttpPath) root)._port;
_query = query;
if (newAttributes != null) {
_virtualHost = (String) newAttributes.get("host");
}
}
/**
* Overrides the default lookup to parse the host and port
* before parsing the path.
*
* @param userPath the path passed in by the user
* @param newAttributes attributes passed by the user
*
* @return the final path.
*/
@Override
public Path lookupImpl(String userPath,
Map newAttributes,
boolean isAllowRoot)
{
String newPath;
if (userPath == null)
return _root.fsWalk(getPath(), newAttributes, "/");
int length = userPath.length();
int colon = userPath.indexOf(':');
int slash = userPath.indexOf('/');
int query = userPath.indexOf('?');
// parent handles scheme:xxx
if (colon != -1 && (colon < slash || slash == -1))
return super.lookupImpl(userPath, newAttributes, isAllowRoot);
// //hostname
if (slash == 0 && length > 1 && userPath.charAt(1) == '/')
return schemeWalk(userPath, newAttributes, userPath, 0);
// /path
else if (slash == 0) {
String queryString = "";
if (query >= 0) {
queryString = userPath.substring(query);
userPath = userPath.substring(0, query);
}
newPath = normalizePath("/", userPath, 0, '/');
if (query >= 0)
newPath += queryString;
}
// path
else {
String queryString = "";
if (query >= 0) {
queryString = userPath.substring(query);
userPath = userPath.substring(0, query);
}
newPath = normalizePath(_pathname, userPath, 0, '/');
if (query >= 0)
newPath += queryString;
}
// XXX: does missing root here cause problems with restrictions?
return _root.fsWalk(userPath, newAttributes, newPath);
}
/**
* Walk down the path starting from the portion immediately following
* the scheme. i.e. schemeWalk is responsible for parsing the host and
* port from the URL.
*
* @param userPath the user's passed in path
* @param attributes the attributes for the new path
* @param uri the normalized full uri
* @param offset offset into the uri to start processing, i.e. after the
* scheme.
*
* @return the looked-up path.
*/
@Override
public Path schemeWalk(String userPath,
Map attributes,
String uri,
int offset)
{
int length = uri.length();
if (length < 2 + offset
|| uri.charAt(offset) != '/'
|| uri.charAt(offset + 1) != '/')
throw new RuntimeException(L.l("bad scheme in `{0}'", uri));
CharBuffer buf = CharBuffer.allocate();
int i = 2 + offset;
int ch = 0;
boolean isInBrace = false;
for (; i < length
&& ((ch = uri.charAt(i)) != ':' || isInBrace)
&& ch != '/'
&& ch != '?';
i++) {
buf.append((char) ch);
if (ch == '[')
isInBrace = true;
else if (ch == ']')
isInBrace = false;
}
String host = buf.close();
if (host.length() == 0)
throw new RuntimeException(L.l("bad host in `{0}'", uri));
int port = 0;
if (ch == ':') {
for (i++; i < length && (ch = uri.charAt(i)) >= '0' && ch <= '9'; i++) {
port = 10 * port + uri.charAt(i) - '0';
}
}
if (port == 0) {
port = getDefaultPort();
}
HttpPath root = create(host, port);
return root.fsWalk(userPath, attributes, uri.substring(i));
}
/**
* Scans the path portion of the URI, i.e. everything after the
* host and port.
*
* @param userPath the user's supplied path
* @param attributes the attributes for the new path
* @param uri the full uri for the new path.
*
* @return the found path.
*/
public Path fsWalk(String userPath,
Map attributes,
String uri)
{
String path;
String query = null;
int queryIndex = uri.indexOf('?');
if (queryIndex >= 0) {
path = uri.substring(0, queryIndex);
query = uri.substring(queryIndex + 1);
} else
path = uri;
if (path.length() == 0)
path = "/";
return create(_root, userPath, attributes, path, query);
}
protected HttpPath create(String host, int port)
{
return new HttpPath(host, port);
}
protected HttpPath create(FilesystemPath root,
String userPath,
Map newAttributes,
String path, String query)
{
return new HttpPath(root, userPath, newAttributes, path, query);
}
/**
* Returns the scheme, http.
*/
public String getScheme()
{
return "http";
}
/**
* Returns a full URL for the path.
*/
public String getURL()
{
int port = getPort();
return (getScheme() + "://" + getHost() +
(port == 80 ? "" : ":" + getPort()) +
getPath() +
(_query == null ? "" : "?" + _query));
}
/**
* Returns the host part of the url.
*/
public String getHost()
{
return _host;
}
/**
* Returns the port part of the url.
*/
public int getPort()
{
return _port;
}
/**
* Returns the user's path.
*/
public String getUserPath()
{
return _userPath;
}
/**
* Returns the virtual host, if any.
*/
public String getVirtualHost()
{
return _virtualHost;
}
/**
* Returns the query string.
*/
public String getQuery()
{
return _query;
}
/**
* Returns the last modified time.
*/
@Override
public long getLastModified()
{
return getCache().lastModified;
}
/**
* Returns the file's length
*/
@Override
public long getLength()
{
return getCache().length;
}
/**
* Returns true if the file exists.
*/
@Override
public boolean exists()
{
// php/1x1e
// return getCache().lastModified >= 0;
return true;
}
/**
* Returns true if the file exists.
*/
@Override
public boolean isFile()
{
// php/1x1e
// return ! getPath().endsWith("/") && getCache().lastModified >= 0;
return true;
}
/**
* Returns true if the file is readable.
*/
@Override
public boolean canRead()
{
// php/1x1e
// return isFile();
return true;
}
/**
* Returns the last modified time.
*/
@Override
public boolean isDirectory()
{
// php/1x1e
// return getPath().endsWith("/") && getCache().lastModified >= 0;
return false;
}
/**
* @return The contents of this directory or null if the path does not
* refer to a directory.
*/
/*
public String []list() throws IOException
{
try {
HttpStream stream = (HttpStream) openReadWriteImpl();
stream.setMethod("PROPFIND");
stream.setAttribute("Depth", "1");
WriteStream os = new WriteStream(stream);
os.println("");
os.println("");
os.println(" ");
os.println(" ");
os.flush();
ReadStream is = new ReadStream(stream);
ListHandler handler = new ListHandler(getPath());
XmlParser parser = new XmlParser();
parser.setContentHandler(handler);
parser.parse(is);
is.close();
os.close();
stream.close();
ArrayList names = handler.getNames();
String []list = new String[names.size()];
names.toArray(list);
return list;
} catch (Exception e) {
throw new IOException(L.l("list() is not supported by this server"));
}
}
*/
protected CacheEntry getCache()
{
if (_cacheEntry == null) {
synchronized (_cache) {
_cacheEntry = _cache.get(getPath());
if (_cacheEntry == null) {
_cacheEntry = new CacheEntry();
_cache.put(getPath(), _cacheEntry);
}
}
}
long now;
now = CurrentTime.getCurrentTime();
synchronized (_cacheEntry) {
try {
if (_cacheEntry.expires > now)
return _cacheEntry;
HttpStreamWrapper stream = (HttpStreamWrapper) openReadImpl();
stream.setHead(true);
stream.setSocketTimeout(120000);
String status = (String) stream.getAttribute("status");
if (status.equals("200")) {
String lastModified = (String) stream.getAttribute("last-modified");
_cacheEntry.lastModified = 0;
if (lastModified != null) {
QDate date = QDate.getGlobalDate();
synchronized (date) {
_cacheEntry.lastModified = date.parseDate(lastModified);
}
}
String length = (String) stream.getAttribute("content-length");
_cacheEntry.length = 0;
if (length != null) {
_cacheEntry.length = Integer.parseInt(length);
}
}
else
_cacheEntry.lastModified = -1;
_cacheEntry.expires = now + 5000;
stream.close();
return _cacheEntry;
} catch (Exception e) {
_cacheEntry.lastModified = -1;
_cacheEntry.expires = now + 5000;
return _cacheEntry;
}
}
}
/**
* Returns a read stream for a GET request.
*/
@Override
public StreamImpl openReadImpl() throws IOException
{
return HttpStream.openRead(this);
}
/**
* Returns a read/write pair for a POST request.
*/
public StreamImpl openReadWriteImpl() throws IOException
{
return HttpStream.openReadWrite(this);
}
@Override
protected Path cacheCopy()
{
return new HttpPath(getRoot(), getUserPath(),
null,
getPath(), _query);
}
/**
* Returns the string form of the http path.
*/
@Override
public String toString()
{
return getURL();
}
/**
* Returns a hashCode for the path.
*/
@Override
public int hashCode()
{
return 65537 * super.hashCode() + 37 * _host.hashCode() + _port;
}
/**
* Overrides equals to test for equality with an HTTP path.
*/
public boolean equals(Object o)
{
if (! (o instanceof HttpPath))
return false;
HttpPath test = (HttpPath) o;
if (! _host.equals(test._host))
return false;
else if (_port != test._port)
return false;
else if (_query != null && ! _query.equals(test._query))
return false;
else if (_query == null && test._query != null)
return false;
else
return true;
}
static class CacheEntry {
long lastModified;
long length;
boolean canRead;
long expires;
}
static class ListHandler extends org.xml.sax.helpers.DefaultHandler {
String _prefix;
ArrayList _names = new ArrayList();
boolean _inHref;
ListHandler(String prefix)
{
_prefix = prefix;
}
ArrayList getNames()
{
return _names;
}
public void startElement (String uri, String localName,
String qName, Attributes attributes)
{
if (localName.equals("href"))
_inHref = true;
}
public void characters(char []data, int offset, int length)
throws SAXException
{
if (! _inHref)
return;
String href = new String(data, offset, length).trim();
if (! href.startsWith(_prefix))
return;
href = href.substring(_prefix.length());
if (href.startsWith("/"))
href = href.substring(1);
int p = href.indexOf('/');
if (href.equals("") || p == 0)
return;
if (p < 0)
_names.add(href);
else
_names.add(href.substring(0, p));
}
public void endElement (String uri, String localName, String qName)
throws SAXException
{
if (localName.equals("href"))
_inHref = false;
}
}
}