Working read-only WebDav server
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 17 Sep 2022 05:14:48 +0000 (07:14 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 17 Sep 2022 05:14:48 +0000 (07:14 +0200)
13 files changed:
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
org.argeo.cms/src/org/argeo/cms/dav/DavClient.java
org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java
org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java
org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java
org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java
org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java
org.argeo.util/src/org/argeo/util/http/HttpResponseStatus.java [deleted file]
org.argeo.util/src/org/argeo/util/http/HttpStatus.java [new file with mode: 0644]

index 11c3db0060a730c12f22bbd79043be9805b39772..1313704f0feb890bad47985067a455028eb7e948 100644 (file)
@@ -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));
                }
 
        }
index 0622e8852eac9f88246efbf9af7c5f04a9711b1e..bc4bbfed3136c973c3700e28ad5f54e38c9d5422 100644 (file)
@@ -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
index 514d0bd36db23b505577db2bb4b2c13f62a75611..a4c14186ac17d299b0673e1b67ee0857db166c13 100644 (file)
@@ -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 extends Closeable> C open(Class<C> 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();
index e79032c4c38f0603d1d71d323b038254f2fba104..785eeb912f727a5a1246d41927536167f5830a26 100644 (file)
@@ -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() {
index e1e5f749948d6c6cdc5a368b12e555428f9b2b02..94f292c8e68cd70e2b3c38201a04959332cf0904 100644 (file)
@@ -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<String> 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",
index f7c4ce7de8fbdd93ac9af5f61206578c35eecdac..1d6c02623d7bf40d3049b53849b704e437547ef5 100644 (file)
@@ -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<Void> 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);
                }
 
        }
index 6d45246db6fd2a8fd66b370271deafeb1af1ef59..828dc2640902d7571e91fa0b1cc8e6aa3b2f76df 100644 (file)
@@ -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<QName> propertyNames = new HashSet<>();
+       private Map<HttpStatus, Set<QName>> propertyNames = new TreeMap<>();
        private Map<QName, String> properties = new HashMap<>();
        private List<QName> resourceTypes = new ArrayList<>();
 
@@ -42,8 +46,14 @@ public class DavResponse {
                return resourceTypes;
        }
 
-       public Set<QName> getPropertyNames() {
-               return propertyNames;
+       public Set<QName> getPropertyNames(HttpStatus status) {
+               if (!propertyNames.containsKey(status))
+                       propertyNames.put(status, new TreeSet<>(DavXmlElement.QNAME_COMPARATOR));
+               return propertyNames.get(status);
+       }
+
+       public Set<HttpStatus> getStatuses() {
+               return propertyNames.keySet();
        }
 
 }
index b980683999a2b15b38478f3a853037ee393902e7..06da6792c1d44b889de57a4653e64e74eccd2483 100644 (file)
@@ -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> QNAME_COMPARATOR = new Comparator<QName>() {
+
+               @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();
        }
 
index 4224e488c19830a4455162fa402468d94ebd24dd..6d22c8e295f959822cf3ffb0a0ebfa6868d1c22e 100644 (file)
@@ -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> {
 
                        DavResponse currentResponse = null;
                        boolean collectiongProperties = false;
+                       Set<QName> currentPropertyNames = null;
+                       HttpStatus currentStatus = null;
 
                        final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation
                        elements: while (reader.hasNext()) {
@@ -69,8 +75,14 @@ class MultiStatusReader implements Iterator<DavResponse> {
 //                                             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<DavResponse> {
                                                                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<DavResponse> {
                                        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())) {
index 4c06b032f092d04623ba61dff316c549224a0f5f..986b2fe9271c665fda435b399f7124d37e9a0a14 100644 (file)
@@ -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<DavResponse> {
        private BlockingQueue<DavResponse> queue = new ArrayBlockingQueue<>(64);
 
@@ -31,6 +33,12 @@ class MultiStatusWriter implements Consumer<DavResponse> {
 
        private AtomicBoolean polling = new AtomicBoolean();
 
+       private String protocol;
+
+       public MultiStatusWriter(String protocol) {
+               this.protocol = protocol;
+       }
+
        public void process(NamespaceContext namespaceContext, OutputStream out, CompletionStage<Void> published,
                        boolean propname) throws IOException {
                published.thenRun(() -> allPublished());
@@ -54,9 +62,9 @@ class MultiStatusWriter implements Consumer<DavResponse> {
                                        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<DavResponse> {
        protected void writeDavResponse(XMLStreamWriter xsWriter, DavResponse davResponse, boolean propname)
                        throws XMLStreamException {
                Set<String> 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<DavResponse> {
                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
        }
index 2c1562fb42eed2b3abca92e0236c415ca1b2991f..c36f410e2ac6fa115292396e4db3e69b0066d8d2 100644 (file)
@@ -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<Long> 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<QName, Object> 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 (file)
index c813a1f..0000000
+++ /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 (file)
index 0000000..8fb109b
--- /dev/null
@@ -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;
+       }
+
+}