From 31c6b659ae6faa5fb03a62e39f13c25b4b536fb2 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sat, 8 Aug 2026 12:56:34 +0200 Subject: [PATCH 1/4] JCR-5095: retry token creation on concurrent modification of the token parent Concurrent logins of the same user each use their own session but add their token node below the same .tokens parent. A concurrent commit below that parent can invalidate this session's pending changes, so that the token node can neither be saved (InvalidItemStateException from validateTransientItems) nor resolved afterwards (ItemNotFoundException while building its path). Either one failed the whole login. Wrap the token node creation in a bounded retry that discards the doomed transient state via session.refresh(false) and re-reads the token parent, mirroring the conflict handling that getTokenParent already performs for the concurrent creation of the token store itself. The original exception is rethrown once the attempts are exhausted, so behaviour on persistent failures is unchanged. This makes token creation tolerate the conflict but does not remove the underlying race in the transient state handling. --- .../authentication/token/TokenProvider.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java index 1b32a3312f4..ae7dc400c72 100644 --- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java +++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java @@ -31,6 +31,8 @@ import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; +import javax.jcr.InvalidItemStateException; +import javax.jcr.ItemNotFoundException; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.Property; @@ -76,6 +78,13 @@ public class TokenProvider extends ProtectedItemModifier { private static final char DELIM = '_'; + /** + * Number of attempts to persist a new token node before giving up. Concurrent logins + * of the same user write below a shared token parent and may invalidate each other's + * pending changes (JCR-5095). + */ + private static final int CREATE_TOKEN_MAX_ATTEMPTS = 3; + private static final Set RESERVED_ATTRIBUTES = new HashSet(3); static { RESERVED_ATTRIBUTES.add(TOKEN_ATTRIBUTE); @@ -146,6 +155,39 @@ public TokenInfo createToken(User user, SimpleCredentials sc) throws RepositoryE */ private TokenInfo createToken(User user, Map attributes) throws RepositoryException { String error = "Failed to create login token. "; + // Concurrent logins of the same user add token nodes below the very same token + // parent. A concurrent commit below that parent may invalidate the pending changes + // of this session, so that the token node can neither be saved + // (InvalidItemStateException) nor resolved afterwards (ItemNotFoundException while + // building its path). Both are transient, so retry with a refreshed session, + // analogous to the conflict handling in getTokenParent (JCR-5095). + for (int attempt = 1; ; attempt++) { + try { + return createTokenNode(user, attributes, error); + } catch (InvalidItemStateException | ItemNotFoundException e) { + if (attempt >= CREATE_TOKEN_MAX_ATTEMPTS) { + throw e; + } + log.debug("Conflict while creating login token (attempt {}) -> retrying", attempt, e); + // discard the token node that could not be persisted before retrying + session.refresh(false); + } + } + } + + /** + * Creates and persists a single token node below the token parent of the given user. + * + * @param user The user for which a new token should be created. + * @param attributes The attributes associated with the new token. + * @param error Prefix used for log messages. + * @return A new {@code TokenInfo} or {@code null} if the token could not be created. + * @throws InvalidItemStateException If the token node could not be persisted because + * the token parent was modified concurrently. + * @throws ItemNotFoundException If the token node could not be resolved after saving + * because the token parent was modified concurrently. + */ + private TokenInfo createTokenNode(User user, Map attributes, String error) throws RepositoryException { NodeImpl tokenParent = getTokenParent(user); if (tokenParent != null) { try { From ef02535ef690b222f2f438a8cdee51e57dc3151e Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Fri, 7 Aug 2026 22:40:43 +0200 Subject: [PATCH 2/4] JCR-5255: Migrate to Apache HttpClient 5 Replace Apache HttpClient 4.5.14 / HttpCore 4.4.16 with HttpClient 5.6.1 and HttpCore 5.4.2 across jackrabbit-webdav, jackrabbit-spi2dav, jackrabbit-jcr-server and jackrabbit-it-osgi. The httpmime artifact is dropped; its classes now live in httpclient5 as org.apache.hc.client5.http.entity.mime. This is a breaking API change. The exported package org.apache.jackrabbit.webdav.client.methods goes from 2.0.0 to 3.0.0: * BaseDavRequest extends HttpUriRequestBase instead of HttpEntityEnclosingRequestBase, and takes the request method as its first constructor argument, since HttpClient 5 has no no-arg base constructor plus setURI. Subclasses no longer override getMethod(). * The response accessors take ClassicHttpResponse in place of HttpResponse. * XmlEntity.create returns org.apache.hc.core5.http.HttpEntity. In jackrabbit-spi2dav, RepositoryServiceImpl.executeRequest returns ClassicHttpResponse, initMethod takes the HttpClient 5 HttpUriRequest, and ExceptionConverter.generate takes HttpUriRequestBase. The public ConnectionOptions API is unchanged. Behaviour that needed explicit handling rather than a mechanical rename: * Connect and socket timeouts moved from RequestConfig to ConnectionConfig. The ConnectionOptions value -1 means "not configured", which was infinite under HttpClient 4; it now maps to Timeout.DISABLED, because omitting the setter would silently pick up the HttpClient 5 default of three minutes and break long-running polls and batches. * HttpClient 5 does not pre-authenticate from a BasicScheme in the AuthCache unless it has been primed, so initPreemptive is now called. * HttpClient 5 follows redirects for every method, where HttpClient 4 redirected only GET and HEAD. GetHeadRedirectStrategy restores the old rules so that a redirected MOVE, COPY, PUT or DELETE surfaces to the caller instead of silently retargeting a write. * ProxyAuthenticationStrategy is gone; proxy authentication is handled by the shared authentication strategy. * HttpHost takes (scheme, host, port) rather than (host, port, scheme), and credentials take a char[] password. * releaseConnection() maps to reset(); in HttpClient 4 releaseConnection() was defined as reset(). * ContentType.get(HttpEntity) is gone. The replacement is guarded, since the HttpClient 4 method returned null for a null entity. * Entities are immutable, so content encoding moves into the constructor. * HttpMultipartMode.RFC6532 is now EXTENDED. HttpComponents publishes no OSGi bundles for the 5.x line: httpclient5-osgi and httpcore5-osgi stop at 5.0-beta, and the plain JARs carry no Bundle-SymbolicName. jackrabbit-it-osgi therefore repacks them as bundles for the test container. The retry behaviour is left at the HttpClient 5 default, which retries once on 429 and 503 where HttpClient 4 never retried on a status code. Two changes to the WebDAV test harness were needed, neither affecting main code: * HttpClient 5 wraps the TLS upgrade in the per-address retry block of DefaultHttpClientConnectionOperator, so an SSLHandshakeException on the first resolved address is logged at DEBUG and the next address is tried; the caller sees whatever the last address produced. HttpClient 4 only retried on ConnectException, NoRouteToHostException and SocketTimeoutException. Where localhost resolves to both 127.0.0.1 and ::1 and the server binds one family, this turned the handshake failure that HttpsSelfSignedTest asserts on into a connection failure, so the HTTPS connector and its URI are now pinned to 127.0.0.1. * HttpClient 5 keeps a pooled connection leased until the response is closed or its entity consumed, including for status-only responses that HttpClient 4 released automatically. The tests read status codes without closing responses, which exhausted the default pool of five per route and then blocked for the three minute lease timeout. The test pool is now sized for the busiest test class, with a short lease timeout so exhaustion fails fast instead of stalling. Main code is unaffected, since it releases via request.reset() in a finally. --- jackrabbit-it-osgi/pom.xml | 10 +- .../org/apache/jackrabbit/osgi/OSGiIT.java | 12 +- .../apache/jackrabbit/osgi/TestBundles.java | 149 +++++++ .../slf4j2/Slf4j_v2_Tika_v2_4_OSGiIT.java | 12 +- .../slf4j2/Slf4j_v2_Tika_v2_9_OSGiIT.java | 12 +- jackrabbit-it-osgi/test-bundles.xml | 4 +- .../jackrabbit/webdav/server/BindTest.java | 178 ++++----- .../webdav/server/ConditionalsTest.java | 58 +-- .../webdav/server/ContentCodingTest.java | 78 ++-- .../webdav/server/HttpsSelfSignedTest.java | 8 +- .../webdav/server/ProppatchTest.java | 18 +- .../jackrabbit/webdav/server/PutTest.java | 10 +- .../server/RFC4918DestinationHeaderTest.java | 42 +- .../webdav/server/RFC4918IfHeaderTest.java | 58 +-- .../webdav/server/RFC4918PropfindTest.java | 14 +- .../webdav/server/RemotingTest.java | 6 +- .../webdav/server/WebDAVTestBase.java | 70 ++-- jackrabbit-parent/pom.xml | 17 +- jackrabbit-spi2dav/pom.xml | 7 +- .../jackrabbit/spi2dav/ConnectionOptions.java | 4 +- .../spi2dav/CredentialsWrapper.java | 17 +- .../spi2dav/ExceptionConverter.java | 4 +- .../spi2dav/GetHeadRedirectStrategy.java | 64 +++ .../spi2dav/RepositoryServiceImpl.java | 369 ++++++++++-------- .../jackrabbit/spi2dav/URIResolverImpl.java | 14 +- .../apache/jackrabbit/spi2davex/HttpPost.java | 15 +- .../spi2davex/RepositoryServiceImpl.java | 61 ++- .../spi2davex/Rfc6532MultipartEntity.java | 175 +++++++++ .../apache/jackrabbit/spi2davex/Utils.java | 43 +- .../jackrabbit/spi2davex/ValueLoader.java | 33 +- .../jackrabbit/spi2dav/ConnectionTest.java | 7 +- .../jackrabbit/spi2dav/DavPropertyTest.java | 12 +- .../spi2dav/GetHeadRedirectStrategyTest.java | 95 +++++ .../spi2dav/RepositoryServiceImplIT.java | 19 +- .../spi2davex/Rfc6532MultipartEntityTest.java | 138 +++++++ jackrabbit-webdav/pom.xml | 10 +- .../webdav/client/methods/BaseDavRequest.java | 59 ++- .../webdav/client/methods/HttpBind.java | 13 +- .../webdav/client/methods/HttpCheckin.java | 13 +- .../webdav/client/methods/HttpCheckout.java | 13 +- .../webdav/client/methods/HttpCopy.java | 13 +- .../webdav/client/methods/HttpDelete.java | 6 +- .../webdav/client/methods/HttpLabel.java | 13 +- .../webdav/client/methods/HttpLock.java | 23 +- .../webdav/client/methods/HttpMerge.java | 13 +- .../webdav/client/methods/HttpMkcol.java | 13 +- .../client/methods/HttpMkworkspace.java | 13 +- .../webdav/client/methods/HttpMove.java | 13 +- .../webdav/client/methods/HttpOptions.java | 10 +- .../webdav/client/methods/HttpOrderpatch.java | 13 +- .../webdav/client/methods/HttpPoll.java | 13 +- .../webdav/client/methods/HttpPropfind.java | 13 +- .../webdav/client/methods/HttpProppatch.java | 13 +- .../webdav/client/methods/HttpRebind.java | 13 +- .../webdav/client/methods/HttpReport.java | 13 +- .../webdav/client/methods/HttpSearch.java | 13 +- .../webdav/client/methods/HttpSubscribe.java | 17 +- .../webdav/client/methods/HttpUnbind.java | 13 +- .../webdav/client/methods/HttpUnlock.java | 13 +- .../client/methods/HttpUnsubscribe.java | 13 +- .../webdav/client/methods/HttpUpdate.java | 13 +- .../client/methods/HttpVersionControl.java | 13 +- .../webdav/client/methods/XmlEntity.java | 6 +- .../webdav/client/methods/package-info.java | 16 +- .../webdav/util/LinkHeaderFieldParser.java | 9 +- 65 files changed, 1421 insertions(+), 841 deletions(-) create mode 100644 jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/TestBundles.java create mode 100644 jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategy.java create mode 100644 jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java create mode 100644 jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategyTest.java create mode 100644 jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java diff --git a/jackrabbit-it-osgi/pom.xml b/jackrabbit-it-osgi/pom.xml index 6503f5edd6a..4babd4fcbe4 100644 --- a/jackrabbit-it-osgi/pom.xml +++ b/jackrabbit-it-osgi/pom.xml @@ -71,15 +71,13 @@ test - org.apache.httpcomponents - httpcore-osgi - 4.4.16 + org.apache.httpcomponents.core5 + httpcore5 test - org.apache.httpcomponents - httpclient-osgi - 4.5.14 + org.apache.httpcomponents.client5 + httpclient5 test diff --git a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/OSGiIT.java b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/OSGiIT.java index d30ac442e69..c96edad9552 100644 --- a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/OSGiIT.java +++ b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/OSGiIT.java @@ -17,7 +17,6 @@ package org.apache.jackrabbit.osgi; import static org.junit.Assert.assertEquals; -import static org.ops4j.pax.exam.CoreOptions.bundle; import static org.ops4j.pax.exam.CoreOptions.frameworkProperty; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; @@ -28,7 +27,6 @@ import java.io.File; import java.io.IOException; import java.net.URI; -import java.net.MalformedURLException; import java.net.URISyntaxException; import javax.inject.Inject; @@ -103,14 +101,8 @@ private String getConfigDir(){ return new File(new File("src", "test"), "config").getAbsolutePath(); } - private Option jarBundles() throws MalformedURLException { - DefaultCompositeOption composite = new DefaultCompositeOption(); - for (File bundle : new File("target", "test-bundles").listFiles()) { - if (bundle.getName().endsWith(".jar") && bundle.isFile()) { - composite.add(bundle(bundle.toURI().toURL().toString())); - } - } - return composite; + private Option jarBundles() throws IOException { + return TestBundles.jarBundles(); } @Inject diff --git a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/TestBundles.java b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/TestBundles.java new file mode 100644 index 00000000000..8e38d0cf24d --- /dev/null +++ b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/TestBundles.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.osgi; + +import static org.ops4j.pax.exam.CoreOptions.bundle; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.options.DefaultCompositeOption; + +/** + * Provisions the JARs assembled into {@code target/test-bundles} for the OSGi tests. + *

+ * Anything that is not already a bundle is repacked as one. HttpComponents publishes no + * OSGi bundles for the 5.x line: {@code httpclient5-osgi} and {@code httpcore5-osgi} stop + * at 5.0-beta, and the plain 5.x JARs carry no {@code Bundle-SymbolicName}, so without + * this the bundles depending on them cannot resolve. This is what the {@code wrap:} URL + * handler does; provisioning {@code wrap:} URLs was tried first and left the Pax Exam + * native container unable to start, so the repack is done here instead. + */ +public final class TestBundles { + + private TestBundles() { + } + + public static Option jarBundles() throws IOException { + DefaultCompositeOption composite = new DefaultCompositeOption(); + for (File jar : new File("target", "test-bundles").listFiles()) { + if (jar.getName().endsWith(".jar") && jar.isFile()) { + File installable = isBundle(jar) ? jar : asBundle(jar); + composite.add(bundle(installable.toURI().toURL().toString())); + } + } + return composite; + } + + private static boolean isBundle(File jar) throws IOException { + try (JarFile jarFile = new JarFile(jar)) { + Manifest manifest = jarFile.getManifest(); + return manifest != null + && manifest.getMainAttributes().getValue("Bundle-SymbolicName") != null; + } + } + + /** + * Repacks a plain JAR as an OSGi bundle exporting every package it contains and + * resolving its own dependencies dynamically. + */ + private static File asBundle(File jar) throws IOException { + File dir = new File("target", "wrapped-bundles"); + dir.mkdirs(); + File wrapped = new File(dir, jar.getName()); + + try (JarFile in = new JarFile(jar)) { + Set packages = new TreeSet(); + for (Enumeration e = in.entries(); e.hasMoreElements();) { + String name = e.nextElement().getName(); + int slash = name.lastIndexOf('/'); + if (name.endsWith(".class") && slash > 0) { + packages.add(name.substring(0, slash).replace('/', '.')); + } + } + + String symbolicName = jar.getName().substring(0, jar.getName().length() - ".jar".length()); + Manifest manifest = new Manifest(); + Attributes main = manifest.getMainAttributes(); + main.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + main.putValue("Bundle-ManifestVersion", "2"); + main.putValue("Bundle-SymbolicName", symbolicName); + main.putValue("Bundle-Version", bundleVersion(in)); + main.putValue("Export-Package", String.join(",", packages)); + main.putValue("DynamicImport-Package", "*"); + + byte[] buffer = new byte[8192]; + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(wrapped), manifest)) { + for (Enumeration e = in.entries(); e.hasMoreElements();) { + JarEntry entry = e.nextElement(); + if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) { + continue; + } + out.putNextEntry(new JarEntry(entry.getName())); + try (InputStream content = in.getInputStream(entry)) { + copy(content, out, buffer); + } + out.closeEntry(); + } + } + } + return wrapped; + } + + /** + * Derives an OSGi-legal Bundle-Version from the JAR's Implementation-Version. The + * assembly renames JARs to {@code .jar}, so the file name carries no + * version. + */ + private static String bundleVersion(JarFile jar) throws IOException { + Manifest manifest = jar.getManifest(); + String version = manifest == null + ? null : manifest.getMainAttributes().getValue("Implementation-Version"); + if (version == null) { + return "0.0.0"; + } + // OSGi wants major.minor.micro; anything after the third segment is a qualifier + String[] parts = version.split("[.-]"); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < 3; i++) { + result.append(i < parts.length && parts[i].matches("\\d+") ? parts[i] : "0"); + if (i < 2) { + result.append('.'); + } + } + return result.toString(); + } + + private static void copy(InputStream in, OutputStream out, byte[] buffer) throws IOException { + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } +} diff --git a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_4_OSGiIT.java b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_4_OSGiIT.java index b3371f8e328..8c284aa1df7 100644 --- a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_4_OSGiIT.java +++ b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_4_OSGiIT.java @@ -17,7 +17,6 @@ package org.apache.jackrabbit.osgi.slf4j2; import static org.junit.Assert.assertEquals; -import static org.ops4j.pax.exam.CoreOptions.bundle; import static org.ops4j.pax.exam.CoreOptions.frameworkProperty; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; @@ -115,14 +114,9 @@ private String getConfigDir(){ return new File(new File("src", "test"), "config").getAbsolutePath(); } - private Option jarBundles() throws MalformedURLException { - DefaultCompositeOption composite = new DefaultCompositeOption(); - for (File bundle : new File("target", "test-bundles").listFiles()) { - if (bundle.getName().endsWith(".jar") && bundle.isFile()) { - composite.add(bundle(bundle.toURI().toURL().toString())); - } - } - return composite; + private Option jarBundles() throws IOException { + // shared with OSGiIT: repacks the HttpClient 5 JARs, which are not bundles + return org.apache.jackrabbit.osgi.TestBundles.jarBundles(); } @Inject diff --git a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_9_OSGiIT.java b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_9_OSGiIT.java index a814892f618..043c43aa568 100644 --- a/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_9_OSGiIT.java +++ b/jackrabbit-it-osgi/src/test/java/org/apache/jackrabbit/osgi/slf4j2/Slf4j_v2_Tika_v2_9_OSGiIT.java @@ -17,7 +17,6 @@ package org.apache.jackrabbit.osgi.slf4j2; import static org.junit.Assert.assertEquals; -import static org.ops4j.pax.exam.CoreOptions.bundle; import static org.ops4j.pax.exam.CoreOptions.frameworkProperty; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; @@ -115,14 +114,9 @@ private String getConfigDir(){ return new File(new File("src", "test"), "config").getAbsolutePath(); } - private Option jarBundles() throws MalformedURLException { - DefaultCompositeOption composite = new DefaultCompositeOption(); - for (File bundle : new File("target", "test-bundles").listFiles()) { - if (bundle.getName().endsWith(".jar") && bundle.isFile()) { - composite.add(bundle(bundle.toURI().toURL().toString())); - } - } - return composite; + private Option jarBundles() throws IOException { + // shared with OSGiIT: repacks the HttpClient 5 JARs, which are not bundles + return org.apache.jackrabbit.osgi.TestBundles.jarBundles(); } @Inject diff --git a/jackrabbit-it-osgi/test-bundles.xml b/jackrabbit-it-osgi/test-bundles.xml index 367af71f31a..94b2a09592d 100644 --- a/jackrabbit-it-osgi/test-bundles.xml +++ b/jackrabbit-it-osgi/test-bundles.xml @@ -36,8 +36,8 @@ org.apache.jackrabbit:jackrabbit-jcr-commons org.apache.jackrabbit:jackrabbit-spi org.apache.jackrabbit:jackrabbit-spi-commons - org.apache.httpcomponents:httpclient-osgi - org.apache.httpcomponents:httpcore-osgi + org.apache.httpcomponents.client5:httpclient5 + org.apache.httpcomponents.core5:httpcore5 org.apache.jackrabbit:jackrabbit-webdav org.apache.jackrabbit:jackrabbit-jcr-server diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java index e009efff65e..b2fa2b82f1d 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java @@ -24,13 +24,13 @@ import java.util.List; import java.util.Set; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.util.EntityUtils; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.MultiStatusResponse; @@ -60,8 +60,8 @@ public class BindTest extends WebDAVTestBase { // http://greenbytes.de/tech/webdav/rfc5842.html#rfc.section.8.1 public void testOptions() throws IOException { HttpOptions options = new HttpOptions(this.uri); - HttpResponse response = this.client.execute(options, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, options, this.context); + int status = response.getCode(); assertEquals(200, status); Set allow = options.getAllowedMethods(response); Set complianceClasses = options.getDavComplianceClasses(response); @@ -80,23 +80,23 @@ public void testResourceId() throws IOException, DavException, URISyntaxExceptio int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); HttpPut put = new HttpPut(testuri1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); // enabling version control always makes the resource referenceable HttpVersionControl versioncontrol = new HttpVersionControl(testuri1); - status = this.client.execute(versioncontrol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, versioncontrol, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201); URI resourceId = getResourceId(testuri1); HttpMove move = new HttpMove(testuri1, testuri2, true); - status = this.client.execute(move, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, move, this.context).getCode(); assertEquals(201, status); URI resourceId2 = getResourceId(testuri2); @@ -113,8 +113,8 @@ private URI getResourceId(String uri) throws IOException, DavException, URISynta DavPropertyNameSet names = new DavPropertyNameSet(); names.add(BindConstants.RESOURCEID); HttpPropfind propfind = new HttpPropfind(uri, names, 0); - HttpResponse response = this.client.execute(propfind, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, propfind, this.context); + int status = response.getCode(); assertEquals(207, status); MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(response); MultiStatusResponse[] responses = multistatus.getResponses(); @@ -133,8 +133,8 @@ private DavProperty getParentSet(String uri) throws IOException, DavException, U DavPropertyNameSet names = new DavPropertyNameSet(); names.add(BindConstants.PARENTSET); HttpPropfind propfind = new HttpPropfind(uri, names, 0); - HttpResponse response = this.client.execute(propfind, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, propfind, this.context); + int status = response.getCode(); assertEquals(207, status); MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(response); MultiStatusResponse[] responses = multistatus.getResponses(); @@ -153,54 +153,54 @@ public void testSimpleBind() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //create new binding of R with path bindtest2/res2 HttpBind bind = new HttpBind(subcol2, new BindInfo(testres1, "res2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(201, status); //check if both bindings report the same DAV:resource-id assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); //compare representations retrieved with both paths HttpGet get = new HttpGet(testres1); - HttpResponse resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); //modify R using the new path put = new HttpPut(testres2); put.setEntity(new StringEntity("bar", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); //compare representations retrieved with both paths get = new HttpGet(testres1); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("bar", EntityUtils.toString(resp.getEntity())); get = new HttpGet(testres2); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("bar", EntityUtils.toString(resp.getEntity())); } finally { @@ -217,44 +217,44 @@ public void testRebind() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); // enabling version control always makes the resource referenceable HttpVersionControl versioncontrol = new HttpVersionControl(testres1); - status = this.client.execute(versioncontrol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, versioncontrol, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201); URI r1 = this.getResourceId(testres1); HttpGet get = new HttpGet(testres1); - HttpResponse resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); //rebind R with path bindtest2/res2 HttpRebind rebind = new HttpRebind(subcol2, new RebindInfo(testres1, "res2")); - status = this.client.execute(rebind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, rebind, this.context).getCode(); assertEquals(201, status); URI r2 = this.getResourceId(testres2); get = new HttpGet(testres2); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); @@ -263,7 +263,7 @@ public void testRebind() throws Exception { //verify that the initial binding is gone HttpHead head = new HttpHead(testres1); - status = this.client.execute(head, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, head, this.context).getCode(); assertEquals(404, status); } finally { delete(testcol); @@ -279,55 +279,55 @@ public void testBindOverwrite() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //create new resource R' with path bindtest2/res2 put = new HttpPut(testres2); put.setEntity(new StringEntity("bar", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //try to create new binding of R with path bindtest2/res2 and Overwrite:F HttpBind bind = new HttpBind(subcol2, new BindInfo(testres1, "res2")); bind.addHeader("Overwrite", "F"); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(412, status); //verify that bindtest2/res2 still points to R' HttpGet get = new HttpGet(testres2); - HttpResponse resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("bar", EntityUtils.toString(resp.getEntity())); //create new binding of R with path bindtest2/res2 bind = new HttpBind(subcol2, new BindInfo(testres1, "res2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); //verify that bindtest2/res2 now points to R get = new HttpGet(testres2); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); //verify that the initial binding is still there HttpHead head = new HttpHead(testres1); - status = this.client.execute(head, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, head, this.context).getCode(); assertEquals(200, status); } finally { delete(testcol); @@ -343,60 +343,60 @@ public void testRebindOverwrite() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path testSimpleBind/bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); // enabling version control always makes the resource referenceable HttpVersionControl versioncontrol = new HttpVersionControl(testres1); - status = this.client.execute(versioncontrol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, versioncontrol, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201); //create new resource R' with path testSimpleBind/bindtest2/res2 put = new HttpPut(testres2); put.setEntity(new StringEntity("bar", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //try rebind R with path testSimpleBind/bindtest2/res2 and Overwrite:F HttpRebind rebind = new HttpRebind(subcol2, new RebindInfo(testres1, "res2")); rebind.addHeader("Overwrite", "F"); - status = this.client.execute(rebind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, rebind, this.context).getCode(); assertEquals(412, status); //verify that testSimpleBind/bindtest2/res2 still points to R' HttpGet get = new HttpGet(testres2); - HttpResponse resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("bar", EntityUtils.toString(resp.getEntity())); //rebind R with path testSimpleBind/bindtest2/res2 rebind = new HttpRebind(subcol2, new RebindInfo(testres1, "res2")); - status = this.client.execute(rebind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, rebind, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); //verify that testSimpleBind/bindtest2/res2 now points to R get = new HttpGet(testres2); - resp = this.client.execute(get, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, get, this.context); + status = resp.getCode(); assertEquals(200, status); assertEquals("foo", EntityUtils.toString(resp.getEntity())); //verify that the initial binding is gone HttpHead head = new HttpHead(testres1); - status = this.client.execute(head, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, head, this.context).getCode(); assertEquals(404, status); } finally { delete(testcol); @@ -412,24 +412,24 @@ public void testParentSet() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path testSimpleBind/bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //create new binding of R with path testSimpleBind/bindtest2/res2 HttpBind bind = new HttpBind(subcol2, new BindInfo(testres1, "res2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(201, status); //check if both bindings report the same DAV:resource-id assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); @@ -483,43 +483,43 @@ public void testBindCollections() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(a1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(a2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create collection resource C mkcol = new HttpMkcol(b1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(c1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create plain resource R HttpPut put = new HttpPut(x1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //create new binding of C with path a2/b2 HttpBind bind = new HttpBind(a2, new BindInfo(b1, "b2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(201, status); //check if both bindings report the same DAV:resource-id assertEquals(this.getResourceId(b1), this.getResourceId(b2)); mkcol = new HttpMkcol(c2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new binding of R with path a2/b2/c2/r2 bind = new HttpBind(c2, new BindInfo(x1, "x2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(201, status); //check if both bindings report the same DAV:resource-id assertEquals(this.getResourceId(x1), this.getResourceId(x2)); @@ -550,41 +550,41 @@ public void testUnbind() throws Exception { int status; try { HttpMkcol mkcol = new HttpMkcol(testcol); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol1); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); mkcol = new HttpMkcol(subcol2); - status = this.client.execute(mkcol, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, mkcol, this.context).getCode(); assertEquals(201, status); //create new resource R with path testSimpleBind/bindtest1/res1 HttpPut put = new HttpPut(testres1); put.setEntity(new StringEntity("foo", ContentType.create("text/plain", "UTF-8"))); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); //create new binding of R with path testSimpleBind/bindtest2/res2 HttpBind bind = new HttpBind(subcol2, new BindInfo(testres1, "res2")); - status = this.client.execute(bind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, bind, this.context).getCode(); assertEquals(201, status); //check if both bindings report the same DAV:resource-id assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); //remove new path HttpUnbind unbind = new HttpUnbind(subcol2, new UnbindInfo("res2")); - status = this.client.execute(unbind, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, unbind, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); //verify that the new binding is gone HttpHead head = new HttpHead(testres2); - status = this.client.execute(head, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, head, this.context).getCode(); assertEquals(404, status); //verify that the initial binding is still there head = new HttpHead(testres1); - status = this.client.execute(head, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, head, this.context).getCode(); assertEquals(200, status); } finally { delete(testcol); diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ConditionalsTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ConditionalsTest.java index cfb0af7bd40..1c9e8675a2a 100755 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ConditionalsTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ConditionalsTest.java @@ -19,13 +19,13 @@ import java.io.IOException; import java.text.ParseException; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.util.EntityUtils; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; public class ConditionalsTest extends WebDAVTestBase { @@ -39,8 +39,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { { HttpPut put = new HttpPut(testUri); put.setEntity(new StringEntity("foobar")); - HttpResponse response = this.client.execute(put, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); + int status = response.getCode(); assertEquals(201, status); } @@ -51,8 +51,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { Header lm = null; { HttpHead head = new HttpHead(testUri); - HttpResponse response = this.client.execute(head, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, head, this.context); + int status = response.getCode(); assertEquals(200, status); lm = response.getFirstHeader("last-modified"); assertNotNull(lm); @@ -65,8 +65,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { { HttpGet get = new HttpGet(testUri); get.setHeader("If-Modified-Since", lm.getValue()); - HttpResponse response = this.client.execute(get, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, get, this.context); + int status = response.getCode(); assertEquals(304, status); if (etag != null) { Header newetag = response.getFirstHeader("etag"); @@ -79,8 +79,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { { HttpHead head = new HttpHead(testUri); head.setHeader("If-Modified-Since", lm.getValue()); - HttpResponse response = this.client.execute(head, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, head, this.context); + int status = response.getCode(); assertEquals(304, status); if (etag != null) { Header newetag = response.getFirstHeader("etag"); @@ -93,8 +93,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { { HttpHead head = new HttpHead(testUri); head.setHeader("If-Modified-Since", "broken"); - HttpResponse response = this.client.execute(head, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, head, this.context); + int status = response.getCode(); assertEquals(200, status); } @@ -103,8 +103,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { HttpGet req = new HttpGet(testUri); req.addHeader("If-Modified-Since", lm.getValue()); req.addHeader("If-Modified-Since", "foo"); - HttpResponse response = this.client.execute(req, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, req, this.context); + int status = response.getCode(); assertEquals(200, status); EntityUtils.consume(response.getEntity()); } @@ -120,8 +120,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { // verify last modified did not change { HttpHead head = new HttpHead(testUri); - HttpResponse response = this.client.execute(head, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, head, this.context); + int status = response.getCode(); assertEquals(200, status); Header newlm = response.getFirstHeader("last-modified"); assertNotNull(newlm); @@ -133,8 +133,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { HttpPut put = new HttpPut(testUri); put.setHeader("If-Unmodified-Since", lm.getValue()); put.setEntity(new StringEntity("qux")); - HttpResponse response = this.client.execute(put, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); + int status = response.getCode(); assertEquals(204, status); } @@ -143,8 +143,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { HttpPut put = new HttpPut(testUri); put.setHeader("If-Unmodified-Since", lm.getValue()); put.setEntity(new StringEntity("lazydog")); - HttpResponse response = this.client.execute(put, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); + int status = response.getCode(); assertEquals(412, status); } @@ -154,8 +154,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { put.addHeader("If-Unmodified-Since", lm.getValue()); put.addHeader("If-Unmodified-Since", "foo"); put.setEntity(new StringEntity("qux")); - HttpResponse response = this.client.execute(put, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); + int status = response.getCode(); assertEquals(204, status); } } finally { @@ -166,8 +166,8 @@ public void testPutCheckLastModified() throws IOException, ParseException { public void testGetCollectionEtag() throws IOException, ParseException { String testUri = this.uri.toString() + (this.uri.toString().endsWith("/") ? "" : "/"); HttpGet get = new HttpGet(testUri); - HttpResponse response = this.client.execute(get, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, get, this.context); + int status = response.getCode(); assertEquals(200, status); Header etag = response.getFirstHeader("etag"); if (etag != null) { diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ContentCodingTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ContentCodingTest.java index d7553dcf9bc..770382130b7 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ContentCodingTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ContentCodingTest.java @@ -26,13 +26,14 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicHeader; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatusResponse; @@ -45,7 +46,7 @@ public void testPutNoContentCoding() throws IOException { try { HttpPut put = new HttpPut(testUri); put.setEntity(new StringEntity("foobar")); - int status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals(201, status); } finally { delete(testUri); @@ -57,10 +58,9 @@ public void testPutUnknownContentCoding() throws IOException { int status = -1; try { HttpPut put = new HttpPut(testUri); - StringEntity entity = new StringEntity("foobarfoobarfoobar"); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "qux")); + StringEntity entity = new StringEntity("foobarfoobarfoobar", ContentType.TEXT_PLAIN, "qux", false); put.setEntity(entity); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertTrue("server must signal error for unknown content coding, got: " + status, status == 415); } finally { if (status / 2 == 100) { @@ -77,16 +77,15 @@ public void testPutGzipContentCoding() throws IOException { HttpPut put = new HttpPut(testUri); byte gzbytes[] = asGzipOctets(bytes); assertTrue(gzbytes.length != bytes.length); - ByteArrayEntity entity = new ByteArrayEntity(gzbytes); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "gzip")); + ByteArrayEntity entity = new ByteArrayEntity(gzbytes, null, "gzip"); put.setEntity(entity); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertTrue("server create or signal error, got: " + status, status == 201 || status == 415); if (status / 2 == 100) { // check length HttpHead head = new HttpHead(testUri); - HttpResponse response = this.client.execute(head, this.context); - assertEquals(200, response.getStatusLine().getStatusCode()); + ClassicHttpResponse response = this.client.executeOpen(null, head, this.context); + assertEquals(200, response.getCode()); assertEquals(bytes.length, Integer.parseInt(response.getFirstHeader("Content-Length").getValue())); } } finally { @@ -98,8 +97,8 @@ public void testPutGzipContentCoding() throws IOException { public void testPropfindNoContentCoding() throws IOException, DavException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - HttpResponse response = this.client.execute(propfind, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, propfind, this.context); + int status = response.getCode(); assertEquals(207, status); List encodings = getContentCodings(response); assertTrue("Accept should list 'gzip' but did not: " + encodings, encodings.contains("gzip")); @@ -109,8 +108,8 @@ public void testPropfindNoContentCoding() throws IOException, DavException { public void testPropfindAcceptReponseEncoding() throws IOException, DavException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); propfind.setHeader(new BasicHeader("Accept-Encoding", "gzip;q=0.555")); - HttpResponse response = this.client.execute(propfind, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, propfind, this.context); + int status = response.getCode(); assertEquals(207, status); MultiStatusResponse[] responses = propfind.getResponseBodyAsMultiStatus(response).getResponses(); assertEquals(1, responses.length); @@ -120,11 +119,10 @@ public void testPropfindAcceptReponseEncoding() throws IOException, DavException public void testPropfindUnknownContentCoding() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - StringEntity entity = new StringEntity(PF); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "qux")); + StringEntity entity = new StringEntity(PF, ContentType.TEXT_PLAIN, "qux", false); propfind.setEntity(entity); - HttpResponse response = this.client.execute(propfind, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, propfind, this.context); + int status = response.getCode(); assertTrue("server must signal error for unknown content coding, got: " + status, status == 415); List encodings = getContentCodings(response); assertTrue("Accept should list 'gzip' but did not: " + encodings, encodings.contains("gzip")); @@ -133,10 +131,9 @@ public void testPropfindUnknownContentCoding() throws IOException { public void testPropfindGzipContentCoding() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(PF)); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "gzip")); + ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(PF), null, "gzip"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(207, status); } @@ -144,47 +141,42 @@ public void testPropfindGzipContentCoding() throws IOException { // coding name public void testPropfindGzipContentCodingTwice() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(asGzipOctets(PF))); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "gziP,, Gzip")); + ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(asGzipOctets(PF)), null, "gziP,, Gzip"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(207, status); } // double encoded, but only when encoding in header field public void testPropfindGzipContentCodingBadSpec() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(asGzipOctets(PF))); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "gzip")); + ByteArrayEntity entity = new ByteArrayEntity(asGzipOctets(asGzipOctets(PF)), null, "gzip"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(400, status); } public void testPropfindDeflateContentCoding() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(PF)); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "deflate")); + ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(PF), null, "deflate"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(207, status); } public void testPropfindGzipDeflateContentCoding() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(asGzipOctets(PF))); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "gzip, deflate")); + ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(asGzipOctets(PF)), null, "gzip, deflate"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(207, status); } public void testPropfindGzipDeflateContentCodingMislabeled() throws IOException { HttpPropfind propfind = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, 0); - ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(asGzipOctets(PF))); - entity.setContentEncoding(new BasicHeader("Content-Encoding", "deflate, gzip")); + ByteArrayEntity entity = new ByteArrayEntity(asDeflateOctets(asGzipOctets(PF)), null, "deflate, gzip"); propfind.setEntity(entity); - int status = this.client.execute(propfind, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, propfind, this.context).getCode(); assertEquals(400, status); } @@ -214,7 +206,7 @@ private static byte[] asDeflateOctets(byte[] input) throws IOException { return bos.toByteArray(); } - private static List getContentCodings(HttpResponse response) { + private static List getContentCodings(ClassicHttpResponse response) { List result = Collections.emptyList(); for (Header l : response.getHeaders("Accept-Encoding")) { for (String h : l.getValue().split(",")) { diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/HttpsSelfSignedTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/HttpsSelfSignedTest.java index 6cee0eeb801..2a4d9697942 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/HttpsSelfSignedTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/HttpsSelfSignedTest.java @@ -21,9 +21,9 @@ import javax.net.ssl.SSLHandshakeException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; public class HttpsSelfSignedTest extends WebDAVTestBase { @@ -34,7 +34,7 @@ public void testPutCheckLastModified() throws IOException, ParseException { String testUri = this.httpsUri.toString(); HttpPut put = new HttpPut(testUri); put.setEntity(new StringEntity("foobar")); - HttpResponse response = this.client.execute(put, this.context); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); fail("should failt with SSLHandshakeException, but got: " + response); } catch (SSLHandshakeException expected) { } diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ProppatchTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ProppatchTest.java index dc7d0758cd5..64b20ab219a 100755 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ProppatchTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/ProppatchTest.java @@ -18,10 +18,10 @@ import java.io.IOException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.util.EntityUtils; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.client.methods.HttpPropfind; @@ -44,7 +44,7 @@ public void testPropPatchSurrogate() throws IOException, DavException { HttpPut put = new HttpPut(testuri); put.setEntity(new StringEntity("1")); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals("status: " + status, 201, status); DavPropertyName name = DavPropertyName.create("foobar", Namespace.EMPTY_NAMESPACE); @@ -52,16 +52,16 @@ public void testPropPatchSurrogate() throws IOException, DavException { DavProperty foobar = new DefaultDavProperty<>(name, "\uD83D\uDCA9"); props.add(foobar); HttpProppatch proppatch = new HttpProppatch(testuri, props, new DavPropertyNameSet()); - HttpResponse resp = this.client.execute(proppatch, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, proppatch, this.context); + status = resp.getCode(); assertEquals(207, status); EntityUtils.consume(resp.getEntity()); DavPropertyNameSet names = new DavPropertyNameSet(); names.add(name); HttpPropfind propfind = new HttpPropfind(testuri, names, 0); - resp = this.client.execute(propfind, this.context); - status = resp.getStatusLine().getStatusCode(); + resp = this.client.executeOpen(null, propfind, this.context); + status = resp.getCode(); assertEquals(207, status); MultiStatusResponse[] responses = propfind.getResponseBodyAsMultiStatus(resp).getResponses(); assertEquals(1, responses.length); diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/PutTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/PutTest.java index 056dc014b9e..7238f71985f 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/PutTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/PutTest.java @@ -19,9 +19,9 @@ import java.io.IOException; import java.text.ParseException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; /** * Test cases for HTTP PUT method @@ -33,8 +33,8 @@ public void testPutWithContentRange() throws IOException, ParseException { HttpPut put = new HttpPut(testUri); put.addHeader("Content-Range", "bytes 0-5/6"); put.setEntity(new StringEntity("foobar")); - HttpResponse response = this.client.execute(put, this.context); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, put, this.context); + int status = response.getCode(); assertEquals(400, status); } } diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java index ec2a6718586..71f420300a9 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java @@ -20,10 +20,10 @@ import java.net.URI; import java.net.URISyntaxException; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.jackrabbit.webdav.client.methods.HttpMove; /** @@ -40,48 +40,48 @@ public void testMove() throws IOException, URISyntaxException { // make sure the scheme is removed assertFalse(destinationpath.contains(":")); - HttpRequestBase requestBase = null; + HttpUriRequestBase requestBase = null; try { requestBase = new HttpPut(testuri); - int status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); // try to move outside the servlet's name space requestBase = new HttpMove(testuri, "/foobar", true); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 502); - requestBase.releaseConnection(); + requestBase.reset(); // try a relative path requestBase = new HttpMove(testuri, "foobar", true); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 400); - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpMove(testuri, destinationpath, true); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpHead(destinationuri); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200); - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpHead(testuri); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 404); } finally { - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpDelete(testuri); - int status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204 || status == 404); - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpDelete(destinationuri); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204 || status == 404); - requestBase.releaseConnection(); + requestBase.reset(); } } } diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918IfHeaderTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918IfHeaderTest.java index f6507321338..585ba1a0134 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918IfHeaderTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918IfHeaderTest.java @@ -20,11 +20,11 @@ import java.net.URI; import java.net.URISyntaxException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.jackrabbit.webdav.client.methods.HttpLock; import org.apache.jackrabbit.webdav.lock.LockInfo; import org.apache.jackrabbit.webdav.lock.Scope; @@ -46,17 +46,17 @@ public void testPutIfEtag() throws IOException { String condition = "<" + testuri + "> ([" + "\"an-etag-this-testcase-invented\"" + "])"; put.setEntity(new StringEntity("1")); put.setHeader("If", condition); - int status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals("status: " + status, 412, status); - put.releaseConnection(); + put.reset(); } finally { - put.releaseConnection(); + put.reset(); HttpDelete delete = new HttpDelete(testuri); - int status = this.client.execute(delete, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, delete, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204 || status == 404); - delete.releaseConnection(); + delete.reset(); } } @@ -65,78 +65,78 @@ public void testPutIfLockToken() throws IOException, URISyntaxException { String testuri = this.root + "iflocktest"; String locktoken = null; - HttpRequestBase requestBase = null; + HttpUriRequestBase requestBase = null; try { requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("1")); - int status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 201 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpLock(testuri, new LockInfo( Scope.EXCLUSIVE, Type.WRITE, "testcase", 10000, true)); - HttpResponse response = this.client.execute(requestBase, this.context); - status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = this.client.executeOpen(null, requestBase, this.context); + status = response.getCode(); assertEquals("status", 200, status); locktoken = ((HttpLock)requestBase).getLockToken(response); assertNotNull(locktoken); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite without lock token requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("2")); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertEquals("status: " + status, 423, status); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite using bad lock token requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("2")); requestBase.setHeader("If", "(<" + "DAV:foobar" + ">)"); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertEquals("status: " + status, 412, status); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite using correct lock token, using No-Tag-list format requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("2")); requestBase.setHeader("If", "(<" + locktoken + ">)"); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite using correct lock token, using Tagged-list format // and full URI requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("3")); requestBase.setHeader("If", "<" + testuri + ">" + "(<" + locktoken + ">)"); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite using correct lock token, using Tagged-list format // and absolute path only requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("4")); requestBase.setHeader("If", "<" + new URI(testuri).getRawPath() + ">" + "(<" + locktoken + ">)"); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); - requestBase.releaseConnection(); + requestBase.reset(); // try to overwrite using correct lock token, using Tagged-list format // and bad path requestBase = new HttpPut(testuri); ((HttpPut)requestBase).setEntity(new StringEntity("5")); requestBase.setHeader("If", "" + "(<" + locktoken + ">)"); - status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 404 || status == 412); } finally { - requestBase.releaseConnection(); + requestBase.reset(); requestBase = new HttpDelete(testuri); if (locktoken != null) { requestBase.setHeader("If", "(<" + locktoken + ">)"); } - int status = this.client.execute(requestBase, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, requestBase, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204 || status == 404); } } diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918PropfindTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918PropfindTest.java index a1c6cbd5f92..0185c040d00 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918PropfindTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918PropfindTest.java @@ -18,9 +18,9 @@ import java.io.IOException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; @@ -40,7 +40,7 @@ public class RFC4918PropfindTest extends WebDAVTestBase { public void testOptions() throws IOException { HttpOptions options = new HttpOptions(this.root); - HttpResponse response = this.client.execute(options, this.context); + ClassicHttpResponse response = this.client.executeOpen(null, options, this.context); assertTrue(options.getDavComplianceClasses(response).contains("3")); } @@ -52,14 +52,14 @@ public void testPropfindInclude() throws IOException, DavException { try { HttpPut put = new HttpPut(testuri); put.setEntity(new StringEntity("1")); - status = this.client.execute(put, this.context).getStatusLine().getStatusCode(); + status = this.client.executeOpen(null, put, this.context).getCode(); assertEquals("status: " + status, 201, status); DavPropertyNameSet names = new DavPropertyNameSet(); names.add(DeltaVConstants.COMMENT); HttpPropfind propfind = new HttpPropfind(testuri, DavConstants.PROPFIND_ALL_PROP_INCLUDE, names, 0); - HttpResponse resp = this.client.execute(propfind, this.context); - status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, propfind, this.context); + status = resp.getCode(); assertEquals(207, status); MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(resp); diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RemotingTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RemotingTest.java index ee4a780685e..8eedee1d3d8 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RemotingTest.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/RemotingTest.java @@ -18,7 +18,7 @@ import java.io.IOException; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; @@ -43,8 +43,8 @@ public void testRoot() throws IOException, DavException { names.add(pntn); HttpPropfind propfind = new HttpPropfind(testuri, DavConstants.PROPFIND_BY_PROPERTY, names, 0); - HttpResponse resp = this.client.execute(propfind, this.context); - int status = resp.getStatusLine().getStatusCode(); + ClassicHttpResponse resp = this.client.executeOpen(null, propfind, this.context); + int status = resp.getCode(); assertEquals(207, status); MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(resp); diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/WebDAVTestBase.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/WebDAVTestBase.java index 97c9347b8f9..ed70e88dcb9 100644 --- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/WebDAVTestBase.java +++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/server/WebDAVTestBase.java @@ -27,19 +27,21 @@ import javax.servlet.ServletException; import org.apache.commons.io.IOUtils; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.util.Timeout; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.auth.AuthCache; +import org.apache.hc.client5.http.auth.CredentialsStore; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.classic.methods.HttpDelete; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.auth.BasicAuthCache; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.jackrabbit.core.RepositoryContext; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; @@ -83,6 +85,9 @@ public class WebDAVTestBase extends AbstractJCRTest { public HttpClient client; public HttpClientContext context; + // see the comment on the HTTPS connector below + private static final String LOOPBACK = "127.0.0.1"; + private static final String KEYSTORE = "keystore"; private static final String KEYSTOREPW = "geheimer"; @@ -153,7 +158,12 @@ public Repository getRepository() { sslContextFactory.setTrustStorePassword(KEYSTOREPW); SslConnectionFactory cfac = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()); httpsConnector = new ServerConnector(server, cfac, new HttpConnectionFactory(new HttpConfiguration())); - httpsConnector.setHost("localhost"); + // Bind to a literal address rather than "localhost": where localhost + // resolves to both 127.0.0.1 and ::1, HttpClient 5 treats a TLS handshake + // failure on the first address as a reason to try the next one, so the + // SSLHandshakeException that HttpsSelfSignedTest asserts on would be + // replaced by a connection failure against the unbound address. + httpsConnector.setHost(LOOPBACK); httpsConnector.setPort(0); server.addConnector(httpsConnector); } @@ -168,21 +178,30 @@ public Repository getRepository() { this.uri = new URI("http", null, "localhost", httpConnector.getLocalPort(), "/default/", null, null); this.remotingUri = new URI("http", null, "localhost", httpConnector.getLocalPort(), REMOTING_PREFIX + "/", null, null); - this.httpsUri = new URI("https", null, "localhost", httpsConnector.getLocalPort(), "/default/", null, null); + this.httpsUri = new URI("https", null, LOOPBACK, httpsConnector.getLocalPort(), "/default/", null, null); this.root = this.uri.toASCIIString(); + // HttpClient 5 keeps a pooled connection leased until the response is closed or + // its entity consumed, including for status-only responses that HttpClient 4 + // released automatically. These tests read status codes without closing the + // response, so the default pool of 5 per route is exhausted quickly; size the + // pool for the busiest test class and fail fast rather than block for the + // default three minutes if it is ever exhausted anyway. PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); - //cm.setMaxTotal(100); + cm.setDefaultMaxPerRoute(100); + cm.setMaxTotal(100); HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort()); - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope(targetHost.getHostName(), targetHost.getPort()), - new UsernamePasswordCredentials("admin", "admin")); + UsernamePasswordCredentials credentials = + new UsernamePasswordCredentials("admin", "admin".toCharArray()); + CredentialsStore credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost), credentials); AuthCache authCache = new BasicAuthCache(); - // Generate BASIC scheme object and add it to the local auth cache + // Generate BASIC scheme object and add it to the local auth cache; + // HttpClient 5 only pre-authenticates from a primed scheme BasicScheme basicAuth = new BasicScheme(); + basicAuth.initPreemptive(credentials); authCache.put(targetHost, basicAuth); // Add AuthCache to the execution context @@ -190,14 +209,19 @@ public Repository getRepository() { this.context.setCredentialsProvider(credsProvider); this.context.setAuthCache(authCache); - this.client = HttpClients.custom().setConnectionManager(cm).build(); + this.client = HttpClients.custom() + .setConnectionManager(cm) + .setDefaultRequestConfig(RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofSeconds(10)) + .build()) + .build(); super.setUp(); } protected void delete(String uri) throws IOException { HttpDelete delete = new HttpDelete(uri); - int status = this.client.execute(delete, this.context).getStatusLine().getStatusCode(); + int status = this.client.executeOpen(null, delete, this.context).getCode(); assertTrue("status: " + status, status == 200 || status == 204); } diff --git a/jackrabbit-parent/pom.xml b/jackrabbit-parent/pom.xml index 9c888e264e3..3a9ce62cb32 100644 --- a/jackrabbit-parent/pom.xml +++ b/jackrabbit-parent/pom.xml @@ -60,6 +60,8 @@ 1.22.24 9.4.58.v20250814 2.4.1 + 5.6.1 + 5.4.2 ${project.build.sourceEncoding} 1.7.36 1.7.36 @@ -249,9 +251,8 @@ https://s.apache.org/jcr-2.0-javadoc/ https://jackrabbit.apache.org/oak/docs/apidocs - https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/ - https://hc.apache.org/httpcomponents-client-4.5.x/current/httpmime/apidocs/ - https://hc.apache.org/httpcomponents-core-4.4.x/current/httpcore/apidocs/ + https://hc.apache.org/httpcomponents-client-5.6.x/current/httpclient5/apidocs/ + https://hc.apache.org/httpcomponents-core-5.4.x/current/httpcore5/apidocs/ @@ -631,6 +632,16 @@ mockito-core 5.23.0 + + org.apache.httpcomponents.client5 + httpclient5 + ${httpcomponents.client5.version} + + + org.apache.httpcomponents.core5 + httpcore5 + ${httpcomponents.core5.version} + diff --git a/jackrabbit-spi2dav/pom.xml b/jackrabbit-spi2dav/pom.xml index ef70fbeda1e..3df3fb6b25f 100644 --- a/jackrabbit-spi2dav/pom.xml +++ b/jackrabbit-spi2dav/pom.xml @@ -41,6 +41,8 @@ **/ConnectionOptionsTest.java **/spi2dav/ConnectionTest.java + **/spi2dav/GetHeadRedirectStrategyTest.java + **/spi2davex/Rfc6532MultipartEntityTest.java @@ -175,9 +177,8 @@ javax.servlet-api - org.apache.httpcomponents - httpmime - 4.5.14 + org.apache.httpcomponents.client5 + httpclient5 junit diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ConnectionOptions.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ConnectionOptions.java index 210c2ff16fd..6f5a5de0661 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ConnectionOptions.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ConnectionOptions.java @@ -19,8 +19,8 @@ import java.util.HashMap; import java.util.Map; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java index f37b72e6c55..fb459a59d3c 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java @@ -18,8 +18,8 @@ import javax.jcr.SimpleCredentials; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; /** * CredentialsWrapper... @@ -38,10 +38,19 @@ class CredentialsWrapper { } else if (creds instanceof SimpleCredentials) { SimpleCredentials sCred = (SimpleCredentials) creds; userId = sCred.getUserID(); - this.credentials = new UsernamePasswordCredentials(userId, String.valueOf(sCred.getPassword())); + this.credentials = new UsernamePasswordCredentials(userId, sCred.getPassword()); } else { userId = ""; - this.credentials = new UsernamePasswordCredentials(creds.toString()); + // HttpClient 5 dropped the single-argument "username:password" + // constructor, so split the pair here instead + String usernamePassword = creds.toString(); + int colon = usernamePassword.indexOf(':'); + if (colon < 0) { + this.credentials = new UsernamePasswordCredentials(usernamePassword, new char[0]); + } else { + this.credentials = new UsernamePasswordCredentials(usernamePassword.substring(0, colon), + usernamePassword.substring(colon + 1).toCharArray()); + } } } diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ExceptionConverter.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ExceptionConverter.java index e0346c7bebb..00fd9359159 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ExceptionConverter.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/ExceptionConverter.java @@ -26,7 +26,7 @@ import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavMethods; @@ -46,7 +46,7 @@ public static RepositoryException generate(DavException davExc) { return generate(davExc, null); } - public static RepositoryException generate(DavException davExc, HttpRequestBase request) { + public static RepositoryException generate(DavException davExc, HttpUriRequestBase request) { String name = (request == null) ? "_undefined_" : request.getMethod(); int code = DavMethods.getMethodCode(name); return generate(davExc, code, name); diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategy.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategy.java new file mode 100644 index 00000000000..481456588e2 --- /dev/null +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategy.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.spi2dav; + +import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.protocol.HttpContext; + +/** + * Redirect strategy that only follows redirects for safe methods. + *

+ * HttpClient 5 follows redirects for every method, whereas HttpClient 4 restricted + * automatic redirects to GET and HEAD. Following a redirect automatically for a + * WebDAV method such as MOVE, COPY, PUT or DELETE would silently retarget a write + * at a different resource instead of surfacing the redirect to the caller, so this + * strategy reproduces the HttpClient 4 rules. + * + * @see org.apache.hc.client5.http.impl.DefaultRedirectStrategy + */ +final class GetHeadRedirectStrategy extends DefaultRedirectStrategy { + + static final GetHeadRedirectStrategy INSTANCE = new GetHeadRedirectStrategy(); + + @Override + public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) { + switch (response.getCode()) { + case HttpStatus.SC_MOVED_TEMPORARILY: + // 302 additionally required a Location header under HttpClient 4 + return isRedirectable(request.getMethod()) + && response.getFirstHeader(HttpHeaders.LOCATION) != null; + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_TEMPORARY_REDIRECT: + case HttpStatus.SC_PERMANENT_REDIRECT: + return isRedirectable(request.getMethod()); + case HttpStatus.SC_SEE_OTHER: + // 303 tells the client to fetch a different resource with GET, + // which is safe regardless of the original method + return true; + default: + return false; + } + } + + private static boolean isRedirectable(String method) { + return "GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method); + } +} diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java index 05755a9e297..37f0f2e84aa 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java @@ -56,42 +56,41 @@ import javax.net.ssl.SSLContext; import javax.xml.parsers.ParserConfigurationException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthCache; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.BasicAuthCache; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; -import org.apache.http.impl.conn.DefaultProxyRoutePlanner; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.protocol.HttpContext; -import org.apache.http.ssl.SSLContextBuilder; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.auth.AuthCache; +import org.apache.hc.client5.http.auth.CredentialsStore; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.auth.BasicAuthCache; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.util.Timeout; import org.apache.jackrabbit.commons.webdav.AtomFeedConstants; import org.apache.jackrabbit.commons.webdav.EventUtil; import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants; @@ -242,7 +241,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants { /** * Default value for the maximum number of connections per host such as - * configured with {@link PoolingHttpClientConnectionManager#setDefaultMaxPerRoute(int)}. + * configured with {@link PoolingHttpClientConnectionManagerBuilder#setMaxConnPerRoute(int)}. */ public static final int MAX_CONNECTIONS_DEFAULT = 20; @@ -260,7 +259,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants { private final HttpHost httpHost; private final ConcurrentMap clients; private final HttpClientBuilder httpClientBuilder; - private final Map commonCredentials; + private final Map commonCredentials; private final Map nodeTypeDefinitions = new HashMap(); @@ -345,7 +344,7 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, try { URI repositoryUri = computeRepositoryUri(uri); - httpHost = new HttpHost(repositoryUri.getHost(), repositoryUri.getPort(), repositoryUri.getScheme()); + httpHost = new HttpHost(repositoryUri.getScheme(), repositoryUri.getHost(), repositoryUri.getPort()); nsCache = new NamespaceCache(); uriResolver = new URIResolverImpl(repositoryUri, this, DomUtil.createDocument()); @@ -360,14 +359,24 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, HttpClientBuilder hcb = HttpClients.custom(); + // HttpClient 5 would follow redirects for every method; restrict this to the + // safe methods that HttpClient 4 redirected, so that a redirected MOVE, COPY, + // PUT or DELETE surfaces to the caller instead of silently retargeting + hcb.setRedirectStrategy(GetHeadRedirectStrategy.INSTANCE); + final SSLConnectionSocketFactory sslSocketFactory; // request config - RequestConfig requestConfig = RequestConfig.custom(). - setConnectTimeout(connectionOptions.getConnectionTimeoutMs()). - setConnectionRequestTimeout(connectionOptions.getRequestTimeoutMs()). - setSocketTimeout(connectionOptions.getSocketTimeoutMs()).build(); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(toLeaseTimeout(connectionOptions.getRequestTimeoutMs())) + .build(); hcb.setDefaultRequestConfig(requestConfig); + + // connect and socket timeouts moved from RequestConfig to ConnectionConfig in HttpClient 5 + ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(toTimeout(connectionOptions.getConnectionTimeoutMs())) + .setSocketTimeout(toTimeout(connectionOptions.getSocketTimeoutMs())) + .build(); if (Boolean.getBoolean("jackrabbit.client.useSystemProperties") || connectionOptions.isUseSystemPropertes()) { log.debug("Using system properties for establishing connection!"); @@ -391,7 +400,6 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, if (connectionOptions.isAllowSelfSignedCertificates()) { log.warn("Nonsecure TLS setting: Accepting self-signed certificates!"); sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); - hcb.setSSLContext(sslContext); } else { sslContext = SSLContextBuilder.create().build(); } @@ -408,33 +416,33 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, } } - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslSocketFactory) - .build(); + PoolingHttpClientConnectionManagerBuilder cmgrBuilder = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslSocketFactory) + .setDefaultConnectionConfig(connectionConfig); - PoolingHttpClientConnectionManager cmgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry); int maxConnections = connectionOptions.getMaxConnections(); if (maxConnections > 0) { - cmgr.setDefaultMaxPerRoute(connectionOptions.getMaxConnections()); - cmgr.setMaxTotal(connectionOptions.getMaxConnections()); + cmgrBuilder.setMaxConnPerRoute(maxConnections); + cmgrBuilder.setMaxConnTotal(maxConnections); } else { maxConnections = ConnectionOptions.MAX_CONNECTIONS_DEFAULT; } - hcb.setConnectionManager(cmgr); + hcb.setConnectionManager(cmgrBuilder.build()); if (connectionOptions.getProxyHost() != null) { - // https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html#d5e485 - HttpHost proxy = new HttpHost(connectionOptions.getProxyHost(), connectionOptions.getProxyPort(), connectionOptions.getProxyProtocol()); + // https://hc.apache.org/httpcomponents-client-5.6.x/current/tutorial/html/connmgmt.html + HttpHost proxy = new HttpHost(connectionOptions.getProxyProtocol(), connectionOptions.getProxyHost(), connectionOptions.getProxyPort()); DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy); hcb.setRoutePlanner(routePlanner); log.debug("Connection via proxy {}", proxy); if (connectionOptions.getProxyUsername() != null) { log.debug("Proxy connection with credentials {}", proxy); + // HttpClient 5 handles proxy authentication through the shared + // authentication strategy, so no separate proxy strategy is needed commonCredentials.put( new AuthScope(proxy), - new UsernamePasswordCredentials(connectionOptions.getProxyUsername(), connectionOptions.getProxyPassword())); - hcb.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + new UsernamePasswordCredentials(connectionOptions.getProxyUsername(), + connectionOptions.getProxyPassword().toCharArray())); } } httpClientBuilder = hcb; @@ -446,6 +454,34 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, clients = new ConcurrentHashMap(maxConnections, .75f, maxConnections); } + /** + * A timeout that never expires. Note that neither {@link Timeout#DISABLED} nor + * {@link Timeout#INFINITE} can be used for this: both are zero, which the socket + * layer reads as "no timeout" but the connection pool reads as "do not wait at + * all", so a contended lease fails immediately. + */ + private static final Timeout NO_TIMEOUT = Timeout.ofMilliseconds(Long.MAX_VALUE); + + /** + * Converts a jackrabbit connect or socket timeout in milliseconds to an HttpClient 5 + * {@link Timeout}. The value -1 means "not configured", which under HttpClient 4 left + * the timeout infinite; zero carries that meaning to the socket layer. Simply omitting + * the setter would instead pick up the HttpClient 5 default of three minutes. + */ + private static Timeout toTimeout(int timeoutMs) { + return timeoutMs == -1 ? Timeout.DISABLED : Timeout.ofMilliseconds(timeoutMs); + } + + /** + * Converts a jackrabbit connection request timeout in milliseconds to an HttpClient 5 + * {@link Timeout}. This is the time spent waiting for a connection from the pool, where + * a zero timeout means "fail immediately" rather than "wait forever", so -1 has to map + * to an explicitly unbounded value. + */ + private static Timeout toLeaseTimeout(int timeoutMs) { + return timeoutMs == -1 ? NO_TIMEOUT : Timeout.ofMilliseconds(timeoutMs); + } + private static void checkSessionInfo(SessionInfo sessionInfo) throws RepositoryException { if (!(sessionInfo instanceof SessionInfoImpl)) { throw new RepositoryException("Unknown SessionInfo implementation."); @@ -624,18 +660,21 @@ protected HttpClient getClient(SessionInfo sessionInfo) throws RepositoryExcepti protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { HttpClientContext result = HttpClientContext.create(); - CredentialsProvider credsProvider = new BasicCredentialsProvider(); + CredentialsStore credsProvider = new BasicCredentialsProvider(); result.setCredentialsProvider(credsProvider); // take over default credentials (e.g. for proxy) - for (Map.Entry entry : commonCredentials.entrySet()) { + for (Map.Entry entry : commonCredentials.entrySet()) { credsProvider.setCredentials(entry.getKey(), entry.getValue()); } if (sessionInfo != null) { checkSessionInfo(sessionInfo); - org.apache.http.auth.Credentials creds = ((SessionInfoImpl) sessionInfo).getCredentials().getHttpCredentials(); + org.apache.hc.client5.http.auth.Credentials creds = ((SessionInfoImpl) sessionInfo).getCredentials().getHttpCredentials(); if (creds != null) { - credsProvider.setCredentials(new org.apache.http.auth.AuthScope(httpHost.getHostName(), httpHost.getPort()), creds); + credsProvider.setCredentials(new AuthScope(httpHost), creds); BasicScheme basicAuth = new BasicScheme(); + // HttpClient 5 only pre-authenticates from a cached scheme that + // has been primed with the credentials + basicAuth.initPreemptive(creds); AuthCache authCache = new BasicAuthCache(); authCache.put(httpHost, basicAuth); result.setAuthCache(authCache); @@ -730,11 +769,11 @@ int getIndex(DavPropertySet propSet) { /** * Execute a 'Workspace' operation. */ - private HttpResponse execute(BaseDavRequest request, SessionInfo sessionInfo) throws RepositoryException { + private ClassicHttpResponse execute(BaseDavRequest request, SessionInfo sessionInfo) throws RepositoryException { try { initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); return response; } catch (IOException e) { @@ -778,8 +817,8 @@ public Map getRepositoryDescriptors() throws RepositoryExcepti HttpReport request = null; try { request = new HttpReport(uriResolver.getRepositoryUri(), info); - HttpResponse response = executeRequest(null, request); - int sc = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = executeRequest(null, request); + int sc = response.getCode(); if (sc == HttpStatus.SC_UNAUTHORIZED || sc == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) { // JCR-3076: Mandatory authentication prevents us from @@ -822,7 +861,7 @@ public Map getRepositoryDescriptors() throws RepositoryExcepti throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -861,7 +900,7 @@ private SessionInfo obtain(CredentialsWrapper credentials, String workspaceName) nameSet.add(JcrRemotingConstants.JCR_WORKSPACE_NAME_LN, ItemResourceConstants.NAMESPACE); request = new HttpPropfind(uriResolver.getWorkspaceUri(workspaceName), nameSet, DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -895,7 +934,7 @@ private SessionInfo obtain(CredentialsWrapper credentials, String workspaceName) throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } @@ -927,7 +966,7 @@ public String[] getWorkspaceNames(SessionInfo sessionInfo) throws RepositoryExce HttpPropfind request = null; try { request = new HttpPropfind(uriResolver.getRepositoryUri(), nameSet, DEPTH_1); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); Set wspNames = new HashSet(); @@ -947,7 +986,7 @@ public String[] getWorkspaceNames(SessionInfo sessionInfo) throws RepositoryExce throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -961,7 +1000,7 @@ public boolean isGranted(SessionInfo sessionInfo, ItemId itemId, String[] action reportInfo.setContentElement(DomUtil.hrefToXml(uri, DomUtil.createDocument())); request = new HttpReport(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()), reportInfo); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -993,7 +1032,7 @@ public boolean isGranted(SessionInfo sessionInfo, ItemId itemId, String[] action throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1018,7 +1057,7 @@ public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId nodeId) throws R HttpPropfind propfindRequest = null; try { propfindRequest = new HttpPropfind(uri, nameSet, DEPTH_0); - HttpResponse response = execute(propfindRequest, sessionInfo); + ClassicHttpResponse response = execute(propfindRequest, sessionInfo); propfindRequest.checkSuccess(response); MultiStatusResponse[] mresponses = propfindRequest.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1044,7 +1083,7 @@ public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId nodeId) throws R throw ExceptionConverter.generate(e); } finally { if (propfindRequest != null) { - propfindRequest.releaseConnection(); + propfindRequest.reset(); } } } @@ -1055,7 +1094,7 @@ private PrivilegeDefinition[] internalGetPrivilegeDefinitions(SessionInfo sessio HttpPropfind request = null; try { request = new HttpPropfind(uri, nameSet, DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1096,7 +1135,7 @@ private PrivilegeDefinition[] internalGetPrivilegeDefinitions(SessionInfo sessio throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1140,7 +1179,7 @@ private QItemDefinition getItemDefinition(SessionInfo sessionInfo, ItemId itemId try { String uri = getItemUri(itemId, sessionInfo); request = new HttpPropfind(uri, nameSet, DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1186,7 +1225,7 @@ private QItemDefinition getItemDefinition(SessionInfo sessionInfo, ItemId itemId throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1209,7 +1248,7 @@ public NodeInfo getNodeInfo(SessionInfo sessionInfo, NodeId nodeId) throws Repos try { String uri = getItemUri(nodeId, sessionInfo); request = new HttpPropfind(uri, nameSet, DEPTH_1); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1264,7 +1303,7 @@ public NodeInfo getNodeInfo(SessionInfo sessionInfo, NodeId nodeId) throws Repos throw new RepositoryException(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1354,7 +1393,7 @@ public Iterator getChildInfos(SessionInfo sessionInfo, NodeId parentI try { String uri = getItemUri(parentId, sessionInfo); request = new HttpPropfind(uri, nameSet, DEPTH_1); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); List childEntries; @@ -1384,7 +1423,7 @@ public Iterator getChildInfos(SessionInfo sessionInfo, NodeId parentI throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1408,7 +1447,7 @@ public Iterator getReferences(SessionInfo sessionInfo, NodeId nodeId try { String uri = getItemUri(nodeId, sessionInfo); request = new HttpPropfind(uri, nameSet, DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1441,7 +1480,7 @@ public Iterator getReferences(SessionInfo sessionInfo, NodeId nodeId throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1452,17 +1491,17 @@ public PropertyInfo getPropertyInfo(SessionInfo sessionInfo, PropertyId property try { String uri = getItemUri(propertyId, sessionInfo); request = new HttpGet(uri); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); - int status = response.getStatusLine().getStatusCode(); + int status = response.getCode(); if (status != DavServletResponse.SC_OK) { - throw ExceptionConverter.generate(new DavException(status, response.getStatusLine().getReasonPhrase())); + throw ExceptionConverter.generate(new DavException(status, response.getReasonPhrase())); } Path path = uriResolver.getQPath(uri, sessionInfo); HttpEntity entity = response.getEntity(); - ContentType ct = ContentType.get(entity); + ContentType ct = (entity == null) ? null : ContentType.parse(entity.getContentType()); boolean isMultiValued; QValue[] values; @@ -1509,7 +1548,7 @@ public PropertyInfo getPropertyInfo(SessionInfo sessionInfo, PropertyId property throw new RepositoryException(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1558,7 +1597,7 @@ private int loadType(String propertyURI, HttpClient client, PropertyId propertyI HttpPropfind request = null; try { request = new HttpPropfind(propertyURI, nameSet, DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1575,7 +1614,7 @@ private int loadType(String propertyURI, HttpClient client, PropertyId propertyI } } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1597,29 +1636,29 @@ public void submit(Batch batch) throws RepositoryException { return; } - HttpRequestBase request = null; + HttpUriRequestBase request = null; try { HttpClient client = batchImpl.start(); boolean success = false; try { - Iterator it = batchImpl.requests(); + Iterator it = batchImpl.requests(); while (it.hasNext()) { request = it.next(); initMethod(request, batchImpl, true); - HttpResponse response = client.execute(request); + ClassicHttpResponse response = client.executeOpen(null, request, null); if (request instanceof BaseDavRequest) { ((BaseDavRequest) request).checkSuccess(response); } else { // use generic HTTP status code checking - int statusCode = response.getStatusLine().getStatusCode(); + int statusCode = response.getCode(); if (statusCode < 200 || statusCode >= 300) { throw new DavException(statusCode, "Unexpected status code " + statusCode + " in response to " + request.getMethod() + " request."); } } - request.releaseConnection(); + request.reset(); } success = true; } finally { @@ -1663,7 +1702,7 @@ public void move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNod HttpMove request = new HttpMove(uri, destUri, false); try { initMethod(request, sessionInfo); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); // need to clear the cache as the move may have affected nodes with // uuid. @@ -1673,7 +1712,7 @@ public void move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNod } catch (DavException e) { throw ExceptionConverter.generate(e, request); } finally { - request.releaseConnection(); + request.reset(); } } @@ -1687,14 +1726,14 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod HttpCopy request = new HttpCopy(uri, destUri, false, false); try { initMethod(request, sessionInfo); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException ex) { throw new RepositoryException(ex); } catch (DavException e) { throw ExceptionConverter.generate(e, request); } finally { - request.releaseConnection(); + request.reset(); } } @@ -1725,7 +1764,7 @@ public LockInfo getLockInfo(SessionInfo sessionInfo, NodeId nodeId) throws Repos request = new HttpPropfind(uri, nameSet, DEPTH_0); initMethod(request, sessionInfo, false); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -1753,7 +1792,7 @@ public LockInfo getLockInfo(SessionInfo sessionInfo, NodeId nodeId) throws Repos throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1776,7 +1815,7 @@ public LockInfo lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boole Scope scope = (sessionScoped) ? ItemResourceConstants.EXCLUSIVE_SESSION : Scope.EXCLUSIVE; request = new HttpLock(uri, new org.apache.jackrabbit.webdav.lock.LockInfo(scope, Type.WRITE, ownerInfo, davTimeout, deep)); - HttpResponse response = execute(request, sessionInfo); + ClassicHttpResponse response = execute(request, sessionInfo); String lockToken = request.getLockToken(response); ((SessionInfoImpl) sessionInfo).addLockToken(lockToken, sessionScoped); @@ -1789,7 +1828,7 @@ public LockInfo lock(SessionInfo sessionInfo, NodeId nodeId, boolean deep, boole throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -1808,7 +1847,7 @@ public void refreshLock(SessionInfo sessionInfo, NodeId nodeId) throws Repositor execute(httpLock, sessionInfo); } finally { if (httpLock != null) { - httpLock.releaseConnection(); + httpLock.reset(); } } } @@ -1837,7 +1876,7 @@ public void unlock(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryExce execute(unlockRequest, sessionInfo); ((SessionInfoImpl) sessionInfo).removeLockToken(lockToken, isSessionScoped); } finally { - unlockRequest.releaseConnection(); + unlockRequest.reset(); } } @@ -1883,16 +1922,16 @@ public NodeId checkin(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryE HttpCheckin request = new HttpCheckin(uri); try { initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); - org.apache.http.Header rh = response.getFirstHeader(DeltaVConstants.HEADER_LOCATION); + Header rh = response.getFirstHeader(DeltaVConstants.HEADER_LOCATION); return uriResolver.getNodeId(resolve(uri, rh.getValue()), sessionInfo); } catch (IOException e) { throw new RepositoryException(e); } catch (DavException ex) { throw ExceptionConverter.generate(ex); } finally { - request.releaseConnection(); + request.reset(); } } @@ -1902,14 +1941,14 @@ public void checkout(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryEx HttpCheckout request = new HttpCheckout(uri); try { initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); } catch (DavException ex) { throw ExceptionConverter.generate(ex); } finally { - request.releaseConnection(); + request.reset(); } } @@ -1947,14 +1986,14 @@ public void removeVersion(SessionInfo sessionInfo, NodeId versionHistoryId, Node HttpDelete request = new HttpDelete(uri); try { initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException ex) { throw new RepositoryException(ex); } catch (DavException ex) { throw ExceptionConverter.generate(ex); } finally { - request.releaseConnection(); + request.reset(); } } @@ -1991,7 +2030,7 @@ public void restore(SessionInfo sessionInfo, NodeId nodeId, NodeId versionId, bo private boolean exists(SessionInfo sInfo, String uri) { HttpHead request = new HttpHead(uri); try { - int statusCode = executeRequest(sInfo, request).getStatusLine().getStatusCode(); + int statusCode = executeRequest(sInfo, request).getCode(); return (statusCode == DavServletResponse.SC_OK); } catch (IOException e) { log.error("Unexpected error while testing existence of item.", e); @@ -2000,7 +2039,7 @@ private boolean exists(SessionInfo sInfo, String uri) { log.error(e.getMessage()); return false; } finally { - request.releaseConnection(); + request.reset(); } } @@ -2036,7 +2075,7 @@ private void update(String uri, Path relPath, String[] updateSource, int updateT request = new HttpUpdate(uri, uInfo); initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2046,7 +2085,7 @@ private void update(String uri, Path relPath, String[] updateSource, int updateT throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2071,7 +2110,7 @@ public Iterator merge(SessionInfo sessionInfo, NodeId nodeId, String src String uri = getItemUri(nodeId, sessionInfo); request = new HttpMerge(uri, mInfo); initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] resps = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -2089,7 +2128,7 @@ public Iterator merge(SessionInfo sessionInfo, NodeId nodeId, String src throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2115,7 +2154,7 @@ public void resolveMergeConflict(SessionInfo sessionInfo, NodeId nodeId, NodeId[ request = new HttpProppatch(getItemUri(nodeId, sessionInfo), changeList); initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2123,7 +2162,7 @@ public void resolveMergeConflict(SessionInfo sessionInfo, NodeId nodeId, NodeId[ throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2136,14 +2175,14 @@ public void addVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, No String strLabel = getNamePathResolver(sessionInfo).getJCRName(label); request = new HttpLabel(uri, new LabelInfo(strLabel, moveLabel ? LabelInfo.TYPE_SET : LabelInfo.TYPE_ADD)); initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); } catch (DavException ex) { throw ExceptionConverter.generate(ex); } finally { - request.releaseConnection(); + request.reset(); } } @@ -2155,14 +2194,14 @@ public void removeVersionLabel(SessionInfo sessionInfo, NodeId versionHistoryId, String strLabel = getNamePathResolver(sessionInfo).getJCRName(label); request = new HttpLabel(uri, new LabelInfo(strLabel, LabelInfo.TYPE_REMOVE)); initMethod(request, sessionInfo, !isUnLockMethod(request)); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); } catch (DavException ex) { throw ExceptionConverter.generate(ex); } finally { - request.releaseConnection(); + request.reset(); } } @@ -2194,8 +2233,8 @@ public NodeId createConfiguration(SessionInfo sessionInfo, NodeId nodeId) throws public String[] getSupportedQueryLanguages(SessionInfo sessionInfo) throws RepositoryException { HttpOptions request = new HttpOptions(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName())); try { - HttpResponse response = executeRequest(sessionInfo, request); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = executeRequest(sessionInfo, request); + int status = response.getCode(); if (status != DavServletResponse.SC_OK) { throw new DavException(status); } @@ -2205,7 +2244,7 @@ public String[] getSupportedQueryLanguages(SessionInfo sessionInfo) throws Repos } catch (DavException e) { throw ExceptionConverter.generate(e); } finally { - request.releaseConnection(); + request.reset(); } } @@ -2240,7 +2279,7 @@ public QueryInfo executeQuery(SessionInfo sessionInfo, String statement, String } request = new HttpSearch(uri, sInfo); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatus ms = request.getResponseBodyAsMultiStatus(response); @@ -2252,7 +2291,7 @@ public QueryInfo executeQuery(SessionInfo sessionInfo, String statement, String throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2309,10 +2348,10 @@ public EventBundle getEvents(SessionInfo sessionInfo, EventFilter filter, long a request.addHeader("If-None-Match", "\"" + Long.toHexString(after) + "\""); // TODO initMethod(request, sessionInfo); - HttpResponse response = executeRequest(sessionInfo, request); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = executeRequest(sessionInfo, request); + int status = response.getCode(); if (status != 200) { - throw new RepositoryException("getEvents to " + rootUri + " failed with " + response.getStatusLine()); + throw new RepositoryException("getEvents to " + rootUri + " failed with " + new StatusLine(response)); } HttpEntity entity = response.getEntity(); @@ -2355,7 +2394,7 @@ public EventBundle getEvents(SessionInfo sessionInfo, EventFilter filter, long a throw new RepositoryException("extracting events from journal feed: " + ex.getMessage(), ex); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2430,7 +2469,7 @@ private String subscribe(String uri, SubscriptionInfo subscriptionInfo, request.setHeader(ch.getHeaderName(), ch.getHeaderValue()); } - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); org.apache.jackrabbit.webdav.observation.Subscription[] subs = request.getResponseBodyAsSubscriptionDiscovery(response) @@ -2447,7 +2486,7 @@ private String subscribe(String uri, SubscriptionInfo subscriptionInfo, throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2457,7 +2496,7 @@ private void unsubscribe(String uri, String subscriptionId, SessionInfo sessionI try { request = new HttpUnsubscribe(uri, subscriptionId); initMethod(request, sessionInfo); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2465,7 +2504,7 @@ private void unsubscribe(String uri, String subscriptionId, SessionInfo sessionI throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2486,7 +2525,7 @@ private EventBundle[] poll(String uri, String subscriptionId, long timeout, Sess HttpPoll request = null; try { request = new HttpPoll(uri, subscriptionId, timeout); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); EventDiscovery disc = request.getResponseBodyAsEventDiscovery(response); @@ -2522,7 +2561,7 @@ private EventBundle[] poll(String uri, String subscriptionId, long timeout, Sess throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2622,7 +2661,7 @@ public Map getRegisteredNamespaces(SessionInfo sessionInfo) thro HttpReport request = null; try { request = new HttpReport(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()), info); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); Document doc = request.getResponseBodyAsDocument(response.getEntity()); @@ -2655,7 +2694,7 @@ public Map getRegisteredNamespaces(SessionInfo sessionInfo) thro throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2734,7 +2773,7 @@ private void internalSetNamespaces(SessionInfo sessionInfo, Map request = new HttpProppatch(uri, setProperties, new DavPropertyNameSet()); initMethod(request, sessionInfo, true); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2742,7 +2781,7 @@ private void internalSetNamespaces(SessionInfo sessionInfo, Map throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2756,7 +2795,7 @@ public Iterator getQNodeTypeDefinitions(SessionInfo session String workspaceUri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()); request = new HttpReport(workspaceUri, info); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); Document reportDoc = request.getResponseBodyAsDocument(response.getEntity()); @@ -2769,7 +2808,7 @@ public Iterator getQNodeTypeDefinitions(SessionInfo session throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2790,7 +2829,7 @@ public void registerNodeTypes(SessionInfo sessionInfo, QNodeTypeDefinition[] nod String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()); request = new HttpProppatch(uri, setProperties, new DavPropertyNameSet()); initMethod(request, sessionInfo, true); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2798,7 +2837,7 @@ public void registerNodeTypes(SessionInfo sessionInfo, QNodeTypeDefinition[] nod throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2812,7 +2851,7 @@ public void unregisterNodeTypes(SessionInfo sessionInfo, Name[] nodeTypeNames) t String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()); request = new HttpProppatch(uri, setProperties, new DavPropertyNameSet()); initMethod(request, sessionInfo, true); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2820,7 +2859,7 @@ public void unregisterNodeTypes(SessionInfo sessionInfo, Name[] nodeTypeNames) t throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2835,7 +2874,7 @@ public void createWorkspace(SessionInfo sessionInfo, String name, String srcWork try { request = new HttpMkworkspace(uriResolver.getWorkspaceUri(name)); initMethod(request, sessionInfo, true); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2843,7 +2882,7 @@ public void createWorkspace(SessionInfo sessionInfo, String name, String srcWork throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2854,7 +2893,7 @@ public void deleteWorkspace(SessionInfo sessionInfo, String name) throws Reposit try { request = new HttpDelete(uriResolver.getWorkspaceUri(name)); initMethod(request, sessionInfo, true); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -2862,7 +2901,7 @@ public void deleteWorkspace(SessionInfo sessionInfo, String name) throws Reposit throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -2887,8 +2926,8 @@ public static URI computeRepositoryUri(String uri) throws URISyntaxException { return repositoryUri; } - public HttpResponse executeRequest(SessionInfo sessionInfo, HttpUriRequest request) throws IOException, RepositoryException { - return getClient(sessionInfo).execute(request, getContext(sessionInfo)); + public ClassicHttpResponse executeRequest(SessionInfo sessionInfo, HttpUriRequest request) throws IOException, RepositoryException { + return getClient(sessionInfo).executeOpen(null, request, getContext(sessionInfo)); } /** @@ -3022,8 +3061,8 @@ private Set getDavComplianceClasses(SessionInfo sessionInfo) throws Repo if (this.remoteDavComplianceClasses == null) { HttpOptions request = new HttpOptions(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName())); try { - HttpResponse response = executeRequest(sessionInfo, request); - int status = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = executeRequest(sessionInfo, request); + int status = response.getCode(); if (status != DavServletResponse.SC_OK) { throw new DavException(status); } @@ -3033,7 +3072,7 @@ private Set getDavComplianceClasses(SessionInfo sessionInfo) throws Repo } catch (DavException e) { throw ExceptionConverter.generate(e); } finally { - request.releaseConnection(); + request.reset(); } } return this.remoteDavComplianceClasses; @@ -3081,7 +3120,7 @@ private class BatchImpl implements Batch { private final SessionInfo sessionInfo; private final ItemId targetId; - private final List requests = new ArrayList(); + private final List requests = new ArrayList(); private final NamePathResolver resolver; private String batchId; @@ -3106,8 +3145,8 @@ private HttpClient start() throws RepositoryException { initMethod(request, sessionInfo, true); HttpClient client = getClient(sessionInfo); - HttpResponse response = client.execute(request,getContext(sessionInfo)); - if (response.getStatusLine().getStatusCode() == DavServletResponse.SC_PRECONDITION_FAILED) { + ClassicHttpResponse response = client.executeOpen(null, request, getContext(sessionInfo)); + if (response.getCode() == DavServletResponse.SC_PRECONDITION_FAILED) { throw new InvalidItemStateException("Unable to persist transient changes."); } request.checkSuccess(response); @@ -3121,7 +3160,7 @@ private HttpClient start() throws RepositoryException { throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -3140,7 +3179,7 @@ private void end(HttpClient client, boolean commit) throws RepositoryException { // in contrast to standard UNLOCK, the tx-unlock provides a // request body. request.setEntity(XmlEntity.create(new TransactionInfo(commit))); - HttpResponse response = client.execute(request, getContext(sessionInfo)); + ClassicHttpResponse response = client.executeOpen(null, request, getContext(sessionInfo)); request.checkSuccess(response); if (sessionInfo instanceof SessionInfoImpl) { ((SessionInfoImpl) sessionInfo).setLastBatchId(batchId); @@ -3155,7 +3194,7 @@ private void end(HttpClient client, boolean commit) throws RepositoryException { } finally { if (request != null) { // release UNLOCK method - request.releaseConnection(); + request.reset(); } } } @@ -3175,7 +3214,7 @@ private boolean isEmpty() { return requests.isEmpty(); } - private Iterator requests() { + private Iterator requests() { return requests.iterator(); } diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java index ff2c3f37c89..8911e88f287 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java @@ -16,7 +16,7 @@ */ package org.apache.jackrabbit.spi2dav; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; @@ -122,7 +122,7 @@ String getItemUri(ItemId itemId, String workspaceName, SessionInfo sessionInfo) String wspUri = getWorkspaceUri(workspaceName); request = new HttpReport(wspUri, rInfo); - HttpResponse response = service.executeRequest(sessionInfo, request); + ClassicHttpResponse response = service.executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatus ms = request.getResponseBodyAsMultiStatus(response); @@ -141,7 +141,7 @@ String getItemUri(ItemId itemId, String workspaceName, SessionInfo sessionInfo) throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -282,10 +282,10 @@ private NodeId getNodeId(String uri, SessionInfo sessionInfo, boolean nodeIsGone try { request = new HttpPropfind(uri, nameSet, DavConstants.DEPTH_0); - HttpResponse response = service.executeRequest(sessionInfo, request); - if (response.getStatusLine().getStatusCode() != DavServletResponse.SC_MULTI_STATUS) { + ClassicHttpResponse response = service.executeRequest(sessionInfo, request); + if (response.getCode() != DavServletResponse.SC_MULTI_STATUS) { throw new ItemNotFoundException("Unable to retrieve the node with id " + uri + ", response status was: " - + response.getStatusLine().getStatusCode()); + + response.getCode()); } MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(response).getResponses(); if (responses.length != 1) { @@ -298,7 +298,7 @@ private NodeId getNodeId(String uri, SessionInfo sessionInfo, boolean nodeIsGone throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/HttpPost.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/HttpPost.java index 703031b88c6..f9af80c78c9 100755 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/HttpPost.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/HttpPost.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest; @@ -26,21 +26,16 @@ public class HttpPost extends BaseDavRequest { public HttpPost(URI uri) { - super(uri); + super(DavMethods.METHOD_POST, uri); } public HttpPost(String uri) { - super(URI.create(uri)); + this(URI.create(uri)); } @Override - public String getMethod() { - return DavMethods.METHOD_POST; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_NO_CONTENT || statusCode == DavServletResponse.SC_CREATED; } diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java index 1a497fb828b..9d2547c29e7 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java @@ -27,22 +27,22 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.jcr.Credentials; import javax.jcr.ItemNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.FormBodyPart; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.message.BasicNameValuePair; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.commons.json.JsonParser; import org.apache.jackrabbit.commons.json.JsonUtil; @@ -337,10 +337,10 @@ public Iterator getItemInfos(SessionInfo sessionInfo, ItemId int depth = batchReadConfig.getDepth(path, this.getNamePathResolver(sessionInfo)); HttpGet request = new HttpGet(uri + "." + depth + ".json"); - HttpResponse response = null; + ClassicHttpResponse response = null; try { response = executeRequest(sessionInfo, request); - int statusCode = response.getStatusLine().getStatusCode(); + int statusCode = response.getCode(); if (statusCode == DavServletResponse.SC_OK) { HttpEntity entity = response.getEntity(); if (entity.getContentLength() == 0) { @@ -353,7 +353,7 @@ public Iterator getItemInfos(SessionInfo sessionInfo, ItemId ItemInfoJsonHandler handler = new ItemInfoJsonHandler(resolver, nInfo, getRootURI(sessionInfo), getQValueFactory(sessionInfo), getPathFactory(), getIdFactory()); JsonParser ps = new JsonParser(handler); - ps.parse(entity.getContent(), ContentType.get(entity).getCharset().name()); + ps.parse(entity.getContent(), ContentType.parse(entity.getContentType()).getCharset().name()); Iterator it = handler.getItemInfos(); if (!it.hasNext()) { @@ -367,7 +367,7 @@ public Iterator getItemInfos(SessionInfo sessionInfo, ItemId log.error("Internal error while retrieving NodeInfo for " + uri + ".", e); throw new RepositoryException(e.getMessage(), e); } finally { - request.releaseConnection(); + request.reset(); } } } @@ -382,7 +382,7 @@ public PropertyInfo getPropertyInfo(SessionInfo sessionInfo, PropertyId property HttpPropfind request = null; try { request = new HttpPropfind(uri, LAZY_PROPERTY_NAME_SET, DavConstants.DEPTH_0); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); MultiStatusResponse[] mresponses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -428,7 +428,7 @@ public PropertyInfo getPropertyInfo(SessionInfo sessionInfo, PropertyId property throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -467,7 +467,7 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod HttpPost request = null; try { request = new HttpPost(getWorkspaceURI(sessionInfo)); - request.setHeader("Referer", request.getURI().toASCIIString()); + request.setHeader("Referer", request.getRequestUri()); addIfHeader(sessionInfo, request); NamePathResolver resolver = getNamePathResolver(sessionInfo); @@ -485,7 +485,7 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod List nvps = Collections.singletonList(new BasicNameValuePair(PARAM_COPY, args.toString())); HttpEntity entity = new UrlEncodedFormEntity(nvps, Charset.forName("UTF-8")); request.setEntity(entity); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); } catch (IOException e) { throw new RepositoryException(e); @@ -493,7 +493,7 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod throw ExceptionConverter.generate(e, request); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -503,7 +503,7 @@ public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNo HttpPost request = null; try { request = new HttpPost(getWorkspaceURI(sessionInfo)); - request.setHeader("Referer", request.getURI().toASCIIString()); + request.setHeader("Referer", request.getRequestUri()); addIfHeader(sessionInfo, request); NamePathResolver resolver = getNamePathResolver(sessionInfo); @@ -522,7 +522,7 @@ public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNo List nvps = Collections.singletonList(new BasicNameValuePair(PARAM_CLONE, args.toString())); HttpEntity entity = new UrlEncodedFormEntity(nvps, Charset.forName("UTF-8")); request.setEntity(entity); - HttpResponse response = executeRequest(sessionInfo, request); + ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); if (removeExisting) { clearItemUriCache(sessionInfo); @@ -533,7 +533,7 @@ public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNo throw ExceptionConverter.generate(e, request); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } @@ -580,7 +580,7 @@ private BatchImpl(ItemId targetId, SessionInfo sessionInfo) { private void start() throws RepositoryException { checkConsumed(); - request.setHeader("Referer", request.getURI().toASCIIString()); + request.setHeader("Referer", request.getRequestUri()); // add lock tokens addIfHeader(sessionInfo, request); @@ -599,16 +599,13 @@ private void start() throws RepositoryException { // engine has a form-size restriction (JCR-3726) Utils.addPart(PARAM_DIFF, buf.toString(), parts); - // JCR-4317: need RFC6532 mode so that values are encoded in UTF-8 - MultipartEntityBuilder b = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532); - for (FormBodyPart p : parts) { - b.addPart(p.getName(), p.getBody()); - } - request.setEntity(b.build()); + // JCR-4317: part names carry the JCR path and must survive as UTF-8, + // which MultipartEntityBuilder cannot do in HttpClient 5 + request.setEntity(new Rfc6532MultipartEntity(parts, "----=_Part_" + UUID.randomUUID())); - org.apache.http.client.HttpClient client = getClient(sessionInfo); + HttpClient client = getClient(sessionInfo); try { - HttpResponse response = client.execute(request, getContext(sessionInfo)); + ClassicHttpResponse response = client.executeOpen(null, request, getContext(sessionInfo)); request.checkSuccess(response); if (clear) { RepositoryServiceImpl.super.clearItemUriCache(sessionInfo); @@ -618,7 +615,7 @@ private void start() throws RepositoryException { } catch (DavException e) { throw ExceptionConverter.generate(e, request); } finally { - request.releaseConnection(); + request.reset(); } } diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java new file mode 100644 index 00000000000..6c0201f74ab --- /dev/null +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.spi2davex; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.client5.http.entity.mime.MimeField; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; + +/** + * A multipart entity that writes part headers as UTF-8, as described by + * RFC 6532. + *

+ * The server matches parts by the JCR path carried in the {@code name} parameter of + * {@code Content-Disposition}, so a part name has to survive verbatim (JCR-4317). + * HttpClient 4 achieved this with {@code HttpMultipartMode.RFC6532}, but HttpClient 5 + * offers no equivalent through {@code MultipartEntityBuilder}: its {@code EXTENDED} and + * {@code STRICT} modes replace a non-ASCII part name with {@code '?'}, and its + * {@code LEGACY} mode preserves the name but omits the per-part {@code Content-Type} and + * {@code Content-Transfer-Encoding} headers. HttpClient 5 also advertises + * {@code charset=ISO-8859-1} on the entity content type, which makes a server decode + * those UTF-8 header bytes as Latin-1. + *

+ * This entity therefore does the framing itself. The parts are still built with + * {@code FormBodyPartBuilder}, whose generated headers are byte for byte what HttpClient 4 + * produced; only the assembly differs. The output is byte identical to that of HttpClient 4 + * in {@code RFC6532} mode, which {@code Rfc6532MultipartEntityTest} verifies. + */ +final class Rfc6532MultipartEntity implements HttpEntity { + + private static final byte[] CR_LF = { '\r', '\n' }; + private static final byte[] TWO_HYPHENS = { '-', '-' }; + + private final List parts; + private final String boundary; + private final long contentLength; + + Rfc6532MultipartEntity(List parts, String boundary) { + this.parts = parts; + this.boundary = boundary; + this.contentLength = computeContentLength(); + } + + /** + * @return the total length, or -1 as soon as any part has an unknown length, which + * matches what HttpClient 4 reported for a stream body + */ + private long computeContentLength() { + long total = 0; + for (FormBodyPart part : parts) { + long length = part.getBody().getContentLength(); + if (length < 0) { + return -1; + } + total += length; + } + ByteArrayOutputStream framing = new ByteArrayOutputStream(); + try { + writeTo(framing, false); + } catch (IOException e) { + return -1; + } + return total + framing.size(); + } + + @Override + public void writeTo(OutputStream out) throws IOException { + writeTo(out, true); + } + + private void writeTo(OutputStream out, boolean writeContent) throws IOException { + byte[] boundaryBytes = boundary.getBytes(StandardCharsets.ISO_8859_1); + for (FormBodyPart part : parts) { + out.write(TWO_HYPHENS); + out.write(boundaryBytes); + out.write(CR_LF); + for (MimeField field : part.getHeader()) { + // the field body already carries any parameters, fully formatted + out.write(field.getName().getBytes(StandardCharsets.UTF_8)); + out.write(':'); + out.write(' '); + out.write(field.getBody().getBytes(StandardCharsets.UTF_8)); + out.write(CR_LF); + } + out.write(CR_LF); + if (writeContent) { + part.getBody().writeTo(out); + } + out.write(CR_LF); + } + out.write(TWO_HYPHENS); + out.write(boundaryBytes); + out.write(TWO_HYPHENS); + out.write(CR_LF); + } + + @Override + public String getContentType() { + // deliberately without a charset parameter, so that a server does not decode the + // UTF-8 part headers as anything else + return "multipart/form-data; boundary=" + boundary; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public boolean isRepeatable() { + return contentLength != -1; + } + + @Override + public boolean isChunked() { + return contentLength == -1; + } + + @Override + public boolean isStreaming() { + return contentLength == -1; + } + + @Override + public String getContentEncoding() { + return null; + } + + @Override + public InputStream getContent() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + writeTo(buffer, true); + return new ByteArrayInputStream(buffer.toByteArray()); + } + + @Override + public Supplier> getTrailers() { + return null; + } + + @Override + public Set getTrailerNames() { + return Collections.emptySet(); + } + + @Override + public void close() throws IOException { + // the part bodies are closed by the caller via Utils.removeParts + } +} diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java index 7f1e93cccc4..0da7a03563b 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java @@ -24,11 +24,12 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.FormBodyPart; -import org.apache.http.entity.mime.FormBodyPartBuilder; -import org.apache.http.entity.mime.content.InputStreamBody; -import org.apache.http.entity.mime.content.StringBody; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.client5.http.entity.mime.FormBodyPartBuilder; +import org.apache.hc.client5.http.entity.mime.InputStreamBody; +import org.apache.hc.client5.http.entity.mime.MimeField; +import org.apache.hc.client5.http.entity.mime.StringBody; import org.apache.jackrabbit.commons.json.JsonUtil; import org.apache.jackrabbit.commons.webdav.JcrValueType; import org.apache.jackrabbit.spi.QValue; @@ -39,8 +40,23 @@ final class Utils { private static final String DEFAULT_CHARSET = "UTF-8"; private static final ContentType DEFAULT_TYPE = ContentType.create("text/plain", DEFAULT_CHARSET); + private static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + private static final String TRANSFER_ENCODING_TEXT = "8bit"; + private static final String TRANSFER_ENCODING_BINARY = "binary"; + private Utils() {}; + /** + * HttpClient 5 no longer derives a Content-Transfer-Encoding header from the body, + * whereas HttpClient 4 emitted 8bit for string bodies and binary for stream bodies. + * Adding it after the part is built keeps it in the same position as before, i.e. + * after Content-Disposition and Content-Type. + */ + private static FormBodyPart withTransferEncoding(FormBodyPart part, String encoding) { + part.getHeader().addField(new MimeField(CONTENT_TRANSFER_ENCODING, encoding)); + return part; + } + static String getJsonKey(String str) { return JsonUtil.getJsonString(str) + ":"; } @@ -80,7 +96,9 @@ static String getJsonString(QValue value) throws RepositoryException { * @param value */ static void addPart(String paramName, String value, List parts) { - parts.add(FormBodyPartBuilder.create().setName(paramName).setBody(new StringBody(value, DEFAULT_TYPE)).build()); + parts.add(withTransferEncoding( + FormBodyPartBuilder.create().setName(paramName).setBody(new StringBody(value, DEFAULT_TYPE)).build(), + TRANSFER_ENCODING_TEXT)); } /** @@ -99,16 +117,21 @@ static void addPart(String paramName, QValue value, NamePathResolver resolver, L case PropertyType.BINARY: binaries.add(value); // server detects binaries based on presence of filename parameters (JCR-4154) - part = builder.setBody(new InputStreamBody(value.getStream(), ctype, paramName)).build(); + part = withTransferEncoding( + builder.setBody(new InputStreamBody(value.getStream(), ctype, paramName)).build(), + TRANSFER_ENCODING_BINARY); break; case PropertyType.NAME: - part = builder.setBody(new StringBody(resolver.getJCRName(value.getName()), ctype)).build(); + part = withTransferEncoding( + builder.setBody(new StringBody(resolver.getJCRName(value.getName()), ctype)).build(), TRANSFER_ENCODING_TEXT); break; case PropertyType.PATH: - part = builder.setBody(new StringBody(resolver.getJCRPath(value.getPath()), ctype)).build(); + part = withTransferEncoding( + builder.setBody(new StringBody(resolver.getJCRPath(value.getPath()), ctype)).build(), TRANSFER_ENCODING_TEXT); break; default: - part = builder.setBody(new StringBody(value.getString(), ctype)).build(); + part = withTransferEncoding( + builder.setBody(new StringBody(value.getString(), ctype)).build(), TRANSFER_ENCODING_TEXT); } parts.add(part); diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java index e309e970f15..b97e328eea4 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java @@ -25,12 +25,13 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.protocol.HttpContext; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants; import org.apache.jackrabbit.spi2dav.ExceptionConverter; import org.apache.jackrabbit.spi2dav.ItemResourceConstants; @@ -59,15 +60,15 @@ class ValueLoader { void loadBinary(String uri, int index, Target target) throws RepositoryException, IOException { HttpGet request = new HttpGet(uri); try { - HttpResponse response = client.execute(request, context); - int statusCode = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = client.executeOpen(null, request, context); + int statusCode = response.getCode(); if (statusCode == DavServletResponse.SC_OK) { target.setStream(response.getEntity().getContent()); } else { - throw ExceptionConverter.generate(new DavException(statusCode, ("Unable to load binary at " + uri + " - Status line = " + response.getStatusLine()))); + throw ExceptionConverter.generate(new DavException(statusCode, ("Unable to load binary at " + uri + " - Status line = " + new StatusLine(response)))); } } finally { - request.releaseConnection(); + request.reset(); } } @@ -75,8 +76,8 @@ public Map loadHeaders(String uri, String[] headerNames) throws RepositoryException { HttpHead request = new HttpHead(uri); try { - HttpResponse response = client.execute(request, context); - int statusCode = response.getStatusLine().getStatusCode(); + ClassicHttpResponse response = client.executeOpen(null, request, context); + int statusCode = response.getCode(); if (statusCode == DavServletResponse.SC_OK) { Map headers = new HashMap(); for (String name : headerNames) { @@ -87,10 +88,10 @@ public Map loadHeaders(String uri, String[] headerNames) throws } return headers; } else { - throw ExceptionConverter.generate(new DavException(statusCode, ("Unable to load headers at " + uri + " - Status line = " + response.getStatusLine().toString()))); + throw ExceptionConverter.generate(new DavException(statusCode, ("Unable to load headers at " + uri + " - Status line = " + new StatusLine(response)))); } } finally { - request.releaseConnection(); + request.reset(); } } @@ -101,7 +102,7 @@ int loadType(String uri) throws RepositoryException, IOException { HttpPropfind request = null; try { request = new HttpPropfind(uri, nameSet, DavConstants.DEPTH_0); - HttpResponse response = client.execute(request, context); + ClassicHttpResponse response = client.executeOpen(null, request, context); request.checkSuccess(response); MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(response).getResponses(); @@ -120,7 +121,7 @@ int loadType(String uri) throws RepositoryException, IOException { throw ExceptionConverter.generate(e); } finally { if (request != null) { - request.releaseConnection(); + request.reset(); } } } diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java index 36a95ddf58f..88b9772878f 100644 --- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java @@ -27,7 +27,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.http.conn.ConnectTimeoutException; +import org.apache.hc.client5.http.ConnectTimeoutException; import org.apache.jackrabbit.spi.RepositoryService; import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; import org.apache.jackrabbit.webdav.server.WebDAVTestBase; @@ -48,7 +48,10 @@ RepositoryService createRepositoryService(boolean isViaHttps, final Map parameters = new HashMap<>(); if (isViaHttps) { parameters.put(Spi2davexRepositoryServiceFactory.PARAM_REPOSITORY_URI, - new URI("https", null, remotingUri.getHost(), httpsUri.getPort(), remotingUri.getPath(), null, null).toString()); + // take the host from httpsUri, which the test base pins to a single address: + // where localhost resolves to both 127.0.0.1 and ::1, HttpClient 5 retries + // the next address after a TLS failure and reports a connection error instead + new URI("https", null, httpsUri.getHost(), httpsUri.getPort(), remotingUri.getPath(), null, null).toString()); } else { parameters.put(Spi2davexRepositoryServiceFactory.PARAM_REPOSITORY_URI, remotingUri.toString()); } diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/DavPropertyTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/DavPropertyTest.java index 65201d09d61..87dcb8d8df1 100644 --- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/DavPropertyTest.java +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/DavPropertyTest.java @@ -23,9 +23,9 @@ import javax.jcr.PropertyType; import javax.jcr.RepositoryException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.client5.http.classic.HttpClient; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.spi.AbstractSPITest; import org.apache.jackrabbit.spi.Batch; @@ -136,7 +136,7 @@ private static void assertPropertyNames(DavPropertyNameSet expected, DavProperty private DavPropertyNameSet doPropFindNames(String uri) throws Exception { HttpPropfind request = new HttpPropfind(uri, DavConstants.PROPFIND_PROPERTY_NAMES, DavConstants.DEPTH_0); HttpClient cl = rs.getClient(si); - HttpResponse response = cl.execute(request, rs.getContext(si)); + ClassicHttpResponse response = cl.executeOpen(null, request, rs.getContext(si)); request.checkSuccess(response); MultiStatus ms = request.getResponseBodyAsMultiStatus(response); @@ -147,7 +147,7 @@ private DavPropertyNameSet doPropFindNames(String uri) throws Exception { private DavPropertyNameSet doPropFindAll(String uri) throws Exception { HttpPropfind request = new HttpPropfind(uri, DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0); HttpClient cl = rs.getClient(si); - HttpResponse response = cl.execute(request, rs.getContext(si)); + ClassicHttpResponse response = cl.executeOpen(null, request, rs.getContext(si)); request.checkSuccess(response); MultiStatus ms = request.getResponseBodyAsMultiStatus(response); @@ -158,7 +158,7 @@ private DavPropertyNameSet doPropFindAll(String uri) throws Exception { private DavPropertyNameSet doPropFindByProp(String uri, DavPropertyNameSet props) throws Exception { HttpPropfind request = new HttpPropfind(uri, DavConstants.PROPFIND_BY_PROPERTY, props, DavConstants.DEPTH_0); HttpClient cl = rs.getClient(si); - HttpResponse response = cl.execute(request, rs.getContext(si)); + ClassicHttpResponse response = cl.executeOpen(null, request, rs.getContext(si)); request.checkSuccess(response); MultiStatus ms = request.getResponseBodyAsMultiStatus(response); diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategyTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategyTest.java new file mode 100644 index 00000000000..4388d12175c --- /dev/null +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/GetHeadRedirectStrategyTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.spi2dav; + +import java.net.URI; + +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.apache.hc.core5.http.protocol.BasicHttpContext; +import org.apache.jackrabbit.webdav.DavMethods; +import org.junit.Assert; +import org.junit.Test; + +/** + * Checks that automatic redirects stay restricted to the safe methods, as they were + * under HttpClient 4. HttpClient 5 would otherwise follow redirects for every method. + */ +public class GetHeadRedirectStrategyTest { + + private boolean isRedirected(String method, int status, boolean withLocation) throws Exception { + BasicClassicHttpRequest request = new BasicClassicHttpRequest(method, URI.create("http://localhost/a")); + BasicClassicHttpResponse response = new BasicClassicHttpResponse(status); + if (withLocation) { + response.setHeader(HttpHeaders.LOCATION, "http://localhost/b"); + } + return GetHeadRedirectStrategy.INSTANCE.isRedirected(request, response, new BasicHttpContext()); + } + + @Test + public void testSafeMethodsAreRedirected() throws Exception { + for (int status : new int[] { HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_MOVED_TEMPORARILY, + HttpStatus.SC_TEMPORARY_REDIRECT, HttpStatus.SC_PERMANENT_REDIRECT }) { + Assert.assertTrue("GET should follow " + status, isRedirected("GET", status, true)); + Assert.assertTrue("HEAD should follow " + status, isRedirected("HEAD", status, true)); + } + } + + @Test + public void testWriteMethodsAreNotRedirected() throws Exception { + String[] methods = { "PUT", "DELETE", "POST", DavMethods.METHOD_MOVE, DavMethods.METHOD_COPY, + DavMethods.METHOD_PROPPATCH, DavMethods.METHOD_MKCOL, DavMethods.METHOD_LOCK }; + for (String method : methods) { + for (int status : new int[] { HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_MOVED_TEMPORARILY, + HttpStatus.SC_TEMPORARY_REDIRECT, HttpStatus.SC_PERMANENT_REDIRECT }) { + Assert.assertFalse(method + " must not follow " + status, isRedirected(method, status, true)); + } + } + } + + /** + * PROPFIND is read-only but was not redirected by HttpClient 4 either, since only + * GET and HEAD were on the list. + */ + @Test + public void testPropfindIsNotRedirected() throws Exception { + Assert.assertFalse(isRedirected(DavMethods.METHOD_PROPFIND, HttpStatus.SC_MOVED_PERMANENTLY, true)); + } + + @Test + public void testSeeOtherIsAlwaysRedirected() throws Exception { + // 303 directs the client to fetch a different resource with GET + Assert.assertTrue(isRedirected("GET", HttpStatus.SC_SEE_OTHER, true)); + Assert.assertTrue(isRedirected(DavMethods.METHOD_MOVE, HttpStatus.SC_SEE_OTHER, true)); + } + + @Test + public void testFoundRequiresLocationHeader() throws Exception { + Assert.assertTrue(isRedirected("GET", HttpStatus.SC_MOVED_TEMPORARILY, true)); + Assert.assertFalse(isRedirected("GET", HttpStatus.SC_MOVED_TEMPORARILY, false)); + } + + @Test + public void testNonRedirectStatusIsNotRedirected() throws Exception { + for (int status : new int[] { HttpStatus.SC_OK, HttpStatus.SC_NOT_MODIFIED, HttpStatus.SC_USE_PROXY, + HttpStatus.SC_MULTI_STATUS, HttpStatus.SC_NOT_FOUND }) { + Assert.assertFalse("must not follow " + status, isRedirected("GET", status, true)); + } + } +} diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImplIT.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImplIT.java index 64377e7b70b..996037dd858 100644 --- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImplIT.java +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImplIT.java @@ -29,12 +29,11 @@ import java.nio.file.Path; import javax.jcr.RepositoryException; -import javax.net.ssl.SSLException; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -61,7 +60,7 @@ public void testGetAgainstTrustedCertServer() throws RepositoryException, Client RepositoryServiceImpl repositoryServiceImpl = RepositoryServiceImplTest.getRepositoryService("https://jackrabbit.apache.org/jcr", ConnectionOptions.builder().build()); HttpClient client = repositoryServiceImpl.getClient(null); HttpGet get = new HttpGet("https://jackrabbit.apache.org/jcr/index.html"); - String content = client.execute(get, new BasicResponseHandler()); + String content = client.execute(get, new BasicHttpClientResponseHandler()); assertFalse(content.isEmpty()); } @@ -81,8 +80,12 @@ public void testGetAgainstTrustedCertServerWithSystemProperties() throws Reposit RepositoryServiceImpl repositoryServiceImpl = RepositoryServiceImplTest.getRepositoryService("https://jackrabbit.apache.org/jcr", connectionOptions); HttpClient client = repositoryServiceImpl.getClient(null); HttpGet get = new HttpGet("https://jackrabbit.apache.org/jcr/index.html"); - // connection must fail as cert is not trusted due to used trust store being empty - assertThrows(SSLException.class, () -> client.execute(get, new BasicResponseHandler())); + // The connection must fail, as the certificate is not trusted with an empty + // trust store. The exact exception type cannot be asserted: HttpClient 5 runs + // the TLS upgrade inside its per-address retry block, so for a host resolving + // to several addresses the SSLException raised by the first address is logged + // at DEBUG and the caller sees whatever the last address produced. + assertThrows(IOException.class, () -> client.execute(get, new BasicHttpClientResponseHandler())); } finally { setOrClearSystemProperty("javax.net.ssl.trustStore", oldTrustStore); setOrClearSystemProperty("javax.net.ssl.trustStorePassword", oldTrustStorePassword); diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java new file mode 100644 index 00000000000..07339780a10 --- /dev/null +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.spi2davex; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hc.client5.http.entity.mime.FormBodyPart; +import org.apache.hc.client5.http.entity.mime.FormBodyPartBuilder; +import org.apache.hc.client5.http.entity.mime.InputStreamBody; +import org.apache.hc.client5.http.entity.mime.MimeField; +import org.apache.hc.client5.http.entity.mime.StringBody; +import org.apache.hc.core5.http.ContentType; +import org.junit.Assert; +import org.junit.Test; + +/** + * Pins the wire format produced for a davex batch, which has to stay byte for byte what + * HttpClient 4 wrote in {@code RFC6532} mode. The expectations below were taken from + * HttpClient 4.5.14 output. The server matches parts by the JCR path in the {@code name} + * parameter, so a non-ASCII name surviving verbatim is the point of the exercise + * (JCR-4317); an HttpClient upgrade that changes any of this should fail here rather than + * in the remoting conformance suite. + */ +public class Rfc6532MultipartEntityTest { + + private static final ContentType TEXT = ContentType.create("text/plain", "UTF-8"); + private static final ContentType BINARY = ContentType.create("jcr-value/binary", "UTF-8"); + + private static FormBodyPart part(String name, String value, ContentType type, String encoding) { + FormBodyPart part = FormBodyPartBuilder.create().setName(name) + .setBody(new StringBody(value, type)).build(); + part.getHeader().addField(new MimeField("Content-Transfer-Encoding", encoding)); + return part; + } + + private static String write(List parts) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new Rfc6532MultipartEntity(parts, "B").writeTo(out); + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } + + @Test + public void testNonAsciiPartNameSurvivesAsUtf8() throws Exception { + String name = "/testroot/Test-ä/jcr:content/jcr:data"; + List parts = new ArrayList(); + FormBodyPart binary = FormBodyPartBuilder.create().setName(name) + .setBody(new InputStreamBody( + new ByteArrayInputStream("XYZ".getBytes(StandardCharsets.UTF_8)), BINARY, name)) + .build(); + binary.getHeader().addField(new MimeField("Content-Transfer-Encoding", "binary")); + parts.add(binary); + + Assert.assertEquals( + "--B\r\n" + + "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + name + "\"\r\n" + + "Content-Type: jcr-value/binary; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "XYZ\r\n" + + "--B--\r\n", + write(parts)); + } + + @Test + public void testDiffPartKeepsTypeAndEncoding() throws Exception { + List parts = new ArrayList(); + parts.add(part(":diff", "+/testroot/Test-ä : {}", TEXT, "8bit")); + + Assert.assertEquals( + "--B\r\n" + + "Content-Disposition: form-data; name=\":diff\"\r\n" + + "Content-Type: text/plain; charset=UTF-8\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "+/testroot/Test-ä : {}\r\n" + + "--B--\r\n", + write(parts)); + } + + /** + * JCR names may contain quotes and backslashes, which have to be escaped in the + * Content-Disposition parameters exactly as HttpClient 4 escaped them. + */ + @Test + public void testQuotesAndBackslashesInPartNameAreEscaped() throws Exception { + List parts = new ArrayList(); + parts.add(part("/testroot/na\"me-with\\quote-ä", "v", TEXT, "8bit")); + + Assert.assertTrue(write(parts).contains( + "name=\"/testroot/na\\\"me-with\\\\quote-ä\"")); + } + + @Test + public void testContentTypeCarriesNoCharset() { + // a charset here would make the server decode the UTF-8 part headers as something else + Assert.assertEquals("multipart/form-data; boundary=B", + new Rfc6532MultipartEntity(new ArrayList(), "B").getContentType()); + } + + @Test + public void testLengthIsKnownForStringPartsAndUnknownForStreams() throws Exception { + List strings = new ArrayList(); + strings.add(part(":diff", "abc", TEXT, "8bit")); + Rfc6532MultipartEntity known = new Rfc6532MultipartEntity(strings, "B"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + known.writeTo(out); + Assert.assertEquals(out.size(), known.getContentLength()); + Assert.assertTrue(known.isRepeatable()); + Assert.assertFalse(known.isChunked()); + + List streams = new ArrayList(); + streams.add(FormBodyPartBuilder.create().setName("bin") + .setBody(new InputStreamBody( + new ByteArrayInputStream("XYZ".getBytes(StandardCharsets.UTF_8)), BINARY, "bin")) + .build()); + Rfc6532MultipartEntity unknown = new Rfc6532MultipartEntity(streams, "B"); + Assert.assertEquals(-1, unknown.getContentLength()); + Assert.assertTrue(unknown.isChunked()); + } +} diff --git a/jackrabbit-webdav/pom.xml b/jackrabbit-webdav/pom.xml index 468a93f378e..d65a794b1a7 100644 --- a/jackrabbit-webdav/pom.xml +++ b/jackrabbit-webdav/pom.xml @@ -69,14 +69,12 @@ provided - org.apache.httpcomponents - httpcore - 4.4.16 + org.apache.httpcomponents.core5 + httpcore5 - org.apache.httpcomponents - httpclient - 4.5.14 + org.apache.httpcomponents.client5 + httpclient5 org.slf4j diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java index ca365b75f02..b5fd5721865 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java @@ -22,10 +22,9 @@ import javax.xml.parsers.ParserConfigurationException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpEntity; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; @@ -44,13 +43,12 @@ /** * Base class for HTTP request classes defined in this package. */ -public abstract class BaseDavRequest extends HttpEntityEnclosingRequestBase { +public abstract class BaseDavRequest extends HttpUriRequestBase { private static Logger log = LoggerFactory.getLogger(BaseDavRequest.class); - public BaseDavRequest(URI uri) { - super(); - super.setURI(uri); + public BaseDavRequest(String method, URI uri) { + super(method, uri); } /** @@ -82,15 +80,15 @@ public Document getResponseBodyAsDocument(HttpEntity entity) throws IOException * @throws IllegalStateException when response does not represent a {@link MultiStatus} * @throws DavException for failures in obtaining/parsing the response body */ - public MultiStatus getResponseBodyAsMultiStatus(HttpResponse response) throws DavException { + public MultiStatus getResponseBodyAsMultiStatus(ClassicHttpResponse response) throws DavException { try { Document doc = getResponseBodyAsDocument(response.getEntity()); if (doc == null) { - throw new DavException(response.getStatusLine().getStatusCode(), "no response body"); + throw new DavException(response.getCode(), "no response body"); } return MultiStatus.createFromXml(doc.getDocumentElement()); } catch (IOException ex) { - throw new DavException(response.getStatusLine().getStatusCode(), ex); + throw new DavException(response.getCode(), ex); } } @@ -99,29 +97,29 @@ public MultiStatus getResponseBodyAsMultiStatus(HttpResponse response) throws Da * @throws IllegalStateException when response does not represent a {@link LockDiscovery} * @throws DavException for failures in obtaining/parsing the response body */ - public LockDiscovery getResponseBodyAsLockDiscovery(HttpResponse response) throws DavException { + public LockDiscovery getResponseBodyAsLockDiscovery(ClassicHttpResponse response) throws DavException { try { Document doc = getResponseBodyAsDocument(response.getEntity()); if (doc == null) { - throw new DavException(response.getStatusLine().getStatusCode(), "no response body"); + throw new DavException(response.getCode(), "no response body"); } Element root = doc.getDocumentElement(); if (!DomUtil.matches(root, DavConstants.XML_PROP, DavConstants.NAMESPACE) && DomUtil.hasChildElement(root, DavConstants.PROPERTY_LOCKDISCOVERY, DavConstants.NAMESPACE)) { - throw new DavException(response.getStatusLine().getStatusCode(), + throw new DavException(response.getCode(), "Missing DAV:prop response body in LOCK response."); } Element lde = DomUtil.getChildElement(root, DavConstants.PROPERTY_LOCKDISCOVERY, DavConstants.NAMESPACE); if (!DomUtil.hasChildElement(lde, DavConstants.XML_ACTIVELOCK, DavConstants.NAMESPACE)) { - throw new DavException(response.getStatusLine().getStatusCode(), + throw new DavException(response.getCode(), "The DAV:lockdiscovery must contain a least a single DAV:activelock in response to a successful LOCK request."); } return LockDiscovery.createFromXml(lde); } catch (IOException ex) { - throw new DavException(response.getStatusLine().getStatusCode(), ex); + throw new DavException(response.getCode(), ex); } } @@ -130,18 +128,18 @@ public LockDiscovery getResponseBodyAsLockDiscovery(HttpResponse response) throw * @throws IllegalStateException when response does not represent a {@link SubscriptionDiscovery} * @throws DavException for failures in obtaining/parsing the response body */ - public SubscriptionDiscovery getResponseBodyAsSubscriptionDiscovery(HttpResponse response) throws DavException { + public SubscriptionDiscovery getResponseBodyAsSubscriptionDiscovery(ClassicHttpResponse response) throws DavException { try { Document doc = getResponseBodyAsDocument(response.getEntity()); if (doc == null) { - throw new DavException(response.getStatusLine().getStatusCode(), "no response body"); + throw new DavException(response.getCode(), "no response body"); } Element root = doc.getDocumentElement(); if (!DomUtil.matches(root, DavConstants.XML_PROP, DavConstants.NAMESPACE) && DomUtil.hasChildElement(root, ObservationConstants.SUBSCRIPTIONDISCOVERY.getName(), ObservationConstants.SUBSCRIPTIONDISCOVERY.getNamespace())) { - throw new DavException(response.getStatusLine().getStatusCode(), + throw new DavException(response.getCode(), "Missing DAV:prop response body in SUBSCRIBE response."); } @@ -151,11 +149,11 @@ public SubscriptionDiscovery getResponseBodyAsSubscriptionDiscovery(HttpResponse if (((Subscription[]) sd.getValue()).length > 0) { return sd; } else { - throw new DavException(response.getStatusLine().getStatusCode(), + throw new DavException(response.getCode(), "Missing 'subscription' elements in SUBSCRIBE response body. At least a single subscription must be present if SUBSCRIBE was successful."); } } catch (IOException ex) { - throw new DavException(response.getStatusLine().getStatusCode(), ex); + throw new DavException(response.getCode(), ex); } } @@ -164,22 +162,22 @@ public SubscriptionDiscovery getResponseBodyAsSubscriptionDiscovery(HttpResponse * @throws IllegalStateException when response does not represent a {@link EventDiscovery} * @throws DavException for failures in obtaining/parsing the response body */ - public EventDiscovery getResponseBodyAsEventDiscovery(HttpResponse response) throws DavException { + public EventDiscovery getResponseBodyAsEventDiscovery(ClassicHttpResponse response) throws DavException { try { Document doc = getResponseBodyAsDocument(response.getEntity()); if (doc == null) { - throw new DavException(response.getStatusLine().getStatusCode(), "no response body"); + throw new DavException(response.getCode(), "no response body"); } return EventDiscovery.createFromXml(doc.getDocumentElement()); } catch (IOException ex) { - throw new DavException(response.getStatusLine().getStatusCode(), ex); + throw new DavException(response.getCode(), ex); } } /** * Check the response and throw when it is considered to represent a failure. */ - public void checkSuccess(HttpResponse response) throws DavException { + public void checkSuccess(ClassicHttpResponse response) throws DavException { if (!succeeded(response)) { throw getResponseException(response); } @@ -189,14 +187,13 @@ public void checkSuccess(HttpResponse response) throws DavException { * Obtain a {@link DavException} representing the response. * @throws IllegalStateException when the response is considered to be successful */ - public DavException getResponseException(HttpResponse response) { + public DavException getResponseException(ClassicHttpResponse response) { if (succeeded(response)) { String msg = "Cannot retrieve exception from successful response."; log.warn(msg); throw new IllegalStateException(msg); } - StatusLine st = response.getStatusLine(); Element responseRoot = null; try { responseRoot = getResponseBodyAsDocument(response.getEntity()).getDocumentElement(); @@ -204,16 +201,16 @@ public DavException getResponseException(HttpResponse response) { // non-parseable body -> use null element } - return new DavException(st.getStatusCode(), st.getReasonPhrase(), null, responseRoot); + return new DavException(response.getCode(), response.getReasonPhrase(), null, responseRoot); } /** - * Check the provided {@link HttpResponse} for successful execution. The default implementation treats all + * Check the provided {@link ClassicHttpResponse} for successful execution. The default implementation treats all * 2xx status codes (RFC 7231, Section 6.3). * Implementations can further restrict the accepted range of responses (or even check the response body). */ - public boolean succeeded(HttpResponse response) { - int status = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int status = response.getCode(); return status >= 200 && status <= 299; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpBind.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpBind.java index 4c2161810f5..cdcecaf0876 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpBind.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpBind.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.bind.BindInfo; @@ -33,7 +33,7 @@ public class HttpBind extends BaseDavRequest { public HttpBind(URI uri, BindInfo info) throws IOException { - super(uri); + super(DavMethods.METHOD_BIND, uri); super.setEntity(XmlEntity.create(info)); } @@ -42,13 +42,8 @@ public HttpBind(String uri, BindInfo info) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_BIND; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_CREATED; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckin.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckin.java index 06b67aec070..1af8c825091 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckin.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckin.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -31,7 +31,7 @@ public class HttpCheckin extends BaseDavRequest { public HttpCheckin(URI uri) { - super(uri); + super(DavMethods.METHOD_CHECKIN, uri); } public HttpCheckin(String uri) { @@ -39,13 +39,8 @@ public HttpCheckin(String uri) { } @Override - public String getMethod() { - return DavMethods.METHOD_CHECKIN; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_CREATED; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckout.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckout.java index a7f5e009bda..0ecdd361a3a 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckout.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCheckout.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -31,7 +31,7 @@ public class HttpCheckout extends BaseDavRequest { public HttpCheckout(URI uri) { - super(uri); + super(DavMethods.METHOD_CHECKOUT, uri); } public HttpCheckout(String uri) { @@ -39,13 +39,8 @@ public HttpCheckout(String uri) { } @Override - public String getMethod() { - return DavMethods.METHOD_CHECKOUT; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCopy.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCopy.java index 4f5337b8732..0b413c1be3e 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCopy.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpCopy.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -32,7 +32,7 @@ public class HttpCopy extends BaseDavRequest { public HttpCopy(URI uri, URI dest, boolean overwrite, boolean shallow) { - super(uri); + super(DavMethods.METHOD_COPY, uri); super.setHeader(DavConstants.HEADER_DESTINATION, dest.toASCIIString()); if (!overwrite) { super.setHeader(DavConstants.HEADER_OVERWRITE, "F"); @@ -47,13 +47,8 @@ public HttpCopy(String uri, String dest, boolean overwrite, boolean shallow) { } @Override - public String getMethod() { - return DavMethods.METHOD_COPY; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_NO_CONTENT; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpDelete.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpDelete.java index 99c283bab96..f948d5e0234 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpDelete.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpDelete.java @@ -29,15 +29,11 @@ public class HttpDelete extends BaseDavRequest { public HttpDelete(URI uri){ - super(uri); + super(DavMethods.METHOD_DELETE, uri); } public HttpDelete(String uri) { this(URI.create(uri)); } - @Override - public String getMethod() { - return DavMethods.METHOD_DELETE; - } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLabel.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLabel.java index 05c6f48ceea..a45b5fc951f 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLabel.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLabel.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.header.DepthHeader; @@ -34,7 +34,7 @@ public class HttpLabel extends BaseDavRequest { public HttpLabel(URI uri, LabelInfo labelInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_LABEL, uri); DepthHeader dh = new DepthHeader(labelInfo.getDepth()); super.setHeader(dh.getHeaderName(), dh.getHeaderValue()); super.setEntity(XmlEntity.create(labelInfo)); @@ -45,13 +45,8 @@ public HttpLabel(String uri, LabelInfo labelInfo) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_LABEL; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java index 171776d17c5..5ea33ce6db1 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpLock.java @@ -20,8 +20,8 @@ import java.net.URI; import java.util.Arrays; -import org.apache.http.Header; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -45,7 +45,7 @@ public class HttpLock extends BaseDavRequest { private final boolean isRefresh; public HttpLock(URI uri, LockInfo lockInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_LOCK, uri); TimeoutHeader th = new TimeoutHeader(lockInfo.getTimeout()); super.setHeader(th.getHeaderName(), th.getHeaderValue()); @@ -62,7 +62,7 @@ public HttpLock(String uri, LockInfo lockInfo) throws IOException { } public HttpLock(URI uri, long timeout, String[] lockTokens) { - super(uri); + super(DavMethods.METHOD_LOCK, uri); TimeoutHeader th = new TimeoutHeader(timeout); super.setHeader(th.getHeaderName(), th.getHeaderValue()); @@ -75,22 +75,17 @@ public HttpLock(String uri, long timeout, String[] lockTokens) { this(URI.create(uri), timeout, lockTokens); } - @Override - public String getMethod() { - return DavMethods.METHOD_LOCK; - } - - public String getLockToken(HttpResponse response) { + public String getLockToken(ClassicHttpResponse response) { Header[] ltHeader = response.getHeaders(DavConstants.HEADER_LOCK_TOKEN); if (ltHeader == null || ltHeader.length == 0) { return null; } else if (ltHeader.length != 1) { - LOG.debug("Multiple 'Lock-Token' header fields in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + LOG.debug("Multiple 'Lock-Token' header fields in response for " + getRequestUri() + ": " + Arrays.asList(ltHeader)); return null; } else { String v = ltHeader[0].getValue().trim(); if (!v.startsWith("<") || !v.endsWith(">")) { - LOG.debug("Invalid 'Lock-Token' header field in response for " + getURI() + ": " + Arrays.asList(ltHeader)); + LOG.debug("Invalid 'Lock-Token' header field in response for " + getRequestUri() + ": " + Arrays.asList(ltHeader)); return null; } else { return v.substring(1, v.length() - 1); @@ -99,8 +94,8 @@ public String getLockToken(HttpResponse response) { } @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); boolean lockTokenHeaderOk = isRefresh || null != getLockToken(response); return lockTokenHeaderOk && (statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_CREATED); } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMerge.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMerge.java index b8db71dcff2..58c31048dd8 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMerge.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMerge.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.version.MergeInfo; @@ -33,7 +33,7 @@ public class HttpMerge extends BaseDavRequest { public HttpMerge(URI uri, MergeInfo mergeInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_MERGE, uri); super.setEntity(XmlEntity.create(mergeInfo)); } @@ -42,13 +42,8 @@ public HttpMerge(String uri, MergeInfo mergeInfo) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_MERGE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); // TODO: is this correct? return statusCode == DavServletResponse.SC_MULTI_STATUS; } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkcol.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkcol.java index 5ecf6a237c1..5eb92587fa4 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkcol.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkcol.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -31,7 +31,7 @@ public class HttpMkcol extends BaseDavRequest { public HttpMkcol(URI uri) { - super(uri); + super(DavMethods.METHOD_MKCOL, uri); } public HttpMkcol(String uri) { @@ -39,13 +39,8 @@ public HttpMkcol(String uri) { } @Override - public String getMethod() { - return DavMethods.METHOD_MKCOL; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_CREATED; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkworkspace.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkworkspace.java index 1c14fe52ac3..1f593822eaa 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkworkspace.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMkworkspace.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -31,7 +31,7 @@ public class HttpMkworkspace extends BaseDavRequest { public HttpMkworkspace(URI uri) { - super(uri); + super(DavMethods.METHOD_MKWORKSPACE, uri); } public HttpMkworkspace(String uri) { @@ -39,13 +39,8 @@ public HttpMkworkspace(String uri) { } @Override - public String getMethod() { - return DavMethods.METHOD_MKWORKSPACE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_CREATED; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMove.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMove.java index 2411e0891a8..86f41adb2b0 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMove.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpMove.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -32,7 +32,7 @@ public class HttpMove extends BaseDavRequest { public HttpMove(URI uri, URI dest, boolean overwrite) { - super(uri); + super(DavMethods.METHOD_MOVE, uri); super.setHeader(DavConstants.HEADER_DESTINATION, dest.toASCIIString()); if (!overwrite) { super.setHeader(DavConstants.HEADER_OVERWRITE, "F"); @@ -44,13 +44,8 @@ public HttpMove(String uri, String dest, boolean overwrite) { } @Override - public String getMethod() { - return DavMethods.METHOD_MOVE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_NO_CONTENT; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOptions.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOptions.java index 4986ee2b9a7..449e1a4a96c 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOptions.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOptions.java @@ -21,8 +21,8 @@ import java.util.HashSet; import java.util.Set; -import org.apache.http.Header; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.header.FieldValueParser; import org.apache.jackrabbit.webdav.search.SearchConstants; @@ -33,7 +33,7 @@ * @see RFC 7231, Section 4.3.7 * @since 2.13.6 */ -public class HttpOptions extends org.apache.http.client.methods.HttpOptions { +public class HttpOptions extends org.apache.hc.client5.http.classic.methods.HttpOptions { public HttpOptions(URI uri) { super(uri); @@ -46,7 +46,7 @@ public HttpOptions(String uri) { /** * Compute the set of compliance classes returned in the "dav" header field */ - public Set getDavComplianceClasses(HttpResponse response) { + public Set getDavComplianceClasses(ClassicHttpResponse response) { Header[] headers = response.getHeaders(DavConstants.HEADER_DAV); return parseTokenOrCodedUrlheaderField(headers, false); } @@ -54,7 +54,7 @@ public Set getDavComplianceClasses(HttpResponse response) { /** * Compute set of search grammars returned in the "dasl" header field */ - public Set getSearchGrammars(HttpResponse response) { + public Set getSearchGrammars(ClassicHttpResponse response) { Header[] headers = response.getHeaders(SearchConstants.HEADER_DASL); return parseTokenOrCodedUrlheaderField(headers, true); } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOrderpatch.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOrderpatch.java index 0e6f0fbf06e..401f7682366 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOrderpatch.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpOrderpatch.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.ordering.OrderPatch; @@ -33,7 +33,7 @@ public class HttpOrderpatch extends BaseDavRequest { public HttpOrderpatch(URI uri, OrderPatch info) throws IOException { - super(uri); + super(DavMethods.METHOD_ORDERPATCH, uri); super.setEntity(XmlEntity.create(info)); } @@ -42,12 +42,7 @@ public HttpOrderpatch(String uri, OrderPatch info) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_ORDERPATCH; - } - - @Override - public boolean succeeded(HttpResponse response) { - return response.getStatusLine().getStatusCode() == DavServletResponse.SC_OK; + public boolean succeeded(ClassicHttpResponse response) { + return response.getCode() == DavServletResponse.SC_OK; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPoll.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPoll.java index af7d620846b..f8307fa1cce 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPoll.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPoll.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.header.PollTimeoutHeader; @@ -33,7 +33,7 @@ public class HttpPoll extends BaseDavRequest { public HttpPoll(URI uri, String subscriptionId, long timeout) { - super(uri); + super(DavMethods.METHOD_POLL, uri); super.setHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, subscriptionId); if (timeout > 0) { PollTimeoutHeader th = new PollTimeoutHeader(timeout); @@ -46,13 +46,8 @@ public HttpPoll(String uri, String subscriptionId, long timeout) { } @Override - public String getMethod() { - return DavMethods.METHOD_POLL; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPropfind.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPropfind.java index e548db0697a..a969dbe2245 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPropfind.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpPropfind.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -36,7 +36,7 @@ public class HttpPropfind extends BaseDavRequest { public HttpPropfind(URI uri, int propfindType, DavPropertyNameSet names, int depth) throws IOException { - super(uri); + super(DavMethods.METHOD_PROPFIND, uri); DepthHeader dh = new DepthHeader(depth); super.setHeader(dh.getHeaderName(), dh.getHeaderValue()); @@ -66,12 +66,7 @@ public HttpPropfind(String uri, DavPropertyNameSet names, int depth) throws IOEx } @Override - public String getMethod() { - return DavMethods.METHOD_PROPFIND; - } - - @Override - public boolean succeeded(HttpResponse response) { - return response.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS; + public boolean succeeded(ClassicHttpResponse response) { + return response.getCode() == DavServletResponse.SC_MULTI_STATUS; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpProppatch.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpProppatch.java index 2cd35f8f0f6..3fbfb9e5fb1 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpProppatch.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpProppatch.java @@ -20,7 +20,7 @@ import java.net.URI; import java.util.List; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; @@ -39,7 +39,7 @@ public class HttpProppatch extends BaseDavRequest { // private DavPropertyNameSet propertyNames; public HttpProppatch(URI uri, ProppatchInfo info) throws IOException { - super(uri); + super(DavMethods.METHOD_PROPPATCH, uri); super.setEntity(XmlEntity.create(info)); // this.propertyNames = info.getAffectedProperties(); } @@ -61,13 +61,8 @@ public HttpProppatch(String uri, DavPropertySet setProperties, DavPropertyNameSe } @Override - public String getMethod() { - return DavMethods.METHOD_PROPPATCH; - } - - @Override - public boolean succeeded(HttpResponse response) { - return response.getStatusLine().getStatusCode() == DavServletResponse.SC_MULTI_STATUS; + public boolean succeeded(ClassicHttpResponse response) { + return response.getCode() == DavServletResponse.SC_MULTI_STATUS; // disabled code that fails for current PROPPATCH behavior of Jackrabbit // MultiStatusResponse responses[] = super.getResponseBodyAsMultiStatus(response).getResponses(); diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpRebind.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpRebind.java index e72dea89008..292b063d255 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpRebind.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpRebind.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.bind.RebindInfo; @@ -33,7 +33,7 @@ public class HttpRebind extends BaseDavRequest { public HttpRebind(URI uri, RebindInfo info) throws IOException { - super(uri); + super(DavMethods.METHOD_REBIND, uri); super.setEntity(XmlEntity.create(info)); } @@ -42,13 +42,8 @@ public HttpRebind(String uri, RebindInfo info) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_REBIND; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_CREATED; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpReport.java index efb20b6caf7..fd3299f4500 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpReport.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpReport.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -37,7 +37,7 @@ public class HttpReport extends BaseDavRequest { private final boolean isDeep; public HttpReport(URI uri, ReportInfo reportInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_REPORT, uri); DepthHeader dh = new DepthHeader(reportInfo.getDepth()); isDeep = reportInfo.getDepth() > DavConstants.DEPTH_0; super.setHeader(dh.getHeaderName(), dh.getHeaderValue()); @@ -49,13 +49,8 @@ public HttpReport(String uri, ReportInfo reportInfo) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_REPORT; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); if (isDeep) { return statusCode == DavServletResponse.SC_MULTI_STATUS; } else { diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSearch.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSearch.java index f230f2dfbf1..6eeb07c2add 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSearch.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSearch.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.search.SearchInfo; @@ -33,7 +33,7 @@ public class HttpSearch extends BaseDavRequest { public HttpSearch(URI uri, SearchInfo searchInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_SEARCH, uri); super.setEntity(XmlEntity.create(searchInfo)); } @@ -42,13 +42,8 @@ public HttpSearch(String uri, SearchInfo searchInfo) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_SEARCH; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_MULTI_STATUS; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSubscribe.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSubscribe.java index 6bdbb7adcdc..53651caaa41 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSubscribe.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpSubscribe.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -38,7 +38,7 @@ public class HttpSubscribe extends BaseDavRequest { public HttpSubscribe(URI uri, SubscriptionInfo info, String subscriptionId) throws IOException { - super(uri); + super(DavMethods.METHOD_SUBSCRIBE, uri); if (info == null) { throw new IllegalArgumentException("SubscriptionInfo must not be null."); } @@ -64,8 +64,8 @@ public HttpSubscribe(String uri, SubscriptionInfo info, String subscriptionId) t this(URI.create(uri), info, subscriptionId); } - public String getSubscriptionId(HttpResponse response) { - org.apache.http.Header sbHeader = response.getFirstHeader(ObservationConstants.HEADER_SUBSCRIPTIONID); + public String getSubscriptionId(ClassicHttpResponse response) { + org.apache.hc.core5.http.Header sbHeader = response.getFirstHeader(ObservationConstants.HEADER_SUBSCRIPTIONID); if (sbHeader != null) { CodedUrlHeader cuh = new CodedUrlHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, sbHeader.getValue()); return cuh.getCodedUrl(); @@ -76,13 +76,8 @@ public String getSubscriptionId(HttpResponse response) { } @Override - public String getMethod() { - return DavMethods.METHOD_SUBSCRIBE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnbind.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnbind.java index 10e7f4a5b50..cd8be95be88 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnbind.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnbind.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.bind.UnbindInfo; @@ -33,7 +33,7 @@ public class HttpUnbind extends BaseDavRequest { public HttpUnbind(URI uri, UnbindInfo info) throws IOException { - super(uri); + super(DavMethods.METHOD_UNBIND, uri); super.setEntity(XmlEntity.create(info)); } @@ -42,13 +42,8 @@ public HttpUnbind(String uri, UnbindInfo info) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_UNBIND; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_NO_CONTENT; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnlock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnlock.java index aee34736539..e7e9a3fa0b0 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnlock.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnlock.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -33,7 +33,7 @@ public class HttpUnlock extends BaseDavRequest { public HttpUnlock(URI uri, String lockToken) { - super(uri); + super(DavMethods.METHOD_UNLOCK, uri); CodedUrlHeader lth = new CodedUrlHeader(DavConstants.HEADER_LOCK_TOKEN, lockToken); super.setHeader(lth.getHeaderName(), lth.getHeaderValue()); } @@ -43,13 +43,8 @@ public HttpUnlock(String uri, String lockToken) { } @Override - public String getMethod() { - return DavMethods.METHOD_UNLOCK; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK || statusCode == DavServletResponse.SC_NO_CONTENT; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnsubscribe.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnsubscribe.java index 522f17fce6e..a2a7f36c918 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnsubscribe.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUnsubscribe.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.observation.ObservationConstants; @@ -32,7 +32,7 @@ public class HttpUnsubscribe extends BaseDavRequest { public HttpUnsubscribe(URI uri, String subscriptionId) { - super(uri); + super(DavMethods.METHOD_UNSUBSCRIBE, uri); super.setHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, subscriptionId); } @@ -41,13 +41,8 @@ public HttpUnsubscribe(String uri, String subscriptionId) { } @Override - public String getMethod() { - return DavMethods.METHOD_UNSUBSCRIBE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_NO_CONTENT; } } \ No newline at end of file diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUpdate.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUpdate.java index 019a24fff62..6c0845b012a 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUpdate.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpUpdate.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.version.UpdateInfo; @@ -33,7 +33,7 @@ public class HttpUpdate extends BaseDavRequest { public HttpUpdate(URI uri, UpdateInfo updateInfo) throws IOException { - super(uri); + super(DavMethods.METHOD_UPDATE, uri); super.setEntity(XmlEntity.create(updateInfo)); } @@ -42,13 +42,8 @@ public HttpUpdate(String uri, UpdateInfo updateInfo) throws IOException { } @Override - public String getMethod() { - return DavMethods.METHOD_UPDATE; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_MULTI_STATUS; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpVersionControl.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpVersionControl.java index e23a601bee7..cfd8da9e425 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpVersionControl.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/HttpVersionControl.java @@ -18,7 +18,7 @@ import java.net.URI; -import org.apache.http.HttpResponse; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.jackrabbit.webdav.DavMethods; import org.apache.jackrabbit.webdav.DavServletResponse; @@ -31,7 +31,7 @@ public class HttpVersionControl extends BaseDavRequest { public HttpVersionControl(URI uri) { - super(uri); + super(DavMethods.METHOD_VERSION_CONTROL, uri); } public HttpVersionControl(String uri) { @@ -39,13 +39,8 @@ public HttpVersionControl(String uri) { } @Override - public String getMethod() { - return DavMethods.METHOD_VERSION_CONTROL; - } - - @Override - public boolean succeeded(HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); + public boolean succeeded(ClassicHttpResponse response) { + int statusCode = response.getCode(); return statusCode == DavServletResponse.SC_OK; } } diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/XmlEntity.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/XmlEntity.java index 859bbe40442..b92bae083f9 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/XmlEntity.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/XmlEntity.java @@ -22,9 +22,9 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; -import org.apache.http.HttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.ContentType; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.ContentType; import org.apache.jackrabbit.webdav.xml.DomUtil; import org.apache.jackrabbit.webdav.xml.XmlSerializable; import org.slf4j.Logger; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/package-info.java index b667d39c615..6d82fe84abf 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/package-info.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/package-info.java @@ -19,13 +19,17 @@ * Provides classes for use with the Apache HttpClient, supporting WebDAV * request methods. *

- * This version also contains classes for use with the obsolete "Commons - * HttpClient"; they have been marked "deprecated" and will be removed in the - * next major release. - * + * As of package version 3.0.0 these classes are built on Apache HttpClient 5. This is a + * breaking change: {@link org.apache.jackrabbit.webdav.client.methods.BaseDavRequest} + * now extends + * {@code org.apache.hc.client5.http.classic.methods.HttpUriRequestBase} and takes + * the request method as its first constructor argument, and the response + * accessors take {@code org.apache.hc.core5.http.ClassicHttpResponse} in place of + * the HttpClient 4 {@code HttpResponse}. + * * @see JCR-2406 * @see https://hc.apache.org/httpcomponents-client-4.5.x/ + * "https://hc.apache.org/httpcomponents-client-5.6.x/">https://hc.apache.org/httpcomponents-client-5.6.x/ */ -@org.osgi.annotation.versioning.Version("2.0.0") +@org.osgi.annotation.versioning.Version("3.0.0") package org.apache.jackrabbit.webdav.client.methods; diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java index bc183959219..10bc16b1e97 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java @@ -26,8 +26,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicHeaderValueParser; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.message.BasicHeaderValueParser; +import org.apache.hc.core5.http.message.ParserCursor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -161,7 +162,9 @@ public LinkRelation(String field) throws Exception { target = m.group(1); // pass the remainder to the generic parameter parser - NameValuePair[] params = BasicHeaderValueParser.parseParameters(m.group(2), null); + String paramStr = m.group(2); + NameValuePair[] params = BasicHeaderValueParser.INSTANCE.parseParameters( + paramStr, new ParserCursor(0, paramStr.length())); if (params.length == 0) { parameters = Collections.emptyMap(); From e17d122b65b80b60be9473e0f099a79b36f8a0ce Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sat, 8 Aug 2026 11:06:26 +0200 Subject: [PATCH 3/4] JCR-5255: Migrate to Apache HttpClient 5 - address review findings - restore HttpClient 4 semantics for connection options: null proxy password, requestTimeoutMs=0 as infinite lease wait, and negative connect/socket timeouts as disabled - copy the SimpleCredentials password instead of aliasing the caller's array - send the absolute request URI in the Referer header again - build TLS on the supported TlsSocketStrategy API instead of the deprecated SSLConnectionSocketFactory/TrustSelfSignedStrategy - use a token-only multipart boundary and set Content-Disposition explicitly so no RFC 5987 filename* parameter is emitted for non-Latin-1 JCR paths - fail fast in Rfc6532MultipartEntity.getContent() like HttpClient 4 did and measure the framing without buffering it - avoid NPEs on bodyless error responses, responses without a Content-Type charset, and CHECKIN responses without a Location header --- .../spi2dav/CredentialsWrapper.java | 5 ++- .../spi2dav/RepositoryServiceImpl.java | 40 ++++++++++------- .../spi2davex/RepositoryServiceImpl.java | 32 ++++++++++---- .../spi2davex/Rfc6532MultipartEntity.java | 43 +++++++++++++++++-- .../apache/jackrabbit/spi2davex/Utils.java | 19 +++++++- .../spi2davex/Rfc6532MultipartEntityTest.java | 23 ++++++++++ .../webdav/client/methods/BaseDavRequest.java | 5 ++- 7 files changed, 135 insertions(+), 32 deletions(-) diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java index fb459a59d3c..ee20fee7d5c 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/CredentialsWrapper.java @@ -38,7 +38,10 @@ class CredentialsWrapper { } else if (creds instanceof SimpleCredentials) { SimpleCredentials sCred = (SimpleCredentials) creds; userId = sCred.getUserID(); - this.credentials = new UsernamePasswordCredentials(userId, sCred.getPassword()); + // copy the password: SimpleCredentials hands out its internal array and + // UsernamePasswordCredentials keeps the reference, so without a copy a + // caller zeroing the password after login would break the open session + this.credentials = new UsernamePasswordCredentials(userId, sCred.getPassword().clone()); } else { userId = ""; // HttpClient 5 dropped the single-argument "username:password" diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java index 37f0f2e84aa..3dd2ba88547 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java @@ -75,9 +75,9 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; +import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.io.entity.StringEntity; @@ -364,7 +364,7 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, // PUT or DELETE surfaces to the caller instead of silently retargeting hcb.setRedirectStrategy(GetHeadRedirectStrategy.INSTANCE); - final SSLConnectionSocketFactory sslSocketFactory; + final TlsSocketStrategy tlsSocketStrategy; // request config RequestConfig requestConfig = RequestConfig.custom() @@ -392,14 +392,16 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, // support Java system proxy? (JCR-3211) hcb.useSystemProperties(); - sslSocketFactory = SSLConnectionSocketFactory.getSystemSocketFactory(); + tlsSocketStrategy = DefaultClientTlsStrategy.createSystemDefault(); } else { // TLS settings (via connection manager) final SSLContext sslContext; try { if (connectionOptions.isAllowSelfSignedCertificates()) { log.warn("Nonsecure TLS setting: Accepting self-signed certificates!"); - sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + // what the deprecated TrustSelfSignedStrategy did: trust any + // certificate that arrives without an issuer chain + sslContext = SSLContextBuilder.create().loadTrustMaterial((chain, authType) -> chain.length == 1).build(); } else { sslContext = SSLContextBuilder.create().build(); } @@ -410,14 +412,14 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, if (connectionOptions.isDisableHostnameVerification()) { log.warn("Nonsecure TLS setting: Host name verification of TLS certificates disabled!"); // we can optionally disable hostname verification. - sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE); } else { - sslSocketFactory = new SSLConnectionSocketFactory(sslContext); + tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext); } } PoolingHttpClientConnectionManagerBuilder cmgrBuilder = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslSocketFactory) + .setTlsSocketStrategy(tlsSocketStrategy) .setDefaultConnectionConfig(connectionConfig); int maxConnections = connectionOptions.getMaxConnections(); @@ -439,10 +441,11 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, log.debug("Proxy connection with credentials {}", proxy); // HttpClient 5 handles proxy authentication through the shared // authentication strategy, so no separate proxy strategy is needed + String proxyPassword = connectionOptions.getProxyPassword(); commonCredentials.put( new AuthScope(proxy), new UsernamePasswordCredentials(connectionOptions.getProxyUsername(), - connectionOptions.getProxyPassword().toCharArray())); + proxyPassword == null ? new char[0] : proxyPassword.toCharArray())); } } httpClientBuilder = hcb; @@ -464,22 +467,24 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, /** * Converts a jackrabbit connect or socket timeout in milliseconds to an HttpClient 5 - * {@link Timeout}. The value -1 means "not configured", which under HttpClient 4 left - * the timeout infinite; zero carries that meaning to the socket layer. Simply omitting - * the setter would instead pick up the HttpClient 5 default of three minutes. + * {@link Timeout}. The value -1 means "not configured" and HttpClient 4 treated any + * other negative value as infinite too, so all of them map to a disabled timeout; + * zero carries that meaning to the socket layer as well. Simply omitting the setter + * would instead pick up the HttpClient 5 default of three minutes. */ private static Timeout toTimeout(int timeoutMs) { - return timeoutMs == -1 ? Timeout.DISABLED : Timeout.ofMilliseconds(timeoutMs); + return timeoutMs < 0 ? Timeout.DISABLED : Timeout.ofMilliseconds(timeoutMs); } /** * Converts a jackrabbit connection request timeout in milliseconds to an HttpClient 5 * {@link Timeout}. This is the time spent waiting for a connection from the pool, where - * a zero timeout means "fail immediately" rather than "wait forever", so -1 has to map - * to an explicitly unbounded value. + * a zero timeout means "fail immediately" rather than the "wait forever" it meant under + * HttpClient 4, so zero and every negative value have to map to an explicitly unbounded + * value. */ private static Timeout toLeaseTimeout(int timeoutMs) { - return timeoutMs == -1 ? NO_TIMEOUT : Timeout.ofMilliseconds(timeoutMs); + return timeoutMs <= 0 ? NO_TIMEOUT : Timeout.ofMilliseconds(timeoutMs); } private static void checkSessionInfo(SessionInfo sessionInfo) throws RepositoryException { @@ -1925,6 +1930,9 @@ public NodeId checkin(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryE ClassicHttpResponse response = executeRequest(sessionInfo, request); request.checkSuccess(response); Header rh = response.getFirstHeader(DeltaVConstants.HEADER_LOCATION); + if (rh == null) { + throw new RepositoryException("CHECKIN of " + uri + " failed: no Location header in response."); + } return uriResolver.getNodeId(resolve(uri, rh.getValue()), sessionInfo); } catch (IOException e) { throw new RepositoryException(e); diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java index 9d2547c29e7..fb48b088864 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java @@ -21,6 +21,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -353,7 +354,10 @@ public Iterator getItemInfos(SessionInfo sessionInfo, ItemId ItemInfoJsonHandler handler = new ItemInfoJsonHandler(resolver, nInfo, getRootURI(sessionInfo), getQValueFactory(sessionInfo), getPathFactory(), getIdFactory()); JsonParser ps = new JsonParser(handler); - ps.parse(entity.getContent(), ContentType.parse(entity.getContentType()).getCharset().name()); + // both the header and its charset parameter are optional; JSON defaults to UTF-8 + ContentType contentType = ContentType.parse(entity.getContentType()); + Charset charset = contentType == null ? null : contentType.getCharset(); + ps.parse(entity.getContent(), (charset == null ? StandardCharsets.UTF_8 : charset).name()); Iterator it = handler.getItemInfos(); if (!it.hasNext()) { @@ -466,8 +470,11 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod } HttpPost request = null; try { - request = new HttpPost(getWorkspaceURI(sessionInfo)); - request.setHeader("Referer", request.getRequestUri()); + String workspaceUri = getWorkspaceURI(sessionInfo); + request = new HttpPost(workspaceUri); + // the absolute request URI: HttpClient 5's getRequestUri() would only + // return the path, which a CSRF referrer check may reject + request.setHeader("Referer", workspaceUri); addIfHeader(sessionInfo, request); NamePathResolver resolver = getNamePathResolver(sessionInfo); @@ -502,8 +509,10 @@ public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNod public void clone(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, Name destName, boolean removeExisting) throws RepositoryException { HttpPost request = null; try { - request = new HttpPost(getWorkspaceURI(sessionInfo)); - request.setHeader("Referer", request.getRequestUri()); + String workspaceUri = getWorkspaceURI(sessionInfo); + request = new HttpPost(workspaceUri); + // the absolute request URI, see copy() + request.setHeader("Referer", workspaceUri); addIfHeader(sessionInfo, request); NamePathResolver resolver = getNamePathResolver(sessionInfo); @@ -580,7 +589,12 @@ private BatchImpl(ItemId targetId, SessionInfo sessionInfo) { private void start() throws RepositoryException { checkConsumed(); - request.setHeader("Referer", request.getRequestUri()); + try { + // the absolute request URI, see copy() + request.setHeader("Referer", request.getUri().toASCIIString()); + } catch (URISyntaxException e) { + throw new RepositoryException(e); + } // add lock tokens addIfHeader(sessionInfo, request); @@ -600,8 +614,10 @@ private void start() throws RepositoryException { Utils.addPart(PARAM_DIFF, buf.toString(), parts); // JCR-4317: part names carry the JCR path and must survive as UTF-8, - // which MultipartEntityBuilder cannot do in HttpClient 5 - request.setEntity(new Rfc6532MultipartEntity(parts, "----=_Part_" + UUID.randomUUID())); + // which MultipartEntityBuilder cannot do in HttpClient 5. + // The boundary must consist of token characters only ('=' is not one), + // because Rfc6532MultipartEntity does not quote it in the Content-Type + request.setEntity(new Rfc6532MultipartEntity(parts, "----Part_" + UUID.randomUUID())); HttpClient client = getClient(sessionInfo); try { diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java index 6c0201f74ab..b179cc9118b 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntity.java @@ -29,6 +29,7 @@ import org.apache.hc.client5.http.entity.mime.FormBodyPart; import org.apache.hc.client5.http.entity.mime.MimeField; import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.ContentTooLongException; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; @@ -79,13 +80,32 @@ private long computeContentLength() { } total += length; } - ByteArrayOutputStream framing = new ByteArrayOutputStream(); + CountingOutputStream framing = new CountingOutputStream(); try { writeTo(framing, false); } catch (IOException e) { - return -1; + throw new AssertionError("CountingOutputStream does not throw", e); + } + return total + framing.count; + } + + /** + * Measures the framing without buffering it: the part headers are written once + * more for real in {@link #writeTo(OutputStream)}. + */ + private static final class CountingOutputStream extends OutputStream { + + private long count; + + @Override + public void write(int b) { + count++; + } + + @Override + public void write(byte[] b, int off, int len) { + count += len; } - return total + framing.size(); } @Override @@ -122,7 +142,8 @@ private void writeTo(OutputStream out, boolean writeContent) throws IOException @Override public String getContentType() { // deliberately without a charset parameter, so that a server does not decode the - // UTF-8 part headers as anything else + // UTF-8 part headers as anything else. The boundary is emitted unquoted and must + // therefore consist of token characters only. return "multipart/form-data; boundary=" + boundary; } @@ -151,8 +172,22 @@ public String getContentEncoding() { return null; } + /** + * The 25 KB limit HttpClient 4's {@code MultipartFormEntity.getContent()} enforced + * before buffering; anything larger (or of unknown length) is expected to go + * through {@link #writeTo(OutputStream)} instead. + */ + private static final long MAX_BUFFERED_CONTENT_LENGTH = 25 * 1024; + @Override public InputStream getContent() throws IOException { + // do not buffer arbitrarily large bodies; buffering would also drain any + // stream-backed part, leaving a subsequent writeTo with empty part bodies + if (contentLength < 0) { + throw new ContentTooLongException("Content length is unknown"); + } else if (contentLength > MAX_BUFFERED_CONTENT_LENGTH) { + throw new ContentTooLongException("Content length is too long: " + contentLength); + } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); writeTo(buffer, true); return new ByteArrayInputStream(buffer.toByteArray()); diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java index 0da7a03563b..c7a6eab28cd 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java @@ -46,6 +46,14 @@ final class Utils { private Utils() {}; + /** + * Quotes a Content-Disposition parameter value, escaping backslashes and quotes the + * way HttpClient 4 did. + */ + private static String quote(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + /** * HttpClient 5 no longer derives a Content-Transfer-Encoding header from the body, * whereas HttpClient 4 emitted 8bit for string bodies and binary for stream bodies. @@ -116,9 +124,16 @@ static void addPart(String paramName, QValue value, NamePathResolver resolver, L switch (value.getType()) { case PropertyType.BINARY: binaries.add(value); - // server detects binaries based on presence of filename parameters (JCR-4154) + // server detects binaries based on presence of filename parameters (JCR-4154). + // Set the Content-Disposition explicitly: for a path outside ISO-8859-1 the + // builder would append an RFC 5987 encoded filename* parameter that + // HttpClient 4 never emitted, and the server would prefer it over the + // verbatim JCR path in the filename parameter part = withTransferEncoding( - builder.setBody(new InputStreamBody(value.getStream(), ctype, paramName)).build(), + builder.setBody(new InputStreamBody(value.getStream(), ctype, paramName)) + .setField("Content-Disposition", + "form-data; name=" + quote(paramName) + "; filename=" + quote(paramName)) + .build(), TRANSFER_ENCODING_BINARY); break; case PropertyType.NAME: diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java index 07339780a10..a203df49570 100644 --- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2davex/Rfc6532MultipartEntityTest.java @@ -108,6 +108,29 @@ public void testQuotesAndBackslashesInPartNameAreEscaped() throws Exception { "name=\"/testroot/na\\\"me-with\\\\quote-ä\"")); } + /** + * For a path outside ISO-8859-1 the builder would append an RFC 5987 encoded + * {@code filename*} parameter that HttpClient 4 never emitted, so {@code Utils} + * sets the Content-Disposition explicitly; this pins that construction. + */ + @Test + public void testNonLatin1PartNameSurvivesWithoutFilenameStar() throws Exception { + String name = "/testroot/テスト/jcr:content/jcr:data"; + List parts = new ArrayList(); + FormBodyPart binary = FormBodyPartBuilder.create().setName(name) + .setBody(new InputStreamBody( + new ByteArrayInputStream("XYZ".getBytes(StandardCharsets.UTF_8)), BINARY, name)) + .setField("Content-Disposition", "form-data; name=\"" + name + "\"; filename=\"" + name + "\"") + .build(); + binary.getHeader().addField(new MimeField("Content-Transfer-Encoding", "binary")); + parts.add(binary); + + String written = write(parts); + Assert.assertFalse("no RFC 5987 filename* parameter expected", written.contains("filename*")); + Assert.assertTrue(written.contains( + "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + name + "\"\r\n")); + } + @Test public void testContentTypeCarriesNoCharset() { // a charset here would make the server decode the UTF-8 part headers as something else diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java index b5fd5721865..da649c36234 100644 --- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java +++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BaseDavRequest.java @@ -196,7 +196,10 @@ public DavException getResponseException(ClassicHttpResponse response) { Element responseRoot = null; try { - responseRoot = getResponseBodyAsDocument(response.getEntity()).getDocumentElement(); + Document doc = getResponseBodyAsDocument(response.getEntity()); + if (doc != null) { + responseRoot = doc.getDocumentElement(); + } } catch (IOException e) { // non-parseable body -> use null element } From 1697ce8cb654095c2176bf855da5041443f10867 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sat, 8 Aug 2026 11:50:07 +0200 Subject: [PATCH 4/4] JCR-5255: fix TLS hostname verification handling for HttpClient 5 DefaultClientTlsStrategy defaults to HostnameVerificationPolicy.BOTH, so the JSSE built-in endpoint identification still ran even when a NoopHostnameVerifier was passed, making ConnectionOptions.disableHostnameVerification ineffective. Pass HostnameVerificationPolicy.CLIENT along with the noop verifier. Also relax the ConnectionTest assertion for the enabled-verification case: the built-in check rejects the host name during the handshake with an SSLHandshakeException instead of the SSLPeerUnverifiedException thrown by the client-side verifier of HttpClient 4. --- .../jackrabbit/spi2dav/RepositoryServiceImpl.java | 6 +++++- .../org/apache/jackrabbit/spi2dav/ConnectionTest.java | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java index 3dd2ba88547..04d91791af9 100644 --- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java +++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java @@ -76,6 +76,7 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.client5.http.ssl.HostnameVerificationPolicy; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.TlsSocketStrategy; import org.apache.hc.core5.http.ContentType; @@ -412,7 +413,10 @@ public RepositoryServiceImpl(String uri, IdFactory idFactory, if (connectionOptions.isDisableHostnameVerification()) { log.warn("Nonsecure TLS setting: Host name verification of TLS certificates disabled!"); // we can optionally disable hostname verification. - tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE); + // HostnameVerificationPolicy.CLIENT is required in addition to the noop verifier: with the + // default policy the JSSE built-in endpoint identification still runs during the handshake + tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext, HostnameVerificationPolicy.CLIENT, + NoopHostnameVerifier.INSTANCE); } else { tlsSocketStrategy = new DefaultClientTlsStrategy(sslContext); } diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java index 88b9772878f..c142667653f 100644 --- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java +++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/ConnectionTest.java @@ -24,6 +24,7 @@ import javax.jcr.RepositoryException; import javax.jcr.SimpleCredentials; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -88,9 +89,13 @@ public void testObtainWithTLSSelfSignedCertAllowed() throws RepositoryException, try { repositoryService.obtain(new SimpleCredentials("admin", "admin".toCharArray()), null); } catch (RepositoryException e) { - Throwable cause = ExceptionUtils.getRootCause(e); - if (!(cause instanceof SSLPeerUnverifiedException)) { - fail("should have failed with SSLPeerUnverifiedException but got " + e.getCause()); + // HttpClient 5 enables the JSSE built-in endpoint identification by default, which rejects the + // host name during the handshake (SSLHandshakeException wrapping a CertificateException) instead + // of afterwards (SSLPeerUnverifiedException, as thrown by the client-side verifier of HttpClient 4) + boolean isHostnameVerificationFailure = ExceptionUtils.getThrowableList(e).stream() + .anyMatch(t -> t instanceof SSLPeerUnverifiedException || t instanceof SSLHandshakeException); + if (!isHostnameVerificationFailure) { + fail("should have failed with SSLPeerUnverifiedException or SSLHandshakeException but got " + e.getCause()); } } }