/* * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation */ package org.nuxeo.ecm.webdav; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.InputStream; import javax.inject.Inject; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.InputStreamRequestEntity; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.client.methods.DavMethod; import org.apache.jackrabbit.webdav.client.methods.LockMethod; import org.apache.jackrabbit.webdav.client.methods.MkColMethod; import org.apache.jackrabbit.webdav.client.methods.MoveMethod; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; import org.apache.jackrabbit.webdav.lock.LockDiscovery; import org.apache.jackrabbit.webdav.lock.Scope; import org.apache.jackrabbit.webdav.lock.Type; import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertySet; import org.junit.BeforeClass; import org.junit.Test; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; import org.nuxeo.runtime.transaction.TransactionHelper; import org.w3c.dom.Element; /** * Jackrabbit includes a WebDAV client library. Let's use it to test our server. */ public class WebDavClientTest extends AbstractServerTest { private static String USERNAME = "Administrator"; private static String PASSWORD = "Administrator"; private static HttpClient client; @Inject protected CoreSession session; @BeforeClass public static void setUpClass() { client = createClient(USERNAME, PASSWORD); } protected static HttpClient createClient(String username, String password) { HostConfiguration hostConfig = new HostConfiguration(); hostConfig.setHost("localhost", WebDavServerFeature.PORT); HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); HttpConnectionManagerParams params = new HttpConnectionManagerParams(); int maxHostConnections = 20; params.setMaxConnectionsPerHost(hostConfig, maxHostConnections); connectionManager.setParams(params); HttpClient httpClient = new HttpClient(connectionManager); httpClient.setHostConfiguration(hostConfig); Credentials creds = new UsernamePasswordCredentials(username, password); httpClient.getState().setCredentials(AuthScope.ANY, creds); httpClient.getParams().setAuthenticationPreemptive(true); return httpClient; } @Test public void testNotFoundVirtualRoot() throws Exception { DavMethod method = new PropFindMethod(TEST_URI + "/nosuchpath", DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_NOT_FOUND, status); } @Test public void testNotFoundRegularPath() throws Exception { DavMethod method = new PropFindMethod(ROOT_URI + "/nosuchpath", DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_NOT_FOUND, status); } @Test public void testPropFindOnFolderDepthInfinity() throws Exception { DavMethod pFind = new PropFindMethod(ROOT_URI, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_INFINITY); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); // Not quite nice, but for a example ok DavPropertySet props = multiStatus.getResponses()[0].getProperties(200); for (DavPropertyName propName : props.getPropertyNames()) { // System.out.println(propName + " " + props.get(propName).getValue()); } } @Test public void testPropFindOnFolderDepthZero() throws Exception { DavMethod pFind = new PropFindMethod(ROOT_URI, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); // Not quite nice, but for a example ok DavPropertySet props = multiStatus.getResponses()[0].getProperties(200); for (DavPropertyName propName : props.getPropertyNames()) { // System.out.println(propName + " " + props.get(propName).getValue()); } } @Test public void testListFolderContents() throws Exception { DavMethod pFind = new PropFindMethod(ROOT_URI, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_1); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multiStatus.getResponses(); StringBuilder failmsg = new StringBuilder("Failed to get 4 responses, got: "); if (responses.length < 4) { for (MultiStatusResponse response : responses) { failmsg.append(response.getHref()); failmsg.append("\n"); } } assertTrue(failmsg.toString(), responses.length >= 4); // there may be more than 4 entries if other testCreate* tests // run before this method boolean found = false; for (MultiStatusResponse response : responses) { if (response.getHref().endsWith("quality.jpg")) { found = true; } } assertTrue(found); } @Test public void testGetDocProperties() throws Exception { DavMethod pFind = new PropFindMethod(ROOT_URI + "quality.jpg", DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_1); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multiStatus.getResponses(); assertEquals(1L, responses.length); MultiStatusResponse response = responses[0]; assertEquals("123631", response.getProperties(200).get("getcontentlength").getValue()); } @Test public void testCreateFolder() throws Exception { String name = "newfolder"; DavMethod method = new MkColMethod(ROOT_URI + name); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_CREATED, status); // check using Nuxeo Core APIs session.save(); // process invalidations PathRef pathRef = new PathRef("/workspaces/workspace/" + name); assertTrue(session.exists(pathRef)); DocumentModel doc = session.getDocument(pathRef); assertEquals("Folder", doc.getType()); assertEquals(name, doc.getTitle()); } @Test public void testCreateBinaryFile() throws Exception { String name = "newfile.bin"; // The bin extension is not in the MimetypeRegistry, so the default mimetype is used String mimeType = MimetypeRegistry.DEFAULT_MIMETYPE; byte[] bytes = new byte[] { 1, 2, 3, 4, 5 }; String expectedType = "File"; doTestPutFile(name, bytes, mimeType, expectedType); } @Test public void testCreateTextFile() throws Exception { String name = "newfile.txt"; String mimeType = "text/plain"; byte[] bytes = "Hello, world!".getBytes("UTF-8"); String expectedType = "Note"; doTestPutFile(name, bytes, mimeType, expectedType); } @Test public void testCreateTextFileWithSemiColon() throws Exception { String name = "newfile;;;.txt"; // name with semicolons String mimeType = "text/plain"; byte[] bytes = "Hello, world!".getBytes("UTF-8"); String expectedType = "Note"; doTestPutFile(name, bytes, mimeType, expectedType); } @Test // NXP-12735: disabled because failing under windows + pgsql public void testOverwriteExistingFile() throws Exception { String name = "test.txt"; // this file already exists String mimeType = "text/plain"; PathRef pathRef = new PathRef("/workspaces/workspace/" + name); assertTrue(session.exists(pathRef)); byte[] bytes = new byte[] { 1, 2, 3, 4, 5 }; String expectedType = "File"; doTestPutFile(name, bytes, mimeType, expectedType); } @Test public void testMoveWithRenaming() throws Exception { // create a fake bin tmp file which will finally be a docx file String name = "tmpfile.tmp"; String mimeType = MimetypeRegistry.DEFAULT_MIMETYPE; byte[] bytes = "Fake BIN".getBytes("UTF-8"); String expectedType = "File"; doTestPutFile(name, bytes, mimeType, expectedType); PathRef pathRef = new PathRef("/workspaces/workspace/" + name); assertTrue(session.exists(pathRef)); DocumentModel doc = session.getDocument(pathRef); assertEquals(name, doc.getTitle()); Blob blob = (Blob) doc.getPropertyValue("file:content"); assertEquals(name, blob.getFilename()); assertEquals(MimetypeRegistry.DEFAULT_MIMETYPE, blob.getMimeType()); // rename it to a docx file String newName = "sample.docx"; HttpMethod method = new MoveMethod(ROOT_URI + name, ROOT_URI + newName, false); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_CREATED, status); TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); doc = session.getDocument(pathRef); assertEquals(newName, doc.getTitle()); blob = (Blob) doc.getPropertyValue("file:content"); assertEquals(newName, blob.getFilename()); assertEquals("application/vnd.openxmlformats-officedocument.wordprocessingml.document", blob.getMimeType()); } protected void doTestPutFile(String name, byte[] bytes, String mimeType, String expectedType) throws Exception { InputStream is = new ByteArrayInputStream(bytes); PutMethod method = new PutMethod(ROOT_URI + name); method.setRequestEntity(new InputStreamRequestEntity(is, bytes.length, mimeType)); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_CREATED, status); // check using Nuxeo Core APIs TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); PathRef pathRef = new PathRef("/workspaces/workspace/" + name); assertTrue(session.exists(pathRef)); DocumentModel doc = session.getDocument(pathRef); assertEquals(expectedType, doc.getType()); assertEquals(name, doc.getTitle()); BlobHolder bh = doc.getAdapter(BlobHolder.class); assertNotNull(bh); Blob blob = bh.getBlob(); assertNotNull(blob); assertEquals(bytes.length, blob.getLength()); assertEquals(mimeType, blob.getMimeType()); assertArrayEquals(bytes, blob.getByteArray()); } @Test public void testDeleteFile() throws Exception { String name = "test.txt"; HttpMethod method = new DeleteMethod(ROOT_URI + name); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_NO_CONTENT, status); // check using Nuxeo Core APIs session.save(); // process invalidations PathRef pathRef = new PathRef("/workspaces/workspace/" + name); assertFalse(session.exists(pathRef)); // in trash with different name // recreate it, for other tests using the same repo byte[] bytes = "Hello, world!".getBytes("UTF-8"); doTestPutFile(name, bytes, "text/plain", "Note"); } @Test public void testDeleteMissingFile() throws Exception { String name = "nosuchfile.txt"; HttpMethod method = new DeleteMethod(ROOT_URI + name); int status = client.executeMethod(method); assertEquals(HttpStatus.SC_NOT_FOUND, status); } @Test public void testGetFolderPropertiesAcceptTextXml() throws Exception { checkAccept("text/xml"); } @Test public void testGetFolderPropertiesAcceptTextMisc() throws Exception { checkAccept("text/html, image/jpeg;q=0.9, image/png;q=0.9, text/*;q=0.9, image/*;q=0.9, */*;q=0.8"); } protected void checkAccept(String accept) throws Exception { DavMethod pFind = new PropFindMethod(ROOT_URI, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0); pFind.setRequestHeader("Accept", accept); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multiStatus.getResponses(); assertEquals(1, responses.length); MultiStatusResponse response = responses[0]; assertEquals("workspace", response.getProperties(200).get(DavConstants.PROPERTY_DISPLAYNAME).getValue()); } @Test public void testPropFindOnLockedFile() throws Exception { String fileUri = ROOT_URI + "quality.jpg"; DavMethod pLock = new LockMethod(fileUri, Scope.EXCLUSIVE, Type.WRITE, USERNAME, 10000l, false); client.executeMethod(pLock); pLock.checkSuccess(); DavMethod pFind = new PropFindMethod(fileUri, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_1); client.executeMethod(pFind); MultiStatus multiStatus = pFind.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multiStatus.getResponses(); assertEquals(1L, responses.length); MultiStatusResponse response = responses[0]; DavProperty<?> pLockDiscovery = response.getProperties(200).get(DavConstants.PROPERTY_LOCKDISCOVERY); Element eLockDiscovery = (Element) ((Element) pLockDiscovery.getValue()).getParentNode(); LockDiscovery lockDiscovery = LockDiscovery.createFromXml(eLockDiscovery); assertEquals(USERNAME, lockDiscovery.getValue().get(0).getOwner()); } }