/* ========================================================================== *
* Copyright (C) 2004-2005 Pier Fumagalli <http://www.betaversion.org/~pier/> *
* All rights reserved. *
* ========================================================================== *
* *
* Licensed under the Apache License, Version 2.0 (the "License"). You may *
* not use this file except in compliance with the License. You may obtain a *
* copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>. *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
* License for the specific language governing permissions and limitations *
* under the License. *
* *
* ========================================================================== */
package com.adito.vfs.webdav.methods;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.RandomAccessContent;
import org.apache.commons.vfs.util.RandomAccessMode;
import com.adito.boot.SystemProperties;
import com.adito.boot.Util;
import com.adito.vfs.VFSLockManager;
import com.adito.vfs.VFSResource;
import com.adito.vfs.webdav.DAVTransaction;
import com.adito.vfs.webdav.LockedException;
/**
* <p>
* <a href="http://www.rfc-editor.org/rfc/rfc2616.txt">HTTP</a>
* <code>GET</code> metohd implementation.
* </p>
*
* @author <a href="/">Pier Fumagalli</a>
*/
public class GET extends HEAD {
static Log log = LogFactory.getLog(GET.class);
/**
* <p>
* The mime type this method will return for collections.
* </p>
*/
public static final String COLLECTION_MIME_TYPE = "text/html";
/**
* <p>
* Create a new {@link GET} instance.
* </p>
*/
public GET() {
super();
}
/**
* <p>
* Process the <code>GET</code> method.
* </p>
*/
public void process(DAVTransaction transaction, VFSResource resource) throws LockedException, IOException {
String handle = VFSLockManager.getNewHandle();
VFSLockManager.getInstance().lock(resource, transaction.getSessionInfo(), false, true, handle);
try {
doHead(transaction, resource);
try {
if (resource.isCollection() || resource.isMount()) {
if(SystemProperties.get("adito.disableFolderBrowsing", "true").equals("true") && !resource.isBrowsable()) {
transaction.getResponse().sendError(404);
return;
}
String mime = COLLECTION_MIME_TYPE + "; charset=\"utf-8\"";
transaction.setContentType(mime);
Util.noCache(transaction.getResponse());
PrintWriter out = transaction.write("utf-8");
String path = resource.getFullPath();
out.println("<html>");
out.println("<head>");
out.println("<title>Collection: " + path + "</title>");
out.println("</head>");
out.println("<body>");
out.println("<h2>Collection: " + path + "</h2>");
out.println("<table>");
out.println("<thead>");
out.println("<tr>");
out.println("<td>Name</td>");
out.println("<td>Type</td>");
out.println("<td>Size</td>");
out.println("<td>Date</td>");
out.println("</tr>");
out.println("</thead>");
out.println("<tbody>");
/* Process the parent */
VFSResource parent = resource.getParent();
if (parent != null && (parent.isBrowsable() || !SystemProperties.get("adito.disableFolderBrowsing", "true").equals("true"))) {
out.println("<tr>");
out.print("<td><li><a href=\"..\">../</a></td>");
out.println("<td>Dir</td><td>");
try {
out.println(parent.getFile().getContent().getSize());
}
catch(Exception e) {
}
out.println("</td><td>");
try {
out.println(SimpleDateFormat.getDateTimeInstance().format(new Date(parent.getFile().getContent().getLastModifiedTime())));
}
catch(Exception e) {
}
out.println("</td></tr>");
}
/* Process the children */
Iterator iterator = resource.getChildren();
if (iterator != null) {
while (iterator.hasNext()) {
VFSResource child = (VFSResource) iterator.next();
String childPath = child.getDisplayName();
out.println("<tr>");
out.print("<td><li><a href=\"" + child.getWebFolderPath() + "\">" + childPath + "</a></td>");
if(child.isCollection())
out.println("<td>Dir</td><td>");
else if(child.isResource())
out.println("<td>Resource</td><td>");
else
out.println("<td>Unknown</td><td>");
try {
out.println(child.getFile().getContent().getSize());
}
catch(Exception e) {
}
out.println("</td><td>");
try {
out.println(SimpleDateFormat.getDateTimeInstance().format(new Date(child.getFile().getContent().getLastModifiedTime())));
}
catch(Exception e) {
}
out.println("</td></tr>");
out.println("</tr>");
}
}
out.println("</tbody>");
out.println("</table>");
out.println("</html>");
out.flush();
if(resource.getMount() != null) {
resource.getMount().resourceAccessList(resource, transaction, null);
}
return;
}
} catch (Exception e) {
if(resource.getMount() != null) {
resource.getMount().resourceAccessList(resource, transaction, e);
}
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
int total = 0;
try {
Range[] ranges = null;
try {
ranges = processRangeHeader(transaction, resource);
} catch(IOException ex) {
// Invalid range means send full entity with 200 OK.
ranges = null;
}
transaction.setHeader("Content-Type", resource.getContentType());
resource.getMount().resourceAccessDownloading(resource, transaction);
OutputStream out = transaction.getOutputStream();
byte buffer[] = new byte[32768];
InputStream in = null;
try {
if(ranges == null || ranges.length > 1 /* We dont support multiple ranges yet */) {
/* Processing a normal resource request */
in = resource.getInputStream();
int k;
while ((k = in.read(buffer)) != -1) {
out.write(buffer, 0, k);
total += k;
}
} else {
/* Process a single range */
RandomAccessContent content = resource.getFile().getContent().getRandomAccessContent(RandomAccessMode.READ);
content.seek(ranges[0].startPosition);
in = content.getInputStream();
long count = ranges[0].count;
int k;
while(count > 0) {
while ((k = in.read(buffer, 0, (int) (count < buffer.length ? count : buffer.length))) > 0) {
out.write(buffer, 0, k);
total += k;
count -= k;
}
}
transaction.setHeader("Content-Range", "bytes " + ranges[0].startPosition
+ "-" + ranges[0].startPosition
+ ranges[0].count + "/" + resource.getFile().getContent().getSize());
transaction.setStatus(206 /* Partial */);
}
}
finally {
Util.closeStream(in);
}
resource.getMount().resourceAccessDownloadComplete(resource, transaction, null);
} catch (IOException ioe) {
resource.getMount().resourceAccessDownloadComplete(resource, transaction, ioe);
throw ioe;
}
} finally {
VFSLockManager.getInstance().unlock(transaction.getSessionInfo(), handle);
}
}
private Range[] processRangeHeader(DAVTransaction transaction, VFSResource resource) throws IOException {
try {
if(transaction.getRequest().getHeader("Range")!=null) {
String header = transaction.getRequest().getHeader("Range").toLowerCase();
if(header.startsWith("bytes=")) {
Vector v = new Vector();
StringTokenizer tokens = new StringTokenizer(header.substring(6), ",");
while(tokens.hasMoreTokens()) {
String r = tokens.nextToken();
if(log.isDebugEnabled())
log.debug("Processing byte range " + r);
int idx = r.indexOf('-');
String startPoint = r.substring(0, idx);
String endPoint = r.substring(idx+1);
Range newRange = new Range();
if("".equals(startPoint) && !"".equals(endPoint)) {
newRange.count = Long.parseLong(endPoint);
newRange.startPosition = resource.getFile().getContent().getSize() - newRange.count;
v.add(newRange);
} else if(!"".equals(startPoint) && "".equals(endPoint)) {
newRange.startPosition = Long.parseLong(startPoint);
newRange.count = resource.getFile().getContent().getSize() - newRange.startPosition;
v.add(newRange);
} else if(!"".equals(startPoint) && !"".equals(endPoint)) {
newRange.startPosition = Long.parseLong(startPoint);
newRange.count = Long.parseLong(endPoint) - newRange.startPosition;
v.add(newRange);
} else {
log.error("Unsupported byte range element: " + r);
}
}
if(v.size() > 0) {
return (Range[]) v.toArray(new Range[0]);
}
}
}
} catch (Throwable t) {
log.error("Failed to process byte range header " + transaction.getRequest().getHeader("Range"), t);
throw new IOException("Invalid range");
}
return null;
}
class Range {
long startPosition;
long count;
}
}