Skip to content

Commit b56cc18

Browse files
authored
Merge pull request #14237 from NixOS/url-parser-regression
Remove validation of URLs passed to FileTransferRequest verbatim
2 parents 0f85ef3 + 47f427a commit b56cc18

File tree

7 files changed

+44
-37
lines changed

7 files changed

+44
-37
lines changed

src/libfetchers/tarball.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ DownloadFileResult downloadFile(
4242
if (cached && !cached->expired)
4343
return useCached();
4444

45-
FileTransferRequest request(ValidURL{url});
45+
FileTransferRequest request(VerbatimURL{url});
4646
request.headers = headers;
4747
if (cached)
4848
request.expectedETag = getStrAttr(cached->value, "etag");
@@ -107,13 +107,13 @@ DownloadFileResult downloadFile(
107107
static DownloadTarballResult downloadTarball_(
108108
const Settings & settings, const std::string & urlS, const Headers & headers, const std::string & displayPrefix)
109109
{
110-
ValidURL url = urlS;
110+
ParsedURL url = parseURL(urlS);
111111

112112
// Some friendly error messages for common mistakes.
113113
// Namely lets catch when the url is a local file path, but
114114
// it is not in fact a tarball.
115-
if (url.scheme() == "file") {
116-
std::filesystem::path localPath = renderUrlPathEnsureLegal(url.path());
115+
if (url.scheme == "file") {
116+
std::filesystem::path localPath = renderUrlPathEnsureLegal(url.path);
117117
if (!exists(localPath)) {
118118
throw Error("tarball '%s' does not exist.", localPath);
119119
}
@@ -164,7 +164,7 @@ static DownloadTarballResult downloadTarball_(
164164

165165
/* Note: if the download is cached, `importTarball()` will receive
166166
no data, which causes it to import an empty tarball. */
167-
auto archive = !url.path().empty() && hasSuffix(toLower(url.path().back()), ".zip") ? ({
167+
auto archive = !url.path.empty() && hasSuffix(toLower(url.path.back()), ".zip") ? ({
168168
/* In streaming mode, libarchive doesn't handle
169169
symlinks in zip files correctly (#10649). So write
170170
the entire file to disk so libarchive can access it
@@ -178,7 +178,7 @@ static DownloadTarballResult downloadTarball_(
178178
}
179179
TarArchive{path};
180180
})
181-
: TarArchive{*source};
181+
: TarArchive{*source};
182182
auto tarballCache = getTarballCache();
183183
auto parseSink = tarballCache->getFileSystemObjectSink();
184184
auto lastModified = unpackTarfileToSink(archive, *parseSink);

src/libstore/builtins/fetchurl.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static void builtinFetchurl(const BuiltinBuilderContext & ctx)
3737

3838
auto fetch = [&](const std::string & url) {
3939
auto source = sinkToSource([&](Sink & sink) {
40-
FileTransferRequest request(ValidURL{url});
40+
FileTransferRequest request(VerbatimURL{url});
4141
request.decompress = false;
4242

4343
auto decompressor = makeDecompressionSink(unpack && hasSuffix(mainUrl, ".xz") ? "xz" : "none", sink);

src/libstore/include/nix/store/filetransfer.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ struct UsernameAuth
9595

9696
struct FileTransferRequest
9797
{
98-
ValidURL uri;
98+
VerbatimURL uri;
9999
Headers headers;
100100
std::string expectedETag;
101101
bool verifyTLS = true;
@@ -121,7 +121,7 @@ struct FileTransferRequest
121121
std::optional<std::string> preResolvedAwsSessionToken;
122122
#endif
123123

124-
FileTransferRequest(ValidURL uri)
124+
FileTransferRequest(VerbatimURL uri)
125125
: uri(std::move(uri))
126126
, parentAct(getCurActivity())
127127
{

src/libutil/include/nix/util/url.hh

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
#include "nix/util/error.hh"
88
#include "nix/util/canon-path.hh"
9+
#include "nix/util/split.hh"
10+
#include "nix/util/util.hh"
11+
#include "nix/util/variant-wrapper.hh"
912

1013
namespace nix {
1114

@@ -342,8 +345,7 @@ ParsedURL fixGitURL(const std::string & url);
342345
bool isValidSchemeName(std::string_view scheme);
343346

344347
/**
345-
* Either a ParsedURL or a verbatim string, but the string must be a valid
346-
* ParsedURL. This is necessary because in certain cases URI must be passed
348+
* Either a ParsedURL or a verbatim string. This is necessary because in certain cases URI must be passed
347349
* verbatim (e.g. in builtin fetchers), since those are specified by the user.
348350
* In those cases normalizations performed by the ParsedURL might be surprising
349351
* and undesirable, since Nix must be a universal client that has to work with
@@ -354,23 +356,23 @@ bool isValidSchemeName(std::string_view scheme);
354356
*
355357
* Though we perform parsing and validation for internal needs.
356358
*/
357-
struct ValidURL : private ParsedURL
359+
struct VerbatimURL
358360
{
359-
std::optional<std::string> encoded;
361+
using Raw = std::variant<std::string, ParsedURL>;
362+
Raw raw;
360363

361-
ValidURL(std::string str)
362-
: ParsedURL(parseURL(str, /*lenient=*/false))
363-
, encoded(std::move(str))
364+
VerbatimURL(std::string_view s)
365+
: raw(std::string{s})
364366
{
365367
}
366368

367-
ValidURL(std::string_view str)
368-
: ValidURL(std::string{str})
369+
VerbatimURL(std::string s)
370+
: raw(std::move(s))
369371
{
370372
}
371373

372-
ValidURL(ParsedURL parsed)
373-
: ParsedURL{std::move(parsed)}
374+
VerbatimURL(ParsedURL url)
375+
: raw(std::move(url))
374376
{
375377
}
376378

@@ -379,25 +381,35 @@ struct ValidURL : private ParsedURL
379381
*/
380382
std::string to_string() const
381383
{
382-
return encoded.or_else([&]() -> std::optional<std::string> { return ParsedURL::to_string(); }).value();
384+
return std::visit(
385+
overloaded{
386+
[](const std::string & str) { return str; }, [](const ParsedURL & url) { return url.to_string(); }},
387+
raw);
383388
}
384389

385-
const ParsedURL & parsed() const &
390+
const ParsedURL parsed() const
386391
{
387-
return *this;
392+
return std::visit(
393+
overloaded{
394+
[](const std::string & str) { return parseURL(str); }, [](const ParsedURL & url) { return url; }},
395+
raw);
388396
}
389397

390398
std::string_view scheme() const &
391399
{
392-
return ParsedURL::scheme;
393-
}
394-
395-
const auto & path() const &
396-
{
397-
return ParsedURL::path;
400+
return std::visit(
401+
overloaded{
402+
[](std::string_view str) {
403+
auto scheme = splitPrefixTo(str, ':');
404+
if (!scheme)
405+
throw BadURL("URL '%s' doesn't have a scheme", str);
406+
return *scheme;
407+
},
408+
[](const ParsedURL & url) -> std::string_view { return url.scheme; }},
409+
raw);
398410
}
399411
};
400412

401-
std::ostream & operator<<(std::ostream & os, const ValidURL & url);
413+
std::ostream & operator<<(std::ostream & os, const VerbatimURL & url);
402414

403415
} // namespace nix

src/libutil/url.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ bool isValidSchemeName(std::string_view s)
434434
return std::regex_match(s.begin(), s.end(), regex, std::regex_constants::match_default);
435435
}
436436

437-
std::ostream & operator<<(std::ostream & os, const ValidURL & url)
437+
std::ostream & operator<<(std::ostream & os, const VerbatimURL & url)
438438
{
439439
os << url.to_string();
440440
return os;

src/nix/prefetch.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ std::tuple<StorePath, Hash> prefetchFile(
105105

106106
FdSink sink(fd.get());
107107

108-
FileTransferRequest req(ValidURL{url});
108+
FileTransferRequest req(VerbatimURL{url});
109109
req.decompress = false;
110110
getFileTransfer()->download(std::move(req), sink);
111111
}

tests/functional/fetchurl.sh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,3 @@ requireDaemonNewerThan "2.20"
8888
expected=100
8989
if [[ -v NIX_DAEMON_PACKAGE ]]; then expected=1; fi # work around the daemon not returning a 100 status correctly
9090
expectStderr $expected nix-build --expr '{ url }: builtins.derivation { name = "nix-cache-info"; system = "x86_64-linux"; builder = "builtin:fetchurl"; inherit url; outputHashMode = "flat"; }' --argstr url "file://$narxz" 2>&1 | grep 'must be a fixed-output or impure derivation'
91-
92-
requireDaemonNewerThan "2.32.0pre20250831"
93-
94-
expect 1 nix-build --expr 'import <nix/fetchurl.nix>' --argstr name 'name' --argstr url "file://authority.not.allowed/fetchurl.sh?a=1&a=2" --no-out-link |&
95-
grepQuiet "error: file:// URL 'file://authority.not.allowed/fetchurl.sh?a=1&a=2' has unexpected authority 'authority.not.allowed'"

0 commit comments

Comments
 (0)