-
Notifications
You must be signed in to change notification settings - Fork 106
[WAGON-583] WebDavWagon and tests now adhere to RFC 4918 #845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: wagon-3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -122,16 +122,24 @@ 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; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } 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) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| throw new IOException("Unable to create collection: " + url + "; status code = " + status); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -140,7 +148,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(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Recursive call to handle redirect - execute() will handle the redirect automatically | ||||||||||||||||||||||||||||||||||||||||||||||||
| // but we need to return the final status | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+159
to
+160
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Recursive call to handle redirect - execute() will handle the redirect automatically | |
| // but we need to return the final status | |
| // Follow the redirect by recursively invoking doMkCol on the new URL | |
| // and return the status code from the final resolved location |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
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
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The collectionExists method doesn't validate whether the resource is actually a collection. It returns true for any resource (file or collection) that returns OK or MULTI_STATUS. According to RFC 4918, PROPFIND should verify that the resourcetype property contains a collection element. Consider parsing the response to check if the resource is specifically a collection, similar to the isDirectory method implementation.
| DavPropertyNameSet nameSet = new DavPropertyNameSet(); | |
| nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_RESOURCETYPE)); | |
| CloseableHttpResponse closeableHttpResponse = null; | |
| HttpPropfind method = null; | |
| try { | |
| method = new HttpPropfind(url, nameSet, DavConstants.DEPTH_0); | |
| closeableHttpResponse = execute(method); | |
| int statusCode = closeableHttpResponse.getStatusLine().getStatusCode(); | |
| return statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_MULTI_STATUS; | |
| } catch (HttpException e) { | |
| throw new IOException(e.getMessage(), e); | |
| } finally { | |
| if (method != null) { | |
| method.releaseConnection(); | |
| } | |
| if (closeableHttpResponse != null) { | |
| closeableHttpResponse.close(); | |
| } | |
| try { | |
| return isDirectory(url); | |
| } catch (DavException e) { | |
| throw new IOException(e.getMessage(), e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backward traversal loop doesn't handle unexpected status codes properly. If doMkCol returns a status code that is not CREATED, OK, METHOD_NOT_ALLOWED, or CONFLICT (e.g., 403 Forbidden, 401 Unauthorized, 500 Internal Server Error), the loop will continue traversing backwards indefinitely until reaching the root. This could lead to incorrect behavior or silent failures. Consider throwing an exception for unexpected error status codes.