Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,27 @@ protected void mkdirs(String dir) throws IOException {
do {
String url = baseUrl + "/" + navigator.getPath();
status = doMkCol(url);
if (status == HttpStatus.SC_CREATED || status == HttpStatus.SC_METHOD_NOT_ALLOWED) {
// RFC 4918: Accept 201 Created, 200 OK (already exists), 405 Method Not Allowed (already exists)
if (status == HttpStatus.SC_CREATED
|| status == HttpStatus.SC_OK
|| status == HttpStatus.SC_METHOD_NOT_ALLOWED) {
break;
}
// RFC 4918: 409 Conflict means intermediate collection is missing, continue traversing backwards
if (status == HttpStatus.SC_CONFLICT) {
continue;
}
Comment thread
slachiewicz marked this conversation as resolved.
// Any other status is unexpected here: stop traversal and report an error
throw new IOException("Unexpected status code while creating collection: " + url
+ "; status code = " + status);
} while (navigator.backward());

// traverse forward creating missing directories
while (navigator.forward()) {
String url = baseUrl + "/" + navigator.getPath();
status = doMkCol(url);
if (status != HttpStatus.SC_CREATED) {
// RFC 4918: Accept 201 Created or 200 OK (if collection already exists from another request)
if (status != HttpStatus.SC_CREATED && status != HttpStatus.SC_OK) {
Comment on lines +144 to +145
throw new IOException("Unable to create collection: " + url + "; status code = " + status);
}
}
Expand All @@ -140,7 +151,21 @@ protected void mkdirs(String dir) throws IOException {
private int doMkCol(String url) throws IOException {
HttpMkcol method = new HttpMkcol(url);
try (CloseableHttpResponse closeableHttpResponse = execute(method)) {
return closeableHttpResponse.getStatusLine().getStatusCode();
int statusCode = closeableHttpResponse.getStatusLine().getStatusCode();

// RFC 4918: Handle redirects for MKCOL
// 3xx redirects should be followed to the new location
if (statusCode >= HttpStatus.SC_MULTIPLE_CHOICES && statusCode < HttpStatus.SC_BAD_REQUEST) {
org.apache.http.Header locationHeader = closeableHttpResponse.getFirstHeader("Location");
if (locationHeader != null) {
String redirectUrl = locationHeader.getValue();
// Follow the redirect by recursively invoking doMkCol on the new URL
// and return the status code from the final resolved location
return doMkCol(redirectUrl);
}
Comment on lines +158 to +165
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect handling logic has a potential infinite recursion issue. If a server responds with a redirect that points back to the original URL or creates a redirect loop, this will cause a stack overflow. The recursive call should include protection against redirect loops, such as tracking visited URLs or limiting the maximum number of redirects.

Copilot uses AI. Check for mistakes.
Comment on lines 153 to +165
}

return statusCode;
Comment on lines +154 to +168
} catch (HttpException e) {
throw new IOException(e.getMessage(), e);
} finally {
Expand Down Expand Up @@ -172,6 +197,14 @@ public void putDirectory(File sourceDirectory, String destinationDirectory)
}
}

protected boolean collectionExists(String url) throws IOException {
try {
return isDirectory(url);
} catch (DavException e) {
throw new IOException(e.getMessage(), e);
}
}

private boolean isDirectory(String url) throws IOException, DavException {
DavPropertyNameSet nameSet = new DavPropertyNameSet();
nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_RESOURCETYPE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,34 +137,35 @@ public void testMkdirs() throws Exception {
wagon.connect(testRepository, getAuthInfo());

try {
File dir = getRepositoryDirectory();

// check basedir also doesn't exist and will need to be created
dir = new File(dir, testRepository.getBasedir());
assertFalse(dir.exists());
String repositoryUrl = testRepository.getUrl();

// test leading /
assertFalse(new File(dir, "foo").exists());
String fooUrl = repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + "foo";
assertFalse("Collection should not exist before creation", wagon.collectionExists(fooUrl));
wagon.mkdirs("/foo");
assertTrue(new File(dir, "foo").exists());
assertTrue("Collection should exist after creation", wagon.collectionExists(fooUrl));

// test trailing /
assertFalse(new File(dir, "bar").exists());
String barUrl = repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + "bar";
assertFalse("Collection should not exist before creation", wagon.collectionExists(barUrl));
wagon.mkdirs("bar/");
assertTrue(new File(dir, "bar").exists());
assertTrue("Collection should exist after creation", wagon.collectionExists(barUrl));

// test when already exists
// test when already exists (should not fail)
wagon.mkdirs("bar");
assertTrue("Collection should still exist", wagon.collectionExists(barUrl));

// test several parts
assertFalse(new File(dir, "1/2/3/4").exists());
String deepUrl = repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + "1/2/3/4";
assertFalse("Deep collection should not exist before creation", wagon.collectionExists(deepUrl));
wagon.mkdirs("1/2/3/4");
assertTrue(new File(dir, "1/2/3/4").exists());
assertTrue("Deep collection should exist after creation", wagon.collectionExists(deepUrl));

// test additional part and trailing /
assertFalse(new File(dir, "1/2/3/4/5").exists());
String deeperUrl = repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + "1/2/3/4/5";
assertFalse("Deeper collection should not exist before creation", wagon.collectionExists(deeperUrl));
wagon.mkdirs("1/2/3/4/5/");
assertTrue(new File(dir, "1/2/3/4").exists());
assertTrue("Deeper collection should exist after creation", wagon.collectionExists(deeperUrl));
Comment on lines +140 to +168
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL construction pattern is repetitive and error-prone. The same logic for appending paths to repositoryUrl is duplicated multiple times. Consider extracting this into a helper method to reduce duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
} finally {
wagon.disconnect();

Expand All @@ -186,16 +187,13 @@ public void testMkdirsWithNoBasedir() throws Exception {
wagon.connect(testRepository, getAuthInfo());

try {
File dir = getRepositoryDirectory();

// check basedir also doesn't exist and will need to be created
dir = new File(dir, testRepository.getBasedir());
assertTrue(dir.exists());
String repositoryUrl = testRepository.getUrl();

// test leading /
assertFalse(new File(dir, "foo").exists());
String fooUrl = repositoryUrl + (repositoryUrl.endsWith("/") ? "" : "/") + "foo";
assertFalse("Collection should not exist before creation", wagon.collectionExists(fooUrl));
wagon.mkdirs("/foo");
assertTrue(new File(dir, "foo").exists());
assertTrue("Collection should exist after creation", wagon.collectionExists(fooUrl));
} finally {
wagon.disconnect();

Expand Down
Loading