Repository: swagger-api/swagger-parser
Pull request #1820 (2022-10-06 13:30:29)

ISSUE-1745 - Change the buildUrl to handle more cases of path traversal


Patch:
diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java
index 91008997..7fcf56a4 100644
--- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java
+++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java
@@ -11,6 +11,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -150,48 +151,20 @@ public class RefUtils {
     }
 
     public static String buildUrl(String rootPath, String relativePath) {
-        String[] rootPathParts = rootPath.split("/");
-        String [] relPathParts = relativePath.split("/");
-
-        if(rootPath == null || relativePath == null) {
-            return null;
-        }
-
-        int trimRoot = 0;
-        int trimRel = 0;
-
-        if(!"".equals(rootPathParts[rootPathParts.length - 1])) {
-            trimRoot = 1;
-        }
-        if("".equals(relPathParts[0])) {
-            trimRel = 1; trimRoot = rootPathParts.length-3;
-        }        
-        for(int i = 0; i < rootPathParts.length; i++) {
-            if("".equals(rootPathParts[i])) {
-                trimRel += 1;
-            }
-            else {
-                break;
-            }
-        }
-        for(int i = 0; i < relPathParts.length; i ++) {
-            if(".".equals(relPathParts[i])) {
-                trimRel += 1;
-            }
-            else if ("..".equals(relPathParts[i])) {
-                trimRel += 1; trimRoot += 1;
-            }
-        }
-
-        String [] outputParts = new String[rootPathParts.length + relPathParts.length - trimRoot - trimRel];
-        System.arraycopy(rootPathParts, 0, outputParts, 0, rootPathParts.length - trimRoot);
-        System.arraycopy(relPathParts,
-                trimRel,
-                outputParts,
-                rootPathParts.length - trimRoot,
-                relPathParts.length - trimRel);
-
-        return StringUtils.join(outputParts, "/");
+      if(rootPath == null || relativePath == null) {
+          return null;
+      }
+
+      try {
+          int until = rootPath.lastIndexOf("/")+1;
+          String root = rootPath.substring(0, until);
+          URL rootUrl = new URL(root);
+          URL finalUrl = new URL(rootUrl, relativePath);
+          return finalUrl.toString();
+      }
+      catch(Exception e) {
+          throw new RuntimeException(e);
+      }
     }
 
     public static String readExternalRef(String file, RefFormat refFormat, List<AuthorizationValue> auths,


Test patch:
diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java
index 2fc973cd..8c8ab5d3 100644
--- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java
+++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/util/RefUtilsTest.java
@@ -312,6 +312,13 @@ public class RefUtilsTest {
         assertEquals(RefUtils.buildUrl("http://foo.bar.com/my/dir/file.yaml", "/my/newFile.yaml"), "http://foo.bar.com/my/newFile.yaml");
     }
 
+    @Test
+    public void testPathJoinIssue1745() {
+        assertEquals(RefUtils.buildUrl("http://foo.bar.com/my/dir/file.yaml", "./second/../newFile.yaml"), "http://foo.bar.com/my/dir/newFile.yaml");
+        assertEquals(RefUtils.buildUrl("http://foo.bar.com/my/dir/", "./second/../newFile.yaml"), "http://foo.bar.com/my/dir/newFile.yaml");
+        // This is a little strange in the output (beacuse it has not completely eliminated the ..) but is still correct - paste a similar url into a browser and it resolves it correctly.
+        assertEquals(RefUtils.buildUrl("http://foo.bar.com/my/dir/file.yaml", "/second/../newFile.yaml"), "http://foo.bar.com/second/../newFile.yaml");
+    }
 
     @Test
     public void shouldReturnEmptyExternalPathForInternalReference() {