From 3d51c0eacc8478df7b4bf00cee7838035765d306 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 10 Dec 2024 16:22:17 +0100 Subject: [PATCH] CPLGetPath()/CPLGetDirname(): make them work with /vsicurl? and URL encoded Fixes #11467 --- autotest/cpp/test_cpl.cpp | 12 ++++++ autotest/ogr/ogr_shape.py | 29 ++++++++++++++ port/cpl_path.cpp | 81 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/autotest/cpp/test_cpl.cpp b/autotest/cpp/test_cpl.cpp index 111510f688ad..046116e2fbb2 100644 --- a/autotest/cpp/test_cpl.cpp +++ b/autotest/cpp/test_cpl.cpp @@ -1059,6 +1059,12 @@ TEST_F(test_cpl, CPLGetPath) EXPECT_STREQ(CPLGetPath("/foo/bar"), "/foo"); EXPECT_STREQ(CPLGetPath("/vsicurl/http://example.com/foo/bar?suffix"), "/vsicurl/http://example.com/foo?suffix"); + EXPECT_STREQ( + CPLGetPath( + "/vsicurl?foo=bar&url=https%3A%2F%2Fraw.githubusercontent.com%" + "2FOSGeo%2Fgdal%2Fmaster%2Fautotest%2Fogr%2Fdata%2Fpoly.shp"), + "/vsicurl?foo=bar&url=https%3A%2F%2Fraw.githubusercontent.com%2FOSGeo%" + "2Fgdal%2Fmaster%2Fautotest%2Fogr%2Fdata"); } TEST_F(test_cpl, CPLGetDirname) @@ -1067,6 +1073,12 @@ TEST_F(test_cpl, CPLGetDirname) EXPECT_STREQ(CPLGetDirname("/foo/bar"), "/foo"); EXPECT_STREQ(CPLGetDirname("/vsicurl/http://example.com/foo/bar?suffix"), "/vsicurl/http://example.com/foo?suffix"); + EXPECT_STREQ( + CPLGetDirname( + "/vsicurl?foo=bar&url=https%3A%2F%2Fraw.githubusercontent.com%" + "2FOSGeo%2Fgdal%2Fmaster%2Fautotest%2Fogr%2Fdata%2Fpoly.shp"), + "/vsicurl?foo=bar&url=https%3A%2F%2Fraw.githubusercontent.com%2FOSGeo%" + "2Fgdal%2Fmaster%2Fautotest%2Fogr%2Fdata"); } TEST_F(test_cpl, VSIGetDiskFreeSpace) diff --git a/autotest/ogr/ogr_shape.py b/autotest/ogr/ogr_shape.py index 0df5c2359e33..1c463bb361e5 100755 --- a/autotest/ogr/ogr_shape.py +++ b/autotest/ogr/ogr_shape.py @@ -1745,6 +1745,35 @@ def test_ogr_shape_44(): assert f is not None, "did not get expected feature" +############################################################################### +# Test /vsicurl?url= + + +@pytest.mark.require_curl() +def test_ogr_shape_vsicurl_url(): + + conn = gdaltest.gdalurlopen( + "https://raw.githubusercontent.com/OSGeo/gdal/release/3.10/autotest/ogr/data/poly.shp" + ) + if conn is None: + pytest.skip("cannot open URL") + conn.close() + + ds = ogr.Open( + "/vsicurl?url=https%3A%2F%2Fraw.githubusercontent.com%2FOSGeo%2Fgdal%2Frelease%2F3.10%2Fautotest%2Fogr%2Fdata%2Fpoly.shp" + ) + assert ds is not None + + lyr = ds.GetLayer(0) + + srs = lyr.GetSpatialRef() + wkt = srs.ExportToWkt() + assert wkt.find("OSGB") != -1, "did not get expected SRS" + + f = lyr.GetNextFeature() + assert f is not None, "did not get expected feature" + + ############################################################################### # Test ignored fields works ok on a shapefile. diff --git a/port/cpl_path.cpp b/port/cpl_path.cpp index f5733d8e5eea..ee4ab5762911 100644 --- a/port/cpl_path.cpp +++ b/port/cpl_path.cpp @@ -128,6 +128,10 @@ static int CPLFindFilenameStart(const char *pszFilename, size_t nStart = 0) const char *CPLGetPath(const char *pszFilename) { + char *pszStaticResult = CPLGetStaticResult(); + if (pszStaticResult == nullptr) + return CPLStaticBufferTooSmall(pszStaticResult); + size_t nSuffixPos = 0; if (STARTS_WITH(pszFilename, "/vsicurl/http")) { @@ -135,11 +139,41 @@ const char *CPLGetPath(const char *pszFilename) if (pszQuestionMark) nSuffixPos = static_cast(pszQuestionMark - pszFilename); } + else if (STARTS_WITH(pszFilename, "/vsicurl?") && + strstr(pszFilename, "url=")) + { + std::string osRet; + const CPLStringList aosTokens( + CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0)); + for (int i = 0; i < aosTokens.size(); i++) + { + if (osRet.empty()) + osRet = "/vsicurl?"; + else + osRet += '&'; + if (STARTS_WITH(aosTokens[i], "url=") && + !STARTS_WITH(aosTokens[i], "url=/vsicurl")) + { + char *pszUnescaped = + CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL); + char *pszPath = CPLEscapeString( + CPLGetPath(pszUnescaped + strlen("url=")), -1, CPLES_URL); + osRet += "url="; + osRet += pszPath; + CPLFree(pszPath); + CPLFree(pszUnescaped); + } + else + { + osRet += aosTokens[i]; + } + } + CPLStrlcpy(pszStaticResult, osRet.c_str(), CPL_PATH_BUF_SIZE); + return pszStaticResult; + } const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos); - char *pszStaticResult = CPLGetStaticResult(); - - if (pszStaticResult == nullptr || iFileStart >= CPL_PATH_BUF_SIZE) + if (iFileStart >= CPL_PATH_BUF_SIZE) return CPLStaticBufferTooSmall(pszStaticResult); CPLAssert(!(pszFilename >= pszStaticResult && @@ -197,6 +231,10 @@ const char *CPLGetPath(const char *pszFilename) const char *CPLGetDirname(const char *pszFilename) { + char *pszStaticResult = CPLGetStaticResult(); + if (pszStaticResult == nullptr) + return CPLStaticBufferTooSmall(pszStaticResult); + size_t nSuffixPos = 0; if (STARTS_WITH(pszFilename, "/vsicurl/http")) { @@ -204,11 +242,42 @@ const char *CPLGetDirname(const char *pszFilename) if (pszQuestionMark) nSuffixPos = static_cast(pszQuestionMark - pszFilename); } + else if (STARTS_WITH(pszFilename, "/vsicurl?") && + strstr(pszFilename, "url=")) + { + std::string osRet; + const CPLStringList aosTokens( + CSLTokenizeString2(pszFilename + strlen("/vsicurl?"), "&", 0)); + for (int i = 0; i < aosTokens.size(); i++) + { + if (osRet.empty()) + osRet = "/vsicurl?"; + else + osRet += '&'; + if (STARTS_WITH(aosTokens[i], "url=") && + !STARTS_WITH(aosTokens[i], "url=/vsicurl")) + { + char *pszUnescaped = + CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL); + char *pszPath = CPLEscapeString( + CPLGetDirname(pszUnescaped + strlen("url=")), -1, + CPLES_URL); + osRet += "url="; + osRet += pszPath; + CPLFree(pszPath); + CPLFree(pszUnescaped); + } + else + { + osRet += aosTokens[i]; + } + } + CPLStrlcpy(pszStaticResult, osRet.c_str(), CPL_PATH_BUF_SIZE); + return pszStaticResult; + } const int iFileStart = CPLFindFilenameStart(pszFilename, nSuffixPos); - char *pszStaticResult = CPLGetStaticResult(); - - if (pszStaticResult == nullptr || iFileStart >= CPL_PATH_BUF_SIZE) + if (iFileStart >= CPL_PATH_BUF_SIZE) return CPLStaticBufferTooSmall(pszStaticResult); CPLAssert(!(pszFilename >= pszStaticResult &&