diff --git a/VERSION.txt b/VERSION.txt index 9f0adfc11..7062ff153 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.20251022.0 +0.20251027.0 diff --git a/src/file/file.cpp b/src/file/file.cpp index 789f046fc..736948135 100644 --- a/src/file/file.cpp +++ b/src/file/file.cpp @@ -27,7 +27,7 @@ File::Impl::Impl(const std::string &pFileNameOrUrl, bool pRetrieveContents) { // Check whether we are dealing with a local file or a URL. - auto [isLocalFile, fileNameOrUrl] = retrieveFileInfo(pFileNameOrUrl); + auto [isLocalFile, fileNameOrUrl] = retrieveFileInfo(decodeUrl(pFileNameOrUrl)); if (isLocalFile) { mFilePath = stringToPath(fileNameOrUrl); diff --git a/src/misc/utils.cpp b/src/misc/utils.cpp index c2e3eb2bb..b400b3c2e 100644 --- a/src/misc/utils.cpp +++ b/src/misc/utils.cpp @@ -145,6 +145,39 @@ bool fuzzyCompare(double pNb1, double pNb2) return std::fabs(pNb1 - pNb2) * ONE_TRILLION <= std::fmin(std::fabs(pNb1), std::fabs(pNb2)); } +std::string encodeUrl(const std::string &pUrl) +{ + std::ostringstream res; + + for (const char c : pUrl) { + if ((isalnum(c) != 0) || (std::string("!#$&'()*+,-./:;=?@_~").find(c) != std::string::npos)) { + res << c; + } else { + res << '%' << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << static_cast(static_cast(c)); + } + } + return res.str(); +} + +std::string decodeUrl(const std::string &pUrl) +{ + static constexpr auto BASE_16 = 16; + + std::string res; + + for (size_t i = 0; i < pUrl.size(); ++i) { + if ((pUrl[i] == '%') && (i + 2 < pUrl.size()) && (std::isxdigit(pUrl[i + 1]) != 0) && (std::isxdigit(pUrl[i + 2]) != 0)) { + res += static_cast(std::stoi(pUrl.substr(i + 1, 2), nullptr, BASE_16)); + + i += 2; + } else { + res += pUrl[i]; + } + } + + return res; +} + #ifdef BUILDING_USING_MSVC std::string forwardSlashPath(const std::string &pPath) { @@ -436,7 +469,7 @@ std::tuple downloadFile(const std::string &pUrl) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(curl, CURLOPT_URL, pUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, encodeUrl(pUrl).c_str()); curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(&file)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); diff --git a/src/misc/utils.h b/src/misc/utils.h index f3928ea07..816031073 100644 --- a/src/misc/utils.h +++ b/src/misc/utils.h @@ -78,6 +78,9 @@ void LIBOPENCOR_UNIT_TESTING_EXPORT printArray(const std::string &pName, const D bool LIBOPENCOR_UNIT_TESTING_EXPORT fuzzyCompare(double pNb1, double pNb2); +std::string LIBOPENCOR_UNIT_TESTING_EXPORT encodeUrl(const std::string &pUrl); +std::string LIBOPENCOR_UNIT_TESTING_EXPORT decodeUrl(const std::string &pUrl); + #ifdef BUILDING_USING_MSVC std::string LIBOPENCOR_UNIT_TESTING_EXPORT forwardSlashPath(const std::string &pPath); #endif diff --git a/tests/api/file/basictests.cpp b/tests/api/file/basictests.cpp index 00466beda..4abf091df 100644 --- a/tests/api/file/basictests.cpp +++ b/tests/api/file/basictests.cpp @@ -104,6 +104,17 @@ TEST(BasicFileTest, remoteFile) EXPECT_FALSE(file->contents().empty()); } +TEST(BasicFileTest, encodedRemoteFile) +{ + auto file = libOpenCOR::File::create(libOpenCOR::ENCODED_REMOTE_FILE); + + EXPECT_EQ(file->type(), libOpenCOR::File::Type::CELLML_FILE); + EXPECT_NE(file->fileName(), ""); + EXPECT_EQ(file->url(), libOpenCOR::NON_ENCODED_REMOTE_FILE); + EXPECT_EQ(file->path(), libOpenCOR::NON_ENCODED_REMOTE_FILE); + EXPECT_FALSE(file->contents().empty()); +} + TEST(BasicFileTest, localVirtualFile) { auto file = libOpenCOR::File::create(libOpenCOR::resourcePath(libOpenCOR::UNKNOWN_FILE), false); diff --git a/tests/bindings/javascript/file.basic.test.js b/tests/bindings/javascript/file.basic.test.js index e0d3b5082..90ad4813a 100644 --- a/tests/bindings/javascript/file.basic.test.js +++ b/tests/bindings/javascript/file.basic.test.js @@ -77,6 +77,23 @@ test.describe('File basic tests', () => { assertIssues(loc, file, expectedUnknownFileIssues); }); + test('Encoded remote file', () => { + const file = new loc.File(utils.ENCODED_REMOTE_FILE); + + assert.strictEqual(file.type.value, loc.File.Type.UNKNOWN_FILE.value); + assert.strictEqual(file.fileName, '/some/path/file'); + assert.strictEqual(file.url, utils.NON_ENCODED_REMOTE_FILE); + assert.strictEqual(file.path, utils.NON_ENCODED_REMOTE_FILE); + assert.deepStrictEqual(file.contents(), utils.NO_CONTENTS); + assertIssues(loc, file, expectedNoIssues); + + file.setContents(unknownContentsPtr, utils.UNKNOWN_CONTENTS.length); + + assert.strictEqual(file.type.value, loc.File.Type.UNKNOWN_FILE.value); + assert.deepStrictEqual(file.contents(), utils.UNKNOWN_CONTENTS); + assertIssues(loc, file, expectedUnknownFileIssues); + }); + test('File manager', () => { const fileManager = loc.FileManager.instance(); diff --git a/tests/bindings/javascript/utils.in.js b/tests/bindings/javascript/utils.in.js index f42151a46..d73d3b992 100644 --- a/tests/bindings/javascript/utils.in.js +++ b/tests/bindings/javascript/utils.in.js @@ -35,6 +35,10 @@ export const HTTP_REMOTE_COMBINE_ARCHIVE = 'http://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.omex'; export const REMOTE_BASE_PATH = 'https://raw.githubusercontent.com/opencor/libopencor/master/tests/res'; export const REMOTE_FILE = 'https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml'; +export const ENCODED_REMOTE_FILE = + 'https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO%20BG%20example%203.1.cellml'; +export const NON_ENCODED_REMOTE_FILE = + 'https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO BG example 3.1.cellml'; export const NO_CONTENTS = stringToArrayBuffer(''); export const NULL_CHARACTER_CONTENTS = stringToArrayBuffer('\0'); diff --git a/tests/bindings/python/test_file_basic.py b/tests/bindings/python/test_file_basic.py index 757c6bc40..202f3fbe8 100644 --- a/tests/bindings/python/test_file_basic.py +++ b/tests/bindings/python/test_file_basic.py @@ -104,6 +104,16 @@ def test_remote_file(): assert file.contents != [] +def test_encoded_remote_file(): + file = loc.File(utils.EncodedRemoteFile) + + assert file.type == loc.File.Type.CellmlFile + assert file.file_name != "" + assert file.url == utils.NonEncodedRemoteFile + assert file.path == utils.NonEncodedRemoteFile + assert file.contents != [] + + def test_local_virtual_file(): file = loc.File(utils.resource_path(utils.UnknownFile), False) diff --git a/tests/bindings/python/utils.in.py b/tests/bindings/python/utils.in.py index 314122f36..0388e9b22 100644 --- a/tests/bindings/python/utils.in.py +++ b/tests/bindings/python/utils.in.py @@ -52,6 +52,8 @@ ) RemoteBasePath = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res" RemoteFile = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml" +EncodedRemoteFile = "https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO%20BG%20example%203.1.cellml" +NonEncodedRemoteFile = "https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO BG example 3.1.cellml" UnknownRemoteFile = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/unknown_file.txt" IrretrievableRemoteFile = "https://some.domain.com/irretrievable_file.txt" diff --git a/tests/misc/failingsimulationtests.cpp b/tests/misc/failingsimulationtests.cpp index 73edd599d..82b8f8c7c 100644 --- a/tests/misc/failingsimulationtests.cpp +++ b/tests/misc/failingsimulationtests.cpp @@ -22,7 +22,7 @@ TEST(FailingSimulationTest, basic) { // Default simulation. - auto file = libOpenCOR::File::create(libOpenCOR::resourcePath("../misc/res/tt04.omex")); + auto file = libOpenCOR::File::create(libOpenCOR::resourcePath("misc/failablesimulation.omex")); auto document = libOpenCOR::SedDocument::create(file); auto instance = document->instantiate(); diff --git a/tests/misc/tests.cmake b/tests/misc/tests.cmake index 5336fe44d..6bbdbb438 100644 --- a/tests/misc/tests.cmake +++ b/tests/misc/tests.cmake @@ -20,4 +20,5 @@ set(${TEST}_CATEGORY) set(${TEST}_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/compilertests.cpp ${CMAKE_CURRENT_LIST_DIR}/failingsimulationtests.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils.cpp ) diff --git a/tests/misc/utils.cpp b/tests/misc/utils.cpp new file mode 100644 index 000000000..457358ccf --- /dev/null +++ b/tests/misc/utils.cpp @@ -0,0 +1,35 @@ +/* +Copyright libOpenCOR contributors. + +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. +*/ + +#include "utils.h" + +#include "tests/utils.h" + +TEST(UtilsTest, encodeUrl) +{ + EXPECT_EQ(libOpenCOR::encodeUrl(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"), + "%20!%22#$%25&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"); +} + +TEST(UtilsTest, decodeUrl) +{ + EXPECT_EQ(libOpenCOR::decodeUrl("%20!%22#$%25&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"), + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); + EXPECT_EQ(libOpenCOR::decodeUrl("%"), "%"); + EXPECT_EQ(libOpenCOR::decodeUrl("%X"), "%X"); + EXPECT_EQ(libOpenCOR::decodeUrl("%XX"), "%XX"); + EXPECT_EQ(libOpenCOR::decodeUrl("%2X"), "%2X"); +} diff --git a/tests/misc/res/tt04.omex b/tests/res/misc/failablesimulation.omex similarity index 100% rename from tests/misc/res/tt04.omex rename to tests/res/misc/failablesimulation.omex diff --git a/tests/utils.in.h b/tests/utils.in.h index 2b117f78f..be4ddb929 100644 --- a/tests/utils.in.h +++ b/tests/utils.in.h @@ -56,6 +56,8 @@ static constexpr auto HTTP_REMOTE_SEDML_FILE = "http://raw.githubusercontent.com static constexpr auto HTTP_REMOTE_COMBINE_ARCHIVE = "http://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.omex"; static constexpr auto REMOTE_BASE_PATH = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res"; static constexpr auto REMOTE_FILE = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/cellml_2.cellml"; +static constexpr auto ENCODED_REMOTE_FILE = "https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO%20BG%20example%203.1.cellml"; +static constexpr auto NON_ENCODED_REMOTE_FILE = "https://models.physiomeproject.org/workspace/aed/@@rawfile/d4accf8429dbf5bdd5dfa1719790f361f5baddbe/FAIRDO BG example 3.1.cellml"; static constexpr auto UNKNOWN_REMOTE_FILE = "https://raw.githubusercontent.com/opencor/libopencor/master/tests/res/unknown_file.txt"; static constexpr auto IRRETRIEVABLE_REMOTE_FILE = "https://some.domain.com/irretrievable_file.txt";