From: Mathieu Baudier Date: Sat, 17 Sep 2022 05:14:48 +0000 (+0200) Subject: Working read-only WebDav server X-Git-Tag: v2.3.10~41 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=bbfad683e435f1989cb7ed4aa56a2fed52b64245 Working read-only WebDav server --- diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java index 11c3db006..1313704f0 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java @@ -17,6 +17,7 @@ import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.cms.acr.AbstractContent; import org.argeo.cms.acr.ContentUtils; import org.argeo.cms.dav.DavResponse; +import org.argeo.util.http.HttpStatus; public class DavContent extends AbstractContent { private final DavContentProvider provider; @@ -103,7 +104,7 @@ public class DavContent extends AbstractContent { DavResponse response = responses.next(); String relativePath = response.getHref(); URI contentUri = provider.relativePathToUri(relativePath); - return new DavContent(getSession(), provider, contentUri, response.getPropertyNames()); + return new DavContent(getSession(), provider, contentUri, response.getPropertyNames(HttpStatus.OK)); } } diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java index 0622e8852..bc4bbfed3 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java @@ -10,6 +10,7 @@ import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedSession; import org.argeo.cms.dav.DavClient; import org.argeo.cms.dav.DavResponse; +import org.argeo.util.http.HttpStatus; public class DavContentProvider implements ContentProvider { private String mountPath; @@ -45,7 +46,7 @@ public class DavContentProvider implements ContentProvider { DavContent getDavContent(ProvidedSession session, URI uri) { DavResponse response = davClient.get(uri); - return new DavContent(session, this, uri, response.getPropertyNames()); + return new DavContent(session, this, uri, response.getPropertyNames(HttpStatus.OK)); } @Override diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java index 514d0bd36..a4c14186a 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java @@ -1,5 +1,12 @@ package org.argeo.cms.acr.xml; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.HashSet; @@ -9,15 +16,19 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ForkJoinPool; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; +import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentName; @@ -333,6 +344,27 @@ public class DomContent extends AbstractContent implements ProvidedContent { return super.write(clss); } + @SuppressWarnings("unchecked") + @Override + public C open(Class clss) throws IOException, IllegalArgumentException { + if (InputStream.class.isAssignableFrom(clss)) { + PipedOutputStream out = new PipedOutputStream(); + ForkJoinPool.commonPool().execute(() -> { + try { + Source source = new DOMSource(element); + Result result = new StreamResult(out); + provider.getTransformerFactory().newTransformer().transform(source, result); + out.flush(); + out.close(); + } catch (TransformerException | IOException e) { + throw new RuntimeException("Cannot read " + getPath(), e); + } + }); + return (C) new PipedInputStream(out); + } + return super.open(clss); + } + @Override public int getSiblingIndex() { Node curr = element.getPreviousSibling(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index e79032c4c..785eeb912 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -15,7 +15,7 @@ import org.argeo.api.cms.CmsSession; import org.argeo.cms.internal.runtime.CmsContextImpl; import org.argeo.util.CurrentSubject; import org.argeo.util.http.HttpHeader; -import org.argeo.util.http.HttpResponseStatus; +import org.argeo.util.http.HttpStatus; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; @@ -155,7 +155,7 @@ public class RemoteAuthUtils { .startsWith(HttpHeader.NEGOTIATE)) { negotiateFailed = true; } else { - return HttpResponseStatus.FORBIDDEN.getCode(); + return HttpStatus.FORBIDDEN.getCode(); } } @@ -175,7 +175,7 @@ public class RemoteAuthUtils { // response.setHeader("Keep-Alive", "timeout=5, max=97"); // response.setContentType("text/html; charset=UTF-8"); - return HttpResponseStatus.UNAUTHORIZED.getCode(); + return HttpStatus.UNAUTHORIZED.getCode(); } private static boolean hasAcceptorCredentials() { diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java index e1e5f7499..94f292c8e 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java @@ -2,6 +2,8 @@ package org.argeo.cms.dav; import java.io.IOException; import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -15,7 +17,7 @@ import javax.xml.namespace.QName; import org.argeo.util.http.HttpHeader; import org.argeo.util.http.HttpMethod; -import org.argeo.util.http.HttpResponseStatus; +import org.argeo.util.http.HttpStatus; public class DavClient { @@ -25,14 +27,14 @@ public class DavClient { httpClient = HttpClient.newBuilder() // // .sslContext(insecureContext()) // .version(HttpClient.Version.HTTP_1_1) // -// .authenticator(new Authenticator() { -// -// @Override -// protected PasswordAuthentication getPasswordAuthentication() { -// return new PasswordAuthentication("root", "demo".toCharArray()); -// } -// -// }) // + .authenticator(new Authenticator() { + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("root", "demo".toCharArray()); + } + + }) // .build(); } @@ -102,7 +104,7 @@ public class DavClient { HttpResponse response = httpClient.send(request, bodyHandler); System.out.println(response.body()); int responseStatusCode = response.statusCode(); - if (responseStatusCode == HttpResponseStatus.NOT_FOUND.getCode()) + if (responseStatusCode == HttpStatus.NOT_FOUND.getCode()) return false; if (responseStatusCode >= 200 && responseStatusCode < 300) return true; @@ -149,7 +151,7 @@ public class DavClient { while (responses.hasNext()) { DavResponse response = responses.next(); System.out.println(response.getHref() + (response.isCollection() ? " (collection)" : "")); - System.out.println(" " + response.getPropertyNames()); + //System.out.println(" " + response.getPropertyNames(HttpStatus.OK)); } // davClient.setProperty("http://localhost/unstable/a2/org.argeo.tp.sdk/org.opentest4j.1.2.jar", diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java index f7c4ce7de..1d6c02623 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java @@ -12,7 +12,7 @@ import javax.xml.namespace.NamespaceContext; import org.argeo.api.acr.ContentNotFoundException; import org.argeo.util.http.HttpHeader; import org.argeo.util.http.HttpMethod; -import org.argeo.util.http.HttpResponseStatus; +import org.argeo.util.http.HttpStatus; import org.argeo.util.http.HttpServerUtils; import com.sun.net.httpserver.HttpExchange; @@ -20,7 +20,7 @@ import com.sun.net.httpserver.HttpHandler; /** * Centralise patterns which are not ACR specific. Not really meant as a - * framework for building WebDav servers, but rather to make uppe-level of + * framework for building WebDav servers, but rather to make upper-level of * ACR-specific code more readable and maintainable. */ public abstract class DavHttpHandler implements HttpHandler { @@ -34,7 +34,7 @@ public abstract class DavHttpHandler implements HttpHandler { handleGET(exchange, subPath); } else if (HttpMethod.OPTIONS.name().equals(method)) { handleOPTIONS(exchange, subPath); - exchange.sendResponseHeaders(HttpResponseStatus.NO_CONTENT.getCode(), -1); + exchange.sendResponseHeaders(HttpStatus.NO_CONTENT.getCode(), -1); } else if (HttpMethod.PROPFIND.name().equals(method)) { DavDepth depth = DavDepth.fromHttpExchange(exchange); if (depth == null) { @@ -45,9 +45,9 @@ public abstract class DavHttpHandler implements HttpHandler { try (InputStream in = exchange.getRequestBody()) { davPropfind = DavPropfind.load(depth, in); } - MultiStatusWriter multiStatusWriter = new MultiStatusWriter(); + MultiStatusWriter multiStatusWriter = new MultiStatusWriter(exchange.getProtocol()); CompletableFuture published = handlePROPFIND(exchange, subPath, davPropfind, multiStatusWriter); - exchange.sendResponseHeaders(HttpResponseStatus.MULTI_STATUS.getCode(), 0l); + exchange.sendResponseHeaders(HttpStatus.MULTI_STATUS.getCode(), 0l); NamespaceContext namespaceContext = getNamespaceContext(exchange, subPath); try (OutputStream out = exchange.getResponseBody()) { multiStatusWriter.process(namespaceContext, out, published.minimalCompletionStage(), @@ -57,13 +57,14 @@ public abstract class DavHttpHandler implements HttpHandler { throw new IllegalArgumentException("Unsupported method " + method); } } catch (ContentNotFoundException e) { - exchange.sendResponseHeaders(HttpResponseStatus.NOT_FOUND.getCode(), -1); + exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1); } // TODO return a structured error message catch (UnsupportedOperationException e) { - exchange.sendResponseHeaders(HttpResponseStatus.NOT_IMPLEMENTED.getCode(), -1); + e.printStackTrace(); + exchange.sendResponseHeaders(HttpStatus.NOT_IMPLEMENTED.getCode(), -1); } catch (Exception e) { - exchange.sendResponseHeaders(HttpResponseStatus.INTERNAL_SERVER_ERROR.getCode(), -1); + exchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1); } } diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java index 6d45246db..828dc2640 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java @@ -2,19 +2,23 @@ package org.argeo.cms.dav; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import javax.xml.namespace.QName; +import org.argeo.util.http.HttpStatus; + +/** The WebDav response for a given resource. */ public class DavResponse { final static String MOD_DAV_NAMESPACE = "http://apache.org/dav/props/"; private String href; private boolean collection; - private Set propertyNames = new HashSet<>(); + private Map> propertyNames = new TreeMap<>(); private Map properties = new HashMap<>(); private List resourceTypes = new ArrayList<>(); @@ -42,8 +46,14 @@ public class DavResponse { return resourceTypes; } - public Set getPropertyNames() { - return propertyNames; + public Set getPropertyNames(HttpStatus status) { + if (!propertyNames.containsKey(status)) + propertyNames.put(status, new TreeSet<>(DavXmlElement.QNAME_COMPARATOR)); + return propertyNames.get(status); + } + + public Set getStatuses() { + return propertyNames.keySet(); } } diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java b/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java index b98068399..06da6792c 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java @@ -1,5 +1,8 @@ package org.argeo.cms.dav; +import java.util.Comparator; +import java.util.Objects; + import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -21,6 +24,7 @@ enum DavXmlElement implements QNamed { propname, // include, // propstat, // + status, // // locking lockscope, // @@ -36,6 +40,19 @@ enum DavXmlElement implements QNamed { final static String WEBDAV_NAMESPACE_URI = "DAV:"; final static String WEBDAV_DEFAULT_PREFIX = "D"; + final static Comparator QNAME_COMPARATOR = new Comparator() { + + @Override + public int compare(QName qn1, QName qn2) { + if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace + return qn1.getLocalPart().compareTo(qn2.getLocalPart()); + } else { + return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI()); + } + } + + }; + // private final QName value; // // private DavXmlElement() { @@ -71,7 +88,7 @@ enum DavXmlElement implements QNamed { return; } startElement(xsWriter); - xsWriter.writeCData(value); + xsWriter.writeCharacters(value); xsWriter.writeEndElement(); } diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java index 4224e488c..6d22c8e29 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java @@ -2,7 +2,9 @@ package org.argeo.cms.dav; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.Iterator; +import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; @@ -17,6 +19,8 @@ import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import org.argeo.util.http.HttpStatus; + /** * Asynchronously iterate over the response statuses of the response to a * PROPFIND request. @@ -46,6 +50,8 @@ class MultiStatusReader implements Iterator { DavResponse currentResponse = null; boolean collectiongProperties = false; + Set currentPropertyNames = null; + HttpStatus currentStatus = null; final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation elements: while (reader.hasNext()) { @@ -69,8 +75,14 @@ class MultiStatusReader implements Iterator { // case collection: // currentResponse.setCollection(true); // break; + case status: + reader.next(); + String statusLine = reader.getText(); + currentStatus = HttpStatus.parseStatusLine(statusLine); + break; case prop: collectiongProperties = true; + currentPropertyNames = new HashSet<>(); break; case resourcetype: while (reader.hasNext()) { @@ -106,7 +118,7 @@ class MultiStatusReader implements Iterator { continue elements; // skip mod_dav properties assert currentResponse != null; - currentResponse.getPropertyNames().add(name); + currentPropertyNames.add(name); if (value != null) currentResponse.getProperties().put(name, value); @@ -118,6 +130,10 @@ class MultiStatusReader implements Iterator { DavXmlElement davXmlElement = DavXmlElement.toEnum(name); if (davXmlElement != null) switch (davXmlElement) { + case propstat: + currentResponse.getPropertyNames(currentStatus).addAll(currentPropertyNames); + currentPropertyNames = null; + break; case response: assert currentResponse != null; if (ignoredHref == null || !ignoredHref.equals(currentResponse.getHref())) { diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java index 4c06b032f..986b2fe92 100644 --- a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java +++ b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java @@ -20,6 +20,8 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import org.argeo.util.http.HttpStatus; + class MultiStatusWriter implements Consumer { private BlockingQueue queue = new ArrayBlockingQueue<>(64); @@ -31,6 +33,12 @@ class MultiStatusWriter implements Consumer { private AtomicBoolean polling = new AtomicBoolean(); + private String protocol; + + public MultiStatusWriter(String protocol) { + this.protocol = protocol; + } + public void process(NamespaceContext namespaceContext, OutputStream out, CompletionStage published, boolean propname) throws IOException { published.thenRun(() -> allPublished()); @@ -54,9 +62,9 @@ class MultiStatusWriter implements Consumer { davResponse = queue.poll(10, TimeUnit.MILLISECONDS); if (davResponse == null) continue poll; - System.err.println(davResponse.getHref()); + //System.err.println(davResponse.getHref()); } catch (InterruptedException e) { - System.err.println(e); + //System.err.println(e); continue poll; } finally { polling.set(false); @@ -79,13 +87,14 @@ class MultiStatusWriter implements Consumer { protected void writeDavResponse(XMLStreamWriter xsWriter, DavResponse davResponse, boolean propname) throws XMLStreamException { Set namespaces = new HashSet<>(); - for (QName key : davResponse.getPropertyNames()) { - if (key.getNamespaceURI().equals(DavXmlElement.WEBDAV_NAMESPACE_URI)) - continue; // skip - if (key.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) - continue; // skip - namespaces.add(key.getNamespaceURI()); - } + for (HttpStatus status : davResponse.getStatuses()) + for (QName key : davResponse.getPropertyNames(status)) { + if (key.getNamespaceURI().equals(DavXmlElement.WEBDAV_NAMESPACE_URI)) + continue; // skip + if (key.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI)) + continue; // skip + namespaces.add(key.getNamespaceURI()); + } DavXmlElement.response.startElement(xsWriter); // namespaces for (String ns : namespaces) @@ -94,30 +103,45 @@ class MultiStatusWriter implements Consumer { DavXmlElement.href.setSimpleValue(xsWriter, davResponse.getHref()); { - DavXmlElement.propstat.startElement(xsWriter); - { - DavXmlElement.prop.startElement(xsWriter); - if (!davResponse.getResourceTypes().isEmpty() || davResponse.isCollection()) { - DavXmlElement.resourcetype.startElement(xsWriter); - if (davResponse.isCollection()) - DavXmlElement.collection.emptyElement(xsWriter); - for (QName resourceType : davResponse.getResourceTypes()) { - xsWriter.writeEmptyElement(resourceType.getNamespaceURI(), resourceType.getLocalPart()); - } - xsWriter.writeEndElement();// resource type - } - for (QName key : davResponse.getPropertyNames()) { - if (propname) { - xsWriter.writeEmptyElement(key.getNamespaceURI(), key.getLocalPart()); - } else { - xsWriter.writeStartElement(key.getNamespaceURI(), key.getLocalPart()); - xsWriter.writeCData(davResponse.getProperties().get(key)); - xsWriter.writeEndElement(); + for (HttpStatus status : davResponse.getStatuses()) { + DavXmlElement.propstat.startElement(xsWriter); + { + DavXmlElement.prop.startElement(xsWriter); + + // resourcetype + if (HttpStatus.OK.equals(status)) + if (propname) { + DavXmlElement.resourcetype.emptyElement(xsWriter); + } else { + if (!davResponse.getResourceTypes().isEmpty() || davResponse.isCollection()) { + DavXmlElement.resourcetype.startElement(xsWriter); + if (davResponse.isCollection()) + DavXmlElement.collection.emptyElement(xsWriter); + for (QName resourceType : davResponse.getResourceTypes()) { + xsWriter.writeEmptyElement(resourceType.getNamespaceURI(), + resourceType.getLocalPart()); + } + xsWriter.writeEndElement();// resource type + } + } + + properties: for (QName key : davResponse.getPropertyNames(status)) { + if (DavXmlElement.resourcetype.qName().equals(key)) + continue properties; + + if (propname) { + xsWriter.writeEmptyElement(key.getNamespaceURI(), key.getLocalPart()); + } else { + xsWriter.writeStartElement(key.getNamespaceURI(), key.getLocalPart()); + xsWriter.writeCData(davResponse.getProperties().get(key)); + xsWriter.writeEndElement(); + } } + xsWriter.writeEndElement();// prop } - xsWriter.writeEndElement();// prop + DavXmlElement.status.setSimpleValue(xsWriter, status.getStatusLine(protocol)); + xsWriter.writeEndElement();// propstat } - xsWriter.writeEndElement();// propstat } xsWriter.writeEndElement();// response } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java index 2c1562fb4..c36f410e2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java @@ -27,7 +27,7 @@ import org.argeo.cms.dav.DavPropfind; import org.argeo.cms.dav.DavResponse; import org.argeo.cms.internal.http.RemoteAuthHttpExchange; import org.argeo.util.StreamUtils; -import org.argeo.util.http.HttpResponseStatus; +import org.argeo.util.http.HttpStatus; import com.sun.net.httpserver.HttpExchange; @@ -50,7 +50,7 @@ public class CmsAcrHttpHandler extends DavHttpHandler { Content content = session.get(path); Optional size = content.get(DName.getcontentlength, Long.class); try (InputStream in = content.open(InputStream.class)) { - exchange.sendResponseHeaders(HttpResponseStatus.OK.getCode(), size.orElse(0l)); + exchange.sendResponseHeaders(HttpStatus.OK.getCode(), size.orElse(0l)); StreamUtils.copy(in, exchange.getResponseBody()); } catch (IOException e) { throw new RuntimeException("Cannot process " + path, e); @@ -87,20 +87,22 @@ public class CmsAcrHttpHandler extends DavHttpHandler { davResponse.setCollection(true); if (davPropfind.isAllprop()) { for (Map.Entry entry : content.entrySet()) { - davResponse.getPropertyNames().add(entry.getKey()); + davResponse.getPropertyNames(HttpStatus.OK).add(entry.getKey()); processMapEntry(davResponse, entry.getKey(), entry.getValue()); } davResponse.getResourceTypes().addAll(content.getContentClasses()); } else if (davPropfind.isPropname()) { for (QName key : content.keySet()) { - davResponse.getPropertyNames().add(key); + davResponse.getPropertyNames(HttpStatus.OK).add(key); } } else { for (QName key : davPropfind.getProps()) { if (content.containsKey(key)) { - davResponse.getPropertyNames().add(key); + davResponse.getPropertyNames(HttpStatus.OK).add(key); Object value = content.get(key); processMapEntry(davResponse, key, value); + } else { + davResponse.getPropertyNames(HttpStatus.NOT_FOUND).add(key); } if (DName.resourcetype.qName().equals(key)) { davResponse.getResourceTypes().addAll(content.getContentClasses()); diff --git a/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java b/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java deleted file mode 100644 index c813a1f6b..000000000 --- a/org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.util.http; - -/** - * Standard HTTP response status codes (including WebDav ones). - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status - */ -public enum HttpResponseStatus { - // Successful responses (200–299) - OK(200), // - NO_CONTENT(204), // - MULTI_STATUS(207), // WebDav - // Client error responses (400–499) - UNAUTHORIZED(401), // - FORBIDDEN(403), // - NOT_FOUND(404), // - // Server error responses (500-599) - INTERNAL_SERVER_ERROR(500), // - NOT_IMPLEMENTED(501), // - ; - - private final int code; - - HttpResponseStatus(int statusCode) { - this.code = statusCode; - } - - public int getCode() { - return code; - } - -} diff --git a/org.argeo.util/src/org/argeo/util/http/HttpStatus.java b/org.argeo.util/src/org/argeo/util/http/HttpStatus.java new file mode 100644 index 000000000..8fb109bd4 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/http/HttpStatus.java @@ -0,0 +1,66 @@ +package org.argeo.util.http; + +/** + * Standard HTTP response status codes (including WebDav ones). + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + */ +public enum HttpStatus { + // Successful responses (200–299) + OK(200, "OK"), // + NO_CONTENT(204, "No Content"), // + MULTI_STATUS(207, "Multi-Status"), // WebDav + // Client error responses (400–499) + UNAUTHORIZED(401, "Unauthorized"), // + FORBIDDEN(403, "Forbidden"), // + NOT_FOUND(404, "Not Found"), // + // Server error responses (500-599) + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), // + NOT_IMPLEMENTED(501, "Not Implemented"), // + ; + + private final int code; + private final String reasonPhrase; + + HttpStatus(int statusCode, String reasonPhrase) { + this.code = statusCode; + this.reasonPhrase = reasonPhrase; + } + + public int getCode() { + return code; + } + + public String getReasonPhrase() { + return reasonPhrase; + } + + /** + * The status line, as defined by RFC2616. + * + * @see https://www.rfc-editor.org/rfc/rfc2616#section-6.1 + */ + public String getStatusLine(String httpVersion) { + return httpVersion + " " + code + " " + reasonPhrase; + } + + public static HttpStatus parseStatusLine(String statusLine) { + try { + String[] arr = statusLine.split(" "); + int code = Integer.parseInt(arr[1]); + for (HttpStatus status : values()) { + if (status.getCode() == code) + return status; + } + } catch (Exception e) { + throw new IllegalArgumentException("Invalid status line: " + statusLine, e); + } + throw new IllegalArgumentException("Unkown status code: " + statusLine); + } + + @Override + public String toString() { + return code + " " + reasonPhrase; + } + +}