Skip to content

fix #89: reconstruct baseUrl from URL components to avoid query/fragment corruption - #109

Open
phaneendra-injarapu wants to merge 2 commits into
apache:masterfrom
phaneendra-injarapu:fix/issue-89-baseurl-query-fragment
Open

fix #89: reconstruct baseUrl from URL components to avoid query/fragment corruption#109
phaneendra-injarapu wants to merge 2 commits into
apache:masterfrom
phaneendra-injarapu:fix/issue-89-baseurl-query-fragment

Conversation

@phaneendra-injarapu

@phaneendra-injarapu phaneendra-injarapu commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #89

Problem

DefaultDownloadManager.java reconstructed the Wagon baseUrl by subtracting
the length of the URL path from the end of the raw URL string:

// Before (buggy)
String remotePath = sourceUrl.getPath();   // e.g. "/path/file.jar" — no query/fragment
String baseUrl = url.substring(0, url.length() - remotePath.length());

URL.getPath() correctly strips the query string and fragment, but the
subtraction is applied against the full raw URL string which still carries
those suffixes. The result is a truncated, invalid baseUrl:

┌─────────────────────────────────────┬─────────────────────────┬────────────────────────────┐
│              Input URLremotePathBuggy baseUrl        │
├─────────────────────────────────────┼─────────────────────────┼────────────────────────────┤
│ http://host/path/file.jar?token=abc │ /path/file.jar (len 14) │ http://host/path/file.j ❌ │
├─────────────────────────────────────┼─────────────────────────┼────────────────────────────┤
│ http://host/path/file.jar#section   │ /path/file.jar (len 14) │ http://host/path/file ❌   │
└─────────────────────────────────────┴─────────────────────────┴────────────────────────────┘

This corrupts the Repository URL passed to wagon.connect(), breaking Wagon
connectivity for any URL that contains query parameters or a fragment.

Fix

Reconstruct baseUrl explicitly from the java.net.URL object's own component
fieldsprotocol, userInfo, host, and port. These fields are never
contaminated by query strings or fragments:

// After (correct)
String remotePath = sourceUrl.getPath();
int port = sourceUrl.getPort();
String baseUrl = sourceUrl.getProtocol() + "://"
        + (sourceUrl.getUserInfo() != null ? sourceUrl.getUserInfo() + "@" : "")
        + sourceUrl.getHost()
        + (port != -1 ? ":" + port : "");

Correct results for all URL forms:

┌─────────────────────────────────────┬────────────────────────┐
│              Input URLNew baseUrl       │
├─────────────────────────────────────┼────────────────────────┤
│ http://host/path/file.jar           │ http://host ✅         │
├─────────────────────────────────────┼────────────────────────┤
│ http://host/path/file.jar?token=abc │ http://host ✅         │
├─────────────────────────────────────┼────────────────────────┤
│ http://host/path/file.jar#section   │ http://host ✅         │
├─────────────────────────────────────┼────────────────────────┤
│ http://host:8080/path/file.jar      │ http://host:8080 ✅    │
├─────────────────────────────────────┼────────────────────────┤
│ http://user:pw@host/path/file.jar   │ http://user:pw@host ✅ │
├─────────────────────────────────────┼────────────────────────┤
│ file:///tmp/file.jar                │ file:// ✅             │
└─────────────────────────────────────┴────────────────────────┘

All pre-existing URL forms produce the same result as beforeno behaviour
change for plain URLs without query strings or fragments.

Changes

DefaultDownloadManager.javareplaced 1 line of string arithmetic with
4-line component-based reconstruction (lines 117-124).

DefaultDownloadManagerTest.javaadded 2 regression tests using
EasyMock's Capture<Repository> to assert that Repository.getUrl() equals
exactly "http://example.com" with no trailing path, query string, or fragment:

- shouldUseCorrectBaseUrlWhenUrlHasQueryStringinput: http://example.com/path/file.jar?token=abc
- shouldUseCorrectBaseUrlWhenUrlHasFragmentinput: http://example.com/path/file.jar#section

Contribution Checklist

  • Your pull request should address just one issue, without pulling in other changes.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body.
    Note that commits might be squashed by a maintainer on merge.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied.
    (Two regression tests added: shouldHandleUrlWithQueryString and shouldHandleUrlWithFragment
    both assert the exact baseUrl passed to wagon.connect() and would fail without the fix.
    All 82 tests pass.)
  • Run mvn verify to make sure basic checks pass.
    (Ran mvn surefire:test — 82 tests, 0 failures, 0 errors. mvn verify fails locally due to
    missing network access for spotless/checkstyle plugins; CI will perform the full check.)

If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.

(This PR changes 56 lines of code — ICLA may be required. Please check with the developers list if unsure.)

To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.

…/fragment corruption

When DefaultDownloadManager split a download URL into baseUrl and
remotePath, it used string arithmetic:

    String remotePath = sourceUrl.getPath();
    String baseUrl = url.substring(0, url.length() - remotePath.length());

sourceUrl.getPath() returns only the path portion (e.g. /path/file.jar),
stripping any query string or fragment.  The full 'url' string still
carries those suffixes, so the substring cut lands in the middle of the
path, producing a completely wrong baseUrl and breaking Wagon
connectivity (e.g. http://host/path/file.j instead of http://host).

Fix: reconstruct baseUrl explicitly from the URL object's own
protocol/userInfo/host/port components, which are never contaminated
by query strings or fragments.

Adds two regression tests (query string and fragment) that capture
the Repository argument passed to wagon.connect() and assert the URL
equals exactly "http://example.com" with no trailing path or query.
@phaneendra-injarapu
phaneendra-injarapu force-pushed the fix/issue-89-baseurl-query-fragment branch from bf570dd to cc51f2c Compare July 6, 2026 07:49
@elharo
elharo requested a review from Copilot August 1, 2026 11:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a URL-splitting bug in DefaultDownloadManager where reconstructing the Wagon baseUrl via string subtraction could corrupt the URL when a query string (?…) or fragment (#…) is present, breaking Wagon connectivity.

Changes:

  • Reworks baseUrl reconstruction in DefaultDownloadManager to avoid query/fragment corruption.
  • Adds regression tests asserting the exact Repository.getUrl() passed to wagon.connect() for URLs containing a query string or fragment.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java Updates base URL reconstruction logic used for Wagon connection.
src/test/java/org/apache/maven/shared/io/download/DefaultDownloadManagerTest.java Adds regression coverage to ensure baseUrl excludes query strings/fragments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/org/apache/maven/shared/io/download/DefaultDownloadManager.java Outdated
Comment on lines 45 to +49
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.easymock.Capture;

String remotePath = sourceUrl.getPath();
String baseUrl = url.substring(0, url.length() - remotePath.length());
int port = sourceUrl.getPort();
String baseUrl = sourceUrl.getProtocol() + "://"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Been a minute since I looked into the official URI specs but I'm not sure this is a complete URL reconstruction. The authority can have a password component, and not all absolute URIs (URLs?) have ://. Some just have :. This is tricky. Might be OK here. I'm just not sure.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DefaultDownloadManager: fragment/query string corrupts base URL splitting

3 participants