URIFormat.java
/*
* Copyright 2025 The Sigstore Authors.
*
* Licensed 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 dev.sigstore.http;
import java.net.URI;
import java.net.URISyntaxException;
/**
* A utility class for formatting URIs, providing predictable path appending.
*
* <p>This is preferable to {@link java.net.URI#resolve(String)} for simple path appending, as it
* avoids {@code resolve()}'s specific handling of base paths without trailing slashes and appended
* paths with leading slashes.
*/
public final class URIFormat {
private URIFormat() {}
/**
* Ensures the given URI's path has a trailing slash. This method correctly handles URIs with
* query parameters and fragments.
*
* @param input the URI to check.
* @return a new URI with a trailing slash, or the original URI if it already had one.
*/
public static URI addTrailingSlash(URI input) {
String path = input.getPath();
if (path == null || path.isEmpty()) {
path = "";
} else if (path.endsWith("/")) {
return input;
}
try {
return new URI(
input.getScheme(),
input.getAuthority(),
path + "/",
input.getQuery(),
input.getFragment());
} catch (URISyntaxException e) {
// This should be unreachable with a valid input URI
throw new IllegalStateException("Could not append slash to invalid URI: " + input, e);
}
}
/**
* Appends a path segment to a base URI, ensuring exactly one slash separates them. This method
* will erase any query parameters or fragments
*
* @param base the base URI (e.g., "http://example.com/api?key=1").
* @param path the path segment to append (e.g., "users" or "/users").
* @return a new URI with the path appended (e.g., "http://example.com/api/users").
*/
public static URI appendPath(URI base, String path) {
String relativePath = path.replaceAll("^/+", "");
// resolve has some goofy behavior unless we normalize everything before applying
return addTrailingSlash(base).resolve(relativePath);
}
}