gnodet-bot commented on code in PR #28183:
URL: https://github.com/apache/camel/pull/28183#discussion_r4328713182


##########
components/camel-openapi-java/src/main/java/org/apache/camel/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+package org.apache.camel.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * This is a copy of the class of the same name in camel-rest-openapi, which 
neither module can depend on the other for;
+ * keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());
+        } catch (RuntimeException e) {

Review Comment:
   âš ī¸ **Silent document skip** — catching `RuntimeException` here and returning 
`null` means any document that cannot be read (missing, unreadable, corrupt) is 
silently dropped from the security check. The caller at line 98 skips the 
`check.scan()` call when `text == null`, so references inside unreadable 
documents are never validated. Consider logging at WARN and propagating (or at 
minimum making this a hard failure rather than a silent skip).



##########
components/camel-openapi-java/src/main/java/org/apache/camel/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+package org.apache.camel.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * This is a copy of the class of the same name in camel-rest-openapi, which 
neither module can depend on the other for;
+ * keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());

Review Comment:
   🔒 **Security: CWD-relative resolution** — `new File(".").toPath()` resolves 
relative `$ref` paths against the JVM working directory, not the directory 
containing the referencing document. A spec at `/opt/camel/specs/api.yaml` with 
`$ref: 'models/pet.yaml'` should resolve to `/opt/camel/specs/models/pet.yaml`, 
but this code resolves to `./models/pet.yaml` instead. Pass the containing 
document's parent directory as the base path.



##########
components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.rest.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * camel-openapi-java has a copy of this class for the specification it serves 
as api-doc; keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());

Review Comment:
   🔒 **Security: CWD-relative resolution** (same issue as in 
`camel-openapi-java`) — `new File(".").toPath()` uses the JVM working directory 
as the resolution base instead of the containing document's directory.



##########
components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.rest.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * camel-openapi-java has a copy of this class for the specification it serves 
as api-doc; keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());
+        } catch (RuntimeException e) {

Review Comment:
   âš ī¸ **Silent document skip** (same issue as in `camel-openapi-java`) — 
`RuntimeException` caught here returns `null`, silently skipping security 
validation of the unreadable document's references.



##########
components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.rest.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * camel-openapi-java has a copy of this class for the specification it serves 
as api-doc; keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());
+        } catch (RuntimeException e) {
+            LOG.debug("Cannot read {} to check its references, the parser 
reports it when it resolves the specification: {}",
+                    file, e.getMessage());
+            return null;
+        }
+    }
+
+    private static boolean isHttp(String file) {
+        String lower = file.toLowerCase(Locale.ROOT);
+        return lower.startsWith("http:") || lower.startsWith("https:");

Review Comment:
   â„šī¸ **`file:` URI handling** (same as in `camel-openapi-java`) — `file:` URIs 
are not recognized by `isHttp()` and silently fall through to the local path.



##########
components/camel-openapi-java/src/main/java/org/apache/camel/openapi/OpenApiExternalReferences.java:
##########
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+package org.apache.camel.openapi;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.parser.models.RefFormat;
+import io.swagger.v3.parser.util.RefUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Checks the references ({@code $ref}) of an OpenAPI specification against 
the {@code externalReferences} option before
+ * the parser resolves them. The parser loads every external reference it 
meets: from the working directory and the
+ * classpath, or over http(s) when the reference is a URL or the specification 
itself comes from a URL.
+ * <p>
+ * This is a copy of the class of the same name in camel-rest-openapi, which 
neither module can depend on the other for;
+ * keep the two in sync.
+ */
+final class OpenApiExternalReferences {
+
+    static final String ALL = "all";
+    static final String LOCAL = "local";
+    static final String NONE = "none";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(OpenApiExternalReferences.class);
+
+    private OpenApiExternalReferences() {
+    }
+
+    /**
+     * The value of the option to apply: {@link #ALL} when none is set.
+     *
+     * @throws IllegalArgumentException for a value other than all, local or 
none
+     */
+    static String policy(String externalReferences) {
+        if (externalReferences == null || externalReferences.isBlank()) {
+            return ALL;
+        }
+        String policy = externalReferences.trim().toLowerCase(Locale.ROOT);
+        if (ALL.equals(policy) || LOCAL.equals(policy) || NONE.equals(policy)) 
{
+            return policy;
+        }
+        throw new IllegalArgumentException(
+                "Unknown externalReferences value: " + externalReferences + ", 
expected all, local or none");
+    }
+
+    /**
+     * Checks every {@code $ref} of the specification, and of the local 
documents it references, against the option.
+     *
+     * @param  externalReferences       the option: all, local or none
+     * @param  specification            the specification, as JSON or YAML text
+     * @param  specificationUri         where the specification comes from, 
for the error message
+     * @param  remote                   whether the specification comes from a 
URL: the parser then resolves its
+     *                                  external references against that URL
+     * @throws IllegalArgumentException for the first reference the option 
does not allow
+     */
+    static void check(String externalReferences, String specification, String 
specificationUri, boolean remote) {
+        String policy = policy(externalReferences);
+        if (ALL.equals(policy)) {
+            return;
+        }
+        checkDocument(policy, specification, specificationUri, null, remote, 
new HashSet<>());
+    }
+
+    private static void checkDocument(
+            String policy, String content, String specificationUri, String 
document, boolean remote,
+            Set<String> checked) {
+        DocumentCheck check = new DocumentCheck(policy, specificationUri, 
document, remote);
+        check.scan(readTree(content, specificationUri, document), "");
+        // a local document can reference further documents, which the parser 
loads as well
+        for (String file : check.localDocuments) {
+            if (checked.add(file)) {
+                String text = readLocalDocument(file);
+                if (text != null) {
+                    checkDocument(policy, text, specificationUri, file, false, 
checked);
+                }
+            }
+        }
+    }
+
+    private static JsonNode readTree(String content, String specificationUri, 
String document) {
+        try {
+            return (content.trim().startsWith("{") ? Json.mapper() : 
Yaml.mapper()).readTree(content);
+        } catch (JsonProcessingException e) {
+            throw new IllegalArgumentException(
+                    "Cannot read the OpenAPI specification " + specificationUri
+                                               + (document != null ? " (" + 
document + ")" : "")
+                                               + " to check its references: " 
+ e.getMessage(),
+                    e);
+        }
+    }
+
+    private static String readLocalDocument(String file) {
+        try {
+            // how the parser reads a relative reference of a specification 
that has no location: from the working
+            // directory, then from the classpath
+            return RefUtils.readExternalRef(file, RefFormat.RELATIVE, null, 
new File(".").toPath());
+        } catch (RuntimeException e) {
+            LOG.debug("Cannot read {} to check its references, the parser 
reports it when it resolves the specification: {}",
+                    file, e.getMessage());
+            return null;
+        }
+    }
+
+    private static boolean isHttp(String file) {
+        String lower = file.toLowerCase(Locale.ROOT);
+        return lower.startsWith("http:") || lower.startsWith("https:");

Review Comment:
   â„šī¸ **`file:` URI handling** — `isHttp()` only recognizes `http:` and 
`https:`. A `$ref` value like `file:///etc/passwd` will not be caught by this 
check and will fall through to the local-document branch. If `file:` URIs are 
intentionally treated as local references, please add a comment explaining the 
intent.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to