From cb2b19701638a6b25d31d48a1ed9b2bf0fa2372d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Wed, 18 Feb 2026 09:51:39 +0100 Subject: [PATCH 01/25] PPM SSO Ported https://github.com/r-lib/keyring/pull/178 --- DESCRIPTION | 3 + R/ppm-sso.R | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 R/ppm-sso.R diff --git a/DESCRIPTION b/DESCRIPTION index 9ac266a51b..994d5d09e0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -74,6 +74,9 @@ BugReports: https://github.com/r-lib/pak/issues Depends: R (>= 3.5) Imports: + RcppTOML, + openssl, + httr2, tools, utils Suggests: diff --git a/R/ppm-sso.R b/R/ppm-sso.R new file mode 100644 index 0000000000..56b65d0a79 --- /dev/null +++ b/R/ppm-sso.R @@ -0,0 +1,288 @@ +ppm_sso_data <- new.env(parent = emptyenv()) +ppm_sso_data$name <- "ppm" +ppm_sso_data$viable <- FALSE + +ppm_sso_init <- function(url = NULL) { + url <- url %||% Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) + if (!is_string(url)) { + stop( + "Please set the PACKAGEMANAGER_ADDRESS environment variable to ", + "the URL of your RStudio Package Manager instance." + ) + } + + parsed_url <- regmatches( + url, + regexec("^(?:https?://)?([^/]+)", url) + )[[1]] + if (length(parsed_url) < 2) { + stop("Invalid Package Manager URL: ", url) + } + + ppm_sso_data$ppm_url <- url + ppm_sso_data$service_name <- parsed_url[2] + ppm_sso_data$token_file_path <- file.path( + path.expand("~"), + ".ppm", + "tokens.toml" + ) + ppm_sso_data$viable <- TRUE +} + +ppm_sso_login <- function(service) { + if (!ppm_sso_data$viable) { + ppm_sso_init() + } + + if (!ppm_are_requirements_valid(service)) { + stop( + "Package Manager SSO is not properly configured. Please ensure that ", + "the PACKAGEMANAGER_ADDRESS environment variable is set to the URL of ", + "your Posit Package Manager instance." + ) + } + + existing_token <- ppm_sso_get_existing_token() + if (!is.null(existing_token) && ppm_sso_can_authenticate(existing_token)) { + return(existing_token) + } + + identity_token <- ppm_sso_get_identity_token_from_file() %||% + ppm_sso_device_flow() + ppm_token <- ppm_sso_identity_to_ppm_token(identity_token) + ppm_sso_write_token_to_file(ppm_token) + + ppm_token +} + +ppm_are_requirements_valid <- function(service) { + is_string(ppm_sso_data$ppm_url) && startsWith(service, ppm_sso_data$ppm_url) +} + +ppm_sso_get_existing_token <- function() { + if (!file.exists(ppm_sso_data$token_file_path)) { + return(NULL) + } + tryCatch( + { + tokens_data <- RcppTOML::parseTOML(ppm_sso_data$token_file_path) + for (conn in tokens_data$connection) { + if (identical(conn$url, ppm_sso_data$ppm_url)) { + return(conn$token) + } + } + }, + error = function(e) { + NULL + } + ) +} + +ppm_sso_can_authenticate <- function(token) { + req <- httr2::request(ppm_sso_data$ppm_url) |> + httr2::req_auth_bearer_token(token) |> + httr2::req_error(is_error = function(resp) FALSE) # Handle errors manually + + resp <- httr2::req_perform(req) + + status <- httr2::resp_status(resp) + status < 500 && status != 401 && status != 403 +} + +ppm_sso_get_identity_token_from_file <- function() { + token_file <- Sys.getenv("PACKAGEMANAGER_IDENTITY_TOKEN_FILE", unset = NA) + if (is.na(token_file)) { + return(NULL) + } + + tryCatch( + { + trimws(readLines(token_file, n = 1, warn = FALSE)) + }, + error = function(e) { + NULL + } + ) +} + +ppm_sso_device_flow <- function() { + verifier <- ppm_sso_new_pkce_verifier() + challenge <- ppm_sso_new_pkce_challenge(verifier) + + # 1. Initiate Device Auth + init_url <- paste0(ppm_sso_data$ppm_url, "/__api__/device") + payload <- list( + code_challenge_method = "S256", + code_challenge = challenge + ) + init_resp_body <- httr2::request(init_url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_perform() |> + httr2::resp_body_json() + + display_uri <- init_resp_body$verification_uri_complete %||% + init_resp_body$verification_uri + if (is.null(display_uri)) { + stop("No verification URI found in device auth response.") + } + + message("\nPlease open the following URL in your browser:") + message(paste(" ", display_uri)) + message("\nAnd enter the following code when prompted:") + message(paste(" ", init_resp_body$user_code)) + message("\nWaiting for authorization...") + + try(utils::browseURL(display_uri), silent = TRUE) + + # 2. Poll for token + token_resp_body <- ppm_sso_complete_device_auth( + init_resp_body$device_code, + verifier, + init_resp_body$interval %||% 5, + init_resp_body$expires_in %||% 300 + ) + + if (is.null(token_resp_body) || is.null(token_resp_body$id_token)) { + stop("Failed to complete device authorization or obtain identity token.") + } + + token_resp_body$id_token +} + +ppm_sso_identity_to_ppm_token <- function(identity_token) { + url <- paste0(ppm_sso_data$ppm_url, "/__api__/token") + payload <- list( + grant_type = "urn:ietf:params:oauth:grant-type:token-exchange", + subject_token = identity_token, + subject_token_type = "urn:ietf:params:oauth:token-type:id_token" + ) + + resp <- httr2::request(url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_perform() + + token_data <- httr2::resp_body_json(resp) + if (is.null(token_data$access_token)) { + stop("Failed to exchange identity token for PPM token.") + } + + token_data$access_token +} + +ppm_sso_write_token_to_file <- function(token) { + dir.create( + dirname(ppm_sso_data$token_file_path), + showWarnings = FALSE, + recursive = TRUE + ) + + new_connection <- list( + url = ppm_sso_data$ppm_url, + token = token, + method = "sso" + ) + + existing_data <- if (file.exists(ppm_sso_data$token_file_path)) { + tryCatch( + RcppTOML::parseTOML(ppm_sso_data$token_file_path), + error = function(e) { + list(connection = list()) + } + ) + } else { + list(connection = list()) + } + + # Find and update existing entry or add a new one + found <- FALSE + if ( + !is.null(existing_data$connection) && length(existing_data$connection) > 0 + ) { + for (i in seq_along(existing_data$connection)) { + if (identical(existing_data$connection[[i]]$url, ppm_sso_data$ppm_url)) { + existing_data$connection[[i]] <- new_connection + found <- TRUE + break + } + } + } + + if (!found) { + existing_data$connection <- c( + existing_data$connection, + list(new_connection) + ) + } + + # Manually construct TOML output + output_lines <- c() + for (conn in existing_data$connection) { + output_lines <- c( + output_lines, + "[[connection]]", + paste0("url = \"", conn$url, "\""), + paste0("token = \"", conn$token, "\""), + paste0("method = \"", conn$method, "\""), + "" + ) + } + writeLines(output_lines, ppm_sso_data$token_file_path) +} + +ppm_sso_base64url_encode <- function(x) { + encoded <- openssl::base64_encode(x) + # Make it URL-safe + gsub("\\+", "-", gsub("\\/", "_", gsub("=+$", "", encoded))) +} + +ppm_sso_new_pkce_verifier <- function() { + ppm_sso_base64url_encode(openssl::rand_bytes(32)) +} + +ppm_sso_new_pkce_challenge <- function(verifier) { + hash <- openssl::sha256(charToRaw(verifier)) + ppm_sso_base64url_encode(hash) +} + +ppm_sso_complete_device_auth = function( + device_code, + verifier, + interval, + expires_in +) { + url <- paste0(ppm_sso_data$ppm_url, "/__api__/device_access") + start_time <- Sys.time() + payload <- list( + device_code = device_code, + code_verifier = verifier + ) + + while (as.numeric(Sys.time() - start_time) < expires_in) { + resp <- httr2::request(url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_error(is_error = \(resp) FALSE) |> # Handle errors manually + httr2::req_perform() + + status <- httr2::resp_status(resp) + + if (status == 200) { + return(httr2::resp_body_json(resp)) + } else if (status == 400) { + error_data <- httr2::resp_body_json(resp) + error_code <- error_data$error + if (error_code == "access_denied") { + stop("Access denied by user.") + } + if (error_code == "expired_token") { + stop("Device authorization request expired.") + } + # For "authorization_pending" or "slow_down", just wait and retry. + } else { + httr2::resp_raise_for_status(resp) # Raise for other unexpected errors + } + + Sys.sleep(interval) + } + + stop("Device authorization timed out.") +} From 793d1cf46e36a6aa37400ce58631fe1026eac7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 11:44:42 +0200 Subject: [PATCH 02/25] Suggest http2, RcppTOML, openssl No hard dependencies for pak. --- DESCRIPTION | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 330d22f6b1..60c18b7823 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -74,9 +74,6 @@ BugReports: https://github.com/r-lib/pak/issues Depends: R (>= 3.5) Imports: - RcppTOML, - openssl, - httr2, tools, utils Suggests: @@ -88,8 +85,10 @@ Suggests: filelock (>= 1.0.2), gitcreds, glue (>= 1.6.2), + httr2, jsonlite (>= 1.8.0), keyring (>= 1.4.0), + openssl, pingr, pkgbuild (>= 1.4.2), pkgcache (>= 2.2.4), @@ -98,6 +97,7 @@ Suggests: pkgsearch (>= 3.1.0), processx (>= 3.8.1), ps (>= 1.6.0), + RcppTOML, rstudioapi, testthat (>= 3.2.0), webfakes, From 12a0e4802bacc265ad491cc0c97d8f13d83e7f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 11:45:57 +0200 Subject: [PATCH 03/25] Default service to PPM URL --- R/ppm-sso.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/ppm-sso.R b/R/ppm-sso.R index 56b65d0a79..a10f9dfbba 100644 --- a/R/ppm-sso.R +++ b/R/ppm-sso.R @@ -29,7 +29,10 @@ ppm_sso_init <- function(url = NULL) { ppm_sso_data$viable <- TRUE } -ppm_sso_login <- function(service) { +ppm_sso_login <- function(service = NULL) { + service <- service %||% + ppm_sso_data$ppm_url %||% + Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) if (!ppm_sso_data$viable) { ppm_sso_init() } From d6d4ddee29ce7c16a73f46630927f51937901c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 11:46:53 +0200 Subject: [PATCH 04/25] Fix httr2 function name --- R/ppm-sso.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/ppm-sso.R b/R/ppm-sso.R index a10f9dfbba..e1f3d8dec3 100644 --- a/R/ppm-sso.R +++ b/R/ppm-sso.R @@ -281,7 +281,7 @@ ppm_sso_complete_device_auth = function( } # For "authorization_pending" or "slow_down", just wait and retry. } else { - httr2::resp_raise_for_status(resp) # Raise for other unexpected errors + httr2::resp_check_status(resp) } Sys.sleep(interval) From c121a42394408cf7b76d51cccdd9ab1e08aa083c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 11:47:05 +0200 Subject: [PATCH 05/25] Add webfakes app for SSO --- R/ppm-sso.R | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/R/ppm-sso.R b/R/ppm-sso.R index e1f3d8dec3..ac73bb1bea 100644 --- a/R/ppm-sso.R +++ b/R/ppm-sso.R @@ -289,3 +289,158 @@ ppm_sso_complete_device_auth = function( stop("Device authorization timed out.") } + +# nocov start + +# Fake PPM server that proxies to Auth0, for testing ppm_sso_device_flow(). +# Auth0 device flow does not use PKCE, so we verify the PKCE challenge +# locally and forward only the device_code to Auth0's /oauth/token. +ppm_sso_fake_app <- function( + auth0_domain, + client_id, + audience = NULL, + scope = "openid profile email" +) { + app <- webfakes::new_app() + + app$use("logger" = webfakes::mw_log()) + app$use("urlencoded body parser" = webfakes::mw_urlencoded()) + app$use("json body parser" = webfakes::mw_json()) + + app$locals$challenges <- new.env(parent = emptyenv()) + app$locals$auth0_domain <- auth0_domain + app$locals$client_id <- client_id + app$locals$audience <- audience + app$locals$scope <- scope + + # Bearer-token check used by ppm_sso_can_authenticate(): any token passes. + app$get("/", function(req, res) { + res$set_status(200L)$send("ok") + }) + + app$post("/__api__/device", function(req, res) { + challenge <- req$form$code_challenge + method <- req$form$code_challenge_method %||% "S256" + if (!identical(method, "S256")) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_challenge_method") + )) + } + + payload <- list( + client_id = app$locals$client_id, + scope = app$locals$scope + ) + if (!is.null(app$locals$audience)) { + payload$audience <- app$locals$audience + } + + upstream <- httr2::request( + paste0("https://", app$locals$auth0_domain, "/oauth/device/code") + ) |> + httr2::req_body_form(!!!payload) |> + httr2::req_error(is_error = function(r) FALSE) |> + httr2::req_perform() + + body <- httr2::resp_body_json(upstream) + if (httr2::resp_status(upstream) >= 400L) { + return(res$set_status(httr2::resp_status(upstream))$send_json( + auto_unbox = TRUE, + body + )) + } + + assign(body$device_code, challenge, envir = app$locals$challenges) + + res$send_json( + auto_unbox = TRUE, + list( + device_code = body$device_code, + user_code = body$user_code, + verification_uri = body$verification_uri, + verification_uri_complete = body$verification_uri_complete, + expires_in = body$expires_in, + interval = body$interval %||% 5L + ) + ) + }) + + app$post("/__api__/device_access", function(req, res) { + device_code <- req$form$device_code + verifier <- req$form$code_verifier + + if (!exists(device_code, envir = app$locals$challenges, inherits = FALSE)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "expired_token") + )) + } + expected <- get( + device_code, + envir = app$locals$challenges, + inherits = FALSE + ) + actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) + if (!identical(expected, actual)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "invalid_grant") + )) + } + + upstream <- httr2::request( + paste0("https://", app$locals$auth0_domain, "/oauth/token") + ) |> + httr2::req_body_form( + grant_type = "urn:ietf:params:oauth:grant-type:device_code", + device_code = device_code, + client_id = app$locals$client_id + ) |> + httr2::req_error(is_error = function(r) FALSE) |> + httr2::req_perform() + + body <- httr2::resp_body_json(upstream) + if (httr2::resp_status(upstream) == 200L) { + rm(list = device_code, envir = app$locals$challenges) + return(res$send_json( + auto_unbox = TRUE, + list(id_token = body$id_token) + )) + } + + # Auth0 returns 403 for authorization_pending / slow_down; the PPM client + # only treats 400 as a soft pending state, so translate the status. + res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = body$error %||% "unknown_error") + ) + }) + + # Trivial token exchange: echo subject_token back as access_token. + app$post("/__api__/token", function(req, res) { + if ( + !identical( + req$form$grant_type, + "urn:ietf:params:oauth:grant-type:token-exchange" + ) + ) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_grant_type") + )) + } + res$send_json( + auto_unbox = TRUE, + list( + access_token = req$form$subject_token, + token_type = "Bearer", + issued_token_type = "urn:ietf:params:oauth:token-type:access_token" + ) + ) + }) + + app +} + +# nocov end From 094a3021f2cab79b0171f0cb0f782d0edb92195c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 13:27:22 +0200 Subject: [PATCH 06/25] Remove PPM-SSO code, it lives in pkgcache now --- DESCRIPTION | 3 - R/ppm-sso.R | 446 ---------------------------------------------------- 2 files changed, 449 deletions(-) delete mode 100644 R/ppm-sso.R diff --git a/DESCRIPTION b/DESCRIPTION index 60c18b7823..8d4d6468d5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -85,10 +85,8 @@ Suggests: filelock (>= 1.0.2), gitcreds, glue (>= 1.6.2), - httr2, jsonlite (>= 1.8.0), keyring (>= 1.4.0), - openssl, pingr, pkgbuild (>= 1.4.2), pkgcache (>= 2.2.4), @@ -97,7 +95,6 @@ Suggests: pkgsearch (>= 3.1.0), processx (>= 3.8.1), ps (>= 1.6.0), - RcppTOML, rstudioapi, testthat (>= 3.2.0), webfakes, diff --git a/R/ppm-sso.R b/R/ppm-sso.R deleted file mode 100644 index ac73bb1bea..0000000000 --- a/R/ppm-sso.R +++ /dev/null @@ -1,446 +0,0 @@ -ppm_sso_data <- new.env(parent = emptyenv()) -ppm_sso_data$name <- "ppm" -ppm_sso_data$viable <- FALSE - -ppm_sso_init <- function(url = NULL) { - url <- url %||% Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) - if (!is_string(url)) { - stop( - "Please set the PACKAGEMANAGER_ADDRESS environment variable to ", - "the URL of your RStudio Package Manager instance." - ) - } - - parsed_url <- regmatches( - url, - regexec("^(?:https?://)?([^/]+)", url) - )[[1]] - if (length(parsed_url) < 2) { - stop("Invalid Package Manager URL: ", url) - } - - ppm_sso_data$ppm_url <- url - ppm_sso_data$service_name <- parsed_url[2] - ppm_sso_data$token_file_path <- file.path( - path.expand("~"), - ".ppm", - "tokens.toml" - ) - ppm_sso_data$viable <- TRUE -} - -ppm_sso_login <- function(service = NULL) { - service <- service %||% - ppm_sso_data$ppm_url %||% - Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) - if (!ppm_sso_data$viable) { - ppm_sso_init() - } - - if (!ppm_are_requirements_valid(service)) { - stop( - "Package Manager SSO is not properly configured. Please ensure that ", - "the PACKAGEMANAGER_ADDRESS environment variable is set to the URL of ", - "your Posit Package Manager instance." - ) - } - - existing_token <- ppm_sso_get_existing_token() - if (!is.null(existing_token) && ppm_sso_can_authenticate(existing_token)) { - return(existing_token) - } - - identity_token <- ppm_sso_get_identity_token_from_file() %||% - ppm_sso_device_flow() - ppm_token <- ppm_sso_identity_to_ppm_token(identity_token) - ppm_sso_write_token_to_file(ppm_token) - - ppm_token -} - -ppm_are_requirements_valid <- function(service) { - is_string(ppm_sso_data$ppm_url) && startsWith(service, ppm_sso_data$ppm_url) -} - -ppm_sso_get_existing_token <- function() { - if (!file.exists(ppm_sso_data$token_file_path)) { - return(NULL) - } - tryCatch( - { - tokens_data <- RcppTOML::parseTOML(ppm_sso_data$token_file_path) - for (conn in tokens_data$connection) { - if (identical(conn$url, ppm_sso_data$ppm_url)) { - return(conn$token) - } - } - }, - error = function(e) { - NULL - } - ) -} - -ppm_sso_can_authenticate <- function(token) { - req <- httr2::request(ppm_sso_data$ppm_url) |> - httr2::req_auth_bearer_token(token) |> - httr2::req_error(is_error = function(resp) FALSE) # Handle errors manually - - resp <- httr2::req_perform(req) - - status <- httr2::resp_status(resp) - status < 500 && status != 401 && status != 403 -} - -ppm_sso_get_identity_token_from_file <- function() { - token_file <- Sys.getenv("PACKAGEMANAGER_IDENTITY_TOKEN_FILE", unset = NA) - if (is.na(token_file)) { - return(NULL) - } - - tryCatch( - { - trimws(readLines(token_file, n = 1, warn = FALSE)) - }, - error = function(e) { - NULL - } - ) -} - -ppm_sso_device_flow <- function() { - verifier <- ppm_sso_new_pkce_verifier() - challenge <- ppm_sso_new_pkce_challenge(verifier) - - # 1. Initiate Device Auth - init_url <- paste0(ppm_sso_data$ppm_url, "/__api__/device") - payload <- list( - code_challenge_method = "S256", - code_challenge = challenge - ) - init_resp_body <- httr2::request(init_url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_perform() |> - httr2::resp_body_json() - - display_uri <- init_resp_body$verification_uri_complete %||% - init_resp_body$verification_uri - if (is.null(display_uri)) { - stop("No verification URI found in device auth response.") - } - - message("\nPlease open the following URL in your browser:") - message(paste(" ", display_uri)) - message("\nAnd enter the following code when prompted:") - message(paste(" ", init_resp_body$user_code)) - message("\nWaiting for authorization...") - - try(utils::browseURL(display_uri), silent = TRUE) - - # 2. Poll for token - token_resp_body <- ppm_sso_complete_device_auth( - init_resp_body$device_code, - verifier, - init_resp_body$interval %||% 5, - init_resp_body$expires_in %||% 300 - ) - - if (is.null(token_resp_body) || is.null(token_resp_body$id_token)) { - stop("Failed to complete device authorization or obtain identity token.") - } - - token_resp_body$id_token -} - -ppm_sso_identity_to_ppm_token <- function(identity_token) { - url <- paste0(ppm_sso_data$ppm_url, "/__api__/token") - payload <- list( - grant_type = "urn:ietf:params:oauth:grant-type:token-exchange", - subject_token = identity_token, - subject_token_type = "urn:ietf:params:oauth:token-type:id_token" - ) - - resp <- httr2::request(url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_perform() - - token_data <- httr2::resp_body_json(resp) - if (is.null(token_data$access_token)) { - stop("Failed to exchange identity token for PPM token.") - } - - token_data$access_token -} - -ppm_sso_write_token_to_file <- function(token) { - dir.create( - dirname(ppm_sso_data$token_file_path), - showWarnings = FALSE, - recursive = TRUE - ) - - new_connection <- list( - url = ppm_sso_data$ppm_url, - token = token, - method = "sso" - ) - - existing_data <- if (file.exists(ppm_sso_data$token_file_path)) { - tryCatch( - RcppTOML::parseTOML(ppm_sso_data$token_file_path), - error = function(e) { - list(connection = list()) - } - ) - } else { - list(connection = list()) - } - - # Find and update existing entry or add a new one - found <- FALSE - if ( - !is.null(existing_data$connection) && length(existing_data$connection) > 0 - ) { - for (i in seq_along(existing_data$connection)) { - if (identical(existing_data$connection[[i]]$url, ppm_sso_data$ppm_url)) { - existing_data$connection[[i]] <- new_connection - found <- TRUE - break - } - } - } - - if (!found) { - existing_data$connection <- c( - existing_data$connection, - list(new_connection) - ) - } - - # Manually construct TOML output - output_lines <- c() - for (conn in existing_data$connection) { - output_lines <- c( - output_lines, - "[[connection]]", - paste0("url = \"", conn$url, "\""), - paste0("token = \"", conn$token, "\""), - paste0("method = \"", conn$method, "\""), - "" - ) - } - writeLines(output_lines, ppm_sso_data$token_file_path) -} - -ppm_sso_base64url_encode <- function(x) { - encoded <- openssl::base64_encode(x) - # Make it URL-safe - gsub("\\+", "-", gsub("\\/", "_", gsub("=+$", "", encoded))) -} - -ppm_sso_new_pkce_verifier <- function() { - ppm_sso_base64url_encode(openssl::rand_bytes(32)) -} - -ppm_sso_new_pkce_challenge <- function(verifier) { - hash <- openssl::sha256(charToRaw(verifier)) - ppm_sso_base64url_encode(hash) -} - -ppm_sso_complete_device_auth = function( - device_code, - verifier, - interval, - expires_in -) { - url <- paste0(ppm_sso_data$ppm_url, "/__api__/device_access") - start_time <- Sys.time() - payload <- list( - device_code = device_code, - code_verifier = verifier - ) - - while (as.numeric(Sys.time() - start_time) < expires_in) { - resp <- httr2::request(url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_error(is_error = \(resp) FALSE) |> # Handle errors manually - httr2::req_perform() - - status <- httr2::resp_status(resp) - - if (status == 200) { - return(httr2::resp_body_json(resp)) - } else if (status == 400) { - error_data <- httr2::resp_body_json(resp) - error_code <- error_data$error - if (error_code == "access_denied") { - stop("Access denied by user.") - } - if (error_code == "expired_token") { - stop("Device authorization request expired.") - } - # For "authorization_pending" or "slow_down", just wait and retry. - } else { - httr2::resp_check_status(resp) - } - - Sys.sleep(interval) - } - - stop("Device authorization timed out.") -} - -# nocov start - -# Fake PPM server that proxies to Auth0, for testing ppm_sso_device_flow(). -# Auth0 device flow does not use PKCE, so we verify the PKCE challenge -# locally and forward only the device_code to Auth0's /oauth/token. -ppm_sso_fake_app <- function( - auth0_domain, - client_id, - audience = NULL, - scope = "openid profile email" -) { - app <- webfakes::new_app() - - app$use("logger" = webfakes::mw_log()) - app$use("urlencoded body parser" = webfakes::mw_urlencoded()) - app$use("json body parser" = webfakes::mw_json()) - - app$locals$challenges <- new.env(parent = emptyenv()) - app$locals$auth0_domain <- auth0_domain - app$locals$client_id <- client_id - app$locals$audience <- audience - app$locals$scope <- scope - - # Bearer-token check used by ppm_sso_can_authenticate(): any token passes. - app$get("/", function(req, res) { - res$set_status(200L)$send("ok") - }) - - app$post("/__api__/device", function(req, res) { - challenge <- req$form$code_challenge - method <- req$form$code_challenge_method %||% "S256" - if (!identical(method, "S256")) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "unsupported_challenge_method") - )) - } - - payload <- list( - client_id = app$locals$client_id, - scope = app$locals$scope - ) - if (!is.null(app$locals$audience)) { - payload$audience <- app$locals$audience - } - - upstream <- httr2::request( - paste0("https://", app$locals$auth0_domain, "/oauth/device/code") - ) |> - httr2::req_body_form(!!!payload) |> - httr2::req_error(is_error = function(r) FALSE) |> - httr2::req_perform() - - body <- httr2::resp_body_json(upstream) - if (httr2::resp_status(upstream) >= 400L) { - return(res$set_status(httr2::resp_status(upstream))$send_json( - auto_unbox = TRUE, - body - )) - } - - assign(body$device_code, challenge, envir = app$locals$challenges) - - res$send_json( - auto_unbox = TRUE, - list( - device_code = body$device_code, - user_code = body$user_code, - verification_uri = body$verification_uri, - verification_uri_complete = body$verification_uri_complete, - expires_in = body$expires_in, - interval = body$interval %||% 5L - ) - ) - }) - - app$post("/__api__/device_access", function(req, res) { - device_code <- req$form$device_code - verifier <- req$form$code_verifier - - if (!exists(device_code, envir = app$locals$challenges, inherits = FALSE)) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "expired_token") - )) - } - expected <- get( - device_code, - envir = app$locals$challenges, - inherits = FALSE - ) - actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) - if (!identical(expected, actual)) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "invalid_grant") - )) - } - - upstream <- httr2::request( - paste0("https://", app$locals$auth0_domain, "/oauth/token") - ) |> - httr2::req_body_form( - grant_type = "urn:ietf:params:oauth:grant-type:device_code", - device_code = device_code, - client_id = app$locals$client_id - ) |> - httr2::req_error(is_error = function(r) FALSE) |> - httr2::req_perform() - - body <- httr2::resp_body_json(upstream) - if (httr2::resp_status(upstream) == 200L) { - rm(list = device_code, envir = app$locals$challenges) - return(res$send_json( - auto_unbox = TRUE, - list(id_token = body$id_token) - )) - } - - # Auth0 returns 403 for authorization_pending / slow_down; the PPM client - # only treats 400 as a soft pending state, so translate the status. - res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = body$error %||% "unknown_error") - ) - }) - - # Trivial token exchange: echo subject_token back as access_token. - app$post("/__api__/token", function(req, res) { - if ( - !identical( - req$form$grant_type, - "urn:ietf:params:oauth:grant-type:token-exchange" - ) - ) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "unsupported_grant_type") - )) - } - res$send_json( - auto_unbox = TRUE, - list( - access_token = req$form$subject_token, - token_type = "Bearer", - issued_token_type = "urn:ietf:params:oauth:token-type:access_token" - ) - ) - }) - - app -} - -# nocov end From c73e66f0f1ab837e9a6ee31ee47b588e0cd9645d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 13:39:28 +0200 Subject: [PATCH 07/25] Update embedded pkgcache for PPM SSO --- R/embed.R | 14 +- src/library/pkgcache/DESCRIPTION | 10 +- src/library/pkgcache/R/auth.R | 43 ++- src/library/pkgcache/R/ppm-sso.R | 446 +++++++++++++++++++++++++++++++ 4 files changed, 500 insertions(+), 13 deletions(-) create mode 100644 src/library/pkgcache/R/ppm-sso.R diff --git a/R/embed.R b/R/embed.R index f446c9861c..bff9086bec 100644 --- a/R/embed.R +++ b/R/embed.R @@ -225,6 +225,15 @@ embed <- local({ ) lib <- lib_dir() + + if (grepl("@", pkg)) { + pkgsplit <- strsplit(pkg, "@")[[1]] + pkg <- pkgsplit[1] + ref <- pkgsplit[2] + } else { + ref <- "main" + } + pkg_name <- sub("^.*/", "", pkg) if (mode == "add") { if (file.exists(file.path(lib, pkg_name))) { @@ -236,8 +245,9 @@ embed <- local({ on.exit(unlink(tmp, recursive = TRUE), add = TRUE) if (grepl("/", pkg)) { url <- sprintf( - "https://github.com/%s/archive/refs/heads/main.tar.gz", - pkg + "https://github.com/%s/archive/refs/heads/%s.tar.gz", + pkg, + ref ) path1 <- file.path(tmp, paste0(pkg_name, ".tar.gz")) download.file(url, path1) diff --git a/src/library/pkgcache/DESCRIPTION b/src/library/pkgcache/DESCRIPTION index 138a247828..76dcbf3588 100644 --- a/src/library/pkgcache/DESCRIPTION +++ b/src/library/pkgcache/DESCRIPTION @@ -13,12 +13,12 @@ License: MIT + file LICENSE URL: https://r-lib.github.io/pkgcache/, https://github.com/r-lib/pkgcache BugReports: https://github.com/r-lib/pkgcache/issues -Depends: R (>= 3.4) +Depends: R (>= 4.1.0) Imports: callr (>= 2.0.4.9000), cli (>= 3.2.0), curl (>= 3.2), filelock, jsonlite, processx (>= 3.3.0.9001), R6, tools, utils -Suggests: covr, debugme, desc, fs, keyring, pillar, pingr, rprojroot, - sessioninfo, spelling, testthat (>= 3.2.0), webfakes (>= - 1.1.5), withr, zip +Suggests: covr, debugme, desc, fs, httr2, keyring, openssl, pillar, + pingr, RcppTOML, rprojroot, sessioninfo, spelling, testthat (>= + 3.2.0), webfakes (>= 1.1.5), withr, zip Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 Config/usethis/last-upkeep: 2025-04-30 @@ -27,7 +27,7 @@ Language: en-US Roxygen: list(markdown = TRUE, r6 = FALSE) RoxygenNote: 7.3.2.9000 NeedsCompilation: yes -Packaged: 2026-04-22 20:18:20 UTC; gaborcsardi +Packaged: 2026-05-08 11:38:13 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Posit Software, PBC [cph, fnd] (ROR: ) Maintainer: Gábor Csárdi diff --git a/src/library/pkgcache/R/auth.R b/src/library/pkgcache/R/auth.R index 5b9baf664f..bd6511f2ee 100644 --- a/src/library/pkgcache/R/auth.R +++ b/src/library/pkgcache/R/auth.R @@ -83,7 +83,9 @@ repo_auth <- function( url <- res$url[w] if (check_credentials) { cred <- repo_auth_headers(url, warn = FALSE) - if (is.null(cred)) next + if (is.null(cred)) { + next + } res$username[w] <- cred$username res$has_password[w] <- cred$found res$auth_domains[w] <- list(cred$auth_domains) @@ -197,10 +199,18 @@ repo_auth_headers <- function( error = NULL ) - pwd <- repo_auth_netrc(parsed_url$host, parsed_url$username) + pwd <- repo_auth_sso(parsed_url$repourl, parsed_url$username) if (!is.null(pwd)) { res$auth_domain <- parsed_url$host - res$source <- paste0(".netrc") + res$source <- "SSO" + } + + if (is.null(pwd)) { + pwd <- repo_auth_netrc(parsed_url$host, parsed_url$username) + if (!is.null(pwd)) { + res$auth_domain <- parsed_url$host + res$source <- paste0(".netrc") + } } if (is.null(pwd) && !requireNamespace("keyring", quietly = TRUE)) { @@ -315,7 +325,9 @@ parse_url_basic_auth <- function(url) { add_auth_status <- function(repos) { maybe_has_auth <- grepl("^https?://[^/]*@", repos$url) - if (!any(maybe_has_auth)) return(repos) + if (!any(maybe_has_auth)) { + return(repos) + } key <- random_key() on.exit(clear_auth_cache(key), add = TRUE) @@ -326,7 +338,9 @@ add_auth_status <- function(repos) { for (w in which(maybe_has_auth)) { url <- repos$url[w] creds <- repo_auth_headers(url, warn = FALSE) - if (is.null(creds)) next + if (is.null(creds)) { + next + } repos$username[w] <- creds$username repos$has_password[w] <- creds$found } @@ -342,7 +356,9 @@ repo_auth_netrc <- function(host, username) { netrc_path <- path.expand("~/_netrc") } } - if (!file.exists(netrc_path)) return(NULL) + if (!file.exists(netrc_path)) { + return(NULL) + } # netrc files do not allow port numbers host <- sub(":[0-9]+$", "", host) @@ -453,3 +469,18 @@ repo_auth_netrc <- function(host, username) { NULL } + +repo_auth_sso <- function(repourl, username) { + ppm_url <- Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) + if (is.na(ppm_url)) { + return(NULL) + } + + if (!startsWith(repourl, ppm_url)) { + return(NULL) + } + + token <- try_catch_null(ppm_sso_login(service = repourl)) + + token +} diff --git a/src/library/pkgcache/R/ppm-sso.R b/src/library/pkgcache/R/ppm-sso.R new file mode 100644 index 0000000000..ac73bb1bea --- /dev/null +++ b/src/library/pkgcache/R/ppm-sso.R @@ -0,0 +1,446 @@ +ppm_sso_data <- new.env(parent = emptyenv()) +ppm_sso_data$name <- "ppm" +ppm_sso_data$viable <- FALSE + +ppm_sso_init <- function(url = NULL) { + url <- url %||% Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) + if (!is_string(url)) { + stop( + "Please set the PACKAGEMANAGER_ADDRESS environment variable to ", + "the URL of your RStudio Package Manager instance." + ) + } + + parsed_url <- regmatches( + url, + regexec("^(?:https?://)?([^/]+)", url) + )[[1]] + if (length(parsed_url) < 2) { + stop("Invalid Package Manager URL: ", url) + } + + ppm_sso_data$ppm_url <- url + ppm_sso_data$service_name <- parsed_url[2] + ppm_sso_data$token_file_path <- file.path( + path.expand("~"), + ".ppm", + "tokens.toml" + ) + ppm_sso_data$viable <- TRUE +} + +ppm_sso_login <- function(service = NULL) { + service <- service %||% + ppm_sso_data$ppm_url %||% + Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) + if (!ppm_sso_data$viable) { + ppm_sso_init() + } + + if (!ppm_are_requirements_valid(service)) { + stop( + "Package Manager SSO is not properly configured. Please ensure that ", + "the PACKAGEMANAGER_ADDRESS environment variable is set to the URL of ", + "your Posit Package Manager instance." + ) + } + + existing_token <- ppm_sso_get_existing_token() + if (!is.null(existing_token) && ppm_sso_can_authenticate(existing_token)) { + return(existing_token) + } + + identity_token <- ppm_sso_get_identity_token_from_file() %||% + ppm_sso_device_flow() + ppm_token <- ppm_sso_identity_to_ppm_token(identity_token) + ppm_sso_write_token_to_file(ppm_token) + + ppm_token +} + +ppm_are_requirements_valid <- function(service) { + is_string(ppm_sso_data$ppm_url) && startsWith(service, ppm_sso_data$ppm_url) +} + +ppm_sso_get_existing_token <- function() { + if (!file.exists(ppm_sso_data$token_file_path)) { + return(NULL) + } + tryCatch( + { + tokens_data <- RcppTOML::parseTOML(ppm_sso_data$token_file_path) + for (conn in tokens_data$connection) { + if (identical(conn$url, ppm_sso_data$ppm_url)) { + return(conn$token) + } + } + }, + error = function(e) { + NULL + } + ) +} + +ppm_sso_can_authenticate <- function(token) { + req <- httr2::request(ppm_sso_data$ppm_url) |> + httr2::req_auth_bearer_token(token) |> + httr2::req_error(is_error = function(resp) FALSE) # Handle errors manually + + resp <- httr2::req_perform(req) + + status <- httr2::resp_status(resp) + status < 500 && status != 401 && status != 403 +} + +ppm_sso_get_identity_token_from_file <- function() { + token_file <- Sys.getenv("PACKAGEMANAGER_IDENTITY_TOKEN_FILE", unset = NA) + if (is.na(token_file)) { + return(NULL) + } + + tryCatch( + { + trimws(readLines(token_file, n = 1, warn = FALSE)) + }, + error = function(e) { + NULL + } + ) +} + +ppm_sso_device_flow <- function() { + verifier <- ppm_sso_new_pkce_verifier() + challenge <- ppm_sso_new_pkce_challenge(verifier) + + # 1. Initiate Device Auth + init_url <- paste0(ppm_sso_data$ppm_url, "/__api__/device") + payload <- list( + code_challenge_method = "S256", + code_challenge = challenge + ) + init_resp_body <- httr2::request(init_url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_perform() |> + httr2::resp_body_json() + + display_uri <- init_resp_body$verification_uri_complete %||% + init_resp_body$verification_uri + if (is.null(display_uri)) { + stop("No verification URI found in device auth response.") + } + + message("\nPlease open the following URL in your browser:") + message(paste(" ", display_uri)) + message("\nAnd enter the following code when prompted:") + message(paste(" ", init_resp_body$user_code)) + message("\nWaiting for authorization...") + + try(utils::browseURL(display_uri), silent = TRUE) + + # 2. Poll for token + token_resp_body <- ppm_sso_complete_device_auth( + init_resp_body$device_code, + verifier, + init_resp_body$interval %||% 5, + init_resp_body$expires_in %||% 300 + ) + + if (is.null(token_resp_body) || is.null(token_resp_body$id_token)) { + stop("Failed to complete device authorization or obtain identity token.") + } + + token_resp_body$id_token +} + +ppm_sso_identity_to_ppm_token <- function(identity_token) { + url <- paste0(ppm_sso_data$ppm_url, "/__api__/token") + payload <- list( + grant_type = "urn:ietf:params:oauth:grant-type:token-exchange", + subject_token = identity_token, + subject_token_type = "urn:ietf:params:oauth:token-type:id_token" + ) + + resp <- httr2::request(url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_perform() + + token_data <- httr2::resp_body_json(resp) + if (is.null(token_data$access_token)) { + stop("Failed to exchange identity token for PPM token.") + } + + token_data$access_token +} + +ppm_sso_write_token_to_file <- function(token) { + dir.create( + dirname(ppm_sso_data$token_file_path), + showWarnings = FALSE, + recursive = TRUE + ) + + new_connection <- list( + url = ppm_sso_data$ppm_url, + token = token, + method = "sso" + ) + + existing_data <- if (file.exists(ppm_sso_data$token_file_path)) { + tryCatch( + RcppTOML::parseTOML(ppm_sso_data$token_file_path), + error = function(e) { + list(connection = list()) + } + ) + } else { + list(connection = list()) + } + + # Find and update existing entry or add a new one + found <- FALSE + if ( + !is.null(existing_data$connection) && length(existing_data$connection) > 0 + ) { + for (i in seq_along(existing_data$connection)) { + if (identical(existing_data$connection[[i]]$url, ppm_sso_data$ppm_url)) { + existing_data$connection[[i]] <- new_connection + found <- TRUE + break + } + } + } + + if (!found) { + existing_data$connection <- c( + existing_data$connection, + list(new_connection) + ) + } + + # Manually construct TOML output + output_lines <- c() + for (conn in existing_data$connection) { + output_lines <- c( + output_lines, + "[[connection]]", + paste0("url = \"", conn$url, "\""), + paste0("token = \"", conn$token, "\""), + paste0("method = \"", conn$method, "\""), + "" + ) + } + writeLines(output_lines, ppm_sso_data$token_file_path) +} + +ppm_sso_base64url_encode <- function(x) { + encoded <- openssl::base64_encode(x) + # Make it URL-safe + gsub("\\+", "-", gsub("\\/", "_", gsub("=+$", "", encoded))) +} + +ppm_sso_new_pkce_verifier <- function() { + ppm_sso_base64url_encode(openssl::rand_bytes(32)) +} + +ppm_sso_new_pkce_challenge <- function(verifier) { + hash <- openssl::sha256(charToRaw(verifier)) + ppm_sso_base64url_encode(hash) +} + +ppm_sso_complete_device_auth = function( + device_code, + verifier, + interval, + expires_in +) { + url <- paste0(ppm_sso_data$ppm_url, "/__api__/device_access") + start_time <- Sys.time() + payload <- list( + device_code = device_code, + code_verifier = verifier + ) + + while (as.numeric(Sys.time() - start_time) < expires_in) { + resp <- httr2::request(url) |> + httr2::req_body_form(!!!payload) |> + httr2::req_error(is_error = \(resp) FALSE) |> # Handle errors manually + httr2::req_perform() + + status <- httr2::resp_status(resp) + + if (status == 200) { + return(httr2::resp_body_json(resp)) + } else if (status == 400) { + error_data <- httr2::resp_body_json(resp) + error_code <- error_data$error + if (error_code == "access_denied") { + stop("Access denied by user.") + } + if (error_code == "expired_token") { + stop("Device authorization request expired.") + } + # For "authorization_pending" or "slow_down", just wait and retry. + } else { + httr2::resp_check_status(resp) + } + + Sys.sleep(interval) + } + + stop("Device authorization timed out.") +} + +# nocov start + +# Fake PPM server that proxies to Auth0, for testing ppm_sso_device_flow(). +# Auth0 device flow does not use PKCE, so we verify the PKCE challenge +# locally and forward only the device_code to Auth0's /oauth/token. +ppm_sso_fake_app <- function( + auth0_domain, + client_id, + audience = NULL, + scope = "openid profile email" +) { + app <- webfakes::new_app() + + app$use("logger" = webfakes::mw_log()) + app$use("urlencoded body parser" = webfakes::mw_urlencoded()) + app$use("json body parser" = webfakes::mw_json()) + + app$locals$challenges <- new.env(parent = emptyenv()) + app$locals$auth0_domain <- auth0_domain + app$locals$client_id <- client_id + app$locals$audience <- audience + app$locals$scope <- scope + + # Bearer-token check used by ppm_sso_can_authenticate(): any token passes. + app$get("/", function(req, res) { + res$set_status(200L)$send("ok") + }) + + app$post("/__api__/device", function(req, res) { + challenge <- req$form$code_challenge + method <- req$form$code_challenge_method %||% "S256" + if (!identical(method, "S256")) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_challenge_method") + )) + } + + payload <- list( + client_id = app$locals$client_id, + scope = app$locals$scope + ) + if (!is.null(app$locals$audience)) { + payload$audience <- app$locals$audience + } + + upstream <- httr2::request( + paste0("https://", app$locals$auth0_domain, "/oauth/device/code") + ) |> + httr2::req_body_form(!!!payload) |> + httr2::req_error(is_error = function(r) FALSE) |> + httr2::req_perform() + + body <- httr2::resp_body_json(upstream) + if (httr2::resp_status(upstream) >= 400L) { + return(res$set_status(httr2::resp_status(upstream))$send_json( + auto_unbox = TRUE, + body + )) + } + + assign(body$device_code, challenge, envir = app$locals$challenges) + + res$send_json( + auto_unbox = TRUE, + list( + device_code = body$device_code, + user_code = body$user_code, + verification_uri = body$verification_uri, + verification_uri_complete = body$verification_uri_complete, + expires_in = body$expires_in, + interval = body$interval %||% 5L + ) + ) + }) + + app$post("/__api__/device_access", function(req, res) { + device_code <- req$form$device_code + verifier <- req$form$code_verifier + + if (!exists(device_code, envir = app$locals$challenges, inherits = FALSE)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "expired_token") + )) + } + expected <- get( + device_code, + envir = app$locals$challenges, + inherits = FALSE + ) + actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) + if (!identical(expected, actual)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "invalid_grant") + )) + } + + upstream <- httr2::request( + paste0("https://", app$locals$auth0_domain, "/oauth/token") + ) |> + httr2::req_body_form( + grant_type = "urn:ietf:params:oauth:grant-type:device_code", + device_code = device_code, + client_id = app$locals$client_id + ) |> + httr2::req_error(is_error = function(r) FALSE) |> + httr2::req_perform() + + body <- httr2::resp_body_json(upstream) + if (httr2::resp_status(upstream) == 200L) { + rm(list = device_code, envir = app$locals$challenges) + return(res$send_json( + auto_unbox = TRUE, + list(id_token = body$id_token) + )) + } + + # Auth0 returns 403 for authorization_pending / slow_down; the PPM client + # only treats 400 as a soft pending state, so translate the status. + res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = body$error %||% "unknown_error") + ) + }) + + # Trivial token exchange: echo subject_token back as access_token. + app$post("/__api__/token", function(req, res) { + if ( + !identical( + req$form$grant_type, + "urn:ietf:params:oauth:grant-type:token-exchange" + ) + ) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_grant_type") + )) + } + res$send_json( + auto_unbox = TRUE, + list( + access_token = req$form$subject_token, + token_type = "Bearer", + issued_token_type = "urn:ietf:params:oauth:token-type:access_token" + ) + ) + }) + + app +} + +# nocov end From 262f51a0e013a6996dbed8bf217e3c292bb70a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 13:49:50 +0200 Subject: [PATCH 08/25] Copy PACKAGEMANAGER_ADDRESS env var to subprocess --- R/subprocess.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/subprocess.R b/R/subprocess.R index 6cfe161695..f2e9e70b89 100644 --- a/R/subprocess.R +++ b/R/subprocess.R @@ -74,10 +74,12 @@ remote <- function(func, args = list()) { opts <- options() extraopts <- c("Ncpus", "BioC_mirror") pkg_options <- opts[ - grepl("^pkg[.]", names(opts)) | grepl("^async_http_", names(opts)) | names(opts) %in% extraopts + grepl("^pkg[.]", names(opts)) | + grepl("^async_http_", names(opts)) | + names(opts) %in% extraopts ] envs <- Sys.getenv() - extraenvs <- c("R_BIOC_VERSION", "PATH") + extraenvs <- c("R_BIOC_VERSION", "PATH", "PACKAGEMANAGER_ADDRESS") if (any(grepl("@", subst_args[["__repos__"]]))) { extraenvs <- c(extraenvs, envs[grep("^https?://", names(envs))]) } From 752ee15927c30e034d868148fdb1babf3b085d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 14:03:45 +0200 Subject: [PATCH 09/25] Update embedded curl --- src/library/curl.patch | 34 -- src/library/curl/DESCRIPTION | 37 +- src/library/curl/LICENSE | 4 +- src/library/curl/NAMESPACE | 6 + src/library/curl/NEWS | 73 ++++ src/library/curl/R/curl.R | 12 +- src/library/curl/R/download.R | 20 +- src/library/curl/R/echo.R | 23 +- src/library/curl/R/email.R | 48 +-- src/library/curl/R/errcodes.R | 42 +++ src/library/curl/R/fetch.R | 51 ++- src/library/curl/R/form.R | 4 +- src/library/curl/R/handle.R | 49 +-- src/library/curl/R/multi.R | 61 ++-- src/library/curl/R/multi_download.R | 59 ++- src/library/curl/R/nslookup.R | 12 +- src/library/curl/R/onload.R | 47 ++- src/library/curl/R/options.R | 56 +-- src/library/curl/R/parser.R | 164 +++++++++ src/library/curl/R/sysdata.rda | Bin 12051 -> 12702 bytes src/library/curl/R/upload.R | 10 +- src/library/curl/R/utilities.R | 35 +- src/library/curl/R/writer.R | 6 +- src/library/curl/cleanup | 3 +- src/library/curl/configure | 51 ++- src/library/curl/inst/WORDLIST | 8 +- src/library/curl/src/Makevars.in | 6 +- src/library/curl/src/Makevars.win | 16 +- src/library/curl/src/callbacks.c | 4 +- src/library/curl/src/curl-common.h | 26 +- src/library/curl/src/curl.c | 67 ++-- src/library/curl/src/download.c | 10 +- src/library/curl/src/dryrun.c | 44 +++ src/library/curl/src/handle.c | 212 +++++------ src/library/curl/src/ieproxy.c | 6 +- src/library/curl/src/init.c | 16 +- src/library/curl/src/interrupt.c | 32 +- src/library/curl/src/macos-polyfill.h | 42 +++ src/library/curl/src/multi.c | 53 ++- src/library/curl/src/options.c | 4 - src/library/curl/src/ssl.c | 2 +- src/library/curl/src/typechecking.c | 28 -- src/library/curl/src/typelist.h | 63 ---- src/library/curl/src/urlparser.c | 101 +++++ src/library/curl/src/utils.c | 86 +++-- src/library/curl/src/version.c | 58 +-- src/library/curl/tools/errorcodes.txt | 128 +++++++ src/library/curl/tools/make-errorcodes.R | 11 + src/library/curl/tools/symbols-in-versions | 124 +++++-- src/library/curl/tools/symbols.R | 23 +- src/library/curl/tools/testversion.R | 3 + src/library/curl/tools/typelist.R | 13 - src/library/curl/tools/typelist.h.in | 62 ---- src/library/curl/tools/version.c | 4 + src/library/curl/tools/winlibs.R | 30 +- src/library/curl/vignettes/intro.Rmd | 406 +++++++++++++++++++++ src/library/curl/vignettes/windows.Rmd | 14 +- 57 files changed, 1815 insertions(+), 794 deletions(-) delete mode 100644 src/library/curl.patch create mode 100644 src/library/curl/R/errcodes.R create mode 100644 src/library/curl/R/parser.R create mode 100644 src/library/curl/src/dryrun.c create mode 100644 src/library/curl/src/macos-polyfill.h delete mode 100644 src/library/curl/src/typelist.h create mode 100644 src/library/curl/src/urlparser.c create mode 100644 src/library/curl/tools/errorcodes.txt create mode 100644 src/library/curl/tools/make-errorcodes.R create mode 100644 src/library/curl/tools/testversion.R delete mode 100644 src/library/curl/tools/typelist.R delete mode 100644 src/library/curl/tools/typelist.h.in create mode 100644 src/library/curl/tools/version.c create mode 100644 src/library/curl/vignettes/intro.Rmd diff --git a/src/library/curl.patch b/src/library/curl.patch deleted file mode 100644 index b830bc6241..0000000000 --- a/src/library/curl.patch +++ /dev/null @@ -1,34 +0,0 @@ -diff --git a/src/multi.c b/src/multi.c -index 2e0be9c..3cf9332 100644 ---- a/src/library/curl/src/multi.c -+++ b/src/library/curl/src/multi.c -@@ -1,6 +1,11 @@ - #include "curl-common.h" - #include - -+#include -+#if R_VERSION < R_Version(4, 5, 0) -+# define R_ClosureFormals(x) FORMALS(x) -+#endif -+ - /* Notes: - * - First check for unhandled messages in curl_multi_info_read() before curl_multi_perform() - * - Use Rf_eval() to callback instead of R_tryEval() to propagate interrupt or error back to C -@@ -146,7 +151,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ - if(status == CURLE_OK){ - total_success++; - if(Rf_isFunction(cb_complete)){ -- int arglen = Rf_length(FORMALS(cb_complete)); -+ int arglen = Rf_length(R_ClosureFormals(cb_complete)); - SEXP out = PROTECT(make_handle_response(ref)); - SET_VECTOR_ELT(out, 6, buf); - SEXP call = PROTECT(Rf_lcons(cb_complete, arglen ? Rf_lcons(out, R_NilValue) : R_NilValue)); -@@ -157,7 +162,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ - } else { - total_fail++; - if(Rf_isFunction(cb_error)){ -- int arglen = Rf_length(FORMALS(cb_error)); -+ int arglen = Rf_length(R_ClosureFormals(cb_error)); - SEXP buf = PROTECT(Rf_mkString(strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(status))); - SEXP call = PROTECT(Rf_lcons(cb_error, arglen ? Rf_lcons(buf, R_NilValue) : R_NilValue)); - //R_tryEval(call, R_GlobalEnv, &cbfail); diff --git a/src/library/curl/DESCRIPTION b/src/library/curl/DESCRIPTION index d30f08985b..cb6860e847 100644 --- a/src/library/curl/DESCRIPTION +++ b/src/library/curl/DESCRIPTION @@ -1,39 +1,34 @@ Package: curl Type: Package Title: A Modern and Flexible Web Client for R -Version: 5.2.3 +Version: 7.1.0 Authors@R: c( person("Jeroen", "Ooms", role = c("aut", "cre"), email = "jeroenooms@gmail.com", comment = c(ORCID = "0000-0002-4035-0289")), - person("Hadley", "Wickham", , "hadley@rstudio.com", role = "ctb"), - person("RStudio", role = "cph") - ) -Description: The curl() and curl_download() functions provide highly - configurable drop-in replacements for base url() and download.file() with - better performance, support for encryption (https, ftps), gzip compression, - authentication, and other 'libcurl' goodies. The core of the package implements a - framework for performing fully customized requests where data can be processed - either in memory, on disk, or streaming via the callback or connection - interfaces. Some knowledge of 'libcurl' is recommended; for a more-user-friendly - web client see the 'httr' package which builds on this package with http - specific tools and logic. + person("Hadley", "Wickham", role = "ctb"), + person("Posit Software, PBC", role = "cph")) +Description: Bindings to 'libcurl' for performing fully + configurable HTTP/FTP requests where responses can be processed in memory, on + disk, or streaming via the callback or connection interfaces. Some knowledge + of 'libcurl' is recommended; for a more-user-friendly web client see the + 'httr2' package which builds on this package with http specific tools and logic. License: MIT + file LICENSE -SystemRequirements: libcurl: libcurl-devel (rpm) or - libcurl4-openssl-dev (deb). -URL: https://jeroen.r-universe.dev/curl https://curl.se/libcurl/ +SystemRequirements: libcurl (>= 7.73): libcurl-devel (rpm) or + libcurl4-openssl-dev (deb) +URL: https://jeroen.r-universe.dev/curl BugReports: https://github.com/jeroen/curl/issues Suggests: spelling, testthat (>= 1.0.0), knitr, jsonlite, later, rmarkdown, httpuv (>= 1.4.4), webutils VignetteBuilder: knitr Depends: R (>= 3.0.0) -RoxygenNote: 7.3.0 +RoxygenNote: 7.3.2 Encoding: UTF-8 Language: en-US NeedsCompilation: yes -Packaged: 2024-09-19 15:43:51 UTC; jeroen -Author: Jeroen Ooms [aut, cre] (), +Packaged: 2026-04-21 14:47:54 UTC; jeroen +Author: Jeroen Ooms [aut, cre] (ORCID: ), Hadley Wickham [ctb], - RStudio [cph] + Posit Software, PBC [cph] Maintainer: Jeroen Ooms Repository: CRAN -Date/Publication: 2024-09-20 11:50:24 UTC +Date/Publication: 2026-04-22 09:40:02 UTC diff --git a/src/library/curl/LICENSE b/src/library/curl/LICENSE index a8cf97ef4f..46f81fa6c8 100644 --- a/src/library/curl/LICENSE +++ b/src/library/curl/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2022 -COPYRIGHT HOLDER: Jeroen Ooms; RStudio +YEAR: 2024 +COPYRIGHT HOLDER: Jeroen Ooms; Posit Software, PBC diff --git a/src/library/curl/NAMESPACE b/src/library/curl/NAMESPACE index 3cd613d39b..ecef1b62e8 100644 --- a/src/library/curl/NAMESPACE +++ b/src/library/curl/NAMESPACE @@ -13,7 +13,10 @@ export(curl_fetch_echo) export(curl_fetch_memory) export(curl_fetch_multi) export(curl_fetch_stream) +export(curl_modify_url) export(curl_options) +export(curl_options_table) +export(curl_parse_url) export(curl_symbols) export(curl_unescape) export(curl_upload) @@ -46,6 +49,7 @@ export(parse_headers) export(parse_headers_list) export(send_mail) useDynLib(curl,R_curl_connection) +useDynLib(curl,R_curl_dryrun) useDynLib(curl,R_curl_escape) useDynLib(curl,R_curl_fetch_disk) useDynLib(curl,R_curl_fetch_memory) @@ -65,6 +69,7 @@ useDynLib(curl,R_handle_getheaders) useDynLib(curl,R_handle_reset) useDynLib(curl,R_handle_setform) useDynLib(curl,R_handle_setopt) +useDynLib(curl,R_modify_url) useDynLib(curl,R_multi_add) useDynLib(curl,R_multi_cancel) useDynLib(curl,R_multi_fdset) @@ -76,6 +81,7 @@ useDynLib(curl,R_new_file_writer) useDynLib(curl,R_new_handle) useDynLib(curl,R_nslookup) useDynLib(curl,R_option_types) +useDynLib(curl,R_parse_url) useDynLib(curl,R_proxy_info) useDynLib(curl,R_split_string) useDynLib(curl,R_total_handles) diff --git a/src/library/curl/NEWS b/src/library/curl/NEWS index 67f8f1a3d0..c56890f13e 100644 --- a/src/library/curl/NEWS +++ b/src/library/curl/NEWS @@ -1,3 +1,76 @@ +7.1.0 + - Everything now works out of the box under emscripten (webR) by automatically + bootstrapping a ws gateway. + - Increase max size of string returned by ie_proxy_info() to 65536 + - Fix a unit test for libcurl 8.20 + +7.0.0 + - Major cleanup: packge now requires libcurl >= 7.73. Removed all conditioning + and fallbacks for older libcurl versions (#413). + - Removed the fallback ADA parser and unconditinoally use the curl URL parser. + - Removed the legacy type-checking code as we can unconditionally use the easy- + option API. + - Support option('netrc') to match base R >= 4.6.0. + - Setting any value in curl_modify_url() to NA or "" will now unset it. + +6.4.0 + - Vendor a static libcurl on RHEL/CentOS/Rocky 7 and 8 because stock version is + too old now to support most functionality. + - curl_modify_url() and curl_parse_url() now force uppercase percentage codes + for url-encoding to match curl_escape() + - Error messages are now printed with a linebreak in between. + - Better documentation for handle_setform() and handle_getdata() + +6.3.0 + - New function curl_modify_url() + - MacOS/Windows: update to libcurl 8.14.1 + - MacOS: our static build of libcurl now links to the MacOS native LibreSSL + and nghttp in order to use the same keychain certificates as native curl. + +6.2.3 + - Non blocking connections now wait for 10ms before returning no data to prevent + busy waiting when readBin is called in a loop. See #399 + - Fix a flaky unit test + +6.2.2 + - Fix a unit test for libcurl 8.13.0 + - Cleanup build + +6.2.1 + - Workaround for change of behavior in libcurl 8.12.0 + - The multi-run fail callback argument now has a curl-error class + - Fixed a slowness in curl_echo() due to threading problem in httpuv + +6.2.0 + - MacOS: we have temporarily switched to a static build of libcurl 8.11.1 until + apple updates their very buggy libcurl (#376) + - Fix LTO error for R_multi_setopt + - Fix parsing of #fragment links in curl_parse() (#366) + +6.1.0 + - Fix a rchk bug + - Enable CURLOPT_PIPEWAIT by default to prefer multiplex when possible + - Enable setting max_streams in multi_set(), default to 10 + - Allow open(curl::curl()) to be interrupted + - Interrupting a download no longer causes an error (#365) + +6.0.1 + - Fix a build issue with libcurl 8.11.0 + - Support multi_fdset() for connection objects (#355) + +6.0.0 + - New curl_parse_url() function to expose curl URL parsing interface. + - Errors now have classes and more detailed messages + - Do not use shared connection pool for FTP requests (works around bug in + libcurl < 8.8.0) + - Properly clean handle when open() fails for a curl() connection + - Parameter 'timeout' renamed to 'multi-timeout' in multi_download() + - Default behavior is now to error if a download is stalled for 600 seconds + - The MacOS binary packages now require at least libcurl 7.80 which is included + with all current MacOS versions (13+) as well as recent patch releases for + legacy MacOS versions (11.7 and 12.7). + - Windows: update to libcurl 8.10.1 + 5.2.3 - Remove some CMD check verbosity per new CRAN rules - New maintainer email address diff --git a/src/library/curl/R/curl.R b/src/library/curl/R/curl.R index 848294a530..70d362f8d9 100644 --- a/src/library/curl/R/curl.R +++ b/src/library/curl/R/curl.R @@ -1,13 +1,13 @@ #' Curl connection interface #' -#' Drop-in replacement for base \code{\link{url}} that supports https, ftps, -#' gzip, deflate, etc. Default behavior is identical to \code{\link{url}}, but -#' request can be fully configured by passing a custom \code{\link{handle}}. +#' Drop-in replacement for base [url()] that supports https, ftps, +#' gzip, deflate, etc. Default behavior is identical to [url()], but +#' request can be fully configured by passing a custom [handle()]. #' -#' As of version 2.3 curl connections support \code{open(con, blocking = FALSE)}. -#' In this case \code{readBin} and \code{readLines} will return immediately with data +#' As of version 2.3 curl connections support `open(con, blocking = FALSE)`. +#' In this case `readBin` and `readLines` will return immediately with data #' that is available without waiting. For such non-blocking connections the caller -#' needs to call \code{\link{isIncomplete}} to check if the download has completed +#' needs to call [isIncomplete()] to check if the download has completed #' yet. #' #' @useDynLib curl R_curl_connection diff --git a/src/library/curl/R/download.R b/src/library/curl/R/download.R index 6fa9e3e8f1..dc006c26f1 100644 --- a/src/library/curl/R/download.R +++ b/src/library/curl/R/download.R @@ -1,16 +1,18 @@ #' Download file to disk #' -#' Libcurl implementation of \code{C_download} (the "internal" download method) +#' Libcurl implementation of `C_download` (the "internal" download method) #' with added support for https, ftps, gzip, etc. Default behavior is identical -#' to \code{\link{download.file}}, but request can be fully configured by passing -#' a custom \code{\link{handle}}. +#' to [download.file()], but request can be fully configured by passing +#' a custom [handle()]. #' -#' The main difference between \code{curl_download} and \code{curl_fetch_disk} -#' is that \code{curl_download} checks the http status code before starting the +#' The main difference between `curl_download` and `curl_fetch_disk` +#' is that `curl_download` checks the http status code before starting the #' download, and raises an error when status is non-successful. The behavior of -#' \code{curl_fetch_disk} on the other hand is to proceed as normal and write +#' `curl_fetch_disk` on the other hand is to proceed as normal and write #' the error page to disk in case of a non success response. #' +#' The `curl_download` function does support resuming and removes the temporary +#' file if the download did not complete successfully. #' For a more advanced download interface which supports concurrent requests and #' resuming large files, have a look at the [multi_download] function. #' @@ -19,11 +21,11 @@ #' @param url A character string naming the URL of a resource to be downloaded. #' @param destfile A character string with the name where the downloaded file #' is saved. Tilde-expansion is performed. -#' @param quiet If \code{TRUE}, suppress status messages (if any), and the +#' @param quiet If `TRUE`, suppress status messages (if any), and the #' progress bar. #' @param mode A character string specifying the mode with which to write the file. -#' Useful values are \code{"w"}, \code{"wb"} (binary), \code{"a"} (append) -#' and \code{"ab"}. +#' Useful values are `"w"`, `"wb"` (binary), `"a"` (append) +#' and `"ab"`. #' @param handle a curl handle object #' @return Path of downloaded file (invisibly). #' @export diff --git a/src/library/curl/R/echo.R b/src/library/curl/R/echo.R index 4b0f014064..ea9210d03c 100644 --- a/src/library/curl/R/echo.R +++ b/src/library/curl/R/echo.R @@ -65,10 +65,6 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file # Workaround bug in httpuv on windows that keeps protecting handler until next startServer() on.exit(rm(handle), add = TRUE) - # Workaround for weird threading issue on Linux - # See: https://github.com/jeroen/curl/issues/327 - wait <- ifelse(isTRUE(grepl('linux', R.version$platform)), 0.001, 0) - # Post data from curl xfer <- function(down, up){ if(progress){ @@ -79,7 +75,6 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file as.integer(100 * up[2] / up[1])), file = stderr()) } } - later::run_now(wait) TRUE } handle_setopt(handle, connecttimeout = 2, xferinfofunction = xfer, noprogress = FALSE, forbid_reuse = TRUE) @@ -90,11 +85,16 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file host <- sub("https?://([^/]+).*", "\\1", input_url) #hostname <- gsub(":[0-9]+$", "", host) #handle_setopt(handle, port = port, resolve = paste0(hostname, ":", port, ':127.0.0.1')) - handle_setopt(handle, httpheader = c(paste0("Host:", host), handle_getheaders(handle))) + request_headers <- handle_getheaders(handle) + if(!any(grepl("^Host:", request_headers, ignore.case = TRUE))){ + request_headers <- c(paste("Host:", host), request_headers) + } + handle_setopt(handle, httpheader = request_headers) } else { target_url <- paste0("http://127.0.0.1:", port) } - curl_fetch_memory(target_url, handle = handle) + handle_setopt(handle, url = target_url) + curl_dryrun(handle) output$url <- input_url if(progress) cat("\n", file = stderr()) return(output) @@ -138,3 +138,12 @@ find_port <- function(range = NULL){ range <- as.integer(range) .Call(R_findport, range) } + +#' @useDynLib curl R_curl_dryrun +curl_dryrun <- function(handle){ + .Call(R_curl_dryrun, handle) +} + +later_wrapper <- function(){ + later::run_now() +} diff --git a/src/library/curl/R/email.R b/src/library/curl/R/email.R index 955f416820..1fb43f8a51 100644 --- a/src/library/curl/R/email.R +++ b/src/library/curl/R/email.R @@ -1,30 +1,30 @@ #' Send email #' -#' Use the curl SMTP client to send an email. The \code{message} argument must be -#' properly formatted \href{https://www.rfc-editor.org/rfc/rfc2822}{RFC2822} email message with From/To/Subject headers and CRLF +#' Use the curl SMTP client to send an email. The `message` argument must be +#' properly formatted [RFC2822](https://www.rfc-editor.org/rfc/rfc2822) email message with From/To/Subject headers and CRLF #' line breaks. #' #' @section Specifying the server, port, and protocol: #' -#' The \code{smtp_server} argument takes a hostname, or an SMTP URL: +#' The `smtp_server` argument takes a hostname, or an SMTP URL: #' #' \itemize{ -#' \item \code{mail.example.com} - hostname only -#' \item \code{mail.example.com:587} - hostname and port -#' \item \code{smtp://mail.example.com} - protocol and hostname -#' \item \code{smtp://mail.example.com:587} - full SMTP URL -#' \item \code{smtps://mail.example.com:465} - full SMTPS URL +#' \item `mail.example.com` - hostname only +#' \item `mail.example.com:587` - hostname and port +#' \item `smtp://mail.example.com` - protocol and hostname +#' \item `smtp://mail.example.com:587` - full SMTP URL +#' \item `smtps://mail.example.com:465` - full SMTPS URL #' } #' -#' By default, the port will be 25, unless \code{smtps://} is specified--then +#' By default, the port will be 25, unless `smtps://` is specified--then #' the default will be 465 instead. #' #' For internet SMTP servers you probably need to pass a -#' \href{https://curl.se/libcurl/c/CURLOPT_USERNAME.html}{username} and -#' \href{https://curl.se/libcurl/c/CURLOPT_PASSWORD.html}{passwords} option. +#' [username](https://curl.se/libcurl/c/CURLOPT_USERNAME.html) and +#' [passwords](https://curl.se/libcurl/c/CURLOPT_PASSWORD.html) option. #' For some servers you also need to pass a string with -#' \href{https://curl.se/libcurl/c/CURLOPT_LOGIN_OPTIONS.html}{login_options} -#' for example \code{login_options="AUTH=NTLM"}. +#' [login_options](https://curl.se/libcurl/c/CURLOPT_LOGIN_OPTIONS.html) +#' for example `login_options="AUTH=NTLM"`. #' #' @section Encrypting connections via SMTPS or STARTTLS: #' @@ -34,31 +34,31 @@ #' secure TLS connection using the STARTTLS command. It is important to know #' which method your server expects. #' -#' If your smtp server listens on port 465, then use a \code{smtps://hostname:465} -#' URL. The SMTPS protocol \emph{guarantees} that TLS will be used to protect +#' If your smtp server listens on port 465, then use a `smtps://hostname:465` +#' URL. The SMTPS protocol *guarantees* that TLS will be used to protect #' all communications from the start. #' -#' If your email server listens on port 25 or 587, use an \code{smtp://} URL in -#' combination with the \code{use_ssl} parameter to control if the connection -#' should be upgraded with STARTTLS. The default value \code{"try"} will -#' \emph{opportunistically} try to upgrade to a secure connection if the server +#' If your email server listens on port 25 or 587, use an `smtp://` URL in +#' combination with the `use_ssl` parameter to control if the connection +#' should be upgraded with STARTTLS. The default value `"try"` will +#' *opportunistically* try to upgrade to a secure connection if the server #' supports it, and proceed as normal otherwise. #' #' @export #' @param mail_rcpt one or more recipient email addresses. Do not include names, -#' these go into the \code{message} headers. +#' these go into the `message` headers. #' @param mail_from email address of the sender. #' @param message either a string or connection with (properly formatted) email #' message, including sender/recipient/subject headers. See example. #' @param smtp_server hostname or address of the SMTP server, or, an -#' \code{smtp://} or \code{smtps://} URL. See "Specifying the server, port, +#' `smtp://` or `smtps://` URL. See "Specifying the server, port, #' and protocol" below. #' @param use_ssl Request to upgrade the connection to SSL using the STARTTLS command, -#' see \href{https://curl.se/libcurl/c/CURLOPT_USE_SSL.html}{CURLOPT_USE_SSL} +#' see [CURLOPT_USE_SSL](https://curl.se/libcurl/c/CURLOPT_USE_SSL.html) #' for details. Default will try to SSL, proceed as normal otherwise. #' @param verbose print output -#' @param ... other options passed to \code{\link{handle_setopt}}. In most cases -#' you will need to set a \code{username} and \code{password} or \code{login_options} +#' @param ... other options passed to [handle_setopt()]. In most cases +#' you will need to set a `username` and `password` or `login_options` #' to authenticate with the SMTP server, see details. #' @examples \dontrun{# Set sender and recipients (email addresses only) #' recipients <- readline("Enter your email address to receive test: ") diff --git a/src/library/curl/R/errcodes.R b/src/library/curl/R/errcodes.R new file mode 100644 index 0000000000..acf0efa2ca --- /dev/null +++ b/src/library/curl/R/errcodes.R @@ -0,0 +1,42 @@ +# This file is autogenerated using make-errorcodes.R +libcurl_error_codes <- +c("curl_error_unsupported_protocol", "curl_error_failed_init", +"curl_error_url_malformat", "curl_error_not_built_in", "curl_error_couldnt_resolve_proxy", +"curl_error_couldnt_resolve_host", "curl_error_couldnt_connect", +"curl_error_weird_server_reply", "curl_error_remote_access_denied", +"curl_error_ftp_accept_failed", "curl_error_ftp_weird_pass_reply", +"curl_error_ftp_accept_timeout", "curl_error_ftp_weird_pasv_reply", +"curl_error_ftp_weird_227_format", "curl_error_ftp_cant_get_host", +"curl_error_http2", "curl_error_ftp_couldnt_set_type", "curl_error_partial_file", +"curl_error_ftp_couldnt_retr_file", "curl_error_obsolete20", +"curl_error_quote_error", "curl_error_http_returned_error", "curl_error_write_error", +"curl_error_obsolete24", "curl_error_upload_failed", "curl_error_read_error", +"curl_error_out_of_memory", "curl_error_operation_timedout", +"curl_error_obsolete29", "curl_error_ftp_port_failed", "curl_error_ftp_couldnt_use_rest", +"curl_error_obsolete32", "curl_error_range_error", "curl_error_http_post_error", +"curl_error_ssl_connect_error", "curl_error_bad_download_resume", +"curl_error_file_couldnt_read_file", "curl_error_ldap_cannot_bind", +"curl_error_ldap_search_failed", "curl_error_obsolete40", "curl_error_function_not_found", +"curl_error_aborted_by_callback", "curl_error_bad_function_argument", +"curl_error_obsolete44", "curl_error_interface_failed", "curl_error_obsolete46", +"curl_error_too_many_redirects", "curl_error_unknown_option", +"curl_error_setopt_option_syntax", "curl_error_obsolete50", "curl_error_peer_failed_verification", +"curl_error_got_nothing", "curl_error_ssl_engine_notfound", "curl_error_ssl_engine_setfailed", +"curl_error_send_error", "curl_error_recv_error", "curl_error_obsolete57", +"curl_error_ssl_certproblem", "curl_error_ssl_cipher", "curl_error_peer_failed_verification", +"curl_error_bad_content_encoding", "curl_error_ldap_invalid_url", +"curl_error_filesize_exceeded", "curl_error_use_ssl_failed", +"curl_error_send_fail_rewind", "curl_error_ssl_engine_initfailed", +"curl_error_login_denied", "curl_error_tftp_notfound", "curl_error_tftp_perm", +"curl_error_remote_disk_full", "curl_error_tftp_illegal", "curl_error_tftp_unknownid", +"curl_error_remote_file_exists", "curl_error_tftp_nosuchuser", +"curl_error_conv_failed", "curl_error_conv_reqd", "curl_error_ssl_cacert_badfile", +"curl_error_remote_file_not_found", "curl_error_ssh", "curl_error_ssl_shutdown_failed", +"curl_error_again", "curl_error_ssl_crl_badfile", "curl_error_ssl_issuer_error", +"curl_error_ftp_pret_failed", "curl_error_rtsp_cseq_error", "curl_error_rtsp_session_error", +"curl_error_ftp_bad_file_list", "curl_error_chunk_failed", "curl_error_no_connection_available", +"curl_error_ssl_pinnedpubkeynotmatch", "curl_error_ssl_invalidcertstatus", +"curl_error_http2_stream", "curl_error_recursive_api_call", "curl_error_auth_error", +"curl_error_http3", "curl_error_quic_connect_error", "curl_error_proxy", +"curl_error_ssl_clientcert", "curl_error_unrecoverable_poll", +"curl_error_too_large", "curl_error_ech_required") diff --git a/src/library/curl/R/fetch.R b/src/library/curl/R/fetch.R index 12c70f9699..5672023f3d 100644 --- a/src/library/curl/R/fetch.R +++ b/src/library/curl/R/fetch.R @@ -1,30 +1,41 @@ #' Fetch the contents of a URL #' #' Low-level bindings to write data from a URL into memory, disk or a callback -#' function. These are mainly intended for \code{httr}, most users will be better -#' off using the \code{\link{curl}} or \code{\link{curl_download}} function, or the -#' http specific wrappers in the \code{httr} package. +#' function. #' -#' The curl_fetch functions automatically raise an error upon protocol problems -#' (network, disk, ssl) but do not implement application logic. For example for -#' you need to check the status code of http requests yourself in the response, +#' The `curl_fetch_*()` functions automatically raise an error upon protocol problems +#' (network, disk, TLS, etc.) but do not implement application logic. For example, +#' you need to check the status code of HTTP requests in the response by yourself, #' and deal with it accordingly. #' -#' Both \code{curl_fetch_memory} and \code{curl_fetch_disk} have a blocking and +#' Both `curl_fetch_memory()` and `curl_fetch_disk` have a blocking and a #' non-blocking C implementation. The latter is slightly slower but allows for #' interrupting the download prematurely (using e.g. CTRL+C or ESC). Interrupting #' is enabled when R runs in interactive mode or when -#' \code{getOption("curl_interrupt") == TRUE}. +#' `getOption("curl_interrupt") == TRUE`. #' -#' The \code{curl_fetch_multi} function is the asynchronous equivalent of -#' \code{curl_fetch_memory}. It wraps \code{multi_add} to schedule requests which -#' are executed concurrently when calling \code{multi_run}. For each successful -#' request the \code{done} callback is triggered with response data. For failed -#' requests (when \code{curl_fetch_memory} would raise an error), the \code{fail} -#' function is triggered with the error message. +#' The `curl_fetch_multi()` function is the asynchronous equivalent of +#' `curl_fetch_memory()`. It wraps [`multi_add()`][multi_add] to +#' schedule requests which are executed concurrently when calling +#' [`multi_run()`][multi_run]\code{}. For each successful request, the +#' `done` callback is triggered with response data. For failed requests +#' (when `curl_fetch_memory()` would raise an error), the `fail` function +#' is triggered with the error message. +#' +#' After a request has been performed, metadata from the request can be read +#' from the handle object using `handle_data()` (this same information also gets +#' returned by `curl_fetch_memory()` directly). It includes things like: +#' - Final URL (after redirects) +#' - HTTP status code +#' - Content-type +#' - Response headers +#' - Timings +#' - Http-version +#' This data remains available in the handle until it is either re-used for a +#' new request, or `handle_reset()` is called. #' #' @param url A character string naming the URL of a resource to be downloaded. -#' @param handle a curl handle object +#' @param handle A curl handle object. #' @export #' @rdname curl_fetch #' @useDynLib curl R_curl_fetch_memory @@ -118,3 +129,13 @@ curl_fetch_echo <- function(url, handle = new_handle()){ handle_setopt(handle, url = enc2utf8(url)) curl_echo(handle) } + +#' @export +#' @rdname curl_fetch +#' @useDynLib curl R_get_handle_response +handle_data <- function(handle){ + stopifnot(inherits(handle, "curl_handle")) + out <- .Call(R_get_handle_response, handle) + out$content = NULL + out +} diff --git a/src/library/curl/R/form.R b/src/library/curl/R/form.R index d3fbb9925e..c44d109490 100644 --- a/src/library/curl/R/form.R +++ b/src/library/curl/R/form.R @@ -1,7 +1,7 @@ #' POST files or data #' -#' Build multipart form data elements. The \code{form_file} function uploads a -#' file. The \code{form_data} function allows for posting a string or raw vector +#' Build multipart form data elements. The `form_file` function uploads a +#' file. The `form_data` function allows for posting a string or raw vector #' with a custom content-type. #' #' @param path a string with a path to an existing file on disk diff --git a/src/library/curl/R/handle.R b/src/library/curl/R/handle.R index af96da5284..5214560850 100644 --- a/src/library/curl/R/handle.R +++ b/src/library/curl/R/handle.R @@ -2,24 +2,29 @@ #' #' Handles are the work horses of libcurl. A handle is used to configure a #' request with custom options, headers and payload. Once the handle has been -#' set up, it can be passed to any of the download functions such as \code{\link{curl}} -#' ,\code{\link{curl_download}} or \code{\link{curl_fetch_memory}}. The handle will maintain +#' set up, it can be passed to any of the download functions such as [curl()] +#' ,[curl_download()] or [curl_fetch_memory()]. The handle will maintain #' state in between requests, including keep-alive connections, cookies and #' settings. #' -#' Use \code{new_handle()} to create a new clean curl handle that can be -#' configured with custom options and headers. Note that \code{handle_setopt} -#' appends or overrides options in the handle, whereas \code{handle_setheaders} -#' replaces the entire set of headers with the new ones. The \code{handle_reset} +#' Use `new_handle()` to create a new clean curl handle that can be +#' configured with custom options and headers. Note that `handle_setopt` +#' appends or overrides options in the handle, whereas `handle_setheaders` +#' replaces the entire set of headers with the new ones. The `handle_reset` #' function resets only options/headers/forms in the handle. It does not affect #' active connections, cookies or response data from previous requests. The safest #' way to perform multiple independent requests is by using a separate handle for #' each request. There is very little performance overhead in creating handles. #' +#' The [handle_setform] function is used to perform a `multipart/form-data` HTTP +#' POST request (a.k.a. posting a form). The form fields can be specified as +#' strings, raw vectors (for binary data), or [form_file] and [form_data] for +#' upload elements. See the examples. +#' #' @family handles #' @param ... named options / headers to be set in the handle. -#' To send a file, see \code{\link{form_file}}. To list all allowed options, -#' see \code{\link{curl_options}} +#' To send a file, see [form_file()]. To list all allowed options, +#' see [curl_options()] #' @return A handle object (external pointer to the underlying curl handle). #' All functions modify the handle in place but also return the handle #' so you can create a pipeline of operations. @@ -40,6 +45,17 @@ #' handle_setform(h, .list = list(a = "1", b = "2")) #' r <- curl_fetch_memory("https://hb.cran.dev/put", h) #' cat(rawToChar(r$content)) +#' +#' # Posting multipart forms +#' h <- new_handle() +#' handle_setform(h, +#' foo = "blabla", +#' bar = charToRaw("boeboe"), +#' iris = form_data(serialize(iris, NULL), "application/rda"), +#' description = form_file(system.file("DESCRIPTION")), +#' logo = form_file(file.path(R.home('doc'), "html/logo.jpg"), "image/jpeg") +#' ) +#' req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) new_handle <- function(...){ h <- .Call(R_new_handle) handle_setopt(h, ...) @@ -50,7 +66,7 @@ new_handle <- function(...){ #' @useDynLib curl R_handle_setopt #' @param handle Handle to modify #' @param .list A named list of options. This is useful if you've created -#' a list of options elsewhere, avoiding the use of \code{do.call()}. +#' a list of options elsewhere, avoiding the use of `do.call()`. #' @rdname handle handle_setopt <- function(handle, ..., .list = list()){ stopifnot(inherits(handle, "curl_handle")) @@ -94,7 +110,6 @@ handle_getheaders <- function(handle){ } #' @useDynLib curl R_handle_getcustom -#' @rdname handle handle_getcustom <- function(handle){ stopifnot(inherits(handle, "curl_handle")) .Call(R_handle_getcustom, handle) @@ -129,8 +144,8 @@ handle_reset <- function(handle){ #' Extract cookies from a handle #' -#' The \code{handle_cookies} function returns a data frame with 7 columns as specified in the -#' \href{http://www.cookiecentral.com/faq/#3.5}{netscape cookie file format}. +#' The `handle_cookies` function returns a data frame with 7 columns as specified in the +#' [netscape cookie file format](http://www.cookiecentral.com/faq/#3.5). #' #' @useDynLib curl R_get_handle_cookies #' @export @@ -171,16 +186,6 @@ handle_cookies <- function(handle){ } -#' @export -#' @rdname handle -#' @useDynLib curl R_get_handle_response -handle_data <- function(handle){ - stopifnot(inherits(handle, "curl_handle")) - out <- .Call(R_get_handle_response, handle) - out$content = NULL - out -} - # This is for internal use in progress bars. When the download is complete, # the speed is equal to content-size / elapsed-time. #' @useDynLib curl R_get_handle_speed diff --git a/src/library/curl/R/multi.R b/src/library/curl/R/multi.R index 5149042e95..a1d36de293 100644 --- a/src/library/curl/R/multi.R +++ b/src/library/curl/R/multi.R @@ -4,20 +4,20 @@ #' Results are only available via callback functions. Advanced use only! #' For downloading many files in parallel use [multi_download] instead. #' -#' Requests are created in the usual way using a curl \link{handle} and added -#' to the scheduler with \link{multi_add}. This function returns immediately -#' and does not perform the request yet. The user needs to call \link{multi_run} +#' Requests are created in the usual way using a curl [handle] and added +#' to the scheduler with [multi_add]. This function returns immediately +#' and does not perform the request yet. The user needs to call [multi_run] #' which performs all scheduled requests concurrently. It returns when all -#' requests have completed, or case of a \code{timeout} or \code{SIGINT} (e.g. -#' if the user presses \code{ESC} or \code{CTRL+C} in the console). In case of -#' the latter, simply call \link{multi_run} again to resume pending requests. +#' requests have completed, or case of a `timeout` or `SIGINT` (e.g. +#' if the user presses `ESC` or `CTRL+C` in the console). In case of +#' the latter, simply call [multi_run] again to resume pending requests. #' -#' When the request succeeded, the \code{done} callback gets triggered with -#' the response data. The structure if this data is identical to \link{curl_fetch_memory}. -#' When the request fails, the \code{fail} callback is triggered with an error +#' When the request succeeded, the `done` callback gets triggered with +#' the response data. The structure if this data is identical to [curl_fetch_memory]. +#' When the request fails, the `fail` callback is triggered with an error #' message. Note that failure here means something went wrong in performing the #' request such as a connection failure, it does not check the http status code. -#' Just like \link{curl_fetch_memory}, the user has to implement application logic. +#' Just like [curl_fetch_memory], the user has to implement application logic. #' #' Raising an error within a callback function stops execution of that function #' but does not affect other requests. @@ -28,14 +28,14 @@ #' It is up to the user to make sure the same handle is not used in concurrent #' requests. #' -#' The \link{multi_cancel} function can be used to cancel a pending request. +#' The [multi_cancel] function can be used to cancel a pending request. #' It has no effect if the request was already completed or canceled. #' -#' The \link{multi_fdset} function returns the file descriptors curl is +#' The [multi_fdset] function returns the file descriptors curl is #' polling currently, and also a timeout parameter, the number of #' milliseconds an application should wait (at most) before proceeding. It -#' is equivalent to the \code{curl_multi_fdset} and -#' \code{curl_multi_timeout} calls. It is handy for applications that is +#' is equivalent to the `curl_multi_fdset` and +#' `curl_multi_timeout` calls. It is handy for applications that is #' expecting input (or writing output) through both curl, and other file #' descriptors. #' @@ -43,18 +43,18 @@ #' @rdname multi #' @seealso Advanced download interface: [multi_download] #' @useDynLib curl R_multi_add -#' @param handle a curl \link{handle} with preconfigured \code{url} option. +#' @param handle a curl [handle] with preconfigured `url` option. #' @param done callback function for completed request. Single argument with -#' response data in same structure as \link{curl_fetch_memory}. +#' response data in same structure as [curl_fetch_memory]. #' @param fail callback function called on failed request. Argument contains #' error message. #' @param data (advanced) callback function, file path, or connection object for writing -#' incoming data. This callback should only be used for \emph{streaming} applications, +#' incoming data. This callback should only be used for *streaming* applications, #' where small pieces of incoming data get written before the request has completed. The -#' signature for the callback function is \code{write(data, final = FALSE)}. If set -#' to \code{NULL} the entire response gets buffered internally and returned by in -#' the \code{done} callback (which is usually what you want). -#' @param pool a multi handle created by \link{new_pool}. Default uses a global pool. +#' signature for the callback function is `write(data, final = FALSE)`. If set +#' to `NULL` the entire response gets buffered internally and returned by in +#' the `done` callback (which is usually what you want). +#' @param pool a multi handle created by [new_pool]. Default uses a global pool. #' @export #' @examples #' results <- list() @@ -118,9 +118,9 @@ multi_add <- function(handle, done = NULL, fail = NULL, data = NULL, pool = NULL .Call(R_multi_add, handle, done, fail, data, pool) } -#' @param timeout max time in seconds to wait for results. Use \code{0} to poll for results without +#' @param timeout max time in seconds to wait for results. Use `0` to poll for results without #' waiting at all. -#' @param poll If \code{TRUE} then return immediately after any of the requests has completed. +#' @param poll If `TRUE` then return immediately after any of the requests has completed. #' May also be an integer in which case it returns after n requests have completed. #' @export #' @useDynLib curl R_multi_run @@ -135,18 +135,20 @@ multi_run <- function(timeout = Inf, poll = FALSE, pool = NULL){ #' @param total_con max total concurrent connections. #' @param host_con max concurrent connections per host. -#' @param multiplex enable HTTP/2 multiplexing if supported by host and client. +#' @param max_streams max HTTP/2 concurrent multiplex streams per connection. +#' @param multiplex use HTTP/2 multiplexing if supported by host and client. #' @export #' @useDynLib curl R_multi_setopt #' @rdname multi -multi_set <- function(total_con = 50, host_con = 6, multiplex = TRUE, pool = NULL){ +multi_set <- function(total_con = 50, host_con = 6, max_streams = 10, multiplex = TRUE, pool = NULL){ if(is.null(pool)) pool <- multi_default() stopifnot(inherits(pool, "curl_multi")) stopifnot(is.numeric(total_con)) stopifnot(is.numeric(host_con)) + stopifnot(is.numeric(max_streams)) stopifnot(is.logical(multiplex)) - .Call(R_multi_setopt, pool, total_con, host_con, multiplex) + .Call(R_multi_setopt, pool, total_con, host_con, max_streams, multiplex) } #' @export @@ -170,9 +172,9 @@ multi_cancel <- function(handle){ #' @export #' @useDynLib curl R_multi_new #' @rdname multi -new_pool <- function(total_con = 100, host_con = 6, multiplex = TRUE){ +new_pool <- function(total_con = 100, host_con = 6, max_streams = 10, multiplex = TRUE){ pool <- .Call(R_multi_new) - multi_set(pool = pool, total_con = total_con, host_con = host_con, multiplex = multiplex) + multi_set(pool = pool, total_con = total_con, host_con = host_con, max_streams = max_streams, multiplex = multiplex) } multi_default <- local({ @@ -199,6 +201,7 @@ print.curl_multi <- function(x, ...){ multi_fdset <- function(pool = NULL){ if(is.null(pool)) pool <- multi_default() - stopifnot(inherits(pool, "curl_multi")) + # line below duplicates checks made by C code, but may need to be reinstated if that ever changes + # stopifnot(inherits(pool, c("curl_multi", "curl"))) .Call(R_multi_fdset, pool) } diff --git a/src/library/curl/R/multi_download.R b/src/library/curl/R/multi_download.R index fdac49205e..d045d086e6 100644 --- a/src/library/curl/R/multi_download.R +++ b/src/library/curl/R/multi_download.R @@ -10,7 +10,7 @@ #' of the HTTP status code). If it failed, e.g. due to a networking issue, the error #' message is in the `error` column. A `success` value `NA` indicates that the request #' was still in progress when the function was interrupted or reached the elapsed -#' `timeout` and perhaps the download can be resumed if the server supports it. +#' `multi_timeout` and perhaps the download can be resumed if the server supports it. #' #' It is also important to inspect the `status_code` column to see if any of the #' requests were successful but had a non-success HTTP code, and hence the downloaded @@ -61,12 +61,13 @@ #' - `headers` vector with http response headers for the request. #' #' @export -#' @param urls vector with files to download +#' @param urls vector with URLs to download. Alternatively it may also be a +#' list of [handle][new_handle] objects that have the `url` option already set. #' @param destfiles vector (of equal length as `urls`) with paths of output files, #' or `NULL` to use [basename] of urls. #' @param resume if the file already exists, resume the download. Note that this may #' change server responses, see details. -#' @param timeout in seconds, passed to [multi_run] +#' @param multi_timeout in seconds, passed to [multi_run] #' @param progress print download progress information #' @param multiplex passed to [new_pool] #' @param ... extra handle options passed to each request [new_handle] @@ -101,10 +102,14 @@ #' #' } multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TRUE, - timeout = Inf, multiplex = FALSE, ...){ - urls <- enc2utf8(urls) + multi_timeout = Inf, multiplex = TRUE, ...){ + if(inherits(urls, 'curl_handle')) + urls <- list(urls) + if(!is.character(urls) && !is.list(urls)) + stop("Argument 'urls' must be a character vector or list of handles") + handles <- lapply(urls, make_one_handle) if(is.null(destfiles)){ - destfiles <- basename(sub("[?#].*", "", urls)) + destfiles <- vapply(handles, guess_handle_filename, character(1)) } dupes <- setdiff(destfiles[duplicated(destfiles)], c("/dev/null", "NUL")) if(length(dupes)){ @@ -112,7 +117,6 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR } stopifnot(length(urls) == length(destfiles)) destfiles <- normalizePath(destfiles, mustWork = FALSE) - handles <- rep(list(NULL), length(urls)) writers <- rep(list(NULL), length(urls)) errors <- rep(NA_character_, length(urls)) success <- rep(NA, length(urls)) @@ -123,8 +127,8 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR total <- 0 lapply(seq_along(urls), function(i){ dest <- destfiles[i] - handle <- new_handle(url = urls[i], ...) - handle_setopt(handle, noprogress = TRUE) + handle <- handles[[i]] + handle_setopt(handle, ..., noprogress = TRUE) if(isTRUE(resume) && file.exists(dest)){ startsize <- file.info(dest)$size handle_setopt(handle, resume_from_large = startsize) @@ -159,7 +163,6 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR errors[i] <<- err dlspeed[i] <<- 0 }) - handles[[i]] <<- handle writers[[i]] <<- writer if(isTRUE(progress) && (i %% 100 == 0)){ print_stream("\rPreparing request %d of %d...", i, length(urls)) @@ -170,7 +173,7 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR writer(raw(0), close = TRUE) })) tryCatch({ - multi_run(timeout = timeout, pool = pool) + multi_run(timeout = multi_timeout, pool = pool) if(isTRUE(progress)){ print_progress(success, total, sum(dlspeed), sum(expected), TRUE) } @@ -180,14 +183,14 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR out <- lapply(handles, handle_data) results <- data.frame( success = success, - status_code = sapply(out, function(x){x$status_code}), + status_code = vapply(out, function(x){x$status_code}, numeric(1)), resumefrom = resumefrom, - url = sapply(out, function(x){x$url}), - destfile = destfiles, + url = vapply(out, function(x){x$url}, character(1)), + destfile = normalizePath(destfiles, mustWork = FALSE), error = errors, - type = sapply(out, function(x){x$type}), - modified = structure(sapply(out, function(x){x$modified}), class = c("POSIXct", "POSIXt")), - time = sapply(out, function(x){unname(x$times['total'])}), + type = vapply(out, function(x){x$type}, character(1)), + modified = structure(vapply(out, function(x){x$modified}, numeric(1)), class = c("POSIXct", "POSIXt")), + time = vapply(out, function(x){unname(x$times['total'])}, numeric(1)), stringsAsFactors = FALSE ) results$headers <- lapply(out, function(x){parse_headers(x$headers)}) @@ -195,6 +198,28 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR results } +make_one_handle <- function(url_or_handle){ + if(inherits(url_or_handle, 'curl_handle')){ + handle <- url_or_handle + url <- handle_data(handle)$url + if(is.na(url) || identical(url, "")) + stop("Handle passed to multi_download() but has no URL set") + return(handle) + } + if(is.character(url_or_handle)){ + return(new_handle(url = url_or_handle)) + } + stop("Argument urls does not contain url or curl handle") +} + +guess_handle_filename <- function(handle){ + url <- handle_data(handle)$url + destfile <- basename(curl_parse_url(url)$path) + if(!length(destfile) || is.na(destfile) || !nchar(destfile)) + stop("Failed to guess filename for: ", url) + destfile +} + # Print at most 10x per second in interactive, and once per sec in batch/CI print_progress <- local({ last <- 0 diff --git a/src/library/curl/R/nslookup.R b/src/library/curl/R/nslookup.R index 3a2d20282e..33cfc2dd19 100644 --- a/src/library/curl/R/nslookup.R +++ b/src/library/curl/R/nslookup.R @@ -1,22 +1,22 @@ #' Lookup a hostname #' -#' The \code{nslookup} function is similar to \code{nsl} but works on all platforms +#' The `nslookup` function is similar to `nsl` but works on all platforms #' and can resolve ipv6 addresses if supported by the OS. Default behavior raises an #' error if lookup fails. #' -#' The \code{has_internet} function tests for internet connectivity by performing a +#' The `has_internet` function tests for internet connectivity by performing a #' dns lookup. If a proxy server is detected, it will also check for connectivity by #' connecting via the proxy. #' #' @export #' @param host a string with a hostname -#' @param error raise an error for failed DNS lookup. Otherwise returns \code{NULL}. +#' @param error raise an error for failed DNS lookup. Otherwise returns `NULL`. #' @param ipv4_only always return ipv4 address. Set to `FALSE` to allow for ipv6 as well. #' @param multiple returns multiple ip addresses if possible #' @rdname nslookup #' @useDynLib curl R_nslookup #' @examples # Should always work if we are online -#' nslookup("www.r-project.org") +#' nslookup("www.google.com") #' #' # If your OS supports IPv6 #' nslookup("ipv6.test-ipv6.com", ipv4_only = FALSE, error = FALSE) @@ -48,6 +48,10 @@ has_internet <- local({ if(any(c("8.8.4.4", "8.8.8.8") %in% ip_addr)) return(TRUE) + ip_addr <- nslookup('time.cloudflare.com', multiple = TRUE, error = FALSE, ipv4_only = TRUE) + if(any(c("162.159.200.1", "162.159.200.123") %in% ip_addr)) + return(TRUE) + # Method 2: look for a proxy server proxy_vars <- Sys.getenv(c('ALL_PROXY', 'https_proxy', 'HTTPS_PROXY', 'HTTPS_proxy'), NA) diff --git a/src/library/curl/R/onload.R b/src/library/curl/R/onload.R index c1b665ddc0..b4dde37377 100644 --- a/src/library/curl/R/onload.R +++ b/src/library/curl/R/onload.R @@ -1,9 +1,50 @@ .onAttach <- function(libname, pkgname){ - ssl <- sub("\\(.*\\)\\W*", "", curl_version()$ssl_version) - msg <- paste("Using libcurl", curl_version()$version, "with", ssl) + version <- curl_version() + ssl <- sub("\\(.*\\)\\W*", "", version$ssl_version) + msg <- paste("Using libcurl", version$version, "with", ssl) packageStartupMessage(msg) + if(grepl("redhat", R.version$platform) && !('smtp' %in% version$protocols)){ + packageStartupMessage(c("Your system runs libcurl-minimal which does not support all protocols: ", + "See also https://github.com/jeroen/curl/issues/350")) + } + try({ + proxy <- Sys.getenv('ALL_PROXY') + if(nchar(proxy)){ + proxy_info <- curl::curl_parse_url(proxy) + packageStartupMessage(sprintf("Using proxy server %s://%s:%s", + proxy_info$scheme, proxy_info$host, proxy_info$port)) + } + }, silent = TRUE) } .onLoad <- function(libname, pkgname){ - assign("option_type_table", make_option_type_table(), environment(.onLoad)) + if(grepl('emscripten', R.version[['platform']])){ + set_emscripten_gateway() + } +} + +# Sets a default http gateway for using curl in WebR +# See https://github.com/r-wasm/ws-proxy +# Note socks5 proxy is just a dumb gateway that relays the encrypted https +# traffic as-is. There is no snooping or tempering with cert verification +# possible by the proxy server. +set_emscripten_gateway <- function(){ + proxy <- Sys.getenv('ALL_PROXY') + + # TODO: fix this unfortunate default envvar in webR? + if(proxy == '' || proxy == "socks5h://localhost:8580"){ + try({ + # Note the websocket runs wss (port 443) but inside we mimic plain http + # therefore for curl it looks like http:// but on 443. But it is actually + # https because of the wss:// layer in emscripten. + h <- new_handle(connecttimeout = 2, noproxy = '*') + req <- curl_fetch_memory("http://get-ws-proxy.r-universe.dev:443", handle = h) + if(req$status == 200){ + wsproxy <- rawToChar(req$content) + if(grepl('^socks5h://', wsproxy)){ + Sys.setenv(ALL_PROXY = wsproxy) + } + } + }, silent = TRUE) + } } diff --git a/src/library/curl/R/options.R b/src/library/curl/R/options.R index 18792861d9..48ffbba40a 100644 --- a/src/library/curl/R/options.R +++ b/src/library/curl/R/options.R @@ -1,12 +1,13 @@ #' List curl version and options. #' -#' \code{curl_version()} shows the versions of libcurl, libssl and zlib and -#' supported protocols. \code{curl_options()} lists all options available in -#' the current version of libcurl. The dataset \code{curl_symbols} lists all +#' `curl_version()` shows the versions of libcurl, libssl and zlib and +#' supported protocols. `curl_options()` lists all options available in +#' the current version of libcurl. The dataset `curl_symbols` lists all #' symbols (including options) provides more information about the symbols, #' including when support was added/removed from libcurl. #' #' @export +#' @rdname curl_options #' @param filter string: only return options with string in name #' @examples # Available options #' curl_options() @@ -17,48 +18,31 @@ #' # Symbol table #' curl_symbols("proxy") curl_options <- function(filter = ""){ - opts <- curl_options_list() + option_type_table <- make_option_type_table() + opts <- structure(option_type_table$value, names = option_type_table$name) m <- grep(filter, names(opts), ignore.case = TRUE) opts[m] } -option_table <- (function(){ - env <- new.env() - if(file.exists("tools/option_table.txt")){ - source("tools/option_table.txt", env) - } else if(file.exists("../tools/option_table.txt")){ - source("../tools/option_table.txt", env) - } else { - stop("Failed to find 'tools/option_table.txt' from:", getwd()) - } - - option_table <- unlist(as.list(env)) - names(option_table) <- sub("^curlopt_", "", tolower(names(option_table))) - option_table[order(names(option_table))] -})() - - -#' @useDynLib curl R_option_types -make_option_type_table <- function(){ - # Only available for libcurl 7.73 and up. - out <- .Call(R_option_types) - if(!length(out)) return(out) - out$name <- tolower(out$name) - out$type <- factor(out$type, levels = 0:8, labels = c("long", "values", "off_t", - "object", "string", "slist", "cbptr", "blob", "function")) - structure(out, class = 'data.frame', row.names = seq_along(out$name)) +#' @export +#' @rdname curl_options +curl_options_table <- function(filter = ""){ + option_type_table <- make_option_type_table() + m <- grep(filter, option_type_table$name, ignore.case = TRUE) + option_type_table[m,] } -curl_options_list <- local({ +#' @useDynLib curl R_option_types +make_option_type_table <- local({ cache <- NULL function(){ if(is.null(cache)){ - cache <<- if(length(option_type_table)){ - structure(option_type_table$value, names = option_type_table$name) - } else { - # Fallback method: extracted from headers at build-time - option_table - } + out <- .Call(R_option_types) + if(!length(out)) return(out) + out$name <- tolower(out$name) + out$type <- factor(out$type, levels = 0:8, labels = c("long", "values", "off_t", + "object", "string", "slist", "cbptr", "blob", "function")) + cache <<- structure(out, class = 'data.frame', row.names = seq_along(out$name)) } return(cache) } diff --git a/src/library/curl/R/parser.R b/src/library/curl/R/parser.R new file mode 100644 index 0000000000..777173b2d8 --- /dev/null +++ b/src/library/curl/R/parser.R @@ -0,0 +1,164 @@ +#' Normalizing URL parser +#' +#' Interfaces the libcurl [URL parser](https://curl.se/libcurl/c/libcurl-url.html). +#' URLs are automatically normalized where possible, such as in the case of +#' relative paths or url-encoded queries (see examples). +#' When parsing hyperlinks from a HTML document, it is possible to set `baseurl` +#' to the location of the document itself such that relative links can be resolved. +#' +#' A valid URL contains at least a scheme and a host, other pieces are optional. +#' If these are missing, the parser raises an error. Otherwise it returns +#' a list with the following elements: +#' - *url*: the normalized input URL +#' - *scheme*: the protocol part before the `://` (required) +#' - *host*: name of host without port (required) +#' - *port*: decimal between 0 and 65535 +#' - *path*: normalized path up till the `?` of the url +#' - *query*: search query: part between the `?` and `#` of the url. Use `params` below to get individual parameters from the query. +#' - *fragment*: the hash part after the `#` of the url +#' - *user*: authentication username +#' - *password*: authentication password +#' - *params*: named vector with parameters from `query` if set +#' +#' Each element above is either a string or `NULL`, except for `params` which +#' is always a character vector with the length equal to the number of parameters. +#' +#' Note that the `params` field is only usable if the `query` is in the usual +#' `application/x-www-form-urlencoded` format which is technically not part of +#' the RFC. Some services may use e.g. a json blob as the query, in which case +#' the parsed `params` field here can be ignored. There is no way for the parser +#' to automatically infer or validate the query format, this is up to the caller. +#' +#' For more details on the URL format see +#' [rfc3986](https://datatracker.ietf.org/doc/html/rfc3986) +#' or the steps explained in the [whatwg basic url parser](https://url.spec.whatwg.org/#concept-basic-url-parser). +#' +#' @export +#' @param url a character string of length one +#' @param baseurl use this as the parent if `url` may be a relative path +#' @param decode automatically [url-decode][curl_escape] output into the actual +#' values. If set to `FALSE`, values for `query`, `path`, `fragment`, `user` and `password` are returned in url-encoded format. +#' @param params parse individual parameters assuming query is in `application/x-www-form-urlencoded` format. +#' @param default_scheme when `url` is provided without a scheme prefix, assume `https://`. +#' @useDynLib curl R_parse_url +#' @examples +#' url <- "https://jerry:secret@google.com:888/foo/bar?test=123#bla" +#' curl_parse_url(url) +#' +#' # Resolve relative links from a baseurl +#' curl_parse_url("/somelink", baseurl = url) +#' +#' # Paths get normalized +#' curl_parse_url("https://foobar.com/foo/bar/../baz/../yolo")$url +#' +#' # Also normalizes URL-encoding (these URLs are equivalent): +#' url1 <- "https://ja.wikipedia.org/wiki/\u5bff\u53f8" +#' url2 <- "https://ja.wikipedia.org/wiki/%e5%af%bf%e5%8f%b8" +#' curl_parse_url(url1)$path +#' curl_parse_url(url2)$path +#' curl_parse_url(url1, decode = FALSE)$path +#' curl_parse_url(url1, decode = FALSE)$path +curl_parse_url <- function(url, baseurl = NULL, decode = TRUE, params = TRUE, + default_scheme = FALSE){ + stopifnot(is.character(url)) + stopifnot(length(url) == 1) + baseurl <- as.character(baseurl) + + # Workaround for #366 + if(length(baseurl) && substr(url, 1, 1) == '#'){ + url <- sub('(#.*)?$', url, baseurl) + } + + result <- .Call(R_parse_url, url, baseurl, default_scheme) + result$url <- toupper_url_encoding(result$url) + + + # Need to parse query before url-decoding + if(params){ + tryCatch({ + result$params <- parse_query_urlencoded(result$query) + result$query <- NULL + }, error = message) + } + + result$path <- normalize_field(result$path, decode) + result$query <- normalize_field(result$query, decode) + result$fragment <- normalize_field(result$fragment, decode) + result$user <- normalize_field(result$user, decode) + result$password <- normalize_field(result$password, decode) + result +} + +#' @details You can use [curl_modify_url()] both to modify an existing URL, or to +#' create new URL from scratch. Arguments get automatically URL-encoded where +#' needed, unless wrapped in `I()`. If `params` is given, this gets converted +#' into a `application/x-www-form-urlencoded` string which overrides `query`. +#' When modifying a URL, use an empty string `""` to unset a piece of the URL. +#' @export +#' @rdname curl_parse_url +#' @useDynLib curl R_modify_url +#' @param url either URL string or list returned by [curl_parse_url]. +#' Use this to modify a URL using the other parameters. +#' @param scheme string with e.g. `https`. Required if no `url` parameter was given. +#' @param host string with hostname. Required if no `url` parameter was given. +#' @param port string or number with port, e.g. `"443"`. +#' @param path piece of the url starting with `/` up till `?` or `#` +#' @param query piece of url starting with `?` up till `#`. Only used if no `params` is given. +#' @param fragment part of url starting with `#`. +#' @param user string with username +#' @param password string with password +#' @param params named character vector with http GET parameters. This will automatically +#' be converted to `application/x-www-form-urlencoded` and override `query`, +curl_modify_url <- function(url = NULL, scheme = NULL, host = NULL, port = NULL, path = NULL, + query = NULL, fragment = NULL, user = NULL, password = NULL, params = NULL){ + if(is.list(url)){ + url <- do.call(curl_modify_url, url) + } + if(!is.null(params)){ + query <- I(build_query_urlencoded(params)) + } + port <- as.character(port) + out <- .Call(R_modify_url, url, scheme, host, port, path, query, fragment, user, password) + toupper_url_encoding(out) +} + +toupper_url_encoding <- function(x){ + gsub('(%..)', replacement = '\\U\\1', x, perl = TRUE) +} + +normalize_field <- function(x, decode = TRUE){ + if(is.null(x)){ + return(x) + } + if(isTRUE(decode)){ + curl_unescape(x) + } else { + toupper_url_encoding(x) + } +} + +# Parses a string in 'application/x-www-form-urlencoded' format +parse_query_urlencoded <- function(query){ + if(!length(query)) return(character()) + query <- chartr('+',' ', query) + argstr <- strsplit(query, "&", fixed = TRUE)[[1]] + args <- lapply(argstr, function(x){ + c(curl_unescape(strsplit(x, "=", fixed = TRUE)[[1]]), "") + }) + values <- vapply(args, `[`, character(1), 2) + names(values) <- vapply(args, `[`, character(1), 1) + return(values) +} + +build_query_urlencoded <- function(params){ + if(!is.character(params) || length(names(params)) != length(params)){ + stop("params must be named character vector") + } + nms <- curl_escape(names(params)) + values <- gsub("%20", "+", curl_escape(params), fixed = TRUE) + paste(nms, values, collapse = '&', sep = '=') +} + +try_parse_url <- function(url){ + tryCatch(curl_parse_url(url), error = function(e){}) +} diff --git a/src/library/curl/R/sysdata.rda b/src/library/curl/R/sysdata.rda index dfe072a56b83e1d286a0d81e973ed694701ac7be..5f8884ad52a59e1ef705a44aae0ecbe60f054342 100644 GIT binary patch literal 12702 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 YoiBfgZLHPsow1+(|MSRT#F;|@0AK?Hc>n+a literal 12051 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 diff --git a/src/library/curl/R/upload.R b/src/library/curl/R/upload.R index b6f1b40242..339984d0c5 100644 --- a/src/library/curl/R/upload.R +++ b/src/library/curl/R/upload.R @@ -1,16 +1,16 @@ #' Upload a File #' -#' Upload a file to an \code{http://}, \code{ftp://}, or \code{sftp://} (ssh) -#' server. Uploading to HTTP means performing an \code{HTTP PUT} on that URL. +#' Upload a file to an `http://`, `ftp://`, or `sftp://` (ssh) +#' server. Uploading to HTTP means performing an `HTTP PUT` on that URL. #' Be aware that sftp is only available for libcurl clients built with libssh2. #' #' @export #' @param file connection object or path to an existing file on disk -#' @param url where to upload, should start with e.g. \code{ftp://} +#' @param url where to upload, should start with e.g. `ftp://` #' @param verbose emit some progress output #' @param reuse try to keep alive and recycle connections when possible -#' @param ... other arguments passed to \code{\link{handle_setopt}}, for -#' example a \code{username} and \code{password}. +#' @param ... other arguments passed to [handle_setopt()], for +#' example a `username` and `password`. #' @examples \dontrun{# Upload package to winbuilder: #' curl_upload('mypkg_1.3.tar.gz', 'ftp://win-builder.r-project.org/R-devel/') #' } diff --git a/src/library/curl/R/utilities.R b/src/library/curl/R/utilities.R index 6c4694aaa9..0dcd915b97 100644 --- a/src/library/curl/R/utilities.R +++ b/src/library/curl/R/utilities.R @@ -18,8 +18,8 @@ curl_version <- function(){ #' Parse date/time #' #' Can be used to parse dates appearing in http response headers such -#' as \code{Expires} or \code{Last-Modified}. Automatically recognizes -#' most common formats. If the format is known, \code{\link{strptime}} +#' as `Expires` or `Last-Modified`. Automatically recognizes +#' most common formats. If the format is known, [strptime()] #' might be easier. #' #' @param datestring a string consisting of a timestamp @@ -47,5 +47,34 @@ trimws <- function(x) { } is_string <- function(x){ - is.character(x) && length(x) + is.character(x) && length(x) && nchar(x) +} + +# Callback for typed libcurl errors +raise_libcurl_error <- function(errnum, message, errbuf = NULL, source_url = NULL, error_cb = NULL){ + error_code <- libcurl_error_codes[errnum] + if(is.na(error_code)) + error_code <- NULL #future proof new error codes + if(is_string(source_url)){ + host <- try_parse_url(source_url)$host + if(is_string(host)) + message <- sprintf('%s [%s]', message, host) + } + if(is_string(errbuf)){ + message <- sprintf('%s:\n%s', message, errbuf) + } + if(is.function(error_cb)){ + if(length(formals(error_cb)) > 0){ + error_cb(structure(message, class = c(error_code, "curl_error", "character"))) + } else { + error_cb() + } + } else { + cl <- sys.call(-1) + e <- structure( + class = c(error_code, "curl_error", "error", "condition"), + list(message = message, call = cl) + ) + stop(e) + } } diff --git a/src/library/curl/R/writer.R b/src/library/curl/R/writer.R index 48942d0b06..6c85e69ebe 100644 --- a/src/library/curl/R/writer.R +++ b/src/library/curl/R/writer.R @@ -3,15 +3,15 @@ #' Generates a closure that writes binary (raw) data to a file. #' #' The writer function automatically opens the file on the first write and closes when -#' it goes out of scope, or explicitly by setting \code{close = TRUE}. This can be used -#' for the \code{data} callback in \code{multi_add()} or \code{curl_fetch_multi()} such +#' it goes out of scope, or explicitly by setting `close = TRUE`. This can be used +#' for the `data` callback in `multi_add()` or `curl_fetch_multi()` such #' that we only keep open file handles for active downloads. This prevents running out #' of file descriptors when performing thousands of concurrent requests. #' #' @export #' @param path file name or path on disk #' @param append open file in append mode -#' @return Function with signature \code{writer(data = raw(), close = FALSE)} +#' @return Function with signature `writer(data = raw(), close = FALSE)` #' @examples #' # Doesn't open yet #' tmp <- tempfile() diff --git a/src/library/curl/cleanup b/src/library/curl/cleanup index 894bb4d65f..6d7a5f039a 100755 --- a/src/library/curl/cleanup +++ b/src/library/curl/cleanup @@ -1,2 +1,3 @@ #!/bin/sh -rm -f src/Makevars configure.log +rm -f src/Makevars configure.log get-curl-linux.sh +rm -Rf .deps autobrew diff --git a/src/library/curl/configure b/src/library/curl/configure index 153309a239..d84579f1cf 100755 --- a/src/library/curl/configure +++ b/src/library/curl/configure @@ -1,5 +1,5 @@ #!/bin/sh -# Anticonf (tm) script by Jeroen Ooms (2022) +# Anticonf (tm) script by Jeroen Ooms (2025) # This script will query 'pkg-config' for the required cflags and ldflags. # If pkg-config is unavailable or does not find the library, try setting # INCLUDE_DIR and LIB_DIR manually via e.g: @@ -9,14 +9,21 @@ PKG_CONFIG_NAME="libcurl" PKG_DEB_NAME="libcurl4-openssl-dev" PKG_RPM_NAME="libcurl-devel" +PKG_APK_NAME="curl-dev" PKG_TEST_HEADER="" PKG_LIBS="-lcurl" PKG_CFLAGS="" -# export PKG_CONFIG_PATH="/usr/local/opt/curl/lib/pkgconfig" +#export PKG_CONFIG_PATH="/opt/homebrew/opt/curl/lib/pkgconfig" + +# (Jan 2025) MacOS ships a very buggy libcurl 8.7.1 so we avoid this until apple updates it +# See: https://github.com/jeroen/curl/issues/376 +if [ `uname` = "Darwin" ]; then +MINVERSION="--atleast-version=8.8.0" +fi # Use pkg-config if available -pkg-config --version >/dev/null 2>&1 +pkg-config ${PKG_CONFIG_NAME} ${MINVERSION} 2>/dev/null if [ $? -eq 0 ]; then PKGCONFIG_CFLAGS=`pkg-config --cflags ${PKG_CONFIG_NAME}` case "$PKGCONFIG_CFLAGS" in @@ -26,7 +33,11 @@ if [ $? -eq 0 ]; then fi # Note that cflags may be empty in case of success -if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then +if [ "$CURL_CFLAGS" ] || [ "$CURL_LIBS" ]; then + echo "Found CURL_CFLAGS and/or CURL_LIBS!" + PKG_CFLAGS="$CURL_CFLAGS" + PKG_LIBS="$CURL_LIBS" +elif [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then echo "Found INCLUDE_DIR and/or LIB_DIR!" PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS" PKG_LIBS="-L$LIB_DIR $PKG_LIBS" @@ -34,6 +45,10 @@ elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then echo "Found pkg-config cflags and libs!" PKG_CFLAGS=${PKGCONFIG_CFLAGS} PKG_LIBS=${PKGCONFIG_LIBS} +elif [ `uname` = "Darwin" ]; then + # Temporary fix for: https://github.com/jeroen/curl/issues/376 + curl -sfL "https://autobrew.github.io/scripts/libcurl-macos" > autobrew + . ./autobrew fi # Find compiler @@ -54,6 +69,7 @@ if [ $? -ne 0 ]; then echo "Configuration failed because $PKG_CONFIG_NAME was not found. Try installing:" echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)" echo " * rpm: $PKG_RPM_NAME (Fedora, CentOS, RHEL)" + echo " * apk: $PKG_APK_NAME (Alpine)" echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your" echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config" echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:" @@ -64,21 +80,26 @@ if [ $? -ne 0 ]; then exit 1 fi -# Disable purling on oldrel due to https://github.com/yihui/knitr/issues/2338 -if [ `uname` = "Darwin" ] && [ "${R_VERSION}" = "4.3.3" ]; then - for file in vignettes/*.Rmd; do - sed -i '' '/```/,$d' $file || true - touch inst/doc/* || true - done +# Test minimum version +${CC} -E ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} tools/version.c >/dev/null 2>&1 +if [ $? -ne 0 ]; then + +# On curl < 7.73 (RHEL-8 / Ubuntu 20.04) enable the static library +if [ `uname` = "Linux" ]; then + echo "Local libcurl is too old. Downloading a static libcurl for legacy Linux..." + echo "For alternative solutions see: https://github.com/jeroen/curl/issues/416" + curl -sOL "https://github.com/jeroen/curl/releases/download/libcurl-8.14.1/get-curl-linux.sh" && . ./get-curl-linux.sh +fi + +# MacOS 13 CRAN builder has MacOS 11.3 SDK +# Also for people compiling from source on MacOS 11 +if [ `uname` = "Darwin" ] && [ -z "$BREWDIR" ] && ${R_HOME}/bin/Rscript --vanilla tools/testversion.R "7.80"; then + PKG_CFLAGS="$PKG_CFLAGS -DENABLE_MACOS_POLYFILL" +fi fi # Write to Makevars sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars -# Extract curlopt symbols -echo '#include ' | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - \ - | grep "^[ \t]*CURLOPT_.*," | sed s/,// | sed 's/__attribute__[(][(].*[)][)] =/=/' \ - > tools/option_table.txt - # Success exit 0 diff --git a/src/library/curl/inst/WORDLIST b/src/library/curl/inst/WORDLIST index 941a588784..6cc6f25dda 100644 --- a/src/library/curl/inst/WORDLIST +++ b/src/library/curl/inst/WORDLIST @@ -11,7 +11,8 @@ MacOS OpenSSL PEM RHEL -Rtools +RStudio +SCP SMTP SMTPS SSL @@ -21,6 +22,7 @@ TLS async auth config +customizable dev dns enum @@ -36,17 +38,21 @@ httpuv httr ip ipv +json libcurl libssh libssl netscape openssl +params preconfigured proxying rb +rfc sftp slist smtp ssl webservers +whatwg zlib diff --git a/src/library/curl/src/Makevars.in b/src/library/curl/src/Makevars.in index 8d833e2ef2..09dee0a919 100644 --- a/src/library/curl/src/Makevars.in +++ b/src/library/curl/src/Makevars.in @@ -2,7 +2,7 @@ PKG_CFLAGS=$(C_VISIBILITY) PKG_CPPFLAGS=@cflags@ -DSTRICT_R_HEADERS -DR_NO_REMAP PKG_LIBS=@libs@ -all: clean +all: $(SHLIB) cleanup -clean: - rm -f $(SHLIB) $(OBJECTS) +cleanup: $(SHLIB) + @rm -Rf ../.deps diff --git a/src/library/curl/src/Makevars.win b/src/library/curl/src/Makevars.win index 8019d6287b..5ebb0b8b57 100644 --- a/src/library/curl/src/Makevars.win +++ b/src/library/curl/src/Makevars.win @@ -1,4 +1,4 @@ -RWINLIB = ../windows/libcurl +RWINLIB = ../.deps/libcurl TARGET = lib$(subst gcc,,$(COMPILED_BY))$(R_ARCH) PKG_LIBS = \ @@ -9,13 +9,13 @@ PKG_LIBS = \ PKG_CPPFLAGS= \ -I$(RWINLIB)/include -DCURL_STATICLIB -DSTRICT_R_HEADERS -DR_NO_REMAP -all: clean winlibs +all: $(SHLIB) cleanup -clean: - rm -f $(SHLIB) $(OBJECTS) +# Needed for parallel make +$(OBJECTS): | $(RWINLIB) -winlibs: clean - "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" - echo '#include ' | $(CC) $(PKG_CPPFLAGS) -std=gnu99 -E -xc - | grep "^[ \t]*CURLOPT_.*," | sed s/,// > ../tools/option_table.txt +$(RWINLIB): + @"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" -.PHONY: all winlibs clean +cleanup: $(SHLIB) + @rm -Rf $(RWINLIB) diff --git a/src/library/curl/src/callbacks.c b/src/library/curl/src/callbacks.c index a01dfe158c..6c70ddc040 100644 --- a/src/library/curl/src/callbacks.c +++ b/src/library/curl/src/callbacks.c @@ -51,7 +51,7 @@ size_t R_curl_callback_read(char *buffer, size_t size, size_t nitems, SEXP fun) } size_t bytes_read = Rf_length(res); - memcpy(buffer, RAW(res), bytes_read); + if (bytes_read) memcpy(buffer, RAW_RO(res), bytes_read); UNPROTECT(3); return bytes_read; @@ -73,7 +73,7 @@ int R_curl_callback_debug(CURL *handle, curl_infotype type_, char *data, /* wrap type and msg into R types */ SEXP type = PROTECT(Rf_ScalarInteger(type_)); SEXP msg = PROTECT(Rf_allocVector(RAWSXP, size)); - memcpy(RAW(msg), data, size); + if (size) memcpy(RAW(msg), data, size); /* call the R function */ SEXP call = PROTECT(Rf_lang3(fun, type, msg)); diff --git a/src/library/curl/src/curl-common.h b/src/library/curl/src/curl-common.h index 465510ba45..56f0c8ca0b 100644 --- a/src/library/curl/src/curl-common.h +++ b/src/library/curl/src/curl-common.h @@ -8,18 +8,15 @@ #include #include -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 28) -#define HAS_MULTI_WAIT 1 -#endif +#define make_string(x) x != NULL ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) +#define get_string(x) CHAR(STRING_ELT(x, 0)) +#define len_string(x) Rf_length(STRING_ELT(x, 0)) +#define assert(x) assert_message(x, NULL) -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 55) -#define USE_CURL_OFF_T 1 -#endif -#ifndef DISABLE_CURL_EASY_OPTION -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 73) -#define HAS_CURL_EASY_OPTION 1 -#endif +//RHEL-9 has curl 7.76.1 +#if CURL_AT_LEAST_VERSION(7,80,0) +#define HAS_CURL_PARSER_STRERROR 1 #endif typedef struct { @@ -57,10 +54,10 @@ typedef struct { CURL* get_handle(SEXP ptr); reference* get_ref(SEXP ptr); +void raise_libcurl_error(CURLcode res, reference *ref, SEXP error_cb); void assert_status(CURLcode res, reference *ref); -void assert(CURLcode res); +void assert_message(CURLcode res, const char *str); void massert(CURLMcode res); -void stop_for_status(CURL *http_handle); SEXP slist_to_vec(struct curl_slist *slist); struct curl_slist* vec_to_slist(SEXP vec); struct curl_httppost* make_form(SEXP form); @@ -79,3 +76,8 @@ SEXP make_handle_response(reference *ref); SEXP reflist_init(void); SEXP reflist_add(SEXP x, SEXP target); SEXP reflist_remove(SEXP x, SEXP target); + +/* Workaround for CRAN using outdated MacOS11 SDK */ +#if defined(__APPLE__) && defined(ENABLE_MACOS_POLYFILL) && !CURL_AT_LEAST_VERSION(7,81,0) +#include "macos-polyfill.h" +#endif diff --git a/src/library/curl/src/curl.c b/src/library/curl/src/curl.c index 29b3d66006..503f578fc8 100644 --- a/src/library/curl/src/curl.c +++ b/src/library/curl/src/curl.c @@ -98,7 +98,7 @@ static size_t pop(void *target, size_t max, request *req){ return copy_size; } -void check_manager(CURLM *manager, reference *ref) { +static void check_handles(CURLM *manager, reference *ref) { for(int msg = 1; msg > 0;){ CURLMsg *out = curl_multi_info_read(manager, &msg); if(out) @@ -106,31 +106,16 @@ void check_manager(CURLM *manager, reference *ref) { } } -//NOTE: renamed because the name 'fetch' caused crash/conflict on Solaris. -void fetchdata(request *req) { +static void fetchdata(request *req) { R_CheckUserInterrupt(); - long timeout = 10*1000; - massert(curl_multi_timeout(req->manager, &timeout)); - /* massert(curl_multi_perform(req->manager, &(req->has_more))); */ - - /* On libcurl < 7.20 we need to check for CURLM_CALL_MULTI_PERFORM, see docs */ - CURLMcode res = CURLM_CALL_MULTI_PERFORM; - while(res == CURLM_CALL_MULTI_PERFORM){ - res = curl_multi_perform(req->manager, &(req->has_more)); - } - massert(res); - /* End */ - check_manager(req->manager, req->ref); + massert(curl_multi_perform(req->manager, &(req->has_more))); + check_handles(req->manager, req->ref); } -/* Support for readBin() */ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { request *req = (request*) con->private; size_t req_size = sz * ni; - - /* append data to the target buffer */ size_t total_size = pop(target, req_size, req); - if (total_size > 0 && (!con->blocking || req->partial)) { // If we can return data without waiting, and the connection is // non-blocking (or using curl_fetch_stream()), do so. @@ -141,12 +126,9 @@ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { } while((req_size > total_size) && req->has_more) { - /* wait for activity, timeout or "nothing" */ -#ifdef HAS_MULTI_WAIT int numfds; - if(con->blocking) - massert(curl_multi_wait(req->manager, NULL, 0, 1000, &numfds)); -#endif + massert(curl_multi_wait(req->manager, NULL, 0, con->blocking ? 1000 : 10, &numfds)); + fetchdata(req); total_size += pop((char*)target + total_size, (req_size-total_size), req); @@ -158,7 +140,6 @@ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { return total_size; } -/* naive implementation of readLines */ static int rcurl_fgetc(Rconnection con) { int x = 0; #ifdef WORDS_BIGENDIAN @@ -168,13 +149,15 @@ static int rcurl_fgetc(Rconnection con) { #endif } -void cleanup(Rconnection con) { - //Rprintf("Destroying connection.\n"); +static void cleanup(Rconnection con) { request *req = (request*) con->private; reference *ref = req->ref; /* free thee handle connection */ curl_multi_remove_handle(req->manager, req->handle); + curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(req->handle, CURLOPT_FAILONERROR, 0L); ref->locked = 0; /* delayed finalizer cleanup */ @@ -189,10 +172,12 @@ void cleanup(Rconnection con) { } /* reset to pre-opened state */ -void reset(Rconnection con) { - //Rprintf("Resetting connection object.\n"); +static void reset(Rconnection con) { request *req = (request*) con->private; curl_multi_remove_handle(req->manager, req->handle); + curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(req->handle, CURLOPT_FAILONERROR, 0L); req->ref->locked = 0; con->isopen = FALSE; con->text = TRUE; @@ -231,22 +216,33 @@ static Rboolean rcurl_open(Rconnection con) { /* fully non-blocking has 's' in open mode */ int block_open = strchr(con->mode, 's') == NULL; int force_open = strchr(con->mode, 'f') != NULL; + if(block_open && !force_open) + curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L); /* Wait for first data to arrive. Monitoring a change in status code does not suffice in case of http redirects */ while(block_open && req->has_more && !req->has_data) { -#ifdef HAS_MULTI_WAIT int numfds; massert(curl_multi_wait(req->manager, NULL, 0, 1000, &numfds)); -#endif - fetchdata(req); + if(pending_interrupt()) { + reset(con); //cleanup before jumping + assert_message(CURLE_ABORTED_BY_CALLBACK, NULL); + } + massert(curl_multi_perform(req->manager, &(req->has_more))); + for(int msg = 1; msg > 0;){ + CURLMsg *out = curl_multi_info_read(req->manager, &msg); + if(out && out->data.result != CURLE_OK){ + const char *errmsg = strlen(req->ref->errbuf) ? req->ref->errbuf : curl_easy_strerror(out->data.result); + Rf_warningcall(R_NilValue, "Failed to open '%s': %s", req->url, errmsg); + reset(con); + return FALSE; + } + } } /* check http status code */ /* Stream connections should be checked via handle_data() */ /* Non-blocking open connections get checked during read */ - if(block_open && !force_open) - stop_for_status(handle); /* set mode in case open() changed it */ con->text = strchr(con->mode, 'b') ? FALSE : TRUE; @@ -298,6 +294,9 @@ SEXP R_curl_connection(SEXP url, SEXP ptr, SEXP partial) { /* protect the handle */ (req->ref->refCount)++; + /* store the CURLM address in con->ex_ptr which is the 'conn_id' attribute */ + R_SetExternalPtrAddr((SEXP) con->ex_ptr, req->manager); + UNPROTECT(1); return rc; } diff --git a/src/library/curl/src/download.c b/src/library/curl/src/download.c index 1f149aa42a..12fe4d39e0 100644 --- a/src/library/curl/src/download.c +++ b/src/library/curl/src/download.c @@ -27,25 +27,23 @@ SEXP R_download_curl(SEXP url, SEXP destfile, SEXP quiet, SEXP mode, SEXP ptr, S /* set options */ curl_easy_setopt(handle, CURLOPT_URL, Rf_translateCharUTF8(Rf_asChar(url))); - curl_easy_setopt(handle, CURLOPT_NOPROGRESS, Rf_asLogical(quiet)); + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, (long) Rf_asLogical(quiet)); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, push_disk); curl_easy_setopt(handle, CURLOPT_WRITEDATA, dest); + curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L); /* perform blocking request */ CURLcode status = Rf_asLogical(nonblocking) ? curl_perform_with_interrupt(handle) : curl_easy_perform(handle); /* cleanup */ - curl_easy_setopt(handle, CURLOPT_URL, NULL); - curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); + curl_easy_setopt(handle, CURLOPT_FAILONERROR, 0L); fclose(dest); /* raise for curl errors */ assert_status(status, get_ref(ptr)); - - /* check for success */ - stop_for_status(handle); return Rf_ScalarInteger(0); } diff --git a/src/library/curl/src/dryrun.c b/src/library/curl/src/dryrun.c new file mode 100644 index 0000000000..752883cd36 --- /dev/null +++ b/src/library/curl/src/dryrun.c @@ -0,0 +1,44 @@ +/* * + * Blocking easy interfaces to libcurl for R. + * Example: https://curl.se/libcurl/c/getinmemory.html + */ + +#include "curl-common.h" + +static size_t write_nothing(void *contents, size_t sz, size_t nmemb, void *ctx) { + return sz * nmemb; +} + +static void run_httpuv(void *dummy) { + SEXP expr = PROTECT(Rf_lang1(Rf_install("later_wrapper"))); + SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); + Rf_eval(expr, env); + UNPROTECT(2); +} + +static int process_server(void) { + return !(R_ToplevelExec(run_httpuv, NULL)); +} + +SEXP R_curl_dryrun(SEXP ptr){ + CURL *handle = get_handle(ptr); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_nothing); + CURLM * multi_handle = curl_multi_init(); + if(CURLM_OK != curl_multi_add_handle(multi_handle, handle)) + Rf_error("Failed to add handle"); + int still_running = 1; + while(still_running) { + if(process_server()) + break; + if(curl_multi_perform(multi_handle, &(still_running)) != CURLM_OK) + break; + } + int msgq = 0; + CURLMsg *m = curl_multi_info_read(multi_handle, &msgq); + CURLcode status = (m && (m->msg == CURLMSG_DONE)) ? m->data.result : CURLE_ABORTED_BY_CALLBACK; + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); + curl_multi_remove_handle(multi_handle, handle); + curl_multi_cleanup(multi_handle); + assert_status(status, get_ref(ptr)); + return R_NilValue; +} diff --git a/src/library/curl/src/handle.c b/src/library/curl/src/handle.c index dfc900712f..8dbe9c757d 100644 --- a/src/library/curl/src/handle.c +++ b/src/library/curl/src/handle.c @@ -8,25 +8,11 @@ extern int r_curl_is_off_t_option(CURLoption x); extern int r_curl_is_string_option(CURLoption x); extern int r_curl_is_postfields_option(CURLoption x); -#define make_string(x) x ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) - #ifndef MAX_PATH #define MAX_PATH 1024 #endif -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 47) -#define HAS_HTTP_VERSION_2TLS 1 -#endif - -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 32) -#define HAS_XFERINFOFUNCTION 1 -#endif - -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 36) -#define HAS_CURLOPT_EXPECT_100_TIMEOUT_MS 1 -#endif - -int total_handles = 0; +static int total_handles = 0; void clean_handle(reference *ref){ if(ref->refCount == 0){ @@ -45,7 +31,7 @@ void clean_handle(reference *ref){ } } -void fin_handle(SEXP ptr){ +static void fin_handle(SEXP ptr){ reference *ref = (reference*) R_ExternalPtrAddr(ptr); //this kind of strange but the multi finalizer needs the ptr value @@ -59,15 +45,11 @@ void fin_handle(SEXP ptr){ } /* the default readfunc os fread which can cause R to freeze */ -size_t dummy_read(char *buffer, size_t size, size_t nitems, void *instream){ +static size_t dummy_read(char *buffer, size_t size, size_t nitems, void *instream){ return 0; } -#ifdef HAS_XFERINFOFUNCTION #define xftype curl_off_t -#else -#define xftype double -#endif static int xferinfo_callback(void *clientp, xftype dltotal, xftype dlnow, xftype ultotal, xftype ulnow){ static xftype dlprev = 0; @@ -101,6 +83,20 @@ static struct curl_slist * default_headers(void){ return headers; } +static void assert_setopt(CURLcode res, CURLoption opt, const char *optname){ + if(res != CURLE_OK){ + char errmsg [256] = {0}; + if(opt == CURLOPT_MAIL_RCPT || opt == CURLOPT_MAIL_FROM || opt == CURLOPT_MAIL_AUTH){ + snprintf(errmsg, 256, "Error setting '%s': your libcurl may have disabled SMTP support", optname); + } else { + snprintf(errmsg, 256, "Invalid or unsupported value when setting curl option '%s'", optname); + } + assert_message(CURLE_BAD_FUNCTION_ARGUMENT, errmsg); + } +} + +#define set_user_option(option, value) assert_setopt(curl_easy_setopt(handle, option, value), option, optname) + static void set_headers(reference *ref, struct curl_slist *newheaders){ if(ref->headers) curl_slist_free_all(ref->headers); @@ -161,11 +157,15 @@ static void set_handle_defaults(reference *ref){ /* a sensible timeout (10s) */ assert(curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10L)); + /* error if download is stalled for 10 minutes (prevent CI hangs) */ + assert(curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, 1L)); + assert(curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, 600L)); + /* needed to start the cookie engine */ assert(curl_easy_setopt(handle, CURLOPT_COOKIEFILE, "")); assert(curl_easy_setopt(handle, CURLOPT_FILETIME, 1L)); - /* set the default user agent */ + /* set the default user agent to match base R */ SEXP agent = Rf_GetOption1(Rf_install("HTTPUserAgent")); if(Rf_isString(agent) && Rf_length(agent)){ assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, CHAR(STRING_ELT(agent, 0)))); @@ -173,16 +173,18 @@ static void set_handle_defaults(reference *ref){ assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, "r/curl/jeroen")); } + /* set the default netrc path to match base R */ + SEXP netrc = Rf_GetOption1(Rf_install("netrc")); + if (Rf_isString(netrc) && Rf_length(netrc)) { + const char *path = R_ExpandFileName(CHAR(STRING_ELT(netrc, 0))); + assert(curl_easy_setopt(handle, CURLOPT_NETRC, CURL_NETRC_OPTIONAL)); + assert(curl_easy_setopt(handle, CURLOPT_NETRC_FILE, path)); + } + /* allow all authentication methods */ assert(curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY)); assert(curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)); - /* enables HTTP2 on HTTPS (match behavior of curl cmd util) */ -//#if defined(CURL_VERSION_HTTP2) && defined(HAS_HTTP_VERSION_2TLS) -// if(curl_version_info(CURLVERSION_NOW)->features & CURL_VERSION_HTTP2) -// assert(curl_easy_setopt(handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS)); -//#endif - /* set an error buffer */ assert(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, ref->errbuf)); @@ -190,20 +192,17 @@ static void set_handle_defaults(reference *ref){ assert(curl_easy_setopt(handle, CURLOPT_READFUNCTION, dummy_read)); /* set default progress printer (disabled by default) */ -#ifdef HAS_XFERINFOFUNCTION assert(curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, xferinfo_callback)); -#else - assert(curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, xferinfo_callback)); -#endif /* Disable the 'Expect: 100' header (deprecated in recent libcurl) */ set_headers(ref, NULL); -#ifdef HAS_CURLOPT_EXPECT_100_TIMEOUT_MS assert(curl_easy_setopt(handle, CURLOPT_EXPECT_100_TIMEOUT_MS, 0L)); -#endif /* Send verbose outout to R front-end virtual stderr */ assert(curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, default_verbose_cb)); + + /* Prefer using multiplex when possible */ + assert(curl_easy_setopt(handle, CURLOPT_PIPEWAIT, 1L)); } SEXP R_new_handle(void){ @@ -212,7 +211,7 @@ SEXP R_new_handle(void){ ref->handle = curl_easy_init(); total_handles++; set_handle_defaults(ref); - SEXP prot = PROTECT(Rf_allocVector(VECSXP, 7)); //for protecting callback functions + SEXP prot = PROTECT(Rf_allocVector(VECSXP, 8)); //for protecting callback functions SEXP ptr = PROTECT(R_MakeExternalPtr(ref, R_NilValue, prot)); R_RegisterCFinalizerEx(ptr, fin_handle, TRUE); Rf_setAttrib(ptr, R_ClassSymbol, Rf_mkString("curl_handle")); @@ -266,62 +265,53 @@ SEXP R_handle_setopt(SEXP ptr, SEXP keys, SEXP values){ const char* optname = CHAR(STRING_ELT(optnames, i)); SEXP val = VECTOR_ELT(values, i); if(val == R_NilValue){ - assert(curl_easy_setopt(handle, key, NULL)); -#ifdef HAS_XFERINFOFUNCTION + set_user_option(key, NULL); } else if (key == CURLOPT_XFERINFOFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - assert(curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, - (curl_progress_callback) R_curl_callback_xferinfo)); - assert(curl_easy_setopt(handle, CURLOPT_XFERINFODATA, val)); - assert(curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0)); + set_user_option(CURLOPT_XFERINFOFUNCTION, (curl_xferinfo_callback) R_curl_callback_xferinfo); + set_user_option(CURLOPT_XFERINFODATA, val); + set_user_option(CURLOPT_NOPROGRESS, 0L); SET_VECTOR_ELT(prot, 1, val); //protect gc -#endif } else if (key == CURLOPT_PROGRESSFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - assert(curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, - (curl_progress_callback) R_curl_callback_progress)); - assert(curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, val)); - assert(curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0)); + set_user_option(CURLOPT_PROGRESSFUNCTION,(curl_progress_callback) R_curl_callback_progress); + set_user_option(CURLOPT_PROGRESSDATA, val); + set_user_option(CURLOPT_NOPROGRESS, 0L); SET_VECTOR_ELT(prot, 2, val); //protect gc } else if (key == CURLOPT_READFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - assert(curl_easy_setopt(handle, CURLOPT_READFUNCTION, - (curl_read_callback) R_curl_callback_read)); - assert(curl_easy_setopt(handle, CURLOPT_READDATA, val)); + set_user_option(CURLOPT_READFUNCTION, (curl_read_callback) R_curl_callback_read); + set_user_option(CURLOPT_READDATA, val); SET_VECTOR_ELT(prot, 3, val); //protect gc } else if (key == CURLOPT_DEBUGFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - - assert(curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, - (curl_debug_callback) R_curl_callback_debug)); - assert(curl_easy_setopt(handle, CURLOPT_DEBUGDATA, val)); + set_user_option(CURLOPT_DEBUGFUNCTION, (curl_debug_callback) R_curl_callback_debug); + set_user_option(CURLOPT_DEBUGDATA, val); SET_VECTOR_ELT(prot, 4, val); //protect gc } else if (key == CURLOPT_SSL_CTX_FUNCTION){ if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - assert(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, - (curl_ssl_ctx_callback) R_curl_callback_ssl_ctx)); - assert(curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, val)); + set_user_option(CURLOPT_SSL_CTX_FUNCTION, (curl_ssl_ctx_callback) R_curl_callback_ssl_ctx); + set_user_option(CURLOPT_SSL_CTX_DATA, val); SET_VECTOR_ELT(prot, 5, val); //protect gc } else if (key == CURLOPT_SEEKFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - assert(curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, - (curl_seek_callback) R_curl_callback_seek)); - assert(curl_easy_setopt(handle, CURLOPT_SEEKDATA, val)); + set_user_option(CURLOPT_SEEKFUNCTION, (curl_seek_callback) R_curl_callback_seek); + set_user_option(CURLOPT_SEEKDATA, val); SET_VECTOR_ELT(prot, 6, val); //protect gc } else if (key == CURLOPT_URL) { /* always use utf-8 for urls */ const char * url_utf8 = Rf_translateCharUTF8(STRING_ELT(val, 0)); - assert(curl_easy_setopt(handle, CURLOPT_URL, url_utf8)); + set_user_option(CURLOPT_URL, url_utf8); } else if(key == CURLOPT_HTTPHEADER){ if(!Rf_isString(val)) Rf_error("Value for option %s (%d) must be a string vector", optname, key); @@ -330,31 +320,30 @@ SEXP R_handle_setopt(SEXP ptr, SEXP keys, SEXP values){ if(!Rf_isString(val)) Rf_error("Value for option %s (%d) must be a string vector", optname, key); ref->custom = vec_to_slist(val); - assert(curl_easy_setopt(handle, key, ref->custom)); + set_user_option(key, ref->custom); } else if(r_curl_is_long_option(key)){ - if(!Rf_isNumeric(val) || Rf_length(val) != 1) { + if(!Rf_isNumeric(val) || Rf_length(val) != 1) Rf_error("Value for option %s (%d) must be a number.", optname, key); - } - assert(curl_easy_setopt(handle, key, (long) Rf_asInteger(val))); + set_user_option(key, (long) Rf_asInteger(val)); } else if(r_curl_is_off_t_option(key)){ - if(!Rf_isNumeric(val) || Rf_length(val) != 1) { + if(!Rf_isNumeric(val) || Rf_length(val) != 1) Rf_error("Value for option %s (%d) must be a number.", optname, key); - } - assert(curl_easy_setopt(handle, key, (curl_off_t) Rf_asReal(val))); + set_user_option(key, (curl_off_t) Rf_asReal(val)); } else if(r_curl_is_postfields_option(key) || r_curl_is_string_option(key)){ - if(key == CURLOPT_POSTFIELDS){ - key = CURLOPT_COPYPOSTFIELDS; + if(r_curl_is_postfields_option(key)){ + key = CURLOPT_POSTFIELDS; //avoid bug #313 + SET_VECTOR_ELT(prot, 7, val); } switch (TYPEOF(val)) { case RAWSXP: - if(key == CURLOPT_COPYPOSTFIELDS) - assert(curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) Rf_length(val))); - assert(curl_easy_setopt(handle, key, RAW(val))); + if(key == CURLOPT_POSTFIELDS) + set_user_option(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) Rf_xlength(val)); + set_user_option(key, RAW(val)); break; case STRSXP: if (Rf_length(val) != 1) Rf_error("Value for option %s (%d) must be length-1 string", optname, key); - assert(curl_easy_setopt(handle, key, CHAR(STRING_ELT(val, 0)))); + set_user_option(key, CHAR(STRING_ELT(val, 0))); break; default: Rf_error("Value for option %s (%d) must be a string or raw vector.", optname, key); @@ -374,7 +363,7 @@ SEXP R_handle_setform(SEXP ptr, SEXP form){ return Rf_ScalarLogical(1); } -SEXP make_timevec(CURL *handle){ +static SEXP make_timevec(CURL *handle){ double time_redirect, time_lookup, time_connect, time_pre, time_start, time_total; assert(curl_easy_getinfo(handle, CURLINFO_REDIRECT_TIME, &time_redirect)); assert(curl_easy_getinfo(handle, CURLINFO_NAMELOOKUP_TIME, &time_lookup)); @@ -404,7 +393,7 @@ SEXP make_timevec(CURL *handle){ } /* Extract current cookies (state) from handle */ -SEXP make_cookievec(CURL *handle){ +static SEXP make_cookievec(CURL *handle){ /* linked list of strings */ struct curl_slist *cookies; assert(curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies)); @@ -413,25 +402,35 @@ SEXP make_cookievec(CURL *handle){ return out; } -SEXP make_status(CURL *handle){ +static SEXP make_info_integer(CURL *handle, CURLINFO info){ long res_status; - assert(curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res_status)); + assert(curl_easy_getinfo(handle, info, &res_status)); return Rf_ScalarInteger(res_status); } -SEXP make_ctype(CURL *handle){ - char * ct; - assert(curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &ct)); - return make_string(ct); +static SEXP make_info_string(CURL *handle, CURLINFO info){ + char *res_url = NULL; + assert(curl_easy_getinfo(handle, info, &res_url)); + return make_string(res_url); } -SEXP make_url(CURL *handle){ - char *res_url; - assert(curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &res_url)); - return Rf_ScalarString(Rf_mkCharCE(res_url, CE_UTF8)); +static SEXP make_info_http_version(CURL * handle){ + long res = 0; + assert(curl_easy_getinfo(handle, CURLINFO_HTTP_VERSION, &res)); + switch (res) { + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + return Rf_ScalarInteger(1); + case CURL_HTTP_VERSION_2_0: + return Rf_ScalarInteger(2); + case CURL_HTTP_VERSION_3: + return Rf_ScalarInteger(3); + default: + return Rf_ScalarInteger(NA_INTEGER); + } } -SEXP make_filetime(CURL *handle){ +static SEXP make_filetime(CURL *handle){ long filetime; assert(curl_easy_getinfo(handle, CURLINFO_FILETIME, &filetime)); if(filetime < 0){ @@ -448,7 +447,7 @@ SEXP make_filetime(CURL *handle){ return out; } -SEXP make_rawvec(unsigned char *ptr, size_t size){ +static SEXP make_rawvec(unsigned char *ptr, size_t size){ SEXP out = PROTECT(Rf_allocVector(RAWSXP, size)); if(size > 0) memcpy(RAW(out), ptr, size); @@ -456,15 +455,18 @@ SEXP make_rawvec(unsigned char *ptr, size_t size){ return out; } -SEXP make_namesvec(void){ - SEXP names = PROTECT(Rf_allocVector(STRSXP, 7)); +static SEXP make_namesvec(void){ + SEXP names = PROTECT(Rf_allocVector(STRSXP, 10)); SET_STRING_ELT(names, 0, Rf_mkChar("url")); SET_STRING_ELT(names, 1, Rf_mkChar("status_code")); SET_STRING_ELT(names, 2, Rf_mkChar("type")); SET_STRING_ELT(names, 3, Rf_mkChar("headers")); SET_STRING_ELT(names, 4, Rf_mkChar("modified")); SET_STRING_ELT(names, 5, Rf_mkChar("times")); - SET_STRING_ELT(names, 6, Rf_mkChar("content")); + SET_STRING_ELT(names, 6, Rf_mkChar("scheme")); + SET_STRING_ELT(names, 7, Rf_mkChar("http_version")); + SET_STRING_ELT(names, 8, Rf_mkChar("method")); + SET_STRING_ELT(names, 9, Rf_mkChar("content")); UNPROTECT(1); return names; } @@ -475,14 +477,17 @@ SEXP R_get_handle_cookies(SEXP ptr){ SEXP make_handle_response(reference *ref){ CURL *handle = ref->handle; - SEXP res = PROTECT(Rf_allocVector(VECSXP, 7)); - SET_VECTOR_ELT(res, 0, make_url(handle)); - SET_VECTOR_ELT(res, 1, make_status(handle)); - SET_VECTOR_ELT(res, 2, make_ctype(handle)); + SEXP res = PROTECT(Rf_allocVector(VECSXP, 10)); + SET_VECTOR_ELT(res, 0, make_info_string(handle, CURLINFO_EFFECTIVE_URL)); + SET_VECTOR_ELT(res, 1, make_info_integer(handle, CURLINFO_RESPONSE_CODE)); + SET_VECTOR_ELT(res, 2, make_info_string(handle, CURLINFO_CONTENT_TYPE)); SET_VECTOR_ELT(res, 3, make_rawvec(ref->resheaders.buf, ref->resheaders.size)); SET_VECTOR_ELT(res, 4, make_filetime(handle)); SET_VECTOR_ELT(res, 5, make_timevec(handle)); - SET_VECTOR_ELT(res, 6, R_NilValue); + SET_VECTOR_ELT(res, 6, make_info_string(handle, CURLINFO_SCHEME)); + SET_VECTOR_ELT(res, 7, make_info_http_version(handle)); + SET_VECTOR_ELT(res, 8, make_info_string(handle, CURLINFO_EFFECTIVE_METHOD)); + SET_VECTOR_ELT(res, 9, R_NilValue); Rf_setAttrib(res, R_NamesSymbol, make_namesvec()); UNPROTECT(1); return res; @@ -496,17 +501,10 @@ SEXP R_get_handle_response(SEXP ptr){ SEXP R_get_handle_speed(SEXP ptr){ CURL *handle = get_handle(ptr); -#ifdef USE_CURL_OFF_T curl_off_t dl = 0; curl_off_t ul = 0; curl_easy_getinfo(handle, CURLINFO_SPEED_DOWNLOAD_T, &dl); curl_easy_getinfo(handle, CURLINFO_SPEED_UPLOAD_T, &ul); -#else - double dl = 0; - double ul = 0; - curl_easy_getinfo(handle, CURLINFO_SPEED_DOWNLOAD, &dl); - curl_easy_getinfo(handle, CURLINFO_SPEED_UPLOAD, &ul); -#endif SEXP out = Rf_allocVector(REALSXP, 2); REAL(out)[0] = (double) dl; REAL(out)[1] = (double) ul; @@ -515,25 +513,15 @@ SEXP R_get_handle_speed(SEXP ptr){ SEXP R_get_handle_clength(SEXP ptr){ CURL *handle = get_handle(ptr); -#ifdef USE_CURL_OFF_T curl_off_t cl = 0; curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); -#else - double cl = 0; - curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &cl); -#endif return Rf_ScalarReal((double) cl < 0 ? NA_REAL : cl); } SEXP R_get_handle_received(SEXP ptr){ CURL *handle = get_handle(ptr); -#ifdef USE_CURL_OFF_T curl_off_t dl = 0; curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD_T, &dl); -#else - double dl = 0; - curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD, &dl); -#endif return Rf_ScalarReal((double) dl); } diff --git a/src/library/curl/src/ieproxy.c b/src/library/curl/src/ieproxy.c index 3fd8504920..207eee228d 100644 --- a/src/library/curl/src/ieproxy.c +++ b/src/library/curl/src/ieproxy.c @@ -125,20 +125,20 @@ SEXP R_get_proxy_for_url(SEXP target_url, SEXP auto_detect, SEXP autoproxy_url){ } //store output data - char buffer[500]; + char buffer[65536]; SEXP vec = PROTECT(Rf_allocVector(VECSXP, 3)); SET_VECTOR_ELT(vec, 0, Rf_ScalarLogical( ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY || ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_DEFAULT_PROXY)); if(ProxyInfo.lpszProxy != NULL) { - wcstombs(buffer, ProxyInfo.lpszProxy, 500); + wcstombs(buffer, ProxyInfo.lpszProxy, 65536); SET_VECTOR_ELT(vec, 1, Rf_mkString(buffer)); GlobalFree((void*) ProxyInfo.lpszProxy); } if(ProxyInfo.lpszProxyBypass != NULL) { - wcstombs(buffer, ProxyInfo.lpszProxyBypass, 500); + wcstombs(buffer, ProxyInfo.lpszProxyBypass, 65536); SET_VECTOR_ELT(vec, 2, Rf_mkString(buffer)); GlobalFree((void*) ProxyInfo.lpszProxyBypass ); } diff --git a/src/library/curl/src/init.c b/src/library/curl/src/init.c index a76a615771..4f86272cf2 100644 --- a/src/library/curl/src/init.c +++ b/src/library/curl/src/init.c @@ -7,6 +7,7 @@ /* .Call calls */ extern SEXP R_curl_connection(SEXP, SEXP, SEXP); +extern SEXP R_curl_dryrun(SEXP); extern SEXP R_curl_escape(SEXP, SEXP); extern SEXP R_curl_fetch_disk(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_curl_fetch_memory(SEXP, SEXP, SEXP); @@ -28,16 +29,18 @@ extern SEXP R_handle_setform(SEXP, SEXP); extern SEXP R_handle_setheaders(SEXP, SEXP); extern SEXP R_handle_setopt(SEXP, SEXP, SEXP); extern SEXP R_option_types(void); +extern SEXP R_modify_url(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_multi_add(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_multi_cancel(SEXP); extern SEXP R_multi_fdset(SEXP); extern SEXP R_multi_list(SEXP); extern SEXP R_multi_new(void); extern SEXP R_multi_run(SEXP, SEXP, SEXP); -extern SEXP R_multi_setopt(SEXP, SEXP, SEXP, SEXP); +extern SEXP R_multi_setopt(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_new_file_writer(SEXP); extern SEXP R_new_handle(void); extern SEXP R_nslookup(SEXP, SEXP); +extern SEXP R_parse_url(SEXP, SEXP, SEXP); extern SEXP R_proxy_info(void); extern SEXP R_split_string(SEXP, SEXP); extern SEXP R_total_handles(void); @@ -47,6 +50,7 @@ extern SEXP R_write_file_writer(SEXP, SEXP, SEXP); static const R_CallMethodDef CallEntries[] = { {"R_curl_connection", (DL_FUNC) &R_curl_connection, 3}, + {"R_curl_dryrun", (DL_FUNC) &R_curl_dryrun, 1}, {"R_curl_escape", (DL_FUNC) &R_curl_escape, 2}, {"R_curl_fetch_disk", (DL_FUNC) &R_curl_fetch_disk, 5}, {"R_curl_fetch_memory", (DL_FUNC) &R_curl_fetch_memory, 3}, @@ -67,16 +71,18 @@ static const R_CallMethodDef CallEntries[] = { {"R_handle_setform", (DL_FUNC) &R_handle_setform, 2}, {"R_handle_setopt", (DL_FUNC) &R_handle_setopt, 3}, {"R_option_types", (DL_FUNC) &R_option_types, 0}, + {"R_modify_url", (DL_FUNC) &R_modify_url, 9}, {"R_multi_add", (DL_FUNC) &R_multi_add, 5}, {"R_multi_cancel", (DL_FUNC) &R_multi_cancel, 1}, {"R_multi_fdset", (DL_FUNC) &R_multi_fdset, 1}, {"R_multi_list", (DL_FUNC) &R_multi_list, 1}, {"R_multi_new", (DL_FUNC) &R_multi_new, 0}, {"R_multi_run", (DL_FUNC) &R_multi_run, 3}, - {"R_multi_setopt", (DL_FUNC) &R_multi_setopt, 4}, + {"R_multi_setopt", (DL_FUNC) &R_multi_setopt, 5}, {"R_new_file_writer", (DL_FUNC) &R_new_file_writer, 1}, {"R_new_handle", (DL_FUNC) &R_new_handle, 0}, {"R_nslookup", (DL_FUNC) &R_nslookup, 2}, + {"R_parse_url", (DL_FUNC) &R_parse_url, 3}, {"R_proxy_info", (DL_FUNC) &R_proxy_info, 0}, {"R_split_string", (DL_FUNC) &R_split_string, 2}, {"R_total_handles", (DL_FUNC) &R_total_handles, 0}, @@ -87,17 +93,17 @@ static const R_CallMethodDef CallEntries[] = { }; void switch_to_openssl_on_vista(void); -CURLM *multi_handle = NULL; +CURLM *shared_multi_handle = NULL; attribute_visible void R_init_curl(DllInfo *info) { switch_to_openssl_on_vista(); curl_global_init(CURL_GLOBAL_DEFAULT); - multi_handle = curl_multi_init(); + shared_multi_handle = curl_multi_init(); R_registerRoutines(info, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(info, FALSE); } attribute_visible void R_unload_curl(DllInfo *info) { - curl_multi_cleanup(multi_handle); + curl_multi_cleanup(shared_multi_handle); //curl_global_cleanup(); } diff --git a/src/library/curl/src/interrupt.c b/src/library/curl/src/interrupt.c index 5366ec7a82..ee6b06a956 100644 --- a/src/library/curl/src/interrupt.c +++ b/src/library/curl/src/interrupt.c @@ -14,15 +14,33 @@ int pending_interrupt(void) { return !(R_ToplevelExec(check_interrupt_fn, NULL)); } +static int str_starts_with(const char *a, const char *b) { + if(strncmp(a, b, strlen(b)) == 0) return 1; + return 0; +} + /* created in init.c */ -extern CURLM * multi_handle; +extern CURLM * shared_multi_handle; /* Don't call Rf_error() until we remove the handle from the multi handle! */ CURLcode curl_perform_with_interrupt(CURL *handle){ /* start settings */ CURLcode status = CURLE_FAILED_INIT; + CURLM * temp_multi_handle = NULL; + CURLM * multi_handle = NULL; int still_running = 1; + /* Do not reuse FTP connections, because it is buggy */ + /* For example https://github.com/jeroen/curl/issues/348 */ + const char *effective_url = NULL; + curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &effective_url); + if(effective_url && str_starts_with(effective_url, "ftp")){ + temp_multi_handle = curl_multi_init(); + multi_handle = temp_multi_handle; + } else { + multi_handle = shared_multi_handle; + } + if(CURLM_OK != curl_multi_add_handle(multi_handle, handle)){ return CURLE_FAILED_INIT; } @@ -34,20 +52,12 @@ CURLcode curl_perform_with_interrupt(CURL *handle){ break; } -#ifdef HAS_MULTI_WAIT - /* wait for activity, timeout or "nothing" */ int numfds; if(curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds) != CURLM_OK) break; -#endif - - /* Required by old versions of libcurl */ - CURLMcode res = CURLM_CALL_MULTI_PERFORM; - while(res == CURLM_CALL_MULTI_PERFORM) - res = curl_multi_perform(multi_handle, &(still_running)); /* check for multi errors */ - if(res != CURLM_OK) + if(curl_multi_perform(multi_handle, &(still_running)) != CURLM_OK) break; } @@ -65,5 +75,7 @@ CURLcode curl_perform_with_interrupt(CURL *handle){ /* cleanup first */ curl_multi_remove_handle(multi_handle, handle); + if(temp_multi_handle) + curl_multi_cleanup(temp_multi_handle); return status; } diff --git a/src/library/curl/src/macos-polyfill.h b/src/library/curl/src/macos-polyfill.h new file mode 100644 index 0000000000..8a85750cec --- /dev/null +++ b/src/library/curl/src/macos-polyfill.h @@ -0,0 +1,42 @@ +/* MacOS-11 SDK headers are older than it's runtime, so we add this polyfill */ + +const char *curl_url_strerror(CURLUcode); +#define CURLINFO_EFFECTIVE_METHOD CURLINFO_STRING + 58 +#define CURL_HTTP_VERSION_3 30 +#define CURLMOPT_MAX_CONCURRENT_STREAMS 16 +#define HAS_CURL_PARSER_STRERROR 1 + +#ifndef CURLINC_OPTIONS_H +#define CURLINC_OPTIONS_H +typedef enum { + CURLOT_LONG, + CURLOT_VALUES, + CURLOT_OFF_T, + CURLOT_OBJECT, + CURLOT_STRING, + CURLOT_SLIST, + CURLOT_CBPTR, + CURLOT_BLOB, + CURLOT_FUNCTION +} curl_easytype; + +#define CURLOT_FLAG_ALIAS (1<<0) +struct curl_easyoption { + const char *name; + CURLoption id; + curl_easytype type; + unsigned int flags; +}; + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_by_name(const char *name); + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_by_id(CURLoption id); + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_next(const struct curl_easyoption *prev); + +#ifdef __cplusplus +#endif +#endif diff --git a/src/library/curl/src/multi.c b/src/library/curl/src/multi.c index 3cf93328d1..3a85a3be3f 100644 --- a/src/library/curl/src/multi.c +++ b/src/library/curl/src/multi.c @@ -11,10 +11,6 @@ * - Use Rf_eval() to callback instead of R_tryEval() to propagate interrupt or error back to C */ -#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 30) -#define HAS_CURLMOPT_MAX_TOTAL_CONNECTIONS 1 -#endif - multiref *get_multiref(SEXP ptr){ if(TYPEOF(ptr) != EXTPTRSXP || !Rf_inherits(ptr, "curl_multi")) Rf_error("pool ptr is not a curl_multi handle"); @@ -24,6 +20,23 @@ multiref *get_multiref(SEXP ptr){ return mref; } +/* retrieves CURLM from connections as well as pools */ +CURLM *get_curlm(SEXP ptr){ + CURLM *multi; + if(Rf_inherits(ptr, "curl")){ + ptr = Rf_getAttrib(ptr, Rf_install("conn_id")); + if (TYPEOF(ptr) != EXTPTRSXP) + Rf_error("pool ptr is not a curl connection"); + multi = (CURLM*) R_ExternalPtrAddr(ptr); + if(!multi) + Rf_error("CURLM pointer is dead"); + } else { + multiref *mref = get_multiref(ptr); + multi = mref->m; + } + return multi; +} + void multi_release(reference *ref){ /* Release the easy-handle */ CURL *handle = ref->handle; @@ -153,7 +166,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ if(Rf_isFunction(cb_complete)){ int arglen = Rf_length(R_ClosureFormals(cb_complete)); SEXP out = PROTECT(make_handle_response(ref)); - SET_VECTOR_ELT(out, 6, buf); + SET_VECTOR_ELT(out, 9, buf); SEXP call = PROTECT(Rf_lcons(cb_complete, arglen ? Rf_lcons(out, R_NilValue) : R_NilValue)); //R_tryEval(call, R_GlobalEnv, &cbfail); Rf_eval(call, R_GlobalEnv); //OK to error here @@ -162,12 +175,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ } else { total_fail++; if(Rf_isFunction(cb_error)){ - int arglen = Rf_length(R_ClosureFormals(cb_error)); - SEXP buf = PROTECT(Rf_mkString(strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(status))); - SEXP call = PROTECT(Rf_lcons(cb_error, arglen ? Rf_lcons(buf, R_NilValue) : R_NilValue)); - //R_tryEval(call, R_GlobalEnv, &cbfail); - Rf_eval(call, R_GlobalEnv); //OK to error here - UNPROTECT(2); + raise_libcurl_error(status, ref, cb_error); } } UNPROTECT(4); @@ -180,7 +188,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ R_CheckUserInterrupt(); /* check for timeout or max result*/ - if(result_max > 0 && total_success + total_fail >= result_max) + if(total_pending > 0 && result_max > 0 && total_success + total_fail >= result_max) break; if(time_max == 0 && total_pending != -1) break; @@ -194,19 +202,13 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ if(total_pending == 0 && !dirty) break; -#ifdef HAS_MULTI_WAIT - /* wait for activity, timeout or "nothing" */ int numfds; double waitforit = fmin(time_max - seconds_elapsed, 1); //at most 1 sec to support interrupts if(time_max > 0) massert(curl_multi_wait(multi, NULL, 0, (int) waitforit * 1000, &numfds)); -#endif /* poll libcurl for new data - updates total_pending */ - CURLMcode res = CURLM_CALL_MULTI_PERFORM; - while(res == CURLM_CALL_MULTI_PERFORM) - res = curl_multi_perform(multi, &(total_pending)); - if(res != CURLM_OK) + if(curl_multi_perform(multi, &(total_pending)) != CURLM_OK) break; } @@ -248,19 +250,13 @@ SEXP R_multi_new(void){ return ptr; } -SEXP R_multi_setopt(SEXP pool_ptr, SEXP total_con, SEXP host_con, SEXP multiplex){ - #ifdef HAS_CURLMOPT_MAX_TOTAL_CONNECTIONS +SEXP R_multi_setopt(SEXP pool_ptr, SEXP total_con, SEXP host_con, SEXP max_streams, SEXP multiplex){ CURLM *multi = get_multiref(pool_ptr)->m; massert(curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) Rf_asInteger(total_con))); massert(curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, (long) Rf_asInteger(host_con))); - #endif - - // NOTE: CURLPIPE_HTTP1 is unsafe for non idempotent requests - #ifdef CURLPIPE_MULTIPLEX massert(curl_multi_setopt(multi, CURLMOPT_PIPELINING, Rf_asLogical(multiplex) ? CURLPIPE_MULTIPLEX : CURLPIPE_NOTHING)); - #endif - + massert(curl_multi_setopt(multi, CURLMOPT_MAX_CONCURRENT_STREAMS, (long) Rf_asInteger(max_streams))); return pool_ptr; } @@ -269,8 +265,7 @@ SEXP R_multi_list(SEXP pool_ptr){ } SEXP R_multi_fdset(SEXP pool_ptr){ - multiref *mref = get_multiref(pool_ptr); - CURLM *multi = mref->m; + CURLM *multi = get_curlm(pool_ptr); fd_set read_fd_set, write_fd_set, exc_fd_set; int max_fd, i, num_read = 0, num_write = 0, num_exc = 0; int *pread, *pwrite, *pexc; diff --git a/src/library/curl/src/options.c b/src/library/curl/src/options.c index 4d5a513082..4d02e0061a 100644 --- a/src/library/curl/src/options.c +++ b/src/library/curl/src/options.c @@ -1,7 +1,6 @@ #include "curl-common.h" SEXP R_option_types(void){ -#ifdef HAS_CURL_EASY_OPTION int len = 0; const struct curl_easyoption *o = NULL; while((o = curl_easy_option_next(o))){ @@ -32,7 +31,4 @@ SEXP R_option_types(void){ SET_STRING_ELT(listnms, 2, Rf_mkChar("type")); UNPROTECT(5); return out; -#else - return R_NilValue; -#endif } diff --git a/src/library/curl/src/ssl.c b/src/library/curl/src/ssl.c index 961c68e8dc..25a1499978 100644 --- a/src/library/curl/src/ssl.c +++ b/src/library/curl/src/ssl.c @@ -12,7 +12,7 @@ void switch_to_openssl_on_vista(void){ #if defined(_WIN32) && defined(HAS_MULTI_SSL) /* If a CURL_SSL_BACKEND is set, do not override */ char *envvar = getenv("CURL_SSL_BACKEND"); - if(envvar != NULL){ + if(envvar != NULL && *envvar != 0){ REprintf("Initiating curl with CURL_SSL_BACKEND: %s\n", envvar); return; } diff --git a/src/library/curl/src/typechecking.c b/src/library/curl/src/typechecking.c index 3f00557040..8794d96973 100644 --- a/src/library/curl/src/typechecking.c +++ b/src/library/curl/src/typechecking.c @@ -1,7 +1,5 @@ #include "curl-common.h" -#ifdef HAS_CURL_EASY_OPTION - int r_curl_is_string_option(CURLoption x){ return curl_easy_option_by_id(x)->type == CURLOT_STRING; } @@ -22,29 +20,3 @@ int r_curl_is_off_t_option(CURLoption x){ int r_curl_is_postfields_option(CURLoption x){ return curl_easy_option_by_id(x)->type == CURLOT_OBJECT; } - -#else //CURLOT_FLAG_ALIAS - -#include "typelist.h" - -int r_curl_is_string_option(CURLoption x){ - return curlcheck_string_option(x); -} - -int r_curl_is_slist_option(CURLoption x){ - return curlcheck_slist_option(x); -} - -int r_curl_is_long_option(CURLoption x){ - return curlcheck_long_option(x); -} - -int r_curl_is_off_t_option(CURLoption x){ - return curlcheck_off_t_option(x); -} - -int r_curl_is_postfields_option(CURLoption x){ - return curlcheck_postfields_option(x); -} - -#endif //CURLOT_FLAG_ALIAS diff --git a/src/library/curl/src/typelist.h b/src/library/curl/src/typelist.h deleted file mode 100644 index c64b3cc6e3..0000000000 --- a/src/library/curl/src/typelist.h +++ /dev/null @@ -1,63 +0,0 @@ -/* This file is autogenerated from typelist.h.in */ -/* if headers are not included, we provide a copy */ -#ifndef CURLINC_TYPECHECK_GCC_H - -/* evaluates to true if option takes a long argument */ -#define curlcheck_long_option(option) \ - (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) - -#ifdef CURLOPTTYPE_BLOB -#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T && (option) < CURLOPTTYPE_BLOB) -#else -#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T) -#endif - -/* evaluates to true if option takes a data argument to pass to a callback */ -#define curlcheck_cb_data_option(option) \ - ((option) == /*CURLOPT_CHUNK_DATA*/ 10201 || \ - (option) == /*CURLOPT_CLOSESOCKETDATA*/ 10209 || \ - (option) == /*CURLOPT_DEBUGDATA*/ 10095 || \ - (option) == /*CURLOPT_FNMATCH_DATA*/ 10202 || \ - (option) == /*CURLOPT_HEADERDATA*/ 10029 || \ - (option) == /*CURLOPT_HSTSREADDATA*/ 10302 || \ - (option) == /*CURLOPT_HSTSWRITEDATA*/ 10304 || \ - (option) == /*CURLOPT_INTERLEAVEDATA*/ 10195 || \ - (option) == /*CURLOPT_IOCTLDATA*/ 10131 || \ - (option) == /*CURLOPT_OPENSOCKETDATA*/ 10164 || \ - (option) == /*CURLOPT_PREREQDATA*/ 10313 || \ - (option) == /*CURLOPT_PROGRESSDATA*/ 10057 || \ - (option) == /*CURLOPT_READDATA*/ 10009 || \ - (option) == /*CURLOPT_SEEKDATA*/ 10168 || \ - (option) == /*CURLOPT_SOCKOPTDATA*/ 10149 || \ - (option) == /*CURLOPT_SSH_KEYDATA*/ 10185 || \ - (option) == /*CURLOPT_SSL_CTX_DATA*/ 10109 || \ - (option) == /*CURLOPT_WRITEDATA*/ 10001 || \ - (option) == /*CURLOPT_RESOLVER_START_DATA*/ 10273 || \ - (option) == /*CURLOPT_TRAILERDATA*/ 10284 || \ - (option) == /*CURLOPT_SSH_HOSTKEYDATA*/ 10317 || \ - 0) - -/* evaluates to true if option takes a POST data argument (void* or char*) */ -#define curlcheck_postfields_option(option) \ - ((option) == /*CURLOPT_POSTFIELDS*/ 10015 || \ - (option) == /*CURLOPT_COPYPOSTFIELDS*/ 10165 || \ - 0) - -/* evaluates to true if option takes a struct curl_slist * argument */ -#define curlcheck_slist_option(option) \ - ((option) == /*CURLOPT_HTTP200ALIASES*/ 10104 || \ - (option) == /*CURLOPT_HTTPHEADER*/ 10023 || \ - (option) == /*CURLOPT_MAIL_RCPT*/ 10187 || \ - (option) == /*CURLOPT_POSTQUOTE*/ 10039 || \ - (option) == /*CURLOPT_PREQUOTE*/ 10093 || \ - (option) == /*CURLOPT_PROXYHEADER*/ 10228 || \ - (option) == /*CURLOPT_QUOTE*/ 10028 || \ - (option) == /*CURLOPT_RESOLVE*/ 10203 || \ - (option) == /*CURLOPT_TELNETOPTIONS*/ 10070 || \ - (option) == /*CURLOPT_CONNECT_TO*/ 10243 || \ - 0) - -#define curlcheck_string_option(x) \ -(x > 10000 && x < 20000 && !curlcheck_slist_option(x) && !curlcheck_cb_data_option(x)); - -#endif diff --git a/src/library/curl/src/urlparser.c b/src/library/curl/src/urlparser.c new file mode 100644 index 0000000000..0d1cd98d97 --- /dev/null +++ b/src/library/curl/src/urlparser.c @@ -0,0 +1,101 @@ +#include "curl-common.h" + +static SEXP make_url_names(void){ + SEXP names = PROTECT(Rf_allocVector(STRSXP, 9)); + SET_STRING_ELT(names, 0, Rf_mkChar("url")); + SET_STRING_ELT(names, 1, Rf_mkChar("scheme")); + SET_STRING_ELT(names, 2, Rf_mkChar("host")); + SET_STRING_ELT(names, 3, Rf_mkChar("port")); + SET_STRING_ELT(names, 4, Rf_mkChar("path")); + SET_STRING_ELT(names, 5, Rf_mkChar("query")); + SET_STRING_ELT(names, 6, Rf_mkChar("fragment")); + SET_STRING_ELT(names, 7, Rf_mkChar("user")); + SET_STRING_ELT(names, 8, Rf_mkChar("password")); + UNPROTECT(1); + return names; +} + +static void fail_if(CURLUcode err){ + if(err != CURLUE_OK) +#ifdef HAS_CURL_PARSER_STRERROR + Rf_error("Failed to parse URL: %s", curl_url_strerror(err)); +#else + Rf_error("Failed to parse URL: error code %d", err); +#endif +} + +static SEXP get_field(CURLU *h, CURLUPart part, CURLUcode field_missing){ + char *str = NULL; + SEXP field = NULL; + CURLUcode err = curl_url_get(h, part, &str, 0); + if(err == field_missing && err != CURLUE_OK){ + field = R_NilValue; + } else { + fail_if(err); + field = make_string(str); + } + curl_free(str); + return field; +} + +/* We use CURLU_NON_SUPPORT_SCHEME to make results consistent across different libcurl configurations and Ada URL + * We use CURLU_URLENCODE to normalize input URLs and also be consistent with Ada URL */ + + +static void set_url(CURLU *h, const char *str, int default_scheme){ + int flags = CURLU_NON_SUPPORT_SCHEME | CURLU_URLENCODE | (default_scheme * CURLU_DEFAULT_SCHEME); + fail_if(curl_url_set(h, CURLUPART_URL, str, flags)); +} + +SEXP R_parse_url(SEXP url, SEXP baseurl, SEXP default_https) { + CURLU *h = curl_url(); + int default_scheme = Rf_length(default_https) && Rf_asLogical(default_https); + if(Rf_length(baseurl)){ + set_url(h, get_string(baseurl), default_scheme); + } + set_url(h, get_string(url), default_scheme); + SEXP out = PROTECT(Rf_allocVector(VECSXP, 9)); + SET_VECTOR_ELT(out, 0, get_field(h, CURLUPART_URL, CURLUE_OK)); + SET_VECTOR_ELT(out, 1, get_field(h, CURLUPART_SCHEME, CURLUE_NO_SCHEME)); + SET_VECTOR_ELT(out, 2, get_field(h, CURLUPART_HOST, CURLUE_NO_HOST)); + SET_VECTOR_ELT(out, 3, get_field(h, CURLUPART_PORT, CURLUE_NO_PORT)); + SET_VECTOR_ELT(out, 4, get_field(h, CURLUPART_PATH, CURLUE_OK)); + SET_VECTOR_ELT(out, 5, get_field(h, CURLUPART_QUERY, CURLUE_NO_QUERY)); + SET_VECTOR_ELT(out, 6, get_field(h, CURLUPART_FRAGMENT, CURLUE_NO_FRAGMENT)); + SET_VECTOR_ELT(out, 7, get_field(h, CURLUPART_USER, CURLUE_NO_USER)); + SET_VECTOR_ELT(out, 8, get_field(h, CURLUPART_PASSWORD, CURLUE_NO_PASSWORD)); + curl_url_cleanup(h); + Rf_setAttrib(out, R_NamesSymbol, make_url_names()); + UNPROTECT(1); + return out; +} + +static void set_value(CURLU *h, CURLUPart part, SEXP value){ + if(Rf_length(value) && Rf_isString(value)){ + if(STRING_ELT(value, 0) == NA_STRING || Rf_length(STRING_ELT(value, 0)) == 0){ + fail_if(curl_url_set(h, part, NULL, 0)); + } else if(Rf_inherits(value, "AsIs")){ + fail_if(curl_url_set(h, part, get_string(value), 0)); + } else { + fail_if(curl_url_set(h, part, get_string(value), CURLU_NON_SUPPORT_SCHEME | CURLU_URLENCODE)); + } + } +} + +SEXP R_modify_url(SEXP url, SEXP scheme, SEXP host, SEXP port, SEXP path, SEXP query, SEXP fragment, SEXP user, SEXP password){ + CURLU *h = curl_url(); + set_value(h, CURLUPART_URL, url); + set_value(h, CURLUPART_SCHEME, scheme); + set_value(h, CURLUPART_HOST, host); + set_value(h, CURLUPART_PORT, port); + set_value(h, CURLUPART_PATH, path); + set_value(h, CURLUPART_QUERY, query); + set_value(h, CURLUPART_FRAGMENT, fragment); + set_value(h, CURLUPART_USER, user); + set_value(h, CURLUPART_PASSWORD, password); + char *str = NULL; + fail_if(curl_url_get(h, CURLUPART_URL, &str, 0)); + SEXP out = make_string(str); + curl_free(str); + return out; +} diff --git a/src/library/curl/src/utils.c b/src/library/curl/src/utils.c index 584b164153..af60159a48 100644 --- a/src/library/curl/src/utils.c +++ b/src/library/curl/src/utils.c @@ -1,6 +1,19 @@ #include "curl-common.h" #include /* SIZE_MAX */ +#ifdef _WIN32 +#include +void send_r_interrupt(void) { + UserBreak = 1; + R_CheckUserInterrupt(); +} +#else +#include +void send_r_interrupt(void) { + Rf_onintr(); +} +#endif + CURL* get_handle(SEXP ptr){ return get_ref(ptr)->handle; } @@ -37,40 +50,42 @@ void reset_errbuf(reference *ref){ memset(ref->errbuf, 0, CURL_ERROR_SIZE); } -void assert(CURLcode res){ - if(res != CURLE_OK) - Rf_error("%s", curl_easy_strerror(res)); -} - -static char * parse_host(const char * input){ - static char buf[8000] = {0}; - char *url = buf; - strncpy(url, input, 7999); - - char *ptr = NULL; - if((ptr = strstr(url, "://"))) - url = ptr + 3; - if((ptr = strchr(url, '/'))) - *ptr = 0; - if((ptr = strchr(url, '#'))) - *ptr = 0; - if((ptr = strchr(url, '?'))) - *ptr = 0; - if((ptr = strchr(url, '@'))) - url = ptr + 1; - return url; +void assert_message(CURLcode res, const char *str){ + if(res == CURLE_OK) + return; + if(res == CURLE_ABORTED_BY_CALLBACK) + send_r_interrupt(); + if(str == NULL) + str = curl_easy_strerror(res); + SEXP code = PROTECT(Rf_ScalarInteger(res)); + SEXP message = PROTECT(make_string(str)); + SEXP expr = PROTECT(Rf_install("raise_libcurl_error")); + SEXP call = PROTECT(Rf_lang3(expr, code, message)); + SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); + Rf_eval(call, env); + UNPROTECT(5); //never happens +} + +void raise_libcurl_error(CURLcode res, reference *ref, SEXP error_cb){ + if(res == CURLE_OK) + return; + if(res == CURLE_ABORTED_BY_CALLBACK) + send_r_interrupt(); + const char *source_url = NULL; + curl_easy_getinfo(ref->handle, CURLINFO_EFFECTIVE_URL, &source_url); + SEXP url = PROTECT(make_string(source_url)); + SEXP code = PROTECT(Rf_ScalarInteger(res)); + SEXP message = PROTECT(make_string(curl_easy_strerror(res))); + SEXP errbuf = PROTECT(make_string(ref->errbuf)); + SEXP expr = PROTECT(Rf_install("raise_libcurl_error")); + SEXP call = PROTECT(Rf_lang6(expr, code, message, errbuf, url, error_cb)); + SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); + Rf_eval(call, env); + UNPROTECT(7); //happens for non-throwing error_cb() } void assert_status(CURLcode res, reference *ref){ - // Customize better error message for timeoutsS - if(res == CURLE_OPERATION_TIMEDOUT || res == CURLE_SSL_CACERT){ - const char *url = NULL; - if(curl_easy_getinfo(ref->handle, CURLINFO_EFFECTIVE_URL, &url) == CURLE_OK){ - Rf_error("%s: [%s] %s", curl_easy_strerror(res), parse_host(url), ref->errbuf); - } - } - if(res != CURLE_OK) - Rf_error("%s", strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(res)); + raise_libcurl_error(res, ref, R_NilValue); } void massert(CURLMcode res){ @@ -78,15 +93,6 @@ void massert(CURLMcode res){ Rf_error("%s", curl_multi_strerror(res)); } -void stop_for_status(CURL *http_handle){ - long status = 0; - assert(curl_easy_getinfo(http_handle, CURLINFO_RESPONSE_CODE, &status)); - - /* check http status code. Not sure what this does for ftp. */ - if(status >= 300) - Rf_error("HTTP error %ld.", status); -} - /* make sure to call curl_slist_free_all on this object */ struct curl_slist* vec_to_slist(SEXP vec){ if(!Rf_isString(vec)) diff --git a/src/library/curl/src/version.c b/src/library/curl/src/version.c index 869712b85b..bcf4c8bfea 100644 --- a/src/library/curl/src/version.c +++ b/src/library/curl/src/version.c @@ -1,20 +1,18 @@ -#include -#include - -#define make_string(x) x ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) +#include "curl-common.h" SEXP R_curl_version(void) { /* retrieve info from curl */ const curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); /* put stuff in a list */ - SEXP list = PROTECT(Rf_allocVector(VECSXP, 10)); + SEXP list = PROTECT(Rf_allocVector(VECSXP, 12)); SET_VECTOR_ELT(list, 0, make_string(data->version)); - SET_VECTOR_ELT(list, 1, make_string(data->ssl_version)); - SET_VECTOR_ELT(list, 2, make_string(data->libz_version)); - SET_VECTOR_ELT(list, 3, make_string(data->libssh_version)); - SET_VECTOR_ELT(list, 4, make_string(data->libidn)); - SET_VECTOR_ELT(list, 5, make_string(data->host)); + SET_VECTOR_ELT(list, 1, make_string(LIBCURL_VERSION)); + SET_VECTOR_ELT(list, 2, make_string(data->ssl_version)); + SET_VECTOR_ELT(list, 3, make_string(data->libz_version)); + SET_VECTOR_ELT(list, 4, make_string(data->libssh_version)); + SET_VECTOR_ELT(list, 5, make_string(data->libidn)); + SET_VECTOR_ELT(list, 6, make_string(data->host)); /* create vector of protocols */ int len = 0; @@ -24,40 +22,46 @@ SEXP R_curl_version(void) { for (int i = 0; i < len; i++){ SET_STRING_ELT(protocols, i, Rf_mkChar(*(data->protocols + i))); } - SET_VECTOR_ELT(list, 6, protocols); + SET_VECTOR_ELT(list, 7, protocols); /* add list names */ - SEXP names = PROTECT(Rf_allocVector(STRSXP, 10)); + SEXP names = PROTECT(Rf_allocVector(STRSXP, 12)); SET_STRING_ELT(names, 0, Rf_mkChar("version")); - SET_STRING_ELT(names, 1, Rf_mkChar("ssl_version")); - SET_STRING_ELT(names, 2, Rf_mkChar("libz_version")); - SET_STRING_ELT(names, 3, Rf_mkChar("libssh_version")); - SET_STRING_ELT(names, 4, Rf_mkChar("libidn_version")); - SET_STRING_ELT(names, 5, Rf_mkChar("host")); - SET_STRING_ELT(names, 6, Rf_mkChar("protocols")); - SET_STRING_ELT(names, 7, Rf_mkChar("ipv6")); - SET_STRING_ELT(names, 8, Rf_mkChar("http2")); - SET_STRING_ELT(names, 9, Rf_mkChar("idn")); + SET_STRING_ELT(names, 1, Rf_mkChar("headers")); + SET_STRING_ELT(names, 2, Rf_mkChar("ssl_version")); + SET_STRING_ELT(names, 3, Rf_mkChar("libz_version")); + SET_STRING_ELT(names, 4, Rf_mkChar("libssh_version")); + SET_STRING_ELT(names, 5, Rf_mkChar("libidn_version")); + SET_STRING_ELT(names, 6, Rf_mkChar("host")); + SET_STRING_ELT(names, 7, Rf_mkChar("protocols")); + SET_STRING_ELT(names, 8, Rf_mkChar("ipv6")); + SET_STRING_ELT(names, 9, Rf_mkChar("http2")); + SET_STRING_ELT(names, 10, Rf_mkChar("idn")); + SET_STRING_ELT(names, 11, Rf_mkChar("url_parser")); Rf_setAttrib(list, R_NamesSymbol, names); #ifdef CURL_VERSION_IPV6 - SET_VECTOR_ELT(list, 7, Rf_ScalarLogical(data->features & CURL_VERSION_IPV6)); + SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(data->features & CURL_VERSION_IPV6)); #else - SET_VECTOR_ELT(list, 7, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(0)); #endif #ifdef CURL_VERSION_HTTP2 - SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(data->features & CURL_VERSION_HTTP2)); + SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(data->features & CURL_VERSION_HTTP2)); #else - SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(0)); #endif #ifdef CURL_VERSION_IDN - SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(data->features & CURL_VERSION_IDN)); + SET_VECTOR_ELT(list, 10, Rf_ScalarLogical(data->features & CURL_VERSION_IDN)); #else - SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 10, Rf_ScalarLogical(0)); #endif + // CURL_PARSER is always assumed to be available + SET_VECTOR_ELT(list, 11, Rf_ScalarLogical(1)); + + /* return */ UNPROTECT(3); return list; diff --git a/src/library/curl/tools/errorcodes.txt b/src/library/curl/tools/errorcodes.txt new file mode 100644 index 0000000000..6c1d287510 --- /dev/null +++ b/src/library/curl/tools/errorcodes.txt @@ -0,0 +1,128 @@ +CURLE_OK = 0, +CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ +CURLE_FAILED_INIT, /* 2 */ +CURLE_URL_MALFORMAT, /* 3 */ +CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for 7.17.0, reused in April 2011 for 7.21.5] */ +CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ +CURLE_COULDNT_RESOLVE_HOST, /* 6 */ +CURLE_COULDNT_CONNECT, /* 7 */ +CURLE_WEIRD_SERVER_REPLY, /* 8 */ +CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server + due to lack of access - when login fails + this is not returned. */ +CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for + 7.15.4, reused in Dec 2011 for 7.24.0]*/ +CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ +CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server + [was obsoleted in August 2007 for 7.17.0, + reused in Dec 2011 for 7.24.0]*/ +CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ +CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ +CURLE_FTP_CANT_GET_HOST, /* 15 */ +CURLE_HTTP2, /* 16 - A problem in the http2 framing layer. + [was obsoleted in August 2007 for 7.17.0, + reused in July 2014 for 7.38.0] */ +CURLE_FTP_COULDNT_SET_TYPE, /* 17 */ +CURLE_PARTIAL_FILE, /* 18 */ +CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ +CURLE_OBSOLETE20, /* 20 - NOT USED */ +CURLE_QUOTE_ERROR, /* 21 - quote command failure */ +CURLE_HTTP_RETURNED_ERROR, /* 22 */ +CURLE_WRITE_ERROR, /* 23 */ +CURLE_OBSOLETE24, /* 24 - NOT USED */ +CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ +CURLE_READ_ERROR, /* 26 - could not open/read from file */ +CURLE_OUT_OF_MEMORY, /* 27 */ +CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ +CURLE_OBSOLETE29, /* 29 - NOT USED */ +CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ +CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ +CURLE_OBSOLETE32, /* 32 - NOT USED */ +CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */ +CURLE_HTTP_POST_ERROR, /* 34 */ +CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ +CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */ +CURLE_FILE_COULDNT_READ_FILE, /* 37 */ +CURLE_LDAP_CANNOT_BIND, /* 38 */ +CURLE_LDAP_SEARCH_FAILED, /* 39 */ +CURLE_OBSOLETE40, /* 40 - NOT USED */ +CURLE_FUNCTION_NOT_FOUND, /* 41 - NOT USED starting with 7.53.0 */ +CURLE_ABORTED_BY_CALLBACK, /* 42 */ +CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ +CURLE_OBSOLETE44, /* 44 - NOT USED */ +CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ +CURLE_OBSOLETE46, /* 46 - NOT USED */ +CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */ +CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ +CURLE_SETOPT_OPTION_SYNTAX, /* 49 - Malformed setopt option */ +CURLE_OBSOLETE50, /* 50 - NOT USED */ +CURLE_PEER_FAILED_VERIFICATION, /* 51 - NOT USED */ +CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ +CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ +CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ +CURLE_SEND_ERROR, /* 55 - failed sending network data */ +CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ +CURLE_OBSOLETE57, /* 57 - NOT IN USE */ +CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ +CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */ +CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint + was not verified fine */ +CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ +CURLE_LDAP_INVALID_URL, /* 62 - NOT IN USE since 7.82.0 */ +CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ +CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ +CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind + that failed */ +CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ +CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ +CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ +CURLE_TFTP_PERM, /* 69 - permission problem on server */ +CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */ +CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ +CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ +CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ +CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ +CURLE_CONV_FAILED, /* 75 - NOT IN USE since 7.82.0 */ +CURLE_CONV_REQD, /* 76 - NOT IN USE since 7.82.0 */ +CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing + or wrong format */ +CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ +CURLE_SSH, /* 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + +CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL + connection */ +CURLE_AGAIN, /* 81 - socket is not ready for send/recv, + wait till it is ready and try again (Added + in 7.18.2) */ +CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or + wrong format (Added in 7.19.0) */ +CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in + 7.19.0) */ +CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ +CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ +CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */ +CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */ +CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ +CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the + session will be queued */ +CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not + match */ +CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ +CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer + */ +CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from + inside a callback */ +CURLE_AUTH_ERROR, /* 94 - an authentication function returned an + error */ +CURLE_HTTP3, /* 95 - An HTTP/3 layer problem */ +CURLE_QUIC_CONNECT_ERROR, /* 96 - QUIC connection error */ +CURLE_PROXY, /* 97 - proxy handshake error */ +CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ +CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ +CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ +CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */ +CURL_LAST /* never use! */ diff --git a/src/library/curl/tools/make-errorcodes.R b/src/library/curl/tools/make-errorcodes.R new file mode 100644 index 0000000000..df525c85b1 --- /dev/null +++ b/src/library/curl/tools/make-errorcodes.R @@ -0,0 +1,11 @@ +lines <- readLines('tools/errorcodes.txt') +errors <- grep('^CURLE', lines, value = TRUE)[-1] +error_codes <- sub(",.*", "", errors) +stopifnot(error_codes[100] == "CURLE_TOO_LARGE") +error_codes <- sub("^curle_", "curl_error_", tolower(error_codes)) +out <- file('R/errcodes.R', 'w') +writeLines("# This file is autogenerated using make-errorcodes.R", out) +writeLines("libcurl_error_codes <- ", out) +dput(error_codes, out) +close(out) + diff --git a/src/library/curl/tools/symbols-in-versions b/src/library/curl/tools/symbols-in-versions index c590d084f1..ddda26d832 100644 --- a/src/library/curl/tools/symbols-in-versions +++ b/src/library/curl/tools/symbols-in-versions @@ -12,6 +12,9 @@ Name Introduced Deprecated Last +CURL_AT_LEAST_VERSION 7.43.0 +CURL_BLOB_COPY 7.71.0 +CURL_BLOB_NOCOPY 7.71.0 CURL_CHUNK_BGN_FUNC_FAIL 7.21.0 CURL_CHUNK_BGN_FUNC_OK 7.21.0 CURL_CHUNK_BGN_FUNC_SKIP 7.21.0 @@ -20,6 +23,7 @@ CURL_CHUNK_END_FUNC_OK 7.21.0 CURL_CSELECT_ERR 7.16.3 CURL_CSELECT_IN 7.16.3 CURL_CSELECT_OUT 7.16.3 +CURL_DEPRECATED 7.87.0 CURL_DID_MEMORY_FUNC_TYPEDEFS 7.49.0 CURL_EASY_NONE 7.14.0 - 7.15.4 CURL_EASY_TIMEOUT 7.14.0 - 7.15.4 @@ -49,6 +53,7 @@ CURL_HTTP_VERSION_2_0 7.33.0 CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE 7.49.0 CURL_HTTP_VERSION_2TLS 7.47.0 CURL_HTTP_VERSION_3 7.66.0 +CURL_HTTP_VERSION_3ONLY 7.88.0 CURL_HTTP_VERSION_NONE 7.9.1 CURL_HTTPPOST_BUFFER 7.46.0 CURL_HTTPPOST_CALLBACK 7.46.0 @@ -58,15 +63,18 @@ CURL_HTTPPOST_PTRBUFFER 7.46.0 CURL_HTTPPOST_PTRCONTENTS 7.46.0 CURL_HTTPPOST_PTRNAME 7.46.0 CURL_HTTPPOST_READFILE 7.46.0 +CURL_IGNORE_DEPRECATION 7.87.0 CURL_IPRESOLVE_V4 7.10.8 CURL_IPRESOLVE_V6 7.10.8 CURL_IPRESOLVE_WHATEVER 7.10.8 +CURL_ISOCPP 7.10.2 CURL_LOCK_ACCESS_NONE 7.10.3 CURL_LOCK_ACCESS_SHARED 7.10.3 CURL_LOCK_ACCESS_SINGLE 7.10.3 CURL_LOCK_DATA_CONNECT 7.10.3 CURL_LOCK_DATA_COOKIE 7.10.3 CURL_LOCK_DATA_DNS 7.10.3 +CURL_LOCK_DATA_HSTS 7.88.0 CURL_LOCK_DATA_NONE 7.10.3 CURL_LOCK_DATA_PSL 7.61.0 CURL_LOCK_DATA_SHARE 7.10.4 @@ -92,6 +100,7 @@ CURL_PREREQFUNC_OK 7.79.0 CURL_PROGRESS_BAR 7.1.1 - 7.4.1 CURL_PROGRESS_STATS 7.1.1 - 7.4.1 CURL_PROGRESSFUNC_CONTINUE 7.68.0 +CURL_PULL_SYS_POLL_H 7.56.0 CURL_PUSH_DENY 7.44.0 CURL_PUSH_ERROROUT 7.72.0 CURL_PUSH_OK 7.44.0 @@ -148,6 +157,7 @@ CURL_TRAILERFUNC_OK 7.64.0 CURL_UPKEEP_INTERVAL_DEFAULT 7.62.0 CURL_VERSION_ALTSVC 7.64.1 CURL_VERSION_ASYNCHDNS 7.10.7 +CURL_VERSION_BITS 7.43.0 CURL_VERSION_BROTLI 7.57.0 CURL_VERSION_CONV 7.15.4 CURL_VERSION_CURLDEBUG 7.19.6 @@ -167,7 +177,7 @@ CURL_VERSION_LARGEFILE 7.11.1 CURL_VERSION_LIBZ 7.10 CURL_VERSION_MULTI_SSL 7.56.0 CURL_VERSION_NTLM 7.10.6 -CURL_VERSION_NTLM_WB 7.22.0 +CURL_VERSION_NTLM_WB 7.22.0 8.8.0 CURL_VERSION_PSL 7.47.0 CURL_VERSION_SPNEGO 7.10.8 CURL_VERSION_SSL 7.10 @@ -180,7 +190,8 @@ CURL_VERSION_ZSTD 7.72.0 CURL_WAIT_POLLIN 7.28.0 CURL_WAIT_POLLOUT 7.28.0 CURL_WAIT_POLLPRI 7.28.0 -CURL_WIN32 7.69.0 +CURL_WIN32 7.69.0 - 8.5.0 +CURL_WRITEFUNC_ERROR 7.87.0 CURL_WRITEFUNC_PAUSE 7.18.0 CURL_ZERO_TERMINATED 7.56.0 CURLALTSVC_H1 7.64.1 @@ -199,14 +210,14 @@ CURLAUTH_GSSNEGOTIATE 7.10.6 7.38.0 CURLAUTH_NEGOTIATE 7.38.0 CURLAUTH_NONE 7.10.6 CURLAUTH_NTLM 7.10.6 -CURLAUTH_NTLM_WB 7.22.0 +CURLAUTH_NTLM_WB 7.22.0 8.8.0 CURLAUTH_ONLY 7.21.3 -CURLCLOSEPOLICY_CALLBACK 7.7 -CURLCLOSEPOLICY_LEAST_RECENTLY_USED 7.7 -CURLCLOSEPOLICY_LEAST_TRAFFIC 7.7 -CURLCLOSEPOLICY_NONE 7.7 -CURLCLOSEPOLICY_OLDEST 7.7 -CURLCLOSEPOLICY_SLOWEST 7.7 +CURLCLOSEPOLICY_CALLBACK 7.7 7.16.1 +CURLCLOSEPOLICY_LEAST_RECENTLY_USED 7.7 7.16.1 +CURLCLOSEPOLICY_LEAST_TRAFFIC 7.7 7.16.1 +CURLCLOSEPOLICY_NONE 7.7 7.16.1 +CURLCLOSEPOLICY_OLDEST 7.7 7.16.1 +CURLCLOSEPOLICY_SLOWEST 7.7 7.16.1 CURLE_ABORTED_BY_CALLBACK 7.1 CURLE_AGAIN 7.18.2 CURLE_ALREADY_COMPLETE 7.7.2 7.8 @@ -217,7 +228,7 @@ CURLE_BAD_DOWNLOAD_RESUME 7.10 CURLE_BAD_FUNCTION_ARGUMENT 7.1 CURLE_BAD_PASSWORD_ENTERED 7.4.2 7.17.0 CURLE_CHUNK_FAILED 7.21.0 -CURLE_CONV_FAILED 7.15.4 +CURLE_CONV_FAILED 7.15.4 7.82.0 CURLE_CONV_REQD 7.15.4 7.82.0 CURLE_COULDNT_CONNECT 7.1 CURLE_COULDNT_RESOLVE_HOST 7.1 @@ -317,6 +328,7 @@ CURLE_TFTP_NOSUCHUSER 7.15.0 CURLE_TFTP_NOTFOUND 7.15.0 CURLE_TFTP_PERM 7.15.0 CURLE_TFTP_UNKNOWNID 7.15.0 +CURLE_TOO_LARGE 8.6.0 CURLE_TOO_MANY_REDIRECTS 7.5 CURLE_UNKNOWN_OPTION 7.21.5 CURLE_UNKNOWN_TELNET_OPTION 7.7 7.21.5 @@ -328,6 +340,7 @@ CURLE_URL_MALFORMAT_USER 7.1 7.17.0 CURLE_USE_SSL_FAILED 7.17.0 CURLE_WEIRD_SERVER_REPLY 7.51.0 CURLE_WRITE_ERROR 7.1 +CURLE_ECH_REQUIRED 8.8.0 CURLFILETYPE_DEVICE_BLOCK 7.21.0 CURLFILETYPE_DEVICE_CHAR 7.21.0 CURLFILETYPE_DIRECTORY 7.21.0 @@ -406,21 +419,23 @@ CURLHSTS_READONLYFILE 7.74.0 CURLINFO_ACTIVESOCKET 7.45.0 CURLINFO_APPCONNECT_TIME 7.19.0 CURLINFO_APPCONNECT_TIME_T 7.61.0 -CURLINFO_CAPATH 7.84.0 CURLINFO_CAINFO 7.84.0 +CURLINFO_CAPATH 7.84.0 CURLINFO_CERTINFO 7.19.1 CURLINFO_CONDITION_UNMET 7.19.4 +CURLINFO_CONN_ID 8.2.0 CURLINFO_CONNECT_TIME 7.4.1 CURLINFO_CONNECT_TIME_T 7.61.0 -CURLINFO_CONTENT_LENGTH_DOWNLOAD 7.6.1 +CURLINFO_CONTENT_LENGTH_DOWNLOAD 7.6.1 7.55.0 CURLINFO_CONTENT_LENGTH_DOWNLOAD_T 7.55.0 -CURLINFO_CONTENT_LENGTH_UPLOAD 7.6.1 +CURLINFO_CONTENT_LENGTH_UPLOAD 7.6.1 7.55.0 CURLINFO_CONTENT_LENGTH_UPLOAD_T 7.55.0 CURLINFO_CONTENT_TYPE 7.9.4 CURLINFO_COOKIELIST 7.14.1 CURLINFO_DATA_IN 7.9.6 CURLINFO_DATA_OUT 7.9.6 CURLINFO_DOUBLE 7.4.1 +CURLINFO_EARLYDATA_SENT_T 8.11.0 CURLINFO_EFFECTIVE_METHOD 7.72.0 CURLINFO_EFFECTIVE_URL 7.4 CURLINFO_END 7.9.6 @@ -435,7 +450,7 @@ CURLINFO_HTTP_CONNECTCODE 7.10.7 CURLINFO_HTTP_VERSION 7.50.0 CURLINFO_HTTPAUTH_AVAIL 7.10.8 CURLINFO_LASTONE 7.4.1 -CURLINFO_LASTSOCKET 7.15.2 +CURLINFO_LASTSOCKET 7.15.2 7.45.0 CURLINFO_LOCAL_IP 7.21.0 CURLINFO_LOCAL_PORT 7.21.0 CURLINFO_LONG 7.4.1 @@ -448,14 +463,16 @@ CURLINFO_OFF_T 7.55.0 CURLINFO_OS_ERRNO 7.12.2 CURLINFO_PRETRANSFER_TIME 7.4.1 CURLINFO_PRETRANSFER_TIME_T 7.61.0 +CURLINFO_POSTTRANSFER_TIME_T 8.10.0 CURLINFO_PRIMARY_IP 7.19.0 CURLINFO_PRIMARY_PORT 7.21.0 CURLINFO_PRIVATE 7.10.3 -CURLINFO_PROTOCOL 7.52.0 +CURLINFO_PROTOCOL 7.52.0 7.85.0 CURLINFO_PROXY_ERROR 7.73.0 CURLINFO_PROXY_SSL_VERIFYRESULT 7.52.0 CURLINFO_PROXYAUTH_AVAIL 7.10.8 CURLINFO_PTR 7.54.1 +CURLINFO_QUEUE_TIME_T 8.6.0 CURLINFO_REDIRECT_COUNT 7.9.7 CURLINFO_REDIRECT_TIME 7.9.7 CURLINFO_REDIRECT_TIME_T 7.61.0 @@ -469,15 +486,15 @@ CURLINFO_RTSP_CSEQ_RECV 7.20.0 CURLINFO_RTSP_SERVER_CSEQ 7.20.0 CURLINFO_RTSP_SESSION_ID 7.20.0 CURLINFO_SCHEME 7.52.0 -CURLINFO_SIZE_DOWNLOAD 7.4.1 +CURLINFO_SIZE_DOWNLOAD 7.4.1 7.55.0 CURLINFO_SIZE_DOWNLOAD_T 7.55.0 -CURLINFO_SIZE_UPLOAD 7.4.1 +CURLINFO_SIZE_UPLOAD 7.4.1 7.55.0 CURLINFO_SIZE_UPLOAD_T 7.55.0 CURLINFO_SLIST 7.12.3 CURLINFO_SOCKET 7.45.0 -CURLINFO_SPEED_DOWNLOAD 7.4.1 +CURLINFO_SPEED_DOWNLOAD 7.4.1 7.55.0 CURLINFO_SPEED_DOWNLOAD_T 7.55.0 -CURLINFO_SPEED_UPLOAD 7.4.1 +CURLINFO_SPEED_UPLOAD 7.4.1 7.55.0 CURLINFO_SPEED_UPLOAD_T 7.55.0 CURLINFO_SSL_DATA_IN 7.12.1 CURLINFO_SSL_DATA_OUT 7.12.1 @@ -492,6 +509,8 @@ CURLINFO_TLS_SSL_PTR 7.48.0 CURLINFO_TOTAL_TIME 7.4.1 CURLINFO_TOTAL_TIME_T 7.61.0 CURLINFO_TYPEMASK 7.4.1 +CURLINFO_USED_PROXY 8.7.0 +CURLINFO_XFER_ID 8.2.0 CURLIOCMD_NOP 7.12.3 CURLIOCMD_RESTARTREAD 7.12.3 CURLIOE_FAILRESTART 7.12.3 @@ -559,6 +578,7 @@ CURLOPT_BUFFERSIZE 7.10 CURLOPT_CAINFO 7.4.2 CURLOPT_CAINFO_BLOB 7.77.0 CURLOPT_CAPATH 7.9.8 +CURLOPT_CA_CACHE_TIMEOUT 7.87.0 CURLOPT_CERTINFO 7.19.1 CURLOPT_CHUNK_BGN_FUNCTION 7.21.0 CURLOPT_CHUNK_DATA 7.21.0 @@ -600,8 +620,9 @@ CURLOPT_DOH_SSL_VERIFYHOST 7.76.0 CURLOPT_DOH_SSL_VERIFYPEER 7.76.0 CURLOPT_DOH_SSL_VERIFYSTATUS 7.76.0 CURLOPT_DOH_URL 7.62.0 -CURLOPT_EGDSOCKET 7.7 -CURLOPT_ENCODING 7.10 +CURLOPT_ECH 8.8.0 +CURLOPT_EGDSOCKET 7.7 7.84.0 +CURLOPT_ENCODING 7.10 7.21.6 CURLOPT_ERRORBUFFER 7.1 CURLOPT_EXPECT_100_TIMEOUT_MS 7.36.0 CURLOPT_FAILONERROR 7.1 @@ -616,7 +637,7 @@ CURLOPT_FTP_ACCOUNT 7.13.0 CURLOPT_FTP_ALTERNATIVE_TO_USER 7.15.5 CURLOPT_FTP_CREATE_MISSING_DIRS 7.10.7 CURLOPT_FTP_FILEMETHOD 7.15.1 -CURLOPT_FTP_RESPONSE_TIMEOUT 7.10.8 +CURLOPT_FTP_RESPONSE_TIMEOUT 7.10.8 7.85.0 CURLOPT_FTP_SKIP_PASV_IP 7.15.0 CURLOPT_FTP_SSL 7.11.0 7.16.4 CURLOPT_FTP_SSL_CCC 7.16.1 @@ -631,6 +652,7 @@ CURLOPT_FTPSSLAUTH 7.12.2 CURLOPT_GSSAPI_DELEGATION 7.22.0 CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0 CURLOPT_HAPROXYPROTOCOL 7.60.0 +CURLOPT_HAPROXY_CLIENT_IP 8.2.0 CURLOPT_HEADER 7.1 CURLOPT_HEADERDATA 7.10 CURLOPT_HEADERFUNCTION 7.7.2 @@ -659,8 +681,8 @@ CURLOPT_INFILESIZE_LARGE 7.11.0 CURLOPT_INTERFACE 7.3 CURLOPT_INTERLEAVEDATA 7.20.0 CURLOPT_INTERLEAVEFUNCTION 7.20.0 -CURLOPT_IOCTLDATA 7.12.3 -CURLOPT_IOCTLFUNCTION 7.12.3 +CURLOPT_IOCTLDATA 7.12.3 7.18.0 +CURLOPT_IOCTLFUNCTION 7.12.3 7.18.0 CURLOPT_IPRESOLVE 7.10.8 CURLOPT_ISSUERCERT 7.19.0 CURLOPT_ISSUERCERT_BLOB 7.71.0 @@ -676,7 +698,9 @@ CURLOPT_LOW_SPEED_TIME 7.1 CURLOPT_MAIL_AUTH 7.25.0 CURLOPT_MAIL_FROM 7.20.0 CURLOPT_MAIL_RCPT 7.20.0 -CURLOPT_MAIL_RCPT_ALLLOWFAILS 7.69.0 +CURLOPT_MAIL_RCPT_ALLLOWFAILS 7.69.0 8.2.0 +CURLOPT_MAIL_RCPT_ALLOWFAILS 8.2.0 +CURLOPT_QUICK_EXIT 7.87.0 CURLOPT_MAX_RECV_SPEED_LARGE 7.15.5 CURLOPT_MAX_SEND_SPEED_LARGE 7.15.5 CURLOPT_MAXAGE_CONN 7.65.0 @@ -721,7 +745,8 @@ CURLOPT_PREREQFUNCTION 7.80.0 CURLOPT_PRIVATE 7.10.3 CURLOPT_PROGRESSDATA 7.1 CURLOPT_PROGRESSFUNCTION 7.1 7.32.0 -CURLOPT_PROTOCOLS 7.19.4 +CURLOPT_PROTOCOLS 7.19.4 7.85.0 +CURLOPT_PROTOCOLS_STR 7.85.0 CURLOPT_PROXY 7.1 CURLOPT_PROXY_CAINFO 7.52.0 CURLOPT_PROXY_CAINFO_BLOB 7.77.0 @@ -755,13 +780,14 @@ CURLOPT_PROXYPORT 7.1 CURLOPT_PROXYTYPE 7.10 CURLOPT_PROXYUSERNAME 7.19.1 CURLOPT_PROXYUSERPWD 7.1 -CURLOPT_PUT 7.1 +CURLOPT_PUT 7.1 7.12.1 CURLOPT_QUOTE 7.1 -CURLOPT_RANDOM_FILE 7.7 +CURLOPT_RANDOM_FILE 7.7 7.84.0 CURLOPT_RANGE 7.1 CURLOPT_READDATA 7.9.7 CURLOPT_READFUNCTION 7.1 -CURLOPT_REDIR_PROTOCOLS 7.19.4 +CURLOPT_REDIR_PROTOCOLS 7.19.4 7.85.0 +CURLOPT_REDIR_PROTOCOLS_STR 7.85.0 CURLOPT_REFERER 7.1 CURLOPT_REQUEST_TARGET 7.55.0 CURLOPT_RESOLVE 7.21.3 @@ -781,6 +807,7 @@ CURLOPT_SASL_IR 7.31.0 CURLOPT_SEEKDATA 7.18.0 CURLOPT_SEEKFUNCTION 7.18.0 CURLOPT_SERVER_RESPONSE_TIMEOUT 7.20.0 +CURLOPT_SERVER_RESPONSE_TIMEOUT_MS 8.6.0 CURLOPT_SERVICE_NAME 7.43.0 CURLOPT_SHARE 7.10 CURLOPT_SOCKOPTDATA 7.16.0 @@ -800,10 +827,10 @@ CURLOPT_SSH_AUTH_TYPES 7.16.1 CURLOPT_SSH_COMPRESSION 7.56.0 CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 7.17.1 CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 7.80.0 +CURLOPT_SSH_HOSTKEYDATA 7.84.0 +CURLOPT_SSH_HOSTKEYFUNCTION 7.84.0 CURLOPT_SSH_KEYDATA 7.19.6 CURLOPT_SSH_KEYFUNCTION 7.19.6 -CURLOPT_SSH_HOSTKEYFUNCTION 7.84.0 -CURLOPT_SSH_HOSTKEYDATA 7.84.0 CURLOPT_SSH_KNOWNHOSTS 7.19.6 CURLOPT_SSH_PRIVATE_KEYFILE 7.16.1 CURLOPT_SSH_PUBLIC_KEYFILE 7.16.1 @@ -812,7 +839,7 @@ CURLOPT_SSL_CTX_DATA 7.10.6 CURLOPT_SSL_CTX_FUNCTION 7.10.6 CURLOPT_SSL_EC_CURVES 7.73.0 CURLOPT_SSL_ENABLE_ALPN 7.36.0 -CURLOPT_SSL_ENABLE_NPN 7.36.0 +CURLOPT_SSL_ENABLE_NPN 7.36.0 7.86.0 CURLOPT_SSL_FALSESTART 7.42.0 CURLOPT_SSL_OPTIONS 7.25.0 CURLOPT_SSL_SESSIONID_CACHE 7.16.0 @@ -839,6 +866,7 @@ CURLOPT_TCP_FASTOPEN 7.49.0 CURLOPT_TCP_KEEPALIVE 7.25.0 CURLOPT_TCP_KEEPIDLE 7.25.0 CURLOPT_TCP_KEEPINTVL 7.25.0 +CURLOPT_TCP_KEEPCNT 8.9.0 CURLOPT_TCP_NODELAY 7.11.2 CURLOPT_TELNETOPTIONS 7.7 CURLOPT_TFTP_BLKSIZE 7.19.4 @@ -858,7 +886,7 @@ CURLOPT_TRANSFER_ENCODING 7.21.6 CURLOPT_TRANSFERTEXT 7.1.1 CURLOPT_UNIX_SOCKET_PATH 7.40.0 CURLOPT_UNRESTRICTED_AUTH 7.10.4 -CURLOPT_UPKEEP_INTERVAL_MS 7.62.0 +CURLOPT_UPKEEP_INTERVAL_MS 7.62.0 CURLOPT_UPLOAD 7.1 CURLOPT_UPLOAD_BUFFERSIZE 7.62.0 CURLOPT_URL 7.1 @@ -872,9 +900,11 @@ CURLOPT_WRITEDATA 7.9.7 CURLOPT_WRITEFUNCTION 7.1 CURLOPT_WRITEHEADER 7.1 CURLOPT_WRITEINFO 7.1 +CURLOPT_WS_OPTIONS 7.86.0 CURLOPT_XFERINFODATA 7.32.0 CURLOPT_XFERINFOFUNCTION 7.32.0 CURLOPT_XOAUTH2_BEARER 7.33.0 +CURLOPTDEPRECATED 7.87.0 CURLOPTTYPE_BLOB 7.71.0 CURLOPTTYPE_CBPOINT 7.73.0 CURLOPTTYPE_FUNCTIONPOINT 7.1 @@ -886,6 +916,7 @@ CURLOPTTYPE_STRINGPOINT 7.46.0 CURLOPTTYPE_VALUES 7.73.0 CURLOT_BLOB 7.73.0 CURLOT_CBPTR 7.73.0 +CURLOT_FLAG_ALIAS 7.73.0 CURLOT_FUNCTION 7.73.0 CURLOT_LONG 7.73.0 CURLOT_OBJECT 7.73.0 @@ -936,6 +967,7 @@ CURLPROTO_TFTP 7.19.4 CURLPROXY_HTTP 7.10 CURLPROXY_HTTP_1_0 7.19.4 CURLPROXY_HTTPS 7.52.0 +CURLPROXY_HTTPS2 8.1.0 CURLPROXY_SOCKS4 7.10 CURLPROXY_SOCKS4A 7.18.0 CURLPROXY_SOCKS5 7.10 @@ -997,6 +1029,7 @@ CURLSSH_AUTH_KEYBOARD 7.16.1 CURLSSH_AUTH_NONE 7.16.1 CURLSSH_AUTH_PASSWORD 7.16.1 CURLSSH_AUTH_PUBLICKEY 7.16.1 +CURLSSLBACKEND_AWSLC 8.1.0 CURLSSLBACKEND_AXTLS 7.38.0 7.61.0 CURLSSLBACKEND_BEARSSL 7.68.0 CURLSSLBACKEND_BORINGSSL 7.49.0 @@ -1022,6 +1055,7 @@ CURLSSLOPT_NATIVE_CA 7.71.0 CURLSSLOPT_NO_PARTIALCHAIN 7.68.0 CURLSSLOPT_NO_REVOKE 7.44.0 CURLSSLOPT_REVOKE_BEST_EFFORT 7.70.0 +CURLSSLOPT_EARLYDATA 8.11.0 CURLSSLSET_NO_BACKENDS 7.56.0 CURLSSLSET_OK 7.56.0 CURLSSLSET_TOO_LATE 7.56.0 @@ -1034,11 +1068,15 @@ CURLU_APPENDQUERY 7.62.0 CURLU_DEFAULT_PORT 7.62.0 CURLU_DEFAULT_SCHEME 7.62.0 CURLU_DISALLOW_USER 7.62.0 +CURLU_GET_EMPTY 8.8.0 CURLU_GUESS_SCHEME 7.62.0 CURLU_NO_AUTHORITY 7.67.0 CURLU_NO_DEFAULT_PORT 7.62.0 +CURLU_NO_GUESS_SCHEME 8.9.0 CURLU_NON_SUPPORT_SCHEME 7.62.0 CURLU_PATH_AS_IS 7.62.0 +CURLU_PUNY2IDN 8.3.0 +CURLU_PUNYCODE 7.88.0 CURLU_URLDECODE 7.62.0 CURLU_URLENCODE 7.62.0 CURLUE_BAD_FILE_URL 7.81.0 @@ -1055,6 +1093,7 @@ CURLUE_BAD_QUERY 7.81.0 CURLUE_BAD_SCHEME 7.81.0 CURLUE_BAD_SLASHES 7.81.0 CURLUE_BAD_USER 7.81.0 +CURLUE_LACKS_IDN 7.88.0 CURLUE_MALFORMED_INPUT 7.62.0 CURLUE_NO_FRAGMENT 7.62.0 CURLUE_NO_HOST 7.62.0 @@ -1067,6 +1106,7 @@ CURLUE_NO_USER 7.62.0 CURLUE_NO_ZONEID 7.81.0 CURLUE_OK 7.62.0 CURLUE_OUT_OF_MEMORY 7.62.0 +CURLUE_TOO_LARGE 8.6.0 CURLUE_UNKNOWN_PART 7.62.0 CURLUE_UNSUPPORTED_SCHEME 7.62.0 CURLUE_URLDECODE 7.62.0 @@ -1087,6 +1127,7 @@ CURLUSESSL_CONTROL 7.17.0 CURLUSESSL_NONE 7.17.0 CURLUSESSL_TRY 7.17.0 CURLVERSION_EIGHTH 7.72.0 +CURLVERSION_ELEVENTH 7.87.0 CURLVERSION_FIFTH 7.57.0 CURLVERSION_FIRST 7.10 CURLVERSION_FOURTH 7.16.1 @@ -1097,3 +1138,20 @@ CURLVERSION_SEVENTH 7.70.0 CURLVERSION_SIXTH 7.66.0 CURLVERSION_TENTH 7.77.0 CURLVERSION_THIRD 7.12.0 +CURLVERSION_TWELFTH 8.8.0 +CURLWARNING 7.66.0 +CURLWS_BINARY 7.86.0 +CURLWS_CLOSE 7.86.0 +CURLWS_CONT 7.86.0 +CURLWS_OFFSET 7.86.0 +CURLWS_PING 7.86.0 +CURLWS_PONG 7.86.0 +CURLWS_RAW_MODE 7.86.0 +CURLWS_TEXT 7.86.0 +LIBCURL_COPYRIGHT 7.18.0 +LIBCURL_TIMESTAMP 7.16.2 +LIBCURL_VERSION 7.11.0 +LIBCURL_VERSION_MAJOR 7.11.0 +LIBCURL_VERSION_MINOR 7.11.0 +LIBCURL_VERSION_NUM 7.11.0 +LIBCURL_VERSION_PATCH 7.11.0 diff --git a/src/library/curl/tools/symbols.R b/src/library/curl/tools/symbols.R index f518cadb0c..bb4df9f8b7 100644 --- a/src/library/curl/tools/symbols.R +++ b/src/library/curl/tools/symbols.R @@ -2,21 +2,20 @@ # Therefore you should only update the symbol table using the latest version of libcurl. # On Mac: 'brew install curl' will install to /usr/local/opt/curl -blacklist <- c("CURL_DID_MEMORY_FUNC_TYPEDEFS", "CURL_STRICTER", "CURL_WIN32", "CURLOPT") - # Function to read a symbol library(inline) getsymbol <- function(name){ - if(name %in% blacklist) return(NA_integer_) - fun = cfunction( - cppargs="-I/usr/local/opt/curl/include", - includes = '#include ', - body = paste("return ScalarInteger((int)", name, ");") - ) - val = fun() - rm(fun); gc(); - cat("Found:", name, "=", val, "\n") - return(val) + tryCatch({ + fun = cfunction( + cppargs="-I/opt/homebrew/opt/curl/include", + includes = '#include ', + body = paste("return ScalarInteger((int)", name, ");") + ) + val = fun() + rm(fun); gc(); + cat("Found:", name, "=", val, "\n") + return(val) + }, error = function(e){NA_integer_}) } # The symbols-in-versions file is included with libcurl diff --git a/src/library/curl/tools/testversion.R b/src/library/curl/tools/testversion.R new file mode 100644 index 0000000000..d75128a082 --- /dev/null +++ b/src/library/curl/tools/testversion.R @@ -0,0 +1,3 @@ +ver <- libcurlVersion() +cat("Curl runtime version", ver, "\n") +q('no', status = (numeric_version(ver) < commandArgs(TRUE))) diff --git a/src/library/curl/tools/typelist.R b/src/library/curl/tools/typelist.R deleted file mode 100644 index b2f907e831..0000000000 --- a/src/library/curl/tools/typelist.R +++ /dev/null @@ -1,13 +0,0 @@ -txt <- readLines('tools/typelist.h.in') -m <- regexpr("CURLOPT_\\w+", txt) -opts <- regmatches(txt, m) -i <- match(opts, curl:::curl_symbol_data$name) -table <- curl:::curl_symbol_data[i, ] -for(i in seq_len(nrow(table))){ - name <- table$name[i] - value <- table$value[i] - txt <- gsub(paste0(name, " "), sprintf("/*%s*/ %d ", name, value), fixed = TRUE, txt) -} -#txt <- gsub('[ ]+\\\\', ' \\\\', txt) -header <- "/* This file is autogenerated from typelist.h.in */" -writeLines(c(header, txt), "src/typelist.h") diff --git a/src/library/curl/tools/typelist.h.in b/src/library/curl/tools/typelist.h.in deleted file mode 100644 index 20be8aa753..0000000000 --- a/src/library/curl/tools/typelist.h.in +++ /dev/null @@ -1,62 +0,0 @@ -/* if headers are not included, we provide a copy */ -#ifndef CURLINC_TYPECHECK_GCC_H - -/* evaluates to true if option takes a long argument */ -#define curlcheck_long_option(option) \ - (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) - -#ifdef CURLOPTTYPE_BLOB -#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T && (option) < CURLOPTTYPE_BLOB) -#else -#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T) -#endif - -/* evaluates to true if option takes a data argument to pass to a callback */ -#define curlcheck_cb_data_option(option) \ - ((option) == CURLOPT_CHUNK_DATA || \ - (option) == CURLOPT_CLOSESOCKETDATA || \ - (option) == CURLOPT_DEBUGDATA || \ - (option) == CURLOPT_FNMATCH_DATA || \ - (option) == CURLOPT_HEADERDATA || \ - (option) == CURLOPT_HSTSREADDATA || \ - (option) == CURLOPT_HSTSWRITEDATA || \ - (option) == CURLOPT_INTERLEAVEDATA || \ - (option) == CURLOPT_IOCTLDATA || \ - (option) == CURLOPT_OPENSOCKETDATA || \ - (option) == CURLOPT_PREREQDATA || \ - (option) == CURLOPT_PROGRESSDATA || \ - (option) == CURLOPT_READDATA || \ - (option) == CURLOPT_SEEKDATA || \ - (option) == CURLOPT_SOCKOPTDATA || \ - (option) == CURLOPT_SSH_KEYDATA || \ - (option) == CURLOPT_SSL_CTX_DATA || \ - (option) == CURLOPT_WRITEDATA || \ - (option) == CURLOPT_RESOLVER_START_DATA || \ - (option) == CURLOPT_TRAILERDATA || \ - (option) == CURLOPT_SSH_HOSTKEYDATA || \ - 0) - -/* evaluates to true if option takes a POST data argument (void* or char*) */ -#define curlcheck_postfields_option(option) \ - ((option) == CURLOPT_POSTFIELDS || \ - (option) == CURLOPT_COPYPOSTFIELDS || \ - 0) - -/* evaluates to true if option takes a struct curl_slist * argument */ -#define curlcheck_slist_option(option) \ - ((option) == CURLOPT_HTTP200ALIASES || \ - (option) == CURLOPT_HTTPHEADER || \ - (option) == CURLOPT_MAIL_RCPT || \ - (option) == CURLOPT_POSTQUOTE || \ - (option) == CURLOPT_PREQUOTE || \ - (option) == CURLOPT_PROXYHEADER || \ - (option) == CURLOPT_QUOTE || \ - (option) == CURLOPT_RESOLVE || \ - (option) == CURLOPT_TELNETOPTIONS || \ - (option) == CURLOPT_CONNECT_TO || \ - 0) - -#define curlcheck_string_option(x) \ -(x > 10000 && x < 20000 && !curlcheck_slist_option(x) && !curlcheck_cb_data_option(x)); - -#endif diff --git a/src/library/curl/tools/version.c b/src/library/curl/tools/version.c new file mode 100644 index 0000000000..358aa44320 --- /dev/null +++ b/src/library/curl/tools/version.c @@ -0,0 +1,4 @@ +#include +#if LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 73 +#error Local libcurl version is too old +#endif diff --git a/src/library/curl/tools/winlibs.R b/src/library/curl/tools/winlibs.R index 03413976bb..9aedf6da28 100644 --- a/src/library/curl/tools/winlibs.R +++ b/src/library/curl/tools/winlibs.R @@ -1,31 +1,19 @@ -if(!file.exists("../windows/libcurl/include/curl/curl.h")){ - unlink("../windows", recursive = TRUE) +if(!file.exists("curl.o") && !file.exists("../.deps/libcurl/include/curl/curl.h")){ + unlink("../.deps", recursive = TRUE) url <- if(grepl("aarch", R.version$platform)){ - "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-clang-aarch64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-clang-aarch64.tar.xz" } else if(grepl("clang", Sys.getenv('R_COMPILED_BY'))){ - "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-clang-x86_64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-clang-x86_64.tar.xz" } else if(getRversion() >= "4.2") { - "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-ucrt-x86_64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-ucrt-x86_64.tar.xz" } else { "https://github.com/rwinlib/libcurl/archive/v7.84.0.tar.gz" } download.file(url, basename(url), quiet = TRUE) - dir.create("../windows", showWarnings = FALSE) - untar(basename(url), exdir = "../windows", tar = 'internal') + dir.create("../.deps", showWarnings = FALSE) + untar(basename(url), exdir = "../.deps", tar = 'internal') unlink(basename(url)) - setwd("../windows") + setwd("../.deps") file.rename(list.files(), 'libcurl') - # fix CR or CRLF line endings in a header file - badfile <- "libcurl/include/nghttp2/nghttp2ver.h" - if (file.exists(badfile)) { - cnts <- readBin(badfile, "raw", file.size(badfile)) - if (any(cnts == 0x0a)) { - # has \r\n, remove the \r - cnts <- cnts[cnts != 0x0d] - } else { - # convert \r to \r - cnts[cnts == 0x0d] <- as.raw(0x0a) - } - writeBin(cnts, badfile) - } + invisible() } diff --git a/src/library/curl/vignettes/intro.Rmd b/src/library/curl/vignettes/intro.Rmd new file mode 100644 index 0000000000..f91781169e --- /dev/null +++ b/src/library/curl/vignettes/intro.Rmd @@ -0,0 +1,406 @@ +--- +title: "The curl package: a modern R interface to libcurl" +date: "`r Sys.Date()`" +output: + html_document: + fig_caption: false + toc: true + toc_float: + collapsed: false + smooth_scroll: false + toc_depth: 3 +vignette: > + %\VignetteIndexEntry{The curl package: a modern R interface to libcurl} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + + +```{r, echo = FALSE, message = FALSE} +knitr::opts_chunk$set(comment = "") +options(width = 120, max.print = 100) +wrap.simpleError <- function(x, options) { + paste0("```\n## Error: ", x$message, "\n```") +} +library(curl) +library(jsonlite) +``` + +The curl package provides bindings to the [libcurl](https://curl.se/libcurl/) C library for R. The package supports retrieving data in-memory, downloading to disk, or streaming using the [R "connection" interface](https://stat.ethz.ch/R-manual/R-devel/library/base/html/connections.html). Some knowledge of curl is recommended to use this package. For a more user-friendly HTTP client, have a look at the [httr](https://cran.r-project.org/package=httr/vignettes/quickstart.html) package which builds on curl with HTTP specific tools and logic. + +## Request interfaces + +The curl package implements several interfaces to retrieve data from a URL: + + - `curl_fetch_memory()` saves response in memory + - `curl_download()` or `curl_fetch_disk()` writes response to disk + - `curl()` or `curl_fetch_stream()` streams response data + - `curl_fetch_multi()` (Advanced) process responses via callback functions + +Each interface performs the same HTTP request, they only differ in how response data is processed. + +### Getting in memory + +The `curl_fetch_memory` function is a blocking interface which waits for the request to complete and returns a list with all content (data, headers, status, timings) of the server response. + + +```{r} +req <- curl_fetch_memory("https://hb.cran.dev/get?foo=123") +str(req) +parse_headers(req$headers) +jsonlite::prettify(rawToChar(req$content)) +``` + +The `curl_fetch_memory` interface is the easiest interface and most powerful for building API clients. However it is not suitable for downloading really large files because it is fully in-memory. If you are expecting 100G of data, you probably need one of the other interfaces. + +### Downloading to disk + +The second method is `curl_download`, which has been designed as a drop-in replacement for `download.file` in r-base. It writes the response straight to disk, which is useful for downloading (large) files. + +```{r} +tmp <- tempfile() +curl_download("https://hb.cran.dev/get?bar=456", tmp) +jsonlite::prettify(readLines(tmp)) +``` + +### Streaming data + +The most flexible interface is the `curl` function, which has been designed as a drop-in replacement for base `url`. It will create a so-called connection object, which allows for incremental (asynchronous) reading of the response. + +```{r} +con <- curl("https://hb.cran.dev/get") +open(con) + +# Get 3 lines +out <- readLines(con, n = 3) +cat(out, sep = "\n") + +# Get 3 more lines +out <- readLines(con, n = 3) +cat(out, sep = "\n") + +# Get remaining lines +out <- readLines(con) +close(con) +cat(out, sep = "\n") +``` + +The example shows how to use `readLines` on an opened connection to read `n` lines at a time. Similarly `readBin` is used to read `n` bytes at a time for stream parsing binary data. + +#### Non blocking connections + +As of version 2.3 it is also possible to open connections in non-blocking mode. In this case `readBin` and `readLines` will return immediately with data that is available without waiting. For non-blocking connections we use `isIncomplete` to check if the download has completed yet. + +```{r, eval=FALSE} +# This httpbin mirror doesn't cache +con <- curl("https://nghttp2.org/httpbin/drip?duration=1&numbytes=50") +open(con, "rb", blocking = FALSE) +while(isIncomplete(con)){ + buf <- readBin(con, raw(), 1024) + if(length(buf)) + cat("received: ", rawToChar(buf), "\n") +} +close(con) +``` + +The `curl_fetch_stream` function provides a very simple wrapper around a non-blocking connection. + + +### Async requests + +As of `curl 2.0` the package provides an async interface which can perform multiple simultaneous requests concurrently. The `curl_fetch_multi` adds a request to a pool and returns immediately; it does not actually perform the request. + +```{r} +pool <- new_pool() +success <- function(req){cat("success:", req$url, ": HTTP:", req$status, "\n")} +failure <- function(err){cat("failure:", err, "\n")} +curl_fetch_multi('https://www.google.com', done = success, fail = failure, pool = pool) +curl_fetch_multi('https://cloud.r-project.org', done = success, fail = failure, pool = pool) +curl_fetch_multi('https://hb.cran.dev/blabla', done = success, fail = failure, pool = pool) +curl_fetch_multi('https://doesnotexit.xyz', done = success, fail = failure, pool = pool) +``` + +When we call `multi_run()`, all scheduled requests are performed concurrently. The callback functions get triggered when each request completes. + +```{r} +# This actually performs requests: +out <- multi_run(pool = pool) +print(out) +``` + +The system allows for running many concurrent non-blocking requests. However it is quite complex and requires careful specification of handler functions. + +## Exception handling + +A HTTP requests can encounter two types of errors: + + 1. Connection failure: network down, host not found, invalid SSL certificate, etc + 2. HTTP non-success status: 401 (DENIED), 404 (NOT FOUND), 503 (SERVER PROBLEM), etc + +The first type of errors (connection failures) will always raise an error in R for each interface. However if the requests succeeds and the server returns a non-success HTTP status code, only `curl()` and `curl_download()` will raise an error. Let's dive a little deeper into this. + +### Error automatically + +The `curl` and `curl_download` functions are safest to use because they automatically raise an error if the request was completed but the server returned a non-success (400 or higher) HTTP status. This mimics behavior of base functions `url` and `download.file`. Therefore we can safely write code like this: + +```{r} +# This is OK +curl_download('https://cloud.r-project.org/CRAN_mirrors.csv', 'mirrors.csv') +mirros <- read.csv('mirrors.csv') +unlink('mirrors.csv') +``` + +If the HTTP request was unsuccessful, R will not continue: + +```{r, error=TRUE} +# Oops! A typo in the URL! +curl_download('https://cloud.r-project.org/CRAN_mirrorZ.csv', 'mirrors.csv') +con <- curl('https://cloud.r-project.org/CRAN_mirrorZ.csv') +open(con) +``` + +```{r, echo = FALSE, message = FALSE, warning=FALSE} +close(con) +rm(con) +``` + + +### Check manually + +When using any of the `curl_fetch_*` functions it is important to realize that these do **not** raise an error if the request was completed but returned a non-200 status code. When using `curl_fetch_memory` or `curl_fetch_disk` you need to implement such application logic yourself and check if the response was successful. + +```{r} +req <- curl_fetch_memory('https://cloud.r-project.org/CRAN_mirrors.csv') +print(req$status_code) +``` + +Same for downloading to disk. If you do not check your status, you might have downloaded an error page! + +```{r} +# Oops a typo! +req <- curl_fetch_disk('https://cloud.r-project.org/CRAN_mirrorZ.csv', 'mirrors.csv') +print(req$status_code) + +# This is not the CSV file we were expecting! +head(readLines('mirrors.csv')) +unlink('mirrors.csv') +``` + +If you *do* want the `curl_fetch_*` functions to automatically raise an error, you should set the [`FAILONERROR`](https://curl.se/libcurl/c/CURLOPT_FAILONERROR.html) option to `TRUE` in the handle of the request. + +```{r, error=TRUE} +h <- new_handle(failonerror = TRUE) +curl_fetch_memory('https://cloud.r-project.org/CRAN_mirrorZ.csv', handle = h) +``` + +## Customizing requests + +By default libcurl uses HTTP GET to issue a request to an HTTP url. To send a customized request, we first need to create and configure a curl handle object that is passed to the specific download interface. + +### Setting handle options + +Creating a new handle is done using `new_handle`. After creating a handle object, we can set the libcurl options and http request headers. + +```{r} +h <- new_handle() +handle_setopt(h, copypostfields = "moo=moomooo"); +handle_setheaders(h, + "Content-Type" = "text/moo", + "Cache-Control" = "no-cache", + "User-Agent" = "A cow" +) +``` + +Use the `curl_options()` function to get a list of the options supported by your version of libcurl. The [libcurl documentation](https://curl.se/libcurl/c/curl_easy_setopt.html) explains what each option does. Option names are not case sensitive. + +It is important you check the [libcurl documentation](https://curl.se/libcurl/c/curl_easy_setopt.html) to set options of the correct type. Options in libcurl take several types: + + - number + - string + - slist (vector of strings) + - enum (long) option + +The R bindings will automatically do some type checking and coercion to convert R values to appropriate libcurl option values. Logical (boolean) values in R automatically get converted to `0` or `1` for example [CURLOPT_VERBOSE](https://curl.se/libcurl/c/CURLOPT_VERBOSE.html): + + +```{r} +handle <- new_handle(verbose = TRUE) +``` + +However R does not know if an option is actually boolean. So passing `TRUE`/ `FALSE` to any numeric option will simply set it to `0` or `1` without a warning or error. If an option value cannot be coerced, you get an error: + +```{r, error = TRUE} +# URLOPT_MASFILESIZE must be a number +handle_setopt(handle, maxfilesize = "foo") + +# CURLOPT_USERAGENT must be a string +handle_setopt(handle, useragent = 12345) +``` + + +### ENUM (long) options + +Some curl options take an long in C that actually corresponds to an ENUM value. + +For example the [CURLOPT_USE_SSL](https://curl.se/libcurl/c/CURLOPT_USE_SSL.html) docs explains that there are 4 possible values for this option: `CURLUSESSL_NONE`, `CURLUSESSL_TRY`, `CURLUSESSL_CONTROL`, and `CURLUSESSL_ALL`. To use this option you have to lookup the integer values for these enums in the symbol table. These symbol values never change, so you only need to lookup the value you need once and then hardcode the integer value in your R code. + +```{r} +curl::curl_symbols("CURLUSESSL") +``` + +So suppose we want to set `CURLOPT_USE_SSL` to `CURLUSESSL_ALL` we would use this R code: + +```{r} +handle_setopt(handle, use_ssl = 3) +``` + +### Disabling HTTP/2 + +Another example is the [CURLOPT_HTTP_VERSION](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html) option. This option is needed to disable or enable HTTP/2. However some users are not aware this is actually an ENUM and not a regular numeric value! + +The [docs](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html) explain HTTP_VERSION can be set to one of several strategies for negotiating the HTTP version between client and server. Valid values are: + +```{r} +curl_symbols('CURL_HTTP_VERSION_') +``` + +As seen, the value `2` corresponds to `CURL_HTTP_VERSION_1_1` and `3` corresponds to `CURL_HTTP_VERSION_2_0`. + +As of libcurl 7.62.0, the default `http_version` is `CURL_HTTP_VERSION_2TLS` which uses HTTP/2 when possible, but only for HTTPS connections. Package authors should usually leave the default to let curl select the best appropriate http protocol. + +One exception is when writing a client for a server that seems to be running a buggy HTTP/2 server. Unfortunately this is not uncommon, and curl is a bit more picky than browsers. If you are frequently seeing `Error in the HTTP2 framing layer` error messages, then there is likely a problem with the HTTP/2 layer on the server. + +The easiest remedy is to __disable http/2__ for this server by forcing http 1.1 until the service has upgraded their webservers. To do so, set the `http_version` to `CURL_HTTP_VERSION_1_1` (value: `2`): + +```{r} +# Force using HTTP 1.1 (the number 2 is an enum value, see above) +handle_setopt(handle, http_version = 2) +``` + +Note that the value `1` corresponds to HTTP 1.0 which is a legacy version of HTTP that you should not use! +Code that sets `http_version` to `1` (or even `1.1` which R simply rounds to 1) is almost always a bug. + +## Performing the request + +After the handle has been configured, it can be used with any of the download interfaces to perform the request. For example `curl_fetch_memory` will load store the output of the request in memory: + +```{r} +req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) +jsonlite::prettify(rawToChar(req$content)) +``` + +Alternatively we can use `curl()` to read the data of via a connection interface: + +```{r} +con <- curl("https://hb.cran.dev/post", handle = h) +jsonlite::prettify(readLines(con)) +``` + +```{r, echo = FALSE, message = FALSE, warning=FALSE} +close(con) +``` + +Or we can use `curl_download` to write the response to disk: + +```{r} +tmp <- tempfile() +curl_download("https://hb.cran.dev/post", destfile = tmp, handle = h) +jsonlite::prettify(readLines(tmp)) +``` + +Or perform the same request with a multi pool: + +```{r} +curl_fetch_multi("https://hb.cran.dev/post", handle = h, done = function(res){ + cat("Request complete! Response content:\n") + cat(rawToChar(res$content)) +}) + +# Perform the request +out <- multi_run() +``` + +### Reading cookies + +Curl handles automatically keep track of cookies set by the server. At any given point we can use `handle_cookies` to see a list of current cookies in the handle. + +```{r} +# Start with a fresh handle +h <- new_handle() + +# Ask server to set some cookies +req <- curl_fetch_memory("https://hb.cran.dev/cookies/set?foo=123&bar=ftw", handle = h) +req <- curl_fetch_memory("https://hb.cran.dev/cookies/set?baz=moooo", handle = h) +handle_cookies(h) + +# Unset a cookie +req <- curl_fetch_memory("https://hb.cran.dev/cookies/delete?foo", handle = h) +handle_cookies(h) +``` + +The `handle_cookies` function returns a data frame with 7 columns as specified in the [netscape cookie file format](http://www.cookiecentral.com/faq/#3.5). + +### On reusing handles + +In most cases you should not re-use a single handle object for more than one request. The only benefit of reusing a handle for multiple requests is to keep track of cookies set by the server (seen above). This could be needed if your server uses session cookies, but this is rare these days. Most APIs set state explicitly via http headers or parameters, rather than implicitly via cookies. + +In recent versions of the curl package there are no performance benefits of reusing handles. The overhead of creating and configuring a new handle object is negligible. The safest way to issue multiple requests, either to a single server or multiple servers is by using a separate handle for each request (which is the default) + +```{r} +req1 <- curl_fetch_memory("https://hb.cran.dev/get") +req2 <- curl_fetch_memory("https://www.google.com") +``` + +In past versions of this package you needed to manually use a handle to take advantage of http Keep-Alive. However as of version 2.3 this is no longer the case: curl automatically maintains global a pool of open http connections shared by all handles. When performing many requests to the same server, curl automatically uses existing connections when possible, eliminating TCP/SSL handshaking overhead: + +```{r} +req <- curl_fetch_memory("https://api.github.com/users/ropensci") +req$times + +req2 <- curl_fetch_memory("https://api.github.com/users/rstudio") +req2$times +``` + +If you really need to re-use a handle, do note that that curl does not cleanup the handle after each request. All of the options and internal fields will linger around for all future request until explicitly reset or overwritten. This can sometimes leads to unexpected behavior. + +```{r} +handle_reset(h) +``` + +The `handle_reset` function will reset all curl options and request headers to the default values. It will **not** erase cookies and it will still keep alive the connections. Therefore it is good practice to call `handle_reset` after performing a request if you want to reuse the handle for a subsequent request. Still it is always safer to create a new fresh handle when possible, rather than recycling old ones. + +### Posting forms + +The `handle_setform` function is used to perform a `multipart/form-data` HTTP POST request (a.k.a. posting a form). Values can be either strings, raw vectors (for binary data) or files. + +```{r} +# Posting multipart +h <- new_handle() +handle_setform(h, + foo = "blabla", + bar = charToRaw("boeboe"), + iris = form_data(serialize(iris, NULL), "application/rda"), + description = form_file(system.file("DESCRIPTION")), + logo = form_file(file.path(R.home('doc'), "html/logo.jpg"), "image/jpeg") +) +req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) +``` + +The `form_file` function is used to upload files with the form post. It has two arguments: a file path, and optionally a content-type value. If no content-type is set, curl will guess the content type of the file based on the file extension. + +The `form_data` function is similar but simply posts a string or raw value with a custom content-type. + +### Using pipes + +All of the `handle_xxx` functions return the handle object so that function calls can be chained using the popular pipe operators: + +```{r eval=getRversion() > "4.1"} +# Perform request +res <- new_handle() |> + handle_setopt(copypostfields = "moo=moomooo") |> + handle_setheaders("Content-Type"="text/moo", "Cache-Control"="no-cache", "User-Agent"="A cow") |> + curl_fetch_memory(url = "https://hb.cran.dev/post") + +# Parse response +res$content |> rawToChar() |> jsonlite::prettify() +``` diff --git a/src/library/curl/vignettes/windows.Rmd b/src/library/curl/vignettes/windows.Rmd index 6e5a9fb278..b358f328a8 100644 --- a/src/library/curl/vignettes/windows.Rmd +++ b/src/library/curl/vignettes/windows.Rmd @@ -46,13 +46,13 @@ Have a look at `curl::curl_version()` to see which ssl backends are available an ```r curl::curl_version() #> $version -#> [1] "7.64.1" +#> [1] "8.8.0" #> -#> $ssl_version -#> [1] "(OpenSSL/1.1.1a) Schannel" +#> $headers +#> [1] "8.8.0" #> -#> $libz_version -#> [1] "1.2.8" +#> $ssl_version +#> [1] "(OpenSSL/3.3.0) Schannel" #> ... ``` @@ -67,8 +67,8 @@ write('CURL_SSL_BACKEND=openssl', file = "~/.Renviron", append = TRUE) Now if you restart R, the default back-end should have changed: ```r -> curl::curl_version()$ssl_version -[1] "OpenSSL/1.1.1m (Schannel)" +curl::curl_version()$ssl_version +#> [1] "OpenSSL/3.3.0 (Schannel)" ``` Optionally, you can also set `CURL_CA_BUNDLE` in your `~/.Renviron` to use a custom trust bundle. If `CURL_CA_BUNDLE` is not set, we use the Windows cert store. When using Schannel, no trust bundle can be specified because we always use the certificates from the native Windows cert store. From 1b887ea05df666c24a58363cab062b094671dc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 14:30:48 +0200 Subject: [PATCH 10/25] Update embedded pkgcache To avoid using |> and httr2. --- src/library/pkgcache/DESCRIPTION | 8 +- src/library/pkgcache/R/ppm-sso-app.R | 146 +++++++++++++++++ src/library/pkgcache/R/ppm-sso.R | 230 ++++++--------------------- 3 files changed, 200 insertions(+), 184 deletions(-) create mode 100644 src/library/pkgcache/R/ppm-sso-app.R diff --git a/src/library/pkgcache/DESCRIPTION b/src/library/pkgcache/DESCRIPTION index 76dcbf3588..86bd766c01 100644 --- a/src/library/pkgcache/DESCRIPTION +++ b/src/library/pkgcache/DESCRIPTION @@ -13,11 +13,11 @@ License: MIT + file LICENSE URL: https://r-lib.github.io/pkgcache/, https://github.com/r-lib/pkgcache BugReports: https://github.com/r-lib/pkgcache/issues -Depends: R (>= 4.1.0) +Depends: R (>= 3.4) Imports: callr (>= 2.0.4.9000), cli (>= 3.2.0), curl (>= 3.2), filelock, jsonlite, processx (>= 3.3.0.9001), R6, tools, utils -Suggests: covr, debugme, desc, fs, httr2, keyring, openssl, pillar, - pingr, RcppTOML, rprojroot, sessioninfo, spelling, testthat (>= +Suggests: covr, debugme, desc, fs, keyring, openssl, pillar, pingr, + RcppTOML, rprojroot, sessioninfo, spelling, testthat (>= 3.2.0), webfakes (>= 1.1.5), withr, zip Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 @@ -27,7 +27,7 @@ Language: en-US Roxygen: list(markdown = TRUE, r6 = FALSE) RoxygenNote: 7.3.2.9000 NeedsCompilation: yes -Packaged: 2026-05-08 11:38:13 UTC; gaborcsardi +Packaged: 2026-05-08 12:30:29 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Posit Software, PBC [cph, fnd] (ROR: ) Maintainer: Gábor Csárdi diff --git a/src/library/pkgcache/R/ppm-sso-app.R b/src/library/pkgcache/R/ppm-sso-app.R new file mode 100644 index 0000000000..351c0b6f7e --- /dev/null +++ b/src/library/pkgcache/R/ppm-sso-app.R @@ -0,0 +1,146 @@ +# nocov start + +# Fake PPM server that proxies to Auth0, for testing ppm_sso_device_flow(). +# Auth0 device flow does not use PKCE, so we verify the PKCE challenge +# locally and forward only the device_code to Auth0's /oauth/token. +ppm_sso_app <- function( + auth0_domain, + client_id, + audience = NULL, + scope = "openid profile email" +) { + app <- webfakes::new_app() + + app$use("logger" = webfakes::mw_log()) + app$use("urlencoded body parser" = webfakes::mw_urlencoded()) + app$use("json body parser" = webfakes::mw_json()) + + app$locals$challenges <- new.env(parent = emptyenv()) + app$locals$auth0_domain <- auth0_domain + app$locals$client_id <- client_id + app$locals$audience <- audience + app$locals$scope <- scope + + # Bearer-token check used by ppm_sso_can_authenticate(): any token passes. + app$get("/", function(req, res) { + res$set_status(200L)$send("ok") + }) + + app$post("/__api__/device", function(req, res) { + challenge <- req$form$code_challenge + method <- req$form$code_challenge_method %||% "S256" + if (!identical(method, "S256")) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_challenge_method") + )) + } + + payload <- list( + client_id = app$locals$client_id, + scope = app$locals$scope, + audience = app$locals$audience + ) + + upstream <- ppm_sso_post_form( + paste0("https://", app$locals$auth0_domain, "/oauth/device/code"), + payload + ) + + if (upstream$status >= 400L) { + return(res$set_status(upstream$status)$send_json( + auto_unbox = TRUE, + upstream$body + )) + } + + assign(upstream$body$device_code, challenge, envir = app$locals$challenges) + + res$send_json( + auto_unbox = TRUE, + list( + device_code = upstream$body$device_code, + user_code = upstream$body$user_code, + verification_uri = upstream$body$verification_uri, + verification_uri_complete = upstream$body$verification_uri_complete, + expires_in = upstream$body$expires_in, + interval = upstream$body$interval %||% 5L + ) + ) + }) + + app$post("/__api__/device_access", function(req, res) { + device_code <- req$form$device_code + verifier <- req$form$code_verifier + + if (!exists(device_code, envir = app$locals$challenges, inherits = FALSE)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "expired_token") + )) + } + expected <- get( + device_code, + envir = app$locals$challenges, + inherits = FALSE + ) + actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) + if (!identical(expected, actual)) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "invalid_grant") + )) + } + + upstream <- ppm_sso_post_form( + paste0("https://", app$locals$auth0_domain, "/oauth/token"), + list( + grant_type = "urn:ietf:params:oauth:grant-type:device_code", + device_code = device_code, + client_id = app$locals$client_id + ) + ) + + if (upstream$status == 200L) { + rm(list = device_code, envir = app$locals$challenges) + return(res$send_json( + auto_unbox = TRUE, + list(id_token = upstream$body$id_token) + )) + } + + # Auth0 returns 403 for authorization_pending / slow_down; the PPM client + # only treats 400 as a soft pending state, so translate the status. + res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = upstream$body$error %||% "unknown_error") + ) + }) + + # Trivial token exchange: echo subject_token back as access_token. + app$post("/__api__/token", function(req, res) { + if ( + !identical( + req$form$grant_type, + "urn:ietf:params:oauth:grant-type:token-exchange" + ) + ) { + return(res$set_status(400L)$send_json( + auto_unbox = TRUE, + list(error = "unsupported_grant_type") + )) + } + res$send_json( + auto_unbox = TRUE, + list( + access_token = req$form$subject_token, + token_type = "Bearer", + issued_token_type = "urn:ietf:params:oauth:token-type:access_token" + ) + ) + }) + + app +} + +# nocov end diff --git a/src/library/pkgcache/R/ppm-sso.R b/src/library/pkgcache/R/ppm-sso.R index ac73bb1bea..6e3daa20bb 100644 --- a/src/library/pkgcache/R/ppm-sso.R +++ b/src/library/pkgcache/R/ppm-sso.R @@ -2,6 +2,29 @@ ppm_sso_data <- new.env(parent = emptyenv()) ppm_sso_data$name <- "ppm" ppm_sso_data$viable <- FALSE +ppm_sso_post_form <- function(url, payload) { + payload <- payload[!vapply(payload, is.null, logical(1))] + body <- paste( + paste0( + curl::curl_escape(names(payload)), + "=", + curl::curl_escape(unlist(payload, use.names = FALSE)) + ), + collapse = "&" + ) + h <- curl::new_handle() + curl::handle_setheaders( + h, + "Content-Type" = "application/x-www-form-urlencoded" + ) + curl::handle_setopt(h, post = TRUE, postfields = body) + resp <- curl::curl_fetch_memory(url, handle = h) + list( + status = resp$status_code, + body = jsonlite::fromJSON(rawToChar(resp$content), simplifyVector = FALSE) + ) +} + ppm_sso_init <- function(url = NULL) { url <- url %||% Sys.getenv("PACKAGEMANAGER_ADDRESS", NA_character_) if (!is_string(url)) { @@ -82,13 +105,10 @@ ppm_sso_get_existing_token <- function() { } ppm_sso_can_authenticate <- function(token) { - req <- httr2::request(ppm_sso_data$ppm_url) |> - httr2::req_auth_bearer_token(token) |> - httr2::req_error(is_error = function(resp) FALSE) # Handle errors manually - - resp <- httr2::req_perform(req) - - status <- httr2::resp_status(resp) + h <- curl::new_handle() + curl::handle_setheaders(h, "Authorization" = paste("Bearer", token)) + resp <- curl::curl_fetch_memory(ppm_sso_data$ppm_url, handle = h) + status <- resp$status_code status < 500 && status != 401 && status != 403 } @@ -118,10 +138,15 @@ ppm_sso_device_flow <- function() { code_challenge_method = "S256", code_challenge = challenge ) - init_resp_body <- httr2::request(init_url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_perform() |> - httr2::resp_body_json() + init_resp <- ppm_sso_post_form(init_url, payload) + if (init_resp$status >= 400) { + stop( + "Failed to initiate device authorization (HTTP ", + init_resp$status, + ")." + ) + } + init_resp_body <- init_resp$body display_uri <- init_resp_body$verification_uri_complete %||% init_resp_body$verification_uri @@ -160,11 +185,16 @@ ppm_sso_identity_to_ppm_token <- function(identity_token) { subject_token_type = "urn:ietf:params:oauth:token-type:id_token" ) - resp <- httr2::request(url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_perform() + resp <- ppm_sso_post_form(url, payload) + if (resp$status >= 400) { + stop( + "Failed to exchange identity token for PPM token (HTTP ", + resp$status, + ")." + ) + } - token_data <- httr2::resp_body_json(resp) + token_data <- resp$body if (is.null(token_data$access_token)) { stop("Failed to exchange identity token for PPM token.") } @@ -261,18 +291,13 @@ ppm_sso_complete_device_auth = function( ) while (as.numeric(Sys.time() - start_time) < expires_in) { - resp <- httr2::request(url) |> - httr2::req_body_form(!!!payload) |> - httr2::req_error(is_error = \(resp) FALSE) |> # Handle errors manually - httr2::req_perform() - - status <- httr2::resp_status(resp) + resp <- ppm_sso_post_form(url, payload) + status <- resp$status if (status == 200) { - return(httr2::resp_body_json(resp)) + return(resp$body) } else if (status == 400) { - error_data <- httr2::resp_body_json(resp) - error_code <- error_data$error + error_code <- resp$body$error if (error_code == "access_denied") { stop("Access denied by user.") } @@ -281,7 +306,7 @@ ppm_sso_complete_device_auth = function( } # For "authorization_pending" or "slow_down", just wait and retry. } else { - httr2::resp_check_status(resp) + stop("Device authorization failed (HTTP ", status, ").") } Sys.sleep(interval) @@ -289,158 +314,3 @@ ppm_sso_complete_device_auth = function( stop("Device authorization timed out.") } - -# nocov start - -# Fake PPM server that proxies to Auth0, for testing ppm_sso_device_flow(). -# Auth0 device flow does not use PKCE, so we verify the PKCE challenge -# locally and forward only the device_code to Auth0's /oauth/token. -ppm_sso_fake_app <- function( - auth0_domain, - client_id, - audience = NULL, - scope = "openid profile email" -) { - app <- webfakes::new_app() - - app$use("logger" = webfakes::mw_log()) - app$use("urlencoded body parser" = webfakes::mw_urlencoded()) - app$use("json body parser" = webfakes::mw_json()) - - app$locals$challenges <- new.env(parent = emptyenv()) - app$locals$auth0_domain <- auth0_domain - app$locals$client_id <- client_id - app$locals$audience <- audience - app$locals$scope <- scope - - # Bearer-token check used by ppm_sso_can_authenticate(): any token passes. - app$get("/", function(req, res) { - res$set_status(200L)$send("ok") - }) - - app$post("/__api__/device", function(req, res) { - challenge <- req$form$code_challenge - method <- req$form$code_challenge_method %||% "S256" - if (!identical(method, "S256")) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "unsupported_challenge_method") - )) - } - - payload <- list( - client_id = app$locals$client_id, - scope = app$locals$scope - ) - if (!is.null(app$locals$audience)) { - payload$audience <- app$locals$audience - } - - upstream <- httr2::request( - paste0("https://", app$locals$auth0_domain, "/oauth/device/code") - ) |> - httr2::req_body_form(!!!payload) |> - httr2::req_error(is_error = function(r) FALSE) |> - httr2::req_perform() - - body <- httr2::resp_body_json(upstream) - if (httr2::resp_status(upstream) >= 400L) { - return(res$set_status(httr2::resp_status(upstream))$send_json( - auto_unbox = TRUE, - body - )) - } - - assign(body$device_code, challenge, envir = app$locals$challenges) - - res$send_json( - auto_unbox = TRUE, - list( - device_code = body$device_code, - user_code = body$user_code, - verification_uri = body$verification_uri, - verification_uri_complete = body$verification_uri_complete, - expires_in = body$expires_in, - interval = body$interval %||% 5L - ) - ) - }) - - app$post("/__api__/device_access", function(req, res) { - device_code <- req$form$device_code - verifier <- req$form$code_verifier - - if (!exists(device_code, envir = app$locals$challenges, inherits = FALSE)) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "expired_token") - )) - } - expected <- get( - device_code, - envir = app$locals$challenges, - inherits = FALSE - ) - actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) - if (!identical(expected, actual)) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "invalid_grant") - )) - } - - upstream <- httr2::request( - paste0("https://", app$locals$auth0_domain, "/oauth/token") - ) |> - httr2::req_body_form( - grant_type = "urn:ietf:params:oauth:grant-type:device_code", - device_code = device_code, - client_id = app$locals$client_id - ) |> - httr2::req_error(is_error = function(r) FALSE) |> - httr2::req_perform() - - body <- httr2::resp_body_json(upstream) - if (httr2::resp_status(upstream) == 200L) { - rm(list = device_code, envir = app$locals$challenges) - return(res$send_json( - auto_unbox = TRUE, - list(id_token = body$id_token) - )) - } - - # Auth0 returns 403 for authorization_pending / slow_down; the PPM client - # only treats 400 as a soft pending state, so translate the status. - res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = body$error %||% "unknown_error") - ) - }) - - # Trivial token exchange: echo subject_token back as access_token. - app$post("/__api__/token", function(req, res) { - if ( - !identical( - req$form$grant_type, - "urn:ietf:params:oauth:grant-type:token-exchange" - ) - ) { - return(res$set_status(400L)$send_json( - auto_unbox = TRUE, - list(error = "unsupported_grant_type") - )) - } - res$send_json( - auto_unbox = TRUE, - list( - access_token = req$form$subject_token, - token_type = "Bearer", - issued_token_type = "urn:ietf:params:oauth:token-type:access_token" - ) - ) - }) - - app -} - -# nocov end From 7bd9152d1111a722eb718e38a3cb013c420a5586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 14:31:31 +0200 Subject: [PATCH 11/25] Revert "Update embedded curl" This reverts commit 752ee15927c30e034d868148fdb1babf3b085d01. --- src/library/curl.patch | 34 ++ src/library/curl/DESCRIPTION | 37 +- src/library/curl/LICENSE | 4 +- src/library/curl/NAMESPACE | 6 - src/library/curl/NEWS | 73 ---- src/library/curl/R/curl.R | 12 +- src/library/curl/R/download.R | 20 +- src/library/curl/R/echo.R | 23 +- src/library/curl/R/email.R | 48 +-- src/library/curl/R/errcodes.R | 42 --- src/library/curl/R/fetch.R | 51 +-- src/library/curl/R/form.R | 4 +- src/library/curl/R/handle.R | 49 ++- src/library/curl/R/multi.R | 61 ++-- src/library/curl/R/multi_download.R | 59 +-- src/library/curl/R/nslookup.R | 12 +- src/library/curl/R/onload.R | 47 +-- src/library/curl/R/options.R | 56 ++- src/library/curl/R/parser.R | 164 --------- src/library/curl/R/sysdata.rda | Bin 12702 -> 12051 bytes src/library/curl/R/upload.R | 10 +- src/library/curl/R/utilities.R | 35 +- src/library/curl/R/writer.R | 6 +- src/library/curl/cleanup | 3 +- src/library/curl/configure | 51 +-- src/library/curl/inst/WORDLIST | 8 +- src/library/curl/src/Makevars.in | 6 +- src/library/curl/src/Makevars.win | 16 +- src/library/curl/src/callbacks.c | 4 +- src/library/curl/src/curl-common.h | 26 +- src/library/curl/src/curl.c | 67 ++-- src/library/curl/src/download.c | 10 +- src/library/curl/src/dryrun.c | 44 --- src/library/curl/src/handle.c | 212 ++++++----- src/library/curl/src/ieproxy.c | 6 +- src/library/curl/src/init.c | 16 +- src/library/curl/src/interrupt.c | 32 +- src/library/curl/src/macos-polyfill.h | 42 --- src/library/curl/src/multi.c | 53 +-- src/library/curl/src/options.c | 4 + src/library/curl/src/ssl.c | 2 +- src/library/curl/src/typechecking.c | 28 ++ src/library/curl/src/typelist.h | 63 ++++ src/library/curl/src/urlparser.c | 101 ----- src/library/curl/src/utils.c | 86 ++--- src/library/curl/src/version.c | 58 ++- src/library/curl/tools/errorcodes.txt | 128 ------- src/library/curl/tools/make-errorcodes.R | 11 - src/library/curl/tools/symbols-in-versions | 124 ++----- src/library/curl/tools/symbols.R | 23 +- src/library/curl/tools/testversion.R | 3 - src/library/curl/tools/typelist.R | 13 + src/library/curl/tools/typelist.h.in | 62 ++++ src/library/curl/tools/version.c | 4 - src/library/curl/tools/winlibs.R | 30 +- src/library/curl/vignettes/intro.Rmd | 406 --------------------- src/library/curl/vignettes/windows.Rmd | 14 +- 57 files changed, 794 insertions(+), 1815 deletions(-) create mode 100644 src/library/curl.patch delete mode 100644 src/library/curl/R/errcodes.R delete mode 100644 src/library/curl/R/parser.R delete mode 100644 src/library/curl/src/dryrun.c delete mode 100644 src/library/curl/src/macos-polyfill.h create mode 100644 src/library/curl/src/typelist.h delete mode 100644 src/library/curl/src/urlparser.c delete mode 100644 src/library/curl/tools/errorcodes.txt delete mode 100644 src/library/curl/tools/make-errorcodes.R delete mode 100644 src/library/curl/tools/testversion.R create mode 100644 src/library/curl/tools/typelist.R create mode 100644 src/library/curl/tools/typelist.h.in delete mode 100644 src/library/curl/tools/version.c delete mode 100644 src/library/curl/vignettes/intro.Rmd diff --git a/src/library/curl.patch b/src/library/curl.patch new file mode 100644 index 0000000000..b830bc6241 --- /dev/null +++ b/src/library/curl.patch @@ -0,0 +1,34 @@ +diff --git a/src/multi.c b/src/multi.c +index 2e0be9c..3cf9332 100644 +--- a/src/library/curl/src/multi.c ++++ b/src/library/curl/src/multi.c +@@ -1,6 +1,11 @@ + #include "curl-common.h" + #include + ++#include ++#if R_VERSION < R_Version(4, 5, 0) ++# define R_ClosureFormals(x) FORMALS(x) ++#endif ++ + /* Notes: + * - First check for unhandled messages in curl_multi_info_read() before curl_multi_perform() + * - Use Rf_eval() to callback instead of R_tryEval() to propagate interrupt or error back to C +@@ -146,7 +151,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ + if(status == CURLE_OK){ + total_success++; + if(Rf_isFunction(cb_complete)){ +- int arglen = Rf_length(FORMALS(cb_complete)); ++ int arglen = Rf_length(R_ClosureFormals(cb_complete)); + SEXP out = PROTECT(make_handle_response(ref)); + SET_VECTOR_ELT(out, 6, buf); + SEXP call = PROTECT(Rf_lcons(cb_complete, arglen ? Rf_lcons(out, R_NilValue) : R_NilValue)); +@@ -157,7 +162,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ + } else { + total_fail++; + if(Rf_isFunction(cb_error)){ +- int arglen = Rf_length(FORMALS(cb_error)); ++ int arglen = Rf_length(R_ClosureFormals(cb_error)); + SEXP buf = PROTECT(Rf_mkString(strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(status))); + SEXP call = PROTECT(Rf_lcons(cb_error, arglen ? Rf_lcons(buf, R_NilValue) : R_NilValue)); + //R_tryEval(call, R_GlobalEnv, &cbfail); diff --git a/src/library/curl/DESCRIPTION b/src/library/curl/DESCRIPTION index cb6860e847..d30f08985b 100644 --- a/src/library/curl/DESCRIPTION +++ b/src/library/curl/DESCRIPTION @@ -1,34 +1,39 @@ Package: curl Type: Package Title: A Modern and Flexible Web Client for R -Version: 7.1.0 +Version: 5.2.3 Authors@R: c( person("Jeroen", "Ooms", role = c("aut", "cre"), email = "jeroenooms@gmail.com", comment = c(ORCID = "0000-0002-4035-0289")), - person("Hadley", "Wickham", role = "ctb"), - person("Posit Software, PBC", role = "cph")) -Description: Bindings to 'libcurl' for performing fully - configurable HTTP/FTP requests where responses can be processed in memory, on - disk, or streaming via the callback or connection interfaces. Some knowledge - of 'libcurl' is recommended; for a more-user-friendly web client see the - 'httr2' package which builds on this package with http specific tools and logic. + person("Hadley", "Wickham", , "hadley@rstudio.com", role = "ctb"), + person("RStudio", role = "cph") + ) +Description: The curl() and curl_download() functions provide highly + configurable drop-in replacements for base url() and download.file() with + better performance, support for encryption (https, ftps), gzip compression, + authentication, and other 'libcurl' goodies. The core of the package implements a + framework for performing fully customized requests where data can be processed + either in memory, on disk, or streaming via the callback or connection + interfaces. Some knowledge of 'libcurl' is recommended; for a more-user-friendly + web client see the 'httr' package which builds on this package with http + specific tools and logic. License: MIT + file LICENSE -SystemRequirements: libcurl (>= 7.73): libcurl-devel (rpm) or - libcurl4-openssl-dev (deb) -URL: https://jeroen.r-universe.dev/curl +SystemRequirements: libcurl: libcurl-devel (rpm) or + libcurl4-openssl-dev (deb). +URL: https://jeroen.r-universe.dev/curl https://curl.se/libcurl/ BugReports: https://github.com/jeroen/curl/issues Suggests: spelling, testthat (>= 1.0.0), knitr, jsonlite, later, rmarkdown, httpuv (>= 1.4.4), webutils VignetteBuilder: knitr Depends: R (>= 3.0.0) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.0 Encoding: UTF-8 Language: en-US NeedsCompilation: yes -Packaged: 2026-04-21 14:47:54 UTC; jeroen -Author: Jeroen Ooms [aut, cre] (ORCID: ), +Packaged: 2024-09-19 15:43:51 UTC; jeroen +Author: Jeroen Ooms [aut, cre] (), Hadley Wickham [ctb], - Posit Software, PBC [cph] + RStudio [cph] Maintainer: Jeroen Ooms Repository: CRAN -Date/Publication: 2026-04-22 09:40:02 UTC +Date/Publication: 2024-09-20 11:50:24 UTC diff --git a/src/library/curl/LICENSE b/src/library/curl/LICENSE index 46f81fa6c8..a8cf97ef4f 100644 --- a/src/library/curl/LICENSE +++ b/src/library/curl/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2024 -COPYRIGHT HOLDER: Jeroen Ooms; Posit Software, PBC +YEAR: 2022 +COPYRIGHT HOLDER: Jeroen Ooms; RStudio diff --git a/src/library/curl/NAMESPACE b/src/library/curl/NAMESPACE index ecef1b62e8..3cd613d39b 100644 --- a/src/library/curl/NAMESPACE +++ b/src/library/curl/NAMESPACE @@ -13,10 +13,7 @@ export(curl_fetch_echo) export(curl_fetch_memory) export(curl_fetch_multi) export(curl_fetch_stream) -export(curl_modify_url) export(curl_options) -export(curl_options_table) -export(curl_parse_url) export(curl_symbols) export(curl_unescape) export(curl_upload) @@ -49,7 +46,6 @@ export(parse_headers) export(parse_headers_list) export(send_mail) useDynLib(curl,R_curl_connection) -useDynLib(curl,R_curl_dryrun) useDynLib(curl,R_curl_escape) useDynLib(curl,R_curl_fetch_disk) useDynLib(curl,R_curl_fetch_memory) @@ -69,7 +65,6 @@ useDynLib(curl,R_handle_getheaders) useDynLib(curl,R_handle_reset) useDynLib(curl,R_handle_setform) useDynLib(curl,R_handle_setopt) -useDynLib(curl,R_modify_url) useDynLib(curl,R_multi_add) useDynLib(curl,R_multi_cancel) useDynLib(curl,R_multi_fdset) @@ -81,7 +76,6 @@ useDynLib(curl,R_new_file_writer) useDynLib(curl,R_new_handle) useDynLib(curl,R_nslookup) useDynLib(curl,R_option_types) -useDynLib(curl,R_parse_url) useDynLib(curl,R_proxy_info) useDynLib(curl,R_split_string) useDynLib(curl,R_total_handles) diff --git a/src/library/curl/NEWS b/src/library/curl/NEWS index c56890f13e..67f8f1a3d0 100644 --- a/src/library/curl/NEWS +++ b/src/library/curl/NEWS @@ -1,76 +1,3 @@ -7.1.0 - - Everything now works out of the box under emscripten (webR) by automatically - bootstrapping a ws gateway. - - Increase max size of string returned by ie_proxy_info() to 65536 - - Fix a unit test for libcurl 8.20 - -7.0.0 - - Major cleanup: packge now requires libcurl >= 7.73. Removed all conditioning - and fallbacks for older libcurl versions (#413). - - Removed the fallback ADA parser and unconditinoally use the curl URL parser. - - Removed the legacy type-checking code as we can unconditionally use the easy- - option API. - - Support option('netrc') to match base R >= 4.6.0. - - Setting any value in curl_modify_url() to NA or "" will now unset it. - -6.4.0 - - Vendor a static libcurl on RHEL/CentOS/Rocky 7 and 8 because stock version is - too old now to support most functionality. - - curl_modify_url() and curl_parse_url() now force uppercase percentage codes - for url-encoding to match curl_escape() - - Error messages are now printed with a linebreak in between. - - Better documentation for handle_setform() and handle_getdata() - -6.3.0 - - New function curl_modify_url() - - MacOS/Windows: update to libcurl 8.14.1 - - MacOS: our static build of libcurl now links to the MacOS native LibreSSL - and nghttp in order to use the same keychain certificates as native curl. - -6.2.3 - - Non blocking connections now wait for 10ms before returning no data to prevent - busy waiting when readBin is called in a loop. See #399 - - Fix a flaky unit test - -6.2.2 - - Fix a unit test for libcurl 8.13.0 - - Cleanup build - -6.2.1 - - Workaround for change of behavior in libcurl 8.12.0 - - The multi-run fail callback argument now has a curl-error class - - Fixed a slowness in curl_echo() due to threading problem in httpuv - -6.2.0 - - MacOS: we have temporarily switched to a static build of libcurl 8.11.1 until - apple updates their very buggy libcurl (#376) - - Fix LTO error for R_multi_setopt - - Fix parsing of #fragment links in curl_parse() (#366) - -6.1.0 - - Fix a rchk bug - - Enable CURLOPT_PIPEWAIT by default to prefer multiplex when possible - - Enable setting max_streams in multi_set(), default to 10 - - Allow open(curl::curl()) to be interrupted - - Interrupting a download no longer causes an error (#365) - -6.0.1 - - Fix a build issue with libcurl 8.11.0 - - Support multi_fdset() for connection objects (#355) - -6.0.0 - - New curl_parse_url() function to expose curl URL parsing interface. - - Errors now have classes and more detailed messages - - Do not use shared connection pool for FTP requests (works around bug in - libcurl < 8.8.0) - - Properly clean handle when open() fails for a curl() connection - - Parameter 'timeout' renamed to 'multi-timeout' in multi_download() - - Default behavior is now to error if a download is stalled for 600 seconds - - The MacOS binary packages now require at least libcurl 7.80 which is included - with all current MacOS versions (13+) as well as recent patch releases for - legacy MacOS versions (11.7 and 12.7). - - Windows: update to libcurl 8.10.1 - 5.2.3 - Remove some CMD check verbosity per new CRAN rules - New maintainer email address diff --git a/src/library/curl/R/curl.R b/src/library/curl/R/curl.R index 70d362f8d9..848294a530 100644 --- a/src/library/curl/R/curl.R +++ b/src/library/curl/R/curl.R @@ -1,13 +1,13 @@ #' Curl connection interface #' -#' Drop-in replacement for base [url()] that supports https, ftps, -#' gzip, deflate, etc. Default behavior is identical to [url()], but -#' request can be fully configured by passing a custom [handle()]. +#' Drop-in replacement for base \code{\link{url}} that supports https, ftps, +#' gzip, deflate, etc. Default behavior is identical to \code{\link{url}}, but +#' request can be fully configured by passing a custom \code{\link{handle}}. #' -#' As of version 2.3 curl connections support `open(con, blocking = FALSE)`. -#' In this case `readBin` and `readLines` will return immediately with data +#' As of version 2.3 curl connections support \code{open(con, blocking = FALSE)}. +#' In this case \code{readBin} and \code{readLines} will return immediately with data #' that is available without waiting. For such non-blocking connections the caller -#' needs to call [isIncomplete()] to check if the download has completed +#' needs to call \code{\link{isIncomplete}} to check if the download has completed #' yet. #' #' @useDynLib curl R_curl_connection diff --git a/src/library/curl/R/download.R b/src/library/curl/R/download.R index dc006c26f1..6fa9e3e8f1 100644 --- a/src/library/curl/R/download.R +++ b/src/library/curl/R/download.R @@ -1,18 +1,16 @@ #' Download file to disk #' -#' Libcurl implementation of `C_download` (the "internal" download method) +#' Libcurl implementation of \code{C_download} (the "internal" download method) #' with added support for https, ftps, gzip, etc. Default behavior is identical -#' to [download.file()], but request can be fully configured by passing -#' a custom [handle()]. +#' to \code{\link{download.file}}, but request can be fully configured by passing +#' a custom \code{\link{handle}}. #' -#' The main difference between `curl_download` and `curl_fetch_disk` -#' is that `curl_download` checks the http status code before starting the +#' The main difference between \code{curl_download} and \code{curl_fetch_disk} +#' is that \code{curl_download} checks the http status code before starting the #' download, and raises an error when status is non-successful. The behavior of -#' `curl_fetch_disk` on the other hand is to proceed as normal and write +#' \code{curl_fetch_disk} on the other hand is to proceed as normal and write #' the error page to disk in case of a non success response. #' -#' The `curl_download` function does support resuming and removes the temporary -#' file if the download did not complete successfully. #' For a more advanced download interface which supports concurrent requests and #' resuming large files, have a look at the [multi_download] function. #' @@ -21,11 +19,11 @@ #' @param url A character string naming the URL of a resource to be downloaded. #' @param destfile A character string with the name where the downloaded file #' is saved. Tilde-expansion is performed. -#' @param quiet If `TRUE`, suppress status messages (if any), and the +#' @param quiet If \code{TRUE}, suppress status messages (if any), and the #' progress bar. #' @param mode A character string specifying the mode with which to write the file. -#' Useful values are `"w"`, `"wb"` (binary), `"a"` (append) -#' and `"ab"`. +#' Useful values are \code{"w"}, \code{"wb"} (binary), \code{"a"} (append) +#' and \code{"ab"}. #' @param handle a curl handle object #' @return Path of downloaded file (invisibly). #' @export diff --git a/src/library/curl/R/echo.R b/src/library/curl/R/echo.R index ea9210d03c..4b0f014064 100644 --- a/src/library/curl/R/echo.R +++ b/src/library/curl/R/echo.R @@ -65,6 +65,10 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file # Workaround bug in httpuv on windows that keeps protecting handler until next startServer() on.exit(rm(handle), add = TRUE) + # Workaround for weird threading issue on Linux + # See: https://github.com/jeroen/curl/issues/327 + wait <- ifelse(isTRUE(grepl('linux', R.version$platform)), 0.001, 0) + # Post data from curl xfer <- function(down, up){ if(progress){ @@ -75,6 +79,7 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file as.integer(100 * up[2] / up[1])), file = stderr()) } } + later::run_now(wait) TRUE } handle_setopt(handle, connecttimeout = 2, xferinfofunction = xfer, noprogress = FALSE, forbid_reuse = TRUE) @@ -85,16 +90,11 @@ curl_echo <- function(handle, port = find_port(), progress = interactive(), file host <- sub("https?://([^/]+).*", "\\1", input_url) #hostname <- gsub(":[0-9]+$", "", host) #handle_setopt(handle, port = port, resolve = paste0(hostname, ":", port, ':127.0.0.1')) - request_headers <- handle_getheaders(handle) - if(!any(grepl("^Host:", request_headers, ignore.case = TRUE))){ - request_headers <- c(paste("Host:", host), request_headers) - } - handle_setopt(handle, httpheader = request_headers) + handle_setopt(handle, httpheader = c(paste0("Host:", host), handle_getheaders(handle))) } else { target_url <- paste0("http://127.0.0.1:", port) } - handle_setopt(handle, url = target_url) - curl_dryrun(handle) + curl_fetch_memory(target_url, handle = handle) output$url <- input_url if(progress) cat("\n", file = stderr()) return(output) @@ -138,12 +138,3 @@ find_port <- function(range = NULL){ range <- as.integer(range) .Call(R_findport, range) } - -#' @useDynLib curl R_curl_dryrun -curl_dryrun <- function(handle){ - .Call(R_curl_dryrun, handle) -} - -later_wrapper <- function(){ - later::run_now() -} diff --git a/src/library/curl/R/email.R b/src/library/curl/R/email.R index 1fb43f8a51..955f416820 100644 --- a/src/library/curl/R/email.R +++ b/src/library/curl/R/email.R @@ -1,30 +1,30 @@ #' Send email #' -#' Use the curl SMTP client to send an email. The `message` argument must be -#' properly formatted [RFC2822](https://www.rfc-editor.org/rfc/rfc2822) email message with From/To/Subject headers and CRLF +#' Use the curl SMTP client to send an email. The \code{message} argument must be +#' properly formatted \href{https://www.rfc-editor.org/rfc/rfc2822}{RFC2822} email message with From/To/Subject headers and CRLF #' line breaks. #' #' @section Specifying the server, port, and protocol: #' -#' The `smtp_server` argument takes a hostname, or an SMTP URL: +#' The \code{smtp_server} argument takes a hostname, or an SMTP URL: #' #' \itemize{ -#' \item `mail.example.com` - hostname only -#' \item `mail.example.com:587` - hostname and port -#' \item `smtp://mail.example.com` - protocol and hostname -#' \item `smtp://mail.example.com:587` - full SMTP URL -#' \item `smtps://mail.example.com:465` - full SMTPS URL +#' \item \code{mail.example.com} - hostname only +#' \item \code{mail.example.com:587} - hostname and port +#' \item \code{smtp://mail.example.com} - protocol and hostname +#' \item \code{smtp://mail.example.com:587} - full SMTP URL +#' \item \code{smtps://mail.example.com:465} - full SMTPS URL #' } #' -#' By default, the port will be 25, unless `smtps://` is specified--then +#' By default, the port will be 25, unless \code{smtps://} is specified--then #' the default will be 465 instead. #' #' For internet SMTP servers you probably need to pass a -#' [username](https://curl.se/libcurl/c/CURLOPT_USERNAME.html) and -#' [passwords](https://curl.se/libcurl/c/CURLOPT_PASSWORD.html) option. +#' \href{https://curl.se/libcurl/c/CURLOPT_USERNAME.html}{username} and +#' \href{https://curl.se/libcurl/c/CURLOPT_PASSWORD.html}{passwords} option. #' For some servers you also need to pass a string with -#' [login_options](https://curl.se/libcurl/c/CURLOPT_LOGIN_OPTIONS.html) -#' for example `login_options="AUTH=NTLM"`. +#' \href{https://curl.se/libcurl/c/CURLOPT_LOGIN_OPTIONS.html}{login_options} +#' for example \code{login_options="AUTH=NTLM"}. #' #' @section Encrypting connections via SMTPS or STARTTLS: #' @@ -34,31 +34,31 @@ #' secure TLS connection using the STARTTLS command. It is important to know #' which method your server expects. #' -#' If your smtp server listens on port 465, then use a `smtps://hostname:465` -#' URL. The SMTPS protocol *guarantees* that TLS will be used to protect +#' If your smtp server listens on port 465, then use a \code{smtps://hostname:465} +#' URL. The SMTPS protocol \emph{guarantees} that TLS will be used to protect #' all communications from the start. #' -#' If your email server listens on port 25 or 587, use an `smtp://` URL in -#' combination with the `use_ssl` parameter to control if the connection -#' should be upgraded with STARTTLS. The default value `"try"` will -#' *opportunistically* try to upgrade to a secure connection if the server +#' If your email server listens on port 25 or 587, use an \code{smtp://} URL in +#' combination with the \code{use_ssl} parameter to control if the connection +#' should be upgraded with STARTTLS. The default value \code{"try"} will +#' \emph{opportunistically} try to upgrade to a secure connection if the server #' supports it, and proceed as normal otherwise. #' #' @export #' @param mail_rcpt one or more recipient email addresses. Do not include names, -#' these go into the `message` headers. +#' these go into the \code{message} headers. #' @param mail_from email address of the sender. #' @param message either a string or connection with (properly formatted) email #' message, including sender/recipient/subject headers. See example. #' @param smtp_server hostname or address of the SMTP server, or, an -#' `smtp://` or `smtps://` URL. See "Specifying the server, port, +#' \code{smtp://} or \code{smtps://} URL. See "Specifying the server, port, #' and protocol" below. #' @param use_ssl Request to upgrade the connection to SSL using the STARTTLS command, -#' see [CURLOPT_USE_SSL](https://curl.se/libcurl/c/CURLOPT_USE_SSL.html) +#' see \href{https://curl.se/libcurl/c/CURLOPT_USE_SSL.html}{CURLOPT_USE_SSL} #' for details. Default will try to SSL, proceed as normal otherwise. #' @param verbose print output -#' @param ... other options passed to [handle_setopt()]. In most cases -#' you will need to set a `username` and `password` or `login_options` +#' @param ... other options passed to \code{\link{handle_setopt}}. In most cases +#' you will need to set a \code{username} and \code{password} or \code{login_options} #' to authenticate with the SMTP server, see details. #' @examples \dontrun{# Set sender and recipients (email addresses only) #' recipients <- readline("Enter your email address to receive test: ") diff --git a/src/library/curl/R/errcodes.R b/src/library/curl/R/errcodes.R deleted file mode 100644 index acf0efa2ca..0000000000 --- a/src/library/curl/R/errcodes.R +++ /dev/null @@ -1,42 +0,0 @@ -# This file is autogenerated using make-errorcodes.R -libcurl_error_codes <- -c("curl_error_unsupported_protocol", "curl_error_failed_init", -"curl_error_url_malformat", "curl_error_not_built_in", "curl_error_couldnt_resolve_proxy", -"curl_error_couldnt_resolve_host", "curl_error_couldnt_connect", -"curl_error_weird_server_reply", "curl_error_remote_access_denied", -"curl_error_ftp_accept_failed", "curl_error_ftp_weird_pass_reply", -"curl_error_ftp_accept_timeout", "curl_error_ftp_weird_pasv_reply", -"curl_error_ftp_weird_227_format", "curl_error_ftp_cant_get_host", -"curl_error_http2", "curl_error_ftp_couldnt_set_type", "curl_error_partial_file", -"curl_error_ftp_couldnt_retr_file", "curl_error_obsolete20", -"curl_error_quote_error", "curl_error_http_returned_error", "curl_error_write_error", -"curl_error_obsolete24", "curl_error_upload_failed", "curl_error_read_error", -"curl_error_out_of_memory", "curl_error_operation_timedout", -"curl_error_obsolete29", "curl_error_ftp_port_failed", "curl_error_ftp_couldnt_use_rest", -"curl_error_obsolete32", "curl_error_range_error", "curl_error_http_post_error", -"curl_error_ssl_connect_error", "curl_error_bad_download_resume", -"curl_error_file_couldnt_read_file", "curl_error_ldap_cannot_bind", -"curl_error_ldap_search_failed", "curl_error_obsolete40", "curl_error_function_not_found", -"curl_error_aborted_by_callback", "curl_error_bad_function_argument", -"curl_error_obsolete44", "curl_error_interface_failed", "curl_error_obsolete46", -"curl_error_too_many_redirects", "curl_error_unknown_option", -"curl_error_setopt_option_syntax", "curl_error_obsolete50", "curl_error_peer_failed_verification", -"curl_error_got_nothing", "curl_error_ssl_engine_notfound", "curl_error_ssl_engine_setfailed", -"curl_error_send_error", "curl_error_recv_error", "curl_error_obsolete57", -"curl_error_ssl_certproblem", "curl_error_ssl_cipher", "curl_error_peer_failed_verification", -"curl_error_bad_content_encoding", "curl_error_ldap_invalid_url", -"curl_error_filesize_exceeded", "curl_error_use_ssl_failed", -"curl_error_send_fail_rewind", "curl_error_ssl_engine_initfailed", -"curl_error_login_denied", "curl_error_tftp_notfound", "curl_error_tftp_perm", -"curl_error_remote_disk_full", "curl_error_tftp_illegal", "curl_error_tftp_unknownid", -"curl_error_remote_file_exists", "curl_error_tftp_nosuchuser", -"curl_error_conv_failed", "curl_error_conv_reqd", "curl_error_ssl_cacert_badfile", -"curl_error_remote_file_not_found", "curl_error_ssh", "curl_error_ssl_shutdown_failed", -"curl_error_again", "curl_error_ssl_crl_badfile", "curl_error_ssl_issuer_error", -"curl_error_ftp_pret_failed", "curl_error_rtsp_cseq_error", "curl_error_rtsp_session_error", -"curl_error_ftp_bad_file_list", "curl_error_chunk_failed", "curl_error_no_connection_available", -"curl_error_ssl_pinnedpubkeynotmatch", "curl_error_ssl_invalidcertstatus", -"curl_error_http2_stream", "curl_error_recursive_api_call", "curl_error_auth_error", -"curl_error_http3", "curl_error_quic_connect_error", "curl_error_proxy", -"curl_error_ssl_clientcert", "curl_error_unrecoverable_poll", -"curl_error_too_large", "curl_error_ech_required") diff --git a/src/library/curl/R/fetch.R b/src/library/curl/R/fetch.R index 5672023f3d..12c70f9699 100644 --- a/src/library/curl/R/fetch.R +++ b/src/library/curl/R/fetch.R @@ -1,41 +1,30 @@ #' Fetch the contents of a URL #' #' Low-level bindings to write data from a URL into memory, disk or a callback -#' function. +#' function. These are mainly intended for \code{httr}, most users will be better +#' off using the \code{\link{curl}} or \code{\link{curl_download}} function, or the +#' http specific wrappers in the \code{httr} package. #' -#' The `curl_fetch_*()` functions automatically raise an error upon protocol problems -#' (network, disk, TLS, etc.) but do not implement application logic. For example, -#' you need to check the status code of HTTP requests in the response by yourself, +#' The curl_fetch functions automatically raise an error upon protocol problems +#' (network, disk, ssl) but do not implement application logic. For example for +#' you need to check the status code of http requests yourself in the response, #' and deal with it accordingly. #' -#' Both `curl_fetch_memory()` and `curl_fetch_disk` have a blocking and a +#' Both \code{curl_fetch_memory} and \code{curl_fetch_disk} have a blocking and #' non-blocking C implementation. The latter is slightly slower but allows for #' interrupting the download prematurely (using e.g. CTRL+C or ESC). Interrupting #' is enabled when R runs in interactive mode or when -#' `getOption("curl_interrupt") == TRUE`. +#' \code{getOption("curl_interrupt") == TRUE}. #' -#' The `curl_fetch_multi()` function is the asynchronous equivalent of -#' `curl_fetch_memory()`. It wraps [`multi_add()`][multi_add] to -#' schedule requests which are executed concurrently when calling -#' [`multi_run()`][multi_run]\code{}. For each successful request, the -#' `done` callback is triggered with response data. For failed requests -#' (when `curl_fetch_memory()` would raise an error), the `fail` function -#' is triggered with the error message. -#' -#' After a request has been performed, metadata from the request can be read -#' from the handle object using `handle_data()` (this same information also gets -#' returned by `curl_fetch_memory()` directly). It includes things like: -#' - Final URL (after redirects) -#' - HTTP status code -#' - Content-type -#' - Response headers -#' - Timings -#' - Http-version -#' This data remains available in the handle until it is either re-used for a -#' new request, or `handle_reset()` is called. +#' The \code{curl_fetch_multi} function is the asynchronous equivalent of +#' \code{curl_fetch_memory}. It wraps \code{multi_add} to schedule requests which +#' are executed concurrently when calling \code{multi_run}. For each successful +#' request the \code{done} callback is triggered with response data. For failed +#' requests (when \code{curl_fetch_memory} would raise an error), the \code{fail} +#' function is triggered with the error message. #' #' @param url A character string naming the URL of a resource to be downloaded. -#' @param handle A curl handle object. +#' @param handle a curl handle object #' @export #' @rdname curl_fetch #' @useDynLib curl R_curl_fetch_memory @@ -129,13 +118,3 @@ curl_fetch_echo <- function(url, handle = new_handle()){ handle_setopt(handle, url = enc2utf8(url)) curl_echo(handle) } - -#' @export -#' @rdname curl_fetch -#' @useDynLib curl R_get_handle_response -handle_data <- function(handle){ - stopifnot(inherits(handle, "curl_handle")) - out <- .Call(R_get_handle_response, handle) - out$content = NULL - out -} diff --git a/src/library/curl/R/form.R b/src/library/curl/R/form.R index c44d109490..d3fbb9925e 100644 --- a/src/library/curl/R/form.R +++ b/src/library/curl/R/form.R @@ -1,7 +1,7 @@ #' POST files or data #' -#' Build multipart form data elements. The `form_file` function uploads a -#' file. The `form_data` function allows for posting a string or raw vector +#' Build multipart form data elements. The \code{form_file} function uploads a +#' file. The \code{form_data} function allows for posting a string or raw vector #' with a custom content-type. #' #' @param path a string with a path to an existing file on disk diff --git a/src/library/curl/R/handle.R b/src/library/curl/R/handle.R index 5214560850..af96da5284 100644 --- a/src/library/curl/R/handle.R +++ b/src/library/curl/R/handle.R @@ -2,29 +2,24 @@ #' #' Handles are the work horses of libcurl. A handle is used to configure a #' request with custom options, headers and payload. Once the handle has been -#' set up, it can be passed to any of the download functions such as [curl()] -#' ,[curl_download()] or [curl_fetch_memory()]. The handle will maintain +#' set up, it can be passed to any of the download functions such as \code{\link{curl}} +#' ,\code{\link{curl_download}} or \code{\link{curl_fetch_memory}}. The handle will maintain #' state in between requests, including keep-alive connections, cookies and #' settings. #' -#' Use `new_handle()` to create a new clean curl handle that can be -#' configured with custom options and headers. Note that `handle_setopt` -#' appends or overrides options in the handle, whereas `handle_setheaders` -#' replaces the entire set of headers with the new ones. The `handle_reset` +#' Use \code{new_handle()} to create a new clean curl handle that can be +#' configured with custom options and headers. Note that \code{handle_setopt} +#' appends or overrides options in the handle, whereas \code{handle_setheaders} +#' replaces the entire set of headers with the new ones. The \code{handle_reset} #' function resets only options/headers/forms in the handle. It does not affect #' active connections, cookies or response data from previous requests. The safest #' way to perform multiple independent requests is by using a separate handle for #' each request. There is very little performance overhead in creating handles. #' -#' The [handle_setform] function is used to perform a `multipart/form-data` HTTP -#' POST request (a.k.a. posting a form). The form fields can be specified as -#' strings, raw vectors (for binary data), or [form_file] and [form_data] for -#' upload elements. See the examples. -#' #' @family handles #' @param ... named options / headers to be set in the handle. -#' To send a file, see [form_file()]. To list all allowed options, -#' see [curl_options()] +#' To send a file, see \code{\link{form_file}}. To list all allowed options, +#' see \code{\link{curl_options}} #' @return A handle object (external pointer to the underlying curl handle). #' All functions modify the handle in place but also return the handle #' so you can create a pipeline of operations. @@ -45,17 +40,6 @@ #' handle_setform(h, .list = list(a = "1", b = "2")) #' r <- curl_fetch_memory("https://hb.cran.dev/put", h) #' cat(rawToChar(r$content)) -#' -#' # Posting multipart forms -#' h <- new_handle() -#' handle_setform(h, -#' foo = "blabla", -#' bar = charToRaw("boeboe"), -#' iris = form_data(serialize(iris, NULL), "application/rda"), -#' description = form_file(system.file("DESCRIPTION")), -#' logo = form_file(file.path(R.home('doc'), "html/logo.jpg"), "image/jpeg") -#' ) -#' req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) new_handle <- function(...){ h <- .Call(R_new_handle) handle_setopt(h, ...) @@ -66,7 +50,7 @@ new_handle <- function(...){ #' @useDynLib curl R_handle_setopt #' @param handle Handle to modify #' @param .list A named list of options. This is useful if you've created -#' a list of options elsewhere, avoiding the use of `do.call()`. +#' a list of options elsewhere, avoiding the use of \code{do.call()}. #' @rdname handle handle_setopt <- function(handle, ..., .list = list()){ stopifnot(inherits(handle, "curl_handle")) @@ -110,6 +94,7 @@ handle_getheaders <- function(handle){ } #' @useDynLib curl R_handle_getcustom +#' @rdname handle handle_getcustom <- function(handle){ stopifnot(inherits(handle, "curl_handle")) .Call(R_handle_getcustom, handle) @@ -144,8 +129,8 @@ handle_reset <- function(handle){ #' Extract cookies from a handle #' -#' The `handle_cookies` function returns a data frame with 7 columns as specified in the -#' [netscape cookie file format](http://www.cookiecentral.com/faq/#3.5). +#' The \code{handle_cookies} function returns a data frame with 7 columns as specified in the +#' \href{http://www.cookiecentral.com/faq/#3.5}{netscape cookie file format}. #' #' @useDynLib curl R_get_handle_cookies #' @export @@ -186,6 +171,16 @@ handle_cookies <- function(handle){ } +#' @export +#' @rdname handle +#' @useDynLib curl R_get_handle_response +handle_data <- function(handle){ + stopifnot(inherits(handle, "curl_handle")) + out <- .Call(R_get_handle_response, handle) + out$content = NULL + out +} + # This is for internal use in progress bars. When the download is complete, # the speed is equal to content-size / elapsed-time. #' @useDynLib curl R_get_handle_speed diff --git a/src/library/curl/R/multi.R b/src/library/curl/R/multi.R index a1d36de293..5149042e95 100644 --- a/src/library/curl/R/multi.R +++ b/src/library/curl/R/multi.R @@ -4,20 +4,20 @@ #' Results are only available via callback functions. Advanced use only! #' For downloading many files in parallel use [multi_download] instead. #' -#' Requests are created in the usual way using a curl [handle] and added -#' to the scheduler with [multi_add]. This function returns immediately -#' and does not perform the request yet. The user needs to call [multi_run] +#' Requests are created in the usual way using a curl \link{handle} and added +#' to the scheduler with \link{multi_add}. This function returns immediately +#' and does not perform the request yet. The user needs to call \link{multi_run} #' which performs all scheduled requests concurrently. It returns when all -#' requests have completed, or case of a `timeout` or `SIGINT` (e.g. -#' if the user presses `ESC` or `CTRL+C` in the console). In case of -#' the latter, simply call [multi_run] again to resume pending requests. +#' requests have completed, or case of a \code{timeout} or \code{SIGINT} (e.g. +#' if the user presses \code{ESC} or \code{CTRL+C} in the console). In case of +#' the latter, simply call \link{multi_run} again to resume pending requests. #' -#' When the request succeeded, the `done` callback gets triggered with -#' the response data. The structure if this data is identical to [curl_fetch_memory]. -#' When the request fails, the `fail` callback is triggered with an error +#' When the request succeeded, the \code{done} callback gets triggered with +#' the response data. The structure if this data is identical to \link{curl_fetch_memory}. +#' When the request fails, the \code{fail} callback is triggered with an error #' message. Note that failure here means something went wrong in performing the #' request such as a connection failure, it does not check the http status code. -#' Just like [curl_fetch_memory], the user has to implement application logic. +#' Just like \link{curl_fetch_memory}, the user has to implement application logic. #' #' Raising an error within a callback function stops execution of that function #' but does not affect other requests. @@ -28,14 +28,14 @@ #' It is up to the user to make sure the same handle is not used in concurrent #' requests. #' -#' The [multi_cancel] function can be used to cancel a pending request. +#' The \link{multi_cancel} function can be used to cancel a pending request. #' It has no effect if the request was already completed or canceled. #' -#' The [multi_fdset] function returns the file descriptors curl is +#' The \link{multi_fdset} function returns the file descriptors curl is #' polling currently, and also a timeout parameter, the number of #' milliseconds an application should wait (at most) before proceeding. It -#' is equivalent to the `curl_multi_fdset` and -#' `curl_multi_timeout` calls. It is handy for applications that is +#' is equivalent to the \code{curl_multi_fdset} and +#' \code{curl_multi_timeout} calls. It is handy for applications that is #' expecting input (or writing output) through both curl, and other file #' descriptors. #' @@ -43,18 +43,18 @@ #' @rdname multi #' @seealso Advanced download interface: [multi_download] #' @useDynLib curl R_multi_add -#' @param handle a curl [handle] with preconfigured `url` option. +#' @param handle a curl \link{handle} with preconfigured \code{url} option. #' @param done callback function for completed request. Single argument with -#' response data in same structure as [curl_fetch_memory]. +#' response data in same structure as \link{curl_fetch_memory}. #' @param fail callback function called on failed request. Argument contains #' error message. #' @param data (advanced) callback function, file path, or connection object for writing -#' incoming data. This callback should only be used for *streaming* applications, +#' incoming data. This callback should only be used for \emph{streaming} applications, #' where small pieces of incoming data get written before the request has completed. The -#' signature for the callback function is `write(data, final = FALSE)`. If set -#' to `NULL` the entire response gets buffered internally and returned by in -#' the `done` callback (which is usually what you want). -#' @param pool a multi handle created by [new_pool]. Default uses a global pool. +#' signature for the callback function is \code{write(data, final = FALSE)}. If set +#' to \code{NULL} the entire response gets buffered internally and returned by in +#' the \code{done} callback (which is usually what you want). +#' @param pool a multi handle created by \link{new_pool}. Default uses a global pool. #' @export #' @examples #' results <- list() @@ -118,9 +118,9 @@ multi_add <- function(handle, done = NULL, fail = NULL, data = NULL, pool = NULL .Call(R_multi_add, handle, done, fail, data, pool) } -#' @param timeout max time in seconds to wait for results. Use `0` to poll for results without +#' @param timeout max time in seconds to wait for results. Use \code{0} to poll for results without #' waiting at all. -#' @param poll If `TRUE` then return immediately after any of the requests has completed. +#' @param poll If \code{TRUE} then return immediately after any of the requests has completed. #' May also be an integer in which case it returns after n requests have completed. #' @export #' @useDynLib curl R_multi_run @@ -135,20 +135,18 @@ multi_run <- function(timeout = Inf, poll = FALSE, pool = NULL){ #' @param total_con max total concurrent connections. #' @param host_con max concurrent connections per host. -#' @param max_streams max HTTP/2 concurrent multiplex streams per connection. -#' @param multiplex use HTTP/2 multiplexing if supported by host and client. +#' @param multiplex enable HTTP/2 multiplexing if supported by host and client. #' @export #' @useDynLib curl R_multi_setopt #' @rdname multi -multi_set <- function(total_con = 50, host_con = 6, max_streams = 10, multiplex = TRUE, pool = NULL){ +multi_set <- function(total_con = 50, host_con = 6, multiplex = TRUE, pool = NULL){ if(is.null(pool)) pool <- multi_default() stopifnot(inherits(pool, "curl_multi")) stopifnot(is.numeric(total_con)) stopifnot(is.numeric(host_con)) - stopifnot(is.numeric(max_streams)) stopifnot(is.logical(multiplex)) - .Call(R_multi_setopt, pool, total_con, host_con, max_streams, multiplex) + .Call(R_multi_setopt, pool, total_con, host_con, multiplex) } #' @export @@ -172,9 +170,9 @@ multi_cancel <- function(handle){ #' @export #' @useDynLib curl R_multi_new #' @rdname multi -new_pool <- function(total_con = 100, host_con = 6, max_streams = 10, multiplex = TRUE){ +new_pool <- function(total_con = 100, host_con = 6, multiplex = TRUE){ pool <- .Call(R_multi_new) - multi_set(pool = pool, total_con = total_con, host_con = host_con, max_streams = max_streams, multiplex = multiplex) + multi_set(pool = pool, total_con = total_con, host_con = host_con, multiplex = multiplex) } multi_default <- local({ @@ -201,7 +199,6 @@ print.curl_multi <- function(x, ...){ multi_fdset <- function(pool = NULL){ if(is.null(pool)) pool <- multi_default() - # line below duplicates checks made by C code, but may need to be reinstated if that ever changes - # stopifnot(inherits(pool, c("curl_multi", "curl"))) + stopifnot(inherits(pool, "curl_multi")) .Call(R_multi_fdset, pool) } diff --git a/src/library/curl/R/multi_download.R b/src/library/curl/R/multi_download.R index d045d086e6..fdac49205e 100644 --- a/src/library/curl/R/multi_download.R +++ b/src/library/curl/R/multi_download.R @@ -10,7 +10,7 @@ #' of the HTTP status code). If it failed, e.g. due to a networking issue, the error #' message is in the `error` column. A `success` value `NA` indicates that the request #' was still in progress when the function was interrupted or reached the elapsed -#' `multi_timeout` and perhaps the download can be resumed if the server supports it. +#' `timeout` and perhaps the download can be resumed if the server supports it. #' #' It is also important to inspect the `status_code` column to see if any of the #' requests were successful but had a non-success HTTP code, and hence the downloaded @@ -61,13 +61,12 @@ #' - `headers` vector with http response headers for the request. #' #' @export -#' @param urls vector with URLs to download. Alternatively it may also be a -#' list of [handle][new_handle] objects that have the `url` option already set. +#' @param urls vector with files to download #' @param destfiles vector (of equal length as `urls`) with paths of output files, #' or `NULL` to use [basename] of urls. #' @param resume if the file already exists, resume the download. Note that this may #' change server responses, see details. -#' @param multi_timeout in seconds, passed to [multi_run] +#' @param timeout in seconds, passed to [multi_run] #' @param progress print download progress information #' @param multiplex passed to [new_pool] #' @param ... extra handle options passed to each request [new_handle] @@ -102,14 +101,10 @@ #' #' } multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TRUE, - multi_timeout = Inf, multiplex = TRUE, ...){ - if(inherits(urls, 'curl_handle')) - urls <- list(urls) - if(!is.character(urls) && !is.list(urls)) - stop("Argument 'urls' must be a character vector or list of handles") - handles <- lapply(urls, make_one_handle) + timeout = Inf, multiplex = FALSE, ...){ + urls <- enc2utf8(urls) if(is.null(destfiles)){ - destfiles <- vapply(handles, guess_handle_filename, character(1)) + destfiles <- basename(sub("[?#].*", "", urls)) } dupes <- setdiff(destfiles[duplicated(destfiles)], c("/dev/null", "NUL")) if(length(dupes)){ @@ -117,6 +112,7 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR } stopifnot(length(urls) == length(destfiles)) destfiles <- normalizePath(destfiles, mustWork = FALSE) + handles <- rep(list(NULL), length(urls)) writers <- rep(list(NULL), length(urls)) errors <- rep(NA_character_, length(urls)) success <- rep(NA, length(urls)) @@ -127,8 +123,8 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR total <- 0 lapply(seq_along(urls), function(i){ dest <- destfiles[i] - handle <- handles[[i]] - handle_setopt(handle, ..., noprogress = TRUE) + handle <- new_handle(url = urls[i], ...) + handle_setopt(handle, noprogress = TRUE) if(isTRUE(resume) && file.exists(dest)){ startsize <- file.info(dest)$size handle_setopt(handle, resume_from_large = startsize) @@ -163,6 +159,7 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR errors[i] <<- err dlspeed[i] <<- 0 }) + handles[[i]] <<- handle writers[[i]] <<- writer if(isTRUE(progress) && (i %% 100 == 0)){ print_stream("\rPreparing request %d of %d...", i, length(urls)) @@ -173,7 +170,7 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR writer(raw(0), close = TRUE) })) tryCatch({ - multi_run(timeout = multi_timeout, pool = pool) + multi_run(timeout = timeout, pool = pool) if(isTRUE(progress)){ print_progress(success, total, sum(dlspeed), sum(expected), TRUE) } @@ -183,14 +180,14 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR out <- lapply(handles, handle_data) results <- data.frame( success = success, - status_code = vapply(out, function(x){x$status_code}, numeric(1)), + status_code = sapply(out, function(x){x$status_code}), resumefrom = resumefrom, - url = vapply(out, function(x){x$url}, character(1)), - destfile = normalizePath(destfiles, mustWork = FALSE), + url = sapply(out, function(x){x$url}), + destfile = destfiles, error = errors, - type = vapply(out, function(x){x$type}, character(1)), - modified = structure(vapply(out, function(x){x$modified}, numeric(1)), class = c("POSIXct", "POSIXt")), - time = vapply(out, function(x){unname(x$times['total'])}, numeric(1)), + type = sapply(out, function(x){x$type}), + modified = structure(sapply(out, function(x){x$modified}), class = c("POSIXct", "POSIXt")), + time = sapply(out, function(x){unname(x$times['total'])}), stringsAsFactors = FALSE ) results$headers <- lapply(out, function(x){parse_headers(x$headers)}) @@ -198,28 +195,6 @@ multi_download <- function(urls, destfiles = NULL, resume = FALSE, progress = TR results } -make_one_handle <- function(url_or_handle){ - if(inherits(url_or_handle, 'curl_handle')){ - handle <- url_or_handle - url <- handle_data(handle)$url - if(is.na(url) || identical(url, "")) - stop("Handle passed to multi_download() but has no URL set") - return(handle) - } - if(is.character(url_or_handle)){ - return(new_handle(url = url_or_handle)) - } - stop("Argument urls does not contain url or curl handle") -} - -guess_handle_filename <- function(handle){ - url <- handle_data(handle)$url - destfile <- basename(curl_parse_url(url)$path) - if(!length(destfile) || is.na(destfile) || !nchar(destfile)) - stop("Failed to guess filename for: ", url) - destfile -} - # Print at most 10x per second in interactive, and once per sec in batch/CI print_progress <- local({ last <- 0 diff --git a/src/library/curl/R/nslookup.R b/src/library/curl/R/nslookup.R index 33cfc2dd19..3a2d20282e 100644 --- a/src/library/curl/R/nslookup.R +++ b/src/library/curl/R/nslookup.R @@ -1,22 +1,22 @@ #' Lookup a hostname #' -#' The `nslookup` function is similar to `nsl` but works on all platforms +#' The \code{nslookup} function is similar to \code{nsl} but works on all platforms #' and can resolve ipv6 addresses if supported by the OS. Default behavior raises an #' error if lookup fails. #' -#' The `has_internet` function tests for internet connectivity by performing a +#' The \code{has_internet} function tests for internet connectivity by performing a #' dns lookup. If a proxy server is detected, it will also check for connectivity by #' connecting via the proxy. #' #' @export #' @param host a string with a hostname -#' @param error raise an error for failed DNS lookup. Otherwise returns `NULL`. +#' @param error raise an error for failed DNS lookup. Otherwise returns \code{NULL}. #' @param ipv4_only always return ipv4 address. Set to `FALSE` to allow for ipv6 as well. #' @param multiple returns multiple ip addresses if possible #' @rdname nslookup #' @useDynLib curl R_nslookup #' @examples # Should always work if we are online -#' nslookup("www.google.com") +#' nslookup("www.r-project.org") #' #' # If your OS supports IPv6 #' nslookup("ipv6.test-ipv6.com", ipv4_only = FALSE, error = FALSE) @@ -48,10 +48,6 @@ has_internet <- local({ if(any(c("8.8.4.4", "8.8.8.8") %in% ip_addr)) return(TRUE) - ip_addr <- nslookup('time.cloudflare.com', multiple = TRUE, error = FALSE, ipv4_only = TRUE) - if(any(c("162.159.200.1", "162.159.200.123") %in% ip_addr)) - return(TRUE) - # Method 2: look for a proxy server proxy_vars <- Sys.getenv(c('ALL_PROXY', 'https_proxy', 'HTTPS_PROXY', 'HTTPS_proxy'), NA) diff --git a/src/library/curl/R/onload.R b/src/library/curl/R/onload.R index b4dde37377..c1b665ddc0 100644 --- a/src/library/curl/R/onload.R +++ b/src/library/curl/R/onload.R @@ -1,50 +1,9 @@ .onAttach <- function(libname, pkgname){ - version <- curl_version() - ssl <- sub("\\(.*\\)\\W*", "", version$ssl_version) - msg <- paste("Using libcurl", version$version, "with", ssl) + ssl <- sub("\\(.*\\)\\W*", "", curl_version()$ssl_version) + msg <- paste("Using libcurl", curl_version()$version, "with", ssl) packageStartupMessage(msg) - if(grepl("redhat", R.version$platform) && !('smtp' %in% version$protocols)){ - packageStartupMessage(c("Your system runs libcurl-minimal which does not support all protocols: ", - "See also https://github.com/jeroen/curl/issues/350")) - } - try({ - proxy <- Sys.getenv('ALL_PROXY') - if(nchar(proxy)){ - proxy_info <- curl::curl_parse_url(proxy) - packageStartupMessage(sprintf("Using proxy server %s://%s:%s", - proxy_info$scheme, proxy_info$host, proxy_info$port)) - } - }, silent = TRUE) } .onLoad <- function(libname, pkgname){ - if(grepl('emscripten', R.version[['platform']])){ - set_emscripten_gateway() - } -} - -# Sets a default http gateway for using curl in WebR -# See https://github.com/r-wasm/ws-proxy -# Note socks5 proxy is just a dumb gateway that relays the encrypted https -# traffic as-is. There is no snooping or tempering with cert verification -# possible by the proxy server. -set_emscripten_gateway <- function(){ - proxy <- Sys.getenv('ALL_PROXY') - - # TODO: fix this unfortunate default envvar in webR? - if(proxy == '' || proxy == "socks5h://localhost:8580"){ - try({ - # Note the websocket runs wss (port 443) but inside we mimic plain http - # therefore for curl it looks like http:// but on 443. But it is actually - # https because of the wss:// layer in emscripten. - h <- new_handle(connecttimeout = 2, noproxy = '*') - req <- curl_fetch_memory("http://get-ws-proxy.r-universe.dev:443", handle = h) - if(req$status == 200){ - wsproxy <- rawToChar(req$content) - if(grepl('^socks5h://', wsproxy)){ - Sys.setenv(ALL_PROXY = wsproxy) - } - } - }, silent = TRUE) - } + assign("option_type_table", make_option_type_table(), environment(.onLoad)) } diff --git a/src/library/curl/R/options.R b/src/library/curl/R/options.R index 48ffbba40a..18792861d9 100644 --- a/src/library/curl/R/options.R +++ b/src/library/curl/R/options.R @@ -1,13 +1,12 @@ #' List curl version and options. #' -#' `curl_version()` shows the versions of libcurl, libssl and zlib and -#' supported protocols. `curl_options()` lists all options available in -#' the current version of libcurl. The dataset `curl_symbols` lists all +#' \code{curl_version()} shows the versions of libcurl, libssl and zlib and +#' supported protocols. \code{curl_options()} lists all options available in +#' the current version of libcurl. The dataset \code{curl_symbols} lists all #' symbols (including options) provides more information about the symbols, #' including when support was added/removed from libcurl. #' #' @export -#' @rdname curl_options #' @param filter string: only return options with string in name #' @examples # Available options #' curl_options() @@ -18,31 +17,48 @@ #' # Symbol table #' curl_symbols("proxy") curl_options <- function(filter = ""){ - option_type_table <- make_option_type_table() - opts <- structure(option_type_table$value, names = option_type_table$name) + opts <- curl_options_list() m <- grep(filter, names(opts), ignore.case = TRUE) opts[m] } -#' @export -#' @rdname curl_options -curl_options_table <- function(filter = ""){ - option_type_table <- make_option_type_table() - m <- grep(filter, option_type_table$name, ignore.case = TRUE) - option_type_table[m,] -} +option_table <- (function(){ + env <- new.env() + if(file.exists("tools/option_table.txt")){ + source("tools/option_table.txt", env) + } else if(file.exists("../tools/option_table.txt")){ + source("../tools/option_table.txt", env) + } else { + stop("Failed to find 'tools/option_table.txt' from:", getwd()) + } + + option_table <- unlist(as.list(env)) + names(option_table) <- sub("^curlopt_", "", tolower(names(option_table))) + option_table[order(names(option_table))] +})() + #' @useDynLib curl R_option_types -make_option_type_table <- local({ +make_option_type_table <- function(){ + # Only available for libcurl 7.73 and up. + out <- .Call(R_option_types) + if(!length(out)) return(out) + out$name <- tolower(out$name) + out$type <- factor(out$type, levels = 0:8, labels = c("long", "values", "off_t", + "object", "string", "slist", "cbptr", "blob", "function")) + structure(out, class = 'data.frame', row.names = seq_along(out$name)) +} + +curl_options_list <- local({ cache <- NULL function(){ if(is.null(cache)){ - out <- .Call(R_option_types) - if(!length(out)) return(out) - out$name <- tolower(out$name) - out$type <- factor(out$type, levels = 0:8, labels = c("long", "values", "off_t", - "object", "string", "slist", "cbptr", "blob", "function")) - cache <<- structure(out, class = 'data.frame', row.names = seq_along(out$name)) + cache <<- if(length(option_type_table)){ + structure(option_type_table$value, names = option_type_table$name) + } else { + # Fallback method: extracted from headers at build-time + option_table + } } return(cache) } diff --git a/src/library/curl/R/parser.R b/src/library/curl/R/parser.R deleted file mode 100644 index 777173b2d8..0000000000 --- a/src/library/curl/R/parser.R +++ /dev/null @@ -1,164 +0,0 @@ -#' Normalizing URL parser -#' -#' Interfaces the libcurl [URL parser](https://curl.se/libcurl/c/libcurl-url.html). -#' URLs are automatically normalized where possible, such as in the case of -#' relative paths or url-encoded queries (see examples). -#' When parsing hyperlinks from a HTML document, it is possible to set `baseurl` -#' to the location of the document itself such that relative links can be resolved. -#' -#' A valid URL contains at least a scheme and a host, other pieces are optional. -#' If these are missing, the parser raises an error. Otherwise it returns -#' a list with the following elements: -#' - *url*: the normalized input URL -#' - *scheme*: the protocol part before the `://` (required) -#' - *host*: name of host without port (required) -#' - *port*: decimal between 0 and 65535 -#' - *path*: normalized path up till the `?` of the url -#' - *query*: search query: part between the `?` and `#` of the url. Use `params` below to get individual parameters from the query. -#' - *fragment*: the hash part after the `#` of the url -#' - *user*: authentication username -#' - *password*: authentication password -#' - *params*: named vector with parameters from `query` if set -#' -#' Each element above is either a string or `NULL`, except for `params` which -#' is always a character vector with the length equal to the number of parameters. -#' -#' Note that the `params` field is only usable if the `query` is in the usual -#' `application/x-www-form-urlencoded` format which is technically not part of -#' the RFC. Some services may use e.g. a json blob as the query, in which case -#' the parsed `params` field here can be ignored. There is no way for the parser -#' to automatically infer or validate the query format, this is up to the caller. -#' -#' For more details on the URL format see -#' [rfc3986](https://datatracker.ietf.org/doc/html/rfc3986) -#' or the steps explained in the [whatwg basic url parser](https://url.spec.whatwg.org/#concept-basic-url-parser). -#' -#' @export -#' @param url a character string of length one -#' @param baseurl use this as the parent if `url` may be a relative path -#' @param decode automatically [url-decode][curl_escape] output into the actual -#' values. If set to `FALSE`, values for `query`, `path`, `fragment`, `user` and `password` are returned in url-encoded format. -#' @param params parse individual parameters assuming query is in `application/x-www-form-urlencoded` format. -#' @param default_scheme when `url` is provided without a scheme prefix, assume `https://`. -#' @useDynLib curl R_parse_url -#' @examples -#' url <- "https://jerry:secret@google.com:888/foo/bar?test=123#bla" -#' curl_parse_url(url) -#' -#' # Resolve relative links from a baseurl -#' curl_parse_url("/somelink", baseurl = url) -#' -#' # Paths get normalized -#' curl_parse_url("https://foobar.com/foo/bar/../baz/../yolo")$url -#' -#' # Also normalizes URL-encoding (these URLs are equivalent): -#' url1 <- "https://ja.wikipedia.org/wiki/\u5bff\u53f8" -#' url2 <- "https://ja.wikipedia.org/wiki/%e5%af%bf%e5%8f%b8" -#' curl_parse_url(url1)$path -#' curl_parse_url(url2)$path -#' curl_parse_url(url1, decode = FALSE)$path -#' curl_parse_url(url1, decode = FALSE)$path -curl_parse_url <- function(url, baseurl = NULL, decode = TRUE, params = TRUE, - default_scheme = FALSE){ - stopifnot(is.character(url)) - stopifnot(length(url) == 1) - baseurl <- as.character(baseurl) - - # Workaround for #366 - if(length(baseurl) && substr(url, 1, 1) == '#'){ - url <- sub('(#.*)?$', url, baseurl) - } - - result <- .Call(R_parse_url, url, baseurl, default_scheme) - result$url <- toupper_url_encoding(result$url) - - - # Need to parse query before url-decoding - if(params){ - tryCatch({ - result$params <- parse_query_urlencoded(result$query) - result$query <- NULL - }, error = message) - } - - result$path <- normalize_field(result$path, decode) - result$query <- normalize_field(result$query, decode) - result$fragment <- normalize_field(result$fragment, decode) - result$user <- normalize_field(result$user, decode) - result$password <- normalize_field(result$password, decode) - result -} - -#' @details You can use [curl_modify_url()] both to modify an existing URL, or to -#' create new URL from scratch. Arguments get automatically URL-encoded where -#' needed, unless wrapped in `I()`. If `params` is given, this gets converted -#' into a `application/x-www-form-urlencoded` string which overrides `query`. -#' When modifying a URL, use an empty string `""` to unset a piece of the URL. -#' @export -#' @rdname curl_parse_url -#' @useDynLib curl R_modify_url -#' @param url either URL string or list returned by [curl_parse_url]. -#' Use this to modify a URL using the other parameters. -#' @param scheme string with e.g. `https`. Required if no `url` parameter was given. -#' @param host string with hostname. Required if no `url` parameter was given. -#' @param port string or number with port, e.g. `"443"`. -#' @param path piece of the url starting with `/` up till `?` or `#` -#' @param query piece of url starting with `?` up till `#`. Only used if no `params` is given. -#' @param fragment part of url starting with `#`. -#' @param user string with username -#' @param password string with password -#' @param params named character vector with http GET parameters. This will automatically -#' be converted to `application/x-www-form-urlencoded` and override `query`, -curl_modify_url <- function(url = NULL, scheme = NULL, host = NULL, port = NULL, path = NULL, - query = NULL, fragment = NULL, user = NULL, password = NULL, params = NULL){ - if(is.list(url)){ - url <- do.call(curl_modify_url, url) - } - if(!is.null(params)){ - query <- I(build_query_urlencoded(params)) - } - port <- as.character(port) - out <- .Call(R_modify_url, url, scheme, host, port, path, query, fragment, user, password) - toupper_url_encoding(out) -} - -toupper_url_encoding <- function(x){ - gsub('(%..)', replacement = '\\U\\1', x, perl = TRUE) -} - -normalize_field <- function(x, decode = TRUE){ - if(is.null(x)){ - return(x) - } - if(isTRUE(decode)){ - curl_unescape(x) - } else { - toupper_url_encoding(x) - } -} - -# Parses a string in 'application/x-www-form-urlencoded' format -parse_query_urlencoded <- function(query){ - if(!length(query)) return(character()) - query <- chartr('+',' ', query) - argstr <- strsplit(query, "&", fixed = TRUE)[[1]] - args <- lapply(argstr, function(x){ - c(curl_unescape(strsplit(x, "=", fixed = TRUE)[[1]]), "") - }) - values <- vapply(args, `[`, character(1), 2) - names(values) <- vapply(args, `[`, character(1), 1) - return(values) -} - -build_query_urlencoded <- function(params){ - if(!is.character(params) || length(names(params)) != length(params)){ - stop("params must be named character vector") - } - nms <- curl_escape(names(params)) - values <- gsub("%20", "+", curl_escape(params), fixed = TRUE) - paste(nms, values, collapse = '&', sep = '=') -} - -try_parse_url <- function(url){ - tryCatch(curl_parse_url(url), error = function(e){}) -} diff --git a/src/library/curl/R/sysdata.rda b/src/library/curl/R/sysdata.rda index 5f8884ad52a59e1ef705a44aae0ecbe60f054342..dfe072a56b83e1d286a0d81e973ed694701ac7be 100644 GIT binary patch literal 12051 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 literal 12702 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 YoiBfgZLHPsow1+(|MSRT#F;|@0AK?Hc>n+a diff --git a/src/library/curl/R/upload.R b/src/library/curl/R/upload.R index 339984d0c5..b6f1b40242 100644 --- a/src/library/curl/R/upload.R +++ b/src/library/curl/R/upload.R @@ -1,16 +1,16 @@ #' Upload a File #' -#' Upload a file to an `http://`, `ftp://`, or `sftp://` (ssh) -#' server. Uploading to HTTP means performing an `HTTP PUT` on that URL. +#' Upload a file to an \code{http://}, \code{ftp://}, or \code{sftp://} (ssh) +#' server. Uploading to HTTP means performing an \code{HTTP PUT} on that URL. #' Be aware that sftp is only available for libcurl clients built with libssh2. #' #' @export #' @param file connection object or path to an existing file on disk -#' @param url where to upload, should start with e.g. `ftp://` +#' @param url where to upload, should start with e.g. \code{ftp://} #' @param verbose emit some progress output #' @param reuse try to keep alive and recycle connections when possible -#' @param ... other arguments passed to [handle_setopt()], for -#' example a `username` and `password`. +#' @param ... other arguments passed to \code{\link{handle_setopt}}, for +#' example a \code{username} and \code{password}. #' @examples \dontrun{# Upload package to winbuilder: #' curl_upload('mypkg_1.3.tar.gz', 'ftp://win-builder.r-project.org/R-devel/') #' } diff --git a/src/library/curl/R/utilities.R b/src/library/curl/R/utilities.R index 0dcd915b97..6c4694aaa9 100644 --- a/src/library/curl/R/utilities.R +++ b/src/library/curl/R/utilities.R @@ -18,8 +18,8 @@ curl_version <- function(){ #' Parse date/time #' #' Can be used to parse dates appearing in http response headers such -#' as `Expires` or `Last-Modified`. Automatically recognizes -#' most common formats. If the format is known, [strptime()] +#' as \code{Expires} or \code{Last-Modified}. Automatically recognizes +#' most common formats. If the format is known, \code{\link{strptime}} #' might be easier. #' #' @param datestring a string consisting of a timestamp @@ -47,34 +47,5 @@ trimws <- function(x) { } is_string <- function(x){ - is.character(x) && length(x) && nchar(x) -} - -# Callback for typed libcurl errors -raise_libcurl_error <- function(errnum, message, errbuf = NULL, source_url = NULL, error_cb = NULL){ - error_code <- libcurl_error_codes[errnum] - if(is.na(error_code)) - error_code <- NULL #future proof new error codes - if(is_string(source_url)){ - host <- try_parse_url(source_url)$host - if(is_string(host)) - message <- sprintf('%s [%s]', message, host) - } - if(is_string(errbuf)){ - message <- sprintf('%s:\n%s', message, errbuf) - } - if(is.function(error_cb)){ - if(length(formals(error_cb)) > 0){ - error_cb(structure(message, class = c(error_code, "curl_error", "character"))) - } else { - error_cb() - } - } else { - cl <- sys.call(-1) - e <- structure( - class = c(error_code, "curl_error", "error", "condition"), - list(message = message, call = cl) - ) - stop(e) - } + is.character(x) && length(x) } diff --git a/src/library/curl/R/writer.R b/src/library/curl/R/writer.R index 6c85e69ebe..48942d0b06 100644 --- a/src/library/curl/R/writer.R +++ b/src/library/curl/R/writer.R @@ -3,15 +3,15 @@ #' Generates a closure that writes binary (raw) data to a file. #' #' The writer function automatically opens the file on the first write and closes when -#' it goes out of scope, or explicitly by setting `close = TRUE`. This can be used -#' for the `data` callback in `multi_add()` or `curl_fetch_multi()` such +#' it goes out of scope, or explicitly by setting \code{close = TRUE}. This can be used +#' for the \code{data} callback in \code{multi_add()} or \code{curl_fetch_multi()} such #' that we only keep open file handles for active downloads. This prevents running out #' of file descriptors when performing thousands of concurrent requests. #' #' @export #' @param path file name or path on disk #' @param append open file in append mode -#' @return Function with signature `writer(data = raw(), close = FALSE)` +#' @return Function with signature \code{writer(data = raw(), close = FALSE)} #' @examples #' # Doesn't open yet #' tmp <- tempfile() diff --git a/src/library/curl/cleanup b/src/library/curl/cleanup index 6d7a5f039a..894bb4d65f 100755 --- a/src/library/curl/cleanup +++ b/src/library/curl/cleanup @@ -1,3 +1,2 @@ #!/bin/sh -rm -f src/Makevars configure.log get-curl-linux.sh -rm -Rf .deps autobrew +rm -f src/Makevars configure.log diff --git a/src/library/curl/configure b/src/library/curl/configure index d84579f1cf..153309a239 100755 --- a/src/library/curl/configure +++ b/src/library/curl/configure @@ -1,5 +1,5 @@ #!/bin/sh -# Anticonf (tm) script by Jeroen Ooms (2025) +# Anticonf (tm) script by Jeroen Ooms (2022) # This script will query 'pkg-config' for the required cflags and ldflags. # If pkg-config is unavailable or does not find the library, try setting # INCLUDE_DIR and LIB_DIR manually via e.g: @@ -9,21 +9,14 @@ PKG_CONFIG_NAME="libcurl" PKG_DEB_NAME="libcurl4-openssl-dev" PKG_RPM_NAME="libcurl-devel" -PKG_APK_NAME="curl-dev" PKG_TEST_HEADER="" PKG_LIBS="-lcurl" PKG_CFLAGS="" -#export PKG_CONFIG_PATH="/opt/homebrew/opt/curl/lib/pkgconfig" - -# (Jan 2025) MacOS ships a very buggy libcurl 8.7.1 so we avoid this until apple updates it -# See: https://github.com/jeroen/curl/issues/376 -if [ `uname` = "Darwin" ]; then -MINVERSION="--atleast-version=8.8.0" -fi +# export PKG_CONFIG_PATH="/usr/local/opt/curl/lib/pkgconfig" # Use pkg-config if available -pkg-config ${PKG_CONFIG_NAME} ${MINVERSION} 2>/dev/null +pkg-config --version >/dev/null 2>&1 if [ $? -eq 0 ]; then PKGCONFIG_CFLAGS=`pkg-config --cflags ${PKG_CONFIG_NAME}` case "$PKGCONFIG_CFLAGS" in @@ -33,11 +26,7 @@ if [ $? -eq 0 ]; then fi # Note that cflags may be empty in case of success -if [ "$CURL_CFLAGS" ] || [ "$CURL_LIBS" ]; then - echo "Found CURL_CFLAGS and/or CURL_LIBS!" - PKG_CFLAGS="$CURL_CFLAGS" - PKG_LIBS="$CURL_LIBS" -elif [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then +if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then echo "Found INCLUDE_DIR and/or LIB_DIR!" PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS" PKG_LIBS="-L$LIB_DIR $PKG_LIBS" @@ -45,10 +34,6 @@ elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then echo "Found pkg-config cflags and libs!" PKG_CFLAGS=${PKGCONFIG_CFLAGS} PKG_LIBS=${PKGCONFIG_LIBS} -elif [ `uname` = "Darwin" ]; then - # Temporary fix for: https://github.com/jeroen/curl/issues/376 - curl -sfL "https://autobrew.github.io/scripts/libcurl-macos" > autobrew - . ./autobrew fi # Find compiler @@ -69,7 +54,6 @@ if [ $? -ne 0 ]; then echo "Configuration failed because $PKG_CONFIG_NAME was not found. Try installing:" echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)" echo " * rpm: $PKG_RPM_NAME (Fedora, CentOS, RHEL)" - echo " * apk: $PKG_APK_NAME (Alpine)" echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your" echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config" echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:" @@ -80,26 +64,21 @@ if [ $? -ne 0 ]; then exit 1 fi -# Test minimum version -${CC} -E ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} tools/version.c >/dev/null 2>&1 -if [ $? -ne 0 ]; then - -# On curl < 7.73 (RHEL-8 / Ubuntu 20.04) enable the static library -if [ `uname` = "Linux" ]; then - echo "Local libcurl is too old. Downloading a static libcurl for legacy Linux..." - echo "For alternative solutions see: https://github.com/jeroen/curl/issues/416" - curl -sOL "https://github.com/jeroen/curl/releases/download/libcurl-8.14.1/get-curl-linux.sh" && . ./get-curl-linux.sh -fi - -# MacOS 13 CRAN builder has MacOS 11.3 SDK -# Also for people compiling from source on MacOS 11 -if [ `uname` = "Darwin" ] && [ -z "$BREWDIR" ] && ${R_HOME}/bin/Rscript --vanilla tools/testversion.R "7.80"; then - PKG_CFLAGS="$PKG_CFLAGS -DENABLE_MACOS_POLYFILL" -fi +# Disable purling on oldrel due to https://github.com/yihui/knitr/issues/2338 +if [ `uname` = "Darwin" ] && [ "${R_VERSION}" = "4.3.3" ]; then + for file in vignettes/*.Rmd; do + sed -i '' '/```/,$d' $file || true + touch inst/doc/* || true + done fi # Write to Makevars sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars +# Extract curlopt symbols +echo '#include ' | ${CC} ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -E -xc - \ + | grep "^[ \t]*CURLOPT_.*," | sed s/,// | sed 's/__attribute__[(][(].*[)][)] =/=/' \ + > tools/option_table.txt + # Success exit 0 diff --git a/src/library/curl/inst/WORDLIST b/src/library/curl/inst/WORDLIST index 6cc6f25dda..941a588784 100644 --- a/src/library/curl/inst/WORDLIST +++ b/src/library/curl/inst/WORDLIST @@ -11,8 +11,7 @@ MacOS OpenSSL PEM RHEL -RStudio -SCP +Rtools SMTP SMTPS SSL @@ -22,7 +21,6 @@ TLS async auth config -customizable dev dns enum @@ -38,21 +36,17 @@ httpuv httr ip ipv -json libcurl libssh libssl netscape openssl -params preconfigured proxying rb -rfc sftp slist smtp ssl webservers -whatwg zlib diff --git a/src/library/curl/src/Makevars.in b/src/library/curl/src/Makevars.in index 09dee0a919..8d833e2ef2 100644 --- a/src/library/curl/src/Makevars.in +++ b/src/library/curl/src/Makevars.in @@ -2,7 +2,7 @@ PKG_CFLAGS=$(C_VISIBILITY) PKG_CPPFLAGS=@cflags@ -DSTRICT_R_HEADERS -DR_NO_REMAP PKG_LIBS=@libs@ -all: $(SHLIB) cleanup +all: clean -cleanup: $(SHLIB) - @rm -Rf ../.deps +clean: + rm -f $(SHLIB) $(OBJECTS) diff --git a/src/library/curl/src/Makevars.win b/src/library/curl/src/Makevars.win index 5ebb0b8b57..8019d6287b 100644 --- a/src/library/curl/src/Makevars.win +++ b/src/library/curl/src/Makevars.win @@ -1,4 +1,4 @@ -RWINLIB = ../.deps/libcurl +RWINLIB = ../windows/libcurl TARGET = lib$(subst gcc,,$(COMPILED_BY))$(R_ARCH) PKG_LIBS = \ @@ -9,13 +9,13 @@ PKG_LIBS = \ PKG_CPPFLAGS= \ -I$(RWINLIB)/include -DCURL_STATICLIB -DSTRICT_R_HEADERS -DR_NO_REMAP -all: $(SHLIB) cleanup +all: clean winlibs -# Needed for parallel make -$(OBJECTS): | $(RWINLIB) +clean: + rm -f $(SHLIB) $(OBJECTS) -$(RWINLIB): - @"${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" +winlibs: clean + "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" + echo '#include ' | $(CC) $(PKG_CPPFLAGS) -std=gnu99 -E -xc - | grep "^[ \t]*CURLOPT_.*," | sed s/,// > ../tools/option_table.txt -cleanup: $(SHLIB) - @rm -Rf $(RWINLIB) +.PHONY: all winlibs clean diff --git a/src/library/curl/src/callbacks.c b/src/library/curl/src/callbacks.c index 6c70ddc040..a01dfe158c 100644 --- a/src/library/curl/src/callbacks.c +++ b/src/library/curl/src/callbacks.c @@ -51,7 +51,7 @@ size_t R_curl_callback_read(char *buffer, size_t size, size_t nitems, SEXP fun) } size_t bytes_read = Rf_length(res); - if (bytes_read) memcpy(buffer, RAW_RO(res), bytes_read); + memcpy(buffer, RAW(res), bytes_read); UNPROTECT(3); return bytes_read; @@ -73,7 +73,7 @@ int R_curl_callback_debug(CURL *handle, curl_infotype type_, char *data, /* wrap type and msg into R types */ SEXP type = PROTECT(Rf_ScalarInteger(type_)); SEXP msg = PROTECT(Rf_allocVector(RAWSXP, size)); - if (size) memcpy(RAW(msg), data, size); + memcpy(RAW(msg), data, size); /* call the R function */ SEXP call = PROTECT(Rf_lang3(fun, type, msg)); diff --git a/src/library/curl/src/curl-common.h b/src/library/curl/src/curl-common.h index 56f0c8ca0b..465510ba45 100644 --- a/src/library/curl/src/curl-common.h +++ b/src/library/curl/src/curl-common.h @@ -8,15 +8,18 @@ #include #include -#define make_string(x) x != NULL ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) -#define get_string(x) CHAR(STRING_ELT(x, 0)) -#define len_string(x) Rf_length(STRING_ELT(x, 0)) -#define assert(x) assert_message(x, NULL) +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 28) +#define HAS_MULTI_WAIT 1 +#endif +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 55) +#define USE_CURL_OFF_T 1 +#endif -//RHEL-9 has curl 7.76.1 -#if CURL_AT_LEAST_VERSION(7,80,0) -#define HAS_CURL_PARSER_STRERROR 1 +#ifndef DISABLE_CURL_EASY_OPTION +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 73) +#define HAS_CURL_EASY_OPTION 1 +#endif #endif typedef struct { @@ -54,10 +57,10 @@ typedef struct { CURL* get_handle(SEXP ptr); reference* get_ref(SEXP ptr); -void raise_libcurl_error(CURLcode res, reference *ref, SEXP error_cb); void assert_status(CURLcode res, reference *ref); -void assert_message(CURLcode res, const char *str); +void assert(CURLcode res); void massert(CURLMcode res); +void stop_for_status(CURL *http_handle); SEXP slist_to_vec(struct curl_slist *slist); struct curl_slist* vec_to_slist(SEXP vec); struct curl_httppost* make_form(SEXP form); @@ -76,8 +79,3 @@ SEXP make_handle_response(reference *ref); SEXP reflist_init(void); SEXP reflist_add(SEXP x, SEXP target); SEXP reflist_remove(SEXP x, SEXP target); - -/* Workaround for CRAN using outdated MacOS11 SDK */ -#if defined(__APPLE__) && defined(ENABLE_MACOS_POLYFILL) && !CURL_AT_LEAST_VERSION(7,81,0) -#include "macos-polyfill.h" -#endif diff --git a/src/library/curl/src/curl.c b/src/library/curl/src/curl.c index 503f578fc8..29b3d66006 100644 --- a/src/library/curl/src/curl.c +++ b/src/library/curl/src/curl.c @@ -98,7 +98,7 @@ static size_t pop(void *target, size_t max, request *req){ return copy_size; } -static void check_handles(CURLM *manager, reference *ref) { +void check_manager(CURLM *manager, reference *ref) { for(int msg = 1; msg > 0;){ CURLMsg *out = curl_multi_info_read(manager, &msg); if(out) @@ -106,16 +106,31 @@ static void check_handles(CURLM *manager, reference *ref) { } } -static void fetchdata(request *req) { +//NOTE: renamed because the name 'fetch' caused crash/conflict on Solaris. +void fetchdata(request *req) { R_CheckUserInterrupt(); - massert(curl_multi_perform(req->manager, &(req->has_more))); - check_handles(req->manager, req->ref); + long timeout = 10*1000; + massert(curl_multi_timeout(req->manager, &timeout)); + /* massert(curl_multi_perform(req->manager, &(req->has_more))); */ + + /* On libcurl < 7.20 we need to check for CURLM_CALL_MULTI_PERFORM, see docs */ + CURLMcode res = CURLM_CALL_MULTI_PERFORM; + while(res == CURLM_CALL_MULTI_PERFORM){ + res = curl_multi_perform(req->manager, &(req->has_more)); + } + massert(res); + /* End */ + check_manager(req->manager, req->ref); } +/* Support for readBin() */ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { request *req = (request*) con->private; size_t req_size = sz * ni; + + /* append data to the target buffer */ size_t total_size = pop(target, req_size, req); + if (total_size > 0 && (!con->blocking || req->partial)) { // If we can return data without waiting, and the connection is // non-blocking (or using curl_fetch_stream()), do so. @@ -126,9 +141,12 @@ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { } while((req_size > total_size) && req->has_more) { + /* wait for activity, timeout or "nothing" */ +#ifdef HAS_MULTI_WAIT int numfds; - massert(curl_multi_wait(req->manager, NULL, 0, con->blocking ? 1000 : 10, &numfds)); - + if(con->blocking) + massert(curl_multi_wait(req->manager, NULL, 0, 1000, &numfds)); +#endif fetchdata(req); total_size += pop((char*)target + total_size, (req_size-total_size), req); @@ -140,6 +158,7 @@ static size_t rcurl_read(void *target, size_t sz, size_t ni, Rconnection con) { return total_size; } +/* naive implementation of readLines */ static int rcurl_fgetc(Rconnection con) { int x = 0; #ifdef WORDS_BIGENDIAN @@ -149,15 +168,13 @@ static int rcurl_fgetc(Rconnection con) { #endif } -static void cleanup(Rconnection con) { +void cleanup(Rconnection con) { + //Rprintf("Destroying connection.\n"); request *req = (request*) con->private; reference *ref = req->ref; /* free thee handle connection */ curl_multi_remove_handle(req->manager, req->handle); - curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, NULL); - curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, NULL); - curl_easy_setopt(req->handle, CURLOPT_FAILONERROR, 0L); ref->locked = 0; /* delayed finalizer cleanup */ @@ -172,12 +189,10 @@ static void cleanup(Rconnection con) { } /* reset to pre-opened state */ -static void reset(Rconnection con) { +void reset(Rconnection con) { + //Rprintf("Resetting connection object.\n"); request *req = (request*) con->private; curl_multi_remove_handle(req->manager, req->handle); - curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, NULL); - curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, NULL); - curl_easy_setopt(req->handle, CURLOPT_FAILONERROR, 0L); req->ref->locked = 0; con->isopen = FALSE; con->text = TRUE; @@ -216,33 +231,22 @@ static Rboolean rcurl_open(Rconnection con) { /* fully non-blocking has 's' in open mode */ int block_open = strchr(con->mode, 's') == NULL; int force_open = strchr(con->mode, 'f') != NULL; - if(block_open && !force_open) - curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L); /* Wait for first data to arrive. Monitoring a change in status code does not suffice in case of http redirects */ while(block_open && req->has_more && !req->has_data) { +#ifdef HAS_MULTI_WAIT int numfds; massert(curl_multi_wait(req->manager, NULL, 0, 1000, &numfds)); - if(pending_interrupt()) { - reset(con); //cleanup before jumping - assert_message(CURLE_ABORTED_BY_CALLBACK, NULL); - } - massert(curl_multi_perform(req->manager, &(req->has_more))); - for(int msg = 1; msg > 0;){ - CURLMsg *out = curl_multi_info_read(req->manager, &msg); - if(out && out->data.result != CURLE_OK){ - const char *errmsg = strlen(req->ref->errbuf) ? req->ref->errbuf : curl_easy_strerror(out->data.result); - Rf_warningcall(R_NilValue, "Failed to open '%s': %s", req->url, errmsg); - reset(con); - return FALSE; - } - } +#endif + fetchdata(req); } /* check http status code */ /* Stream connections should be checked via handle_data() */ /* Non-blocking open connections get checked during read */ + if(block_open && !force_open) + stop_for_status(handle); /* set mode in case open() changed it */ con->text = strchr(con->mode, 'b') ? FALSE : TRUE; @@ -294,9 +298,6 @@ SEXP R_curl_connection(SEXP url, SEXP ptr, SEXP partial) { /* protect the handle */ (req->ref->refCount)++; - /* store the CURLM address in con->ex_ptr which is the 'conn_id' attribute */ - R_SetExternalPtrAddr((SEXP) con->ex_ptr, req->manager); - UNPROTECT(1); return rc; } diff --git a/src/library/curl/src/download.c b/src/library/curl/src/download.c index 12fe4d39e0..1f149aa42a 100644 --- a/src/library/curl/src/download.c +++ b/src/library/curl/src/download.c @@ -27,23 +27,25 @@ SEXP R_download_curl(SEXP url, SEXP destfile, SEXP quiet, SEXP mode, SEXP ptr, S /* set options */ curl_easy_setopt(handle, CURLOPT_URL, Rf_translateCharUTF8(Rf_asChar(url))); - curl_easy_setopt(handle, CURLOPT_NOPROGRESS, (long) Rf_asLogical(quiet)); + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, Rf_asLogical(quiet)); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, push_disk); curl_easy_setopt(handle, CURLOPT_WRITEDATA, dest); - curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1L); /* perform blocking request */ CURLcode status = Rf_asLogical(nonblocking) ? curl_perform_with_interrupt(handle) : curl_easy_perform(handle); /* cleanup */ - curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(handle, CURLOPT_URL, NULL); + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); - curl_easy_setopt(handle, CURLOPT_FAILONERROR, 0L); fclose(dest); /* raise for curl errors */ assert_status(status, get_ref(ptr)); + + /* check for success */ + stop_for_status(handle); return Rf_ScalarInteger(0); } diff --git a/src/library/curl/src/dryrun.c b/src/library/curl/src/dryrun.c deleted file mode 100644 index 752883cd36..0000000000 --- a/src/library/curl/src/dryrun.c +++ /dev/null @@ -1,44 +0,0 @@ -/* * - * Blocking easy interfaces to libcurl for R. - * Example: https://curl.se/libcurl/c/getinmemory.html - */ - -#include "curl-common.h" - -static size_t write_nothing(void *contents, size_t sz, size_t nmemb, void *ctx) { - return sz * nmemb; -} - -static void run_httpuv(void *dummy) { - SEXP expr = PROTECT(Rf_lang1(Rf_install("later_wrapper"))); - SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); - Rf_eval(expr, env); - UNPROTECT(2); -} - -static int process_server(void) { - return !(R_ToplevelExec(run_httpuv, NULL)); -} - -SEXP R_curl_dryrun(SEXP ptr){ - CURL *handle = get_handle(ptr); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_nothing); - CURLM * multi_handle = curl_multi_init(); - if(CURLM_OK != curl_multi_add_handle(multi_handle, handle)) - Rf_error("Failed to add handle"); - int still_running = 1; - while(still_running) { - if(process_server()) - break; - if(curl_multi_perform(multi_handle, &(still_running)) != CURLM_OK) - break; - } - int msgq = 0; - CURLMsg *m = curl_multi_info_read(multi_handle, &msgq); - CURLcode status = (m && (m->msg == CURLMSG_DONE)) ? m->data.result : CURLE_ABORTED_BY_CALLBACK; - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); - curl_multi_remove_handle(multi_handle, handle); - curl_multi_cleanup(multi_handle); - assert_status(status, get_ref(ptr)); - return R_NilValue; -} diff --git a/src/library/curl/src/handle.c b/src/library/curl/src/handle.c index 8dbe9c757d..dfc900712f 100644 --- a/src/library/curl/src/handle.c +++ b/src/library/curl/src/handle.c @@ -8,11 +8,25 @@ extern int r_curl_is_off_t_option(CURLoption x); extern int r_curl_is_string_option(CURLoption x); extern int r_curl_is_postfields_option(CURLoption x); +#define make_string(x) x ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) + #ifndef MAX_PATH #define MAX_PATH 1024 #endif -static int total_handles = 0; +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 47) +#define HAS_HTTP_VERSION_2TLS 1 +#endif + +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 32) +#define HAS_XFERINFOFUNCTION 1 +#endif + +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 36) +#define HAS_CURLOPT_EXPECT_100_TIMEOUT_MS 1 +#endif + +int total_handles = 0; void clean_handle(reference *ref){ if(ref->refCount == 0){ @@ -31,7 +45,7 @@ void clean_handle(reference *ref){ } } -static void fin_handle(SEXP ptr){ +void fin_handle(SEXP ptr){ reference *ref = (reference*) R_ExternalPtrAddr(ptr); //this kind of strange but the multi finalizer needs the ptr value @@ -45,11 +59,15 @@ static void fin_handle(SEXP ptr){ } /* the default readfunc os fread which can cause R to freeze */ -static size_t dummy_read(char *buffer, size_t size, size_t nitems, void *instream){ +size_t dummy_read(char *buffer, size_t size, size_t nitems, void *instream){ return 0; } +#ifdef HAS_XFERINFOFUNCTION #define xftype curl_off_t +#else +#define xftype double +#endif static int xferinfo_callback(void *clientp, xftype dltotal, xftype dlnow, xftype ultotal, xftype ulnow){ static xftype dlprev = 0; @@ -83,20 +101,6 @@ static struct curl_slist * default_headers(void){ return headers; } -static void assert_setopt(CURLcode res, CURLoption opt, const char *optname){ - if(res != CURLE_OK){ - char errmsg [256] = {0}; - if(opt == CURLOPT_MAIL_RCPT || opt == CURLOPT_MAIL_FROM || opt == CURLOPT_MAIL_AUTH){ - snprintf(errmsg, 256, "Error setting '%s': your libcurl may have disabled SMTP support", optname); - } else { - snprintf(errmsg, 256, "Invalid or unsupported value when setting curl option '%s'", optname); - } - assert_message(CURLE_BAD_FUNCTION_ARGUMENT, errmsg); - } -} - -#define set_user_option(option, value) assert_setopt(curl_easy_setopt(handle, option, value), option, optname) - static void set_headers(reference *ref, struct curl_slist *newheaders){ if(ref->headers) curl_slist_free_all(ref->headers); @@ -157,15 +161,11 @@ static void set_handle_defaults(reference *ref){ /* a sensible timeout (10s) */ assert(curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10L)); - /* error if download is stalled for 10 minutes (prevent CI hangs) */ - assert(curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, 1L)); - assert(curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, 600L)); - /* needed to start the cookie engine */ assert(curl_easy_setopt(handle, CURLOPT_COOKIEFILE, "")); assert(curl_easy_setopt(handle, CURLOPT_FILETIME, 1L)); - /* set the default user agent to match base R */ + /* set the default user agent */ SEXP agent = Rf_GetOption1(Rf_install("HTTPUserAgent")); if(Rf_isString(agent) && Rf_length(agent)){ assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, CHAR(STRING_ELT(agent, 0)))); @@ -173,18 +173,16 @@ static void set_handle_defaults(reference *ref){ assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, "r/curl/jeroen")); } - /* set the default netrc path to match base R */ - SEXP netrc = Rf_GetOption1(Rf_install("netrc")); - if (Rf_isString(netrc) && Rf_length(netrc)) { - const char *path = R_ExpandFileName(CHAR(STRING_ELT(netrc, 0))); - assert(curl_easy_setopt(handle, CURLOPT_NETRC, CURL_NETRC_OPTIONAL)); - assert(curl_easy_setopt(handle, CURLOPT_NETRC_FILE, path)); - } - /* allow all authentication methods */ assert(curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY)); assert(curl_easy_setopt(handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)); + /* enables HTTP2 on HTTPS (match behavior of curl cmd util) */ +//#if defined(CURL_VERSION_HTTP2) && defined(HAS_HTTP_VERSION_2TLS) +// if(curl_version_info(CURLVERSION_NOW)->features & CURL_VERSION_HTTP2) +// assert(curl_easy_setopt(handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS)); +//#endif + /* set an error buffer */ assert(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, ref->errbuf)); @@ -192,17 +190,20 @@ static void set_handle_defaults(reference *ref){ assert(curl_easy_setopt(handle, CURLOPT_READFUNCTION, dummy_read)); /* set default progress printer (disabled by default) */ +#ifdef HAS_XFERINFOFUNCTION assert(curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, xferinfo_callback)); +#else + assert(curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, xferinfo_callback)); +#endif /* Disable the 'Expect: 100' header (deprecated in recent libcurl) */ set_headers(ref, NULL); +#ifdef HAS_CURLOPT_EXPECT_100_TIMEOUT_MS assert(curl_easy_setopt(handle, CURLOPT_EXPECT_100_TIMEOUT_MS, 0L)); +#endif /* Send verbose outout to R front-end virtual stderr */ assert(curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, default_verbose_cb)); - - /* Prefer using multiplex when possible */ - assert(curl_easy_setopt(handle, CURLOPT_PIPEWAIT, 1L)); } SEXP R_new_handle(void){ @@ -211,7 +212,7 @@ SEXP R_new_handle(void){ ref->handle = curl_easy_init(); total_handles++; set_handle_defaults(ref); - SEXP prot = PROTECT(Rf_allocVector(VECSXP, 8)); //for protecting callback functions + SEXP prot = PROTECT(Rf_allocVector(VECSXP, 7)); //for protecting callback functions SEXP ptr = PROTECT(R_MakeExternalPtr(ref, R_NilValue, prot)); R_RegisterCFinalizerEx(ptr, fin_handle, TRUE); Rf_setAttrib(ptr, R_ClassSymbol, Rf_mkString("curl_handle")); @@ -265,53 +266,62 @@ SEXP R_handle_setopt(SEXP ptr, SEXP keys, SEXP values){ const char* optname = CHAR(STRING_ELT(optnames, i)); SEXP val = VECTOR_ELT(values, i); if(val == R_NilValue){ - set_user_option(key, NULL); + assert(curl_easy_setopt(handle, key, NULL)); +#ifdef HAS_XFERINFOFUNCTION } else if (key == CURLOPT_XFERINFOFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_XFERINFOFUNCTION, (curl_xferinfo_callback) R_curl_callback_xferinfo); - set_user_option(CURLOPT_XFERINFODATA, val); - set_user_option(CURLOPT_NOPROGRESS, 0L); + assert(curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, + (curl_progress_callback) R_curl_callback_xferinfo)); + assert(curl_easy_setopt(handle, CURLOPT_XFERINFODATA, val)); + assert(curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0)); SET_VECTOR_ELT(prot, 1, val); //protect gc +#endif } else if (key == CURLOPT_PROGRESSFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_PROGRESSFUNCTION,(curl_progress_callback) R_curl_callback_progress); - set_user_option(CURLOPT_PROGRESSDATA, val); - set_user_option(CURLOPT_NOPROGRESS, 0L); + assert(curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, + (curl_progress_callback) R_curl_callback_progress)); + assert(curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, val)); + assert(curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0)); SET_VECTOR_ELT(prot, 2, val); //protect gc } else if (key == CURLOPT_READFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_READFUNCTION, (curl_read_callback) R_curl_callback_read); - set_user_option(CURLOPT_READDATA, val); + assert(curl_easy_setopt(handle, CURLOPT_READFUNCTION, + (curl_read_callback) R_curl_callback_read)); + assert(curl_easy_setopt(handle, CURLOPT_READDATA, val)); SET_VECTOR_ELT(prot, 3, val); //protect gc } else if (key == CURLOPT_DEBUGFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_DEBUGFUNCTION, (curl_debug_callback) R_curl_callback_debug); - set_user_option(CURLOPT_DEBUGDATA, val); + + assert(curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, + (curl_debug_callback) R_curl_callback_debug)); + assert(curl_easy_setopt(handle, CURLOPT_DEBUGDATA, val)); SET_VECTOR_ELT(prot, 4, val); //protect gc } else if (key == CURLOPT_SSL_CTX_FUNCTION){ if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_SSL_CTX_FUNCTION, (curl_ssl_ctx_callback) R_curl_callback_ssl_ctx); - set_user_option(CURLOPT_SSL_CTX_DATA, val); + assert(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, + (curl_ssl_ctx_callback) R_curl_callback_ssl_ctx)); + assert(curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, val)); SET_VECTOR_ELT(prot, 5, val); //protect gc } else if (key == CURLOPT_SEEKFUNCTION) { if (TYPEOF(val) != CLOSXP) Rf_error("Value for option %s (%d) must be a function.", optname, key); - set_user_option(CURLOPT_SEEKFUNCTION, (curl_seek_callback) R_curl_callback_seek); - set_user_option(CURLOPT_SEEKDATA, val); + assert(curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, + (curl_seek_callback) R_curl_callback_seek)); + assert(curl_easy_setopt(handle, CURLOPT_SEEKDATA, val)); SET_VECTOR_ELT(prot, 6, val); //protect gc } else if (key == CURLOPT_URL) { /* always use utf-8 for urls */ const char * url_utf8 = Rf_translateCharUTF8(STRING_ELT(val, 0)); - set_user_option(CURLOPT_URL, url_utf8); + assert(curl_easy_setopt(handle, CURLOPT_URL, url_utf8)); } else if(key == CURLOPT_HTTPHEADER){ if(!Rf_isString(val)) Rf_error("Value for option %s (%d) must be a string vector", optname, key); @@ -320,30 +330,31 @@ SEXP R_handle_setopt(SEXP ptr, SEXP keys, SEXP values){ if(!Rf_isString(val)) Rf_error("Value for option %s (%d) must be a string vector", optname, key); ref->custom = vec_to_slist(val); - set_user_option(key, ref->custom); + assert(curl_easy_setopt(handle, key, ref->custom)); } else if(r_curl_is_long_option(key)){ - if(!Rf_isNumeric(val) || Rf_length(val) != 1) + if(!Rf_isNumeric(val) || Rf_length(val) != 1) { Rf_error("Value for option %s (%d) must be a number.", optname, key); - set_user_option(key, (long) Rf_asInteger(val)); + } + assert(curl_easy_setopt(handle, key, (long) Rf_asInteger(val))); } else if(r_curl_is_off_t_option(key)){ - if(!Rf_isNumeric(val) || Rf_length(val) != 1) + if(!Rf_isNumeric(val) || Rf_length(val) != 1) { Rf_error("Value for option %s (%d) must be a number.", optname, key); - set_user_option(key, (curl_off_t) Rf_asReal(val)); + } + assert(curl_easy_setopt(handle, key, (curl_off_t) Rf_asReal(val))); } else if(r_curl_is_postfields_option(key) || r_curl_is_string_option(key)){ - if(r_curl_is_postfields_option(key)){ - key = CURLOPT_POSTFIELDS; //avoid bug #313 - SET_VECTOR_ELT(prot, 7, val); + if(key == CURLOPT_POSTFIELDS){ + key = CURLOPT_COPYPOSTFIELDS; } switch (TYPEOF(val)) { case RAWSXP: - if(key == CURLOPT_POSTFIELDS) - set_user_option(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) Rf_xlength(val)); - set_user_option(key, RAW(val)); + if(key == CURLOPT_COPYPOSTFIELDS) + assert(curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) Rf_length(val))); + assert(curl_easy_setopt(handle, key, RAW(val))); break; case STRSXP: if (Rf_length(val) != 1) Rf_error("Value for option %s (%d) must be length-1 string", optname, key); - set_user_option(key, CHAR(STRING_ELT(val, 0))); + assert(curl_easy_setopt(handle, key, CHAR(STRING_ELT(val, 0)))); break; default: Rf_error("Value for option %s (%d) must be a string or raw vector.", optname, key); @@ -363,7 +374,7 @@ SEXP R_handle_setform(SEXP ptr, SEXP form){ return Rf_ScalarLogical(1); } -static SEXP make_timevec(CURL *handle){ +SEXP make_timevec(CURL *handle){ double time_redirect, time_lookup, time_connect, time_pre, time_start, time_total; assert(curl_easy_getinfo(handle, CURLINFO_REDIRECT_TIME, &time_redirect)); assert(curl_easy_getinfo(handle, CURLINFO_NAMELOOKUP_TIME, &time_lookup)); @@ -393,7 +404,7 @@ static SEXP make_timevec(CURL *handle){ } /* Extract current cookies (state) from handle */ -static SEXP make_cookievec(CURL *handle){ +SEXP make_cookievec(CURL *handle){ /* linked list of strings */ struct curl_slist *cookies; assert(curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies)); @@ -402,35 +413,25 @@ static SEXP make_cookievec(CURL *handle){ return out; } -static SEXP make_info_integer(CURL *handle, CURLINFO info){ +SEXP make_status(CURL *handle){ long res_status; - assert(curl_easy_getinfo(handle, info, &res_status)); + assert(curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res_status)); return Rf_ScalarInteger(res_status); } -static SEXP make_info_string(CURL *handle, CURLINFO info){ - char *res_url = NULL; - assert(curl_easy_getinfo(handle, info, &res_url)); - return make_string(res_url); +SEXP make_ctype(CURL *handle){ + char * ct; + assert(curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &ct)); + return make_string(ct); } -static SEXP make_info_http_version(CURL * handle){ - long res = 0; - assert(curl_easy_getinfo(handle, CURLINFO_HTTP_VERSION, &res)); - switch (res) { - case CURL_HTTP_VERSION_1_0: - case CURL_HTTP_VERSION_1_1: - return Rf_ScalarInteger(1); - case CURL_HTTP_VERSION_2_0: - return Rf_ScalarInteger(2); - case CURL_HTTP_VERSION_3: - return Rf_ScalarInteger(3); - default: - return Rf_ScalarInteger(NA_INTEGER); - } +SEXP make_url(CURL *handle){ + char *res_url; + assert(curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &res_url)); + return Rf_ScalarString(Rf_mkCharCE(res_url, CE_UTF8)); } -static SEXP make_filetime(CURL *handle){ +SEXP make_filetime(CURL *handle){ long filetime; assert(curl_easy_getinfo(handle, CURLINFO_FILETIME, &filetime)); if(filetime < 0){ @@ -447,7 +448,7 @@ static SEXP make_filetime(CURL *handle){ return out; } -static SEXP make_rawvec(unsigned char *ptr, size_t size){ +SEXP make_rawvec(unsigned char *ptr, size_t size){ SEXP out = PROTECT(Rf_allocVector(RAWSXP, size)); if(size > 0) memcpy(RAW(out), ptr, size); @@ -455,18 +456,15 @@ static SEXP make_rawvec(unsigned char *ptr, size_t size){ return out; } -static SEXP make_namesvec(void){ - SEXP names = PROTECT(Rf_allocVector(STRSXP, 10)); +SEXP make_namesvec(void){ + SEXP names = PROTECT(Rf_allocVector(STRSXP, 7)); SET_STRING_ELT(names, 0, Rf_mkChar("url")); SET_STRING_ELT(names, 1, Rf_mkChar("status_code")); SET_STRING_ELT(names, 2, Rf_mkChar("type")); SET_STRING_ELT(names, 3, Rf_mkChar("headers")); SET_STRING_ELT(names, 4, Rf_mkChar("modified")); SET_STRING_ELT(names, 5, Rf_mkChar("times")); - SET_STRING_ELT(names, 6, Rf_mkChar("scheme")); - SET_STRING_ELT(names, 7, Rf_mkChar("http_version")); - SET_STRING_ELT(names, 8, Rf_mkChar("method")); - SET_STRING_ELT(names, 9, Rf_mkChar("content")); + SET_STRING_ELT(names, 6, Rf_mkChar("content")); UNPROTECT(1); return names; } @@ -477,17 +475,14 @@ SEXP R_get_handle_cookies(SEXP ptr){ SEXP make_handle_response(reference *ref){ CURL *handle = ref->handle; - SEXP res = PROTECT(Rf_allocVector(VECSXP, 10)); - SET_VECTOR_ELT(res, 0, make_info_string(handle, CURLINFO_EFFECTIVE_URL)); - SET_VECTOR_ELT(res, 1, make_info_integer(handle, CURLINFO_RESPONSE_CODE)); - SET_VECTOR_ELT(res, 2, make_info_string(handle, CURLINFO_CONTENT_TYPE)); + SEXP res = PROTECT(Rf_allocVector(VECSXP, 7)); + SET_VECTOR_ELT(res, 0, make_url(handle)); + SET_VECTOR_ELT(res, 1, make_status(handle)); + SET_VECTOR_ELT(res, 2, make_ctype(handle)); SET_VECTOR_ELT(res, 3, make_rawvec(ref->resheaders.buf, ref->resheaders.size)); SET_VECTOR_ELT(res, 4, make_filetime(handle)); SET_VECTOR_ELT(res, 5, make_timevec(handle)); - SET_VECTOR_ELT(res, 6, make_info_string(handle, CURLINFO_SCHEME)); - SET_VECTOR_ELT(res, 7, make_info_http_version(handle)); - SET_VECTOR_ELT(res, 8, make_info_string(handle, CURLINFO_EFFECTIVE_METHOD)); - SET_VECTOR_ELT(res, 9, R_NilValue); + SET_VECTOR_ELT(res, 6, R_NilValue); Rf_setAttrib(res, R_NamesSymbol, make_namesvec()); UNPROTECT(1); return res; @@ -501,10 +496,17 @@ SEXP R_get_handle_response(SEXP ptr){ SEXP R_get_handle_speed(SEXP ptr){ CURL *handle = get_handle(ptr); +#ifdef USE_CURL_OFF_T curl_off_t dl = 0; curl_off_t ul = 0; curl_easy_getinfo(handle, CURLINFO_SPEED_DOWNLOAD_T, &dl); curl_easy_getinfo(handle, CURLINFO_SPEED_UPLOAD_T, &ul); +#else + double dl = 0; + double ul = 0; + curl_easy_getinfo(handle, CURLINFO_SPEED_DOWNLOAD, &dl); + curl_easy_getinfo(handle, CURLINFO_SPEED_UPLOAD, &ul); +#endif SEXP out = Rf_allocVector(REALSXP, 2); REAL(out)[0] = (double) dl; REAL(out)[1] = (double) ul; @@ -513,15 +515,25 @@ SEXP R_get_handle_speed(SEXP ptr){ SEXP R_get_handle_clength(SEXP ptr){ CURL *handle = get_handle(ptr); +#ifdef USE_CURL_OFF_T curl_off_t cl = 0; curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl); +#else + double cl = 0; + curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &cl); +#endif return Rf_ScalarReal((double) cl < 0 ? NA_REAL : cl); } SEXP R_get_handle_received(SEXP ptr){ CURL *handle = get_handle(ptr); +#ifdef USE_CURL_OFF_T curl_off_t dl = 0; curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD_T, &dl); +#else + double dl = 0; + curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD, &dl); +#endif return Rf_ScalarReal((double) dl); } diff --git a/src/library/curl/src/ieproxy.c b/src/library/curl/src/ieproxy.c index 207eee228d..3fd8504920 100644 --- a/src/library/curl/src/ieproxy.c +++ b/src/library/curl/src/ieproxy.c @@ -125,20 +125,20 @@ SEXP R_get_proxy_for_url(SEXP target_url, SEXP auto_detect, SEXP autoproxy_url){ } //store output data - char buffer[65536]; + char buffer[500]; SEXP vec = PROTECT(Rf_allocVector(VECSXP, 3)); SET_VECTOR_ELT(vec, 0, Rf_ScalarLogical( ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY || ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_DEFAULT_PROXY)); if(ProxyInfo.lpszProxy != NULL) { - wcstombs(buffer, ProxyInfo.lpszProxy, 65536); + wcstombs(buffer, ProxyInfo.lpszProxy, 500); SET_VECTOR_ELT(vec, 1, Rf_mkString(buffer)); GlobalFree((void*) ProxyInfo.lpszProxy); } if(ProxyInfo.lpszProxyBypass != NULL) { - wcstombs(buffer, ProxyInfo.lpszProxyBypass, 65536); + wcstombs(buffer, ProxyInfo.lpszProxyBypass, 500); SET_VECTOR_ELT(vec, 2, Rf_mkString(buffer)); GlobalFree((void*) ProxyInfo.lpszProxyBypass ); } diff --git a/src/library/curl/src/init.c b/src/library/curl/src/init.c index 4f86272cf2..a76a615771 100644 --- a/src/library/curl/src/init.c +++ b/src/library/curl/src/init.c @@ -7,7 +7,6 @@ /* .Call calls */ extern SEXP R_curl_connection(SEXP, SEXP, SEXP); -extern SEXP R_curl_dryrun(SEXP); extern SEXP R_curl_escape(SEXP, SEXP); extern SEXP R_curl_fetch_disk(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_curl_fetch_memory(SEXP, SEXP, SEXP); @@ -29,18 +28,16 @@ extern SEXP R_handle_setform(SEXP, SEXP); extern SEXP R_handle_setheaders(SEXP, SEXP); extern SEXP R_handle_setopt(SEXP, SEXP, SEXP); extern SEXP R_option_types(void); -extern SEXP R_modify_url(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_multi_add(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP R_multi_cancel(SEXP); extern SEXP R_multi_fdset(SEXP); extern SEXP R_multi_list(SEXP); extern SEXP R_multi_new(void); extern SEXP R_multi_run(SEXP, SEXP, SEXP); -extern SEXP R_multi_setopt(SEXP, SEXP, SEXP, SEXP, SEXP); +extern SEXP R_multi_setopt(SEXP, SEXP, SEXP, SEXP); extern SEXP R_new_file_writer(SEXP); extern SEXP R_new_handle(void); extern SEXP R_nslookup(SEXP, SEXP); -extern SEXP R_parse_url(SEXP, SEXP, SEXP); extern SEXP R_proxy_info(void); extern SEXP R_split_string(SEXP, SEXP); extern SEXP R_total_handles(void); @@ -50,7 +47,6 @@ extern SEXP R_write_file_writer(SEXP, SEXP, SEXP); static const R_CallMethodDef CallEntries[] = { {"R_curl_connection", (DL_FUNC) &R_curl_connection, 3}, - {"R_curl_dryrun", (DL_FUNC) &R_curl_dryrun, 1}, {"R_curl_escape", (DL_FUNC) &R_curl_escape, 2}, {"R_curl_fetch_disk", (DL_FUNC) &R_curl_fetch_disk, 5}, {"R_curl_fetch_memory", (DL_FUNC) &R_curl_fetch_memory, 3}, @@ -71,18 +67,16 @@ static const R_CallMethodDef CallEntries[] = { {"R_handle_setform", (DL_FUNC) &R_handle_setform, 2}, {"R_handle_setopt", (DL_FUNC) &R_handle_setopt, 3}, {"R_option_types", (DL_FUNC) &R_option_types, 0}, - {"R_modify_url", (DL_FUNC) &R_modify_url, 9}, {"R_multi_add", (DL_FUNC) &R_multi_add, 5}, {"R_multi_cancel", (DL_FUNC) &R_multi_cancel, 1}, {"R_multi_fdset", (DL_FUNC) &R_multi_fdset, 1}, {"R_multi_list", (DL_FUNC) &R_multi_list, 1}, {"R_multi_new", (DL_FUNC) &R_multi_new, 0}, {"R_multi_run", (DL_FUNC) &R_multi_run, 3}, - {"R_multi_setopt", (DL_FUNC) &R_multi_setopt, 5}, + {"R_multi_setopt", (DL_FUNC) &R_multi_setopt, 4}, {"R_new_file_writer", (DL_FUNC) &R_new_file_writer, 1}, {"R_new_handle", (DL_FUNC) &R_new_handle, 0}, {"R_nslookup", (DL_FUNC) &R_nslookup, 2}, - {"R_parse_url", (DL_FUNC) &R_parse_url, 3}, {"R_proxy_info", (DL_FUNC) &R_proxy_info, 0}, {"R_split_string", (DL_FUNC) &R_split_string, 2}, {"R_total_handles", (DL_FUNC) &R_total_handles, 0}, @@ -93,17 +87,17 @@ static const R_CallMethodDef CallEntries[] = { }; void switch_to_openssl_on_vista(void); -CURLM *shared_multi_handle = NULL; +CURLM *multi_handle = NULL; attribute_visible void R_init_curl(DllInfo *info) { switch_to_openssl_on_vista(); curl_global_init(CURL_GLOBAL_DEFAULT); - shared_multi_handle = curl_multi_init(); + multi_handle = curl_multi_init(); R_registerRoutines(info, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(info, FALSE); } attribute_visible void R_unload_curl(DllInfo *info) { - curl_multi_cleanup(shared_multi_handle); + curl_multi_cleanup(multi_handle); //curl_global_cleanup(); } diff --git a/src/library/curl/src/interrupt.c b/src/library/curl/src/interrupt.c index ee6b06a956..5366ec7a82 100644 --- a/src/library/curl/src/interrupt.c +++ b/src/library/curl/src/interrupt.c @@ -14,33 +14,15 @@ int pending_interrupt(void) { return !(R_ToplevelExec(check_interrupt_fn, NULL)); } -static int str_starts_with(const char *a, const char *b) { - if(strncmp(a, b, strlen(b)) == 0) return 1; - return 0; -} - /* created in init.c */ -extern CURLM * shared_multi_handle; +extern CURLM * multi_handle; /* Don't call Rf_error() until we remove the handle from the multi handle! */ CURLcode curl_perform_with_interrupt(CURL *handle){ /* start settings */ CURLcode status = CURLE_FAILED_INIT; - CURLM * temp_multi_handle = NULL; - CURLM * multi_handle = NULL; int still_running = 1; - /* Do not reuse FTP connections, because it is buggy */ - /* For example https://github.com/jeroen/curl/issues/348 */ - const char *effective_url = NULL; - curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &effective_url); - if(effective_url && str_starts_with(effective_url, "ftp")){ - temp_multi_handle = curl_multi_init(); - multi_handle = temp_multi_handle; - } else { - multi_handle = shared_multi_handle; - } - if(CURLM_OK != curl_multi_add_handle(multi_handle, handle)){ return CURLE_FAILED_INIT; } @@ -52,12 +34,20 @@ CURLcode curl_perform_with_interrupt(CURL *handle){ break; } +#ifdef HAS_MULTI_WAIT + /* wait for activity, timeout or "nothing" */ int numfds; if(curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds) != CURLM_OK) break; +#endif + + /* Required by old versions of libcurl */ + CURLMcode res = CURLM_CALL_MULTI_PERFORM; + while(res == CURLM_CALL_MULTI_PERFORM) + res = curl_multi_perform(multi_handle, &(still_running)); /* check for multi errors */ - if(curl_multi_perform(multi_handle, &(still_running)) != CURLM_OK) + if(res != CURLM_OK) break; } @@ -75,7 +65,5 @@ CURLcode curl_perform_with_interrupt(CURL *handle){ /* cleanup first */ curl_multi_remove_handle(multi_handle, handle); - if(temp_multi_handle) - curl_multi_cleanup(temp_multi_handle); return status; } diff --git a/src/library/curl/src/macos-polyfill.h b/src/library/curl/src/macos-polyfill.h deleted file mode 100644 index 8a85750cec..0000000000 --- a/src/library/curl/src/macos-polyfill.h +++ /dev/null @@ -1,42 +0,0 @@ -/* MacOS-11 SDK headers are older than it's runtime, so we add this polyfill */ - -const char *curl_url_strerror(CURLUcode); -#define CURLINFO_EFFECTIVE_METHOD CURLINFO_STRING + 58 -#define CURL_HTTP_VERSION_3 30 -#define CURLMOPT_MAX_CONCURRENT_STREAMS 16 -#define HAS_CURL_PARSER_STRERROR 1 - -#ifndef CURLINC_OPTIONS_H -#define CURLINC_OPTIONS_H -typedef enum { - CURLOT_LONG, - CURLOT_VALUES, - CURLOT_OFF_T, - CURLOT_OBJECT, - CURLOT_STRING, - CURLOT_SLIST, - CURLOT_CBPTR, - CURLOT_BLOB, - CURLOT_FUNCTION -} curl_easytype; - -#define CURLOT_FLAG_ALIAS (1<<0) -struct curl_easyoption { - const char *name; - CURLoption id; - curl_easytype type; - unsigned int flags; -}; - -CURL_EXTERN const struct curl_easyoption * -curl_easy_option_by_name(const char *name); - -CURL_EXTERN const struct curl_easyoption * -curl_easy_option_by_id(CURLoption id); - -CURL_EXTERN const struct curl_easyoption * -curl_easy_option_next(const struct curl_easyoption *prev); - -#ifdef __cplusplus -#endif -#endif diff --git a/src/library/curl/src/multi.c b/src/library/curl/src/multi.c index 3a85a3be3f..3cf93328d1 100644 --- a/src/library/curl/src/multi.c +++ b/src/library/curl/src/multi.c @@ -11,6 +11,10 @@ * - Use Rf_eval() to callback instead of R_tryEval() to propagate interrupt or error back to C */ +#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 30) +#define HAS_CURLMOPT_MAX_TOTAL_CONNECTIONS 1 +#endif + multiref *get_multiref(SEXP ptr){ if(TYPEOF(ptr) != EXTPTRSXP || !Rf_inherits(ptr, "curl_multi")) Rf_error("pool ptr is not a curl_multi handle"); @@ -20,23 +24,6 @@ multiref *get_multiref(SEXP ptr){ return mref; } -/* retrieves CURLM from connections as well as pools */ -CURLM *get_curlm(SEXP ptr){ - CURLM *multi; - if(Rf_inherits(ptr, "curl")){ - ptr = Rf_getAttrib(ptr, Rf_install("conn_id")); - if (TYPEOF(ptr) != EXTPTRSXP) - Rf_error("pool ptr is not a curl connection"); - multi = (CURLM*) R_ExternalPtrAddr(ptr); - if(!multi) - Rf_error("CURLM pointer is dead"); - } else { - multiref *mref = get_multiref(ptr); - multi = mref->m; - } - return multi; -} - void multi_release(reference *ref){ /* Release the easy-handle */ CURL *handle = ref->handle; @@ -166,7 +153,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ if(Rf_isFunction(cb_complete)){ int arglen = Rf_length(R_ClosureFormals(cb_complete)); SEXP out = PROTECT(make_handle_response(ref)); - SET_VECTOR_ELT(out, 9, buf); + SET_VECTOR_ELT(out, 6, buf); SEXP call = PROTECT(Rf_lcons(cb_complete, arglen ? Rf_lcons(out, R_NilValue) : R_NilValue)); //R_tryEval(call, R_GlobalEnv, &cbfail); Rf_eval(call, R_GlobalEnv); //OK to error here @@ -175,7 +162,12 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ } else { total_fail++; if(Rf_isFunction(cb_error)){ - raise_libcurl_error(status, ref, cb_error); + int arglen = Rf_length(R_ClosureFormals(cb_error)); + SEXP buf = PROTECT(Rf_mkString(strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(status))); + SEXP call = PROTECT(Rf_lcons(cb_error, arglen ? Rf_lcons(buf, R_NilValue) : R_NilValue)); + //R_tryEval(call, R_GlobalEnv, &cbfail); + Rf_eval(call, R_GlobalEnv); //OK to error here + UNPROTECT(2); } } UNPROTECT(4); @@ -188,7 +180,7 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ R_CheckUserInterrupt(); /* check for timeout or max result*/ - if(total_pending > 0 && result_max > 0 && total_success + total_fail >= result_max) + if(result_max > 0 && total_success + total_fail >= result_max) break; if(time_max == 0 && total_pending != -1) break; @@ -202,13 +194,19 @@ SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ if(total_pending == 0 && !dirty) break; +#ifdef HAS_MULTI_WAIT + /* wait for activity, timeout or "nothing" */ int numfds; double waitforit = fmin(time_max - seconds_elapsed, 1); //at most 1 sec to support interrupts if(time_max > 0) massert(curl_multi_wait(multi, NULL, 0, (int) waitforit * 1000, &numfds)); +#endif /* poll libcurl for new data - updates total_pending */ - if(curl_multi_perform(multi, &(total_pending)) != CURLM_OK) + CURLMcode res = CURLM_CALL_MULTI_PERFORM; + while(res == CURLM_CALL_MULTI_PERFORM) + res = curl_multi_perform(multi, &(total_pending)); + if(res != CURLM_OK) break; } @@ -250,13 +248,19 @@ SEXP R_multi_new(void){ return ptr; } -SEXP R_multi_setopt(SEXP pool_ptr, SEXP total_con, SEXP host_con, SEXP max_streams, SEXP multiplex){ +SEXP R_multi_setopt(SEXP pool_ptr, SEXP total_con, SEXP host_con, SEXP multiplex){ + #ifdef HAS_CURLMOPT_MAX_TOTAL_CONNECTIONS CURLM *multi = get_multiref(pool_ptr)->m; massert(curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) Rf_asInteger(total_con))); massert(curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, (long) Rf_asInteger(host_con))); + #endif + + // NOTE: CURLPIPE_HTTP1 is unsafe for non idempotent requests + #ifdef CURLPIPE_MULTIPLEX massert(curl_multi_setopt(multi, CURLMOPT_PIPELINING, Rf_asLogical(multiplex) ? CURLPIPE_MULTIPLEX : CURLPIPE_NOTHING)); - massert(curl_multi_setopt(multi, CURLMOPT_MAX_CONCURRENT_STREAMS, (long) Rf_asInteger(max_streams))); + #endif + return pool_ptr; } @@ -265,7 +269,8 @@ SEXP R_multi_list(SEXP pool_ptr){ } SEXP R_multi_fdset(SEXP pool_ptr){ - CURLM *multi = get_curlm(pool_ptr); + multiref *mref = get_multiref(pool_ptr); + CURLM *multi = mref->m; fd_set read_fd_set, write_fd_set, exc_fd_set; int max_fd, i, num_read = 0, num_write = 0, num_exc = 0; int *pread, *pwrite, *pexc; diff --git a/src/library/curl/src/options.c b/src/library/curl/src/options.c index 4d02e0061a..4d5a513082 100644 --- a/src/library/curl/src/options.c +++ b/src/library/curl/src/options.c @@ -1,6 +1,7 @@ #include "curl-common.h" SEXP R_option_types(void){ +#ifdef HAS_CURL_EASY_OPTION int len = 0; const struct curl_easyoption *o = NULL; while((o = curl_easy_option_next(o))){ @@ -31,4 +32,7 @@ SEXP R_option_types(void){ SET_STRING_ELT(listnms, 2, Rf_mkChar("type")); UNPROTECT(5); return out; +#else + return R_NilValue; +#endif } diff --git a/src/library/curl/src/ssl.c b/src/library/curl/src/ssl.c index 25a1499978..961c68e8dc 100644 --- a/src/library/curl/src/ssl.c +++ b/src/library/curl/src/ssl.c @@ -12,7 +12,7 @@ void switch_to_openssl_on_vista(void){ #if defined(_WIN32) && defined(HAS_MULTI_SSL) /* If a CURL_SSL_BACKEND is set, do not override */ char *envvar = getenv("CURL_SSL_BACKEND"); - if(envvar != NULL && *envvar != 0){ + if(envvar != NULL){ REprintf("Initiating curl with CURL_SSL_BACKEND: %s\n", envvar); return; } diff --git a/src/library/curl/src/typechecking.c b/src/library/curl/src/typechecking.c index 8794d96973..3f00557040 100644 --- a/src/library/curl/src/typechecking.c +++ b/src/library/curl/src/typechecking.c @@ -1,5 +1,7 @@ #include "curl-common.h" +#ifdef HAS_CURL_EASY_OPTION + int r_curl_is_string_option(CURLoption x){ return curl_easy_option_by_id(x)->type == CURLOT_STRING; } @@ -20,3 +22,29 @@ int r_curl_is_off_t_option(CURLoption x){ int r_curl_is_postfields_option(CURLoption x){ return curl_easy_option_by_id(x)->type == CURLOT_OBJECT; } + +#else //CURLOT_FLAG_ALIAS + +#include "typelist.h" + +int r_curl_is_string_option(CURLoption x){ + return curlcheck_string_option(x); +} + +int r_curl_is_slist_option(CURLoption x){ + return curlcheck_slist_option(x); +} + +int r_curl_is_long_option(CURLoption x){ + return curlcheck_long_option(x); +} + +int r_curl_is_off_t_option(CURLoption x){ + return curlcheck_off_t_option(x); +} + +int r_curl_is_postfields_option(CURLoption x){ + return curlcheck_postfields_option(x); +} + +#endif //CURLOT_FLAG_ALIAS diff --git a/src/library/curl/src/typelist.h b/src/library/curl/src/typelist.h new file mode 100644 index 0000000000..c64b3cc6e3 --- /dev/null +++ b/src/library/curl/src/typelist.h @@ -0,0 +1,63 @@ +/* This file is autogenerated from typelist.h.in */ +/* if headers are not included, we provide a copy */ +#ifndef CURLINC_TYPECHECK_GCC_H + +/* evaluates to true if option takes a long argument */ +#define curlcheck_long_option(option) \ + (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) + +#ifdef CURLOPTTYPE_BLOB +#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T && (option) < CURLOPTTYPE_BLOB) +#else +#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T) +#endif + +/* evaluates to true if option takes a data argument to pass to a callback */ +#define curlcheck_cb_data_option(option) \ + ((option) == /*CURLOPT_CHUNK_DATA*/ 10201 || \ + (option) == /*CURLOPT_CLOSESOCKETDATA*/ 10209 || \ + (option) == /*CURLOPT_DEBUGDATA*/ 10095 || \ + (option) == /*CURLOPT_FNMATCH_DATA*/ 10202 || \ + (option) == /*CURLOPT_HEADERDATA*/ 10029 || \ + (option) == /*CURLOPT_HSTSREADDATA*/ 10302 || \ + (option) == /*CURLOPT_HSTSWRITEDATA*/ 10304 || \ + (option) == /*CURLOPT_INTERLEAVEDATA*/ 10195 || \ + (option) == /*CURLOPT_IOCTLDATA*/ 10131 || \ + (option) == /*CURLOPT_OPENSOCKETDATA*/ 10164 || \ + (option) == /*CURLOPT_PREREQDATA*/ 10313 || \ + (option) == /*CURLOPT_PROGRESSDATA*/ 10057 || \ + (option) == /*CURLOPT_READDATA*/ 10009 || \ + (option) == /*CURLOPT_SEEKDATA*/ 10168 || \ + (option) == /*CURLOPT_SOCKOPTDATA*/ 10149 || \ + (option) == /*CURLOPT_SSH_KEYDATA*/ 10185 || \ + (option) == /*CURLOPT_SSL_CTX_DATA*/ 10109 || \ + (option) == /*CURLOPT_WRITEDATA*/ 10001 || \ + (option) == /*CURLOPT_RESOLVER_START_DATA*/ 10273 || \ + (option) == /*CURLOPT_TRAILERDATA*/ 10284 || \ + (option) == /*CURLOPT_SSH_HOSTKEYDATA*/ 10317 || \ + 0) + +/* evaluates to true if option takes a POST data argument (void* or char*) */ +#define curlcheck_postfields_option(option) \ + ((option) == /*CURLOPT_POSTFIELDS*/ 10015 || \ + (option) == /*CURLOPT_COPYPOSTFIELDS*/ 10165 || \ + 0) + +/* evaluates to true if option takes a struct curl_slist * argument */ +#define curlcheck_slist_option(option) \ + ((option) == /*CURLOPT_HTTP200ALIASES*/ 10104 || \ + (option) == /*CURLOPT_HTTPHEADER*/ 10023 || \ + (option) == /*CURLOPT_MAIL_RCPT*/ 10187 || \ + (option) == /*CURLOPT_POSTQUOTE*/ 10039 || \ + (option) == /*CURLOPT_PREQUOTE*/ 10093 || \ + (option) == /*CURLOPT_PROXYHEADER*/ 10228 || \ + (option) == /*CURLOPT_QUOTE*/ 10028 || \ + (option) == /*CURLOPT_RESOLVE*/ 10203 || \ + (option) == /*CURLOPT_TELNETOPTIONS*/ 10070 || \ + (option) == /*CURLOPT_CONNECT_TO*/ 10243 || \ + 0) + +#define curlcheck_string_option(x) \ +(x > 10000 && x < 20000 && !curlcheck_slist_option(x) && !curlcheck_cb_data_option(x)); + +#endif diff --git a/src/library/curl/src/urlparser.c b/src/library/curl/src/urlparser.c deleted file mode 100644 index 0d1cd98d97..0000000000 --- a/src/library/curl/src/urlparser.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "curl-common.h" - -static SEXP make_url_names(void){ - SEXP names = PROTECT(Rf_allocVector(STRSXP, 9)); - SET_STRING_ELT(names, 0, Rf_mkChar("url")); - SET_STRING_ELT(names, 1, Rf_mkChar("scheme")); - SET_STRING_ELT(names, 2, Rf_mkChar("host")); - SET_STRING_ELT(names, 3, Rf_mkChar("port")); - SET_STRING_ELT(names, 4, Rf_mkChar("path")); - SET_STRING_ELT(names, 5, Rf_mkChar("query")); - SET_STRING_ELT(names, 6, Rf_mkChar("fragment")); - SET_STRING_ELT(names, 7, Rf_mkChar("user")); - SET_STRING_ELT(names, 8, Rf_mkChar("password")); - UNPROTECT(1); - return names; -} - -static void fail_if(CURLUcode err){ - if(err != CURLUE_OK) -#ifdef HAS_CURL_PARSER_STRERROR - Rf_error("Failed to parse URL: %s", curl_url_strerror(err)); -#else - Rf_error("Failed to parse URL: error code %d", err); -#endif -} - -static SEXP get_field(CURLU *h, CURLUPart part, CURLUcode field_missing){ - char *str = NULL; - SEXP field = NULL; - CURLUcode err = curl_url_get(h, part, &str, 0); - if(err == field_missing && err != CURLUE_OK){ - field = R_NilValue; - } else { - fail_if(err); - field = make_string(str); - } - curl_free(str); - return field; -} - -/* We use CURLU_NON_SUPPORT_SCHEME to make results consistent across different libcurl configurations and Ada URL - * We use CURLU_URLENCODE to normalize input URLs and also be consistent with Ada URL */ - - -static void set_url(CURLU *h, const char *str, int default_scheme){ - int flags = CURLU_NON_SUPPORT_SCHEME | CURLU_URLENCODE | (default_scheme * CURLU_DEFAULT_SCHEME); - fail_if(curl_url_set(h, CURLUPART_URL, str, flags)); -} - -SEXP R_parse_url(SEXP url, SEXP baseurl, SEXP default_https) { - CURLU *h = curl_url(); - int default_scheme = Rf_length(default_https) && Rf_asLogical(default_https); - if(Rf_length(baseurl)){ - set_url(h, get_string(baseurl), default_scheme); - } - set_url(h, get_string(url), default_scheme); - SEXP out = PROTECT(Rf_allocVector(VECSXP, 9)); - SET_VECTOR_ELT(out, 0, get_field(h, CURLUPART_URL, CURLUE_OK)); - SET_VECTOR_ELT(out, 1, get_field(h, CURLUPART_SCHEME, CURLUE_NO_SCHEME)); - SET_VECTOR_ELT(out, 2, get_field(h, CURLUPART_HOST, CURLUE_NO_HOST)); - SET_VECTOR_ELT(out, 3, get_field(h, CURLUPART_PORT, CURLUE_NO_PORT)); - SET_VECTOR_ELT(out, 4, get_field(h, CURLUPART_PATH, CURLUE_OK)); - SET_VECTOR_ELT(out, 5, get_field(h, CURLUPART_QUERY, CURLUE_NO_QUERY)); - SET_VECTOR_ELT(out, 6, get_field(h, CURLUPART_FRAGMENT, CURLUE_NO_FRAGMENT)); - SET_VECTOR_ELT(out, 7, get_field(h, CURLUPART_USER, CURLUE_NO_USER)); - SET_VECTOR_ELT(out, 8, get_field(h, CURLUPART_PASSWORD, CURLUE_NO_PASSWORD)); - curl_url_cleanup(h); - Rf_setAttrib(out, R_NamesSymbol, make_url_names()); - UNPROTECT(1); - return out; -} - -static void set_value(CURLU *h, CURLUPart part, SEXP value){ - if(Rf_length(value) && Rf_isString(value)){ - if(STRING_ELT(value, 0) == NA_STRING || Rf_length(STRING_ELT(value, 0)) == 0){ - fail_if(curl_url_set(h, part, NULL, 0)); - } else if(Rf_inherits(value, "AsIs")){ - fail_if(curl_url_set(h, part, get_string(value), 0)); - } else { - fail_if(curl_url_set(h, part, get_string(value), CURLU_NON_SUPPORT_SCHEME | CURLU_URLENCODE)); - } - } -} - -SEXP R_modify_url(SEXP url, SEXP scheme, SEXP host, SEXP port, SEXP path, SEXP query, SEXP fragment, SEXP user, SEXP password){ - CURLU *h = curl_url(); - set_value(h, CURLUPART_URL, url); - set_value(h, CURLUPART_SCHEME, scheme); - set_value(h, CURLUPART_HOST, host); - set_value(h, CURLUPART_PORT, port); - set_value(h, CURLUPART_PATH, path); - set_value(h, CURLUPART_QUERY, query); - set_value(h, CURLUPART_FRAGMENT, fragment); - set_value(h, CURLUPART_USER, user); - set_value(h, CURLUPART_PASSWORD, password); - char *str = NULL; - fail_if(curl_url_get(h, CURLUPART_URL, &str, 0)); - SEXP out = make_string(str); - curl_free(str); - return out; -} diff --git a/src/library/curl/src/utils.c b/src/library/curl/src/utils.c index af60159a48..584b164153 100644 --- a/src/library/curl/src/utils.c +++ b/src/library/curl/src/utils.c @@ -1,19 +1,6 @@ #include "curl-common.h" #include /* SIZE_MAX */ -#ifdef _WIN32 -#include -void send_r_interrupt(void) { - UserBreak = 1; - R_CheckUserInterrupt(); -} -#else -#include -void send_r_interrupt(void) { - Rf_onintr(); -} -#endif - CURL* get_handle(SEXP ptr){ return get_ref(ptr)->handle; } @@ -50,42 +37,40 @@ void reset_errbuf(reference *ref){ memset(ref->errbuf, 0, CURL_ERROR_SIZE); } -void assert_message(CURLcode res, const char *str){ - if(res == CURLE_OK) - return; - if(res == CURLE_ABORTED_BY_CALLBACK) - send_r_interrupt(); - if(str == NULL) - str = curl_easy_strerror(res); - SEXP code = PROTECT(Rf_ScalarInteger(res)); - SEXP message = PROTECT(make_string(str)); - SEXP expr = PROTECT(Rf_install("raise_libcurl_error")); - SEXP call = PROTECT(Rf_lang3(expr, code, message)); - SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); - Rf_eval(call, env); - UNPROTECT(5); //never happens -} - -void raise_libcurl_error(CURLcode res, reference *ref, SEXP error_cb){ - if(res == CURLE_OK) - return; - if(res == CURLE_ABORTED_BY_CALLBACK) - send_r_interrupt(); - const char *source_url = NULL; - curl_easy_getinfo(ref->handle, CURLINFO_EFFECTIVE_URL, &source_url); - SEXP url = PROTECT(make_string(source_url)); - SEXP code = PROTECT(Rf_ScalarInteger(res)); - SEXP message = PROTECT(make_string(curl_easy_strerror(res))); - SEXP errbuf = PROTECT(make_string(ref->errbuf)); - SEXP expr = PROTECT(Rf_install("raise_libcurl_error")); - SEXP call = PROTECT(Rf_lang6(expr, code, message, errbuf, url, error_cb)); - SEXP env = PROTECT(R_FindNamespace(Rf_mkString("curl"))); - Rf_eval(call, env); - UNPROTECT(7); //happens for non-throwing error_cb() +void assert(CURLcode res){ + if(res != CURLE_OK) + Rf_error("%s", curl_easy_strerror(res)); +} + +static char * parse_host(const char * input){ + static char buf[8000] = {0}; + char *url = buf; + strncpy(url, input, 7999); + + char *ptr = NULL; + if((ptr = strstr(url, "://"))) + url = ptr + 3; + if((ptr = strchr(url, '/'))) + *ptr = 0; + if((ptr = strchr(url, '#'))) + *ptr = 0; + if((ptr = strchr(url, '?'))) + *ptr = 0; + if((ptr = strchr(url, '@'))) + url = ptr + 1; + return url; } void assert_status(CURLcode res, reference *ref){ - raise_libcurl_error(res, ref, R_NilValue); + // Customize better error message for timeoutsS + if(res == CURLE_OPERATION_TIMEDOUT || res == CURLE_SSL_CACERT){ + const char *url = NULL; + if(curl_easy_getinfo(ref->handle, CURLINFO_EFFECTIVE_URL, &url) == CURLE_OK){ + Rf_error("%s: [%s] %s", curl_easy_strerror(res), parse_host(url), ref->errbuf); + } + } + if(res != CURLE_OK) + Rf_error("%s", strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(res)); } void massert(CURLMcode res){ @@ -93,6 +78,15 @@ void massert(CURLMcode res){ Rf_error("%s", curl_multi_strerror(res)); } +void stop_for_status(CURL *http_handle){ + long status = 0; + assert(curl_easy_getinfo(http_handle, CURLINFO_RESPONSE_CODE, &status)); + + /* check http status code. Not sure what this does for ftp. */ + if(status >= 300) + Rf_error("HTTP error %ld.", status); +} + /* make sure to call curl_slist_free_all on this object */ struct curl_slist* vec_to_slist(SEXP vec){ if(!Rf_isString(vec)) diff --git a/src/library/curl/src/version.c b/src/library/curl/src/version.c index bcf4c8bfea..869712b85b 100644 --- a/src/library/curl/src/version.c +++ b/src/library/curl/src/version.c @@ -1,18 +1,20 @@ -#include "curl-common.h" +#include +#include + +#define make_string(x) x ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) SEXP R_curl_version(void) { /* retrieve info from curl */ const curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); /* put stuff in a list */ - SEXP list = PROTECT(Rf_allocVector(VECSXP, 12)); + SEXP list = PROTECT(Rf_allocVector(VECSXP, 10)); SET_VECTOR_ELT(list, 0, make_string(data->version)); - SET_VECTOR_ELT(list, 1, make_string(LIBCURL_VERSION)); - SET_VECTOR_ELT(list, 2, make_string(data->ssl_version)); - SET_VECTOR_ELT(list, 3, make_string(data->libz_version)); - SET_VECTOR_ELT(list, 4, make_string(data->libssh_version)); - SET_VECTOR_ELT(list, 5, make_string(data->libidn)); - SET_VECTOR_ELT(list, 6, make_string(data->host)); + SET_VECTOR_ELT(list, 1, make_string(data->ssl_version)); + SET_VECTOR_ELT(list, 2, make_string(data->libz_version)); + SET_VECTOR_ELT(list, 3, make_string(data->libssh_version)); + SET_VECTOR_ELT(list, 4, make_string(data->libidn)); + SET_VECTOR_ELT(list, 5, make_string(data->host)); /* create vector of protocols */ int len = 0; @@ -22,46 +24,40 @@ SEXP R_curl_version(void) { for (int i = 0; i < len; i++){ SET_STRING_ELT(protocols, i, Rf_mkChar(*(data->protocols + i))); } - SET_VECTOR_ELT(list, 7, protocols); + SET_VECTOR_ELT(list, 6, protocols); /* add list names */ - SEXP names = PROTECT(Rf_allocVector(STRSXP, 12)); + SEXP names = PROTECT(Rf_allocVector(STRSXP, 10)); SET_STRING_ELT(names, 0, Rf_mkChar("version")); - SET_STRING_ELT(names, 1, Rf_mkChar("headers")); - SET_STRING_ELT(names, 2, Rf_mkChar("ssl_version")); - SET_STRING_ELT(names, 3, Rf_mkChar("libz_version")); - SET_STRING_ELT(names, 4, Rf_mkChar("libssh_version")); - SET_STRING_ELT(names, 5, Rf_mkChar("libidn_version")); - SET_STRING_ELT(names, 6, Rf_mkChar("host")); - SET_STRING_ELT(names, 7, Rf_mkChar("protocols")); - SET_STRING_ELT(names, 8, Rf_mkChar("ipv6")); - SET_STRING_ELT(names, 9, Rf_mkChar("http2")); - SET_STRING_ELT(names, 10, Rf_mkChar("idn")); - SET_STRING_ELT(names, 11, Rf_mkChar("url_parser")); + SET_STRING_ELT(names, 1, Rf_mkChar("ssl_version")); + SET_STRING_ELT(names, 2, Rf_mkChar("libz_version")); + SET_STRING_ELT(names, 3, Rf_mkChar("libssh_version")); + SET_STRING_ELT(names, 4, Rf_mkChar("libidn_version")); + SET_STRING_ELT(names, 5, Rf_mkChar("host")); + SET_STRING_ELT(names, 6, Rf_mkChar("protocols")); + SET_STRING_ELT(names, 7, Rf_mkChar("ipv6")); + SET_STRING_ELT(names, 8, Rf_mkChar("http2")); + SET_STRING_ELT(names, 9, Rf_mkChar("idn")); Rf_setAttrib(list, R_NamesSymbol, names); #ifdef CURL_VERSION_IPV6 - SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(data->features & CURL_VERSION_IPV6)); + SET_VECTOR_ELT(list, 7, Rf_ScalarLogical(data->features & CURL_VERSION_IPV6)); #else - SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 7, Rf_ScalarLogical(0)); #endif #ifdef CURL_VERSION_HTTP2 - SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(data->features & CURL_VERSION_HTTP2)); + SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(data->features & CURL_VERSION_HTTP2)); #else - SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 8, Rf_ScalarLogical(0)); #endif #ifdef CURL_VERSION_IDN - SET_VECTOR_ELT(list, 10, Rf_ScalarLogical(data->features & CURL_VERSION_IDN)); + SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(data->features & CURL_VERSION_IDN)); #else - SET_VECTOR_ELT(list, 10, Rf_ScalarLogical(0)); + SET_VECTOR_ELT(list, 9, Rf_ScalarLogical(0)); #endif - // CURL_PARSER is always assumed to be available - SET_VECTOR_ELT(list, 11, Rf_ScalarLogical(1)); - - /* return */ UNPROTECT(3); return list; diff --git a/src/library/curl/tools/errorcodes.txt b/src/library/curl/tools/errorcodes.txt deleted file mode 100644 index 6c1d287510..0000000000 --- a/src/library/curl/tools/errorcodes.txt +++ /dev/null @@ -1,128 +0,0 @@ -CURLE_OK = 0, -CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ -CURLE_FAILED_INIT, /* 2 */ -CURLE_URL_MALFORMAT, /* 3 */ -CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for 7.17.0, reused in April 2011 for 7.21.5] */ -CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ -CURLE_COULDNT_RESOLVE_HOST, /* 6 */ -CURLE_COULDNT_CONNECT, /* 7 */ -CURLE_WEIRD_SERVER_REPLY, /* 8 */ -CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server - due to lack of access - when login fails - this is not returned. */ -CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for - 7.15.4, reused in Dec 2011 for 7.24.0]*/ -CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ -CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server - [was obsoleted in August 2007 for 7.17.0, - reused in Dec 2011 for 7.24.0]*/ -CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ -CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ -CURLE_FTP_CANT_GET_HOST, /* 15 */ -CURLE_HTTP2, /* 16 - A problem in the http2 framing layer. - [was obsoleted in August 2007 for 7.17.0, - reused in July 2014 for 7.38.0] */ -CURLE_FTP_COULDNT_SET_TYPE, /* 17 */ -CURLE_PARTIAL_FILE, /* 18 */ -CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ -CURLE_OBSOLETE20, /* 20 - NOT USED */ -CURLE_QUOTE_ERROR, /* 21 - quote command failure */ -CURLE_HTTP_RETURNED_ERROR, /* 22 */ -CURLE_WRITE_ERROR, /* 23 */ -CURLE_OBSOLETE24, /* 24 - NOT USED */ -CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ -CURLE_READ_ERROR, /* 26 - could not open/read from file */ -CURLE_OUT_OF_MEMORY, /* 27 */ -CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ -CURLE_OBSOLETE29, /* 29 - NOT USED */ -CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ -CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ -CURLE_OBSOLETE32, /* 32 - NOT USED */ -CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */ -CURLE_HTTP_POST_ERROR, /* 34 */ -CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ -CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */ -CURLE_FILE_COULDNT_READ_FILE, /* 37 */ -CURLE_LDAP_CANNOT_BIND, /* 38 */ -CURLE_LDAP_SEARCH_FAILED, /* 39 */ -CURLE_OBSOLETE40, /* 40 - NOT USED */ -CURLE_FUNCTION_NOT_FOUND, /* 41 - NOT USED starting with 7.53.0 */ -CURLE_ABORTED_BY_CALLBACK, /* 42 */ -CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ -CURLE_OBSOLETE44, /* 44 - NOT USED */ -CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ -CURLE_OBSOLETE46, /* 46 - NOT USED */ -CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */ -CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ -CURLE_SETOPT_OPTION_SYNTAX, /* 49 - Malformed setopt option */ -CURLE_OBSOLETE50, /* 50 - NOT USED */ -CURLE_PEER_FAILED_VERIFICATION, /* 51 - NOT USED */ -CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ -CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ -CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as - default */ -CURLE_SEND_ERROR, /* 55 - failed sending network data */ -CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ -CURLE_OBSOLETE57, /* 57 - NOT IN USE */ -CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ -CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */ -CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint - was not verified fine */ -CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ -CURLE_LDAP_INVALID_URL, /* 62 - NOT IN USE since 7.82.0 */ -CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ -CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ -CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind - that failed */ -CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ -CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not - accepted and we failed to login */ -CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ -CURLE_TFTP_PERM, /* 69 - permission problem on server */ -CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */ -CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ -CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ -CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ -CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ -CURLE_CONV_FAILED, /* 75 - NOT IN USE since 7.82.0 */ -CURLE_CONV_REQD, /* 76 - NOT IN USE since 7.82.0 */ -CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing - or wrong format */ -CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ -CURLE_SSH, /* 79 - error from the SSH layer, somewhat - generic so the error message will be of - interest when this has happened */ - -CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL - connection */ -CURLE_AGAIN, /* 81 - socket is not ready for send/recv, - wait till it is ready and try again (Added - in 7.18.2) */ -CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or - wrong format (Added in 7.19.0) */ -CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in - 7.19.0) */ -CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ -CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ -CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */ -CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */ -CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ -CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the - session will be queued */ -CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not - match */ -CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ -CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer - */ -CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from - inside a callback */ -CURLE_AUTH_ERROR, /* 94 - an authentication function returned an - error */ -CURLE_HTTP3, /* 95 - An HTTP/3 layer problem */ -CURLE_QUIC_CONNECT_ERROR, /* 96 - QUIC connection error */ -CURLE_PROXY, /* 97 - proxy handshake error */ -CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ -CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ -CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ -CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */ -CURL_LAST /* never use! */ diff --git a/src/library/curl/tools/make-errorcodes.R b/src/library/curl/tools/make-errorcodes.R deleted file mode 100644 index df525c85b1..0000000000 --- a/src/library/curl/tools/make-errorcodes.R +++ /dev/null @@ -1,11 +0,0 @@ -lines <- readLines('tools/errorcodes.txt') -errors <- grep('^CURLE', lines, value = TRUE)[-1] -error_codes <- sub(",.*", "", errors) -stopifnot(error_codes[100] == "CURLE_TOO_LARGE") -error_codes <- sub("^curle_", "curl_error_", tolower(error_codes)) -out <- file('R/errcodes.R', 'w') -writeLines("# This file is autogenerated using make-errorcodes.R", out) -writeLines("libcurl_error_codes <- ", out) -dput(error_codes, out) -close(out) - diff --git a/src/library/curl/tools/symbols-in-versions b/src/library/curl/tools/symbols-in-versions index ddda26d832..c590d084f1 100644 --- a/src/library/curl/tools/symbols-in-versions +++ b/src/library/curl/tools/symbols-in-versions @@ -12,9 +12,6 @@ Name Introduced Deprecated Last -CURL_AT_LEAST_VERSION 7.43.0 -CURL_BLOB_COPY 7.71.0 -CURL_BLOB_NOCOPY 7.71.0 CURL_CHUNK_BGN_FUNC_FAIL 7.21.0 CURL_CHUNK_BGN_FUNC_OK 7.21.0 CURL_CHUNK_BGN_FUNC_SKIP 7.21.0 @@ -23,7 +20,6 @@ CURL_CHUNK_END_FUNC_OK 7.21.0 CURL_CSELECT_ERR 7.16.3 CURL_CSELECT_IN 7.16.3 CURL_CSELECT_OUT 7.16.3 -CURL_DEPRECATED 7.87.0 CURL_DID_MEMORY_FUNC_TYPEDEFS 7.49.0 CURL_EASY_NONE 7.14.0 - 7.15.4 CURL_EASY_TIMEOUT 7.14.0 - 7.15.4 @@ -53,7 +49,6 @@ CURL_HTTP_VERSION_2_0 7.33.0 CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE 7.49.0 CURL_HTTP_VERSION_2TLS 7.47.0 CURL_HTTP_VERSION_3 7.66.0 -CURL_HTTP_VERSION_3ONLY 7.88.0 CURL_HTTP_VERSION_NONE 7.9.1 CURL_HTTPPOST_BUFFER 7.46.0 CURL_HTTPPOST_CALLBACK 7.46.0 @@ -63,18 +58,15 @@ CURL_HTTPPOST_PTRBUFFER 7.46.0 CURL_HTTPPOST_PTRCONTENTS 7.46.0 CURL_HTTPPOST_PTRNAME 7.46.0 CURL_HTTPPOST_READFILE 7.46.0 -CURL_IGNORE_DEPRECATION 7.87.0 CURL_IPRESOLVE_V4 7.10.8 CURL_IPRESOLVE_V6 7.10.8 CURL_IPRESOLVE_WHATEVER 7.10.8 -CURL_ISOCPP 7.10.2 CURL_LOCK_ACCESS_NONE 7.10.3 CURL_LOCK_ACCESS_SHARED 7.10.3 CURL_LOCK_ACCESS_SINGLE 7.10.3 CURL_LOCK_DATA_CONNECT 7.10.3 CURL_LOCK_DATA_COOKIE 7.10.3 CURL_LOCK_DATA_DNS 7.10.3 -CURL_LOCK_DATA_HSTS 7.88.0 CURL_LOCK_DATA_NONE 7.10.3 CURL_LOCK_DATA_PSL 7.61.0 CURL_LOCK_DATA_SHARE 7.10.4 @@ -100,7 +92,6 @@ CURL_PREREQFUNC_OK 7.79.0 CURL_PROGRESS_BAR 7.1.1 - 7.4.1 CURL_PROGRESS_STATS 7.1.1 - 7.4.1 CURL_PROGRESSFUNC_CONTINUE 7.68.0 -CURL_PULL_SYS_POLL_H 7.56.0 CURL_PUSH_DENY 7.44.0 CURL_PUSH_ERROROUT 7.72.0 CURL_PUSH_OK 7.44.0 @@ -157,7 +148,6 @@ CURL_TRAILERFUNC_OK 7.64.0 CURL_UPKEEP_INTERVAL_DEFAULT 7.62.0 CURL_VERSION_ALTSVC 7.64.1 CURL_VERSION_ASYNCHDNS 7.10.7 -CURL_VERSION_BITS 7.43.0 CURL_VERSION_BROTLI 7.57.0 CURL_VERSION_CONV 7.15.4 CURL_VERSION_CURLDEBUG 7.19.6 @@ -177,7 +167,7 @@ CURL_VERSION_LARGEFILE 7.11.1 CURL_VERSION_LIBZ 7.10 CURL_VERSION_MULTI_SSL 7.56.0 CURL_VERSION_NTLM 7.10.6 -CURL_VERSION_NTLM_WB 7.22.0 8.8.0 +CURL_VERSION_NTLM_WB 7.22.0 CURL_VERSION_PSL 7.47.0 CURL_VERSION_SPNEGO 7.10.8 CURL_VERSION_SSL 7.10 @@ -190,8 +180,7 @@ CURL_VERSION_ZSTD 7.72.0 CURL_WAIT_POLLIN 7.28.0 CURL_WAIT_POLLOUT 7.28.0 CURL_WAIT_POLLPRI 7.28.0 -CURL_WIN32 7.69.0 - 8.5.0 -CURL_WRITEFUNC_ERROR 7.87.0 +CURL_WIN32 7.69.0 CURL_WRITEFUNC_PAUSE 7.18.0 CURL_ZERO_TERMINATED 7.56.0 CURLALTSVC_H1 7.64.1 @@ -210,14 +199,14 @@ CURLAUTH_GSSNEGOTIATE 7.10.6 7.38.0 CURLAUTH_NEGOTIATE 7.38.0 CURLAUTH_NONE 7.10.6 CURLAUTH_NTLM 7.10.6 -CURLAUTH_NTLM_WB 7.22.0 8.8.0 +CURLAUTH_NTLM_WB 7.22.0 CURLAUTH_ONLY 7.21.3 -CURLCLOSEPOLICY_CALLBACK 7.7 7.16.1 -CURLCLOSEPOLICY_LEAST_RECENTLY_USED 7.7 7.16.1 -CURLCLOSEPOLICY_LEAST_TRAFFIC 7.7 7.16.1 -CURLCLOSEPOLICY_NONE 7.7 7.16.1 -CURLCLOSEPOLICY_OLDEST 7.7 7.16.1 -CURLCLOSEPOLICY_SLOWEST 7.7 7.16.1 +CURLCLOSEPOLICY_CALLBACK 7.7 +CURLCLOSEPOLICY_LEAST_RECENTLY_USED 7.7 +CURLCLOSEPOLICY_LEAST_TRAFFIC 7.7 +CURLCLOSEPOLICY_NONE 7.7 +CURLCLOSEPOLICY_OLDEST 7.7 +CURLCLOSEPOLICY_SLOWEST 7.7 CURLE_ABORTED_BY_CALLBACK 7.1 CURLE_AGAIN 7.18.2 CURLE_ALREADY_COMPLETE 7.7.2 7.8 @@ -228,7 +217,7 @@ CURLE_BAD_DOWNLOAD_RESUME 7.10 CURLE_BAD_FUNCTION_ARGUMENT 7.1 CURLE_BAD_PASSWORD_ENTERED 7.4.2 7.17.0 CURLE_CHUNK_FAILED 7.21.0 -CURLE_CONV_FAILED 7.15.4 7.82.0 +CURLE_CONV_FAILED 7.15.4 CURLE_CONV_REQD 7.15.4 7.82.0 CURLE_COULDNT_CONNECT 7.1 CURLE_COULDNT_RESOLVE_HOST 7.1 @@ -328,7 +317,6 @@ CURLE_TFTP_NOSUCHUSER 7.15.0 CURLE_TFTP_NOTFOUND 7.15.0 CURLE_TFTP_PERM 7.15.0 CURLE_TFTP_UNKNOWNID 7.15.0 -CURLE_TOO_LARGE 8.6.0 CURLE_TOO_MANY_REDIRECTS 7.5 CURLE_UNKNOWN_OPTION 7.21.5 CURLE_UNKNOWN_TELNET_OPTION 7.7 7.21.5 @@ -340,7 +328,6 @@ CURLE_URL_MALFORMAT_USER 7.1 7.17.0 CURLE_USE_SSL_FAILED 7.17.0 CURLE_WEIRD_SERVER_REPLY 7.51.0 CURLE_WRITE_ERROR 7.1 -CURLE_ECH_REQUIRED 8.8.0 CURLFILETYPE_DEVICE_BLOCK 7.21.0 CURLFILETYPE_DEVICE_CHAR 7.21.0 CURLFILETYPE_DIRECTORY 7.21.0 @@ -419,23 +406,21 @@ CURLHSTS_READONLYFILE 7.74.0 CURLINFO_ACTIVESOCKET 7.45.0 CURLINFO_APPCONNECT_TIME 7.19.0 CURLINFO_APPCONNECT_TIME_T 7.61.0 -CURLINFO_CAINFO 7.84.0 CURLINFO_CAPATH 7.84.0 +CURLINFO_CAINFO 7.84.0 CURLINFO_CERTINFO 7.19.1 CURLINFO_CONDITION_UNMET 7.19.4 -CURLINFO_CONN_ID 8.2.0 CURLINFO_CONNECT_TIME 7.4.1 CURLINFO_CONNECT_TIME_T 7.61.0 -CURLINFO_CONTENT_LENGTH_DOWNLOAD 7.6.1 7.55.0 +CURLINFO_CONTENT_LENGTH_DOWNLOAD 7.6.1 CURLINFO_CONTENT_LENGTH_DOWNLOAD_T 7.55.0 -CURLINFO_CONTENT_LENGTH_UPLOAD 7.6.1 7.55.0 +CURLINFO_CONTENT_LENGTH_UPLOAD 7.6.1 CURLINFO_CONTENT_LENGTH_UPLOAD_T 7.55.0 CURLINFO_CONTENT_TYPE 7.9.4 CURLINFO_COOKIELIST 7.14.1 CURLINFO_DATA_IN 7.9.6 CURLINFO_DATA_OUT 7.9.6 CURLINFO_DOUBLE 7.4.1 -CURLINFO_EARLYDATA_SENT_T 8.11.0 CURLINFO_EFFECTIVE_METHOD 7.72.0 CURLINFO_EFFECTIVE_URL 7.4 CURLINFO_END 7.9.6 @@ -450,7 +435,7 @@ CURLINFO_HTTP_CONNECTCODE 7.10.7 CURLINFO_HTTP_VERSION 7.50.0 CURLINFO_HTTPAUTH_AVAIL 7.10.8 CURLINFO_LASTONE 7.4.1 -CURLINFO_LASTSOCKET 7.15.2 7.45.0 +CURLINFO_LASTSOCKET 7.15.2 CURLINFO_LOCAL_IP 7.21.0 CURLINFO_LOCAL_PORT 7.21.0 CURLINFO_LONG 7.4.1 @@ -463,16 +448,14 @@ CURLINFO_OFF_T 7.55.0 CURLINFO_OS_ERRNO 7.12.2 CURLINFO_PRETRANSFER_TIME 7.4.1 CURLINFO_PRETRANSFER_TIME_T 7.61.0 -CURLINFO_POSTTRANSFER_TIME_T 8.10.0 CURLINFO_PRIMARY_IP 7.19.0 CURLINFO_PRIMARY_PORT 7.21.0 CURLINFO_PRIVATE 7.10.3 -CURLINFO_PROTOCOL 7.52.0 7.85.0 +CURLINFO_PROTOCOL 7.52.0 CURLINFO_PROXY_ERROR 7.73.0 CURLINFO_PROXY_SSL_VERIFYRESULT 7.52.0 CURLINFO_PROXYAUTH_AVAIL 7.10.8 CURLINFO_PTR 7.54.1 -CURLINFO_QUEUE_TIME_T 8.6.0 CURLINFO_REDIRECT_COUNT 7.9.7 CURLINFO_REDIRECT_TIME 7.9.7 CURLINFO_REDIRECT_TIME_T 7.61.0 @@ -486,15 +469,15 @@ CURLINFO_RTSP_CSEQ_RECV 7.20.0 CURLINFO_RTSP_SERVER_CSEQ 7.20.0 CURLINFO_RTSP_SESSION_ID 7.20.0 CURLINFO_SCHEME 7.52.0 -CURLINFO_SIZE_DOWNLOAD 7.4.1 7.55.0 +CURLINFO_SIZE_DOWNLOAD 7.4.1 CURLINFO_SIZE_DOWNLOAD_T 7.55.0 -CURLINFO_SIZE_UPLOAD 7.4.1 7.55.0 +CURLINFO_SIZE_UPLOAD 7.4.1 CURLINFO_SIZE_UPLOAD_T 7.55.0 CURLINFO_SLIST 7.12.3 CURLINFO_SOCKET 7.45.0 -CURLINFO_SPEED_DOWNLOAD 7.4.1 7.55.0 +CURLINFO_SPEED_DOWNLOAD 7.4.1 CURLINFO_SPEED_DOWNLOAD_T 7.55.0 -CURLINFO_SPEED_UPLOAD 7.4.1 7.55.0 +CURLINFO_SPEED_UPLOAD 7.4.1 CURLINFO_SPEED_UPLOAD_T 7.55.0 CURLINFO_SSL_DATA_IN 7.12.1 CURLINFO_SSL_DATA_OUT 7.12.1 @@ -509,8 +492,6 @@ CURLINFO_TLS_SSL_PTR 7.48.0 CURLINFO_TOTAL_TIME 7.4.1 CURLINFO_TOTAL_TIME_T 7.61.0 CURLINFO_TYPEMASK 7.4.1 -CURLINFO_USED_PROXY 8.7.0 -CURLINFO_XFER_ID 8.2.0 CURLIOCMD_NOP 7.12.3 CURLIOCMD_RESTARTREAD 7.12.3 CURLIOE_FAILRESTART 7.12.3 @@ -578,7 +559,6 @@ CURLOPT_BUFFERSIZE 7.10 CURLOPT_CAINFO 7.4.2 CURLOPT_CAINFO_BLOB 7.77.0 CURLOPT_CAPATH 7.9.8 -CURLOPT_CA_CACHE_TIMEOUT 7.87.0 CURLOPT_CERTINFO 7.19.1 CURLOPT_CHUNK_BGN_FUNCTION 7.21.0 CURLOPT_CHUNK_DATA 7.21.0 @@ -620,9 +600,8 @@ CURLOPT_DOH_SSL_VERIFYHOST 7.76.0 CURLOPT_DOH_SSL_VERIFYPEER 7.76.0 CURLOPT_DOH_SSL_VERIFYSTATUS 7.76.0 CURLOPT_DOH_URL 7.62.0 -CURLOPT_ECH 8.8.0 -CURLOPT_EGDSOCKET 7.7 7.84.0 -CURLOPT_ENCODING 7.10 7.21.6 +CURLOPT_EGDSOCKET 7.7 +CURLOPT_ENCODING 7.10 CURLOPT_ERRORBUFFER 7.1 CURLOPT_EXPECT_100_TIMEOUT_MS 7.36.0 CURLOPT_FAILONERROR 7.1 @@ -637,7 +616,7 @@ CURLOPT_FTP_ACCOUNT 7.13.0 CURLOPT_FTP_ALTERNATIVE_TO_USER 7.15.5 CURLOPT_FTP_CREATE_MISSING_DIRS 7.10.7 CURLOPT_FTP_FILEMETHOD 7.15.1 -CURLOPT_FTP_RESPONSE_TIMEOUT 7.10.8 7.85.0 +CURLOPT_FTP_RESPONSE_TIMEOUT 7.10.8 CURLOPT_FTP_SKIP_PASV_IP 7.15.0 CURLOPT_FTP_SSL 7.11.0 7.16.4 CURLOPT_FTP_SSL_CCC 7.16.1 @@ -652,7 +631,6 @@ CURLOPT_FTPSSLAUTH 7.12.2 CURLOPT_GSSAPI_DELEGATION 7.22.0 CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0 CURLOPT_HAPROXYPROTOCOL 7.60.0 -CURLOPT_HAPROXY_CLIENT_IP 8.2.0 CURLOPT_HEADER 7.1 CURLOPT_HEADERDATA 7.10 CURLOPT_HEADERFUNCTION 7.7.2 @@ -681,8 +659,8 @@ CURLOPT_INFILESIZE_LARGE 7.11.0 CURLOPT_INTERFACE 7.3 CURLOPT_INTERLEAVEDATA 7.20.0 CURLOPT_INTERLEAVEFUNCTION 7.20.0 -CURLOPT_IOCTLDATA 7.12.3 7.18.0 -CURLOPT_IOCTLFUNCTION 7.12.3 7.18.0 +CURLOPT_IOCTLDATA 7.12.3 +CURLOPT_IOCTLFUNCTION 7.12.3 CURLOPT_IPRESOLVE 7.10.8 CURLOPT_ISSUERCERT 7.19.0 CURLOPT_ISSUERCERT_BLOB 7.71.0 @@ -698,9 +676,7 @@ CURLOPT_LOW_SPEED_TIME 7.1 CURLOPT_MAIL_AUTH 7.25.0 CURLOPT_MAIL_FROM 7.20.0 CURLOPT_MAIL_RCPT 7.20.0 -CURLOPT_MAIL_RCPT_ALLLOWFAILS 7.69.0 8.2.0 -CURLOPT_MAIL_RCPT_ALLOWFAILS 8.2.0 -CURLOPT_QUICK_EXIT 7.87.0 +CURLOPT_MAIL_RCPT_ALLLOWFAILS 7.69.0 CURLOPT_MAX_RECV_SPEED_LARGE 7.15.5 CURLOPT_MAX_SEND_SPEED_LARGE 7.15.5 CURLOPT_MAXAGE_CONN 7.65.0 @@ -745,8 +721,7 @@ CURLOPT_PREREQFUNCTION 7.80.0 CURLOPT_PRIVATE 7.10.3 CURLOPT_PROGRESSDATA 7.1 CURLOPT_PROGRESSFUNCTION 7.1 7.32.0 -CURLOPT_PROTOCOLS 7.19.4 7.85.0 -CURLOPT_PROTOCOLS_STR 7.85.0 +CURLOPT_PROTOCOLS 7.19.4 CURLOPT_PROXY 7.1 CURLOPT_PROXY_CAINFO 7.52.0 CURLOPT_PROXY_CAINFO_BLOB 7.77.0 @@ -780,14 +755,13 @@ CURLOPT_PROXYPORT 7.1 CURLOPT_PROXYTYPE 7.10 CURLOPT_PROXYUSERNAME 7.19.1 CURLOPT_PROXYUSERPWD 7.1 -CURLOPT_PUT 7.1 7.12.1 +CURLOPT_PUT 7.1 CURLOPT_QUOTE 7.1 -CURLOPT_RANDOM_FILE 7.7 7.84.0 +CURLOPT_RANDOM_FILE 7.7 CURLOPT_RANGE 7.1 CURLOPT_READDATA 7.9.7 CURLOPT_READFUNCTION 7.1 -CURLOPT_REDIR_PROTOCOLS 7.19.4 7.85.0 -CURLOPT_REDIR_PROTOCOLS_STR 7.85.0 +CURLOPT_REDIR_PROTOCOLS 7.19.4 CURLOPT_REFERER 7.1 CURLOPT_REQUEST_TARGET 7.55.0 CURLOPT_RESOLVE 7.21.3 @@ -807,7 +781,6 @@ CURLOPT_SASL_IR 7.31.0 CURLOPT_SEEKDATA 7.18.0 CURLOPT_SEEKFUNCTION 7.18.0 CURLOPT_SERVER_RESPONSE_TIMEOUT 7.20.0 -CURLOPT_SERVER_RESPONSE_TIMEOUT_MS 8.6.0 CURLOPT_SERVICE_NAME 7.43.0 CURLOPT_SHARE 7.10 CURLOPT_SOCKOPTDATA 7.16.0 @@ -827,10 +800,10 @@ CURLOPT_SSH_AUTH_TYPES 7.16.1 CURLOPT_SSH_COMPRESSION 7.56.0 CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 7.17.1 CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 7.80.0 -CURLOPT_SSH_HOSTKEYDATA 7.84.0 -CURLOPT_SSH_HOSTKEYFUNCTION 7.84.0 CURLOPT_SSH_KEYDATA 7.19.6 CURLOPT_SSH_KEYFUNCTION 7.19.6 +CURLOPT_SSH_HOSTKEYFUNCTION 7.84.0 +CURLOPT_SSH_HOSTKEYDATA 7.84.0 CURLOPT_SSH_KNOWNHOSTS 7.19.6 CURLOPT_SSH_PRIVATE_KEYFILE 7.16.1 CURLOPT_SSH_PUBLIC_KEYFILE 7.16.1 @@ -839,7 +812,7 @@ CURLOPT_SSL_CTX_DATA 7.10.6 CURLOPT_SSL_CTX_FUNCTION 7.10.6 CURLOPT_SSL_EC_CURVES 7.73.0 CURLOPT_SSL_ENABLE_ALPN 7.36.0 -CURLOPT_SSL_ENABLE_NPN 7.36.0 7.86.0 +CURLOPT_SSL_ENABLE_NPN 7.36.0 CURLOPT_SSL_FALSESTART 7.42.0 CURLOPT_SSL_OPTIONS 7.25.0 CURLOPT_SSL_SESSIONID_CACHE 7.16.0 @@ -866,7 +839,6 @@ CURLOPT_TCP_FASTOPEN 7.49.0 CURLOPT_TCP_KEEPALIVE 7.25.0 CURLOPT_TCP_KEEPIDLE 7.25.0 CURLOPT_TCP_KEEPINTVL 7.25.0 -CURLOPT_TCP_KEEPCNT 8.9.0 CURLOPT_TCP_NODELAY 7.11.2 CURLOPT_TELNETOPTIONS 7.7 CURLOPT_TFTP_BLKSIZE 7.19.4 @@ -886,7 +858,7 @@ CURLOPT_TRANSFER_ENCODING 7.21.6 CURLOPT_TRANSFERTEXT 7.1.1 CURLOPT_UNIX_SOCKET_PATH 7.40.0 CURLOPT_UNRESTRICTED_AUTH 7.10.4 -CURLOPT_UPKEEP_INTERVAL_MS 7.62.0 +CURLOPT_UPKEEP_INTERVAL_MS 7.62.0 CURLOPT_UPLOAD 7.1 CURLOPT_UPLOAD_BUFFERSIZE 7.62.0 CURLOPT_URL 7.1 @@ -900,11 +872,9 @@ CURLOPT_WRITEDATA 7.9.7 CURLOPT_WRITEFUNCTION 7.1 CURLOPT_WRITEHEADER 7.1 CURLOPT_WRITEINFO 7.1 -CURLOPT_WS_OPTIONS 7.86.0 CURLOPT_XFERINFODATA 7.32.0 CURLOPT_XFERINFOFUNCTION 7.32.0 CURLOPT_XOAUTH2_BEARER 7.33.0 -CURLOPTDEPRECATED 7.87.0 CURLOPTTYPE_BLOB 7.71.0 CURLOPTTYPE_CBPOINT 7.73.0 CURLOPTTYPE_FUNCTIONPOINT 7.1 @@ -916,7 +886,6 @@ CURLOPTTYPE_STRINGPOINT 7.46.0 CURLOPTTYPE_VALUES 7.73.0 CURLOT_BLOB 7.73.0 CURLOT_CBPTR 7.73.0 -CURLOT_FLAG_ALIAS 7.73.0 CURLOT_FUNCTION 7.73.0 CURLOT_LONG 7.73.0 CURLOT_OBJECT 7.73.0 @@ -967,7 +936,6 @@ CURLPROTO_TFTP 7.19.4 CURLPROXY_HTTP 7.10 CURLPROXY_HTTP_1_0 7.19.4 CURLPROXY_HTTPS 7.52.0 -CURLPROXY_HTTPS2 8.1.0 CURLPROXY_SOCKS4 7.10 CURLPROXY_SOCKS4A 7.18.0 CURLPROXY_SOCKS5 7.10 @@ -1029,7 +997,6 @@ CURLSSH_AUTH_KEYBOARD 7.16.1 CURLSSH_AUTH_NONE 7.16.1 CURLSSH_AUTH_PASSWORD 7.16.1 CURLSSH_AUTH_PUBLICKEY 7.16.1 -CURLSSLBACKEND_AWSLC 8.1.0 CURLSSLBACKEND_AXTLS 7.38.0 7.61.0 CURLSSLBACKEND_BEARSSL 7.68.0 CURLSSLBACKEND_BORINGSSL 7.49.0 @@ -1055,7 +1022,6 @@ CURLSSLOPT_NATIVE_CA 7.71.0 CURLSSLOPT_NO_PARTIALCHAIN 7.68.0 CURLSSLOPT_NO_REVOKE 7.44.0 CURLSSLOPT_REVOKE_BEST_EFFORT 7.70.0 -CURLSSLOPT_EARLYDATA 8.11.0 CURLSSLSET_NO_BACKENDS 7.56.0 CURLSSLSET_OK 7.56.0 CURLSSLSET_TOO_LATE 7.56.0 @@ -1068,15 +1034,11 @@ CURLU_APPENDQUERY 7.62.0 CURLU_DEFAULT_PORT 7.62.0 CURLU_DEFAULT_SCHEME 7.62.0 CURLU_DISALLOW_USER 7.62.0 -CURLU_GET_EMPTY 8.8.0 CURLU_GUESS_SCHEME 7.62.0 CURLU_NO_AUTHORITY 7.67.0 CURLU_NO_DEFAULT_PORT 7.62.0 -CURLU_NO_GUESS_SCHEME 8.9.0 CURLU_NON_SUPPORT_SCHEME 7.62.0 CURLU_PATH_AS_IS 7.62.0 -CURLU_PUNY2IDN 8.3.0 -CURLU_PUNYCODE 7.88.0 CURLU_URLDECODE 7.62.0 CURLU_URLENCODE 7.62.0 CURLUE_BAD_FILE_URL 7.81.0 @@ -1093,7 +1055,6 @@ CURLUE_BAD_QUERY 7.81.0 CURLUE_BAD_SCHEME 7.81.0 CURLUE_BAD_SLASHES 7.81.0 CURLUE_BAD_USER 7.81.0 -CURLUE_LACKS_IDN 7.88.0 CURLUE_MALFORMED_INPUT 7.62.0 CURLUE_NO_FRAGMENT 7.62.0 CURLUE_NO_HOST 7.62.0 @@ -1106,7 +1067,6 @@ CURLUE_NO_USER 7.62.0 CURLUE_NO_ZONEID 7.81.0 CURLUE_OK 7.62.0 CURLUE_OUT_OF_MEMORY 7.62.0 -CURLUE_TOO_LARGE 8.6.0 CURLUE_UNKNOWN_PART 7.62.0 CURLUE_UNSUPPORTED_SCHEME 7.62.0 CURLUE_URLDECODE 7.62.0 @@ -1127,7 +1087,6 @@ CURLUSESSL_CONTROL 7.17.0 CURLUSESSL_NONE 7.17.0 CURLUSESSL_TRY 7.17.0 CURLVERSION_EIGHTH 7.72.0 -CURLVERSION_ELEVENTH 7.87.0 CURLVERSION_FIFTH 7.57.0 CURLVERSION_FIRST 7.10 CURLVERSION_FOURTH 7.16.1 @@ -1138,20 +1097,3 @@ CURLVERSION_SEVENTH 7.70.0 CURLVERSION_SIXTH 7.66.0 CURLVERSION_TENTH 7.77.0 CURLVERSION_THIRD 7.12.0 -CURLVERSION_TWELFTH 8.8.0 -CURLWARNING 7.66.0 -CURLWS_BINARY 7.86.0 -CURLWS_CLOSE 7.86.0 -CURLWS_CONT 7.86.0 -CURLWS_OFFSET 7.86.0 -CURLWS_PING 7.86.0 -CURLWS_PONG 7.86.0 -CURLWS_RAW_MODE 7.86.0 -CURLWS_TEXT 7.86.0 -LIBCURL_COPYRIGHT 7.18.0 -LIBCURL_TIMESTAMP 7.16.2 -LIBCURL_VERSION 7.11.0 -LIBCURL_VERSION_MAJOR 7.11.0 -LIBCURL_VERSION_MINOR 7.11.0 -LIBCURL_VERSION_NUM 7.11.0 -LIBCURL_VERSION_PATCH 7.11.0 diff --git a/src/library/curl/tools/symbols.R b/src/library/curl/tools/symbols.R index bb4df9f8b7..f518cadb0c 100644 --- a/src/library/curl/tools/symbols.R +++ b/src/library/curl/tools/symbols.R @@ -2,20 +2,21 @@ # Therefore you should only update the symbol table using the latest version of libcurl. # On Mac: 'brew install curl' will install to /usr/local/opt/curl +blacklist <- c("CURL_DID_MEMORY_FUNC_TYPEDEFS", "CURL_STRICTER", "CURL_WIN32", "CURLOPT") + # Function to read a symbol library(inline) getsymbol <- function(name){ - tryCatch({ - fun = cfunction( - cppargs="-I/opt/homebrew/opt/curl/include", - includes = '#include ', - body = paste("return ScalarInteger((int)", name, ");") - ) - val = fun() - rm(fun); gc(); - cat("Found:", name, "=", val, "\n") - return(val) - }, error = function(e){NA_integer_}) + if(name %in% blacklist) return(NA_integer_) + fun = cfunction( + cppargs="-I/usr/local/opt/curl/include", + includes = '#include ', + body = paste("return ScalarInteger((int)", name, ");") + ) + val = fun() + rm(fun); gc(); + cat("Found:", name, "=", val, "\n") + return(val) } # The symbols-in-versions file is included with libcurl diff --git a/src/library/curl/tools/testversion.R b/src/library/curl/tools/testversion.R deleted file mode 100644 index d75128a082..0000000000 --- a/src/library/curl/tools/testversion.R +++ /dev/null @@ -1,3 +0,0 @@ -ver <- libcurlVersion() -cat("Curl runtime version", ver, "\n") -q('no', status = (numeric_version(ver) < commandArgs(TRUE))) diff --git a/src/library/curl/tools/typelist.R b/src/library/curl/tools/typelist.R new file mode 100644 index 0000000000..b2f907e831 --- /dev/null +++ b/src/library/curl/tools/typelist.R @@ -0,0 +1,13 @@ +txt <- readLines('tools/typelist.h.in') +m <- regexpr("CURLOPT_\\w+", txt) +opts <- regmatches(txt, m) +i <- match(opts, curl:::curl_symbol_data$name) +table <- curl:::curl_symbol_data[i, ] +for(i in seq_len(nrow(table))){ + name <- table$name[i] + value <- table$value[i] + txt <- gsub(paste0(name, " "), sprintf("/*%s*/ %d ", name, value), fixed = TRUE, txt) +} +#txt <- gsub('[ ]+\\\\', ' \\\\', txt) +header <- "/* This file is autogenerated from typelist.h.in */" +writeLines(c(header, txt), "src/typelist.h") diff --git a/src/library/curl/tools/typelist.h.in b/src/library/curl/tools/typelist.h.in new file mode 100644 index 0000000000..20be8aa753 --- /dev/null +++ b/src/library/curl/tools/typelist.h.in @@ -0,0 +1,62 @@ +/* if headers are not included, we provide a copy */ +#ifndef CURLINC_TYPECHECK_GCC_H + +/* evaluates to true if option takes a long argument */ +#define curlcheck_long_option(option) \ + (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) + +#ifdef CURLOPTTYPE_BLOB +#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T && (option) < CURLOPTTYPE_BLOB) +#else +#define curlcheck_off_t_option(option) ((option) > CURLOPTTYPE_OFF_T) +#endif + +/* evaluates to true if option takes a data argument to pass to a callback */ +#define curlcheck_cb_data_option(option) \ + ((option) == CURLOPT_CHUNK_DATA || \ + (option) == CURLOPT_CLOSESOCKETDATA || \ + (option) == CURLOPT_DEBUGDATA || \ + (option) == CURLOPT_FNMATCH_DATA || \ + (option) == CURLOPT_HEADERDATA || \ + (option) == CURLOPT_HSTSREADDATA || \ + (option) == CURLOPT_HSTSWRITEDATA || \ + (option) == CURLOPT_INTERLEAVEDATA || \ + (option) == CURLOPT_IOCTLDATA || \ + (option) == CURLOPT_OPENSOCKETDATA || \ + (option) == CURLOPT_PREREQDATA || \ + (option) == CURLOPT_PROGRESSDATA || \ + (option) == CURLOPT_READDATA || \ + (option) == CURLOPT_SEEKDATA || \ + (option) == CURLOPT_SOCKOPTDATA || \ + (option) == CURLOPT_SSH_KEYDATA || \ + (option) == CURLOPT_SSL_CTX_DATA || \ + (option) == CURLOPT_WRITEDATA || \ + (option) == CURLOPT_RESOLVER_START_DATA || \ + (option) == CURLOPT_TRAILERDATA || \ + (option) == CURLOPT_SSH_HOSTKEYDATA || \ + 0) + +/* evaluates to true if option takes a POST data argument (void* or char*) */ +#define curlcheck_postfields_option(option) \ + ((option) == CURLOPT_POSTFIELDS || \ + (option) == CURLOPT_COPYPOSTFIELDS || \ + 0) + +/* evaluates to true if option takes a struct curl_slist * argument */ +#define curlcheck_slist_option(option) \ + ((option) == CURLOPT_HTTP200ALIASES || \ + (option) == CURLOPT_HTTPHEADER || \ + (option) == CURLOPT_MAIL_RCPT || \ + (option) == CURLOPT_POSTQUOTE || \ + (option) == CURLOPT_PREQUOTE || \ + (option) == CURLOPT_PROXYHEADER || \ + (option) == CURLOPT_QUOTE || \ + (option) == CURLOPT_RESOLVE || \ + (option) == CURLOPT_TELNETOPTIONS || \ + (option) == CURLOPT_CONNECT_TO || \ + 0) + +#define curlcheck_string_option(x) \ +(x > 10000 && x < 20000 && !curlcheck_slist_option(x) && !curlcheck_cb_data_option(x)); + +#endif diff --git a/src/library/curl/tools/version.c b/src/library/curl/tools/version.c deleted file mode 100644 index 358aa44320..0000000000 --- a/src/library/curl/tools/version.c +++ /dev/null @@ -1,4 +0,0 @@ -#include -#if LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 73 -#error Local libcurl version is too old -#endif diff --git a/src/library/curl/tools/winlibs.R b/src/library/curl/tools/winlibs.R index 9aedf6da28..03413976bb 100644 --- a/src/library/curl/tools/winlibs.R +++ b/src/library/curl/tools/winlibs.R @@ -1,19 +1,31 @@ -if(!file.exists("curl.o") && !file.exists("../.deps/libcurl/include/curl/curl.h")){ - unlink("../.deps", recursive = TRUE) +if(!file.exists("../windows/libcurl/include/curl/curl.h")){ + unlink("../windows", recursive = TRUE) url <- if(grepl("aarch", R.version$platform)){ - "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-clang-aarch64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-clang-aarch64.tar.xz" } else if(grepl("clang", Sys.getenv('R_COMPILED_BY'))){ - "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-clang-x86_64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-clang-x86_64.tar.xz" } else if(getRversion() >= "4.2") { - "https://github.com/r-windows/bundles/releases/download/curl-8.14.1/curl-8.14.1-ucrt-x86_64.tar.xz" + "https://github.com/r-windows/bundles/releases/download/curl-8.3.0/curl-8.3.0-ucrt-x86_64.tar.xz" } else { "https://github.com/rwinlib/libcurl/archive/v7.84.0.tar.gz" } download.file(url, basename(url), quiet = TRUE) - dir.create("../.deps", showWarnings = FALSE) - untar(basename(url), exdir = "../.deps", tar = 'internal') + dir.create("../windows", showWarnings = FALSE) + untar(basename(url), exdir = "../windows", tar = 'internal') unlink(basename(url)) - setwd("../.deps") + setwd("../windows") file.rename(list.files(), 'libcurl') - invisible() + # fix CR or CRLF line endings in a header file + badfile <- "libcurl/include/nghttp2/nghttp2ver.h" + if (file.exists(badfile)) { + cnts <- readBin(badfile, "raw", file.size(badfile)) + if (any(cnts == 0x0a)) { + # has \r\n, remove the \r + cnts <- cnts[cnts != 0x0d] + } else { + # convert \r to \r + cnts[cnts == 0x0d] <- as.raw(0x0a) + } + writeBin(cnts, badfile) + } } diff --git a/src/library/curl/vignettes/intro.Rmd b/src/library/curl/vignettes/intro.Rmd deleted file mode 100644 index f91781169e..0000000000 --- a/src/library/curl/vignettes/intro.Rmd +++ /dev/null @@ -1,406 +0,0 @@ ---- -title: "The curl package: a modern R interface to libcurl" -date: "`r Sys.Date()`" -output: - html_document: - fig_caption: false - toc: true - toc_float: - collapsed: false - smooth_scroll: false - toc_depth: 3 -vignette: > - %\VignetteIndexEntry{The curl package: a modern R interface to libcurl} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - - -```{r, echo = FALSE, message = FALSE} -knitr::opts_chunk$set(comment = "") -options(width = 120, max.print = 100) -wrap.simpleError <- function(x, options) { - paste0("```\n## Error: ", x$message, "\n```") -} -library(curl) -library(jsonlite) -``` - -The curl package provides bindings to the [libcurl](https://curl.se/libcurl/) C library for R. The package supports retrieving data in-memory, downloading to disk, or streaming using the [R "connection" interface](https://stat.ethz.ch/R-manual/R-devel/library/base/html/connections.html). Some knowledge of curl is recommended to use this package. For a more user-friendly HTTP client, have a look at the [httr](https://cran.r-project.org/package=httr/vignettes/quickstart.html) package which builds on curl with HTTP specific tools and logic. - -## Request interfaces - -The curl package implements several interfaces to retrieve data from a URL: - - - `curl_fetch_memory()` saves response in memory - - `curl_download()` or `curl_fetch_disk()` writes response to disk - - `curl()` or `curl_fetch_stream()` streams response data - - `curl_fetch_multi()` (Advanced) process responses via callback functions - -Each interface performs the same HTTP request, they only differ in how response data is processed. - -### Getting in memory - -The `curl_fetch_memory` function is a blocking interface which waits for the request to complete and returns a list with all content (data, headers, status, timings) of the server response. - - -```{r} -req <- curl_fetch_memory("https://hb.cran.dev/get?foo=123") -str(req) -parse_headers(req$headers) -jsonlite::prettify(rawToChar(req$content)) -``` - -The `curl_fetch_memory` interface is the easiest interface and most powerful for building API clients. However it is not suitable for downloading really large files because it is fully in-memory. If you are expecting 100G of data, you probably need one of the other interfaces. - -### Downloading to disk - -The second method is `curl_download`, which has been designed as a drop-in replacement for `download.file` in r-base. It writes the response straight to disk, which is useful for downloading (large) files. - -```{r} -tmp <- tempfile() -curl_download("https://hb.cran.dev/get?bar=456", tmp) -jsonlite::prettify(readLines(tmp)) -``` - -### Streaming data - -The most flexible interface is the `curl` function, which has been designed as a drop-in replacement for base `url`. It will create a so-called connection object, which allows for incremental (asynchronous) reading of the response. - -```{r} -con <- curl("https://hb.cran.dev/get") -open(con) - -# Get 3 lines -out <- readLines(con, n = 3) -cat(out, sep = "\n") - -# Get 3 more lines -out <- readLines(con, n = 3) -cat(out, sep = "\n") - -# Get remaining lines -out <- readLines(con) -close(con) -cat(out, sep = "\n") -``` - -The example shows how to use `readLines` on an opened connection to read `n` lines at a time. Similarly `readBin` is used to read `n` bytes at a time for stream parsing binary data. - -#### Non blocking connections - -As of version 2.3 it is also possible to open connections in non-blocking mode. In this case `readBin` and `readLines` will return immediately with data that is available without waiting. For non-blocking connections we use `isIncomplete` to check if the download has completed yet. - -```{r, eval=FALSE} -# This httpbin mirror doesn't cache -con <- curl("https://nghttp2.org/httpbin/drip?duration=1&numbytes=50") -open(con, "rb", blocking = FALSE) -while(isIncomplete(con)){ - buf <- readBin(con, raw(), 1024) - if(length(buf)) - cat("received: ", rawToChar(buf), "\n") -} -close(con) -``` - -The `curl_fetch_stream` function provides a very simple wrapper around a non-blocking connection. - - -### Async requests - -As of `curl 2.0` the package provides an async interface which can perform multiple simultaneous requests concurrently. The `curl_fetch_multi` adds a request to a pool and returns immediately; it does not actually perform the request. - -```{r} -pool <- new_pool() -success <- function(req){cat("success:", req$url, ": HTTP:", req$status, "\n")} -failure <- function(err){cat("failure:", err, "\n")} -curl_fetch_multi('https://www.google.com', done = success, fail = failure, pool = pool) -curl_fetch_multi('https://cloud.r-project.org', done = success, fail = failure, pool = pool) -curl_fetch_multi('https://hb.cran.dev/blabla', done = success, fail = failure, pool = pool) -curl_fetch_multi('https://doesnotexit.xyz', done = success, fail = failure, pool = pool) -``` - -When we call `multi_run()`, all scheduled requests are performed concurrently. The callback functions get triggered when each request completes. - -```{r} -# This actually performs requests: -out <- multi_run(pool = pool) -print(out) -``` - -The system allows for running many concurrent non-blocking requests. However it is quite complex and requires careful specification of handler functions. - -## Exception handling - -A HTTP requests can encounter two types of errors: - - 1. Connection failure: network down, host not found, invalid SSL certificate, etc - 2. HTTP non-success status: 401 (DENIED), 404 (NOT FOUND), 503 (SERVER PROBLEM), etc - -The first type of errors (connection failures) will always raise an error in R for each interface. However if the requests succeeds and the server returns a non-success HTTP status code, only `curl()` and `curl_download()` will raise an error. Let's dive a little deeper into this. - -### Error automatically - -The `curl` and `curl_download` functions are safest to use because they automatically raise an error if the request was completed but the server returned a non-success (400 or higher) HTTP status. This mimics behavior of base functions `url` and `download.file`. Therefore we can safely write code like this: - -```{r} -# This is OK -curl_download('https://cloud.r-project.org/CRAN_mirrors.csv', 'mirrors.csv') -mirros <- read.csv('mirrors.csv') -unlink('mirrors.csv') -``` - -If the HTTP request was unsuccessful, R will not continue: - -```{r, error=TRUE} -# Oops! A typo in the URL! -curl_download('https://cloud.r-project.org/CRAN_mirrorZ.csv', 'mirrors.csv') -con <- curl('https://cloud.r-project.org/CRAN_mirrorZ.csv') -open(con) -``` - -```{r, echo = FALSE, message = FALSE, warning=FALSE} -close(con) -rm(con) -``` - - -### Check manually - -When using any of the `curl_fetch_*` functions it is important to realize that these do **not** raise an error if the request was completed but returned a non-200 status code. When using `curl_fetch_memory` or `curl_fetch_disk` you need to implement such application logic yourself and check if the response was successful. - -```{r} -req <- curl_fetch_memory('https://cloud.r-project.org/CRAN_mirrors.csv') -print(req$status_code) -``` - -Same for downloading to disk. If you do not check your status, you might have downloaded an error page! - -```{r} -# Oops a typo! -req <- curl_fetch_disk('https://cloud.r-project.org/CRAN_mirrorZ.csv', 'mirrors.csv') -print(req$status_code) - -# This is not the CSV file we were expecting! -head(readLines('mirrors.csv')) -unlink('mirrors.csv') -``` - -If you *do* want the `curl_fetch_*` functions to automatically raise an error, you should set the [`FAILONERROR`](https://curl.se/libcurl/c/CURLOPT_FAILONERROR.html) option to `TRUE` in the handle of the request. - -```{r, error=TRUE} -h <- new_handle(failonerror = TRUE) -curl_fetch_memory('https://cloud.r-project.org/CRAN_mirrorZ.csv', handle = h) -``` - -## Customizing requests - -By default libcurl uses HTTP GET to issue a request to an HTTP url. To send a customized request, we first need to create and configure a curl handle object that is passed to the specific download interface. - -### Setting handle options - -Creating a new handle is done using `new_handle`. After creating a handle object, we can set the libcurl options and http request headers. - -```{r} -h <- new_handle() -handle_setopt(h, copypostfields = "moo=moomooo"); -handle_setheaders(h, - "Content-Type" = "text/moo", - "Cache-Control" = "no-cache", - "User-Agent" = "A cow" -) -``` - -Use the `curl_options()` function to get a list of the options supported by your version of libcurl. The [libcurl documentation](https://curl.se/libcurl/c/curl_easy_setopt.html) explains what each option does. Option names are not case sensitive. - -It is important you check the [libcurl documentation](https://curl.se/libcurl/c/curl_easy_setopt.html) to set options of the correct type. Options in libcurl take several types: - - - number - - string - - slist (vector of strings) - - enum (long) option - -The R bindings will automatically do some type checking and coercion to convert R values to appropriate libcurl option values. Logical (boolean) values in R automatically get converted to `0` or `1` for example [CURLOPT_VERBOSE](https://curl.se/libcurl/c/CURLOPT_VERBOSE.html): - - -```{r} -handle <- new_handle(verbose = TRUE) -``` - -However R does not know if an option is actually boolean. So passing `TRUE`/ `FALSE` to any numeric option will simply set it to `0` or `1` without a warning or error. If an option value cannot be coerced, you get an error: - -```{r, error = TRUE} -# URLOPT_MASFILESIZE must be a number -handle_setopt(handle, maxfilesize = "foo") - -# CURLOPT_USERAGENT must be a string -handle_setopt(handle, useragent = 12345) -``` - - -### ENUM (long) options - -Some curl options take an long in C that actually corresponds to an ENUM value. - -For example the [CURLOPT_USE_SSL](https://curl.se/libcurl/c/CURLOPT_USE_SSL.html) docs explains that there are 4 possible values for this option: `CURLUSESSL_NONE`, `CURLUSESSL_TRY`, `CURLUSESSL_CONTROL`, and `CURLUSESSL_ALL`. To use this option you have to lookup the integer values for these enums in the symbol table. These symbol values never change, so you only need to lookup the value you need once and then hardcode the integer value in your R code. - -```{r} -curl::curl_symbols("CURLUSESSL") -``` - -So suppose we want to set `CURLOPT_USE_SSL` to `CURLUSESSL_ALL` we would use this R code: - -```{r} -handle_setopt(handle, use_ssl = 3) -``` - -### Disabling HTTP/2 - -Another example is the [CURLOPT_HTTP_VERSION](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html) option. This option is needed to disable or enable HTTP/2. However some users are not aware this is actually an ENUM and not a regular numeric value! - -The [docs](https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html) explain HTTP_VERSION can be set to one of several strategies for negotiating the HTTP version between client and server. Valid values are: - -```{r} -curl_symbols('CURL_HTTP_VERSION_') -``` - -As seen, the value `2` corresponds to `CURL_HTTP_VERSION_1_1` and `3` corresponds to `CURL_HTTP_VERSION_2_0`. - -As of libcurl 7.62.0, the default `http_version` is `CURL_HTTP_VERSION_2TLS` which uses HTTP/2 when possible, but only for HTTPS connections. Package authors should usually leave the default to let curl select the best appropriate http protocol. - -One exception is when writing a client for a server that seems to be running a buggy HTTP/2 server. Unfortunately this is not uncommon, and curl is a bit more picky than browsers. If you are frequently seeing `Error in the HTTP2 framing layer` error messages, then there is likely a problem with the HTTP/2 layer on the server. - -The easiest remedy is to __disable http/2__ for this server by forcing http 1.1 until the service has upgraded their webservers. To do so, set the `http_version` to `CURL_HTTP_VERSION_1_1` (value: `2`): - -```{r} -# Force using HTTP 1.1 (the number 2 is an enum value, see above) -handle_setopt(handle, http_version = 2) -``` - -Note that the value `1` corresponds to HTTP 1.0 which is a legacy version of HTTP that you should not use! -Code that sets `http_version` to `1` (or even `1.1` which R simply rounds to 1) is almost always a bug. - -## Performing the request - -After the handle has been configured, it can be used with any of the download interfaces to perform the request. For example `curl_fetch_memory` will load store the output of the request in memory: - -```{r} -req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) -jsonlite::prettify(rawToChar(req$content)) -``` - -Alternatively we can use `curl()` to read the data of via a connection interface: - -```{r} -con <- curl("https://hb.cran.dev/post", handle = h) -jsonlite::prettify(readLines(con)) -``` - -```{r, echo = FALSE, message = FALSE, warning=FALSE} -close(con) -``` - -Or we can use `curl_download` to write the response to disk: - -```{r} -tmp <- tempfile() -curl_download("https://hb.cran.dev/post", destfile = tmp, handle = h) -jsonlite::prettify(readLines(tmp)) -``` - -Or perform the same request with a multi pool: - -```{r} -curl_fetch_multi("https://hb.cran.dev/post", handle = h, done = function(res){ - cat("Request complete! Response content:\n") - cat(rawToChar(res$content)) -}) - -# Perform the request -out <- multi_run() -``` - -### Reading cookies - -Curl handles automatically keep track of cookies set by the server. At any given point we can use `handle_cookies` to see a list of current cookies in the handle. - -```{r} -# Start with a fresh handle -h <- new_handle() - -# Ask server to set some cookies -req <- curl_fetch_memory("https://hb.cran.dev/cookies/set?foo=123&bar=ftw", handle = h) -req <- curl_fetch_memory("https://hb.cran.dev/cookies/set?baz=moooo", handle = h) -handle_cookies(h) - -# Unset a cookie -req <- curl_fetch_memory("https://hb.cran.dev/cookies/delete?foo", handle = h) -handle_cookies(h) -``` - -The `handle_cookies` function returns a data frame with 7 columns as specified in the [netscape cookie file format](http://www.cookiecentral.com/faq/#3.5). - -### On reusing handles - -In most cases you should not re-use a single handle object for more than one request. The only benefit of reusing a handle for multiple requests is to keep track of cookies set by the server (seen above). This could be needed if your server uses session cookies, but this is rare these days. Most APIs set state explicitly via http headers or parameters, rather than implicitly via cookies. - -In recent versions of the curl package there are no performance benefits of reusing handles. The overhead of creating and configuring a new handle object is negligible. The safest way to issue multiple requests, either to a single server or multiple servers is by using a separate handle for each request (which is the default) - -```{r} -req1 <- curl_fetch_memory("https://hb.cran.dev/get") -req2 <- curl_fetch_memory("https://www.google.com") -``` - -In past versions of this package you needed to manually use a handle to take advantage of http Keep-Alive. However as of version 2.3 this is no longer the case: curl automatically maintains global a pool of open http connections shared by all handles. When performing many requests to the same server, curl automatically uses existing connections when possible, eliminating TCP/SSL handshaking overhead: - -```{r} -req <- curl_fetch_memory("https://api.github.com/users/ropensci") -req$times - -req2 <- curl_fetch_memory("https://api.github.com/users/rstudio") -req2$times -``` - -If you really need to re-use a handle, do note that that curl does not cleanup the handle after each request. All of the options and internal fields will linger around for all future request until explicitly reset or overwritten. This can sometimes leads to unexpected behavior. - -```{r} -handle_reset(h) -``` - -The `handle_reset` function will reset all curl options and request headers to the default values. It will **not** erase cookies and it will still keep alive the connections. Therefore it is good practice to call `handle_reset` after performing a request if you want to reuse the handle for a subsequent request. Still it is always safer to create a new fresh handle when possible, rather than recycling old ones. - -### Posting forms - -The `handle_setform` function is used to perform a `multipart/form-data` HTTP POST request (a.k.a. posting a form). Values can be either strings, raw vectors (for binary data) or files. - -```{r} -# Posting multipart -h <- new_handle() -handle_setform(h, - foo = "blabla", - bar = charToRaw("boeboe"), - iris = form_data(serialize(iris, NULL), "application/rda"), - description = form_file(system.file("DESCRIPTION")), - logo = form_file(file.path(R.home('doc'), "html/logo.jpg"), "image/jpeg") -) -req <- curl_fetch_memory("https://hb.cran.dev/post", handle = h) -``` - -The `form_file` function is used to upload files with the form post. It has two arguments: a file path, and optionally a content-type value. If no content-type is set, curl will guess the content type of the file based on the file extension. - -The `form_data` function is similar but simply posts a string or raw value with a custom content-type. - -### Using pipes - -All of the `handle_xxx` functions return the handle object so that function calls can be chained using the popular pipe operators: - -```{r eval=getRversion() > "4.1"} -# Perform request -res <- new_handle() |> - handle_setopt(copypostfields = "moo=moomooo") |> - handle_setheaders("Content-Type"="text/moo", "Cache-Control"="no-cache", "User-Agent"="A cow") |> - curl_fetch_memory(url = "https://hb.cran.dev/post") - -# Parse response -res$content |> rawToChar() |> jsonlite::prettify() -``` diff --git a/src/library/curl/vignettes/windows.Rmd b/src/library/curl/vignettes/windows.Rmd index b358f328a8..6e5a9fb278 100644 --- a/src/library/curl/vignettes/windows.Rmd +++ b/src/library/curl/vignettes/windows.Rmd @@ -46,13 +46,13 @@ Have a look at `curl::curl_version()` to see which ssl backends are available an ```r curl::curl_version() #> $version -#> [1] "8.8.0" -#> -#> $headers -#> [1] "8.8.0" +#> [1] "7.64.1" #> #> $ssl_version -#> [1] "(OpenSSL/3.3.0) Schannel" +#> [1] "(OpenSSL/1.1.1a) Schannel" +#> +#> $libz_version +#> [1] "1.2.8" #> ... ``` @@ -67,8 +67,8 @@ write('CURL_SSL_BACKEND=openssl', file = "~/.Renviron", append = TRUE) Now if you restart R, the default back-end should have changed: ```r -curl::curl_version()$ssl_version -#> [1] "OpenSSL/3.3.0 (Schannel)" +> curl::curl_version()$ssl_version +[1] "OpenSSL/1.1.1m (Schannel)" ``` Optionally, you can also set `CURL_CA_BUNDLE` in your `~/.Renviron` to use a custom trust bundle. If `CURL_CA_BUNDLE` is not set, we use the Windows cert store. When using Schannel, no trust bundle can be specified because we always use the certificates from the native Windows cert store. From 95c59a14c40c7858af9a9bbb2c4f96c75f71f514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 8 May 2026 15:05:20 +0200 Subject: [PATCH 12/25] Update embedded pkgcache To avoid using openssl. --- src/library/pkgcache/DESCRIPTION | 8 +-- src/library/pkgcache/R/ppm-sso-app.R | 2 +- src/library/pkgcache/R/ppm-sso.R | 16 ++++-- src/library/pkgcache/src/init.c | 2 + src/library/pkgcache/src/pkgcache.h | 2 + src/library/pkgcache/src/rand.c | 85 ++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 src/library/pkgcache/src/rand.c diff --git a/src/library/pkgcache/DESCRIPTION b/src/library/pkgcache/DESCRIPTION index 86bd766c01..e7338e48ec 100644 --- a/src/library/pkgcache/DESCRIPTION +++ b/src/library/pkgcache/DESCRIPTION @@ -16,9 +16,9 @@ BugReports: https://github.com/r-lib/pkgcache/issues Depends: R (>= 3.4) Imports: callr (>= 2.0.4.9000), cli (>= 3.2.0), curl (>= 3.2), filelock, jsonlite, processx (>= 3.3.0.9001), R6, tools, utils -Suggests: covr, debugme, desc, fs, keyring, openssl, pillar, pingr, - RcppTOML, rprojroot, sessioninfo, spelling, testthat (>= - 3.2.0), webfakes (>= 1.1.5), withr, zip +Suggests: covr, debugme, desc, fs, keyring, pillar, pingr, RcppTOML, + rprojroot, sessioninfo, spelling, testthat (>= 3.2.0), webfakes + (>= 1.1.5), withr, zip Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 Config/usethis/last-upkeep: 2025-04-30 @@ -27,7 +27,7 @@ Language: en-US Roxygen: list(markdown = TRUE, r6 = FALSE) RoxygenNote: 7.3.2.9000 NeedsCompilation: yes -Packaged: 2026-05-08 12:30:29 UTC; gaborcsardi +Packaged: 2026-05-08 13:04:59 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Posit Software, PBC [cph, fnd] (ROR: ) Maintainer: Gábor Csárdi diff --git a/src/library/pkgcache/R/ppm-sso-app.R b/src/library/pkgcache/R/ppm-sso-app.R index 351c0b6f7e..79b2f78074 100644 --- a/src/library/pkgcache/R/ppm-sso-app.R +++ b/src/library/pkgcache/R/ppm-sso-app.R @@ -84,7 +84,7 @@ ppm_sso_app <- function( envir = app$locals$challenges, inherits = FALSE ) - actual <- ppm_sso_base64url_encode(openssl::sha256(charToRaw(verifier))) + actual <- ppm_sso_base64url_encode(ppm_sso_sha256_raw(verifier)) if (!identical(expected, actual)) { return(res$set_status(400L)$send_json( auto_unbox = TRUE, diff --git a/src/library/pkgcache/R/ppm-sso.R b/src/library/pkgcache/R/ppm-sso.R index 6e3daa20bb..a7eb13fe20 100644 --- a/src/library/pkgcache/R/ppm-sso.R +++ b/src/library/pkgcache/R/ppm-sso.R @@ -263,18 +263,26 @@ ppm_sso_write_token_to_file <- function(token) { } ppm_sso_base64url_encode <- function(x) { - encoded <- openssl::base64_encode(x) + encoded <- processx::base64_encode(x) # Make it URL-safe gsub("\\+", "-", gsub("\\/", "_", gsub("=+$", "", encoded))) } +ppm_sso_hex_to_raw <- function(s) { + n <- nchar(s) + as.raw(strtoi(substring(s, seq(1L, n, 2L), seq(2L, n, 2L)), 16L)) +} + +ppm_sso_sha256_raw <- function(x) { + ppm_sso_hex_to_raw(cli::hash_sha256(x)) +} + ppm_sso_new_pkce_verifier <- function() { - ppm_sso_base64url_encode(openssl::rand_bytes(32)) + ppm_sso_base64url_encode(.Call(pkgcache_rand_bytes, 32L)) } ppm_sso_new_pkce_challenge <- function(verifier) { - hash <- openssl::sha256(charToRaw(verifier)) - ppm_sso_base64url_encode(hash) + ppm_sso_base64url_encode(ppm_sso_sha256_raw(verifier)) } ppm_sso_complete_device_auth = function( diff --git a/src/library/pkgcache/src/init.c b/src/library/pkgcache/src/init.c index 97944f830c..9334b0ab03 100644 --- a/src/library/pkgcache/src/init.c +++ b/src/library/pkgcache/src/init.c @@ -30,6 +30,8 @@ static const R_CallMethodDef callMethods[] = { REG(pkgcache_parse_packages_raw, 1), REG(pkgcache_graphics_api_version, 0), + REG(pkgcache_rand_bytes, 1), + REG(pkgcache__gcov_flush, 0), { NULL, NULL, 0 } }; diff --git a/src/library/pkgcache/src/pkgcache.h b/src/library/pkgcache/src/pkgcache.h index de0922f5ec..05c0a16954 100644 --- a/src/library/pkgcache/src/pkgcache.h +++ b/src/library/pkgcache/src/pkgcache.h @@ -12,3 +12,5 @@ SEXP pkgcache_parse_descriptions(SEXP paths, SEXP lowercase); SEXP pkgcache_parse_packages_raw(SEXP raw); SEXP pkgcache_graphics_api_version(void); + +SEXP pkgcache_rand_bytes(SEXP n); diff --git a/src/library/pkgcache/src/rand.c b/src/library/pkgcache/src/rand.c new file mode 100644 index 0000000000..f3b67e811e --- /dev/null +++ b/src/library/pkgcache/src/rand.c @@ -0,0 +1,85 @@ +#include "pkgcache.h" + +#include + +#if defined(_WIN32) +# include +# define RtlGenRandom SystemFunction036 +# ifdef __cplusplus +extern "C" +# endif +BOOLEAN NTAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength); +# pragma comment(lib, "advapi32.lib") +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) +# include +#else +# include +# include +# include +# if defined(__linux__) +# include +# endif +#endif + +SEXP pkgcache_rand_bytes(SEXP n) { + int size = Rf_asInteger(n); + if (size == NA_INTEGER || size < 0) { + Rf_error("Invalid number of random bytes requested"); + } + SEXP res = PROTECT(Rf_allocVector(RAWSXP, size)); + if (size == 0) { + UNPROTECT(1); + return res; + } + unsigned char *buf = RAW(res); + +#if defined(_WIN32) + if (!RtlGenRandom((PVOID) buf, (ULONG) size)) { + Rf_error("Failed to obtain random bytes from RtlGenRandom"); + } + +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__DragonFly__) + arc4random_buf(buf, (size_t) size); + +#else + size_t off = 0; +# if defined(__linux__) && defined(SYS_getrandom) + while (off < (size_t) size) { + long r = syscall(SYS_getrandom, buf + off, (size_t) size - off, 0); + if (r > 0) { + off += (size_t) r; + } else if (r < 0 && (errno == EINTR || errno == EAGAIN)) { + continue; + } else { + break; /* fall through to /dev/urandom */ + } + } +# endif + if (off < (size_t) size) { + int fd; + do { + fd = open("/dev/urandom", O_RDONLY); + } while (fd < 0 && errno == EINTR); + if (fd < 0) { + Rf_error("Failed to open /dev/urandom: %s", strerror(errno)); + } + while (off < (size_t) size) { + ssize_t r = read(fd, buf + off, (size_t) size - off); + if (r > 0) { + off += (size_t) r; + } else if (r < 0 && errno == EINTR) { + continue; + } else { + close(fd); + Rf_error("Failed to read from /dev/urandom: %s", strerror(errno)); + } + } + close(fd); + } +#endif + + UNPROTECT(1); + return res; +} From 855f37596e394bae2eb126f3c424728fb59d0327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Sat, 9 May 2026 12:46:12 +0200 Subject: [PATCH 13/25] GHA: update action versions --- .github/workflows/R-CMD-check.yaml | 2 +- .../workflows/linux-builder-containers.yaml | 8 +++---- .github/workflows/nightly.yaml | 22 +++++++++---------- .github/workflows/pkgdown.yaml | 4 ++-- .github/workflows/pr-commands.yaml | 4 ++-- .github/workflows/test-coverage.yaml | 6 ++--- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 55dd03e4cc..597a0cc30a 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -42,7 +42,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 diff --git a/.github/workflows/linux-builder-containers.yaml b/.github/workflows/linux-builder-containers.yaml index 9f8bad1a08..ced0a740dd 100644 --- a/.github/workflows/linux-builder-containers.yaml +++ b/.github/workflows/linux-builder-containers.yaml @@ -30,10 +30,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -69,10 +69,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 5e4ec4a7cc..656820fb11 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -98,7 +98,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -155,7 +155,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -220,7 +220,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -291,7 +291,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -325,13 +325,13 @@ jobs: steps: - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -364,7 +364,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -412,7 +412,7 @@ jobs: sudo apt-get update - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 10 @@ -439,7 +439,7 @@ jobs: - name: Deploy to GitHub pages (test) if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@v4.8.0 with: repository-name: r-lib/r-lib.github.io token: ${{ secrets.PAK_GHCR_TOKEN }} @@ -457,7 +457,7 @@ jobs: - name: Deploy to GitHub Pages (prod) if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@v4.8.0 with: repository-name: r-lib/r-lib.github.io token: ${{ secrets.PAK_GHCR_TOKEN }} diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index f25e128acb..8267c25c50 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -23,7 +23,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-pandoc@v2 @@ -54,7 +54,7 @@ jobs: - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.5.0 + uses: JamesIves/github-pages-deploy-action@v4.8.0 with: clean: false branch: gh-pages diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 2edd93f27e..7b9913ea6f 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -18,7 +18,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/pr-fetch@v2 with: @@ -57,7 +57,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/pr-fetch@v2 with: diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 0ab748d657..a3700e41c6 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -16,7 +16,7 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: r-lib/actions/setup-r@v2 with: @@ -38,7 +38,7 @@ jobs: covr::to_cobertura(cov) shell: Rscript {0} - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v6 with: # Fail if error if not on PR, or if on PR and token is given fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} @@ -56,7 +56,7 @@ jobs: - name: Upload test results if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-test-failures path: ${{ runner.temp }}/package From c96e74d213f0ec5840ae1d95b6706e533f26bc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Wed, 13 May 2026 09:41:31 +0200 Subject: [PATCH 14/25] Embed ts and tstoml --- DESCRIPTION | 2 + R/embed.R | 1 + inst/COPYRIGHTS | 12 + src/dummy/ts/DESCRIPTION | 2 + src/dummy/tstoml/DESCRIPTION | 2 + src/install-embedded.R | 5 +- src/library/ts/DESCRIPTION | 32 + src/library/ts/LICENSE | 2 + src/library/ts/LICENSE.note | 24 + src/library/ts/NAMESPACE | 58 + src/library/ts/NEWS.md | 3 + src/library/ts/R/bracket-ts-tree.R | 96 + src/library/ts/R/cleancall.R | 3 + src/library/ts/R/coerce.R | 36 + src/library/ts/R/collapse.R | 193 + src/library/ts/R/compat-vctrs.R | 643 +++ src/library/ts/R/docs.R | 315 ++ src/library/ts/R/error.R | 283 + src/library/ts/R/friendly-type.R | 184 + src/library/ts/R/glue.R | 37 + src/library/ts/R/print.R | 318 ++ src/library/ts/R/ts-list-parsers.R | 250 + src/library/ts/R/ts-package.R | 43 + src/library/ts/R/ts-tree-ast.R | 134 + src/library/ts/R/ts-tree-delete.R | 79 + src/library/ts/R/ts-tree-dom.R | 98 + src/library/ts/R/ts-tree-format.R | 82 + src/library/ts/R/ts-tree-insert.R | 94 + src/library/ts/R/ts-tree-new.R | 167 + src/library/ts/R/ts-tree-query.R | 103 + src/library/ts/R/ts-tree-select.R | 816 +++ src/library/ts/R/ts-tree-sexpr.R | 52 + src/library/ts/R/ts-tree-unserialize.R | 61 + src/library/ts/R/ts-tree-update.R | 76 + src/library/ts/R/ts-tree-write.R | 86 + src/library/ts/R/utils.R | 71 + src/library/ts/cleanup | 3 + src/library/ts/configure | 19 + src/library/ts/configure.win | 3 + src/library/ts/man/macros/eval.Rd | 1 + src/library/ts/man/roxygen/meta.R | 34 + src/library/ts/src/Makevars | 28 + src/library/ts/src/cleancall.c | 169 + src/library/ts/src/cleancall.h | 54 + src/library/ts/src/glue.c | 359 ++ src/library/ts/src/init.c | 29 + src/library/ts/src/tree-sitter.c | 734 +++ .../tree-sitter/lib/include/tree_sitter/api.h | 1423 +++++ .../ts/src/tree-sitter/lib/src/alloc.c | 48 + .../ts/src/tree-sitter/lib/src/alloc.h | 41 + .../ts/src/tree-sitter/lib/src/array.h | 291 + .../ts/src/tree-sitter/lib/src/atomic.h | 68 + .../ts/src/tree-sitter/lib/src/clock.h | 146 + .../ts/src/tree-sitter/lib/src/error_costs.h | 11 + .../tree-sitter/lib/src/get_changed_ranges.c | 557 ++ .../tree-sitter/lib/src/get_changed_ranges.h | 36 + src/library/ts/src/tree-sitter/lib/src/host.h | 21 + .../ts/src/tree-sitter/lib/src/language.c | 289 + .../ts/src/tree-sitter/lib/src/language.h | 293 + .../ts/src/tree-sitter/lib/src/length.h | 52 + .../ts/src/tree-sitter/lib/src/lexer.c | 483 ++ .../ts/src/tree-sitter/lib/src/lexer.h | 54 + src/library/ts/src/tree-sitter/lib/src/lib.c | 13 + src/library/ts/src/tree-sitter/lib/src/node.c | 869 +++ .../ts/src/tree-sitter/lib/src/parser.c | 2263 ++++++++ .../ts/src/tree-sitter/lib/src/parser.h | 286 + .../ts/src/tree-sitter/lib/src/point.c | 17 + .../ts/src/tree-sitter/lib/src/point.h | 48 + .../src/tree-sitter/lib/src/portable/endian.h | 241 + .../ts/src/tree-sitter/lib/src/query.c | 4384 +++++++++++++++ .../src/tree-sitter/lib/src/reduce_action.h | 34 + .../src/tree-sitter/lib/src/reusable_node.h | 95 + .../ts/src/tree-sitter/lib/src/stack.c | 912 ++++ .../ts/src/tree-sitter/lib/src/stack.h | 133 + .../ts/src/tree-sitter/lib/src/subtree.c | 1036 ++++ .../ts/src/tree-sitter/lib/src/subtree.h | 400 ++ src/library/ts/src/tree-sitter/lib/src/tree.c | 140 + src/library/ts/src/tree-sitter/lib/src/tree.h | 31 + .../ts/src/tree-sitter/lib/src/tree_cursor.c | 716 +++ .../ts/src/tree-sitter/lib/src/tree_cursor.h | 48 + .../ts/src/tree-sitter/lib/src/ts_assert.h | 11 + .../ts/src/tree-sitter/lib/src/unicode.h | 75 + .../src/tree-sitter/lib/src/unicode/ICU_SHA | 1 + .../src/tree-sitter/lib/src/unicode/LICENSE | 414 ++ .../src/tree-sitter/lib/src/unicode/README.md | 29 + .../src/tree-sitter/lib/src/unicode/ptypes.h | 1 + .../tree-sitter/lib/src/unicode/umachine.h | 448 ++ .../src/tree-sitter/lib/src/unicode/urename.h | 1 + .../ts/src/tree-sitter/lib/src/unicode/utf.h | 1 + .../src/tree-sitter/lib/src/unicode/utf16.h | 733 +++ .../ts/src/tree-sitter/lib/src/unicode/utf8.h | 881 +++ .../lib/src/wasm/stdlib-symbols.txt | 24 + .../ts/src/tree-sitter/lib/src/wasm/stdlib.c | 113 + .../tree-sitter/lib/src/wasm/wasm-stdlib.h | 1209 ++++ .../ts/src/tree-sitter/lib/src/wasm_store.c | 1937 +++++++ .../ts/src/tree-sitter/lib/src/wasm_store.h | 31 + src/library/ts/tools/ci/Rprofile | 7 + src/library/ts/tools/dynamic-help.R | 5 + src/library/ts/tools/examples.R | 15 + src/library/ts/tools/man/about.Rmd | 203 + src/library/tstoml/DESCRIPTION | 31 + src/library/tstoml/LICENSE | 2 + src/library/tstoml/NAMESPACE | 27 + src/library/tstoml/R/checks.R | 259 + src/library/tstoml/R/compat-vctrs.R | 643 +++ src/library/tstoml/R/friendly-type.R | 184 + src/library/tstoml/R/parsedate-package.R | 151 + src/library/tstoml/R/rematch2.R | 37 + src/library/tstoml/R/ts-format-toml.R | 28 + src/library/tstoml/R/ts-language-toml.R | 6 + src/library/tstoml/R/ts-parse-toml.R | 44 + src/library/tstoml/R/ts-serialize-toml.R | 445 ++ src/library/tstoml/R/ts-tree-ast.R | 24 + src/library/tstoml/R/ts-tree-delete.R | 195 + src/library/tstoml/R/ts-tree-format.R | 325 ++ src/library/tstoml/R/ts-tree-insert.R | 623 +++ src/library/tstoml/R/ts-tree-new.R | 349 ++ src/library/tstoml/R/ts-tree-query.R | 40 + src/library/tstoml/R/ts-tree-select.R | 216 + src/library/tstoml/R/ts-tree-unserialize.R | 32 + src/library/tstoml/R/ts-tree-update.R | 122 + src/library/tstoml/R/ts-tree-write.R | 15 + src/library/tstoml/R/ts-unserialize-toml.R | 312 ++ src/library/tstoml/R/tstoml-package.R | 50 + src/library/tstoml/R/utils.R | 77 + src/library/tstoml/cleanup | 3 + src/library/tstoml/configure | 18 + src/library/tstoml/configure.win | 3 + src/library/tstoml/man/macros/eval.Rd | 1 + src/library/tstoml/man/roxygen/meta.R | 32 + src/library/tstoml/src/Makevars | 25 + src/library/tstoml/src/init.c | 34 + .../tstoml/src/tree-sitter/toml/grammar.js | 220 + .../tstoml/src/tree-sitter/toml/grammar.json | 865 +++ .../src/tree-sitter/toml/node-types.json | 477 ++ .../tstoml/src/tree-sitter/toml/parser.c | 4860 +++++++++++++++++ .../tstoml/src/tree-sitter/toml/scanner.c | 82 + .../src/tree-sitter/toml/tree_sitter/alloc.h | 54 + .../src/tree-sitter/toml/tree_sitter/array.h | 291 + .../src/tree-sitter/toml/tree_sitter/parser.h | 286 + src/library/tstoml/tools/dynamic-help.R | 5 + src/library/tstoml/tools/man/quickstart.Rmd | 138 + 142 files changed, 39144 insertions(+), 1 deletion(-) create mode 100644 src/dummy/ts/DESCRIPTION create mode 100644 src/dummy/tstoml/DESCRIPTION create mode 100644 src/library/ts/DESCRIPTION create mode 100644 src/library/ts/LICENSE create mode 100644 src/library/ts/LICENSE.note create mode 100644 src/library/ts/NAMESPACE create mode 100644 src/library/ts/NEWS.md create mode 100644 src/library/ts/R/bracket-ts-tree.R create mode 100644 src/library/ts/R/cleancall.R create mode 100644 src/library/ts/R/coerce.R create mode 100644 src/library/ts/R/collapse.R create mode 100644 src/library/ts/R/compat-vctrs.R create mode 100644 src/library/ts/R/docs.R create mode 100644 src/library/ts/R/error.R create mode 100644 src/library/ts/R/friendly-type.R create mode 100644 src/library/ts/R/glue.R create mode 100644 src/library/ts/R/print.R create mode 100644 src/library/ts/R/ts-list-parsers.R create mode 100644 src/library/ts/R/ts-package.R create mode 100644 src/library/ts/R/ts-tree-ast.R create mode 100644 src/library/ts/R/ts-tree-delete.R create mode 100644 src/library/ts/R/ts-tree-dom.R create mode 100644 src/library/ts/R/ts-tree-format.R create mode 100644 src/library/ts/R/ts-tree-insert.R create mode 100644 src/library/ts/R/ts-tree-new.R create mode 100644 src/library/ts/R/ts-tree-query.R create mode 100644 src/library/ts/R/ts-tree-select.R create mode 100644 src/library/ts/R/ts-tree-sexpr.R create mode 100644 src/library/ts/R/ts-tree-unserialize.R create mode 100644 src/library/ts/R/ts-tree-update.R create mode 100644 src/library/ts/R/ts-tree-write.R create mode 100644 src/library/ts/R/utils.R create mode 100755 src/library/ts/cleanup create mode 100755 src/library/ts/configure create mode 100755 src/library/ts/configure.win create mode 100644 src/library/ts/man/macros/eval.Rd create mode 100644 src/library/ts/man/roxygen/meta.R create mode 100644 src/library/ts/src/Makevars create mode 100644 src/library/ts/src/cleancall.c create mode 100644 src/library/ts/src/cleancall.h create mode 100644 src/library/ts/src/glue.c create mode 100644 src/library/ts/src/init.c create mode 100644 src/library/ts/src/tree-sitter.c create mode 100644 src/library/ts/src/tree-sitter/lib/include/tree_sitter/api.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/alloc.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/alloc.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/array.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/atomic.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/clock.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/error_costs.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/get_changed_ranges.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/get_changed_ranges.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/host.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/language.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/language.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/length.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/lexer.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/lexer.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/lib.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/node.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/parser.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/parser.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/point.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/point.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/portable/endian.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/query.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/reduce_action.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/reusable_node.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/stack.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/stack.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/subtree.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/subtree.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/tree.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/tree.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/tree_cursor.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/tree_cursor.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/ts_assert.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/ICU_SHA create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/LICENSE create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/README.md create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/ptypes.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/umachine.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/urename.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/utf.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/utf16.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/unicode/utf8.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/wasm/stdlib-symbols.txt create mode 100644 src/library/ts/src/tree-sitter/lib/src/wasm/stdlib.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/wasm/wasm-stdlib.h create mode 100644 src/library/ts/src/tree-sitter/lib/src/wasm_store.c create mode 100644 src/library/ts/src/tree-sitter/lib/src/wasm_store.h create mode 100644 src/library/ts/tools/ci/Rprofile create mode 100644 src/library/ts/tools/dynamic-help.R create mode 100644 src/library/ts/tools/examples.R create mode 100644 src/library/ts/tools/man/about.Rmd create mode 100644 src/library/tstoml/DESCRIPTION create mode 100644 src/library/tstoml/LICENSE create mode 100644 src/library/tstoml/NAMESPACE create mode 100644 src/library/tstoml/R/checks.R create mode 100644 src/library/tstoml/R/compat-vctrs.R create mode 100644 src/library/tstoml/R/friendly-type.R create mode 100644 src/library/tstoml/R/parsedate-package.R create mode 100644 src/library/tstoml/R/rematch2.R create mode 100644 src/library/tstoml/R/ts-format-toml.R create mode 100644 src/library/tstoml/R/ts-language-toml.R create mode 100644 src/library/tstoml/R/ts-parse-toml.R create mode 100644 src/library/tstoml/R/ts-serialize-toml.R create mode 100644 src/library/tstoml/R/ts-tree-ast.R create mode 100644 src/library/tstoml/R/ts-tree-delete.R create mode 100644 src/library/tstoml/R/ts-tree-format.R create mode 100644 src/library/tstoml/R/ts-tree-insert.R create mode 100644 src/library/tstoml/R/ts-tree-new.R create mode 100644 src/library/tstoml/R/ts-tree-query.R create mode 100644 src/library/tstoml/R/ts-tree-select.R create mode 100644 src/library/tstoml/R/ts-tree-unserialize.R create mode 100644 src/library/tstoml/R/ts-tree-update.R create mode 100644 src/library/tstoml/R/ts-tree-write.R create mode 100644 src/library/tstoml/R/ts-unserialize-toml.R create mode 100644 src/library/tstoml/R/tstoml-package.R create mode 100644 src/library/tstoml/R/utils.R create mode 100755 src/library/tstoml/cleanup create mode 100755 src/library/tstoml/configure create mode 100755 src/library/tstoml/configure.win create mode 100644 src/library/tstoml/man/macros/eval.Rd create mode 100644 src/library/tstoml/man/roxygen/meta.R create mode 100644 src/library/tstoml/src/Makevars create mode 100644 src/library/tstoml/src/init.c create mode 100644 src/library/tstoml/src/tree-sitter/toml/grammar.js create mode 100644 src/library/tstoml/src/tree-sitter/toml/grammar.json create mode 100644 src/library/tstoml/src/tree-sitter/toml/node-types.json create mode 100644 src/library/tstoml/src/tree-sitter/toml/parser.c create mode 100644 src/library/tstoml/src/tree-sitter/toml/scanner.c create mode 100644 src/library/tstoml/src/tree-sitter/toml/tree_sitter/alloc.h create mode 100644 src/library/tstoml/src/tree-sitter/toml/tree_sitter/array.h create mode 100644 src/library/tstoml/src/tree-sitter/toml/tree_sitter/parser.h create mode 100644 src/library/tstoml/tools/dynamic-help.R create mode 100644 src/library/tstoml/tools/man/quickstart.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index 8d4d6468d5..d739caa461 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -118,6 +118,8 @@ Config/needs/dependencies: pkgsearch, processx, ps, + r-lib/ts, + gaborcsardi/tstoml, yaml Config/Needs/website: r-lib/asciicast, diff --git a/R/embed.R b/R/embed.R index bff9086bec..f42b516c4e 100644 --- a/R/embed.R +++ b/R/embed.R @@ -313,6 +313,7 @@ embed <- local({ rimraf(file.path(lib, pkg, "inst", "CITATION")) rimraf(file.path(lib, pkg, "MD5")) rimraf(file.path(lib, pkg, "README.md")) + rimraf(file.path(lib, pkg, "inst", "tsdocs")) } } diff --git a/inst/COPYRIGHTS b/inst/COPYRIGHTS index 961c8a1b27..806b67403c 100644 --- a/inst/COPYRIGHTS +++ b/inst/COPYRIGHTS @@ -42,6 +42,18 @@ pak (c) 2017-2024 Posit Software, PBC (formerly RStudio) (c) Giampaolo Rodola (c) Posit Software, PBC (formerly RStudio) +# ts + +(c) Gábor Csárdi +(c) Tree-sitter authors +(c) Posit Software, PBC (formerly RStudio) + +# tstoml + +(c) Gábor Csárdi +(c) Tree-sitter authors +(c) Posit Software, PBC (formerly RStudio) + ## yaml (c) Shawn P Garbett diff --git a/src/dummy/ts/DESCRIPTION b/src/dummy/ts/DESCRIPTION new file mode 100644 index 0000000000..833fb35e69 --- /dev/null +++ b/src/dummy/ts/DESCRIPTION @@ -0,0 +1,2 @@ +Package: ts +Version: 100.0.0 diff --git a/src/dummy/tstoml/DESCRIPTION b/src/dummy/tstoml/DESCRIPTION new file mode 100644 index 0000000000..ef4e4038dd --- /dev/null +++ b/src/dummy/tstoml/DESCRIPTION @@ -0,0 +1,2 @@ +Package: tstoml +Version: 100.0.0 diff --git a/src/install-embedded.R b/src/install-embedded.R index 76a55ed9e2..70f306b24c 100644 --- a/src/install-embedded.R +++ b/src/install-embedded.R @@ -82,7 +82,10 @@ install_order <- function() { "jsonlite", "lpSolve", "ps", + "ts", "zip", + # ts + "tstoml", # ps, R6 "processx", # processx, R6 @@ -91,7 +94,7 @@ install_order <- function() { "desc", # callr, cli, desc, processx, R6 "pkgbuild", - # callr, cli, curl, filelock, jsonlite, prettyunis, processx, R6 + # callr, cli, curl, filelock, jsonlite, prettyunis, processx, R6, ts, tstoml "pkgcache", # curl, jsonlite "pkgsearch", diff --git a/src/library/ts/DESCRIPTION b/src/library/ts/DESCRIPTION new file mode 100644 index 0000000000..08c3ac9deb --- /dev/null +++ b/src/library/ts/DESCRIPTION @@ -0,0 +1,32 @@ +Package: ts +Title: Tree-Sitter Parsing Tools +Version: 0.0.0.9000 +Authors@R: c( + person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", + role = c("aut", "cre")), + person("Posit Software, PBC", role = c("cph", "fnd"), + comment = c(ROR = "03wc8by49")), + person("Tree-sitter authors", role = "cph", comment = "Tree-sitter C library") + ) +Description: Common tree-sitter parsing tools for R. It is meant to be + used by other packages that specialize in particular languages and + file formats. +License: MIT + file LICENSE +Depends: R (>= 4.1.0) +Imports: cli, utils +Suggests: magrittr, pillar, testthat (>= 3.0.0), tsjsonc, tstoml, withr +Remotes: gaborcsardi/tsjsonc, gaborcsardi/tstoml +Additional_repositories: https://github.com/r-lib/ts/releases/download +Encoding: UTF-8 +RoxygenNote: 7.3.3 +URL: https://github.com/r-lib/ts, https://r-lib.github.io/ts/ +BugReports: https://github.com/r-lib/ts/issues +Config/testthat/edition: 3 +Config/Needs/website: r-lib/asciicast, tidyverse/tidytemplate +Biarch: true +NeedsCompilation: yes +Packaged: 2026-05-13 07:33:05 UTC; gaborcsardi +Author: Gábor Csárdi [aut, cre], + Posit Software, PBC [cph, fnd] (ROR: ), + Tree-sitter authors [cph] (Tree-sitter C library) +Maintainer: Gábor Csárdi diff --git a/src/library/ts/LICENSE b/src/library/ts/LICENSE new file mode 100644 index 0000000000..f00d2e60d9 --- /dev/null +++ b/src/library/ts/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2025--2026 +COPYRIGHT HOLDER: ts authors diff --git a/src/library/ts/LICENSE.note b/src/library/ts/LICENSE.note new file mode 100644 index 0000000000..c8336544bd --- /dev/null +++ b/src/library/ts/LICENSE.note @@ -0,0 +1,24 @@ +Tree-sitter C library +-------------------------------------------------------------------------------- + +The MIT License (MIT) + +Copyright (c) 2018--2026 Max Brunsfeld + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/library/ts/NAMESPACE b/src/library/ts/NAMESPACE new file mode 100644 index 0000000000..5b276a0d85 --- /dev/null +++ b/src/library/ts/NAMESPACE @@ -0,0 +1,58 @@ +# Generated by roxygen2: do not edit by hand + +S3method("$",ts_tree) +S3method("[",ts_tree) +S3method("[[",ts_tree) +S3method("[[<-",ts_tree) +S3method("ts_tree_select<-",ts_tree) +S3method(as.character,ts_caller_arg) +S3method(as.character,ts_tree) +S3method(as.raw,ts_tree) +S3method(format,ts_parse_error) +S3method(format,ts_tree) +S3method(print,ts_parse_error) +S3method(print,ts_tree) +S3method(ts_tree_ast,default) +S3method(ts_tree_dom,default) +S3method(ts_tree_mark_selection1,ts_tree) +S3method(ts_tree_new,ts_language) +S3method(ts_tree_query,default) +S3method(ts_tree_select1,default) +S3method(ts_tree_select1,ts_tree.NULL) +S3method(ts_tree_select1,ts_tree.character) +S3method(ts_tree_select1,ts_tree.integer) +S3method(ts_tree_select1,ts_tree.logical) +S3method(ts_tree_select1,ts_tree.numeric) +S3method(ts_tree_select1,ts_tree.ts_tree_selector_default) +S3method(ts_tree_select1,ts_tree.ts_tree_selector_ids) +S3method(ts_tree_select1,ts_tree.ts_tree_selector_regex) +S3method(ts_tree_select1,ts_tree.ts_tree_selector_tsquery) +S3method(ts_tree_sexpr,default) +S3method(ts_tree_write,default) +export("ts_tree_select<-") +export(as_ts_caller_arg) +export(ts_caller_arg) +export(ts_caller_env) +export(ts_check_named_arg) +export(ts_cnd) +export(ts_collapse) +export(ts_list_parsers) +export(ts_parse_error_cnd) +export(ts_tree_ast) +export(ts_tree_delete) +export(ts_tree_deleted) +export(ts_tree_dom) +export(ts_tree_format) +export(ts_tree_insert) +export(ts_tree_mark_selection1) +export(ts_tree_new) +export(ts_tree_query) +export(ts_tree_select) +export(ts_tree_select1) +export(ts_tree_selected_nodes) +export(ts_tree_selection) +export(ts_tree_sexpr) +export(ts_tree_unserialize) +export(ts_tree_update) +export(ts_tree_write) +useDynLib(ts, .registration = TRUE, .fixes = "c_") diff --git a/src/library/ts/NEWS.md b/src/library/ts/NEWS.md new file mode 100644 index 0000000000..a998f52a28 --- /dev/null +++ b/src/library/ts/NEWS.md @@ -0,0 +1,3 @@ +# ts (development version) + +* Initial CRAN submission. diff --git a/src/library/ts/R/bracket-ts-tree.R b/src/library/ts/R/bracket-ts-tree.R new file mode 100644 index 0000000000..03f47bc833 --- /dev/null +++ b/src/library/ts/R/bracket-ts-tree.R @@ -0,0 +1,96 @@ +#' Convert ts_tree object to a data frame +#' +#' @ts ts_tree_brackets_description +#' Create a data frame for the syntax tree of a JSON document, by indexing +#' a ts_tree object with single brackets. This is occasionally useful for +#' exploration and debugging. +#' @description \eval{ts:::doc_insert("ts_tree_brackets_description")} +#' +#' @ts ts_tree_brackets_details +#' A tree-sitter tree object has at least four classes: +#' * `ts_tree_`, e.g. `ts_tree_tsjsonc`, +#' * `ts_tree`, +#' * `tbl`, from the pillar package, for better printing when converted +#' to a data frame, and +#' * `data.frame`, since it is a data frame internally. +#' +#' The `ts_tree` class has custom [format()] and [print()] methods, that +#' show (part of) the underlying document, and also the selected elements, +#' if any. +#' +#'

+#' +#' It is sometimes useful to treat a `tree` `ts_tree` object as a data +#' frame, and drop the `ts_tree` classes. This can be done by indexing with +#' single brackets, e.g. `tree[]`. This returns a data frame with one +#' row per token, and various columns with information about the tokens. +#' See details in the 'Value' section or this page. +#' +#' @details \eval{ts:::doc_insert("ts::ts_tree_brackets_details")} +#' +#' @ts ts_tree_brackets_param_x +#' A `ts_tree` object. +#' @ts ts_tree_brackets_param_ij +#' Incides, passed to the regular data.frame indexing method, see +#' \code{\link[base:Extract]{'Extract'}}. +#' @ts ts_tree_brackets_param_drop +#' Passed to the regular data.frame indexing method, see +#' \code{\link[base:Extract]{'Extract'}}. +#' +#' @param x \eval{ts:::doc_insert("ts::ts_tree_brackets_param_x")} +#' @param i,j \eval{ts:::doc_insert("ts::ts_tree_brackets_param_ij")} +#' @param drop \eval{ts:::doc_insert("ts::ts_tree_brackets_param_drop")} +#' +#' @ts ts_tree_brackets_return +#' A data frame with one row per token, and columns: +#' * `id`: integer, the id of the token. The (root) document node has id 1. +#' * `parent`: integer, the id of the parent token. The root token has +#' parent `NA` +#' * `field_name`: character, the field name of the token in its parent. +#' * `type`: character, the type of the token. +#' * `code`: character, the actual code of the token. +#' * `start_byte`, `end_byte`: integer, the byte positions of the token +#' in the input. +#' * `start_row`, `start_column`, `end_row`, `end_column`: integer, the +#' position of the token in the input. +#' * `is_missing`: logical, whether the token is a missing token added by +#' the parser to recover from errors. +#' * `has_error`: logical, whether the token has a parse error. +#' * `children`: list of integer vectors, the ids of the children tokens. +#' * `dom_type`: character, the type of the node in the DOM tree. See +#' \code{\link[ts:ts_tree_dom]{ts_tree_dom()}}. Nodes that are not part +#' of the DOM tree have `NA_character_` here. +#' * `dom_children`: list of integer vectors, the ids of the children in the +#' DOM tree. See \code{\link[ts:ts_tree_dom]{ts_tree_dom()}}. +#' * `dom_parent`: integer, the parent of the node in the DOM tree. See +#' \code{\link[ts:ts_tree_dom]{ts_tree_dom()}}. Nodes that are not part +#' of the DOM tree and the document node have have `NA_integer_` here. +#' +#' Other, undocumented columns may also be present, these are considered +#' internal and may change without notice. +#' @return \eval{ts:::doc_insert("ts::ts_tree_brackets_return")} +#' +#' @name ts_tree-brackets +#' @family ts_tree exploration +#' @seealso \eval{ts:::doc_seealso("[")} +#' @export +#' @examplesIf requireNamespace("tsjsonc", quietly = TRUE) +#' # Create a parse tree with tsjsonc ------------------------------------- +#' tree <- tsjsonc::ts_parse_jsonc('{"foo": 42, "bar": [1, 2, 3]}') +#' +#' tree +#' +#' tree[] + +`[.ts_tree` <- function(x, i, j, drop = FALSE) { + class(x) <- setdiff(class(x), "ts_tree") + requireNamespace("pillar", quietly = TRUE) + NextMethod("[") +} + +#' @export + +`$.ts_tree` <- function(x, name) { + class(x) <- setdiff(class(x), "ts_tree") + NextMethod(`$`) +} diff --git a/src/library/ts/R/cleancall.R b/src/library/ts/R/cleancall.R new file mode 100644 index 0000000000..11e4d24607 --- /dev/null +++ b/src/library/ts/R/cleancall.R @@ -0,0 +1,3 @@ +call_with_cleanup <- function(ptr, ...) { + .Call(c_cleancall_call, pairlist(ptr, ...), parent.frame()) +} diff --git a/src/library/ts/R/coerce.R b/src/library/ts/R/coerce.R new file mode 100644 index 0000000000..d71a6e799f --- /dev/null +++ b/src/library/ts/R/coerce.R @@ -0,0 +1,36 @@ +#' Raw bytes of a document of a tree-sitter tree +#' +#' @param x A `ts_tree` object. +#' @return A raw vector containing the bytes of the document of the tree. +#' +#' @export +#' @seealso [as.character.ts_tree()] to get the document as a character scalar. +#' @examples +#' # Create a parse tree with tsjsonc ------------------------------------- +#' tree <- tsjsonc::ts_parse_jsonc('{"foo": 42, "bar": [1, 2, 3]}') +#' +#' tree +#' as.raw(tree) + +as.raw.ts_tree <- function(x) { + attr(x, "text") +} + +#' The document of a tree-sitter tree as a character scalar +#' +#' @param x A `ts_tree` object. +#' @param ... Ignored. +#' @return A character scalar containing the document of the tree. +#' +#' @export +#' @seealso [as.raw.ts_tree()] to get the document as a raw vector. +#' @examples +#' # Create a parse tree with tsjsonc ------------------------------------- +#' tree <- tsjsonc::ts_parse_jsonc('{"foo": 42, "bar": [1, 2, 3]}') +#' +#' tree +#' as.character(tree) + +as.character.ts_tree <- function(x, ...) { + rawToChar(as.raw(x)) +} diff --git a/src/library/ts/R/collapse.R b/src/library/ts/R/collapse.R new file mode 100644 index 0000000000..8331ad35fe --- /dev/null +++ b/src/library/ts/R/collapse.R @@ -0,0 +1,193 @@ +#' @rdname internal +#' @param s For `ts_collapse()` a character vector to collapse. +#' @param sep Separator string for most elements. +#' @param sep2 Separator string for two elements. +#' @param last Separator string before the last element. +#' @param trunc Integer, maximum number of elements to show before +#' truncation. +#' @param width Integer, maximum display width of the collapsed string. +#' If the collapsed string exceeds this width, it will be truncated +#' with `ellipsis`. +#' @param ellipsis String to indicate truncation. +#' @param style Character, the collapsing style to use. Possible values are +#' `"both-ends"` (the default), which shows the first few and last few +#' elements when truncating, and `"head"`, which shows only the first few +#' elements. +#' @return `ts_collapse()` returns a character scalar, the collapsed string. +#' @details `ts_collapse()` collapses a character vector into a single string, +#' with options for truncation by number of elements or display width. +#' It is useful for creating informative error messages. +#' @export +#' @examples +#' ts_collapse(letters[1:3]) +#' ts_collapse(letters[1:10], trunc = 5) + +ts_collapse <- function( + s, + sep = ", ", + sep2 = sub("^,", "", last), + last = ", and ", + trunc = Inf, + width = Inf, + ellipsis = "...", + style = c("both-ends", "head") +) { + style <- match.arg(style) + switch( + style, + "both-ends" = collapse_both_ends( + s, + sep, + sep2, + last, + trunc, + width, + ellipsis + ), + "head" = collapse_head(s, sep, sep2, last, trunc, width, ellipsis) + ) +} + +collapse_head_notrim <- function(x, trunc, sep, sep2, last, ellipsis) { + lnx <- length(x) + + if (lnx == 1L) { + return(x) + } + if (lnx == 2L) { + return(paste0(x, collapse = sep2)) + } + if (lnx <= trunc) { + # no truncation + return(paste0( + paste(x[1:(lnx - 1L)], collapse = sep), + last, + x[lnx] + )) + } else { + # truncation, no need for 'last' + return(paste0( + paste(x[1:trunc], collapse = sep), + sep, + ellipsis + )) + } +} + +collapse_head <- function(x, sep, sep2, last, trunc, width, ellipsis) { + trunc <- max(trunc, 1L) + x <- as.character(x) + lnx <- length(x) + + # special cases that do not need trimming + if (lnx == 0L) { + return("") + } else if (anyNA(x)) { + return(NA_character_) + } + + # easier case, no width trimming + if (width == Inf) { + return(collapse_head_notrim(x, trunc, sep, sep2, last, ellipsis)) + } + + # complex case, with width wrapping + # first we truncate + tcd <- lnx > trunc + if (tcd) { + x <- x[1:trunc] + } + + # then we calculate the width w/o trimming + wx <- nchar(x) + wsep <- nchar(sep, "width") + wsep2 <- nchar(sep2, "width") + wlast <- nchar(last, "width") + well <- nchar(ellipsis, "width") + if (!tcd) { + # x[1] + # x[1] and x[2] + # x[1], x[2], and x[3] + nsep <- if (lnx > 2L) lnx - 2L else 0L + nsep2 <- if (lnx == 2L) 1L else 0L + nlast <- if (lnx > 2L) 1L else 0L + wtot <- sum(wx) + nsep * wsep + nsep2 * wsep2 + nlast * wlast + if (wtot <= width) { + if (lnx == 1L) { + return(x) + } else if (lnx == 2L) { + return(paste0(x, collapse = sep2)) + } else { + return(paste0( + paste(x[1:(lnx - 1L)], collapse = sep), + last, + x[lnx] + )) + } + } + } else { + # x[1], x[2], x[trunc], ... + wtot <- sum(wx) + trunc * wsep + well + if (wtot <= width) { + return(paste0( + paste(x, collapse = sep), + sep, + ellipsis + )) + } + } + + # we need to find the longest possible truncation for the form + # x[1], x[2], x[trunc], ... + # each item is wx + wsep wide, so we search how many fits, with ellipsis + last <- function(x) if (length(x) >= 1L) x[length(x)] else x[NA_integer_] + trunc <- last(which(cumsum(wx + wsep) + well <= width)) + + # not even one element fits + if (is.na(trunc)) { + if (well > width) { + return(strtrim(ellipsis, width)) + } else if (well == width) { + return(ellipsis) + } else if (well + wsep >= width) { + return(paste0(strtrim(x[1L], width), ellipsis)) + } else { + return(paste0( + strtrim(x[1L], max(width - well - wsep, 0L)), + sep, + ellipsis + )) + } + } + + return(paste0( + paste(x[1:trunc], collapse = sep), + sep, + ellipsis + )) +} + +collapse_both_ends <- function(x, sep, sep2, last, trunc, width, ellipsis) { + # we always list at least 5 elements + trunc <- max(trunc, 5L) + trunc <- min(trunc, length(x)) + if (length(x) <= 5L || length(x) <= trunc) { + return(collapse_head(x, sep, sep2, last, trunc = trunc, width, ellipsis)) + } + + # we have at least six elements in the vector + # 1, 2, 3, ..., 9, and 10 + x <- as.character(c(x[1:(trunc - 2L)], x[length(x) - 1L], x[length(x)])) + paste0( + c(x[1:(trunc - 2L)], ellipsis, paste0(x[trunc - 1L], last, x[trunc])), + collapse = sep + ) +} + +trim <- function(x) { + has_newline <- function(x) any(grepl("\\n", x)) + if (length(x) == 0L || !has_newline(x)) { + return(x) + } + call_with_cleanup(c_trim, x) +} diff --git a/src/library/ts/R/compat-vctrs.R b/src/library/ts/R/compat-vctrs.R new file mode 100644 index 0000000000..8b4595e4e0 --- /dev/null +++ b/src/library/ts/R/compat-vctrs.R @@ -0,0 +1,643 @@ +# nocov start + +compat_vctrs <- local({ + # Modified from https://github.com/r-lib/rlang/blob/master/R/compat-vctrs.R + + # Construction ------------------------------------------------------------ + + # Constructs data frames inheriting from `"tbl"`. This allows the + # pillar package to take over printing as soon as it is loaded. + # The data frame otherwise behaves like a base data frame. + data_frame <- function(...) { + new_data_frame(df_list(...), .class = "tbl") + } + + new_data_frame <- function(.x = list(), ..., .size = NULL, .class = NULL) { + n_cols <- length(.x) + if (n_cols != 0 && is.null(names(.x))) { + stop("Columns must be named.", call. = FALSE) + } + + if (is.null(.size)) { + if (n_cols == 0) { + .size <- 0 + } else { + .size <- vec_size(.x[[1]]) + } + } + + structure( + .x, + class = c(.class, "data.frame"), + row.names = .set_row_names(.size), + ... + ) + } + + df_list <- function(..., .size = NULL) { + vec_recycle_common(list(...), size = .size) + } + + # Binding ----------------------------------------------------------------- + + vec_rbind <- function(...) { + xs <- vec_cast_common(list(...)) + do.call(base::rbind, xs) + } + + vec_cbind <- function(...) { + xs <- list(...) + + ptype <- vec_ptype_common(lapply(xs, `[`, 0)) + class <- setdiff(class(ptype), "data.frame") + + xs <- vec_recycle_common(xs) + out <- do.call(base::cbind, xs) + new_data_frame(out, .class = class) + } + + # Slicing ----------------------------------------------------------------- + + vec_size <- function(x) { + if (is.data.frame(x)) { + nrow(x) + } else { + length(x) + } + } + + vec_rep <- function(x, times) { + i <- rep.int(seq_len(vec_size(x)), times) + vec_slice(x, i) + } + + vec_recycle_common <- function(xs, size = NULL) { + sizes <- vapply(xs, vec_size, integer(1)) + + n <- unique(sizes) + + if (length(n) == 1 && is.null(size)) { + return(xs) + } + n <- setdiff(n, 1L) + + ns <- length(n) + + if (ns == 0) { + if (is.null(size)) { + return(xs) + } + } else if (ns == 1) { + if (is.null(size)) { + size <- n + } else if (ns != size) { + stop("Inputs can't be recycled to `size`.", call. = FALSE) + } + } else { + stop("Inputs can't be recycled to a common size.", call. = FALSE) + } + + to_recycle <- sizes == 1L + xs[to_recycle] <- lapply(xs[to_recycle], vec_rep, size) + + xs + } + + vec_slice <- function(x, i) { + if (is.logical(i)) { + i <- which(i) + } + stopifnot(is.numeric(i) || is.character(i)) + + if (is.null(x)) { + return(NULL) + } + + if (is.data.frame(x)) { + # We need to be a bit careful to be generic. First empty all + # columns and expand the df to final size. + out <- x[i, 0, drop = FALSE] + + # Then fill in with sliced columns + out[seq_along(x)] <- lapply(x, vec_slice, i) + + # Reset automatic row names to work around `[` weirdness + if (is.numeric(attr(x, "row.names"))) { + row_names <- .set_row_names(nrow(out)) + } else { + row_names <- attr(out, "row.names") + } + + return(out) + } + + d <- vec_dims(x) + if (d == 1) { + if (is.object(x)) { + out <- x[i] + } else { + out <- x[i, drop = FALSE] + } + } else if (d == 2) { + out <- x[i, , drop = FALSE] + } else { + j <- rep(list(quote(expr = )), d - 1) + out <- eval(as.call(list( + quote(`[`), + quote(x), + quote(i), + j, + drop = FALSE + ))) + } + + out + } + vec_dims <- function(x) { + d <- dim(x) + if (is.null(d)) { + 1L + } else { + length(d) + } + } + + vec_as_location <- function(i, n, names = NULL) { + out <- seq_len(n) + names(out) <- names + + # Special-case recycling to size 0 + if (is_logical(i, n = 1) && !length(out)) { + return(out) + } + + unname(out[i]) + } + + vec_init <- function(x, n = 1L) { + vec_slice(x, rep_len(NA_integer_, n)) + } + + vec_assign <- function(x, i, value) { + if (is.null(x)) { + return(NULL) + } + + if (is.logical(i)) { + i <- which(i) + } + stopifnot( + is.numeric(i) || is.character(i) + ) + + value <- vec_recycle(value, vec_size(i)) + value <- vec_cast(value, to = x) + + d <- vec_dims(x) + + if (d == 1) { + x[i] <- value + } else if (d == 2) { + x[i, ] <- value + } else { + stop("Can't slice-assign arrays.", call. = FALSE) + } + + x + } + + vec_recycle <- function(x, size) { + if (is.null(x) || is.null(size)) { + return(NULL) + } + + n_x <- vec_size(x) + + if (n_x == size) { + x + } else if (size == 0L) { + vec_slice(x, 0L) + } else if (n_x == 1L) { + vec_slice(x, rep(1L, size)) + } else { + stop("Incompatible lengths: ", n_x, ", ", size, call. = FALSE) + } + } + + # Coercion ---------------------------------------------------------------- + + vec_cast_common <- function(xs, to = NULL) { + ptype <- vec_ptype_common(xs, ptype = to) + lapply(xs, vec_cast, to = ptype) + } + + vec_cast <- function(x, to) { + if (is.null(x)) { + return(NULL) + } + if (is.null(to)) { + return(x) + } + + if (vec_is_unspecified(x)) { + return(vec_init(to, vec_size(x))) + } + + stop_incompatible_cast <- function(x, to) { + stop( + sprintf( + "Can't convert <%s> to <%s>.", + .rlang_vctrs_typeof(x), + .rlang_vctrs_typeof(to) + ), + call. = FALSE + ) + } + + lgl_cast <- function(x, to) { + lgl_cast_from_num <- function(x) { + if (any(!x %in% c(0L, 1L))) { + stop_incompatible_cast(x, to) + } + as.logical(x) + } + + switch( + .rlang_vctrs_typeof(x), + logical = x, + integer = , + double = lgl_cast_from_num(x), + stop_incompatible_cast(x, to) + ) + } + + int_cast <- function(x, to) { + int_cast_from_dbl <- function(x) { + out <- suppressWarnings(as.integer(x)) + if (any((out != x) | xor(is.na(x), is.na(out)))) { + stop_incompatible_cast(x, to) + } else { + out + } + } + + switch( + .rlang_vctrs_typeof(x), + logical = as.integer(x), + integer = x, + double = int_cast_from_dbl(x), + stop_incompatible_cast(x, to) + ) + } + + dbl_cast <- function(x, to) { + switch( + .rlang_vctrs_typeof(x), + logical = , + integer = as.double(x), + double = x, + stop_incompatible_cast(x, to) + ) + } + + chr_cast <- function(x, to) { + switch( + .rlang_vctrs_typeof(x), + character = x, + stop_incompatible_cast(x, to) + ) + } + + list_cast <- function(x, to) { + switch( + .rlang_vctrs_typeof(x), + list = x, + stop_incompatible_cast(x, to) + ) + } + + df_cast <- function(x, to) { + # Check for extra columns + if (length(setdiff(names(x), names(to))) > 0) { + stop( + "Can't convert data frame because of missing columns.", + call. = FALSE + ) + } + + # Avoid expensive [.data.frame method + out <- as.list(x) + + # Coerce common columns + common <- intersect(names(x), names(to)) + out[common] <- Map(vec_cast, out[common], to[common]) + + # Add new columns + from_type <- setdiff(names(to), names(x)) + out[from_type] <- lapply(to[from_type], vec_init, n = vec_size(x)) + + # Ensure columns are ordered according to `to` + out <- out[names(to)] + + new_data_frame(out) + } + + rlib_df_cast <- function(x, to) { + new_data_frame(df_cast(x, to), .class = "tbl") + } + tib_cast <- function(x, to) { + new_data_frame(df_cast(x, to), .class = c("tbl_df", "tbl")) + } + + switch( + .rlang_vctrs_typeof(to), + logical = lgl_cast(x, to), + integer = int_cast(x, to), + double = dbl_cast(x, to), + character = chr_cast(x, to), + list = list_cast(x, to), + + base_data_frame = df_cast(x, to), + rlib_data_frame = rlib_df_cast(x, to), + tibble = tib_cast(x, to), + + stop_incompatible_cast(x, to) + ) + } + + vec_ptype_common <- function(xs, ptype = NULL) { + if (!is.null(ptype)) { + return(vec_ptype(ptype)) + } + + xs <- Filter(function(x) !is.null(x), xs) + + if (length(xs) == 0) { + return(NULL) + } + + if (length(xs) == 1) { + out <- vec_ptype(xs[[1]]) + } else { + xs <- map(xs, vec_ptype) + out <- Reduce(vec_ptype2, xs) + } + + vec_ptype_finalise(out) + } + + vec_ptype_finalise <- function(x) { + if (is.data.frame(x)) { + x[] <- lapply(x, vec_ptype_finalise) + return(x) + } + + if (inherits(x, "rlang_unspecified")) { + logical() + } else { + x + } + } + + vec_ptype <- function(x) { + if (vec_is_unspecified(x)) { + return(.rlang_vctrs_unspecified()) + } + + if (is.data.frame(x)) { + out <- new_data_frame(lapply(x, vec_ptype)) + + attrib <- attributes(x) + attrib$row.names <- attr(out, "row.names") + attributes(out) <- attrib + + return(out) + } + + vec_slice(x, 0) + } + + vec_ptype2 <- function(x, y) { + stop_incompatible_type <- function(x, y) { + stop( + sprintf( + "Can't combine types <%s> and <%s>.", + .rlang_vctrs_typeof(x), + .rlang_vctrs_typeof(y) + ), + call. = FALSE + ) + } + + x_type <- .rlang_vctrs_typeof(x) + y_type <- .rlang_vctrs_typeof(y) + + if (x_type == "unspecified" && y_type == "unspecified") { + return(.rlang_vctrs_unspecified()) + } + if (x_type == "unspecified") { + return(y) + } + if (y_type == "unspecified") { + return(x) + } + + df_ptype2 <- function(x, y) { + set_partition <- function(x, y) { + list( + both = intersect(x, y), + only_x = setdiff(x, y), + only_y = setdiff(y, x) + ) + } + + # Avoid expensive [.data.frame + x <- as.list(vec_slice(x, 0)) + y <- as.list(vec_slice(y, 0)) + + # Find column types + names <- set_partition(names(x), names(y)) + if (length(names$both) > 0) { + common_types <- Map(vec_ptype2, x[names$both], y[names$both]) + } else { + common_types <- list() + } + only_x_types <- x[names$only_x] + only_y_types <- y[names$only_y] + + # Combine and construct + out <- c(common_types, only_x_types, only_y_types) + out <- out[c(names(x), names$only_y)] + new_data_frame(out) + } + + rlib_df_ptype2 <- function(x, y) { + new_data_frame(df_ptype2(x, y), .class = "tbl") + } + tib_ptype2 <- function(x, y) { + new_data_frame(df_ptype2(x, y), .class = c("tbl_df", "tbl")) + } + + ptype <- switch( + x_type, + + logical = switch( + y_type, + logical = x, + integer = y, + double = y, + stop_incompatible_type(x, y) + ), + + integer = switch( + .rlang_vctrs_typeof(y), + logical = x, + integer = x, + double = y, + stop_incompatible_type(x, y) + ), + + double = switch( + .rlang_vctrs_typeof(y), + logical = x, + integer = x, + double = x, + stop_incompatible_type(x, y) + ), + + character = switch( + .rlang_vctrs_typeof(y), + character = x, + stop_incompatible_type(x, y) + ), + + list = switch( + .rlang_vctrs_typeof(y), + list = x, + stop_incompatible_type(x, y) + ), + + base_data_frame = switch( + .rlang_vctrs_typeof(y), + base_data_frame = , + s3_data_frame = df_ptype2(x, y), + rlib_data_frame = rlib_df_ptype2(x, y), + tibble = tib_ptype2(x, y), + stop_incompatible_type(x, y) + ), + + rlib_data_frame = switch( + .rlang_vctrs_typeof(y), + base_data_frame = , + rlib_data_frame = , + s3_data_frame = rlib_df_ptype2(x, y), + tibble = tib_ptype2(x, y), + stop_incompatible_type(x, y) + ), + + tibble = switch( + .rlang_vctrs_typeof(y), + base_data_frame = , + rlib_data_frame = , + tibble = , + s3_data_frame = tib_ptype2(x, y), + stop_incompatible_type(x, y) + ), + + stop_incompatible_type(x, y) + ) + + vec_slice(ptype, 0) + } + + .rlang_vctrs_typeof <- function(x) { + if (is.object(x)) { + class <- class(x) + + if (identical(class, "rlang_unspecified")) { + return("unspecified") + } + if (identical(class, "data.frame")) { + return("base_data_frame") + } + if (identical(class, c("tbl", "data.frame"))) { + return("rlib_data_frame") + } + if (identical(class, c("tbl_df", "tbl", "data.frame"))) { + return("tibble") + } + if (inherits(x, "data.frame")) { + return("s3_data_frame") + } + + class <- paste0(class, collapse = "/") + stop(sprintf("Unimplemented class <%s>.", class), call. = FALSE) + } + + type <- typeof(x) + switch( + type, + NULL = return("null"), + logical = if (vec_is_unspecified(x)) { + return("unspecified") + } else { + return(type) + }, + integer = , + double = , + character = , + raw = , + list = return(type) + ) + + stop(sprintf("Unimplemented type <%s>.", type), call. = FALSE) + } + + vec_is_unspecified <- function(x) { + !is.object(x) && + typeof(x) == "logical" && + length(x) && + all(vapply(x, identical, logical(1), NA)) + } + + .rlang_vctrs_unspecified <- function(x = NULL) { + structure( + rep(NA, length(x)), + class = "rlang_unspecified" + ) + } + + .rlang_vctrs_s3_method <- function(generic, class, env = parent.frame()) { + fn <- get(generic, envir = env) + + ns <- asNamespace(topenv(fn)) + tbl <- ns$.__S3MethodsTable__. + + for (c in class) { + name <- paste0(generic, ".", c) + if (exists(name, envir = tbl, inherits = FALSE)) { + return(get(name, envir = tbl)) + } + if (exists(name, envir = globalenv(), inherits = FALSE)) { + return(get(name, envir = globalenv())) + } + } + + NULL + } + + environment() +}) + +data_frame <- compat_vctrs$data_frame + +as_data_frame <- function(x) { + if (is.matrix(x)) { + x <- as.data.frame(x, stringsAsFactors = FALSE) + } else { + x <- compat_vctrs$vec_recycle_common(x) + } + compat_vctrs$new_data_frame(x, .class = "tbl") +} + +# nocov end diff --git a/src/library/ts/R/docs.R b/src/library/ts/R/docs.R new file mode 100644 index 0000000000..932d5c0bdf --- /dev/null +++ b/src/library/ts/R/docs.R @@ -0,0 +1,315 @@ +dglue <- function(..., .envir = parent.frame()) { + glue(..., .open = "<<", .close = ">>", .envir = .envir) +} + +is_rcmd_check <- function() { + if (Sys.getenv("GITHUB_ACTIONS") == "true") { + return(FALSE) + } + Sys.getenv("_R_CHECK_PACKAGE_NAME_", "") != "" || + Sys.getenv("_R_RD_MACROS_PACKAGE_DIR_", "") != "" +} + +doc_has_method <- function(method, package) { + method <- paste0(method, ".ts_tree_", sub("^ts", "", package)) + length(utils::help( + topic = (method), + package = (package), + help_type = "text" + )) > + 0 +} + +doc_seealso <- function(method) { + ts_package <- get_env("R_TS_PACKAGE") + if (is.null(ts_package)) { + psrs <- ts_list_parsers() + psrs <- psrs[!duplicated(psrs$package), ] + links <- vapply( + psrs$package, + function(pkg) { + if (doc_has_method(method, pkg)) { + glue( + "\\code{{\\link[{pkg}:{method}.{pkg}]{{{method}()}}}}", + ) + } else { + "" + } + }, + "" + ) + links <- links[links != ""] + if (length(links) > 0) { + s <- if (length(links) > 1) "s" + glue("Method{s} in installed package{s}: {ts_collapse(links)}.") + } else { + "" + } + } else { + paste0( + "The generic of this method in the ts package: ", + glue("\\code{{\\link[ts:{method}]{{{method}()}}}}"), + "." + ) + } +} + +doc_insert <- function(key, manpkg = NULL) { + if (is_rcmd_check()) { + return("Placeholder.") + } + if (!is.null(manpkg)) { + Sys.setenv("R_TS_PACKAGE" = manpkg) + on.exit(Sys.unsetenv("R_TS_PACKAGE"), add = TRUE) + } + keypcs <- strsplit(key, "::", fixed = TRUE)[[1]] + if (length(keypcs) == 2) { + package <- keypcs[1] + key <- keypcs[2] + } else { + package <- "ts" + } + lib <- dirname(find.package(package)) + output <- doc_create_chunk(key, lib, package, 1L, "<>") + + mch <- gregexpr("\\\\eval\\{[^\\}]+\\}", output, perl = TRUE) + regmatches(output, mch)[[1]] <- lapply( + regmatches(output, mch)[[1]], + function(x) { + x <- sub("^\\\\eval\\{", "", x) + x <- sub("\\}$", "", x) + eval(parse(text = x)) + } + ) + output +} + +doc_tabs <- function(key) { + if (is_rcmd_check()) { + return("Placeholder.") + } + package <- if (nzchar(ev <- Sys.getenv("R_TS_PACKAGE"))) { + ev + } + + if (is.null(package)) { + doc_tabs_all(key) + } else { + doc_tabs_one(key, package) + } +} + +doc_tabs_all <- function(key) { + # list all installed ts packages + psrs <- ts_list_parsers() + psrs <- psrs[!duplicated(psrs$package), ] + + tsdocpath <- doc_path("ts") + t_tab <- read_char(file.path(tsdocpath, "tab.html")) + t_div <- read_char(file.path(tsdocpath, "tabs.html")) + t_btn <- read_char(file.path(tsdocpath, "btn.html")) + output <- buttons <- tabs <- "" + + for (i in seq_len(nrow(psrs))) { + language <- sub("^ts", "", psrs$package[i]) + tab <- doc_create_chunk(key, psrs$library[i], psrs$package[i], i, t_tab) + btn <- dglue( + t_btn, + .envir = c(psrs[i, ], list(idx = i, language = language)) + ) + if (!is.null(tab) && nzchar(tab)) { + tabs <- paste0(tabs, tab, "\n") + buttons <- paste0(buttons, btn, "\n") + } + } + if (tabs != "") { + output <- dglue(t_div, .envir = list(tabs = tabs, buttons = buttons)) + } + + output +} + +doc_tabs_one <- function(key, package) { + lib <- dirname(find.package(package)) + doc_create_chunk(key, lib, package, 1, "<>") +} + +doc_path <- function(package) { + pkgdir <- find.package(package) + docpath <- file.path(pkgdir, "tsdocs") + if (!file.exists(docpath)) { + docpath <- file.path(pkgdir, "inst", "tsdocs") + } + + docpath +} + +doc_create_chunk <- function(key, lib, package, idx, template) { + file <- paste0(key, ".Rd") + path <- file.path(doc_path(package), file) + if (!file.exists(path)) { + return("") + } + x <- read_char(path) + lns <- strsplit(x, "\n", fixed = TRUE)[[1]] + rulepos <- which(lns == "# ---") + lns <- lns[(rulepos + 1):length(lns)] + lang_data <- list( + idx = idx, + package = package, + language = sub("^ts", "", package), + contents = paste(lns, collapse = "\n") + ) + dglue(template, .envir = lang_data) +} + +doc_extra <- function() { + if (is_rcmd_check()) { + return("Placeholder.") + } + tsdocpath <- doc_path("ts") + jspath <- file.path(tsdocpath, "tabs.js") + js <- read_char(jspath) + + csspath1 <- file.path(doc_path("ts"), "w3.css") + css1 <- read_char(csspath1) + css <- "" + if (Sys.getenv("IN_PKGDOWN") == "true") { + csspath2 <- file.path(doc_path("ts"), "pkgdown.css") + css2 <- read_char(csspath2) + css <- gsub("%", "\\%", paste0(css1, "\n\n", css2), fixed = TRUE) + css <- paste0("\n") + } else { + # in Rd we insert CSS with JS, because tidy does not allow