Skip to content

Commit 9db8f7e

Browse files
authored
Switch curl for url parsing (#582)
Fixes #577
1 parent 962d50a commit 9db8f7e

File tree

5 files changed

+33
-63
lines changed

5 files changed

+33
-63
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# httr2 (development version)
22

3+
* `url_parse()` now uses `curl::curl_parse_url()` which is much faster and more correct (#577).
34
* `req_retry()` now defaults to `max_tries = 2` with a message.
45
Set to `max_tries = 1` to disable retries.
56

R/url.R

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,44 +26,20 @@
2626
url_parse <- function(url) {
2727
check_string(url)
2828

29-
# https://datatracker.ietf.org/doc/html/rfc3986#appendix-B
30-
pieces <- parse_match(url, "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?")
31-
32-
scheme <- pieces[[2]]
33-
authority <- pieces[[4]]
34-
path <- pieces[[5]]
35-
query <- pieces[[7]]
36-
if (!is.null(query)) {
37-
query <- query_parse(query)
38-
}
39-
fragment <- pieces[[9]]
40-
41-
# https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
42-
pieces <- parse_match(authority %||% "", "^(([^@]+)@)?([^:]+)?(:([^#]+))?")
43-
44-
userinfo <- pieces[[2]]
45-
if (!is.null(userinfo)) {
46-
userinfo <- parse_in_half(userinfo, ":")
47-
if (userinfo$right == "") {
48-
userinfo$right <- NULL
49-
}
50-
}
51-
hostname <- pieces[[3]]
52-
port <- pieces[[5]]
53-
54-
structure(
55-
list(
56-
scheme = scheme,
57-
hostname = hostname,
58-
username = userinfo$left,
59-
password = userinfo$right,
60-
port = port,
61-
path = path,
62-
query = query,
63-
fragment = fragment
64-
),
65-
class = "httr2_url"
29+
curl <- curl::curl_parse_url(url)
30+
31+
parsed <- list(
32+
scheme = curl$scheme,
33+
hostname = curl$host,
34+
username = curl$user,
35+
password = curl$password,
36+
port = curl$port,
37+
path = curl$path,
38+
query = if (length(curl$params)) as.list(curl$params),
39+
fragment = curl$fragment
6640
)
41+
class(parsed) <- "httr2_url"
42+
parsed
6743
}
6844

6945
url_modify <- function(url, ..., error_call = caller_env()) {

tests/testthat/_snaps/curl.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,48 +40,48 @@
4040
Code
4141
curl_translate("curl http://x.com")
4242
Output
43-
request("http://x.com") |>
43+
request("http://x.com/") |>
4444
req_perform()
4545
Code
4646
curl_translate("curl http://x.com -X DELETE")
4747
Output
48-
request("http://x.com") |>
48+
request("http://x.com/") |>
4949
req_method("DELETE") |>
5050
req_perform()
5151
Code
5252
curl_translate("curl http://x.com -H A:1")
5353
Output
54-
request("http://x.com") |>
54+
request("http://x.com/") |>
5555
req_headers(
5656
A = "1",
5757
) |>
5858
req_perform()
5959
Code
6060
curl_translate("curl http://x.com -H 'A B:1'")
6161
Output
62-
request("http://x.com") |>
62+
request("http://x.com/") |>
6363
req_headers(
6464
`A B` = "1",
6565
) |>
6666
req_perform()
6767
Code
6868
curl_translate("curl http://x.com -u u:p")
6969
Output
70-
request("http://x.com") |>
70+
request("http://x.com/") |>
7171
req_auth_basic("u", "p") |>
7272
req_perform()
7373
Code
7474
curl_translate("curl http://x.com --verbose")
7575
Output
76-
request("http://x.com") |>
76+
request("http://x.com/") |>
7777
req_perform(verbosity = 1)
7878

7979
# can translate query
8080

8181
Code
8282
curl_translate("curl http://x.com?string=abcde&b=2")
8383
Output
84-
request("http://x.com") |>
84+
request("http://x.com/") |>
8585
req_url_query(
8686
string = "abcde",
8787
b = "2",
@@ -93,14 +93,14 @@
9393
Code
9494
curl_translate("curl http://example.com --data abcdef")
9595
Output
96-
request("http://example.com") |>
96+
request("http://example.com/") |>
9797
req_body_raw("abcdef", "application/x-www-form-urlencoded") |>
9898
req_perform()
9999
Code
100100
curl_translate(
101101
"curl http://example.com --data abcdef -H Content-Type:text/plain")
102102
Output
103-
request("http://example.com") |>
103+
request("http://example.com/") |>
104104
req_body_raw("abcdef", "text/plain") |>
105105
req_perform()
106106

@@ -111,7 +111,7 @@
111111
Message
112112
v Copying to clipboard:
113113
Output
114-
request("http://example.com") |>
114+
request("http://example.com/") |>
115115
req_headers(
116116
A = "1",
117117
B = "2",
@@ -120,16 +120,19 @@
120120
Code
121121
clipr::read_clip()
122122
Output
123-
[1] "request(\"http://example.com\") |> " " req_headers("
124-
[3] " A = \"1\"," " B = \"2\","
125-
[5] " ) |> " " req_perform()"
123+
[1] "request(\"http://example.com/\") |> "
124+
[2] " req_headers("
125+
[3] " A = \"1\","
126+
[4] " B = \"2\","
127+
[5] " ) |> "
128+
[6] " req_perform()"
126129

127130
# encode_string2() produces simple strings
128131

129132
Code
130133
curl_translate(cmd)
131134
Output
132-
request("http://example.com") |>
135+
request("http://example.com/") |>
133136
req_method("PATCH") |>
134137
req_body_raw('{"data":{"x":1,"y":"a","nested":{"z":[1,2,3]}}}', "application/json") |>
135138
req_perform()

tests/testthat/test-oauth-flow-auth-code.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ test_that("old args are deprecated", {
7575
expect_snapshot(
7676
redirect <- normalize_redirect_uri("http://localhost", port = 1234)
7777
)
78-
expect_equal(redirect$uri, "http://localhost:1234")
78+
expect_equal(redirect$uri, "http://localhost:1234/")
7979

8080
expect_snapshot(
8181
redirect <- normalize_redirect_uri("http://x.com", host_name = "y.com")
8282
)
83-
expect_equal(redirect$uri, "http://y.com")
83+
expect_equal(redirect$uri, "http://y.com/")
8484

8585
expect_snapshot(
8686
redirect <- normalize_redirect_uri("http://x.com", host_ip = "y.com")

tests/testthat/test-url.R

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
test_that("can parse special cases", {
2-
url <- url_parse("//google.com")
3-
expect_equal(url$scheme, NULL)
4-
expect_equal(url$hostname, "google.com")
5-
62
url <- url_parse("file:///tmp")
73
expect_equal(url$scheme, "file")
84
expect_equal(url$path, "/tmp")
9-
10-
url <- url_parse("/")
11-
expect_equal(url$scheme, NULL)
12-
expect_equal(url$path, "/")
135
})
146

157
test_that("can round trip urls", {
168
urls <- list(
17-
"/",
18-
"//google.com",
199
"file:///",
2010
"http://google.com/",
2111
"http://google.com/path",

0 commit comments

Comments
 (0)