Skip to content

Question: syntax for abs paths passed as file:// URLs in Windows? #3730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jaimergp opened this issue Jan 6, 2025 · 26 comments · May be fixed by #3871
Open

Question: syntax for abs paths passed as file:// URLs in Windows? #3730

jaimergp opened this issue Jan 6, 2025 · 26 comments · May be fixed by #3871
Labels
type::bug Something isn't working where::windows Windows-specific issues

Comments

@jaimergp
Copy link
Contributor

jaimergp commented Jan 6, 2025

What's the actual way? Given this absolute path in Windows D:\a_\temp\popen-gw0\test_croot_with_spaces0\space path:

>>> from libmambapy.specs import CondaURL
>>> CondaURL.parse("file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space path")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space path": Malformed input to a URL function
>>> CondaURL.parse("file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path
")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path": Bad file:// URL
>>> CondaURL.parse("file:///D/a/_temp/popen-gw0/test_croot_with_spaces0/space path")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D/a/_temp/popen-gw0/test_croot_with_spaces0/space path": Malformed input to a URL function
>>> CondaURL.parse("file:///D/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path"
)
file:///D/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path

Only the last one (without the : in the drive letter) is accepted, but not sure if that's understood as a Windows path internally? Should I always remove the : in the drive?

@jaimergp
Copy link
Contributor Author

jaimergp commented Jan 6, 2025

Seeing comments like this one:

/**
* Set the path from a not encoded value.
*
* All '/' are not encoded but interpreted as separators.
* On windows with a file scheme, the colon after the drive letter is not encoded.
* A leading '/' is added if abscent.
*/

makes me think it should be accepted?

@kenodegard
Copy link
Contributor

Per this blogpost from the IE team back in 2006, the proper syntax is as follows:

Screenshot 2025-01-06 at 16 17 23

@kenodegard
Copy link
Contributor

Reading the file_uri_unc2_to_unc4 implementation:

auto file_uri_unc2_to_unc4(std::string_view uri) -> std::string
{
static constexpr std::string_view file_scheme = "file:";
// Not "file:" scheme
if (!util::starts_with(uri, file_scheme))
{
return std::string(uri);
}
// No hostname set in "file://hostname/path/to/data.xml"
auto [slashes, rest] = util::lstrip_parts(util::remove_prefix(uri, file_scheme), '/');
if (slashes.size() != 2)
{
return std::string(uri);
}
const auto s_idx = rest.find('/');
const auto c_idx = rest.find(':');
// ':' found before '/', a Windows drive is specified in "file://C:/path/to/data.xml" (not
// really URI compliant, they should have "file:///" or "file:/"). Otherwise no path in
// "file://hostname", also not URI compliant.
if (c_idx < s_idx)
{
return std::string(uri);
}
const auto hostname = rest.substr(0, s_idx);
// '\' are used as path separator in "file://\\hostname\path\to\data.xml" (also not RFC
// compliant)
if (util::starts_with(hostname, R"(\\)"))
{
return std::string(uri);
}
// Things that means localhost are kept for some reason in ``url_to_path``
// in ``conda.common.path``
if ((hostname == "localhost") || (hostname == "127.0.0.1") || (hostname == "::1"))
{
return std::string(uri);
}
return util::concat("file:////", rest);
}

And testing some more, it seems CondaURL wants 4 leading / and %-encoded spaces:

>>> from libmambapy.specs import CondaURL
>>> CondaURL.parse("file:////D:/a_/temp/popen-gw0/test_croot_with_spaces0/space%20path")
file:////D:/a_/temp/popen-gw0/test_croot_with_spaces0/space%20path

@jaimergp
Copy link
Contributor Author

jaimergp commented Jan 7, 2025

It works with 4+ leading /, so I don't think it was intended. file:///C:/path/to/file should be legal imo.

@jjerphan jjerphan added the type::question Further information is requested label Jan 7, 2025
@jaimergp
Copy link
Contributor Author

jaimergp commented Feb 4, 2025

Is this a question or more like a bug? 🤔

@Hind-M
Copy link
Member

Hind-M commented Feb 21, 2025

When using 2 or 3 slashes, the error is the same (Bad file:// URL), and comes from libcurl when calling curl_url_set here.
From curl documentation:

FILE
Read or write local files. curl does not support accessing file:// URL remotely, but when running on Microsoft Windows using the native UNC approach works. 

It seems that only file://\\ is accepted, thus the 4 slashes.

@Klaim
Copy link
Member

Klaim commented Feb 25, 2025

I believe that indeed file:///C:/... should work, it also work for me correctly when I test with curl on my Windows.
Also should work:

  • file://C:\\...\\...
  • file://\\C: ...

It's not clear to me why a 4th / is added at the end of that function, it doesnt make sense path-wise.

@Hind-M
Copy link
Member

Hind-M commented Feb 25, 2025

I believe that indeed file:///C:/... should work, it also work for me correctly when I test with curl on my Windows.

Hum, what do you use exactly when you test with curl? Is it a command line?
So far, the issue that I observed Bad file:// URL occurs when using curl_url_set.

@Klaim
Copy link
Member

Klaim commented Feb 25, 2025

Hum, what do you use exactly when you test with curl? Is it a command line?

Yes that's when using the curl provided with git-bash (C:\Program Files\Git\mingw64\bin\curl.exe, but I have other versions installed like the system one C:\Windows\System32\curl.exe which confirms the same output). I can also try to make a test exe and test with calling libcurl (from conda-forge packages) directly to confirm.

My understanding so far is that in no cases does file://// is supposed to be valid. file://\\ would be as it's just 1 (anti-)slash for absolute path. Is my understanding correct? (I might be missing something here).

@Hind-M
Copy link
Member

Hind-M commented Feb 25, 2025

Yes that's when using the curl provided with git-bash (C:\Program Files\Git\mingw64\bin\curl.exe, but I have other versions installed like the system one C:\Windows\System32\curl.exe which confirms the same output). I can also try to make a test exe and test with calling libcurl (from conda-forge packages) directly to confirm.

Yes I mean the issue seems to be related to curl_url_set function specifically (in the mamba parsing case at least), so if you are running a command with curl, the behavior may be different...

@jaimergp
Copy link
Contributor Author

So what's the status here? Are we supposed to pass four slashes, three, two...? Or should this be handled internally in libmamba so library users are not exposed to implementation details in libcurl?

@jjerphan jjerphan added the where::windows Windows-specific issues label Mar 24, 2025
@JohanMabille
Copy link
Member

JohanMabille commented Mar 25, 2025

Sorry for the late answer, Mamba should supports any valid URI, independently from curl. Its implementation detail should not leak to the end user.

EDIT: labelling this as a bug.

@JohanMabille JohanMabille added type::bug Something isn't working and removed type::question Further information is requested labels Mar 25, 2025
@Hind-M
Copy link
Member

Hind-M commented Mar 27, 2025

Actually, the issue is only reproducible when we try to parse CondaURL.parse("file:///D:/some_path") on a non windows machine. If executed on a windows machine, it works fine (cf. #3871).
Is that your case as well (not executing on a windows machine)?

@jaimergp
Copy link
Contributor Author

@Hind-M
Copy link
Member

Hind-M commented Apr 3, 2025

Hmm, could that be a difference in libcurl versions?
The added test here (intentionally made to fail for verification purposes) doesn't lead to any of the errors reported but proceeds as expected :/

@jaimergp
Copy link
Contributor Author

jaimergp commented Apr 3, 2025

On conda-build's CI:

libcurl                   8.12.1               h9da9810_0    defaults

On yours:

libcurl                   8.12.1               h88aaa65_0    conda-forge

The build flags used in the recipes are slightly different:

Can you try with defaults in your CI? I'll try with conda-forge on conda-build's.

@jaimergp
Copy link
Contributor Author

jaimergp commented Apr 3, 2025

The CI is not done yet, but I can already see that the test also fails with libcurl from conda-forge:

[gw1] [ 11%] FAILED tests/test_api_build.py::test_croot_with_spaces 

@jaimergp
Copy link
Contributor Author

jaimergp commented Apr 3, 2025

Locally, on Linux, with the attached conda list output, I can still reproduce the error with your tests.

>>> from libmambapy.specs import CondaURL
>>> url = CondaURL.parse("file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D:/a/_temp/popen-gw0/test_croot_with_spaces0/space%20path": Bad file:// URL
>>> url = CondaURL.parse("file:///D:/a/_temp/popen-gw0/some_other_parts")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D:/a/_temp/popen-gw0/some_other_parts": Bad file:// URL
conda list
conda list
# packages in environment at /opt/conda:
#
# Name                    Version                   Build  Channel
_openmp_mutex             4.5                       2_gnu    conda-forge
anaconda-client           1.12.3             pyhd8ed1ab_1    conda-forge
annotated-types           0.7.0              pyhd8ed1ab_1    conda-forge
appdirs                   1.4.4              pyhd8ed1ab_1    conda-forge
archspec                  0.2.3              pyhd8ed1ab_0    conda-forge
attrs                     24.3.0             pyh71513ae_0    conda-forge
backports                 1.0                pyhd8ed1ab_5    conda-forge
backports.tarfile         1.2.0              pyhd8ed1ab_1    conda-forge
beautifulsoup4            4.12.3             pyha770c72_1    conda-forge
blinker                   1.9.0              pyhff2d567_0    conda-forge
boltons                   24.0.0             pyhd8ed1ab_1    conda-forge
boto3                     1.35.92            pyhd8ed1ab_0    conda-forge
botocore                  1.35.92         pyge310_1234567_0    conda-forge
brotli-python             1.1.0           py311h89d996e_2    conda-forge
bzip2                     1.0.8                h31becfc_5    conda-forge
c-ares                    1.34.4               h86ecc28_0    conda-forge
ca-certificates           2025.1.31            hcefe29a_0    conda-forge
cachecontrol              0.14.1             pyha770c72_1    conda-forge
cachecontrol-with-filecache 0.14.1             pyhd8ed1ab_1    conda-forge
cachy                     0.3.0              pyhd8ed1ab_2    conda-forge
certifi                   2025.1.31          pyhd8ed1ab_0    conda-forge
cffi                      1.17.1          py311h14e8bb7_0    conda-forge
cfgv                      3.3.1              pyhd8ed1ab_1    conda-forge
chardet                   5.2.0           py311hfecb2dc_2    conda-forge
charset-normalizer        3.4.1              pyhd8ed1ab_0    conda-forge
click                     8.1.8              pyh707e725_0    conda-forge
click-default-group       1.2.4              pyhd8ed1ab_1    conda-forge
clikit                    0.6.2              pyhd8ed1ab_3    conda-forge
colorama                  0.4.6              pyhd8ed1ab_0    conda-forge
conda                     25.3.1.dev39+g6a2175340.d20250403           dev_0    <develop>
conda-build               24.11.2         py311hec3470c_1    conda-forge
conda-content-trust       0.2.0              pyhd8ed1ab_0    conda-forge
conda-index               0.5.0              pyhd8ed1ab_0    conda-forge
conda-libmamba-solver     25.3.1.dev2+g7212597          pypi_0    pypi
conda-lock                2.5.7              pyhd8ed1ab_1    conda-forge
conda-package-handling    2.4.0              pyha770c72_1    conda-forge
conda-package-streaming   0.9.0              pyhd8ed1ab_0    conda-forge
coverage                  7.6.10          py311ha09ea12_0    conda-forge
cpp-expected              1.1.0                h4c384f3_0    conda-forge
crashtest                 0.4.1              pyhd8ed1ab_1    conda-forge
cryptography              44.0.0          py311h4047cc9_0    conda-forge
dbus                      1.13.6               h12b9eeb_3    conda-forge
defusedxml                0.7.1              pyhd8ed1ab_0    conda-forge
distlib                   0.3.9              pyhd8ed1ab_1    conda-forge
distro                    1.9.0              pyhd8ed1ab_1    conda-forge
ensureconda               1.4.4              pyhd8ed1ab_1    conda-forge
exceptiongroup            1.2.2              pyhd8ed1ab_1    conda-forge
expat                     2.6.4                h5ad3122_0    conda-forge
filelock                  3.16.1             pyhd8ed1ab_1    conda-forge
flask                     3.1.0              pyhff2d567_0    conda-forge
fmt                       11.1.4               h97e1849_1    conda-forge
frozendict                2.4.6           py311ha879c10_0    conda-forge
git                       2.47.1          pl5321h0e2bd52_0    conda-forge
gitdb                     4.0.12             pyhd8ed1ab_0    conda-forge
gitpython                 3.1.44             pyhff2d567_0    conda-forge
html5lib                  1.1                pyhd8ed1ab_2    conda-forge
icu                       75.1                 hf9b3779_0    conda-forge
identify                  2.6.4              pyhd8ed1ab_0    conda-forge
idna                      3.7                pyhd8ed1ab_0    conda-forge
importlib-metadata        8.5.0              pyha770c72_1    conda-forge
importlib_resources       6.4.5              pyhd8ed1ab_1    conda-forge
iniconfig                 2.0.0              pyhd8ed1ab_1    conda-forge
itsdangerous              2.2.0              pyhd8ed1ab_1    conda-forge
jaraco.classes            3.4.0              pyhd8ed1ab_2    conda-forge
jaraco.context            6.0.1              pyhd8ed1ab_0    conda-forge
jaraco.functools          4.1.0              pyhd8ed1ab_0    conda-forge
jeepney                   0.8.0              pyhd8ed1ab_0    conda-forge
jinja2                    3.1.5              pyhd8ed1ab_0    conda-forge
jmespath                  1.0.1              pyhd8ed1ab_1    conda-forge
jsonpatch                 1.33               pyhd8ed1ab_1    conda-forge
jsonpointer               3.0.0           py311hec3470c_1    conda-forge
jsonschema                4.23.0             pyhd8ed1ab_1    conda-forge
jsonschema-specifications 2024.10.1          pyhd8ed1ab_1    conda-forge
jupyter_core              5.7.2              pyh31011fe_1    conda-forge
keyring                   25.6.0             pyha804496_0    conda-forge
keyutils                  1.6.1                h4e544f5_0    conda-forge
krb5                      1.21.3               h50a48e9_0    conda-forge
ld_impl_linux-aarch64     2.40                 h2d8c526_0    conda-forge
libarchive                3.7.7                h6223a6c_3    conda-forge
libcurl                   8.12.1               h6702fde_0    conda-forge
libedit                   3.1.20191231         he28a2e2_2    conda-forge
libev                     4.33                 h31becfc_2    conda-forge
libexpat                  2.6.4                h5ad3122_0    conda-forge
libffi                    3.4.2                h3557bc0_5    conda-forge
libgcc                    14.2.0               he277a41_1    conda-forge
libgcc-ng                 14.2.0               he9431aa_1    conda-forge
libglib                   2.82.2               hc486b8e_0    conda-forge
libgomp                   14.2.0               he277a41_1    conda-forge
libiconv                  1.17                 h31becfc_2    conda-forge
liblief                   0.14.1               h5ad3122_2    conda-forge
liblzma                   5.6.3                h86ecc28_1    conda-forge
libmamba                  2.0.8                hbe5a9cd_2    conda-forge
libmambapy                2.0.8           py311h38ab675_2    conda-forge
libnghttp2                1.64.0               hc8609a4_0    conda-forge
libnsl                    2.0.1                h31becfc_0    conda-forge
libsolv                   0.7.30               h62756fc_0    conda-forge
libsqlite                 3.47.2               h5eb1b54_0    conda-forge
libssh2                   1.11.1               ha41c0db_0    conda-forge
libstdcxx                 14.2.0               h3f4de04_1    conda-forge
libstdcxx-ng              13.2.0               h9a76618_5    conda-forge
libuuid                   2.38.1               hb4cce97_0    conda-forge
libxcrypt                 4.4.36               h31becfc_1    conda-forge
libxml2                   2.13.5               h2e0c361_1    conda-forge
libzlib                   1.3.1                h86ecc28_2    conda-forge
lz4-c                     1.10.0               h5ad3122_1    conda-forge
lzo                       2.10              h516909a_1000    conda-forge
mamba                     2.0.8                hf3e92e0_2    conda-forge
markupsafe                3.0.2           py311ha09ea12_1    conda-forge
menuinst                  2.2.0           py311hec3470c_0    conda-forge
minio-server              2024.03.10.02.53.48      hcefe29a_0    conda-forge
more-itertools            10.5.0             pyhd8ed1ab_1    conda-forge
msgpack-python            1.1.0           py311hc07b1fb_0    conda-forge
nbformat                  5.10.4             pyhd8ed1ab_1    conda-forge
ncurses                   6.4.20240210         h0425590_0    conda-forge
nlohmann_json             3.11.3               h0a1ffab_1    conda-forge
nodeenv                   1.9.1              pyhd8ed1ab_1    conda-forge
openssl                   3.4.1                hd08dc88_0    conda-forge
packaging                 24.2               pyhd8ed1ab_2    conda-forge
pastel                    0.2.1              pyhd8ed1ab_0    conda-forge
patch                     2.7.6             hf897c2e_1002    conda-forge
patchelf                  0.17.2               h884eca8_0    conda-forge
pcre2                     10.44                h070dd5b_2    conda-forge
perl                      5.32.1          7_h31becfc_perl5    conda-forge
pexpect                   4.9.0              pyhd8ed1ab_1    conda-forge
pip                       24.3.1             pyh8b19718_2    conda-forge
pkginfo                   1.12.0             pyhd8ed1ab_1    conda-forge
pkgutil-resolve-name      1.3.10             pyhd8ed1ab_2    conda-forge
platformdirs              4.3.6              pyhd8ed1ab_1    conda-forge
pluggy                    1.5.0              pyhd8ed1ab_1    conda-forge
pre-commit                4.0.1              pyha770c72_1    conda-forge
psutil                    6.1.1           py311ha879c10_0    conda-forge
ptyprocess                0.7.0              pyhd8ed1ab_1    conda-forge
py-lief                   0.14.1          py311h89d996e_2    conda-forge
py-rattler                0.9.0           py311h4047cc9_0    conda-forge
pybind11-abi              4                    hd8ed1ab_3    conda-forge
pycosat                   0.6.6           py311ha879c10_2    conda-forge
pycparser                 2.22               pyhd8ed1ab_0    conda-forge
pydantic                  2.10.4             pyh3cfb1c2_0    conda-forge
pydantic-core             2.27.2          py311h0ca61a2_0    conda-forge
pylev                     1.4.0              pyhd8ed1ab_0    conda-forge
pysocks                   1.7.1              pyha2e5f31_6    conda-forge
pytest                    8.3.4              pyhd8ed1ab_1    conda-forge
pytest-cov                6.0.0              pyhd8ed1ab_1    conda-forge
pytest-mock               3.14.0             pyhd8ed1ab_1    conda-forge
pytest-rerunfailures      15.0               pyhd8ed1ab_1    conda-forge
pytest-split              0.10.0             pyhff2d567_0    conda-forge
pytest-timeout            2.3.1              pyhd8ed1ab_2    conda-forge
pytest-xprocess           1.0.2              pyhd8ed1ab_1    conda-forge
python                    3.11.5          h43d1f9e_0_cpython    conda-forge
python-dateutil           2.9.0.post0        pyhff2d567_1    conda-forge
python-fastjsonschema     2.21.1             pyhd8ed1ab_0    conda-forge
python-libarchive-c       5.1             py311hec3470c_1    conda-forge
python_abi                3.11                    5_cp311    conda-forge
pytz                      2024.2             pyhd8ed1ab_1    conda-forge
pyyaml                    6.0.2           py311ha879c10_1    conda-forge
readline                  8.2                  h8fc344f_1    conda-forge
referencing               0.35.1             pyhd8ed1ab_1    conda-forge
reproc                    14.2.4.post0         h31becfc_1    conda-forge
reproc-cpp                14.2.4.post0         h2f0025b_1    conda-forge
requests                  2.32.3             pyhd8ed1ab_1    conda-forge
requests-toolbelt         1.0.0              pyhd8ed1ab_1    conda-forge
responses                 0.25.3             pyhd8ed1ab_1    conda-forge
ripgrep                   14.1.1               ha3529ed_0    conda-forge
rpds-py                   0.22.3          py311h7270cec_0    conda-forge
ruamel.yaml               0.18.8          py311ha879c10_0    conda-forge
ruamel.yaml.clib          0.2.8           py311ha879c10_1    conda-forge
s3transfer                0.10.4             pyhd8ed1ab_1    conda-forge
secretstorage             3.3.3           py311hfecb2dc_3    conda-forge
setuptools                75.6.0             pyhff2d567_1    conda-forge
setuptools-scm            8.1.0              pyhd8ed1ab_1    conda-forge
setuptools_scm            8.1.0                hd8ed1ab_1    conda-forge
simdjson                  3.12.2               h17cf362_0    conda-forge
six                       1.17.0             pyhd8ed1ab_0    conda-forge
smmap                     5.0.0              pyhd8ed1ab_0    conda-forge
soupsieve                 2.5                pyhd8ed1ab_1    conda-forge
spdlog                    1.15.2               h7344f28_0    conda-forge
tk                        8.6.12               hd8af866_0    conda-forge
toml                      0.10.2             pyhd8ed1ab_1    conda-forge
tomli                     2.2.1              pyhd8ed1ab_1    conda-forge
tomlkit                   0.13.2             pyha770c72_1    conda-forge
toolz                     0.12.1             pyhd8ed1ab_0    conda-forge
tqdm                      4.67.1             pyhd8ed1ab_1    conda-forge
traitlets                 5.14.3             pyhd8ed1ab_1    conda-forge
truststore                0.8.0              pyhd8ed1ab_0    conda-forge
types-pyyaml              6.0.12.20241230    pyhd8ed1ab_0    conda-forge
typing-extensions         4.12.2               hd8ed1ab_1    conda-forge
typing_extensions         4.12.2             pyha770c72_1    conda-forge
tzdata                    2024a                h0c530f3_0    conda-forge
ukkonen                   1.0.1           py311hc07b1fb_5    conda-forge
urllib3                   1.26.19            pyhd8ed1ab_0    conda-forge
virtualenv                20.28.1            pyhd8ed1ab_0    conda-forge
webencodings              0.5.1              pyhd8ed1ab_3    conda-forge
werkzeug                  3.1.3              pyhd8ed1ab_1    conda-forge
wheel                     0.43.0             pyhd8ed1ab_1    conda-forge
xz                        5.2.6                h9cdd2b7_0    conda-forge
yaml                      0.2.5                hf897c2e_2    conda-forge
yaml-cpp                  0.8.0                h2f0025b_0    conda-forge
zipp                      3.21.0             pyhd8ed1ab_1    conda-forge
zstandard                 0.23.0          py311hd5293d8_1    conda-forge
zstd                      1.5.6                h02f22dd_0    conda-forge

@jaimergp
Copy link
Contributor Author

jaimergp commented Apr 3, 2025

On Windows, the number of slashes does not seem to cause an issue, but unencoded spaces do:

>>> from libmambapy.specs import CondaURL
# Any number of slashes are parsable, but 4 introduces an extra leading slash
>>> CondaURL.parse("file://D:/a/_temp/popen-gw0/some_other_parts").path()
'/D:/a/_temp/popen-gw0/some_other_parts'
>>> CondaURL.parse("file:///D:/a/_temp/popen-gw0/some_other_parts").path()
'/D:/a/_temp/popen-gw0/some_other_parts'
>>> CondaURL.parse("file:////D:/a/_temp/popen-gw0/some_other_parts").path()
'//D:/a/_temp/popen-gw0/some_other_parts'


# Backslashes are ok
>>> CondaURL.parse("file://D:\\a\\_temp\\popen-gw0\\some_other_parts").path()
'/D:\\a\\_temp\\popen-gw0\\some_other_parts'


# Spaces break it, no matter the number of slashes
>>> CondaURL.parse("file://D:/a/_temp/popen-gw0/some_other_parts spaces").path()
Traceback (most recent call last):
  File "<python-input-22>", line 1, in <module>
    CondaURL.parse("file://D:/a/_temp/popen-gw0/some_other_parts spaces").path()
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
libmambapy.bindings.specs.ParseError: Failed to parse URL "file://D:/a/_temp/popen-gw0/some_other_parts spaces": Malformed input to a URL function
>>> CondaURL.parse("file:///D:/a/_temp/popen-gw0/some_other_parts spaces").path()
Traceback (most recent call last):
  File "<python-input-23>", line 1, in <module>
    CondaURL.parse("file:///D:/a/_temp/popen-gw0/some_other_parts spaces").path()
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:///D:/a/_temp/popen-gw0/some_other_parts spaces": Malformed input to a URL function
>>> CondaURL.parse("file:////D:/a/_temp/popen-gw0/some_other_parts spaces").path()
Traceback (most recent call last):
  File "<python-input-24>", line 1, in <module>
    CondaURL.parse("file:////D:/a/_temp/popen-gw0/some_other_parts spaces").path()
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
libmambapy.bindings.specs.ParseError: Failed to parse URL "file:////D:/a/_temp/popen-gw0/some_other_parts spaces": Malformed input to a URL function


# Encoded spaces are ok
>>> CondaURL.parse("file://D:/a/_temp/popen-gw0/some_other_parts%20spaces").path()
'/D:/a/_temp/popen-gw0/some_other_parts spaces'
>>> CondaURL.parse("file:///D:/a/_temp/popen-gw0/some_other_parts%20spaces").path()
'/D:/a/_temp/popen-gw0/some_other_parts spaces'
>>> CondaURL.parse("file:////D:/a/_temp/popen-gw0/some_other_parts%20spaces").path()
'//D:/a/_temp/popen-gw0/some_other_parts spaces'

This platform specific behavior is confusing 🤔

@Hind-M
Copy link
Member

Hind-M commented Apr 3, 2025

This platform specific behavior is confusing 🤔

Yes that was what confused me in the beginning when trying a fix on linux.

So on linux/macos, the error is reproducible indeed (with file:///) but since this issue was referring to windows I figured that it wasn't the offending use case, but I suppose file:/// should be working as well on other platforms.
Regarding spaces, I would say that this should be handled indeed and work even if not encoded.

Any other concerns?

@jaimergp
Copy link
Contributor Author

jaimergp commented Apr 3, 2025

I opened conda/conda-libmamba-solver#640 to handle the percent encode for now. If these are handled internally in libmamba, even better. Agreed that the slash behavior should be consistent across platforms.

@Hind-M
Copy link
Member

Hind-M commented Apr 10, 2025

Thinking more about this, encoding spaces may open the door to other inconsistencies - when the URI contains specific characters that shouldn't be encoded (besides the / and considering that we go with encoding the whole URI). Also, AFAIK an URI with non-encoded spaces is not supposed to be valid?

If we choose to encode spaces only with file URLs, wouldn't that make it inconsistent as well (with regards to other URLs)?
And btw, do you know if this was an issue with mamba v1 as well and if it's a regression, or is it more a general "nice to have" option?

@jaimergp
Copy link
Contributor Author

On one hand, we are already using path_to_url before passing things to mamba, but that function didn't touch spaces for some reason.

In previous versions, we were escaping channel URLs as a norm: https://github.com/conda/conda-libmamba-solver/blob/f7b559f422e9fb416a9c33b5d134fecd15f4262e/conda_libmamba_solver/index.py#L87-L98, but we are not doing that anymore because apparently we didn't need to? It does look like mamba v2 has some logic for percent encoding already, so I'm not sure why this case is not covered.

@Hind-M
Copy link
Member

Hind-M commented Apr 10, 2025

It seems that the percent encoding is used in specific cases, either for setting a package, or setting parts of the url (user, password, etc), or when converting from path to file urls.
So the question is, do we do the same when using CondaURL::parse (meaning encoding only file urls)? In that case, my concern is that it may be inconsistent with other non-encoded urls (not with file schemes)?
In the case of encoding all types of urls, it may lead to encoding characters we don't want to (not known beforehand), and also handle different (too specific) use cases depending on the type?

@jaimergp
Copy link
Contributor Author

(meaning encoding only file urls)

I think the overall rule is to always encode the path component of a URL (excluding /). File URLs are all "path" by design, except for Windows drives. There might be more edge cases, but right now this is inconsistency is a bit "annoying" as a library. It's not clear in the documentation either; that would be the minimum work to close this issue imo: document how %-encoding works internally, add tests to ensure there's no drift, and also document when library users are expected to handle it before passing it over to libmamba.

@Hind-M
Copy link
Member

Hind-M commented Apr 11, 2025

CondaURL::parse is actually relying on libcurl to do the parsing, and libcurl is expecting the url to be encoded.
If we want to encode it beforehand, that would mean to first do some parsing to isolate the path to be encoded, which feels like the snake biting its tail...

I found this in the docs:

The CondaURL.parse method assumes that the URL is properly percent encoded.

but definitely lacking some tests and maybe also some comments in the codebase...

@Hind-M Hind-M linked a pull request Apr 14, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type::bug Something isn't working where::windows Windows-specific issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants