You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/08/08 15:18:38 UTC
(maven-wagon) 01/02: [WAGON-1][WAGON-3][WAGON-425] Add user, development/testing and HTTP configuration guides
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a commit to branch wagon-3.x
in repository https://gitbox.apache.org/repos/asf/maven-wagon.git
commit fe3dc5ac893b40166fa7b6e1e39df97d53a88a08
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Sat Aug 8 13:20:09 2026 +0200
[WAGON-1][WAGON-3][WAGON-425] Add user, development/testing and HTTP configuration guides
---
src/site/markdown/developer-guide.md | 450 +++++++++++++++++++++++++++++++
src/site/markdown/http-configuration.md | 457 ++++++++++++++++++++++++++++++++
src/site/markdown/user-guide.md | 364 +++++++++++++++++++++++++
src/site/site.xml | 6 +
4 files changed, 1277 insertions(+)
diff --git a/src/site/markdown/developer-guide.md b/src/site/markdown/developer-guide.md
new file mode 100644
index 00000000..da1a91f1
--- /dev/null
+++ b/src/site/markdown/developer-guide.md
@@ -0,0 +1,450 @@
+---
+title: Development and Testing Guide
+date: 2026-08-08
+---
+
+<!--
+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.
+-->
+
+# Development and Testing Guide
+
+This guide is for someone writing or testing a Wagon provider — either a new one, or
+one of those in this repository. If you only want to *use* a Wagon, read the
+[User Guide](./user-guide.html).
+
+## The modules
+
+| Module | Contents |
+| --- | --- |
+| `wagon-provider-api` | the `Wagon` interface, the base classes, events, exceptions |
+| `wagon-provider-test` | reusable abstract JUnit test cases every provider is expected to pass |
+| `wagon-providers/*` | the providers shipped with Wagon |
+| `wagon-tcks/wagon-tck-http` | a behavioural test compatibility kit for HTTP providers |
+
+`wagon-provider-test` and `wagon-tck-http` are both released artifacts, so a provider
+maintained outside this repository can depend on them.
+
+## The SPI
+
+### `Wagon`
+
+`org.apache.maven.wagon.Wagon` is the contract; see the
+[User Guide](./user-guide.html#What_a_Wagon_is) for the shape of it. Two constants
+matter to an implementor: `Wagon.ROLE` (the Plexus role, equal to the interface's
+fully qualified name) and the timeout defaults `DEFAULT_CONNECTION_TIMEOUT` (60000)
+and `DEFAULT_READ_TIMEOUT` (1800000).
+
+### `AbstractWagon`
+
+`AbstractWagon` implements everything that is protocol-independent, and you almost
+certainly want to extend it (directly or via `StreamWagon`). It gives you:
+
+- **The connect/disconnect skeleton.** All six `connect` overloads funnel into
+ `connect(Repository, AuthenticationInfo, ProxyInfoProvider)`, which applies any
+ `permissionsOverride`, defaults a `null` `AuthenticationInfo` to an empty one,
+ falls back to credentials embedded in the repository URL, fires `SESSION_OPENING`,
+ calls your `openConnectionInternal()`, then fires `SESSION_OPENED`. On failure it
+ fires `SESSION_CONNECTION_REFUSED`. `disconnect()` fires `SESSION_DISCONNECTING`,
+ calls your `closeConnection()` and fires `SESSION_DISCONNECTED`.
+- **The byte pump.** `transfer(Resource, InputStream, OutputStream, int requestType,
+ long maxSize)` copies bytes and fires `TRANSFER_PROGRESS` as it goes. The buffer
+ size comes from `getBufferCapacityForTransfer(long)`, which aims for roughly 100
+ progress notifications per resource, clamped between `DEFAULT_BUFFER_SIZE` (4 KiB)
+ and `MAXIMUM_BUFFER_SIZE` (512 KiB). Data is only written once the buffer is at
+ least half full, to avoid fragmenting the stream into tiny chunks.
+- **`getTransfer(...)` / `putTransfer(...)`**, which wrap the pump with the right
+ events, create the destination's parent directories, and delete a partially written
+ destination file if the transfer fails.
+- **All the `fire*` methods** — `fireGetInitiated`, `fireGetStarted`,
+ `fireGetCompleted`, `firePutInitiated`, `firePutStarted`, `firePutCompleted`,
+ `fireTransferProgress`, `fireTransferError`, `fireTransferDebug`, and the
+ `fireSession*` family.
+- **Listener plumbing**, timeouts, `interactive`, and `getProxyInfo(String protocol,
+ String host)` which already applies the `nonProxyHosts` filter.
+- **Default optional operations** — `putDirectory`, `getFileList` and
+ `resourceExists` throw `UnsupportedOperationException`, and
+ `supportsDirectoryCopy()` returns `false`. Override the ones your protocol can do.
+
+The two methods you must supply are:
+
+```java
+protected abstract void openConnectionInternal() throws ConnectionException, AuthenticationException;
+
+protected abstract void closeConnection() throws ConnectionException;
+```
+
+There are also four empty hooks you can override to release protocol resources at the
+right moment: `finishGetTransfer`, `cleanupGetTransfer`, `finishPutTransfer`,
+`cleanupPutTransfer`.
+
+`postProcessListeners(Resource, File, int requestType)` is provided for providers that
+do *not* stream through `transfer(...)` — an external process, say. It re-reads the
+local file and fires progress events so that state-dependent listeners such as
+`ChecksumObserver` still see the bytes.
+
+### `StreamWagon`
+
+`StreamWagon extends AbstractWagon implements StreamingWagon` and is the base class
+most providers actually use. It reduces the job to two methods:
+
+```java
+public abstract void fillInputData(InputData inputData)
+ throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException;
+
+public abstract void fillOutputData(OutputData outputData) throws TransferFailedException;
+```
+
+`InputData` and `OutputData` are mutable holders of a `Resource` plus a stream. On a
+download, `StreamWagon` hands you an `InputData` with the `Resource` filled in; you
+open the remote stream, call `inputData.setInputStream(...)`, and set the resource's
+content length and last-modified time if you know them. Everything else — the
+`getIfNewer` timestamp comparison, event firing, writing to the destination file,
+closing streams, `getToStream`, `putFromStream` — is handled for you.
+
+`FileWagon` is the smallest complete example: `fillInputData`, `fillOutputData`,
+`openConnectionInternal` and `closeConnection`, plus overrides for the optional
+directory and existence operations.
+
+If your protocol cannot express uploads as an `OutputStream` — `AbstractHttpClientWagon`
+cannot, because HttpClient wants an entity that writes itself — override `put` and
+`putFromStream` directly and make `fillOutputData` throw.
+
+### `CommandExecutor`
+
+`CommandExecutor extends Wagon` and adds `void executeCommand(String)` and
+`Streams executeCommand(String, boolean ignoreFailures)`, the latter returning the
+command's standard output and standard error. Its role constant is
+`CommandExecutor.ROLE`, distinct from `Wagon.ROLE`, so it is a separate component
+registration. `AbstractJschWagon` (`scp`, `sftp`) and `ScpExternalWagon` (`scpexe`)
+implement the interface; the registration under the `CommandExecutor` role is done by
+a thin empty subclass in each module — `ScpCommandExecutor extends ScpWagon` under
+hint `scp`, `ScpExternalCommandExecutor` under hint `scpexe`. Failures are reported as
+`CommandExecutionException`.
+
+## Registering a provider
+
+A provider is a Plexus component with role `org.apache.maven.wagon.Wagon` and a
+role hint equal to the URL scheme it handles. There are two ways to declare it, and
+both are in use in this repository.
+
+### Javadoc tag (most providers)
+
+Put a `@plexus.component` tag in the class Javadoc:
+
+```java
+/**
+ * Wagon Provider for Local File System
+ *
+ * @plexus.component role="org.apache.maven.wagon.Wagon" role-hint="file" instantiation-strategy="per-lookup"
+ */
+public class FileWagon extends StreamWagon {
+```
+
+The tag is processed at build time by `plexus-component-metadata`, which generates
+`META-INF/plexus/components.xml` into the jar. Every provider module enables it:
+
+```xml
+<build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-metadata</artifactId>
+ </plugin>
+ </plugins>
+</build>
+```
+
+The plugin version and its `generate-metadata` execution are pinned in
+`wagon-providers/pom.xml`. The same mechanism registers the non-`Wagon` components in
+`wagon-ssh-common` — `KnownHostsProvider` under hints `file`, `single` and `null`, and
+`InteractiveUserInfo`.
+
+Use `instantiation-strategy="per-lookup"`. A Wagon holds connection state, so a
+singleton would be shared across sessions.
+
+### Hand-written descriptor
+
+When one implementation class serves several hints, it is simpler to write
+`src/main/resources/META-INF/plexus/components.xml` yourself. `wagon-http` does this
+to bind `HttpWagon` to both `http` and `https`:
+
+```xml
+<component-set>
+ <components>
+ <component>
+ <role>org.apache.maven.wagon.Wagon</role>
+ <role-hint>http</role-hint>
+ <implementation>org.apache.maven.wagon.providers.http.HttpWagon</implementation>
+ <instantiation-strategy>per-lookup</instantiation-strategy>
+ </component>
+ <component>
+ <role>org.apache.maven.wagon.Wagon</role>
+ <role-hint>https</role-hint>
+ <implementation>org.apache.maven.wagon.providers.http.HttpWagon</implementation>
+ <instantiation-strategy>per-lookup</instantiation-strategy>
+ </component>
+ </components>
+</component-set>
+```
+
+`wagon-webdav-jackrabbit` does the same for `dav`, `davs`, `dav+http` and `dav+https`.
+
+### Configurable properties
+
+Fields tagged `@plexus.configuration` become settable from a `<configuration>` block
+— in a Maven build, the one inside `<server>` in `settings.xml`:
+
+```java
+/**
+ * @plexus.configuration default-value="true"
+ */
+private boolean passiveMode = true;
+```
+
+The element name is the field name, and Plexus needs a matching setter to be
+reachable from outside. See the [HTTP Configuration guide](./http-configuration.html)
+for the largest example of this in the project.
+
+## Testing with `wagon-provider-test`
+
+`wagon-provider-test` contains abstract test cases that exercise a provider through
+the public API against a real endpoint that you set up. It is a `compile`-scope
+artifact that drags in JUnit 3-style `PlexusTestCase`, Mockito and Jetty; the
+`wagon-providers` parent POM already declares it at `test` scope for every provider
+module.
+
+### `WagonTestCase`
+
+`WagonTestCase extends PlexusTestCase`. Two abstract methods:
+
+```java
+protected abstract String getProtocol(); // the role hint, e.g. "file"
+protected abstract String getTestRepositoryUrl(); // where the tests should write
+```
+
+`getWagon()` looks the provider up with `lookup(Wagon.ROLE, getProtocol())` and
+attaches an `observers.Debug` as both session and transfer listener, so a failing run
+prints a transcript.
+
+Hooks worth knowing:
+
+| Hook | Purpose |
+| --- | --- |
+| `setupWagonTestingFixtures()` / `tearDownWagonTestingFixtures()` | start and stop your server before/after each test |
+| `getAuthInfo()` | credentials to connect with; defaults to an empty `AuthenticationInfo` |
+| `getPermissions()` | `RepositoryPermissions` for the test repository |
+| `getWagon()` | override to configure the instance under test |
+| `customizeContext()` | add values to the Plexus context |
+| `supportsGetIfNewer()` | return `false` to skip the conditional-download tests |
+| `getExpectedLastModifiedOnGet(Repository, Resource)` | what the provider should report as last-modified |
+| `getExpectedContentLengthOnGet(int)` | what it should report as content length |
+| `assertOnTransferProgress()` | return `false` if progress event counts cannot be asserted |
+| `testSkipped` | set in `setUp` to make `runTest()` skip the test entirely |
+
+What you inherit: a file round trip (`testWagon`), the three `getIfNewer` cases,
+`putDirectory` in four shapes (including a deep destination, an existing directory,
+and `.` as the destination), failure paths (`testFailedGet`, `testFailedGetIfNewer`),
+`getFileList` present and absent, and `resourceExists` true and false. The tests use
+a Mockito mock `TransferListener` to assert that the right events fire in the right
+order with the right byte counts.
+
+`FileWagonTest` is the shortest example of a subclass — it supplies a protocol and a
+temporary directory URL and gets the whole suite.
+
+### `StreamingWagonTestCase`
+
+Extends `WagonTestCase` and adds the `StreamingWagon` coverage: `testStreamingWagon`
+(a stream round trip), `testFailedGetToStream`, `testFailedGetIfNewerToStream` and
+the three `getIfNewerToStream` cases. Extend this one if your provider implements
+`StreamingWagon`.
+
+### `CommandExecutorTestCase`
+
+For `CommandExecutor` implementations. Supply `getTestRepository()`; you get
+`testExecuteSuccessfulCommand`, `testErrorInCommandExecuted` and
+`testIgnoreFailuresInCommandExecuted`.
+
+### `HttpWagonTestCase`
+
+`org.apache.maven.wagon.http.HttpWagonTestCase extends StreamingWagonTestCase` and is
+the big one — roughly forty tests run against an embedded Jetty server that it starts
+and stops for you: custom and default headers, user-agent handling, 403/404/500/429
+responses on GET, PUT and HEAD, gzip and deflate content encodings, proxies with and
+without authentication, redirects (GET and PUT, absolute and relative URLs, and the
+non-repeatable-stream case), and Basic authentication in every combination.
+
+Four methods to implement:
+
+```java
+protected abstract void setHttpConfiguration(StreamingWagon wagon, Properties headers, Properties params);
+protected abstract boolean supportPreemptiveAuthenticationGet();
+protected abstract boolean supportPreemptiveAuthenticationPut();
+protected abstract boolean supportProxyPreemptiveAuthentication();
+```
+
+The three `support*` methods are not feature switches you can set freely — they are
+assertions about what your provider does, and the preemptive-authentication tests
+check the request count against them. `HttpWagonTest` (wagon-http) declares
+`Get=false, Put=true, Proxy=true`; `HttpWagonPreemptiveTest` subclasses it, turns
+`usePreemptive` on in `httpConfiguration`, and declares `Get=true`.
+
+`verifyWagonExceptionMessage(Exception, int statusCode, String url, String reasonPhrase)`
+is available to assert that your exception messages carry the URL, status code and
+reason phrase the way `HttpMessageUtils` formats them.
+
+## The HTTP TCK
+
+`wagon-tcks/wagon-tck-http` is a separate, JUnit 4 suite that tests *behaviour* rather
+than mechanics: what a provider does about redirects, latency, timeouts, missing
+hosts and authentication. Unlike `wagon-provider-test`, a provider can declare
+individual use cases unsupported rather than failing them.
+
+### Structure
+
+- `HttpWagonTests` — the abstract base. Per test it starts a `ServerFixture`, looks
+ the wagon up from a `DefaultPlexusContainer` using the configured hint, and tears
+ everything down afterwards.
+- `GetWagonTests extends HttpWagonTests` — the actual tests.
+- `HttpsGetWagonTests extends GetWagonTests` — the same tests with `isSsl()` true.
+- `WagonTestCaseConfigurator` — a Plexus component that names the wagon under test and
+ carries per-use-case configuration.
+- `fixture/ServerFixture` — an embedded Jetty with a webapp rooted at the
+ `default-server-root` classpath resource, HTTP Basic authentication on
+ `/protected/*` via a `HashLoginService`, an ephemeral port, and optional TLS from
+ the `ssl/keystore` resource (password `wagonhttp`). `addServlet`, `addFilter` and
+ `addUser` let a test wire in what it needs.
+- `fixture/*` — the servlets and filters the tests wire in: `LatencyServlet`,
+ `RedirectionServlet`, `ErrorCodeServlet`, `ServletExceptionServlet`,
+ `ProxyAuthenticationFilter`, `ProxyConnectionVerifierFilter`, `AuthSnoopFilter`.
+- `Assertions` — `assertFileContentsFromResource(...)` compares a downloaded file
+ against a classpath resource, and `assertWagonExceptionMessage(...)` checks that an
+ exception message carries the expected URL, status code and reason phrase.
+
+### The test cases
+
+`basic`, `proxied`, `highLatencyHighTimeout`, `highLatencyLowTimeout`,
+`inifiniteLatencyTimeout` (spelled that way in the source), `nonExistentHost`,
+`oneLevelPermanentMove`, `oneLevelTemporaryMove`, `sixLevelPermanentMove`,
+`sixLevelTemporaryMove`, `infinitePermanentMove`, `infiniteTemporaryMove`,
+`permanentMove_TooManyRedirects_limit20`, `temporaryMove_TooManyRedirects_limit20`,
+`missing`, `error`, `proxyTimeout`, `forbidden`, `successfulAuthentication`,
+`unsuccessfulAuthentication`.
+
+### Wiring the TCK into a provider
+
+Add `wagon-tck-http` at test scope, declare a suite, and configure the
+`WagonTestCaseConfigurator` component. `wagon-http` does exactly this:
+
+```java
+@RunWith(Suite.class)
+@Suite.SuiteClasses({GetWagonTests.class, HttpsGetWagonTests.class})
+public class TckTest {
+}
+```
+
+and in `src/test/resources/META-INF/plexus/components.xml`:
+
+```xml
+<component-set>
+ <components>
+ <component>
+ <role>org.apache.maven.wagon.tck.http.WagonTestCaseConfigurator</role>
+ <implementation>org.apache.maven.wagon.tck.http.WagonTestCaseConfigurator</implementation>
+ <configuration>
+ <wagonHint>http</wagonHint>
+ <useCaseConfigs>
+ <highLatencyLowTimeout>
+ <unsupported/>
+ </highLatencyLowTimeout>
+ <inifiniteLatencyTimeout>
+ <unsupported/>
+ </inifiniteLatencyTimeout>
+ </useCaseConfigs>
+ </configuration>
+ </component>
+ </components>
+</component-set>
+```
+
+`<wagonHint>` is the role hint to look up. Each child of `<useCaseConfigs>` is named
+after a test method. An `<unsupported/>` child makes the TCK log the skip and let the
+test pass; any other content is applied to the wagon instance as Plexus component
+configuration before the test runs, which is how you turn on a provider-specific
+option for one test case. The test method name is discovered by walking the stack, so
+the element name must match the method name exactly.
+
+A provider with no `WagonTestCaseConfigurator` configuration at all will fail to look
+one up; the configurator component itself must be declared even if
+`<useCaseConfigs>` is empty.
+
+## Running the tests
+
+The full build:
+
+```
+mvn verify
+```
+
+Some suites are gated:
+
+**SSH.** `wagon-ssh` and `wagon-ssh-external` both define a `no-ssh-tests` profile
+that activates when the `ssh-tests` property is *absent* and excludes the tests that
+need a real SSH server (`SftpWagonTest`, `SshCommandExecutorTest`,
+`KnownHostsProviderTest`, `ScpWagon*Test`, and in `wagon-ssh` also `Embedded*Test`).
+So by default those tests do not run. Pass `-Dssh-tests` to run them — you need an
+SSH server on localhost that accepts the account named by the `test.user` system
+property, which the profiles set to `olamy`, so override it:
+
+```
+mvn verify -pl wagon-providers/wagon-ssh -Dssh-tests -Dtest.user=$USER
+```
+
+A `ssh-embedded` profile (activated by `-Dssh-embedded=true`) runs against the
+embedded Apache MINA SSHD server from `wagon-ssh-common-test` — it excludes the same
+tests minus `Embedded*Test`. On Windows a `windauze` profile applies the same
+exclusions as `no-ssh-tests`.
+
+`wagon-ssh-common-test` is where the embedded-server support lives:
+`SshServerEmbedded`, `AbstractEmbeddedScpWagonTest`,
+`AbstractEmbeddedScpWagonWithKeyTest`, `TestPasswordAuthenticator`,
+`TestPublickeyAuthenticator`, `TestData`, plus test key pairs under
+`src/main/resources/ssh-keys`.
+
+**HTTP.** No profile is needed. The tests and the TCK start their own Jetty on an
+ephemeral port.
+
+There is no `run-its` profile in this repository, despite what `README.md` says.
+
+## Building the site
+
+```
+mvn site
+```
+
+The site is aggregated from the root `src/site` plus each module's own `src/site`.
+Pages live in `src/site/markdown/`; register new ones in `src/site/site.xml`.
+
+## Code style
+
+Formatting comes from the Apache Maven parent POM, which configures Spotless with
+`spotless.action` defaulting to `apply` — a normal build reformats the sources it
+compiles, so you do not need to format by hand. Follow the rest of the
+[Maven code style](https://maven.apache.org/developers/conventions/code.html):
+spaces only for indentation, and no drive-by reformatting of code you did not
+otherwise touch. See `README.md` for the contribution process.
diff --git a/src/site/markdown/http-configuration.md b/src/site/markdown/http-configuration.md
new file mode 100644
index 00000000..c09d219a
--- /dev/null
+++ b/src/site/markdown/http-configuration.md
@@ -0,0 +1,457 @@
+---
+title: HTTP Configuration
+date: 2026-08-08
+---
+
+<!--
+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.
+-->
+
+# HTTP Configuration
+
+This page documents the `<configuration>` block that
+[wagon-http](./wagon-providers/wagon-http/) reads — headers, request parameters,
+timeouts, preemptive authentication and authentication scope — and how each setting
+maps onto Apache HttpClient 4.5.
+
+Everything here comes from `AbstractHttpClientWagon` in `wagon-http-shared`, so it
+applies equally to [wagon-webdav-jackrabbit](./wagon-providers/wagon-webdav-jackrabbit/),
+which extends the same class. It does **not** apply to
+[wagon-http-lightweight](./wagon-providers/wagon-http-lightweight/), which is a
+different implementation built on `java.net.HttpURLConnection`.
+
+Settings that are global rather than per-server are exposed as system properties
+instead; see [System properties](#System_properties) at the end.
+
+## Where configuration goes
+
+In a Maven build, per-server configuration lives in `settings.xml`, inside the
+`<server>` whose `<id>` matches the repository or `distributionManagement` id:
+
+```xml
+<settings>
+ <servers>
+ <server>
+ <id>my-repository</id>
+ <username>deploy</username>
+ <password>secret</password>
+ <configuration>
+ <!-- everything on this page goes here -->
+ </configuration>
+ </server>
+ </servers>
+</settings>
+```
+
+The elements inside `<configuration>` are mapped onto the Wagon instance as Plexus
+component configuration: element names correspond to the fields and setters of the
+provider class. Programmatically the same settings are the `setHttpConfiguration`,
+`setHttpHeaders`, `setBasicAuthScope`, `setProxyBasicAuthScope` and
+`setInitialBackoffSeconds` methods of `AbstractHttpClientWagon`.
+
+## `httpConfiguration`
+
+`httpConfiguration` is the main entry point. It is an
+[`HttpConfiguration`](./apidocs/org/apache/maven/wagon/shared/http/HttpConfiguration.html)
+with five optional children, one for "every method" plus one per HTTP method the
+providers issue:
+
+| Element | Applies to |
+| --- | --- |
+| `all` | every request |
+| `get` | `GET` — downloads |
+| `put` | `PUT` — uploads |
+| `head` | `HEAD` — `resourceExists` |
+| `mkcol` | `MKCOL` — WebDAV collection creation |
+
+Each is an
+[`HttpMethodConfiguration`](./apidocs/org/apache/maven/wagon/shared/http/HttpMethodConfiguration.html)
+with the same six properties:
+
+| Property | Type | Default |
+| --- | --- | --- |
+| `useDefaultHeaders` | boolean | `true` |
+| `headers` | `Properties` | empty |
+| `params` | `Properties` | empty |
+| `connectionTimeout` | int, milliseconds | 60000 |
+| `readTimeout` | int, milliseconds | 1800000, or the value of the `maven.wagon.rto` system property |
+| `usePreemptive` | boolean | `false` |
+
+```xml
+<configuration>
+ <httpConfiguration>
+ <all>
+ <connectionTimeout>10000</connectionTimeout>
+ <readTimeout>60000</readTimeout>
+ <headers>
+ <User-Agent>my-build/1.0</User-Agent>
+ </headers>
+ <params>
+ <http.protocol.max-redirects>5</http.protocol.max-redirects>
+ </params>
+ </all>
+ <put>
+ <readTimeout>600000</readTimeout>
+ </put>
+ </httpConfiguration>
+</configuration>
+```
+
+### How `all` and the per-method block are combined
+
+For each request, `HttpConfiguration.getMethodConfiguration(HttpUriRequest)` looks at
+the method name and merges `all` with the matching block. Anything that is not `GET`,
+`PUT`, `HEAD` or `MKCOL` gets `all` alone.
+
+The merge, in `ConfigurationUtils.merge(base, local)`:
+
+- if only one of the two is present, it is used as-is;
+- otherwise `all` is copied and then the per-method block is layered on top:
+ `headers` and `params` are merged key by key (the per-method value wins);
+ `useDefaultHeaders` is taken from the per-method block if it was set explicitly;
+ `connectionTimeout` and `readTimeout` are taken from the per-method block only if
+ they differ from `Wagon.DEFAULT_CONNECTION_TIMEOUT` and `Wagon.DEFAULT_READ_TIMEOUT`
+ respectively.
+
+That last rule has a consequence worth knowing: you cannot use a per-method block to
+set a timeout *back* to the default value. Writing `<readTimeout>1800000</readTimeout>`
+inside `<get>` is indistinguishable from not writing it at all, and the value from
+`<all>` survives.
+
+**`usePreemptive` is not carried through the merge at all.** The copy step does not
+copy it and the merge step does not apply it, so if both `<all>` and the block for
+the method in question are present, the effective `usePreemptive` is always `false`.
+Set it in `<all>` and leave that method's own block out, or set it only in the
+method block and leave `<all>` out. This is what
+`HttpWagonPreemptiveTest` does — it configures nothing but
+`setAll(new HttpMethodConfiguration().setUsePreemptive(true))`.
+
+### Writing `headers` and `params`
+
+Both are `java.util.Properties`, and Plexus accepts two spellings for those. The
+short form uses the key as the element name:
+
+```xml
+<headers>
+ <User-Agent>my-build/1.0</User-Agent>
+</headers>
+```
+
+The long form spells out each entry and works with every Plexus component
+configurator, including the older `plexus-container-default` used by Wagon's own
+tests:
+
+```xml
+<headers>
+ <property>
+ <name>User-Agent</name>
+ <value>my-build/1.0</value>
+ </property>
+</headers>
+```
+
+Use the long form for keys that are not valid XML element names — most of the `params`
+keys below contain dots, which is fine, but nothing stops a header name from
+containing characters that are not.
+
+### `headers`
+
+Headers from the configuration are applied with `setHeader`, so they replace any
+header of the same name that the provider had already added.
+
+`User-Agent` gets special handling: `getUserAgent(HttpUriRequest)` looks it up in
+`httpHeaders` first, then in the merged method configuration's `headers`, and the
+result is set on the request. If nothing supplies one, no `User-Agent` is added by
+Wagon.
+
+### `useDefaultHeaders`
+
+When `true` — the default — every request gets
+
+```
+Cache-control: no-cache
+Pragma: no-cache
+```
+
+before your own headers are applied. Set it to `false` to suppress them.
+
+### `params`
+
+`<params>` is an escape hatch onto HttpClient's `RequestConfig`. Only the keys below
+are recognised; anything else is silently ignored. Values may optionally be prefixed
+with a legacy type coercion of the form `%type,value` — for example
+`%Integer,3` — in which case only the part after the comma is used.
+
+| Parameter | `RequestConfig` setting |
+| --- | --- |
+| `http.socket.timeout` | `setSocketTimeout(int)` |
+| `http.connection.timeout` | `setConnectTimeout(int)` |
+| `http.connection.stalecheck` | `setStaleConnectionCheckEnabled(boolean)` |
+| `http.conn-manager.timeout` | `setConnectionRequestTimeout(int)` |
+| `http.protocol.expect-continue` | `setExpectContinueEnabled(boolean)` |
+| `http.protocol.handle-authentication` | `setAuthenticationEnabled(boolean)` |
+| `http.protocol.handle-redirects` | `setRedirectsEnabled(boolean)` |
+| `http.protocol.max-redirects` | `setMaxRedirects(int)` |
+| `http.protocol.allow-circular-redirects` | `setCircularRedirectsAllowed(boolean)` |
+| `http.protocol.reject-relative-redirect` | `setRelativeRedirectsAllowed(!value)` |
+| `http.protocol.handle-content-compression` | `setContentCompressionEnabled(boolean)` |
+| `http.protocol.handle-uri-normalization` | `setNormalizeUri(boolean)` |
+| `http.protocol.cookie-policy` | `setCookieSpec(String)` |
+| `http.route.default-proxy` | `setProxy(HttpHost.create(value))` |
+| `http.route.local-address` | `setLocalAddress(InetAddress)`; an unresolvable name is ignored |
+| `http.auth.target-scheme-pref` | `setTargetPreferredAuthSchemes(...)`, comma-separated |
+| `http.auth.proxy-scheme-pref` | `setProxyPreferredAuthSchemes(...)`, comma-separated |
+
+Two of these interact with settings applied elsewhere. `params` are applied *after*
+the request has already been given the Wagon's own connect and read timeouts, so
+`http.socket.timeout` and `http.connection.timeout` take precedence over
+`connectionTimeout`/`readTimeout` and over `setTimeout`/`setReadTimeout`. Also, the
+cookie policy is set to `CookieSpecs.BROWSER_COMPATIBILITY` before `params` are
+applied, so `http.protocol.cookie-policy` overrides that default.
+
+`Expect: 100-continue` is enabled unconditionally for `PUT` requests, before `params`
+are applied; `http.protocol.expect-continue` can therefore turn it off. It is
+deliberately not enabled for `MKCOL`, which has no body.
+
+## Authentication
+
+### Target server credentials
+
+At `connect` time, if the `AuthenticationInfo` has a non-empty user name *and* a
+non-empty password, a `UsernamePasswordCredentials` is registered in the
+`CredentialsProvider` under an `AuthScope` derived from the repository's host and
+port. A user name without a password registers nothing.
+
+The client is built with an auth scheme registry containing exactly three schemes:
+
+| Scheme | Factory |
+| --- | --- |
+| Basic | `BasicSchemeFactory`, UTF-8 |
+| Digest | `DigestSchemeFactory`, UTF-8 |
+| NTLM | `NTLMSchemeFactory` |
+
+Basic and Digest both work against the target server with a plain user name and
+password, negotiated in response to the server's challenge. Use
+`http.auth.target-scheme-pref` if you need to force the order in which schemes are
+tried.
+
+**NTLM against the target server is not supported.** The target credentials are
+always `UsernamePasswordCredentials`, never `NTCredentials`, so the registered NTLM
+scheme has nothing usable to offer. NTLM is only wired up for proxies — see below.
+
+### Preemptive authentication
+
+By default HttpClient waits for a `401` challenge before sending credentials. With
+`usePreemptive` set, a `BasicScheme` is placed in the `AuthCache` for the target host
+before the request goes out, provided credentials for that scope exist — so the first
+request already carries an `Authorization: Basic` header:
+
+```xml
+<configuration>
+ <httpConfiguration>
+ <all>
+ <usePreemptive>true</usePreemptive>
+ </all>
+ </httpConfiguration>
+</configuration>
+```
+
+Note the merge caveat above: adding a `<get>` block alongside this would switch
+preemptive authentication back off.
+
+Preemptive Basic authentication is only ever Basic — the cache entry is a
+`BasicScheme`. It has no effect on Digest or NTLM.
+
+`PUT` is a special case: `AbstractHttpClientWagon.put` primes the `AuthCache` with a
+`BasicScheme` for the target host whenever credentials exist, regardless of
+`usePreemptive`. Uploads are therefore preemptively authenticated by default. The
+source marks this as a known wart ("FIXME Perform only when preemptive has been
+configured").
+
+### `basicAuth` and `proxyAuth` — overriding the authentication scope
+
+By default the `AuthScope` for the target is built from the repository's own host and
+port with `AuthScope.ANY_REALM`, and a port of `-1` becomes `AuthScope.ANY_PORT`.
+[`BasicAuthScope`](./apidocs/org/apache/maven/wagon/shared/http/BasicAuthScope.html)
+lets you override any of `host`, `port` and `realm`; each accepts the literal string
+`ANY` to mean "match anything", and setting all three to `ANY` yields
+`AuthScope.ANY`.
+
+This is the setting to reach for when credentials are not being sent because the
+server's realm or port does not match what Wagon derived from the URL — for instance
+a repository fronted by a redirect to another host.
+
+The two scopes are held in fields named `basicAuth` (for the target) and `proxyAuth`
+(for the proxy), reachable through `setBasicAuthScope` and `setProxyBasicAuthScope`.
+The `BasicAuthScope` Javadoc refers to them as `/server/basicAuth` and
+`/server/proxyBasicAuth`; note that `proxyBasicAuth` matches neither the field name
+(`proxyAuth`) nor the setter name (`setProxyBasicAuthScope`), so if `<proxyBasicAuth>`
+does not take effect, try `<proxyAuth>` or `<proxyBasicAuthScope>`.
+
+```xml
+<configuration>
+ <basicAuth>
+ <host>ANY</host>
+ <port>ANY</port>
+ <realm>ANY</realm>
+ </basicAuth>
+</configuration>
+```
+
+## Proxies
+
+Proxy settings come from Maven's `<proxy>` configuration rather than from
+`<server><configuration>`; Wagon receives them as a
+[`ProxyInfo`](./apidocs/org/apache/maven/wagon/proxy/ProxyInfo.html). What the HTTP
+provider does with them:
+
+- The proxy is only used when the `ProxyInfo`'s `type` matches the repository's
+ protocol and the target host does not match `nonProxyHosts`. See
+ [Proxies in the User Guide](./user-guide.html#Proxies).
+- If the `ProxyInfo` carries a user name and a password, credentials are registered
+ under a scope derived from the proxy host and port, overridable through `proxyAuth`
+ exactly as `basicAuth` overrides the target scope.
+- **NTLM.** If either `ntlmHost` or `ntlmDomain` is set on the `ProxyInfo`, the proxy
+ credentials become `NTCredentials(username, password, ntlmHost, ntlmDomain)`
+ instead of `UsernamePasswordCredentials`, and the registered `NTLMSchemeFactory`
+ handles the challenge. Use `http.auth.proxy-scheme-pref` to control which scheme is
+ preferred when the proxy offers several.
+- Proxy authentication is always primed preemptively: whenever proxy credentials
+ exist, a `BasicScheme` with `ChallengeState.PROXY` is put in the `AuthCache` for the
+ proxy host.
+
+## Deprecated: `httpHeaders`
+
+```xml
+<configuration>
+ <httpHeaders>
+ <property>
+ <name>User-Agent</name>
+ <value>my-build/1.0</value>
+ </property>
+ </httpHeaders>
+</configuration>
+```
+
+`httpHeaders` is a flat `Properties` of headers applied to every request. It predates
+`httpConfiguration` and is marked deprecated in the source in favour of it. It is
+still honoured, and it is applied *before* the `httpConfiguration` headers, so a
+header set in both places takes its value from `httpConfiguration`. The one exception
+is `User-Agent` lookup, which consults `httpHeaders` first.
+
+## Backoff on 429
+
+When the server answers `429 Too Many Requests`, the provider sleeps and retries,
+doubling the wait each time until it would reach the maximum, at which point it
+throws `TransferFailedException`. The initial wait is 5 seconds and the maximum is
+180 seconds; both are system properties (below), and the initial value is also
+settable per server:
+
+```xml
+<configuration>
+ <initialBackoffSeconds>10</initialBackoffSeconds>
+</configuration>
+```
+
+## System properties
+
+These are JVM-wide, not per-server. Several are read into `static final` fields when
+`AbstractHttpClientWagon` is first loaded, so they must be set before any transfer
+starts — on the command line, or in `MAVEN_OPTS`.
+
+### Connection pool
+
+| Property | Default | Effect |
+| --- | --- | --- |
+| `maven.wagon.http.pool` | `true` | enable the pooling connection manager; when `false`, total connections are capped at 1 and idle connections are closed on disconnect |
+| `maven.wagon.httpconnectionManager.maxPerRoute` | `20` | max connections per route |
+| `maven.wagon.httpconnectionManager.maxTotal` | `40` | max connections overall |
+| `maven.wagon.httpconnectionManager.ttlSeconds` | `300` | connection time-to-live; `-1` keeps connections indefinitely |
+
+### Timeouts
+
+| Property | Default | Effect |
+| --- | --- | --- |
+| `maven.wagon.rto` | `1800000` | default read timeout in milliseconds |
+
+### TLS
+
+| Property | Default | Effect |
+| --- | --- | --- |
+| `maven.wagon.http.ssl.insecure` | `false` | accept any certificate chain whose dates are valid |
+| `maven.wagon.http.ssl.allowall` | `false` | with `insecure`, skip hostname verification |
+| `maven.wagon.http.ssl.ignore.validity.dates` | `false` | with `insecure`, also ignore certificate dates |
+| `https.protocols` | — | comma-separated list of enabled TLS protocols |
+| `https.cipherSuites` | — | comma-separated list of enabled cipher suites |
+
+Without `maven.wagon.http.ssl.insecure`, the default JSSE socket factory is used with
+HttpClient's browser-compatible hostname verifier.
+
+### Retries
+
+| Property | Default | Effect |
+| --- | --- | --- |
+| `maven.wagon.http.retryHandler.class` | `standard` | `default`, `standard`, or a fully qualified `HttpRequestRetryHandler` with a no-arg constructor |
+| `maven.wagon.http.retryHandler.requestSentEnabled` | `false` | retry requests that were already sent (`default` and `standard` only) |
+| `maven.wagon.http.retryHandler.count` | `3` | retry count (`default` and `standard` only) |
+| `maven.wagon.http.retryHandler.nonRetryableClasses` | — | comma-separated exception class names to never retry (`default` only) |
+| `maven.wagon.http.serviceUnavailableRetryStrategy.class` | `none` | `none`, `default`, `standard`, or a fully qualified `ServiceUnavailableRetryStrategy` with a no-arg constructor |
+| `maven.wagon.http.serviceUnavailableRetryStrategy.retryInterval` | `1000` | milliseconds between retries |
+| `maven.wagon.http.serviceUnavailableRetryStrategy.maxRetries` | `5` | maximum retries |
+
+A retry handler only reacts to failures while sending the request and reading the
+response head. It cannot recover a response body that fails part-way through.
+
+### Backoff
+
+| Property | Default | Effect |
+| --- | --- | --- |
+| `maven.wagon.httpconnectionManager.backoffSeconds` | `5` | initial wait after a `429` |
+| `maven.wagon.httpconnectionManager.maxBackoffSeconds` | `180` | give up once the next wait would reach this |
+
+## Worked example
+
+A server that needs preemptive Basic authentication, a custom user agent, no
+redirects, and a generous read timeout for uploads:
+
+```xml
+<server>
+ <id>internal-releases</id>
+ <username>deploy</username>
+ <password>secret</password>
+ <configuration>
+ <httpConfiguration>
+ <all>
+ <usePreemptive>true</usePreemptive>
+ <connectionTimeout>10000</connectionTimeout>
+ <headers>
+ <User-Agent>acme-build/2.1</User-Agent>
+ </headers>
+ <params>
+ <http.protocol.handle-redirects>false</http.protocol.handle-redirects>
+ <http.socket.timeout>600000</http.socket.timeout>
+ </params>
+ </all>
+ </httpConfiguration>
+ </configuration>
+</server>
+```
+
+Everything is in `<all>` on purpose: adding a `<get>` or `<put>` block would drop
+`usePreemptive`. Where a per-method setting is unavoidable, use `params` — those do
+merge correctly.
diff --git a/src/site/markdown/user-guide.md b/src/site/markdown/user-guide.md
new file mode 100644
index 00000000..7699c48b
--- /dev/null
+++ b/src/site/markdown/user-guide.md
@@ -0,0 +1,364 @@
+---
+title: User Guide
+date: 2026-08-08
+---
+
+<!--
+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.
+-->
+
+# User Guide
+
+This guide is for someone who wants to *use* a Wagon: transfer a file to or from a
+remote repository, or configure the Wagon that Maven is already using on their
+behalf. If you want to *write* a Wagon provider, read the
+[Development and Testing Guide](./developer-guide.html) instead.
+
+## What a Wagon is
+
+A Wagon is a transport. It knows how to move a byte stream between the local file
+system and a remote repository over one particular protocol, and nothing more. It
+does not know about artifacts, POMs, checksums or repository layout; it moves
+resources identified by a path relative to a repository base URL.
+
+The whole contract is the
+[`org.apache.maven.wagon.Wagon`](./apidocs/org/apache/maven/wagon/Wagon.html)
+interface, which has four groups of methods:
+
+- **transfer** — `get`, `getIfNewer`, `put`, `putDirectory`
+- **query** — `resourceExists`, `getFileList`, `supportsDirectoryCopy`, `getRepository`
+- **session** — `connect`, `disconnect`, `setTimeout`/`getTimeout`,
+ `setReadTimeout`/`getReadTimeout`, `setInteractive`/`isInteractive`
+- **events** — `addTransferListener`, `addSessionListener` and their `remove`/`has`
+ counterparts
+
+Providers that can also expose raw streams implement
+[`StreamingWagon`](./apidocs/org/apache/maven/wagon/StreamingWagon.html), which adds
+`getToStream`, `getIfNewerToStream` and `putFromStream`.
+
+## The providers and the URL schemes they handle
+
+A provider registers itself under one or more *role hints*. The role hint is the URL
+scheme: when something asks for a Wagon for `https://repo.example.com/`, it looks up
+the `Wagon` component with hint `https`.
+
+| Module | Role hints (URL schemes) | Implementation |
+| --- | --- | --- |
+| [wagon-file](./wagon-providers/wagon-file/) | `file` | `FileWagon` |
+| [wagon-http](./wagon-providers/wagon-http/) | `http`, `https` | `HttpWagon` (Apache HttpClient 4.5.x) |
+| [wagon-http-lightweight](./wagon-providers/wagon-http-lightweight/) | `http`, `https` | `LightweightHttpWagon`, `LightweightHttpsWagon` (`java.net.HttpURLConnection`) |
+| [wagon-ftp](./wagon-providers/wagon-ftp/) | `ftp`, `ftps`, `ftph` | `FtpWagon`, `FtpsWagon`, `FtpHttpWagon` (Commons Net) |
+| [wagon-ssh](./wagon-providers/wagon-ssh/) | `scp`, `sftp` | `ScpWagon`, `SftpWagon` (JSch) |
+| [wagon-ssh-external](./wagon-providers/wagon-ssh-external/) | `scpexe` | `ScpExternalWagon` (invokes the system `ssh`/`scp`) |
+| [wagon-webdav-jackrabbit](./wagon-providers/wagon-webdav-jackrabbit/) | `dav`, `davs`, `dav+http`, `dav+https` | `WebDavWagon` |
+| [wagon-scm](./wagon-providers/wagon-scm/) | `scm` | `ScmWagon` (Maven SCM) |
+
+`wagon-http` and `wagon-http-lightweight` both claim `http` and `https`; only one of
+them can be on the classpath for a given scheme.
+
+`WebDavWagon` rewrites its own URL before connecting, so `dav://`, `davs://`,
+`dav+http://` and `dav+https://` all become plain `http://`/`https://` requests. The
+Maven 2.0.x form `dav:http://host/path` also works, because the scheme parsed out of
+it is `dav`.
+
+`ScmWagon` passes the repository URL straight to Maven SCM, so the URL is a full SCM
+URL such as `scm:svn:https://svn.example.com/repo`.
+
+## Getting a Wagon
+
+### In a Maven build
+
+Declare the provider as a build extension. Maven will then resolve the URL scheme to
+that provider for repositories and for `distributionManagement`:
+
+```xml
+<build>
+ <extensions>
+ <extension>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-ftp</artifactId>
+ <version>3.5.3</version>
+ </extension>
+ </extensions>
+</build>
+```
+
+### Programmatically
+
+Providers are Plexus components with role `org.apache.maven.wagon.Wagon` — the
+constant `Wagon.ROLE` — and role hint equal to the URL scheme. Each is declared with
+`instantiation-strategy="per-lookup"`, so every lookup gives you a fresh instance;
+a Wagon is not safe to share between concurrent sessions.
+
+```java
+PlexusContainer container = new DefaultPlexusContainer();
+Wagon wagon = (Wagon) container.lookup(Wagon.ROLE, "http");
+```
+
+Put the provider artifact on the classpath and the component descriptor it carries in
+`META-INF/plexus/components.xml` is picked up automatically.
+
+## The lifecycle
+
+Connect, transfer as many times as you like, disconnect:
+
+```java
+Wagon wagon = (Wagon) container.lookup(Wagon.ROLE, "https");
+
+AuthenticationInfo auth = new AuthenticationInfo();
+auth.setUserName("deploy");
+auth.setPassword("secret");
+
+Repository repository = new Repository("central", "https://repo.example.com/maven2");
+
+wagon.connect(repository, auth);
+try {
+ wagon.get("org/example/lib/1.0/lib-1.0.jar", new File("lib-1.0.jar"));
+ wagon.put(new File("lib-1.0.jar"), "org/example/lib/1.0/lib-1.0.jar");
+} finally {
+ wagon.disconnect();
+}
+```
+
+`connect` is overloaded six ways, covering the presence or absence of an
+`AuthenticationInfo` and of a proxy (either a fixed `ProxyInfo` or a
+`ProxyInfoProvider`). All of them funnel into
+`connect(Repository, AuthenticationInfo, ProxyInfoProvider)`, which stores the
+repository, fires `SESSION_OPENING`, opens the connection and fires `SESSION_OPENED`.
+`disconnect` fires `SESSION_DISCONNECTING`, closes, and fires `SESSION_DISCONNECTED`.
+
+`connect(Repository)` with a `null` repository throws `NullPointerException`.
+
+`openConnection()` is deprecated and is an internal method; use `connect`.
+
+### get, getIfNewer and put
+
+- `get(String resourceName, File destination)` downloads unconditionally.
+- `getIfNewer(String resourceName, File destination, long timestamp)` downloads only
+ if the remote resource is newer than `timestamp` (milliseconds since the epoch,
+ GMT), and returns `true` when it actually transferred something. Passing `0`
+ always transfers.
+- `put(File source, String destination)` uploads a single file.
+- `putDirectory(File sourceDirectory, String destinationDirectory)` uploads a whole
+ tree — but only if `supportsDirectoryCopy()` returns `true`.
+
+Resource names are relative to the repository URL. The destination file's parent
+directories are created for you before the transfer begins; if the transfer fails,
+the partially written destination file is deleted (or marked `deleteOnExit` if it
+cannot be deleted).
+
+### Operations a provider may not implement
+
+`AbstractWagon` — the base class for every provider in this project — implements
+three methods by throwing `UnsupportedOperationException`:
+
+- `putDirectory` — "The wagon you are using has not implemented putDirectory()"
+- `getFileList` — "The wagon you are using has not implemented getFileList()"
+- `resourceExists` — "The wagon you are using has not implemented resourceExists()"
+
+and `supportsDirectoryCopy()` returns `false` by default. Check
+`supportsDirectoryCopy()` before calling `putDirectory`, and be prepared for
+`UnsupportedOperationException` from the other two.
+
+Where the shipped providers stand:
+
+| Provider | `supportsDirectoryCopy()` | `getFileList` | `resourceExists` | `StreamingWagon` |
+| --- | --- | --- | --- | --- |
+| `file` | yes | yes | yes | yes |
+| `http`, `https` (wagon-http) | no | no | yes | yes |
+| `http`, `https` (lightweight) | no | no | yes | yes |
+| `ftp`, `ftps`, `ftph` | yes | yes | yes | yes |
+| `scp`, `sftp` | yes | yes | yes | yes |
+| `scpexe` | yes | yes | yes | no |
+| `dav`, `davs`, `dav+http`, `dav+https` | yes | yes | yes | yes |
+| `scm` | yes | yes | yes | no |
+
+"no" in the `getFileList` column means the inherited `UnsupportedOperationException`.
+
+## Repository URLs and credentials
+
+`Repository.setUrl(String)` parses the URL into protocol, host, port and base
+directory. It also understands credentials embedded in the URL: if the authority
+contains `user` or `user:password`, they are extracted into
+`Repository.getUsername()`/`getPassword()` **and stripped from the stored URL**, so
+`getUrl()` afterwards no longer contains them.
+
+When you call `connect` without an `AuthenticationInfo`, or with one whose user name
+is `null`, `AbstractWagon` falls back to the credentials parsed out of the URL.
+
+If no port is present in the URL, `getPort()` returns
+`WagonConstants.UNKNOWN_PORT` (`-1`). If no host is present, `getHost()` returns
+`localhost`.
+
+## Authentication
+
+Credentials are carried by
+[`AuthenticationInfo`](./apidocs/org/apache/maven/wagon/authentication/AuthenticationInfo.html),
+which has exactly four properties:
+
+| Property | Used for |
+| --- | --- |
+| `userName` | user name |
+| `password` | password |
+| `privateKey` | absolute path to a private key file |
+| `passphrase` | passphrase protecting that private key |
+
+`privateKey` and `passphrase` are only meaningful for providers that authenticate
+with a key pair — in practice the SSH providers.
+
+Which authentication mechanisms are actually attempted, and how, is entirely up to
+the provider. For the HTTP providers see the
+[HTTP Configuration guide](./http-configuration.html).
+
+## Proxies
+
+A proxy is described by
+[`ProxyInfo`](./apidocs/org/apache/maven/wagon/proxy/ProxyInfo.html): `host`, `port`,
+`userName`, `password`, `type`, `nonProxyHosts`, plus `ntlmHost` and `ntlmDomain` for
+NTLM proxies. The class defines three type constants — `PROXY_HTTP` (`"HTTP"`),
+`PROXY_SOCKS4` (`"SOCKS4"`) and `PROXY_SOCKS5` (`"SOCKS_5"`) — but the value that
+matters at runtime is described next.
+
+Two things decide whether a proxy is used for a given request:
+
+1. **Protocol match.** When you pass a plain `ProxyInfo` to `connect`, it is wrapped
+ in a `ProxyInfoProvider` that returns the proxy only when the requested protocol
+ is `null` or equals the proxy's `type`, case-insensitively. The HTTP providers ask
+ for the repository's own protocol, so a `ProxyInfo` with type `http` is not used
+ for an `https` repository. Pass a `ProxyInfoProvider` yourself if you need
+ different behaviour.
+2. **Non-proxy hosts.** `nonProxyHosts` is a `|`-separated list of patterns in which
+ `*` is a wildcard, for example `*.example.com|localhost`. If the target host
+ matches any pattern, the proxy is skipped.
+
+## Timeouts
+
+Two independent timeouts, both in milliseconds:
+
+| Setter | Default | Constant |
+| --- | --- | --- |
+| `setTimeout(int)` — connection timeout | 60000 (1 minute) | `Wagon.DEFAULT_CONNECTION_TIMEOUT` |
+| `setReadTimeout(int)` — read timeout | 1800000 (30 minutes) | `Wagon.DEFAULT_READ_TIMEOUT` |
+
+The read timeout default can be changed globally with the system property
+`maven.wagon.rto`; `AbstractWagon` reads it when the instance is created.
+
+## Streaming versus file transfer
+
+`Wagon.get`/`Wagon.put` work with `java.io.File`. Providers that also implement
+[`StreamingWagon`](./apidocs/org/apache/maven/wagon/StreamingWagon.html) let you skip
+the file:
+
+```java
+StreamingWagon wagon = (StreamingWagon) container.lookup(Wagon.ROLE, "https");
+wagon.connect(repository);
+try (OutputStream out = Files.newOutputStream(target)) {
+ wagon.getToStream("org/example/lib/1.0/lib-1.0.jar", out);
+}
+```
+
+For uploads, prefer
+`putFromStream(InputStream, String destination, long contentLength, long lastModified)`.
+The two-argument `putFromStream(InputStream, String)` is deprecated: without a
+content length the HTTP providers fall back to chunked transfer encoding, which not
+every server accepts.
+
+Note that "streaming" describes the API you use, not the implementation: every
+provider in this project streams the payload internally. `StreamWagon`, the base
+class most providers extend, expresses a transfer as "fill in an `InputStream`" or
+"fill in an `OutputStream`" and lets `AbstractWagon` pump the bytes.
+
+`wagon-http-lightweight` is the exception worth knowing about — its own
+documentation records that it cannot download data that does not fit in memory.
+
+## Listening to transfers
+
+Two listener interfaces, both registered on the Wagon:
+
+[`TransferListener`](./apidocs/org/apache/maven/wagon/events/TransferListener.html)
+receives `transferInitiated`, `transferStarted`, `transferProgress`,
+`transferCompleted`, `transferError` and `debug`. The event carries a
+`TransferEvent` whose `getEventType()` is one of `TRANSFER_INITIATED`,
+`TRANSFER_STARTED`, `TRANSFER_PROGRESS`, `TRANSFER_COMPLETED`, `TRANSFER_ERROR`, and
+whose `getRequestType()` is `REQUEST_GET` or `REQUEST_PUT`.
+
+[`SessionListener`](./apidocs/org/apache/maven/wagon/events/SessionListener.html)
+receives `sessionOpening`, `sessionOpened`, `sessionLoggedIn`, `sessionLoggedOff`,
+`sessionDisconnecting`, `sessionDisconnected`, `sessionConnectionRefused`,
+`sessionError` and `debug`.
+
+Three implementations ship in the API module:
+
+- `observers.AbstractTransferListener` — empty implementations of every
+ `TransferListener` method, to subclass.
+- `observers.Debug` — implements both interfaces and prints a running commentary to a
+ `PrintStream` (`System.out` by default): one `#` per progress event, then a summary
+ line with the byte count and elapsed time.
+- `observers.ChecksumObserver` — a `TransferListener` that digests the bytes as they
+ go past. The no-arg constructor uses MD5; the other takes any algorithm name the
+ JDK's `MessageDigest` accepts. Read the result with `getActualChecksum()`.
+
+`transferProgress` is called synchronously on the transfer thread, once per buffer.
+A slow listener slows the transfer down. The buffer size is chosen from the resource
+content length so that a transfer produces roughly 100 progress events, between 4 KiB
+and 512 KiB per event; a resource of unknown length uses 4 KiB.
+
+## Errors
+
+Every checked exception extends
+[`WagonException`](./apidocs/org/apache/maven/wagon/WagonException.html):
+
+| Exception | Meaning |
+| --- | --- |
+| `ConnectionException` | `connect`/`disconnect` failed |
+| `AuthenticationException` | the credentials supplied were not sufficient to establish the session |
+| `AuthorizationException` | the server refused the operation on this resource |
+| `ResourceDoesNotExistException` | the resource, or the directory being listed, is not there |
+| `TransferFailedException` | anything else that went wrong during a transfer |
+| `UnsupportedProtocolException` | no provider is registered for the URL scheme |
+| `CommandExecutionException` | a `CommandExecutor` command failed |
+
+A note on the HTTP providers: `401`, `403` and `407` all currently surface as
+`AuthorizationException`, not `AuthenticationException`. The message distinguishes
+them — "authentication failed for" for 401, "authorization failed for" for 403,
+"proxy authentication failed for" for 407 — and includes the URL, the status code and
+the reason phrase where the server sent one. `404` and `410` become
+`ResourceDoesNotExistException` ("resource missing at ..."). Messages for failures
+that went through a proxy also name the proxy.
+
+The unchecked `UnsupportedOperationException` is not an error condition: it means the
+provider does not implement that optional operation. See
+[Operations a provider may not implement](#Operations_a_provider_may_not_implement).
+
+## Interactive mode
+
+`setInteractive(boolean)` tells a provider whether it may prompt the user. It
+defaults to `true`. The JSch-based SSH providers (`scp`, `sftp`) act on it: when it
+is `false` they discard the keyboard-interactive handler, install a
+`NullInteractiveUserInfo` that answers nothing, and set JSch's `BatchMode` to `yes`.
+Set it to `false` in an unattended build. `scpexe` does not honour it — its source
+records that it never works in interactive mode anyway.
+
+## File permissions on upload
+
+For protocols that can set them, `RepositoryPermissions` carries a `group`, a
+`fileMode` and a `directoryMode`; modes may be textual (`ugo+rx`) or octal (`644`).
+Set them on the `Repository` before connecting, or set a `RepositoryPermissions` on
+an `AbstractWagon` with `setPermissionsOverride(...)`, which replaces whatever the
+repository carries at `connect` time. Providers that cannot change permissions ignore
+them.
diff --git a/src/site/site.xml b/src/site/site.xml
index 2d99252d..682b833e 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -33,6 +33,12 @@ under the License.
<item name="Download" href="download.html"/>
</menu>
+ <menu name="Guides">
+ <item name="User Guide" href="user-guide.html"/>
+ <item name="Development and Testing Guide" href="developer-guide.html"/>
+ <item name="HTTP Configuration" href="http-configuration.html"/>
+ </menu>
+
<menu ref="modules" />
<menu ref="reports" />
</body>