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;
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));
}
}
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;
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
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;
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;
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();
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;
.startsWith(HttpHeader.NEGOTIATE)) {
negotiateFailed = true;
} else {
- return HttpResponseStatus.FORBIDDEN.getCode();
+ return HttpStatus.FORBIDDEN.getCode();
}
}
// 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() {
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;
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 {
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();
}
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;
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",
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;
/**
* 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 {
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) {
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(),
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);
}
}
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<>();
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();
}
}
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;
propname, //
include, //
propstat, //
+ status, //
// locking
lockscope, //
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() {
return;
}
startElement(xsWriter);
- xsWriter.writeCData(value);
+ xsWriter.writeCharacters(value);
xsWriter.writeEndElement();
}
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;
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.
DavResponse currentResponse = null;
boolean collectiongProperties = false;
+ Set<QName> currentPropertyNames = null;
+ HttpStatus currentStatus = null;
final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation
elements: while (reader.hasNext()) {
// 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()) {
continue elements; // skip mod_dav properties
assert currentResponse != null;
- currentResponse.getPropertyNames().add(name);
+ currentPropertyNames.add(name);
if (value != null)
currentResponse.getProperties().put(name, value);
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())) {
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);
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());
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);
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)
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
}
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;
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);
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());
+++ /dev/null
-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;
- }
-
-}
--- /dev/null
+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;
+ }
+
+}