0001: // HTTPFrame.java
0002: // $Id: HTTPFrame.java,v 1.115 2007/03/10 12:35:21 ylafon Exp $
0003: // (c) COPYRIGHT MIT and INRIA, 1997.
0004: // Please first read the full copyright statement in file COPYRIGHT.html
0005:
0006: package org.w3c.jigsaw.frames;
0007:
0008: import java.io.ByteArrayInputStream;
0009: import java.io.ByteArrayOutputStream;
0010: import java.io.File;
0011: import java.io.FileInputStream;
0012: import java.io.FileNotFoundException;
0013: import java.io.InputStream;
0014: import java.io.IOException;
0015: import java.net.MalformedURLException;
0016: import java.net.URL;
0017: import java.net.URLEncoder;
0018: import java.util.Enumeration;
0019: import java.util.Hashtable;
0020: import java.util.Vector;
0021:
0022: import org.w3c.tools.codec.Base64Encoder;
0023: import org.w3c.tools.sorter.Sorter;
0024: import org.w3c.tools.resources.Attribute;
0025: import org.w3c.tools.resources.AttributeRegistry;
0026: import org.w3c.tools.resources.BooleanAttribute;
0027: import org.w3c.tools.resources.IntegerAttribute;
0028: import org.w3c.tools.resources.LongAttribute;
0029: import org.w3c.tools.resources.DoubleAttribute;
0030: import org.w3c.tools.resources.StringAttribute;
0031: import org.w3c.tools.resources.StringArrayAttribute;
0032: import org.w3c.tools.resources.ProtocolFrame;
0033: import org.w3c.tools.resources.ProtocolFrame;
0034: import org.w3c.tools.resources.LookupState;
0035: import org.w3c.tools.resources.LookupResult;
0036: import org.w3c.tools.resources.ResourceReference;
0037: import org.w3c.tools.resources.InvalidResourceException;
0038: import org.w3c.tools.resources.MultipleLockException;
0039: import org.w3c.tools.resources.Resource;
0040: import org.w3c.tools.resources.FramedResource;
0041: import org.w3c.tools.resources.ContainerInterface;
0042: import org.w3c.tools.resources.ContainerResource;
0043: import org.w3c.tools.resources.DirectoryResource;
0044: import org.w3c.tools.resources.FileResource;
0045: import org.w3c.tools.resources.RequestInterface;
0046: import org.w3c.tools.resources.ReplyInterface;
0047: import org.w3c.tools.resources.event.AttributeChangedEvent;
0048: import org.w3c.jigsaw.http.Client;
0049: import org.w3c.jigsaw.http.ClientException;
0050: import org.w3c.jigsaw.http.httpd;
0051: import org.w3c.jigsaw.http.Request;
0052: import org.w3c.jigsaw.http.Reply;
0053: import org.w3c.jigsaw.http.HTTPException;
0054:
0055: import org.w3c.jigsaw.html.HtmlGenerator;
0056: import org.w3c.jigsaw.html.HtmlLink;
0057: import org.w3c.www.mime.MimeType;
0058: import org.w3c.www.http.ByteRangeOutputStream;
0059: import org.w3c.www.http.HTTP;
0060: import org.w3c.www.http.HttpContentRange;
0061: import org.w3c.www.http.HttpDate;
0062: import org.w3c.www.http.HttpEntityTag;
0063: import org.w3c.www.http.HttpFactory;
0064: import org.w3c.www.http.HttpInteger;
0065: import org.w3c.www.http.HttpMimeType;
0066: import org.w3c.www.http.HttpRange;
0067: import org.w3c.www.http.HttpString;
0068: import org.w3c.www.http.HttpTokenList;
0069:
0070: import org.w3c.tools.crypt.Md5;
0071:
0072: import org.w3c.tools.resources.ProtocolException;
0073: import org.w3c.tools.resources.ResourceException;
0074:
0075: /**
0076: * Default class to handle the HTTP protocol, manage FileResource and
0077: * DirectoryResource.
0078: */
0079: public class HTTPFrame extends ProtocolFrame {
0080:
0081: public static final String STATE_CONTENT_LOCATION = "org.w3c.jigsaw.frames.HTTPFrame.cl";
0082:
0083: private static final boolean debug = false;
0084:
0085: /**
0086: * Condition check return code - Condition existed but failed.
0087: */
0088: public static final int COND_FAILED = 1;
0089: /**
0090: * Condition check return code - Condition existed and succeeded.
0091: */
0092: public static final int COND_OK = 2;
0093: /**
0094: * Condition check return code - Condition existed and succeeded
0095: * but is a weak validation.
0096: */
0097: public static final int COND_WEAK = 3;
0098:
0099: private static HttpTokenList _accept_ranges = null;
0100: static {
0101: String accept_ranges[] = { "bytes" };
0102: _accept_ranges = HttpFactory.makeStringList(accept_ranges);
0103: }
0104: /**
0105: * Methods allowed by instances of that class in particular:
0106: */
0107: protected HttpTokenList allowed = null;
0108:
0109: /**
0110: * Attributes index - The index for the quality attribute.
0111: */
0112: protected static int ATTR_QUALITY = -1;
0113: /**
0114: * Attribute index - The index for the title attribute.
0115: */
0116: protected static int ATTR_TITLE = -1;
0117: /**
0118: * Attribute index - The index for the content languages attribute.
0119: */
0120: protected static int ATTR_CONTENT_LANGUAGE = -1;
0121: /**
0122: * Attribute index - The index for the content encodings attribute.
0123: */
0124: protected static int ATTR_CONTENT_ENCODING = -1;
0125: /**
0126: * Attribute index - The index for the content type attribute.
0127: */
0128: protected static int ATTR_CONTENT_TYPE = -1;
0129: /**
0130: * Attribute index - The index for the charset attribute.
0131: */
0132: protected static int ATTR_CHARSET = -1;
0133: /**
0134: * Attribute index - The index for the content length attribute.
0135: */
0136: protected static int ATTR_CONTENT_LENGTH = -1;
0137: /**
0138: * Attribute index - The icon (if any) associated to the resource.
0139: */
0140: protected static int ATTR_ICON = -1;
0141: /**
0142: * Attribute index - Max age: the maximum drift allowed from reality.
0143: */
0144: protected static int ATTR_MAXAGE = -1;
0145: /**
0146: * Attribute index - Send MD5 Digest: the md5 digest of the resource sent
0147: */
0148: protected static int ATTR_MD5 = -1;
0149: /**
0150: * Attribute index - delete allowed for the associated resource ?
0151: */
0152: protected static int ATTR_ALLOW_DEL = -1;
0153:
0154: //
0155: // Attribute relative to FileResource
0156: //
0157:
0158: /**
0159: * Attribute index - Do we allow PUT method on this file.
0160: */
0161: protected static int ATTR_PUTABLE = -1;
0162:
0163: //
0164: // Attribute relative to DirectoryResource
0165: //
0166:
0167: /**
0168: * Attribute index - The index for our relocate attribute.
0169: */
0170: protected static int ATTR_RELOCATE = -1;
0171: /**
0172: * Attribute index - our index resource name.
0173: */
0174: protected static int ATTR_INDEX = -1;
0175: /**
0176: * Attribute index - our indexes resource name.
0177: */
0178: protected static int ATTR_INDEXES = -1;
0179: /**
0180: * Attribute index - The icon directory to use in dir listing.
0181: */
0182: protected static int ATTR_ICONDIR = -1;
0183: /**
0184: * Attribute index - Allow the GNN browse method.
0185: */
0186: protected static int ATTR_BROWSABLE = -1;
0187: /**
0188: * Attribute index - Style sheet for directory listing
0189: */
0190: protected static int ATTR_STYLE_LINK = -1;
0191:
0192: static {
0193: Attribute a = null;
0194: Class cls = null;
0195:
0196: // Get a pointer to our class:
0197: try {
0198: cls = Class.forName("org.w3c.jigsaw.frames.HTTPFrame");
0199: } catch (Exception ex) {
0200: ex.printStackTrace();
0201: System.exit(1);
0202: }
0203: // The quality attribute:
0204: a = new DoubleAttribute("quality", new Double(1.0),
0205: Attribute.EDITABLE);
0206: ATTR_QUALITY = AttributeRegistry.registerAttribute(cls, a);
0207: // The title attribute:
0208: a = new StringAttribute("title", null, Attribute.EDITABLE);
0209: ATTR_TITLE = AttributeRegistry.registerAttribute(cls, a);
0210: // The content language attribute:
0211: a = new LanguageAttribute("content-language", null,
0212: Attribute.EDITABLE);
0213: ATTR_CONTENT_LANGUAGE = AttributeRegistry.registerAttribute(
0214: cls, a);
0215: // The content encoding attribute:
0216: a = new EncodingAttribute("content-encoding", null,
0217: Attribute.EDITABLE);
0218: ATTR_CONTENT_ENCODING = AttributeRegistry.registerAttribute(
0219: cls, a);
0220: // The content type attribute:
0221: a = new MimeTypeAttribute("content-type", null,
0222: Attribute.EDITABLE);
0223: ATTR_CONTENT_TYPE = AttributeRegistry.registerAttribute(cls, a);
0224: // The Charset attribute:
0225: a = new StringAttribute("charset", null, Attribute.EDITABLE);
0226: ATTR_CHARSET = AttributeRegistry.registerAttribute(cls, a);
0227: // The content length attribute:
0228: a = new IntegerAttribute("content-length", null,
0229: Attribute.COMPUTED);
0230: ATTR_CONTENT_LENGTH = AttributeRegistry.registerAttribute(cls,
0231: a);
0232: // The icon attribute:
0233: a = new StringAttribute("icon", null, Attribute.EDITABLE);
0234: ATTR_ICON = AttributeRegistry.registerAttribute(cls, a);
0235: // The max age attribute (in ms)
0236: a = new LongAttribute("maxage", null, Attribute.EDITABLE);
0237: ATTR_MAXAGE = AttributeRegistry.registerAttribute(cls, a);
0238: // Should we send MD5 digest?
0239: a = new BooleanAttribute("send-md5", Boolean.FALSE,
0240: Attribute.EDITABLE);
0241: ATTR_MD5 = AttributeRegistry.registerAttribute(cls, a);
0242: // delete allowed for the associated resource ?
0243: a = new BooleanAttribute("allow-delete", Boolean.FALSE,
0244: Attribute.EDITABLE);
0245: ATTR_ALLOW_DEL = AttributeRegistry.registerAttribute(cls, a);
0246:
0247: //
0248: // Attribute relative to a FileResource
0249: //
0250:
0251: // The putable flag:
0252: a = new BooleanAttribute("putable", Boolean.FALSE,
0253: Attribute.EDITABLE);
0254: ATTR_PUTABLE = AttributeRegistry.registerAttribute(cls, a);
0255:
0256: //
0257: // Attribute relative to a DirectoryResource
0258: //
0259:
0260: //Should we relocate invalid request to this directory ?
0261: a = new BooleanAttribute("relocate", Boolean.TRUE,
0262: Attribute.EDITABLE);
0263: ATTR_RELOCATE = AttributeRegistry.registerAttribute(cls, a);
0264: // Our index resource name (optional).
0265: a = new StringAttribute("index", null, Attribute.EDITABLE);
0266: ATTR_INDEX = AttributeRegistry.registerAttribute(cls, a);
0267: // Our indexes resource name
0268: a = new StringArrayAttribute("indexes", null,
0269: Attribute.EDITABLE);
0270: ATTR_INDEXES = AttributeRegistry.registerAttribute(cls, a);
0271: // Our icon directory.
0272: a = new StringAttribute("icondir", null, Attribute.EDITABLE);
0273: ATTR_ICONDIR = AttributeRegistry.registerAttribute(cls, a);
0274: // The browsable flag:
0275: a = new BooleanAttribute("browsable", Boolean.FALSE,
0276: Attribute.EDITABLE);
0277: ATTR_BROWSABLE = AttributeRegistry.registerAttribute(cls, a);
0278: // The style sheet attribute:
0279: a = new StringAttribute("style-sheet-link", null,
0280: Attribute.EDITABLE);
0281: ATTR_STYLE_LINK = AttributeRegistry.registerAttribute(cls, a);
0282: }
0283:
0284: /**
0285: * The associated DirectoryResource (if any)
0286: */
0287: protected DirectoryResource dresource = null;
0288:
0289: /**
0290: * The associated FileResource (if any)
0291: */
0292: protected FileResource fresource = null;
0293:
0294: /**
0295: * Register this frame to the given resource.
0296: * @param resource The resource associated with this frame.
0297: */
0298: public void registerResource(FramedResource resource) {
0299: super .registerResource(resource);
0300: if (resource instanceof FileResource)
0301: fresource = (FileResource) resource;
0302: else if (resource instanceof DirectoryResource)
0303: dresource = (DirectoryResource) resource;
0304: }
0305:
0306: /**
0307: * Get the associated FileResource (if any)
0308: * @return a FileResource instance or <strong>null</strong>
0309: * if no FileResource is associated with this frame.
0310: */
0311: public FileResource getFileResource() {
0312: return fresource;
0313: }
0314:
0315: /**
0316: * Get the associated DirectoryResource (if any)
0317: * @return a DirectoryResource instance or <strong>null</strong>
0318: * if no DirectoryResource is associated with this frame.
0319: */
0320: public DirectoryResource getDirectoryResource() {
0321: return dresource;
0322: }
0323:
0324: /**
0325: * use this one instead of registerResource if the resource type
0326: * doesn't matter or if this is not a file or a directory resource.
0327: * In subclasses you should have to do that:
0328: * <pre>
0329: * public void registerResource(FramedResource resource) {
0330: * super.registerOtherResource(resource);
0331: * }
0332: * </pre>
0333: * @param the resource to register.
0334: */
0335: public void registerOtherResource(FramedResource resource) {
0336: super .registerResource(resource);
0337: dresource = null;
0338: fresource = null;
0339: }
0340:
0341: // The HTTPResource keeps a cache of ready to use Http values. This
0342: // allows to save converting to/from wire rep these objects. Not
0343: // much CPU time, but also memory is spared.
0344: HttpMimeType contenttype = null;
0345: HttpInteger contentlength = null;
0346: HttpDate lastmodified = null;
0347: HttpTokenList contentencoding = null;
0348: HttpTokenList contentlanguage = null;
0349:
0350: // The Http entity tag for this resource (for FileResource only)
0351: HttpEntityTag etag = null;
0352: // the MD5 digest for this resource (for FileResource only)
0353: HttpString md5Digest = null;
0354:
0355: /**
0356: * Get this resource's help url.
0357: * @return An URL, encoded as a String, or <strong>null</strong> if not
0358: * available.
0359: */
0360:
0361: public String getHelpURL() {
0362: httpd server = (httpd) getServer();
0363: if (server == null)
0364: return null;
0365: String docurl = server.getDocumentationURL();
0366: if (docurl == null)
0367: return null;
0368: return docurl + "/" + getClass().getName() + ".html";
0369: }
0370:
0371: /**
0372: * Get the help URL for that resource's attribute.
0373: * @param topic The topic (can be an attribute name, or a property, etc).
0374: * @return A String encoded URL, or <strong>null</strong>.
0375: */
0376:
0377: public String getHelpURL(String topic) {
0378: httpd server = (httpd) getServer();
0379: if (server == null)
0380: return null;
0381: String docurl = server.getDocumentationURL();
0382: if (docurl == null)
0383: return null;
0384: Class defines = AttributeRegistry.getAttributeClass(getClass(),
0385: topic);
0386: if (defines != null)
0387: return docurl + "/" + defines.getName() + ".html";
0388: return null;
0389: }
0390:
0391: /**
0392: * give the md5 digest from cache or calculate it
0393: * @return the HttpString version of the digest
0394: */
0395:
0396: private HttpString getMd5Digest() {
0397: if (md5Digest != null)
0398: return md5Digest;
0399: // not found, compute it if necessary!
0400: Resource r = getResource();
0401: if (r instanceof FileResource) {
0402: try {
0403: Md5 md5 = new Md5(new FileInputStream(
0404: ((FileResource) r).getFile()));
0405: String s = null;
0406: try {
0407: byte b[] = md5.getDigest();
0408: Base64Encoder b64;
0409: ByteArrayOutputStream bos = new ByteArrayOutputStream();
0410: b64 = new Base64Encoder(
0411: new ByteArrayInputStream(b), bos);
0412: b64.process();
0413: s = bos.toString();
0414: md5Digest = HttpFactory.makeString(s);
0415: } catch (Exception mdex) {
0416: // error, set it to null
0417: md5Digest = null;
0418: }
0419: return md5Digest;
0420: } catch (FileNotFoundException ex) {
0421: // silent fail
0422: md5Digest = null;
0423: }
0424: }
0425: return null;
0426: }
0427:
0428: /**
0429: * Listen its resource.
0430: */
0431: public void attributeChanged(AttributeChangedEvent evt) {
0432: super .attributeChanged(evt);
0433: String name = evt.getAttribute().getName();
0434: if (name.equals("file-stamp")) {
0435: etag = null;
0436: lastmodified = null;
0437: md5Digest = null;
0438: } else if (name.equals("file-length")) {
0439: setValue(ATTR_CONTENT_LENGTH, evt.getNewValue());
0440: } else if (name.equals("last-modified")) {
0441: setValue(ATTR_LAST_MODIFIED, evt.getNewValue());
0442: } else {
0443: lastmodified = null;
0444: }
0445: }
0446:
0447: /**
0448: * Catch setValue, to maintain cached header values correctness.
0449: * @param idx The index of the attribute to be set.
0450: * @param value The new value for the attribute.
0451: */
0452:
0453: public synchronized void setValue(int idx, Object value) {
0454: super .setValue(idx, value);
0455: if (idx == ATTR_CONTENT_TYPE) {
0456: contenttype = null;
0457: } else if (idx == ATTR_CHARSET) {
0458: contenttype = null;
0459: } else if (idx == ATTR_CONTENT_LENGTH) {
0460: contentlength = null;
0461: } else if (idx == ATTR_CONTENT_ENCODING) {
0462: contentencoding = null;
0463: } else if (idx == ATTR_CONTENT_LANGUAGE) {
0464: contentlanguage = null;
0465: } else if (idx == ATTR_PUTABLE) {
0466: allowed = null;
0467: } else if (idx == ATTR_ALLOW_DEL) {
0468: allowed = null;
0469: } else if (idx == ATTR_MD5) {
0470: md5Digest = null; // reset the digest state
0471: }
0472: // Any attribute setting modifies the last modified time:
0473: lastmodified = null;
0474: }
0475:
0476: /**
0477: * Get the full URL for that resource.
0478: * @return An URL instance.
0479: */
0480: public URL getURL(Request request) {
0481: try {
0482: return new URL(request.getURL(), resource
0483: .unsafeGetURLPath());
0484: } catch (MalformedURLException ex) {
0485: throw new RuntimeException("unable to build "
0486: + getURLPath() + " full URL, from server "
0487: + getServer().getURL());
0488: }
0489: }
0490:
0491: /**
0492: * Get this resource quality.
0493: * @return The resource quality, or some negative value if not defined.
0494: */
0495:
0496: public double getQuality() {
0497: return getDouble(ATTR_QUALITY, -1.0);
0498: }
0499:
0500: /**
0501: * Get this resource quality.
0502: * @return The resource quality, or some negative value if not defined.
0503: */
0504:
0505: public double unsafeGetQuality() {
0506: return unsafeGetDouble(ATTR_QUALITY, -1.0);
0507: }
0508:
0509: /**
0510: * Get this resource title.
0511: * @return This resource's title, or <strong>null</strong> if not
0512: * defined.
0513: */
0514:
0515: public String getTitle() {
0516: return getString(ATTR_TITLE, null);
0517: }
0518:
0519: /**
0520: * Get this resource content language.
0521: * Language are stored as a comma separated String of tokens.
0522: * @return A comma separated string of language tokens, or
0523: * <strong>null</strong> if undefined.
0524: */
0525:
0526: public String getContentLanguage() {
0527: return (String) getValue(ATTR_CONTENT_LANGUAGE, null);
0528: }
0529:
0530: /**
0531: * Get this resource content encoding.
0532: * The content encoding of a resource is stored as a comma separated
0533: * list of tokens (as decribed in the Content_encoding header of the
0534: * HTTP specification, and in the order they should appear in the header).
0535: * @return A string of comma separated encoding tokens, or
0536: * <strong>null</strong> if not defined.
0537: */
0538:
0539: public String getContentEncoding() {
0540: String def = (String) attributes[ATTR_CONTENT_ENCODING]
0541: .getDefault();
0542: String s = (String) getString(ATTR_CONTENT_ENCODING, def);
0543: return (String) getString(ATTR_CONTENT_ENCODING, def);
0544: }
0545:
0546: /**
0547: * Get this resource charset.
0548: * @return A String, or <strong>null</strong> if not
0549: * defined.
0550: */
0551: public String getCharset() {
0552: return (String) getValue(ATTR_CHARSET, null);
0553: }
0554:
0555: /**
0556: * Get this resource content type.
0557: * @return An instance of MIMEType, or <strong>null</strong> if not
0558: * defined.
0559: */
0560: public MimeType getContentType() {
0561: return (MimeType) getValue(ATTR_CONTENT_TYPE, null);
0562: }
0563:
0564: /**
0565: * Compute the ETag string
0566: * @return a string or null if not applicable
0567: */
0568: public String computeETag() {
0569: String etag_s = null;
0570: if (fresource != null) {
0571: long lstamp = fresource.getFileStamp();
0572: if (lstamp >= 0L) {
0573: StringBuffer sb = new StringBuffer(32);
0574: sb.append(Integer.toString(getOid(), 32));
0575: sb.append(':');
0576: sb.append(Long.toString(lstamp, 32));
0577: etag_s = sb.toString();
0578: }
0579: }
0580: return etag_s;
0581: }
0582:
0583: /**
0584: * Get this resource Etag
0585: * @return an instance of HttpEntityTag, or <strong>null</strong> if not
0586: * defined.
0587: */
0588:
0589: public HttpEntityTag getETag() {
0590: if (etag == null) {
0591: String etag_s = computeETag();
0592: // no luck, exit
0593: if (etag_s == null) {
0594: return null;
0595: }
0596: etag = HttpFactory.makeETag(false, etag_s);
0597: }
0598: return etag;
0599: }
0600:
0601: /**
0602: * Get this resource content length.
0603: * @return The resource content length, or <strong>-1</strong> if not
0604: * defined.
0605: */
0606:
0607: public int getContentLength() {
0608: return getInt(ATTR_CONTENT_LENGTH, -1);
0609: }
0610:
0611: /**
0612: * Get this resource's icon.
0613: */
0614:
0615: public String getIcon() {
0616: return getString(ATTR_ICON, null);
0617: }
0618:
0619: /**
0620: * Get this resource's max age.
0621: * The max age of a resource indicates how much drift is allowed between
0622: * the physicall version of the resource, and any in-memory cached version
0623: * of it.
0624: * <p>The max age attribute is a long number giving the number of
0625: * milliseconds of allowed drift.
0626: */
0627:
0628: public long getMaxAge() {
0629: return getLong(ATTR_MAXAGE, (long) -1);
0630: }
0631:
0632: //
0633: // Relative to FileResource ...
0634: //
0635:
0636: /**
0637: * Does this resource support byte ranges.
0638: */
0639: protected boolean acceptRanges = false;
0640:
0641: /**
0642: * Get the PUT'able flag (are we allow to PUT to the resource ?)
0643: */
0644: public boolean getPutableFlag() {
0645: return getBoolean(ATTR_PUTABLE, false);
0646: }
0647:
0648: /**
0649: * Do we send the MD5 digest?
0650: */
0651: public boolean getMD5Flag() {
0652: return getBoolean(ATTR_MD5, false);
0653: }
0654:
0655: /**
0656: * delete allowed for the associated resource ?
0657: */
0658: public boolean getAllowDeleteFlag() {
0659: return getBoolean(ATTR_ALLOW_DEL, false);
0660: }
0661:
0662: /**
0663: * get the Allowed methods for this resource
0664: * @return an HttpTokenList
0665: */
0666: protected HttpTokenList getAllow() {
0667: if (allowed != null) {
0668: return allowed;
0669: }
0670: int size = 4; // the default HEAD GET OPTIONS TRACE
0671: if (getPutableFlag()) {
0672: size++;
0673: }
0674: if (getAllowDeleteFlag()) {
0675: size++;
0676: }
0677: String allow_str[] = new String[size];
0678: int i = 0;
0679: if (getAllowDeleteFlag()) {
0680: allow_str[i++] = "DELETE";
0681: }
0682: allow_str[i++] = "HEAD";
0683: allow_str[i++] = "GET";
0684: allow_str[i++] = "OPTIONS";
0685: if (getPutableFlag()) {
0686: allow_str[i++] = "PUT";
0687: }
0688: allow_str[i] = "TRACE";
0689: allowed = HttpFactory.makeStringList(allow_str);
0690: return allowed;
0691: }
0692:
0693: /**
0694: * handles a Range Request
0695: * @param request, the request
0696: * @param r, the HttpRange
0697: * @return a Reply if range is valid, or null if there is a change in the
0698: * resource, or if the HttpRange is not valid ( 4-2, for example).
0699: * @exception ProtocolException If processsing the request failed.
0700: */
0701:
0702: public Reply handleRangeRequest(Request request, HttpRange r)
0703: throws ProtocolException {
0704: // Should we check against a IfRange header ?
0705: HttpEntityTag t = request.getIfRange();
0706:
0707: if (t != null) {
0708: if (t.isWeak() || !t.getTag().equals(etag.getTag()))
0709: return null;
0710: }
0711: // Check the range:
0712: int cl = getContentLength();
0713: int fb = r.getFirstPosition();
0714: int lb = r.getLastPosition();
0715: int sz;
0716:
0717: if (fb > cl - 1) { // first byte already out of range
0718: HttpContentRange cr = HttpFactory.makeContentRange("bytes",
0719: 0, cl - 1, cl);
0720: Reply rr;
0721: rr = createDefaultReply(request,
0722: HTTP.REQUESTED_RANGE_NOT_SATISFIABLE);
0723: rr.setContentLength(-1);
0724: rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);
0725: if (getMD5Flag())
0726: rr.setContentMD5(null);
0727: return rr;
0728: }
0729:
0730: if ((fb < 0) && (lb >= 0)) { // ex: bytes=-20 final 20 bytes
0731: if (lb >= cl) // cut the end
0732: lb = cl;
0733: sz = lb;
0734: fb = cl - lb;
0735: lb = cl - 1;
0736: } else if (lb < 0) { // ex: bytes=10- the last size - 10
0737: lb = cl - 1;
0738: sz = lb - fb + 1;
0739: } else { // ex: bytes=10-20
0740: if (lb >= cl) // cut the end
0741: lb = cl - 1;
0742: sz = lb - fb + 1;
0743: }
0744: if ((fb < 0) || (lb < 0) || (fb <= lb)) {
0745: HttpContentRange cr = null;
0746: fb = (fb < 0) ? 0 : fb;
0747: lb = ((lb > cl) || (lb < 0)) ? cl : lb;
0748: cr = HttpFactory.makeContentRange("bytes", fb, lb, cl);
0749: // Emit reply:
0750: Reply rr = createDefaultReply(request, HTTP.PARTIAL_CONTENT);
0751: // FIXME check for MD5 of only the subpart
0752: try { // create the MD5 for the subpart
0753: if (getMD5Flag()) {
0754: String s = null;
0755: try {
0756: ByteRangeOutputStream br;
0757: br = new ByteRangeOutputStream(fresource
0758: .getFile(), fb, lb + 1);
0759: Md5 md5 = new Md5(br);
0760: byte b[] = md5.getDigest();
0761: Base64Encoder b64;
0762: ByteArrayOutputStream bs = new ByteArrayOutputStream();
0763: b64 = new Base64Encoder(
0764: new ByteArrayInputStream(b), bs);
0765: b64.process();
0766: s = bs.toString();
0767: } catch (Exception md_ex) {
0768: // default to null, no action here then
0769: }
0770: if (s == null)
0771: rr.setContentMD5(null);
0772: else
0773: rr.setContentMD5(s);
0774: }
0775: rr.setContentLength(sz);
0776: rr.setHeaderValue(rr.H_CONTENT_RANGE, cr);
0777: rr.setStream(new ByteRangeOutputStream(fresource
0778: .getFile(), fb, lb + 1));
0779: return rr;
0780: } catch (IOException ex) {
0781: }
0782: }
0783: return null;
0784: }
0785:
0786: //
0787: // Relative to DirectoryResource ...
0788: //
0789:
0790: /**
0791: * Get this class browsable flag.
0792: */
0793: public boolean getBrowsableFlag() {
0794: return getBoolean(ATTR_BROWSABLE, false);
0795: }
0796:
0797: /**
0798: * Get this frame style sheet link
0799: */
0800: public String getStyleSheetURL() {
0801: return getString(ATTR_STYLE_LINK, null);
0802: }
0803:
0804: /**
0805: * Our current (cached) directory listing.
0806: */
0807: protected HtmlGenerator listing = null;
0808: /**
0809: * The time at which we generated the directory index.
0810: */
0811: protected long listing_stamp = -1;
0812:
0813: private String getUnextendedName(String name) {
0814: int strlen = name.length();
0815: for (int i = 0; i < strlen; i++) {
0816: // FIXME: Should use the system props to get the right sep
0817: if (name.charAt(i) == '.') {
0818: if (i == 0)
0819: return null;
0820: return name.substring(0, i);
0821: }
0822: }
0823: return null;
0824: }
0825:
0826: /**
0827: * Get the optional icon directory.
0828: */
0829: public String getIconDirectory() {
0830: return getString(ATTR_ICONDIR, "/icons");
0831: }
0832:
0833: /**
0834: * Should we relocate invalid requests to this directory.
0835: * @return A boolean <strong>true</strong> if we should relocate.
0836: */
0837: public boolean getRelocateFlag() {
0838: return getBoolean(ATTR_RELOCATE, true);
0839: }
0840:
0841: /**
0842: * Get the optional main index name for this directory listing.
0843: * @return The name of the resource responsible to list that container.
0844: */
0845: public String getIndex() {
0846: return (String) getValue(ATTR_INDEX, null);
0847: }
0848:
0849: /**
0850: * Get the optional index name array for this directory listing.
0851: * @return The index name array (including the main index)
0852: * @see #getIndex
0853: */
0854: public String[] getIndexes() {
0855: String mainIndex = getIndex();
0856: if (mainIndex != null) {
0857: String indexes[] = (String[]) getValue(ATTR_INDEXES, null);
0858: if (indexes != null) {
0859: int len = indexes.length + 1;
0860: String mergeIndex[] = new String[len];
0861: mergeIndex[0] = mainIndex;
0862: System.arraycopy(indexes, 0, mergeIndex, 1, len - 1);
0863: return mergeIndex;
0864: } else {
0865: indexes = new String[1];
0866: indexes[0] = mainIndex;
0867: return indexes;
0868: }
0869: } else {
0870: return (String[]) getValue(ATTR_INDEXES, null);
0871: }
0872: }
0873:
0874: /**
0875: * Add our own Style Sheet to the HtmlGenerator.
0876: * @param g The HtmlGenerator.
0877: */
0878: public void addStyleSheet(HtmlGenerator g) {
0879: // Add style link
0880: String css_url = getStyleSheetURL();
0881: if (css_url != null) {
0882: g.addLink(new HtmlLink("STYLESHEET", css_url));
0883: }
0884: }
0885:
0886: /**
0887: * Get ContainerResource listing
0888: * @param refresh should we refresh the listing?
0889: * @return a boolean (true if refreshed)
0890: */
0891: public boolean computeContainerListing(boolean refresh) {
0892: ContainerResource cresource = (ContainerResource) resource;
0893: synchronized (cresource) {
0894: if ((refresh) || (listing == null)
0895: || (cresource.getLastModified() > listing_stamp)
0896: || (getLastModified() > listing_stamp)) {
0897:
0898: Class http_class = null;
0899: try {
0900: http_class = Class
0901: .forName("org.w3c.jigsaw.frames.HTTPFrame");
0902: } catch (ClassNotFoundException ex) {
0903: http_class = null;
0904: }
0905:
0906: Enumeration e = cresource
0907: .enumerateResourceIdentifiers();
0908: Vector resources = Sorter.sortStringEnumeration(e);
0909: HtmlGenerator g = new HtmlGenerator("Index of "
0910: + cresource.getIdentifier());
0911: // Add style link
0912: addStyleSheet(g);
0913: g.append("<h1>" + cresource.getIdentifier() + "</h1>");
0914: // Link to the parent, when possible:
0915: if (cresource.getParent() != null) {
0916: g.append("<p><a href=\"..\">Parent</a><br>");
0917: }
0918: // List the children:
0919: for (int i = 0; i < resources.size(); i++) {
0920: String name = (String) resources.elementAt(i);
0921: ResourceReference rr = null;
0922: long size = -1;
0923: rr = cresource.lookup(name);
0924: FramedResource resource = null;
0925: if (rr != null) {
0926: try {
0927: resource = (FramedResource) rr.unsafeLock();
0928: // remove manually deleted FileResources
0929: if (resource instanceof FileResource) {
0930: FileResource fr = (FileResource) resource;
0931: if (!fr.getFile().exists()) {
0932: try {
0933: fr.delete();
0934: } catch (MultipleLockException ex) {
0935: }
0936: ;
0937: continue;
0938: } else {
0939: size = fr.getFile().length();
0940: }
0941: }
0942: HTTPFrame itsframe = null;
0943: if (http_class != null)
0944: itsframe = (HTTPFrame) resource
0945: .getFrame(http_class);
0946: if (itsframe != null) {
0947: // Icon first, if available
0948: String icon = itsframe.getIcon();
0949: if (icon != null) {
0950: g.append("<img src=\""
0951: + getIconDirectory() + "/"
0952: + icon + "\" alt=\"" + icon
0953: + "\">");
0954: }
0955: // Resource's name with link:
0956: if (resource instanceof ContainerInterface) {
0957: g.append("<a href=\"", URLEncoder
0958: .encode(name), "/\">"
0959: + name + "</a>");
0960: } else {
0961: g.append("<a href=\"", URLEncoder
0962: .encode(name), "\">" + name
0963: + "</a>");
0964: }
0965: // resource's title, if any:
0966: String title = itsframe.getTitle();
0967: if (title != null) {
0968: g.append(" " + title);
0969: }
0970: //size (if any)
0971: if (size != -1) {
0972: String s = null;
0973: if (size > 1023) {
0974: s = " [" + (size / 1024)
0975: + " KB]";
0976: } else {
0977: s = " [" + size + " bytes]";
0978: }
0979: g.append(s);
0980: }
0981: g.append("<br>\n");
0982: } else {
0983: // Resource's name with link:
0984: g
0985: .append(name
0986: + " (<i>Not available via HTTP.</i>)");
0987: g.append("<br>\n");
0988: }
0989: } catch (InvalidResourceException ex) {
0990: g
0991: .append(name
0992: + " cannot be loaded (server misconfigured)");
0993: g.append("<br>\n");
0994: continue;
0995: } finally {
0996: rr.unlock();
0997: }
0998: }
0999: }
1000: g.close();
1001: listing_stamp = getLastModified();
1002: listing = g;
1003: return true;
1004: }
1005: }
1006: return false;
1007: }
1008:
1009: /**
1010: * Reply with an HTML doc listing the resources of this container.
1011: * This function takes special care not to regenerate a listing
1012: * when one is available. It also caches the date of the
1013: * listing, so that it can win big with NOT_MODIFIED.
1014: * <p>Using a modem, I know that each place I can reply with an
1015: * NOT_MODIFIED, <strong>is</strong> a big win.
1016: * @param request The request to handle.
1017: * @exception ProtocolException If processsing the request failed.
1018: * @exception ResourceException If the resource got a fatal error.
1019: */
1020:
1021: public Reply getDirectoryListing(Request request)
1022: throws ProtocolException, ResourceException {
1023: if (!(resource instanceof ContainerResource)) {
1024: throw new ResourceException(
1025: "this frame is not attached to a "
1026: + "ContainerResource. ("
1027: + resource.getIdentifier() + ")");
1028: }
1029: // delete us if the directory was deleted
1030: boolean refresh = false;
1031: if (dresource != null) {
1032: synchronized (dresource) {
1033: if (!dresource.getDirectory().exists()) {
1034: //delete us and emit an error
1035: String msg = dresource.getIdentifier()
1036: + ": deleted, removing the DirectoryResource";
1037: getServer().errlog(dresource, msg);
1038: try {
1039: dresource.delete();
1040: } catch (MultipleLockException ex) {
1041: }
1042: // Emit an error back:
1043: Reply error = request.makeReply(HTTP.NOT_FOUND);
1044: error.setContent("<h1>Document not found</h1>"
1045: + "<p>The document " + request.getURL()
1046: + " is indexed but not available."
1047: + "<p>The server is misconfigured.");
1048: throw new HTTPException(error);
1049: }
1050: refresh = (dresource.getDirectory().lastModified() > listing_stamp);
1051: }
1052: }
1053: if ((!computeContainerListing(refresh))
1054: && (checkIfModifiedSince(request) == COND_FAILED)) {
1055: // Is it an IMS request ?
1056: Reply reply = createDefaultReply(request, HTTP.NOT_MODIFIED);
1057: return reply;
1058: }
1059: // New content or need update:
1060: Reply reply = createDefaultReply(request, HTTP.OK);
1061: reply.setLastModified(listing_stamp);
1062: reply.setStream(listing);
1063: // check MD5
1064: return reply;
1065: }
1066:
1067: //
1068: // Commom part.
1069: //
1070:
1071: /**
1072: * Update the cached headers value.
1073: * Each resource maintains a set of cached values for headers, this
1074: * allows for a nice sped-up in headers marshalling, which - as the
1075: * complexity of the protocol increases - becomes a bottleneck.
1076: */
1077:
1078: protected void updateCachedHeaders() {
1079: // Precompute a set of header values to keep by:
1080: if (contenttype == null) {
1081: String charset = getCharset();
1082: if (charset == null)
1083: contenttype = HttpFactory
1084: .makeMimeType(getContentType());
1085: else {
1086: MimeType ctype = getContentType().getClone();
1087: ctype.addParameter("charset", charset);
1088: contenttype = HttpFactory.makeMimeType(ctype);
1089: }
1090: }
1091: if (contentlength == null) {
1092: int cl = -1;
1093: if (fresource != null)
1094: cl = fresource.getFileLength();
1095: if (cl >= 0) {
1096: if (cl != getInt(ATTR_CONTENT_LENGTH, -1)) {
1097: setValue(ATTR_CONTENT_LENGTH, new Integer(cl));
1098: }
1099: contentlength = HttpFactory.makeInteger(cl);
1100: }
1101: }
1102: if (lastmodified == null) {
1103: long lm = getLastModified();
1104: if (lm > 0)
1105: lastmodified = HttpFactory.makeDate(lm);
1106: }
1107: if (definesAttribute(ATTR_CONTENT_ENCODING)
1108: && (contentencoding == null))
1109: contentencoding = HttpFactory
1110: .makeStringList(getContentEncoding());
1111: if (definesAttribute(ATTR_CONTENT_LANGUAGE)
1112: && (contentlanguage == null))
1113: contentlanguage = HttpFactory
1114: .makeStringList(getContentLanguage());
1115:
1116: if (fresource != null) {
1117: // We only take care of etag here:
1118: if (etag == null) {
1119: getETag();
1120: }
1121: if (getMD5Flag() && (md5Digest == null)) {
1122: getMd5Digest();
1123: }
1124: }
1125: }
1126:
1127: /**
1128: * Create a reply to answer to request on this file.
1129: * This method will create a suitable reply (matching the given request)
1130: * and will set all its default header values to the appropriate
1131: * values.
1132: * @param request The request to make a reply for.
1133: * @return An instance of Reply, suited to answer this request.
1134: */
1135:
1136: public Reply createDefaultReply(Request request, int status) {
1137: Reply reply = request.makeReply(status);
1138: updateCachedHeaders();
1139: if (status != HTTP.NOT_MODIFIED) {
1140: if (contentlength != null)
1141: reply.setHeaderValue(Reply.H_CONTENT_LENGTH,
1142: contentlength);
1143: if (contenttype != null)
1144: reply.setHeaderValue(Reply.H_CONTENT_TYPE, contenttype);
1145: if (lastmodified != null)
1146: reply.setHeaderValue(Reply.H_LAST_MODIFIED,
1147: lastmodified);
1148: if (contentencoding != null) {
1149: reply.setHeaderValue(Reply.H_CONTENT_ENCODING,
1150: contentencoding);
1151: }
1152: if (contentlanguage != null)
1153: reply.setHeaderValue(Reply.H_CONTENT_LANGUAGE,
1154: contentlanguage);
1155:
1156: }
1157: long maxage = getMaxAge();
1158: if (maxage >= 0) {
1159: if (reply.getMajorVersion() >= 1) {
1160: if (reply.getMinorVersion() >= 1) {
1161: reply.setMaxAge((int) (maxage / 1000));
1162: }
1163: // If max-age is zero, say what you mean:
1164: long expires = (System.currentTimeMillis() + ((maxage == 0) ? -1000
1165: : maxage));
1166: reply.setExpires(expires);
1167: }
1168: }
1169: // Set the date of the reply (round it to secs):
1170: reply.setDate((System.currentTimeMillis() / 1000L) * 1000L);
1171:
1172: if (fresource != null) {
1173: // Set the entity tag:
1174: if (getETag() != null) {
1175: reply.setETag(etag);
1176: }
1177: if (status != HTTP.NOT_MODIFIED) {
1178: if (acceptRanges) {
1179: reply.setHeaderValue(reply.H_ACCEPT_RANGES,
1180: _accept_ranges);
1181: }
1182: if (getMD5Flag()) {
1183: reply.setHeaderValue(reply.H_CONTENT_MD5,
1184: getMd5Digest());
1185: }
1186: }
1187: }
1188: return reply;
1189: }
1190:
1191: /**
1192: * Check the <code>If-Match</code> condition of that request.
1193: * @param request The request to check.
1194: * @return An integer, either <code>COND_FAILED</cond> if condition
1195: * was checked, but failed, <code>COND_OK</code> if condition was checked
1196: * and succeeded, or <strong>0</strong> if the condition was not checked
1197: * at all (eg because the resource or the request didn't support it).
1198: */
1199:
1200: public int checkIfMatch(Request request) {
1201: if (fresource != null) {
1202: HttpEntityTag tags[] = request.getIfMatch();
1203: if (tags != null) {
1204: // Good, real validators in use:
1205: if (etag != null) {
1206: // Note: if etag is null this means that the resource has
1207: // changed and has not been even emited since then...
1208: for (int i = 0; i < tags.length; i++) {
1209: HttpEntityTag t = tags[i];
1210: if (t.getTag().equals(etag.getTag())) {
1211: if (t.isWeak() || etag.isWeak()) {
1212: return COND_WEAK;
1213: } else {
1214: return COND_OK;
1215: }
1216: }
1217: }
1218: }
1219: return COND_FAILED;
1220: }
1221: }
1222: return 0;
1223: }
1224:
1225: /**
1226: * Check the <code>If-None-Match</code> condition of that request.
1227: * @param request The request to check.
1228: * @return An integer, either <code>COND_FAILED</cond> if condition
1229: * was checked, but failed, <code>COND_OK</code> if condition was checked
1230: * and succeeded, or <strong>0</strong> if the condition was not checked
1231: * at all (eg because the resource or the request didn't support it).
1232: */
1233:
1234: public int checkIfNoneMatch(Request request) {
1235: if (fresource != null) {
1236: // Check for an If-None-Match conditional:
1237: HttpEntityTag tags[] = request.getIfNoneMatch();
1238: if (tags != null) {
1239: if (etag == null) {
1240: return COND_OK;
1241: }
1242: int status = COND_OK;
1243: for (int i = 0; i < tags.length; i++) {
1244: HttpEntityTag t = tags[i];
1245: if (t.getTag().equals(etag.getTag())) {
1246: if (t.isWeak() || etag.isWeak()) {
1247: status = COND_WEAK;
1248: } else {
1249: return COND_FAILED;
1250: }
1251: }
1252: if (t.getTag().equals("*")) {
1253: if (fresource != null) {
1254: File f = fresource.getFile();
1255: if (f.exists()) {
1256: return COND_FAILED;
1257: }
1258: } else {
1259: return COND_FAILED;
1260: }
1261: }
1262: }
1263: return status;
1264: }
1265: }
1266: return 0;
1267: }
1268:
1269: /**
1270: * Check the <code>If-Modified-Since</code> condition of that request.
1271: * @param request The request to check.
1272: * @return An integer, either <code>COND_FAILED</cond> if condition
1273: * was checked, but failed, <code>COND_OK</code> if condition was checked
1274: * and succeeded, or <strong>0</strong> if the condition was not checked
1275: * at all (eg because the resource or the request didn't support it).
1276: */
1277:
1278: public int checkIfModifiedSince(Request request) {
1279: // Check for an If-Modified-Since conditional:
1280: long ims = request.getIfModifiedSince();
1281: if (dresource != null) {
1282: if (ims >= 0) {
1283: if (listing_stamp > 0) {
1284: long s_listing_stamp = listing_stamp / 1000;
1285: long s_ims = ims / 1000;
1286: if (s_listing_stamp < s_ims) {
1287: return COND_FAILED;
1288: } else if (s_listing_stamp == s_ims) {
1289: return COND_WEAK;
1290: }
1291: return COND_OK;
1292: }
1293: }
1294: } else if (fresource != null) {
1295: long cmt = getLastModified();
1296: if (ims >= 0) {
1297: if (cmt > 0) {
1298: long s_cmt = cmt / 1000;
1299: long s_ims = ims / 1000;
1300: if (s_cmt < s_ims) {
1301: return COND_FAILED;
1302: } else if (s_cmt == s_ims) {
1303: return COND_WEAK;
1304: }
1305: return COND_OK;
1306: }
1307: }
1308: }
1309: return 0;
1310: }
1311:
1312: /**
1313: * Check the <code>If-Unmodified-Since</code> condition of that request.
1314: * @param request The request to check.
1315: * @return An integer, either <code>COND_FAILED</cond> if condition
1316: * was checked, but failed, <code>COND_OK</code> if condition was checked
1317: * and succeeded, or <strong>0</strong> if the condition was not checked
1318: * at all (eg because the resource or the request didn't support it).
1319: */
1320:
1321: public int checkIfUnmodifiedSince(Request request) {
1322: if (fresource != null) {
1323: // Check for an If-Unmodified-Since conditional:
1324: long iums = request.getIfUnmodifiedSince();
1325: long cmt = getLastModified();
1326: if (iums >= 0)
1327: return ((cmt > 0) && (cmt - 1000) >= iums) ? COND_FAILED
1328: : COND_OK;
1329: }
1330: return 0;
1331: }
1332:
1333: /**
1334: * Check the <code>Expect</code> condition of that request
1335: * @param request The request to check.
1336: * @return A boolean <code>true</code> if the requirement is known
1337: */
1338:
1339: public boolean checkExpect(Request request) {
1340: // crude for now as we only support 100-continue
1341: // so FIXME for a more evolved version of this.
1342: String exp = request.getExpect();
1343: if (exp != null) {
1344: if (!exp.equalsIgnoreCase(HTTP.HTTP_100_CONTINUE)) {
1345: return false;
1346: }
1347: }
1348: return true;
1349: }
1350:
1351: /**
1352: * check the validators namely LMT/Etags according to rfc2616 rules
1353: * @return An integer, either <code>COND_FAILED</cond> if condition
1354: * was checked, but failed, <code>COND_OK</code> if condition was checked
1355: * and succeeded, or <strong>0</strong> if the condition was not checked
1356: * at all (eg because the resource or the request didn't support it).
1357: */
1358: public int checkValidators(Request request) {
1359: int v_inm = checkIfNoneMatch(request);
1360: int v_ims = checkIfModifiedSince(request);
1361:
1362: if ((v_inm == COND_OK) || (v_ims == COND_OK)) {
1363: return COND_OK;
1364: }
1365: if ((v_inm == COND_FAILED) || (v_ims == COND_FAILED)) {
1366: return COND_FAILED;
1367: }
1368: if ((v_inm == COND_WEAK) || (v_ims == COND_WEAK)) {
1369: return COND_FAILED;
1370: }
1371: return 0;
1372: }
1373:
1374: /**
1375: * Lookup the target resource. Lookup filters and then resource.
1376: * @param ls The current lookup state
1377: * @param lr The result
1378: * @return true if lookup is done.
1379: * @exception ProtocolException If an error relative to the protocol occurs
1380: * @see org.w3c.tools.resources.ResourceFrame#lookupFilters
1381: * @see #lookupResource
1382: */
1383: public boolean lookup(LookupState ls, LookupResult lr)
1384: throws ProtocolException {
1385: RequestInterface req = ls.getRequest();
1386: if (!checkRequest(req))
1387: return false;
1388: if (lookupFilters(ls, lr))
1389: return true;
1390: return lookupResource(ls, lr);
1391: }
1392:
1393: /**
1394: * Lookup the target resource (dispath to more specific lookup methods).
1395: * @param ls The current lookup state
1396: * @param lr The result
1397: * @return true if lookup is done.
1398: * @exception ProtocolException If an error relative to the protocol occurs
1399: * @see #lookupDirectory
1400: * @see #lookupFile
1401: * @see #lookupOther
1402: */
1403: protected boolean lookupResource(LookupState ls, LookupResult lr)
1404: throws ProtocolException {
1405: if (fresource != null) {
1406: return lookupFile(ls, lr);
1407: } else if (dresource != null) {
1408: return lookupDirectory(ls, lr);
1409: } else {
1410: return lookupOther(ls, lr);
1411: }
1412: }
1413:
1414: /**
1415: * Lookup the target resource when associated with a DirectoryResource.
1416: * @param ls The current lookup state
1417: * @param lr The result
1418: * @return true if lookup is done.
1419: * @exception ProtocolException If an error relative to the protocol
1420: * occurs
1421: */
1422: protected boolean lookupDirectory(LookupState ls, LookupResult lr)
1423: throws ProtocolException {
1424: // Give a chance to our super-class to run its own lookup scheme:
1425: // do we have to create a resource (PUT) ?
1426: if ((ls.hasMoreComponents()) && getPutableFlag()) {
1427: Request request = (Request) ls.getRequest();
1428: if ((request == null) || request.getMethod().equals("PUT")) {
1429: // We might well want to create a resource:
1430: String name = ls.peekNextComponent();
1431: ResourceReference rr = dresource.lookup(name);
1432: if ((rr == null) && (dresource.getExtensibleFlag())) {
1433: if (ls.countRemainingComponents() == 1)
1434: rr = dresource.createResource(name, request);
1435: else
1436: rr = dresource.createDirectoryResource(name);
1437: if (rr == null) {
1438: Reply error = request
1439: .makeReply(HTTP.UNSUPPORTED_MEDIA_TYPE);
1440: error
1441: .setContent("Failed to create resource "
1442: + name
1443: + " : "
1444: + "Unable to create the appropriate file:"
1445: + request.getURLPath()
1446: + " this media type is not supported");
1447: throw new HTTPException(error);
1448: }
1449: } else if (rr == null) {
1450: Reply error = request.makeReply(HTTP.FORBIDDEN);
1451: error
1452: .setContent("You are not allowed to create resource "
1453: + name
1454: + " : "
1455: + dresource.getIdentifier()
1456: + " is not extensible.");
1457: throw new HTTPException(error);
1458: }
1459: }
1460: }
1461: if (super .lookup(ls, lr)) {
1462: if (!ls.isDirectory() && !ls.isInternal()) {
1463: // The directory lookup URL doesn't end with a slash:
1464: Request request = (Request) ls.getRequest();
1465: if (request == null) {
1466: lr.setTarget(null);
1467: return true;
1468: }
1469: URL url = null;
1470: try {
1471: if ((request != null)
1472: && request.hasState(Request.ORIG_URL_STATE)) {
1473: URL oldurl;
1474: oldurl = (URL) request
1475: .getState(Request.ORIG_URL_STATE);
1476: url = new URL(oldurl, oldurl.getFile() + "/");
1477: } else {
1478: url = (ls.hasRequest() ? getURL(request)
1479: : new URL(getServer().getURL(),
1480: resource.getURLPath()));
1481: }
1482: } catch (MalformedURLException ex) {
1483: getServer().errlog(this ,
1484: "unable to build full URL.");
1485: throw new HTTPException("Internal server error");
1486: }
1487: String msg = "Invalid requested URL: the directory resource "
1488: + " you are trying to reach is available only through "
1489: + " its full URL: <a href=\""
1490: + url
1491: + "\">"
1492: + url + "</a>.";
1493: if (getRelocateFlag()) {
1494: // Emit an error (with reloc if allowed)
1495: Reply reloc = request.makeReply(HTTP.FOUND);
1496: reloc.setContent(msg);
1497: reloc.setLocation(url);
1498: lr.setTarget(null);
1499: lr.setReply(reloc);
1500: return true;
1501: } else {
1502: Reply error = request.makeReply(HTTP.NOT_FOUND);
1503: error.setContent(msg);
1504: lr.setTarget(null);
1505: lr.setReply(error);
1506: return true;
1507: }
1508: } else if (!ls.isInternal()) {
1509: Request request = (Request) ls.getRequest();
1510: request.setState(STATE_CONTENT_LOCATION, "true");
1511: // return the index file.
1512: String indexes[] = getIndexes();
1513: if (indexes != null) {
1514: for (int i = 0; i < indexes.length; i++) {
1515: String index = indexes[i];
1516: if (index != null && index.length() > 0) {
1517: DirectoryResource dir = (DirectoryResource) resource;
1518: ResourceReference rr = dir.lookup(index);
1519: if (rr != null) {
1520: try {
1521: FramedResource rindex = (FramedResource) rr
1522: .lock();
1523: return rindex.lookup(ls, lr);
1524: } catch (InvalidResourceException ex) {
1525: } finally {
1526: rr.unlock();
1527: }
1528: }
1529: }
1530: }
1531: }
1532: }
1533: return true;
1534: }
1535: return false;
1536: }
1537:
1538: /**
1539: * Lookup the target resource when associated with a FileResource.
1540: * @param ls The current lookup state
1541: * @param lr The result
1542: * @return true if lookup is done.
1543: * @exception ProtocolException If an error relative to the protocol occurs
1544: */
1545: protected boolean lookupFile(LookupState ls, LookupResult lr)
1546: throws ProtocolException {
1547: return super .lookup(ls, lr);
1548: }
1549:
1550: /**
1551: * Lookup the target resource when associated with an unknown resource.
1552: * @param ls The current lookup state
1553: * @param lr The result
1554: * @return true if lookup is done.
1555: * @exception ProtocolException If an error relative to the protocol occurs
1556: */
1557: protected boolean lookupOther(LookupState ls, LookupResult lr)
1558: throws ProtocolException {
1559: return super .lookup(ls, lr);
1560: }
1561:
1562: /**
1563: * Check the request.
1564: * @param request the incomming request.
1565: * @return true if the request is an HTTP Request.
1566: */
1567: public boolean checkRequest(RequestInterface request) {
1568: return ((request == null) ? true
1569: : (request instanceof org.w3c.jigsaw.http.Request));
1570: }
1571:
1572: /**
1573: * Perform the request on all the frames of that resource. The
1574: * Reply returned is the first non-null reply.
1575: * @param request A RequestInterface instance.
1576: * @return A ReplyInterface instance.
1577: * @exception ProtocolException If an error relative to the protocol occurs
1578: * @exception ResourceException If an error not relative to the
1579: * protocol occurs
1580: */
1581: protected ReplyInterface performFrames(RequestInterface request)
1582: throws ProtocolException, ResourceException {
1583: return super .performFrames(request);
1584: }
1585:
1586: /**
1587: * Perform the request
1588: * @param req The request to handle.
1589: * @exception ProtocolException If processsing the request failed.
1590: * @exception ResourceException If the resource got a fatal error.
1591: */
1592:
1593: public ReplyInterface perform(RequestInterface req)
1594: throws ProtocolException, ResourceException {
1595: ReplyInterface repi = super .perform(req);
1596: if (repi != null)
1597: return repi;
1598:
1599: if (!checkRequest(req))
1600: return null;
1601:
1602: Reply reply = null;
1603: Request request = (Request) req;
1604: String method = request.getMethod();
1605: // Perform the request:
1606: if (method.equals("GET")) {
1607: reply = get(request);
1608: } else if (method.equals("HEAD")) {
1609: reply = head(request);
1610: } else if (method.equals("POST")) {
1611: reply = post(request);
1612: } else if (method.equals("PUT")) {
1613: reply = put(request);
1614: } else if (method.equals("OPTIONS")) {
1615: reply = options(request);
1616: } else if (method.equals("DELETE")) {
1617: reply = delete(request);
1618: } else if (method.equals("LINK")) {
1619: reply = link(request);
1620: } else if (method.equals("UNLINK")) {
1621: reply = unlink(request);
1622: } else if (method.equals("TRACE")) {
1623: reply = trace(request);
1624: } else {
1625: reply = extended(request);
1626: }
1627: return reply;
1628: }
1629:
1630: /**
1631: * The default GET method.
1632: * @param request The request to handle.
1633: * @exception ProtocolException If processsing the request failed.
1634: * @exception ResourceException If the resource got a fatal error.
1635: */
1636:
1637: public Reply get(Request request) throws ProtocolException,
1638: ResourceException {
1639: if (dresource != null) {
1640: // we manage a DirectoryResource
1641: return getDirectoryResource(request);
1642: } else if (fresource != null) {
1643: // we manage a FileResource
1644: return getFileResource(request);
1645: } else {
1646: return getOtherResource(request);
1647: }
1648: }
1649:
1650: /**
1651: * The default GET method for other king of associated resource
1652: * @param request The request to handle.
1653: * @exception ProtocolException If processsing the request failed.
1654: * @exception ResourceException If the resource got a fatal error.
1655: */
1656: protected Reply getOtherResource(Request request)
1657: throws ProtocolException, ResourceException {
1658: if (resource instanceof ContainerResource) {
1659: return getDirectoryResource(request);
1660: } else {
1661: // we don't manage this kind of resource
1662: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
1663: error.setContent("Method GET not implemented.");
1664: throw new HTTPException(error);
1665: }
1666: }
1667:
1668: /**
1669: * Create the reply relative to the given file.
1670: * @param request the incomming request.
1671: * @return A Reply instance
1672: * @exception ProtocolException If processsing the request failed.
1673: * @exception ResourceException If the resource got a fatal error.
1674: */
1675: protected Reply createFileReply(Request request)
1676: throws ProtocolException, ResourceException {
1677: File file = fresource.getFile();
1678: Reply reply = null;
1679: // Check for a range request:
1680: HttpRange ranges[] = request.getRange();
1681: if ((ranges != null) && (ranges.length == 1)) {
1682: Reply rangereply = handleRangeRequest(request, ranges[0]);
1683: if (rangereply != null)
1684: return rangereply;
1685: }
1686: // Default to full reply:
1687: reply = createDefaultReply(request, HTTP.OK);
1688: try {
1689: reply.setStream(new FileInputStream(file));
1690: } catch (IOException ex) {
1691: Reply error = request.makeReply(HTTP.SERVICE_UNAVAILABLE);
1692: error.setContent("Error while accessing filesystem");
1693: return error;
1694: }
1695: return reply;
1696: }
1697:
1698: /**
1699: * Alway throws an HTTPException
1700: */
1701: protected Reply deleteMe(Request request) throws HTTPException {
1702: // Delete the resource if parent is extensible:
1703: boolean shrinkable = false;
1704: ResourceReference rr = fresource.getParent();
1705: ResourceReference rrtemp = null;
1706: Resource p = null;
1707: while (true) {
1708: try {
1709: if (rr == null)
1710: break;
1711: p = rr.lock();
1712: if (p instanceof DirectoryResource) {
1713: shrinkable = ((DirectoryResource) p)
1714: .getShrinkableFlag();
1715: break;
1716: }
1717: rrtemp = p.getParent();
1718: } catch (InvalidResourceException ex) {
1719: break;
1720: } finally {
1721: if (rr != null)
1722: rr.unlock();
1723: }
1724: rr = rrtemp;
1725: }
1726: if (shrinkable) {
1727: // The resource is indexed but has no file, emit an error
1728: String msg = fresource.getFile()
1729: + ": deleted, removing the FileResource.";
1730: getServer().errlog(fresource, msg);
1731: try {
1732: fresource.delete();
1733: } catch (MultipleLockException ex) {
1734: Reply error = request.makeReply(HTTP.GONE);
1735: error.setContentMD5(null); // FIXME must compute it!
1736: error.setContent("<h1>Document Gone</h1>"
1737: + "<p>The document " + request.getURL()
1738: + " is indexed but no longer available.</p>"
1739: + "<p>" + ex.getMessage() + "</p>");
1740: throw new HTTPException(error);
1741: }
1742: }
1743: // Emit an error back:
1744: Reply error = request.makeReply(HTTP.GONE);
1745: error.setContentMD5(null);
1746: error.setContent("<h1>Document Gone</h1>" + "<p>The document "
1747: + request.getURL()
1748: + " is indexed but no longer available.</p>");
1749: return error;
1750: }
1751:
1752: /**
1753: * Get for FileResource
1754: * @param request the incomming request.
1755: * @return A Reply instance
1756: * @exception ProtocolException If processsing the request failed.
1757: * @exception ResourceException If the resource got a fatal error.
1758: */
1759: protected Reply getFileResource(Request request)
1760: throws ProtocolException, ResourceException {
1761: if (fresource == null)
1762: throw new ResourceException(
1763: "this frame is not attached to a "
1764: + "FileResource. ("
1765: + resource.getIdentifier() + ")");
1766: Reply reply = null;
1767: if (!checkExpect(request)) {
1768: reply = createDefaultReply(request, HTTP.EXPECTATION_FAILED);
1769: reply.setContent("The requested expectation could not be"
1770: + " met by the resource");
1771: return reply;
1772: }
1773: File file = fresource.getFile();
1774: fresource.checkContent();
1775: updateCachedHeaders();
1776: // Check validators:
1777: int cim = checkIfMatch(request);
1778: if ((cim == COND_FAILED) || (cim == COND_WEAK)) {
1779: reply = request.makeReply(HTTP.PRECONDITION_FAILED);
1780: reply.setContent("Pre-conditions failed.");
1781: reply.setContentMD5(null);
1782: return reply;
1783: }
1784: if (checkIfUnmodifiedSince(request) == COND_FAILED) {
1785: reply = request.makeReply(HTTP.PRECONDITION_FAILED);
1786: reply.setContent("Pre-conditions failed.");
1787: reply.setContentMD5(null);
1788: return reply;
1789: }
1790: if (checkValidators(request) == COND_FAILED) {
1791: reply = createDefaultReply(request, HTTP.NOT_MODIFIED);
1792: return reply;
1793: }
1794: // Does this file really exists, if so send it back
1795: if (file.exists()) {
1796: reply = createFileReply(request);
1797: if (request.hasState(STATE_CONTENT_LOCATION))
1798: reply.setContentLocation(getURL(request)
1799: .toExternalForm());
1800: return reply;
1801: } else {
1802: return deleteMe(request);
1803: }
1804: }
1805:
1806: /**
1807: * Perform a GET for the associated DirectoryResource.
1808: * @param request the incomming request.
1809: * @return A Reply instance.
1810: * @exception ProtocolException if request processing failed.
1811: * @exception ResourceException If the resource got a fatal error.
1812: */
1813: protected Reply getDirectoryResource(Request request)
1814: throws ProtocolException, ResourceException {
1815: if (!checkExpect(request)) {
1816: Reply reply = createDefaultReply(request,
1817: HTTP.EXPECTATION_FAILED);
1818: reply.setContent("The requested expectation could not be"
1819: + " met by the resource");
1820: return reply;
1821: }
1822: String index = getIndex();
1823: if (index != null && index.length() > 0) {
1824: if (index.equals("*forbid*")) {
1825: Reply rep = request.makeReply(HTTP.FORBIDDEN);
1826: rep.setContent("<h1>Forbidden</h1>"
1827: + "The directory resource " + request.getURL()
1828: + " cannot be browsed");
1829: return rep;
1830: }
1831: }
1832: return getDirectoryListing(request);
1833: }
1834:
1835: /**
1836: * The default HEAD method replies does a GET and removes entity.
1837: * @param request The request to handle.
1838: * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
1839: * error.
1840: * @exception ResourceException If the resource got a fatal error.
1841: */
1842:
1843: public Reply head(Request request) throws ProtocolException,
1844: ResourceException {
1845: if (dresource != null) {
1846: return headDirectoryResource(request);
1847: } else if (fresource != null) {
1848: return headFileResource(request);
1849: } else {
1850: return headOtherResource(request);
1851: }
1852: }
1853:
1854: /**
1855: * Perform a HEAD request for the associated resource.
1856: * @param request the incomming request.
1857: * @return A Reply instance
1858: * @exception ProtocolException If processsing the request failed.
1859: * @exception ResourceException If the resource got a fatal error.
1860: */
1861: protected Reply headOtherResource(Request request)
1862: throws ProtocolException, ResourceException {
1863: Reply reply = null;
1864: reply = getOtherResource(request);
1865: reply.setStream((InputStream) null);
1866: return reply;
1867: }
1868:
1869: /**
1870: * Perform a HEAD request for the associated DirectoryResource.
1871: * @param request the incomming request.
1872: * @return A Reply instance
1873: * @exception ProtocolException If processsing the request failed.
1874: * @exception ResourceException If the resource got a fatal error.
1875: */
1876: protected Reply headDirectoryResource(Request request)
1877: throws ProtocolException, ResourceException {
1878: Reply reply = null;
1879: reply = getDirectoryResource(request);
1880: reply.setStream((InputStream) null);
1881: return reply;
1882: }
1883:
1884: /**
1885: * Perform a HEAD request for the associated FileResource.
1886: * @param request the incomming request.
1887: * @return A Reply instance
1888: * @exception ProtocolException If processsing the request failed.
1889: * @exception ResourceException If the resource got a fatal error.
1890: */
1891: protected Reply headFileResource(Request request)
1892: throws ProtocolException, ResourceException {
1893: if (fresource == null)
1894: throw new ResourceException(
1895: "this frame is not attached to a "
1896: + "FileResource. ("
1897: + resource.getIdentifier() + ")");
1898: Reply reply = null;
1899: if (!checkExpect(request)) {
1900: reply = createDefaultReply(request, HTTP.EXPECTATION_FAILED);
1901: reply.setContent("The requested expectation could not be"
1902: + " met by the resource");
1903: return reply;
1904: }
1905: fresource.checkContent();
1906: updateCachedHeaders();
1907: // Conditional check:
1908: int cim = checkIfMatch(request);
1909: if ((cim == COND_FAILED) || (cim == COND_WEAK)) {
1910: Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
1911: r.setContent("Pre-conditions failed.");
1912: return r;
1913: }
1914: if (checkIfUnmodifiedSince(request) == COND_FAILED) {
1915: Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
1916: r.setContent("Pre-conditions failed.");
1917: return r;
1918: }
1919: if (checkValidators(request) == COND_FAILED) {
1920: return createDefaultReply(request, HTTP.NOT_MODIFIED);
1921: }
1922: if (!fresource.getFile().exists()) {
1923: return deleteMe(request);
1924: }
1925: reply = createDefaultReply(request, HTTP.OK);
1926: if (request.hasState(STATE_CONTENT_LOCATION))
1927: reply.setContentLocation(getURL(request).toExternalForm());
1928: return reply;
1929:
1930: }
1931:
1932: /**
1933: * The default POST method replies with a not implemented.
1934: * @param request The request to handle.
1935: * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
1936: * error.
1937: * @exception ResourceException If the resource got a fatal error.
1938: */
1939:
1940: public Reply post(Request request) throws ProtocolException,
1941: ResourceException {
1942: if (!checkExpect(request)) {
1943: Reply reply = createDefaultReply(request,
1944: HTTP.EXPECTATION_FAILED);
1945: reply.setContent("The requested expectation could not be"
1946: + " met by the resource");
1947: return reply;
1948: }
1949: Reply error = request.makeReply(HTTP.NOT_ALLOWED);
1950: error.setHeaderValue(Reply.H_ALLOW, getAllow());
1951: error.setContent("Method POST not allowed on this resource.");
1952: throw new HTTPException(error);
1953: }
1954:
1955: /**
1956: * The default PUT method.
1957: * @param request The request to handle.
1958: * @exception ProtocolException If processsing the request failed.
1959: * @exception ResourceException If the resource got a fatal error.
1960: */
1961:
1962: public Reply put(Request request) throws ProtocolException,
1963: ResourceException {
1964: if (fresource != null) {
1965: return putFileResource(request);
1966: } else {
1967: return putOtherResource(request);
1968: }
1969: }
1970:
1971: /**
1972: * Always throw a ProtocolException.
1973: * @param request The incmming request.
1974: * @return a Reply instance.
1975: * @exception ProtocolException (Always thrown).
1976: */
1977: protected Reply putOtherResource(Request request)
1978: throws ProtocolException {
1979: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
1980: error.setContent("Method PUT not implemented.");
1981: throw new HTTPException(error);
1982: }
1983:
1984: /**
1985: * Change the content of the associated FileResource.
1986: * @param request The incomming request.
1987: * @exception org.w3c.tools.resources.ProtocolException if a protocol
1988: * error occurs
1989: * @exception ResourceException If the resource got a fatal error.
1990: */
1991: protected Reply putFileResource(Request request)
1992: throws ProtocolException, ResourceException {
1993: Reply reply = null;
1994: int status = HTTP.OK;
1995:
1996: if (!checkExpect(request)) {
1997: reply = createDefaultReply(request, HTTP.EXPECTATION_FAILED);
1998: reply.setContent("The requested expectation could not be"
1999: + " met by the resource");
2000: return reply;
2001: }
2002: fresource.checkContent();
2003: updateCachedHeaders();
2004: // Is this resource writable ?
2005: if (!getPutableFlag()) {
2006: Reply error = request.makeReply(HTTP.NOT_ALLOWED);
2007: error.setHeaderValue(Reply.H_ALLOW, getAllow());
2008: error.setContent("Method PUT not allowed.");
2009: throw new HTTPException(error);
2010: }
2011: // Check validators:
2012: int cim = checkIfMatch(request);
2013: if ((cim == COND_FAILED) || (cim == COND_WEAK)
2014: || (checkIfNoneMatch(request) == COND_FAILED)
2015: || (checkIfModifiedSince(request) == COND_FAILED)
2016: || (checkIfUnmodifiedSince(request) == COND_FAILED)) {
2017: Reply r = request.makeReply(HTTP.PRECONDITION_FAILED);
2018: r.setContent("Pre-condition failed.");
2019: return r;
2020: }
2021: // Check the request:
2022: InputStream in = null;
2023: try {
2024: in = request.getInputStream();
2025: if (in == null) {
2026: Reply error = request.makeReply(HTTP.BAD_REQUEST);
2027: error
2028: .setContent("<p>Request doesn't have a valid content.");
2029: throw new HTTPException(error);
2030: }
2031: } catch (IOException ex) {
2032: throw new ClientException(request.getClient(), ex);
2033: }
2034: // We do not support (for the time being) put with ranges:
2035: if (request.hasContentRange()) {
2036: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2037: error.setContent("partial PUT not supported.");
2038: throw new HTTPException(error);
2039: }
2040: // REMOVED as it is impossile for clients to behave properly.
2041: // Of course it is fare more unsafe now, but it was too impractical IRL.
2042: // Check that if some type is provided it doesn't conflict:
2043: // if ( request.hasContentType() ) {
2044: // MimeType rtype = request.getContentType() ;
2045: // MimeType type = getContentType() ;
2046: // if ( type == null ) {
2047: // setValue (ATTR_CONTENT_TYPE, rtype) ;
2048: // } else if ( (rtype.match (type) < 0 ) && !rtype.equiv(type) ) {
2049: // if (debug) {
2050: // System.out.println("No match between: ["+
2051: // rtype.toString()+"] and ["+
2052: // type.toString()+"]");
2053: // }
2054: // Reply error = request.makeReply(HTTP.UNSUPPORTED_MEDIA_TYPE) ;
2055: // error.setContent ("<p>Invalid content type: "+rtype.toString()
2056: // + " is not matching resource MIME type: "
2057: // +type.toString());
2058: // throw new HTTPException (error) ;
2059: // }
2060: // }
2061: // Write the body back to the file:
2062: try {
2063: // We are about to accept the put, notify client before continuing
2064: Client client = request.getClient();
2065: if (client != null && request.getExpect() != null) {
2066: // FIXME we should check for "100-continue" explicitely
2067: client.sendContinue();
2068: }
2069: if (fresource.newContent(request.getInputStream()))
2070: status = HTTP.CREATED;
2071: else
2072: status = HTTP.NO_CONTENT;
2073: } catch (IOException ex) {
2074: if (debug)
2075: ex.printStackTrace();
2076: // so we have a problem replacing or creating the content
2077: // it is then a configuration problem (access right for the
2078: // underlying fle resource for example...
2079: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
2080: error.setReason("File Access Error");
2081: error.setContent("<p>Unable to save " + request.getURL()
2082: + " due to IO problems");
2083: throw new HTTPException(error);
2084: }
2085: if (status == HTTP.CREATED) {
2086: reply = request.makeReply(status);
2087: reply.setContent("<P>Resource succesfully created");
2088: if (request.hasState(STATE_CONTENT_LOCATION))
2089: reply.setContentLocation(getURL(request)
2090: .toExternalForm());
2091: // Henrik's fix, create the Etag on 201
2092: if (fresource != null) {
2093: // We only take car eof etag here:
2094: if (etag == null) {
2095: reply.setETag(getETag());
2096: }
2097: }
2098: reply.setLocation(getURL(request));
2099: reply.setContent("<p>Entity body saved succesfully !");
2100: } else {
2101: reply = createDefaultReply(request, status);
2102: }
2103: return reply;
2104: }
2105:
2106: /**
2107: * The default OPTIONS method replies with a not implemented.
2108: * @param request The request to handle.
2109: * @exception ProtocolException In case of errors.
2110: * @exception ResourceException If the resource got a fatal error.
2111: */
2112:
2113: public Reply options(Request request) throws ProtocolException,
2114: ResourceException {
2115: if (!checkExpect(request)) {
2116: Reply reply = createDefaultReply(request,
2117: HTTP.EXPECTATION_FAILED);
2118: reply.setContent("The requested expectation could not be"
2119: + " met by the resource");
2120: return reply;
2121: }
2122: Reply reply = createDefaultReply(request, HTTP.OK);
2123: // set size to 0 according to rfc2616 9.2
2124: reply.setContentLength(0);
2125: // Removed unused headers:
2126: reply.setContentType(null);
2127: // Add the allow header:
2128: reply.setHeaderValue(Reply.H_ALLOW, getAllow());
2129: return reply;
2130: }
2131:
2132: /**
2133: * The default DELETE method, actually the resource (file, directory)
2134: * is moved into the trash directory which is not accessible via HTTP.
2135: * @param request The request to handle.
2136: * @exception ProtocolException If processsing the request failed.
2137: * @exception ResourceException If the resource got a fatal error.
2138: */
2139:
2140: public Reply delete(Request request) throws ProtocolException,
2141: ResourceException {
2142: if (getAllowDeleteFlag()) {
2143: if (dresource != null) {
2144: // we manage a DirectoryResource
2145: return deleteDirectoryResource(request);
2146: } else if (fresource != null) {
2147: // we manage a FileResource
2148: return deleteFileResource(request);
2149: } else {
2150: return deleteOtherResource(request);
2151: }
2152: } else {
2153: Reply error = request.makeReply(HTTP.NOT_ALLOWED);
2154: error.setContent("Method DELETE not allowed.");
2155: error.setHeaderValue(Reply.H_ALLOW, getAllow());
2156: throw new HTTPException(error);
2157: }
2158: }
2159:
2160: protected File computeTrashFile(File file) {
2161: File trashdir = new File(getServer().getTrashDirectory());
2162: if (!trashdir.exists()) {
2163: trashdir.mkdirs();
2164: }
2165: String filename = file.getName();
2166: File trashfile = new File(trashdir, filename);
2167: int nb = 1;
2168: while (trashfile.exists()) {
2169: trashfile = new File(trashdir, filename + "." + nb);
2170: nb++;
2171: }
2172: return trashfile;
2173: }
2174:
2175: protected File computeTrashDir(File dir) {
2176: return computeTrashFile(dir);
2177: }
2178:
2179: /**
2180: * Perform a DELETE for the associated DirectoryResource.
2181: * @param request the incomming request.
2182: * @return A Reply instance.
2183: * @exception ProtocolException if request processing failed.
2184: * @exception ResourceException If the resource got a fatal error.
2185: */
2186: protected Reply deleteDirectoryResource(Request request)
2187: throws ProtocolException, ResourceException {
2188: if (!checkExpect(request)) {
2189: Reply reply = createDefaultReply(request,
2190: HTTP.EXPECTATION_FAILED);
2191: reply.setContent("The requested expectation could not be"
2192: + " met by the resource");
2193: return reply;
2194: }
2195: File olddir = dresource.getDirectory();
2196: File newdir = computeTrashDir(olddir);
2197: try {
2198: //delete the FileResource
2199: dresource.delete();
2200: } catch (MultipleLockException ex) {
2201: Reply error = request.makeReply(HTTP.FORBIDDEN);
2202: error.setContent("Can't delete resource: "
2203: + resource.getIdentifier()
2204: + " is locked. Try again later.");
2205: throw new HTTPException(error);
2206: }
2207: olddir.renameTo(newdir);
2208: return request.makeReply(HTTP.NO_CONTENT);
2209: }
2210:
2211: /**
2212: * Perform a DELETE for the associated FileResource.
2213: * @param request the incomming request.
2214: * @return A Reply instance.
2215: * @exception ProtocolException if request processing failed.
2216: * @exception ResourceException If the resource got a fatal error.
2217: */
2218: protected Reply deleteFileResource(Request request)
2219: throws ProtocolException, ResourceException {
2220: if (!checkExpect(request)) {
2221: Reply reply = createDefaultReply(request,
2222: HTTP.EXPECTATION_FAILED);
2223: reply.setContent("The requested expectation could not be"
2224: + " met by the resource");
2225: return reply;
2226: }
2227: File oldfile = fresource.getFile();
2228: File newfile = computeTrashFile(oldfile);
2229: try {
2230: //delete the FileResource
2231: fresource.delete();
2232: } catch (MultipleLockException ex) {
2233: Reply error = request.makeReply(HTTP.FORBIDDEN);
2234: error.setContent("Can't delete resource: "
2235: + resource.getIdentifier()
2236: + " is locked. Try again later.");
2237: throw new HTTPException(error);
2238: }
2239: oldfile.renameTo(newfile);
2240: return request.makeReply(HTTP.NO_CONTENT);
2241: }
2242:
2243: /**
2244: * Perform a DELETE for the associated resource.
2245: * @param request the incomming request.
2246: * @return A Reply instance.
2247: * @exception ProtocolException if request processing failed.
2248: * @exception ResourceException If the resource got a fatal error.
2249: */
2250: protected Reply deleteOtherResource(Request request)
2251: throws ProtocolException, ResourceException {
2252: // we don't manage this kind of resource
2253: if (!checkExpect(request)) {
2254: Reply reply = createDefaultReply(request,
2255: HTTP.EXPECTATION_FAILED);
2256: reply.setContent("The requested expectation could not be"
2257: + " met by the resource");
2258: return reply;
2259: }
2260: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2261: error.setContent("Method DELETE not implemented.");
2262: throw new HTTPException(error);
2263: }
2264:
2265: /**
2266: * The default LINK method replies with a not implemented.
2267: * @param request The request to handle.
2268: * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
2269: * error.
2270: * @exception ResourceException If the resource got a fatal error.
2271: */
2272:
2273: public Reply link(Request request) throws ProtocolException,
2274: ResourceException {
2275: if (!checkExpect(request)) {
2276: Reply reply = createDefaultReply(request,
2277: HTTP.EXPECTATION_FAILED);
2278: reply.setContent("The requested expectation could not be"
2279: + " met by the resource");
2280: return reply;
2281: }
2282: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2283: error.setContent("Method LINK not implemented.");
2284: throw new HTTPException(error);
2285: }
2286:
2287: /**
2288: * The default UNLINK method replies with a not implemented.
2289: * @param request The request to handle.
2290: * @exception ProtocolException Always thrown, to return a NOT_IMPLEMENTED
2291: * error.
2292: * @exception ResourceException If the resource got a fatal error.
2293: */
2294:
2295: public Reply unlink(Request request) throws ProtocolException,
2296: ResourceException {
2297: if (!checkExpect(request)) {
2298: Reply reply = createDefaultReply(request,
2299: HTTP.EXPECTATION_FAILED);
2300: reply.setContent("The requested expectation could not be"
2301: + " met by the resource");
2302: return reply;
2303: }
2304: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2305: error.setContent("Method UNLINK not implemented.");
2306: throw new HTTPException(error);
2307: }
2308:
2309: /**
2310: * The default TRACE method replies with a not implemented
2311: * @param request The request to handle.
2312: * @exception HTTPException In case of errors.
2313: * @exception ClientException If the client instance controling the
2314: * request processing got a fatal error.
2315: */
2316:
2317: public Reply trace(Request request) throws HTTPException,
2318: ClientException {
2319: Reply reply = null;
2320: if (!checkExpect(request)) {
2321: reply = createDefaultReply(request, HTTP.EXPECTATION_FAILED);
2322: reply.setContent("The requested expectation could not be"
2323: + " met by the resource");
2324: return reply;
2325: }
2326: reply = createDefaultReply(request, HTTP.OK);
2327: reply.setNoCache(); // don't cache this
2328: reply.setMaxAge(-1); //
2329: reply.setContentMD5(null);
2330: // Dump the request as the body
2331: // Removed unused headers:
2332: // FIXME should be something else for chuncked stream
2333: ByteArrayOutputStream ba = new ByteArrayOutputStream();
2334: try {
2335: reply.setContentType(new MimeType("message/http"));
2336: request.dump(ba);
2337: reply.setContentLength(ba.size());
2338: } catch (Exception ex) {
2339: ex.printStackTrace();
2340: }
2341: reply.setStream(new ByteArrayInputStream(ba.toByteArray()));
2342: return reply;
2343: }
2344:
2345: public Reply extended(Request request) throws ProtocolException,
2346: ResourceException {
2347: String method = request.getMethod();
2348: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2349: error.setContent("Method " + method + " not implemented.");
2350: throw new HTTPException(error);
2351: }
2352:
2353: /**
2354: * The Browse Mime type.
2355: */
2356: protected static MimeType browsetype = null;
2357:
2358: /**
2359: * Get the Browse Mime type.
2360: */
2361:
2362: protected synchronized MimeType getBrowseType() {
2363: if (browsetype == null) {
2364: try {
2365: browsetype = new MimeType("application/x-navibrowse");
2366: } catch (Exception ex) {
2367: ex.printStackTrace();
2368: }
2369: }
2370: return browsetype;
2371: }
2372:
2373: /**
2374: * A present to GNNPress users !
2375: * This method implements the <code>BROWSE</code> method that
2376: * AOL press (or GNN press, or whatever its last name is) expects.
2377: * @param request The request to process.
2378: * @exception ProtocolException If some error occurs.
2379: * @return A Reply instance.
2380: */
2381:
2382: public Reply browse(Request request) throws ProtocolException {
2383: if (dresource == null) {
2384: Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED);
2385: error.setContent("Method " + request.getMethod()
2386: + " not implemented.");
2387: throw new HTTPException(error);
2388: }
2389:
2390: Enumeration e = dresource.enumerateResourceIdentifiers();
2391: Vector resources = Sorter.sortStringEnumeration(e);
2392: int rsize = ((resources == null) ? 0 : resources.size());
2393: StringBuffer sb = new StringBuffer();
2394:
2395: // As we have enumerated all resources, just looking the store is ok
2396: for (int i = 0; i < rsize; i++) {
2397: String rname = (String) resources.elementAt(i);
2398: ResourceReference rr = null;
2399: Resource r = null;
2400: try {
2401: rr = dresource.lookup(rname);
2402: r = rr.lock();
2403: // may throw InvalidResourceException
2404:
2405: if (r instanceof DirectoryResource) {
2406: sb
2407: .append("application/x-navidir " + rname
2408: + "\r\n");
2409: } else {
2410: HTTPFrame itsframe = (HTTPFrame) resource
2411: .getFrame(getClass());
2412: if (itsframe != null) {
2413: sb.append(itsframe.getContentType().toString()
2414: + " " + rname + "\r\n");
2415: }
2416: }
2417: } catch (InvalidResourceException ex) {
2418: continue;
2419: } finally {
2420: rr.unlock();
2421: }
2422: }
2423: Reply reply = request.makeReply(HTTP.OK);
2424: reply.addPragma("no-cache");
2425: reply.setNoCache();
2426: reply.setContent(sb.toString());
2427: reply.setContentType(getBrowseType());
2428: return reply;
2429: }
2430:
2431: /**
2432: * Set the values. (MUST be called before initialize).
2433: * @param defs The Hashtable containing the values.
2434: */
2435: public void pickleValues(Hashtable defs) {
2436: Object nvalues[] = new Object[attributes.length];
2437: for (int i = 0; i < nvalues.length; i++) {
2438: String attrname = attributes[i].getName();
2439: Object def = defs.get(attrname);
2440: // note that protocol frames have usually names in the
2441: // frame-XX serie, so it is valid to save (big) on this.
2442: if ((def != null)
2443: && ((i == ATTR_HELP_URL) || (i == ATTR_IDENTIFIER)
2444: || (i == ATTR_TITLE) || (i == ATTR_CHARSET)
2445: || (i == ATTR_CONTENT_LANGUAGE)
2446: || (i == ATTR_CONTENT_ENCODING)
2447: || (i == ATTR_ICON) || (i == ATTR_INDEX)
2448: || (i == ATTR_ICONDIR) || (i == ATTR_STYLE_LINK))
2449: && (def instanceof String)) {
2450: nvalues[i] = ((String) def).intern();
2451: } else {
2452: nvalues[i] = def;
2453: }
2454: }
2455: this .values = nvalues;
2456: }
2457:
2458: /**
2459: * Initialization method for attribute holders.
2460: * This method allows to initialize an attribute holder by providing
2461: * its attributes values through a Hashtable mapping attribute names
2462: * to attribute values.
2463: * @param defs The Hashtable containing the default values.
2464: */
2465:
2466: public void initialize(Hashtable defs) {
2467: Object values[] = ((this .values == null) ? new Object[attributes.length]
2468: : this .values);
2469: for (int i = 0; i < values.length; i++) {
2470: String attrname = attributes[i].getName();
2471: Object def = defs.get(attrname);
2472: if (values[i] == null) {
2473: // for help_url, we can save lots of space by using
2474: // String.intern()
2475: // Those attribute are not, in practise, very variables
2476: // So it is a valid optimisation to intern them.
2477: if ((def != null)
2478: && ((i == ATTR_HELP_URL)
2479: || (i == ATTR_IDENTIFIER)
2480: || (i == ATTR_TITLE)
2481: || (i == ATTR_CHARSET)
2482: || (i == ATTR_CONTENT_LANGUAGE)
2483: || (i == ATTR_CONTENT_ENCODING)
2484: || (i == ATTR_ICON)
2485: || (i == ATTR_INDEX)
2486: || (i == ATTR_ICONDIR) || (i == ATTR_STYLE_LINK))
2487: && (def instanceof String)) {
2488: values[i] = ((String) def).intern();
2489: } else {
2490: values[i] = def;
2491: }
2492: }
2493: }
2494: initialize(values);
2495: }
2496: }
|