Skip to content

Commit 19a5635

Browse files
author
Jeroen Ooms
committed
Strip query string from local target path, keep it in download URL
1 parent cdf677b commit 19a5635

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
as `<repodir>/<path>/<file>`. See
77
https://github.com/r-universe-org/help/issues/715.
88

9+
* Fixed a bug where a query string in the `File` or `Path` field of a
10+
`PACKAGES` entry (e.g. `foo_1.0.0.tar.gz?sha256=...`) was included
11+
verbatim in the local on-disk cache path. This broke downloads on
12+
Windows, because `?` (and other query string characters) are not
13+
legal in Windows file names. The query string is now only used for
14+
the download URL, and stripped from the local cache path.
15+
916
* New `PKG_USE_BIOCONDUCTOR` environment variable and new
1017
`pkg.use_bioconductor` option to opt out from automatic Bioconductor
1118
support.

R/packages-gz.R

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,34 @@ read_packages_file <- function(
114114
pkgs$type <- if (nrow(pkgs)) type else character()
115115
pkgs$direct <- if (nrow(pkgs)) FALSE else logical()
116116
pkgs$status <- if (nrow(pkgs)) "OK" else character()
117-
pkgs$target <- packages_make_target(
117+
118+
# The 'File' and/or 'Path' fields may contain a query string, e.g.
119+
# `foo_1.0.0.tar.gz?sha256=...&file=`, that some repositories (e.g.
120+
# r-universe) use for routing the download on the server side. We
121+
# need to strip the query part from the `target` which we use as a
122+
# local (on-disk) cache path because `?` is an illegal character for
123+
# Windows file names.
124+
url_target <- packages_make_target(
118125
pkgs$platform,
119126
repodir,
120127
pkgs$package,
121128
pkgs$version,
122129
pkgs[["file"]],
123130
pkgs[["path"]]
124131
)
132+
pkgs$target <- packages_make_target(
133+
pkgs$platform,
134+
repodir,
135+
pkgs$package,
136+
pkgs$version,
137+
packages_strip_query(pkgs[["file"]]),
138+
packages_strip_query(pkgs[["path"]])
139+
)
125140
pkgs$mirror <- if (nrow(pkgs)) mirror else character()
126141
pkgs$sources <- packages_make_sources(
127142
mirror,
128143
pkgs$platform,
129-
pkgs$target,
144+
url_target,
130145
repodir,
131146
pkgs$package,
132147
pkgs$version,
@@ -333,6 +348,18 @@ packages_parse_deps <- function(pkgs) {
333348
parsed
334349
}
335350

351+
## Strips a trailing URL query string (`?...`), if any. Used to sanitize
352+
## 'File'/'Path' field values before they are used to construct a local
353+
## (on-disk) cache path, since query strings may contain characters that
354+
## are illegal in file names on some platforms (e.g. `?` and `&` on
355+
## Windows).
356+
packages_strip_query <- function(x) {
357+
if (is.null(x)) {
358+
return(x)
359+
}
360+
sub("[?].*$", "", x)
361+
}
362+
336363
packages_make_target <- function(
337364
platform,
338365
repodir,

tests/testthat/test-packages-gz.R

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ test_that("packages_make_target", {
6969
)
7070
})
7171

72+
test_that("packages_strip_query", {
73+
expect_equal(packages_strip_query(NULL), NULL)
74+
expect_equal(
75+
packages_strip_query(c("foo_1.0.tar.gz", NA, "")),
76+
c("foo_1.0.tar.gz", NA, "")
77+
)
78+
expect_equal(
79+
packages_strip_query("foo_1.0.tar.gz?sha256=abc&file="),
80+
"foo_1.0.tar.gz"
81+
)
82+
expect_equal(
83+
packages_strip_query(c("foo_1.0.tar.gz?sha256=abc", "bar_2.0.tar.gz")),
84+
c("foo_1.0.tar.gz", "bar_2.0.tar.gz")
85+
)
86+
})
87+
7288
test_that("packages_make_sources", {
7389
expect_equal(
7490
packages_make_sources(
@@ -318,6 +334,41 @@ test_that("read_packages_file combines Path and File fields", {
318334
)
319335
})
320336

337+
test_that("read_packages_file strips query string from target, keeps it in sources", {
338+
# Regression test for a query string in 'Path' (or 'File'), e.g. as used
339+
# by r-universe.
340+
pkgs_file <- test_temp_file()
341+
cat(
342+
"Package: unrtf\n",
343+
"Version: 1.4.9000\n",
344+
"NeedsCompilation: yes\n",
345+
"Path: unrtf_1.4.9000.tar.gz?sha256=362c95be248cce252ba2e1a1386711a67e6f724544701ab4fa1c7693741f59b1&file=\n",
346+
"\n",
347+
file = pkgs_file,
348+
sep = ""
349+
)
350+
351+
data <- read_packages_file(
352+
pkgs_file,
353+
mirror = "https://jeroen.r-universe.dev",
354+
repodir = "src/contrib",
355+
platform = "source",
356+
rversion = "*"
357+
)
358+
359+
expect_false(grepl("[?]", data$pkgs$target))
360+
expect_true(grepl("[?]sha256=", data$pkgs$sources[[1]]))
361+
expect_equal(
362+
data$pkgs$sources[[1]],
363+
paste0(
364+
"https://jeroen.r-universe.dev/src/contrib/",
365+
"unrtf_1.4.9000.tar.gz?sha256=",
366+
"362c95be248cce252ba2e1a1386711a67e6f724544701ab4fa1c7693741f59b1",
367+
"&file=/unrtf_1.4.9000.tar.gz"
368+
)
369+
)
370+
})
371+
321372
test_that("empty PACKAGES file", {
322373
pkgs <- test_temp_file()
323374
data <- read_packages_file(

0 commit comments

Comments
 (0)