diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java index 5ca34cefff..160c23c161 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/OpenAPIV3Parser.java @@ -21,6 +21,7 @@ import io.swagger.v3.parser.util.ResolverFully; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -209,6 +210,7 @@ private SwaggerParseResult resolve(SwaggerParseResult result, List + * {@code readLocation} accepts raw local filesystem paths, but reference resolution builds + * {@code java.net.URI} instances from that base (e.g. in {@code ReferenceUtils} and {@code Visitor#readURI}). + * A raw path containing characters that are illegal in a URI (most commonly a space) makes those + * constructions fail with "Illegal character in path", which surfaces as a parse error even for a + * self-contained single-file spec. When the location is not already a valid URI, convert it to a proper + * {@code file:} URI so the base is URI-safe; valid URIs (including scheme-less relative paths) are left untouched. + */ + private static String toUriSafeLocation(String location) { + try { + new URI(location); + return location; + } catch (URISyntaxException e) { + try { + return Paths.get(location).toUri().toString(); + } catch (Exception ex) { + return location; + } + } + } + private String getParseErrorMessage(String originalMessage, String location) { if (Objects.isNull(originalMessage)) { return String.format("Unable to parse `%s`", location); diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV31RawPathWithSpacesTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV31RawPathWithSpacesTest.java new file mode 100644 index 0000000000..2179a1871c --- /dev/null +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV31RawPathWithSpacesTest.java @@ -0,0 +1,70 @@ +package io.swagger.v3.parser.test; + +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; +import org.testng.annotations.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +/** + * Regression test for https://github.com/swagger-api/swagger-parser/issues/2365 + * + * A raw local filesystem path accepted by {@link OpenAPIV3Parser#readLocation} must not + * produce an "Illegal character in path" error while resolving same-file OpenAPI 3.1 refs, + * even when the path contains characters that are illegal in a URI (e.g. spaces). + */ +public class OpenAPIV31RawPathWithSpacesTest { + + @Test + public void resolveSameFileReferenceFromRawPathWithSpaces() throws Exception { + Path directory = Files.createTempDirectory("openapi path with spaces"); + Path root = directory.resolve("openapi.yaml"); + + Files.write(root, ( + "openapi: 3.1.0\n" + + "info:\n" + + " title: Path With Spaces\n" + + " version: 1.0.0\n" + + "paths:\n" + + " /examples:\n" + + " get:\n" + + " responses:\n" + + " '200':\n" + + " $ref: '#/components/responses/ExampleResponse'\n" + + "components:\n" + + " responses:\n" + + " ExampleResponse:\n" + + " description: OK\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " $ref: '#/components/schemas/Example'\n" + + " schemas:\n" + + " Example:\n" + + " type: object\n" + + " properties:\n" + + " id:\n" + + " type: string\n").getBytes(StandardCharsets.UTF_8)); + + ParseOptions options = new ParseOptions(); + options.setResolve(true); + options.setResolveResponses(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readLocation(root.toString(), null, options); + + assertNotNull(result.getOpenAPI()); + assertNotNull(result.getOpenAPI().getComponents().getResponses().get("ExampleResponse")); + + String illegalCharMessage = result.getMessages().stream() + .filter(message -> message.contains("Illegal character in path")) + .findFirst() + .orElse(null); + assertNull(illegalCharMessage, "unexpected parse error: " + illegalCharMessage); + } +}