All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.http.webdav.WebDavProtocol Maven / Gradle / Ivy

Go to download

Milton Community Edition: Supports DAV level 1 and is available on Apache2 license

There is a newer version: 4.0.3.2215
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 io.milton.http.webdav;

import io.milton.common.NameSpace;
import io.milton.http.*;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.exceptions.NotFoundException;
import io.milton.http.http11.CustomPostHandler;
import io.milton.http.http11.ETagGenerator;
import io.milton.http.quota.QuotaDataAccessor;
import io.milton.http.report.QualifiedReport;
import io.milton.http.report.Report;
import io.milton.http.report.ReportHandler;
import io.milton.http.values.SupportedReportSetList;
import io.milton.http.values.ValueWriters;
import io.milton.http.webdav.PropertyMap.StandardProperty;
import io.milton.http.webdav.PropertyMap.WritableStandardProperty;
import io.milton.property.PropertyAuthoriser;
import io.milton.property.PropertySource;
import io.milton.resource.CollectionResource;
import io.milton.resource.DisplayNameResource;
import io.milton.resource.GetableResource;
import io.milton.resource.PropFindableResource;
import io.milton.resource.PutableResource;
import io.milton.resource.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Defines the methods and properties that make up the webdav protocol.
 *
 * We've been a little pragmatic about what the webdav protocol actually is. It
 * generally doesnt include things defined in subsequent protocols (RFC's), but
 * where something is frequently used by other protocols (like REPORT) or is
 * very tightly couple with normal webdav operations (like quota checking)
 * you'll find it here
 *
 *
 * @author brad
 */
public class WebDavProtocol implements HttpExtension, PropertySource {

	private static final Logger log = LoggerFactory.getLogger(WebDavProtocol.class);
	public static final String DAV_URI = "DAV:";
	public static final String DAV_PREFIX = "d";
	public static final NameSpace NS_DAV = new NameSpace(DAV_URI, DAV_PREFIX);
	private final Set handlers;
	private final Map reports;
	private final ResourceTypeHelper resourceTypeHelper;
	private final QuotaDataAccessor quotaDataAccessor;
	private final PropertyMap propertyMap;
	private final List propertySources;
	private final ETagGenerator eTagGenerator;
	private final HandlerHelper handlerHelper;
	private final UserAgentHelper userAgentHelper;
	private final DisplayNameFormatter displayNameFormatter;
	private final MkColHandler mkColHandler;
	private final PropPatchHandler propPatchHandler;
	private List customPostHandlers;

	public WebDavProtocol(HandlerHelper handlerHelper, ResourceTypeHelper resourceTypeHelper, WebDavResponseHandler responseHandler, List propertySources, QuotaDataAccessor quotaDataAccessor, PropPatchSetter patchSetter, PropertyAuthoriser propertyAuthoriser, ETagGenerator eTagGenerator, UrlAdapter urlAdapter, ResourceHandlerHelper resourceHandlerHelper, UserAgentHelper userAgentHelper, PropFindRequestFieldParser requestFieldParser, PropFindPropertyBuilder propertyBuilder, DisplayNameFormatter displayNameFormatter, boolean enableTextContentProperty) {
		this.displayNameFormatter = displayNameFormatter;
		this.userAgentHelper = userAgentHelper;
		this.handlerHelper = handlerHelper;
		this.eTagGenerator = eTagGenerator;
		handlers = new HashSet<>();
		this.resourceTypeHelper = resourceTypeHelper;
		this.quotaDataAccessor = quotaDataAccessor;
		this.propertyMap = new PropertyMap(WebDavProtocol.NS_DAV.getName());

		propertyMap.add(new ContentLengthPropertyWriter());
		propertyMap.add(new ContentTypePropertyWriter());
		propertyMap.add(new CreationDatePropertyWriter("getcreated"));
		propertyMap.add(new CreationDatePropertyWriter("creationdate"));
		propertyMap.add(new DisplayNamePropertyWriter());
		propertyMap.add(new LastModifiedDatePropertyWriter());
		propertyMap.add(new ResourceTypePropertyWriter());
		propertyMap.add(new EtagPropertyWriter());

		propertyMap.add(new MSIsCollectionPropertyWriter());
		propertyMap.add(new MSIsReadOnlyPropertyWriter());
		propertyMap.add(new MSNamePropertyWriter());

		log.info("resourceTypeHelper: " + resourceTypeHelper.getClass());
		if (quotaDataAccessor == null) {
			log.info("no quota data");
		} else {
			log.info("quotaDataAccessor: " + quotaDataAccessor.getClass());
			propertyMap.add(new QuotaAvailableBytesPropertyWriter());
			propertyMap.add(new QuotaUsedBytesPropertyWriter());
		}

		propertyMap.add(new SupportedReportSetProperty());
		if (enableTextContentProperty) {
			propertyMap.add(new MiltonExtTextContentProperty());
		}

		// note valuewriters is also used in DefaultWebDavResponseHandler
		// if using non-default configuration you should inject the same instance into there
		// and here
		ValueWriters valueWriters = new ValueWriters();

		if (propertySources == null) {
			propertySources = new ArrayList<>();
		}
		log.debug("provided property sources: " + propertySources.size());
		this.propertySources = propertySources;

		log.debug("adding webdav as a property source to: " + this.propertySources.getClass() + " hashCode: " + this.propertySources.hashCode());
		addPropertySource(this);
		if (patchSetter == null) {
			log.info("creating default patcheSetter: " + PropertySourcePatchSetter.class);
			patchSetter = new PropertySourcePatchSetter(propertySources, valueWriters);
		}
		//handlers.add(new PropFindHandler(resourceHandlerHelper, resourceTypeHelper, responseHandler, propertySources));
		PropFindHandler propFindHandler = new PropFindHandler(resourceHandlerHelper, requestFieldParser, responseHandler, propertyBuilder);
		handlers.add(propFindHandler);
		mkColHandler = new MkColHandler(responseHandler, handlerHelper);
		handlers.add(mkColHandler);
		propPatchHandler = new PropPatchHandler(resourceHandlerHelper, new DefaultPropPatchParser(), patchSetter, responseHandler, propertyAuthoriser);
		handlers.add(propPatchHandler);
		handlers.add(new CopyHandler(responseHandler, handlerHelper, resourceHandlerHelper, userAgentHelper));
		handlers.add(new MoveHandler(responseHandler, handlerHelper, resourceHandlerHelper, userAgentHelper));

		// Reports are added by other protocols via addReport
		reports = new HashMap<>();
		handlers.add(new ReportHandler(responseHandler, resourceHandlerHelper, reports));
	}

	@Override
	public List getCustomPostHandlers() {
		return customPostHandlers;
	}

	public void setCustomPostHandlers(List customPostHandlers) {
		this.customPostHandlers = customPostHandlers;
	}

	public List getPropertySources() {
		return Collections.unmodifiableList(propertySources);
	}

	public void addPropertySource(PropertySource ps) {
		propertySources.add(ps);
		log.debug("adding property source: " + ps.getClass() + " new size: " + propertySources.size());
	}

	public void addReport(Report report) {
		this.reports.put(report.getName(), report);
	}

	@Override
	public Set getHandlers() {
		return Collections.unmodifiableSet(handlers);
	}

	@Override
	public Object getProperty(QName name, Resource r) {
		return propertyMap.getProperty(name, r);
	}

	@Override
	public void setProperty(QName name, Object value, Resource r) {
		throw new UnsupportedOperationException("Not supported. Standard webdav properties are not writable");
	}

	@Override
	public PropertyMetaData getPropertyMetaData(QName name, Resource r) {
		PropertyMetaData propertyMetaData = propertyMap.getPropertyMetaData(name, r);
		if (propertyMetaData != null) {
			// Nautilus (at least on Ubuntu 12) doesnt like empty properties
			if (userAgentHelper.isNautilus(HttpManager.request())) {
				Object v = getProperty(name, r);
				if (v == null) {
					return PropertyMetaData.UNKNOWN;
				} else if (v instanceof String) {
					String s = (String) v;
					if (s.trim().length() == 0) {
						return PropertyMetaData.UNKNOWN;
					}
				}
			}
		}

		return propertyMetaData;
	}

	@Override
	public void clearProperty(QName name, Resource r) {
		throw new UnsupportedOperationException("Not supported. Standard webdav properties are not writable");
	}

	@Override
	public List getAllPropertyNames(Resource r) {
		return propertyMap.getAllPropertyNames(r);
	}

	/**
	 * Generates the displayname element text. By default is a
	 * CdataDisplayNameFormatter wrapping a DefaultDisplayNameFormatter so that
	 * extended character sets are supported
	 *
	 * @return
	 */
	public DisplayNameFormatter getDisplayNameFormatter() {
		return displayNameFormatter;
	}

	class DisplayNamePropertyWriter implements WritableStandardProperty {

		@Override
		public String getValue(PropFindableResource res) {
			if( res instanceof DisplayNameResource) {
				DisplayNameResource dnr = (DisplayNameResource) res;
				return dnr.getDisplayName();
			}
			return displayNameFormatter.formatDisplayName(res);
		}

		@Override
		public String fieldName() {
			return "displayname";
		}

		@Override
		public Class getValueClass() {
			return String.class;
		}

		@Override
		public void setValue(PropFindableResource res, String value) {
			if( res instanceof DisplayNameResource) {
				DisplayNameResource dnr = (DisplayNameResource) res;
				dnr.setDisplayName(value);
			} else {
				log.warn("Attempt to set displayname property, but resource is not compatible: " + res.getClass());
			}
		}
	}

	static class CreationDatePropertyWriter implements StandardProperty {

		private final String fieldName;

		public CreationDatePropertyWriter(String fieldName) {
			this.fieldName = fieldName;
		}

		@Override
		public String fieldName() {
			return fieldName;
		}

		@Override
		public Date getValue(PropFindableResource res) {
			// BM: was getModifiedDate(), presume that was wrong??
			return res.getCreateDate();
		}

		@Override
		public Class getValueClass() {
			return Date.class;
		}
	}

	static class LastModifiedDatePropertyWriter implements StandardProperty {

		@Override
		public String fieldName() {
			return "getlastmodified";
		}

		@Override
		public Date getValue(PropFindableResource res) {
			return res.getModifiedDate();
		}

		@Override
		public Class getValueClass() {
			return Date.class;
		}
	}

	class ResourceTypePropertyWriter implements StandardProperty> {

		@Override
		public List getValue(PropFindableResource res) {
			return resourceTypeHelper.getResourceTypes(res);
		}

		@Override
		public String fieldName() {
			return "resourcetype";
		}

		@Override
		public Class getValueClass() {
			return List.class;
		}
	}

	static class ContentTypePropertyWriter implements StandardProperty {

		@Override
		public String getValue(PropFindableResource res) {
			if (res instanceof GetableResource) {
				GetableResource getable = (GetableResource) res;
				return getable.getContentType(null);
			} else {
				return "";
			}
		}

		@Override
		public String fieldName() {
			return "getcontenttype";
		}

		@Override
		public Class getValueClass() {
			return String.class;
		}
	}

	static class ContentLengthPropertyWriter implements StandardProperty {

		@Override
		public Long getValue(PropFindableResource res) {
			if (res instanceof GetableResource) {
				GetableResource getable = (GetableResource) res;
				return getable.getContentLength();
			} else {
				return null;
			}
		}

		@Override
		public String fieldName() {
			return "getcontentlength";
		}

		@Override
		public Class getValueClass() {
			return Long.class;
		}
	}

	class QuotaUsedBytesPropertyWriter implements StandardProperty {

		@Override
		public Long getValue(PropFindableResource res) {
			if (quotaDataAccessor != null) {
				return quotaDataAccessor.getQuotaUsed(res);
			} else {
				return null;
			}
		}

		@Override
		public String fieldName() {
			return "quota-used-bytes";
		}

		@Override
		public Class getValueClass() {
			return Long.class;
		}
	}

	class QuotaAvailableBytesPropertyWriter implements StandardProperty {

		@Override
		public Long getValue(PropFindableResource res) {
			if (quotaDataAccessor != null) {
				return quotaDataAccessor.getQuotaAvailable(res);
			} else {
				return null;
			}
		}

		@Override
		public String fieldName() {
			return "quota-available-bytes";
		}

		@Override
		public Class getValueClass() {
			return Long.class;
		}
	}

	class EtagPropertyWriter implements StandardProperty {

		@Override
		public String getValue(PropFindableResource res) {
			return eTagGenerator.generateEtag(res);
		}

		@Override
		public String fieldName() {
			return "getetag";
		}

		@Override
		public Class getValueClass() {
			return String.class;
		}
	}

	// MS specific fields
	class MSNamePropertyWriter extends DisplayNamePropertyWriter {

		@Override
		public String fieldName() {
			return "name";
		}
	}

	static class MSIsCollectionPropertyWriter implements StandardProperty {

		@Override
		public String fieldName() {
			return "iscollection";
		}

		@Override
		public Boolean getValue(PropFindableResource res) {
			return (res instanceof CollectionResource);
		}

		@Override
		public Class getValueClass() {
			return Boolean.class;
		}
	}

	static class MSIsReadOnlyPropertyWriter implements StandardProperty {

		@Override
		public String fieldName() {
			return "isreadonly";
		}

		@Override
		public Boolean getValue(PropFindableResource res) {
			return !(res instanceof PutableResource);
		}

		@Override
		public Class getValueClass() {
			return Boolean.class;
		}
	}

	class SupportedReportSetProperty implements StandardProperty {

		@Override
		public String fieldName() {
			return "supported-report-set";
		}

		@Override
		public SupportedReportSetList getValue(PropFindableResource res) {
			SupportedReportSetList reportSet = new SupportedReportSetList();
			for (Report report: reports.values()) {
				if(report instanceof QualifiedReport)
					reportSet.add(((QualifiedReport) report).getQualifiedName());
				else
					reportSet.add(new QName(DAV_URI, report.getName()));
			}
			return reportSet;
		}

		@Override
		public Class getValueClass() {
			return SupportedReportSetList.class;
		}
	}

	static class MiltonExtTextContentProperty implements StandardProperty {

		@Override
		public String fieldName() {
			return "textcontent";
		}

		@Override
		public String getValue(PropFindableResource res) {
			if (res instanceof GetableResource) {
				GetableResource gr = (GetableResource) res;
				String ct = gr.getContentType("text");
				if (ct != null && ct.startsWith("text")) {
					ByteArrayOutputStream bout = new ByteArrayOutputStream();
					try {
						gr.sendContent(bout, null, Collections.EMPTY_MAP, ct);
						return bout.toString("UTF-8");
					} catch (IOException e) {
						throw new RuntimeException(e);
					} catch (NotAuthorizedException | NotFoundException | BadRequestException e) {
						return null;
					}
				}
			}
			return null;
		}

		@Override
		public Class getValueClass() {
			return String.class;
		}
	}

	protected void sendStringProp(XmlWriter writer, String name, String value) {
		if (value == null) {
			writer.writeProperty(null, name);
		} else {
			writer.writeProperty(null, name, value);
		}
	}

	void sendDateProp(XmlWriter writer, String name, Date date) {
		sendStringProp(writer, name, (date == null ? null : DateUtils.formatDate(date)));
	}

	public HandlerHelper getHandlerHelper() {
		return handlerHelper;
	}

	public QuotaDataAccessor getQuotaDataAccessor() {
		return quotaDataAccessor;
	}

	public Map getReports() {
		return reports;
	}

	public ResourceTypeHelper getResourceTypeHelper() {
		return resourceTypeHelper;
	}

	public ETagGenerator geteTagGenerator() {
		return eTagGenerator;
	}

	public PropertyMap getPropertyMap() {
		return propertyMap;
	}

	public MkColHandler getMkColHandler() {
		return mkColHandler;
	}

	public PropPatchHandler getPropPatchHandler() {
		return propPatchHandler;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy