boB Rudis
5 years ago
73 changed files with 4931 additions and 166 deletions
@ -1,26 +1,30 @@ |
|||
Package: curlparse |
|||
Type: Package |
|||
Title: Parse 'URLs' with 'libcurl' |
|||
Version: 0.1.0 |
|||
Date: 2018-09-10 |
|||
Authors@R: c( |
|||
person("Bob", "Rudis", email = "bob@rud.is", role = c("aut", "cre"), |
|||
comment = c(ORCID = "0000-0001-5670-2640")) |
|||
) |
|||
Maintainer: Bob Rudis <bob@rud.is> |
|||
Description: As of version 7.62.0 'libcurl' has exposed its 'URL' parser. Tools |
|||
are provided to parse 'URLs' using this new parser feature. |
|||
Title: Parse URLs Using 'libcurl' |
|||
Version: 0.2.0 |
|||
Authors@R: c( person("Bob", "Rudis", email = "bob@rud.is", role = |
|||
c("aut", "cre"), comment = c(ORCID = "0000-0001-5670-2640")), |
|||
person("Jeroen", "Ooms", role = c("aut"), email = |
|||
"jeroen@berkeley.edu", comment = c(ORCID = |
|||
"0000-0002-4035-0289")) ) |
|||
Description: Tools are provided to parse URLs using the modern |
|||
'libcurl' built-in parser. |
|||
License: MIT + file LICENSE |
|||
SystemRequirements: libcurl: libcurl-devel (rpm) or |
|||
libcurl4-openssl-dev (deb). |
|||
URL: https://gitlab.com/hrbrmstr/curlparse |
|||
BugReports: https://gitlab.com/hrbrmstr/curlparse/issues |
|||
Suggests: |
|||
jsonlite, |
|||
tinytest, |
|||
knitr |
|||
Depends: R (>= 3.2.0) |
|||
LazyData: true |
|||
RoxygenNote: 6.1.1 |
|||
Encoding: UTF-8 |
|||
License: MIT + file LICENSE |
|||
Suggests: |
|||
testthat, |
|||
covr |
|||
Depends: |
|||
R (>= 3.2.0) |
|||
Imports: |
|||
stringi, |
|||
Rcpp |
|||
RoxygenNote: 6.0.1.9000 |
|||
Language: en-US |
|||
LinkingTo: Rcpp |
|||
Imports: |
|||
Rcpp, |
|||
stringi, |
|||
magrittr |
|||
|
@ -1,2 +1,2 @@ |
|||
YEAR: 2018 |
|||
YEAR: 2019 |
|||
COPYRIGHT HOLDER: Bob Rudis |
|||
|
@ -0,0 +1,3 @@ |
|||
#' @rdname scheme |
|||
#' @export |
|||
domain <- host |
@ -0,0 +1,11 @@ |
|||
#' Pipe operator |
|||
#' |
|||
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. |
|||
#' |
|||
#' @name %>% |
|||
#' @rdname pipe |
|||
#' @keywords internal |
|||
#' @export |
|||
#' @importFrom magrittr %>% |
|||
#' @usage lhs \%>\% rhs |
|||
NULL |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,49 @@ |
|||
# DO NOT CHANGE the "init" and "install" sections below |
|||
|
|||
# Download script file from GitHub |
|||
init: |
|||
ps: | |
|||
$ErrorActionPreference = "Stop" |
|||
Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" |
|||
Import-Module '..\appveyor-tool.ps1' |
|||
|
|||
install: |
|||
ps: Bootstrap |
|||
|
|||
# Adapt as necessary starting from here |
|||
|
|||
environment: |
|||
matrix: |
|||
- R_VERSION: stable |
|||
R_ARCH: x64 |
|||
- R_VERSION: devel |
|||
R_ARCH: x64 |
|||
|
|||
build_script: |
|||
- travis-tool.sh install_deps |
|||
|
|||
test_script: |
|||
- travis-tool.sh run_tests |
|||
|
|||
on_failure: |
|||
- 7z a failure.zip *.Rcheck\* |
|||
- appveyor PushArtifact failure.zip |
|||
|
|||
artifacts: |
|||
- path: '*.Rcheck\**\*.log' |
|||
name: Logs |
|||
|
|||
- path: '*.Rcheck\**\*.out' |
|||
name: Logs |
|||
|
|||
- path: '*.Rcheck\**\*.fail' |
|||
name: Logs |
|||
|
|||
- path: '*.Rcheck\**\*.Rout' |
|||
name: Logs |
|||
|
|||
- path: '\*_*.tar.gz' |
|||
name: Bits |
|||
|
|||
- path: '\*_*.zip' |
|||
name: Bits |
@ -0,0 +1,67 @@ |
|||
library(curlparse) |
|||
|
|||
c( |
|||
"", "foo", "foo;params?query#fragment", "http://foo.com/path", "http://foo.com", |
|||
"//foo.com/path", "//user:pass@foo.com/", "http://user:pass@foo.com/", |
|||
"file:///tmp/junk.txt", "imap://mail.python.org/mbox1", |
|||
"mms://wms.sys.hinet.net/cts/Drama/09006251100.asf", "nfs://server/path/to/file.txt", |
|||
"svn+ssh://svn.zope.org/repos/main/ZConfig/trunk/", |
|||
"git+ssh://git@github.com/user/project.git", "HTTP://WWW.PYTHON.ORG/doc/#frag", |
|||
"http://www.python.org:080/", "http://www.python.org:/", "javascript:console.log('hello')", |
|||
"javascript:console.log('hello');console.log('world')", "http://example.com/?", |
|||
"http://example.com/;", "tel:0108202201", "unknown:0108202201", |
|||
"http://user@example.com:8080/path;param?query#fragment", |
|||
"http://www.python.org:65536/", "http://www.python.org:-20/", |
|||
"http://www.python.org:8589934592/", "http://www.python.org:80hello/", |
|||
"http://:::cnn.com/", "http://./", "http://foo..com/", "http://foo../" |
|||
) -> ugly_urls |
|||
|
|||
structure(list(scheme = c(NA, NA, NA, "http", "http", NA, NA, |
|||
"http", "file", "imap", NA, NA, NA, NA, "http", "http", "http", |
|||
NA, NA, "http", "http", NA, NA, "http", NA, NA, NA, NA, NA, "http", |
|||
"http", "http"), user = c(NA, NA, NA, NA, NA, NA, NA, "user", |
|||
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "user", |
|||
NA, NA, NA, NA, NA, NA, NA, NA), password = c(NA, NA, NA, NA, |
|||
NA, NA, NA, "pass", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, |
|||
NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA), host = c(NA, |
|||
NA, NA, "foo.com", "foo.com", NA, NA, "foo.com", NA, "mail.python.org", |
|||
NA, NA, NA, NA, "WWW.PYTHON.ORG", "www.python.org", "www.python.org", |
|||
NA, NA, "example.com", "example.com", NA, NA, "example.com", |
|||
NA, NA, NA, NA, NA, ".", "foo..com", "foo.."), port = c(NA, NA, |
|||
NA, "80", "80", NA, NA, "80", "0", "143", NA, NA, NA, NA, "80", |
|||
"80", "80", NA, NA, "80", "80", NA, NA, "8080", NA, NA, NA, NA, |
|||
NA, "80", "80", "80"), path = c(NA, NA, NA, "/path", "/", NA, |
|||
NA, "/", "/tmp/junk.txt", "/mbox1", NA, NA, NA, NA, "/doc/", |
|||
"/", "/", NA, NA, "/", "/;", NA, NA, "/path;param", NA, NA, NA, |
|||
NA, NA, "/", "/", "/"), options = c(NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_, |
|||
NA_character_, NA_character_, NA_character_, NA_character_, NA_character_ |
|||
), query = c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, |
|||
NA, NA, NA, NA, NA, NA, NA, "", NA, NA, NA, "query", NA, NA, |
|||
NA, NA, NA, NA, NA, NA), fragment = c(NA, NA, NA, NA, NA, NA, |
|||
NA, NA, NA, NA, NA, NA, NA, NA, "frag", NA, NA, NA, NA, NA, NA, |
|||
NA, NA, "fragment", NA, NA, NA, NA, NA, NA, NA, NA)), class = c("tbl_df", |
|||
"tbl", "data.frame"), row.names = c(NA, -32L)) -> shld_equal |
|||
|
|||
u_parsed <- parse_curl(ugly_urls) |
|||
|
|||
expect_identical(u_parsed, shld_equal) |
|||
|
|||
expect_true( |
|||
all( |
|||
c( |
|||
expect_true(identical(u_parsed$scheme, scheme(ugly_urls))), |
|||
expect_true(identical(u_parsed$user, user(ugly_urls))), |
|||
expect_true(identical(u_parsed$password, password(ugly_urls))), |
|||
expect_true(identical(u_parsed$host, host(ugly_urls))), |
|||
expect_true(identical(u_parsed$path, path(ugly_urls))), |
|||
expect_true(identical(u_parsed$query, query(ugly_urls))), |
|||
expect_true(identical(u_parsed$fragment, fragment(ugly_urls)) |
|||
) |
|||
) |
|||
) |
|||
) |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 28 KiB |
@ -0,0 +1,12 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/utils-pipe.R |
|||
\name{\%>\%} |
|||
\alias{\%>\%} |
|||
\title{Pipe operator} |
|||
\usage{ |
|||
lhs \%>\% rhs |
|||
} |
|||
\description{ |
|||
See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. |
|||
} |
|||
\keyword{internal} |
@ -0,0 +1,37 @@ |
|||
; |
|||
; Definition file of WINHTTP.dll |
|||
; Automatic generated by gendef |
|||
; written by Kai Tietz 2008 |
|||
; |
|||
LIBRARY "WINHTTP.dll" |
|||
EXPORTS |
|||
Private1@20 |
|||
SvchostPushServiceGlobals@4 |
|||
WinHttpAddRequestHeaders@16 |
|||
WinHttpAutoProxySvcMain@8 |
|||
WinHttpCheckPlatform@0 |
|||
WinHttpCloseHandle@4 |
|||
WinHttpConnect@16 |
|||
WinHttpCrackUrl@16 |
|||
WinHttpCreateUrl@16 |
|||
WinHttpDetectAutoProxyConfigUrl@8 |
|||
WinHttpGetDefaultProxyConfiguration@4 |
|||
WinHttpGetIEProxyConfigForCurrentUser@4 |
|||
WinHttpGetProxyForUrl@16 |
|||
WinHttpOpen@20 |
|||
WinHttpOpenRequest@28 |
|||
WinHttpQueryAuthSchemes@16 |
|||
WinHttpQueryDataAvailable@8 |
|||
WinHttpQueryHeaders@24 |
|||
WinHttpQueryOption@16 |
|||
WinHttpReadData@16 |
|||
WinHttpReceiveResponse@8 |
|||
WinHttpSendRequest@28 |
|||
WinHttpSetCredentials@24 |
|||
WinHttpSetDefaultProxyConfiguration@4 |
|||
WinHttpSetOption@16 |
|||
WinHttpSetStatusCallback@16 |
|||
WinHttpSetTimeouts@20 |
|||
WinHttpTimeFromSystemTime@8 |
|||
WinHttpTimeToSystemTime@8 |
|||
WinHttpWriteData@16 |
@ -0,0 +1,37 @@ |
|||
; |
|||
; Definition file of WINHTTP.dll |
|||
; Automatic generated by gendef |
|||
; written by Kai Tietz 2008 |
|||
; |
|||
LIBRARY "WINHTTP.dll" |
|||
EXPORTS |
|||
Private1 |
|||
SvchostPushServiceGlobals |
|||
WinHttpAddRequestHeaders |
|||
WinHttpAutoProxySvcMain |
|||
WinHttpCheckPlatform |
|||
WinHttpCloseHandle |
|||
WinHttpConnect |
|||
WinHttpCrackUrl |
|||
WinHttpCreateUrl |
|||
WinHttpDetectAutoProxyConfigUrl |
|||
WinHttpGetDefaultProxyConfiguration |
|||
WinHttpGetIEProxyConfigForCurrentUser |
|||
WinHttpGetProxyForUrl |
|||
WinHttpOpen |
|||
WinHttpOpenRequest |
|||
WinHttpQueryAuthSchemes |
|||
WinHttpQueryDataAvailable |
|||
WinHttpQueryHeaders |
|||
WinHttpQueryOption |
|||
WinHttpReadData |
|||
WinHttpReceiveResponse |
|||
WinHttpSendRequest |
|||
WinHttpSetCredentials |
|||
WinHttpSetDefaultProxyConfiguration |
|||
WinHttpSetOption |
|||
WinHttpSetStatusCallback |
|||
WinHttpSetTimeouts |
|||
WinHttpTimeFromSystemTime |
|||
WinHttpTimeToSystemTime |
|||
WinHttpWriteData |
@ -0,0 +1,5 @@ |
|||
|
|||
if ( requireNamespace("tinytest", quietly=TRUE) ){ |
|||
tinytest::test_package("curlparse") |
|||
} |
|||
|
@ -0,0 +1,82 @@ |
|||
#include "curl-common.h" |
|||
|
|||
int R_curl_callback_progress(SEXP fun, |
|||
double dltotal, double dlnow, |
|||
double ultotal, double ulnow) { |
|||
|
|||
SEXP down = PROTECT(allocVector(REALSXP, 2)); |
|||
REAL(down)[0] = dltotal; |
|||
REAL(down)[1] = dlnow; |
|||
|
|||
SEXP up = PROTECT(allocVector(REALSXP, 2)); |
|||
REAL(up)[0] = ultotal; |
|||
REAL(up)[1] = ulnow; |
|||
|
|||
SEXP call = PROTECT(Rf_lang3(fun, down, up)); |
|||
int ok; |
|||
SEXP res = PROTECT(R_tryEval(call, R_GlobalEnv, &ok)); |
|||
|
|||
if (ok != 0) { |
|||
UNPROTECT(4); |
|||
return CURL_READFUNC_ABORT; |
|||
} |
|||
|
|||
if (TYPEOF(res) != LGLSXP || length(res) != 1) { |
|||
UNPROTECT(4); |
|||
Rf_warning("progress callback must return boolean"); |
|||
return 0; |
|||
} |
|||
|
|||
int out = asLogical(res); |
|||
UNPROTECT(4); |
|||
return !out; |
|||
} |
|||
|
|||
size_t R_curl_callback_read(char *buffer, size_t size, size_t nitems, SEXP fun) { |
|||
SEXP nbytes = PROTECT(ScalarInteger(size * nitems)); |
|||
SEXP call = PROTECT(Rf_lang2(fun, nbytes)); |
|||
|
|||
int ok; |
|||
SEXP res = PROTECT(R_tryEval(call, R_GlobalEnv, &ok)); |
|||
|
|||
if (ok != 0) { |
|||
UNPROTECT(3); |
|||
return CURL_READFUNC_ABORT; |
|||
} |
|||
|
|||
if (TYPEOF(res) != RAWSXP) { |
|||
UNPROTECT(3); |
|||
Rf_warning("read callback must raw vector"); |
|||
return CURL_READFUNC_ABORT; |
|||
} |
|||
|
|||
size_t bytes_read = length(res); |
|||
memcpy(buffer, RAW(res), bytes_read); |
|||
|
|||
UNPROTECT(3); |
|||
return bytes_read; |
|||
} |
|||
|
|||
int R_curl_callback_debug(CURL *handle, curl_infotype type_, char *data, |
|||
size_t size, SEXP fun) { |
|||
|
|||
/* wrap type and msg into R types */ |
|||
SEXP type = PROTECT(ScalarInteger(type_)); |
|||
SEXP msg = PROTECT(allocVector(RAWSXP, size)); |
|||
memcpy(RAW(msg), data, size); |
|||
|
|||
/* call the R function */ |
|||
SEXP call = PROTECT(Rf_lang3(fun, type, msg)); |
|||
R_tryEval(call, R_GlobalEnv, NULL); |
|||
|
|||
UNPROTECT(3); |
|||
// Debug function must always return 0
|
|||
return 0; |
|||
} |
|||
|
|||
|
|||
int R_curl_callback_xferinfo(SEXP fun, |
|||
curl_off_t dltotal, curl_off_t dlnow, |
|||
curl_off_t ultotal, curl_off_t ulnow) { |
|||
return R_curl_callback_progress(fun, dltotal, dlnow, ultotal, ulnow); |
|||
} |
@ -0,0 +1,9 @@ |
|||
int R_curl_callback_progress(SEXP fun, double dltotal, double dlnow, |
|||
double ultotal, double ulnow); |
|||
size_t R_curl_callback_read(char *buffer, size_t size, size_t nitems, SEXP fun); |
|||
int R_curl_callback_debug(CURL *handle, curl_infotype type_, char *data, |
|||
size_t size, SEXP fun); |
|||
|
|||
int R_curl_callback_xferinfo(SEXP fun, |
|||
curl_off_t dltotal, curl_off_t dlnow, |
|||
curl_off_t ultotal, curl_off_t ulnow); |
@ -0,0 +1,67 @@ |
|||
#include <Rinternals.h> |
|||
#include <curl/curl.h> |
|||
#include <curl/easy.h> |
|||
#include <string.h> |
|||
#include <stdlib.h> |
|||
|
|||
#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 28) |
|||
#define HAS_MULTI_WAIT 1 |
|||
#endif |
|||
|
|||
typedef struct { |
|||
unsigned char *buf; |
|||
size_t size; |
|||
} memory; |
|||
|
|||
typedef struct { |
|||
SEXP multiptr; |
|||
SEXP handles; |
|||
CURLM *m; |
|||
} multiref; |
|||
|
|||
typedef struct { |
|||
multiref *mref; |
|||
struct refnode *node; |
|||
memory content; |
|||
SEXP complete; |
|||
SEXP error; |
|||
SEXP data; |
|||
} async; |
|||
|
|||
typedef struct { |
|||
SEXP handleptr; |
|||
CURL *handle; |
|||
struct curl_httppost *form; |
|||
struct curl_slist *headers; |
|||
struct curl_slist *custom; |
|||
char errbuf[CURL_ERROR_SIZE]; |
|||
memory resheaders; |
|||
async async; |
|||
int refCount; |
|||
int locked; |
|||
} reference; |
|||
|
|||
CURL* get_handle(SEXP ptr); |
|||
reference* get_ref(SEXP ptr); |
|||
void assert_status(CURLcode res, reference *ref); |
|||
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); |
|||
void set_form(reference *ref, struct curl_httppost* newform); |
|||
void reset_resheaders(reference *ref); |
|||
void reset_errbuf(reference *ref); |
|||
void clean_handle(reference *ref); |
|||
size_t push_disk(void* contents, size_t sz, size_t nmemb, FILE *ctx); |
|||
size_t append_buffer(void *contents, size_t sz, size_t nmemb, void *ctx); |
|||
size_t data_callback(void * data, size_t sz, size_t nmemb, SEXP fun); |
|||
CURLcode curl_perform_with_interrupt(CURL *handle); |
|||
int pending_interrupt(); |
|||
SEXP make_handle_response(reference *ref); |
|||
|
|||
/* reflist.c */ |
|||
SEXP reflist_init(); |
|||
SEXP reflist_add(SEXP x, SEXP target); |
|||
SEXP reflist_remove(SEXP x, SEXP target); |
@ -0,0 +1,779 @@ |
|||
#include <curl/curl.h> |
|||
|
|||
#define LIBCURL_HAS(x) \ |
|||
(defined(x ## _FIRST) && (x ## _FIRST <= LIBCURL_VERSION_NUM) && \ |
|||
(!defined(x ## _LAST) || ( x ## _LAST >= LIBCURL_VERSION_NUM))) |
|||
|
|||
#define CURLAUTH_ANY_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_ANYSAFE_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_BASIC_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_DIGEST_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_DIGEST_IE_FIRST 0x071303 /* Added in 7.19.3 */ |
|||
#define CURLAUTH_GSSNEGOTIATE_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_NEGOTIATE_FIRST 0x072600 /* Added in 7.38.0 */ |
|||
#define CURLAUTH_NONE_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_NTLM_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLAUTH_NTLM_WB_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURLAUTH_ONLY_FIRST 0x071503 /* Added in 7.21.3 */ |
|||
#define CURLCLOSEPOLICY_CALLBACK_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLCLOSEPOLICY_LEAST_RECENTLY_USED_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLCLOSEPOLICY_LEAST_TRAFFIC_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLCLOSEPOLICY_NONE_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLCLOSEPOLICY_OLDEST_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLCLOSEPOLICY_SLOWEST_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLE_ABORTED_BY_CALLBACK_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_AGAIN_FIRST 0x071202 /* Added in 7.18.2 */ |
|||
#define CURLE_ALREADY_COMPLETE_FIRST 0x070702 /* Added in 7.7.2 */ |
|||
#define CURLE_BAD_CALLING_ORDER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_BAD_CONTENT_ENCODING_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_BAD_DOWNLOAD_RESUME_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_BAD_FUNCTION_ARGUMENT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_BAD_PASSWORD_ENTERED_FIRST 0x070402 /* Added in 7.4.2 */ |
|||
#define CURLE_CHUNK_FAILED_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLE_CONV_FAILED_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLE_CONV_REQD_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLE_COULDNT_CONNECT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_COULDNT_RESOLVE_HOST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_COULDNT_RESOLVE_PROXY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FAILED_INIT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FILESIZE_EXCEEDED_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLE_FILE_COULDNT_READ_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_ACCEPT_FAILED_FIRST 0x071800 /* Added in 7.24.0 */ |
|||
#define CURLE_FTP_ACCEPT_TIMEOUT_FIRST 0x071800 /* Added in 7.24.0 */ |
|||
#define CURLE_FTP_ACCESS_DENIED_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_BAD_DOWNLOAD_RESUME_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_BAD_FILE_LIST_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLE_FTP_CANT_GET_HOST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_CANT_RECONNECT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_GET_SIZE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_RETR_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_SET_ASCII_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_SET_BINARY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_SET_TYPE_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_FTP_COULDNT_STOR_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_COULDNT_USE_REST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_PARTIAL_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_PORT_FAILED_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_PRET_FAILED_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLE_FTP_QUOTE_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_SSL_FAILED_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLE_FTP_USER_PASSWORD_INCORRECT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WEIRD_227_FORMAT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WEIRD_PASS_REPLY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WEIRD_PASV_REPLY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WEIRD_SERVER_REPLY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WEIRD_USER_REPLY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FTP_WRITE_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_FUNCTION_NOT_FOUND_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_GOT_NOTHING_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLE_HTTP2_FIRST 0x072600 /* Added in 7.38.0 */ |
|||
#define CURLE_HTTP_NOT_FOUND_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_HTTP_PORT_FAILED_FIRST 0x070300 /* Added in 7.3 */ |
|||
#define CURLE_HTTP_POST_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_HTTP_RANGE_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_HTTP_RETURNED_ERROR_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLE_INTERFACE_FAILED_FIRST 0x070c00 /* Added in 7.12.0 */ |
|||
#define CURLE_LDAP_CANNOT_BIND_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_LDAP_INVALID_URL_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLE_LDAP_SEARCH_FAILED_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_LIBRARY_NOT_FOUND_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_LOGIN_DENIED_FIRST 0x070d01 /* Added in 7.13.1 */ |
|||
#define CURLE_MALFORMAT_USER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_NOT_BUILT_IN_FIRST 0x071505 /* Added in 7.21.5 */ |
|||
#define CURLE_NO_CONNECTION_AVAILABLE_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLE_OK_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_OPERATION_TIMEDOUT_FIRST 0x070a02 /* Added in 7.10.2 */ |
|||
#define CURLE_OPERATION_TIMEOUTED_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_OUT_OF_MEMORY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_PARTIAL_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_PEER_FAILED_VERIFICATION_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLE_QUOTE_ERROR_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_RANGE_ERROR_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_READ_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_RECV_ERROR_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_REMOTE_ACCESS_DENIED_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_REMOTE_DISK_FULL_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_REMOTE_FILE_EXISTS_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_REMOTE_FILE_NOT_FOUND_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLE_RTSP_CSEQ_ERROR_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLE_RTSP_SESSION_ERROR_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLE_SEND_ERROR_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_SEND_FAIL_REWIND_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLE_SHARE_IN_USE_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLE_SSH_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLE_SSL_CACERT_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_SSL_CACERT_BADFILE_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLE_SSL_CERTPROBLEM_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_SSL_CIPHER_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLE_SSL_CONNECT_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_SSL_CRL_BADFILE_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLE_SSL_ENGINE_INITFAILED_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLE_SSL_ENGINE_NOTFOUND_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLE_SSL_ENGINE_SETFAILED_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLE_SSL_INVALIDCERTSTATUS_FIRST 0x072900 /* Added in 7.41.0 */ |
|||
#define CURLE_SSL_ISSUER_ERROR_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLE_SSL_PEER_CERTIFICATE_FIRST 0x070800 /* Added in 7.8 */ |
|||
#define CURLE_SSL_PINNEDPUBKEYNOTMATCH_FIRST 0x072700 /* Added in 7.39.0 */ |
|||
#define CURLE_SSL_SHUTDOWN_FAILED_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLE_TELNET_OPTION_SYNTAX_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLE_TFTP_DISKFULL_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_EXISTS_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_ILLEGAL_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_NOSUCHUSER_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_NOTFOUND_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_PERM_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TFTP_UNKNOWNID_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLE_TOO_MANY_REDIRECTS_FIRST 0x070500 /* Added in 7.5 */ |
|||
#define CURLE_UNKNOWN_OPTION_FIRST 0x071505 /* Added in 7.21.5 */ |
|||
#define CURLE_UNKNOWN_TELNET_OPTION_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLE_UNSUPPORTED_PROTOCOL_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_UPLOAD_FAILED_FIRST 0x071003 /* Added in 7.16.3 */ |
|||
#define CURLE_URL_MALFORMAT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_URL_MALFORMAT_USER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLE_USE_SSL_FAILED_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLE_WRITE_ERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLFILETYPE_DEVICE_BLOCK_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_DEVICE_CHAR_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_DIRECTORY_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_DOOR_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_FILE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_NAMEDPIPE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_SOCKET_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_SYMLINK_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFILETYPE_UNKNOWN_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_FILENAME_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_FILETYPE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_GID_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_HLINKCOUNT_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_PERM_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_SIZE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_TIME_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFINFOFLAG_KNOWN_UID_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLFORM_ARRAY_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLFORM_ARRAY_END_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLFORM_ARRAY_END_LAST 0x070906 /* Last featured in 7.9.6 */ |
|||
#define CURLFORM_ARRAY_START_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLFORM_ARRAY_START_LAST 0x070906 /* Last featured in 7.9.6 */ |
|||
#define CURLFORM_BUFFER_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURLFORM_BUFFERLENGTH_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURLFORM_BUFFERPTR_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURLFORM_CONTENTHEADER_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLFORM_CONTENTSLENGTH_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_CONTENTTYPE_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_COPYCONTENTS_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_COPYNAME_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_END_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_FILE_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_FILECONTENT_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLFORM_FILENAME_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLFORM_NAMELENGTH_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_NOTHING_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_PTRCONTENTS_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_PTRNAME_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLFORM_STREAM_FIRST 0x071202 /* Added in 7.18.2 */ |
|||
#define CURLFTPAUTH_DEFAULT_FIRST 0x070c02 /* Added in 7.12.2 */ |
|||
#define CURLFTPAUTH_SSL_FIRST 0x070c02 /* Added in 7.12.2 */ |
|||
#define CURLFTPAUTH_TLS_FIRST 0x070c02 /* Added in 7.12.2 */ |
|||
#define CURLFTPMETHOD_DEFAULT_FIRST 0x070f03 /* Added in 7.15.3 */ |
|||
#define CURLFTPMETHOD_MULTICWD_FIRST 0x070f03 /* Added in 7.15.3 */ |
|||
#define CURLFTPMETHOD_NOCWD_FIRST 0x070f03 /* Added in 7.15.3 */ |
|||
#define CURLFTPMETHOD_SINGLECWD_FIRST 0x070f03 /* Added in 7.15.3 */ |
|||
#define CURLFTPSSL_ALL_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLFTPSSL_CCC_ACTIVE_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLFTPSSL_CCC_NONE_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLFTPSSL_CCC_PASSIVE_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLFTPSSL_CONTROL_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLFTPSSL_NONE_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLFTPSSL_TRY_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLFTP_CREATE_DIR_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLFTP_CREATE_DIR_NONE_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLFTP_CREATE_DIR_RETRY_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLGSSAPI_DELEGATION_FLAG_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURLGSSAPI_DELEGATION_NONE_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURLGSSAPI_DELEGATION_POLICY_FLAG_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURLHEADER_SEPARATE_FIRST 0x072500 /* Added in 7.37.0 */ |
|||
#define CURLHEADER_UNIFIED_FIRST 0x072500 /* Added in 7.37.0 */ |
|||
#define CURLINFO_APPCONNECT_TIME_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLINFO_CERTINFO_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLINFO_CONDITION_UNMET_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLINFO_CONNECT_TIME_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_CONTENT_LENGTH_DOWNLOAD_FIRST 0x070601 /* Added in 7.6.1 */ |
|||
#define CURLINFO_CONTENT_LENGTH_UPLOAD_FIRST 0x070601 /* Added in 7.6.1 */ |
|||
#define CURLINFO_CONTENT_TYPE_FIRST 0x070904 /* Added in 7.9.4 */ |
|||
#define CURLINFO_COOKIELIST_FIRST 0x070e01 /* Added in 7.14.1 */ |
|||
#define CURLINFO_DATA_IN_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_DATA_OUT_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_DOUBLE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_EFFECTIVE_URL_FIRST 0x070400 /* Added in 7.4 */ |
|||
#define CURLINFO_END_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_FILETIME_FIRST 0x070500 /* Added in 7.5 */ |
|||
#define CURLINFO_FTP_ENTRY_PATH_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLINFO_HEADER_IN_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_HEADER_OUT_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_HEADER_SIZE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_HTTPAUTH_AVAIL_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLINFO_HTTP_CODE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_HTTP_CONNECTCODE_FIRST 0x070a07 /* Added in 7.10.7 */ |
|||
#define CURLINFO_LASTONE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_LASTSOCKET_FIRST 0x070f02 /* Added in 7.15.2 */ |
|||
#define CURLINFO_LOCAL_IP_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLINFO_LOCAL_PORT_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLINFO_LONG_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_MASK_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_NAMELOOKUP_TIME_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_NONE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_NUM_CONNECTS_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLINFO_OS_ERRNO_FIRST 0x070c02 /* Added in 7.12.2 */ |
|||
#define CURLINFO_PRETRANSFER_TIME_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_PRIMARY_IP_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLINFO_PRIMARY_PORT_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLINFO_PRIVATE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLINFO_PROXYAUTH_AVAIL_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLINFO_REDIRECT_COUNT_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURLINFO_REDIRECT_TIME_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURLINFO_REDIRECT_URL_FIRST 0x071202 /* Added in 7.18.2 */ |
|||
#define CURLINFO_REQUEST_SIZE_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_RESPONSE_CODE_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLINFO_RTSP_CLIENT_CSEQ_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLINFO_RTSP_CSEQ_RECV_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLINFO_RTSP_SERVER_CSEQ_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLINFO_RTSP_SESSION_ID_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLINFO_SIZE_DOWNLOAD_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_SIZE_UPLOAD_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_SLIST_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLINFO_SPEED_DOWNLOAD_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_SPEED_UPLOAD_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_SSL_DATA_IN_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLINFO_SSL_DATA_OUT_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLINFO_SSL_ENGINES_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLINFO_SSL_VERIFYRESULT_FIRST 0x070500 /* Added in 7.5 */ |
|||
#define CURLINFO_STARTTRANSFER_TIME_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURLINFO_STRING_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_TEXT_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLINFO_TLS_SESSION_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLINFO_TOTAL_TIME_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLINFO_TYPEMASK_FIRST 0x070401 /* Added in 7.4.1 */ |
|||
#define CURLIOCMD_NOP_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLIOCMD_RESTARTREAD_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLIOE_FAILRESTART_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLIOE_OK_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLIOE_UNKNOWNCMD_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLKHMATCH_MISMATCH_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHMATCH_MISSING_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHMATCH_OK_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHSTAT_DEFER_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHSTAT_FINE_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHSTAT_FINE_ADD_TO_FILE_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHSTAT_REJECT_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHTYPE_DSS_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHTYPE_RSA_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHTYPE_RSA1_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLKHTYPE_UNKNOWN_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_MAXCONNECTS_FIRST 0x071003 /* Added in 7.16.3 */ |
|||
#define CURLMOPT_MAX_HOST_CONNECTIONS_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_MAX_PIPELINE_LENGTH_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_MAX_TOTAL_CONNECTIONS_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_PIPELINING_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLMOPT_PIPELINING_SERVER_BL_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_PIPELINING_SITE_BL_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURLMOPT_SOCKETDATA_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLMOPT_SOCKETFUNCTION_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLMOPT_TIMERDATA_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLMOPT_TIMERFUNCTION_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLMSG_DONE_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLMSG_NONE_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_ADDED_ALREADY_FIRST 0x072001 /* Added in 7.32.1 */ |
|||
#define CURLM_BAD_EASY_HANDLE_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_BAD_HANDLE_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_BAD_SOCKET_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLM_CALL_MULTI_PERFORM_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_CALL_MULTI_SOCKET_FIRST 0x070f05 /* Added in 7.15.5 */ |
|||
#define CURLM_INTERNAL_ERROR_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_OK_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_OUT_OF_MEMORY_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLM_UNKNOWN_OPTION_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLOPTTYPE_FUNCTIONPOINT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPTTYPE_LONG_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPTTYPE_OBJECTPOINT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPTTYPE_OFF_T_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_ACCEPTTIMEOUT_MS_FIRST 0x071800 /* Added in 7.24.0 */ |
|||
#define CURLOPT_ACCEPT_ENCODING_FIRST 0x071506 /* Added in 7.21.6 */ |
|||
#define CURLOPT_ADDRESS_SCOPE_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLOPT_APPEND_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLOPT_AUTOREFERER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_BUFFERSIZE_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_CAINFO_FIRST 0x070402 /* Added in 7.4.2 */ |
|||
#define CURLOPT_CAPATH_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURLOPT_CERTINFO_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_CHUNK_BGN_FUNCTION_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_CHUNK_DATA_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_CHUNK_END_FUNCTION_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_CLOSEFUNCTION_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_CLOSEFUNCTION_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_CLOSEPOLICY_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_CLOSESOCKETDATA_FIRST 0x071507 /* Added in 7.21.7 */ |
|||
#define CURLOPT_CLOSESOCKETFUNCTION_FIRST 0x071507 /* Added in 7.21.7 */ |
|||
#define CURLOPT_CONNECTTIMEOUT_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_CONNECTTIMEOUT_MS_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLOPT_CONNECT_ONLY_FIRST 0x070f02 /* Added in 7.15.2 */ |
|||
#define CURLOPT_CONV_FROM_NETWORK_FUNCTION_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLOPT_CONV_FROM_UTF8_FUNCTION_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLOPT_CONV_TO_NETWORK_FUNCTION_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURLOPT_COOKIE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_COOKIEFILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_COOKIEJAR_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLOPT_COOKIELIST_FIRST 0x070e01 /* Added in 7.14.1 */ |
|||
#define CURLOPT_COOKIESESSION_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURLOPT_COPYPOSTFIELDS_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLOPT_CRLF_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_CRLFILE_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLOPT_CUSTOMREQUEST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_DEBUGDATA_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLOPT_DEBUGFUNCTION_FIRST 0x070906 /* Added in 7.9.6 */ |
|||
#define CURLOPT_DIRLISTONLY_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLOPT_DNS_CACHE_TIMEOUT_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_DNS_INTERFACE_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURLOPT_DNS_LOCAL_IP4_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURLOPT_DNS_LOCAL_IP6_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURLOPT_DNS_SERVERS_FIRST 0x071800 /* Added in 7.24.0 */ |
|||
#define CURLOPT_DNS_USE_GLOBAL_CACHE_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_EGDSOCKET_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_ENCODING_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_ERRORBUFFER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_EXPECT_100_TIMEOUT_MS_FIRST 0x072400 /* Added in 7.36.0 */ |
|||
#define CURLOPT_FAILONERROR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FILETIME_FIRST 0x070500 /* Added in 7.5 */ |
|||
#define CURLOPT_FNMATCH_DATA_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_FNMATCH_FUNCTION_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_FOLLOWLOCATION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FORBID_REUSE_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_FRESH_CONNECT_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_FTPAPPEND_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FTPASCII_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FTPASCII_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_FTPLISTONLY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FTPPORT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_FTPSSLAUTH_FIRST 0x070c02 /* Added in 7.12.2 */ |
|||
#define CURLOPT_FTP_ACCOUNT_FIRST 0x070d00 /* Added in 7.13.0 */ |
|||
#define CURLOPT_FTP_ALTERNATIVE_TO_USER_FIRST 0x070f05 /* Added in 7.15.5 */ |
|||
#define CURLOPT_FTP_CREATE_MISSING_DIRS_FIRST 0x070a07 /* Added in 7.10.7 */ |
|||
#define CURLOPT_FTP_FILEMETHOD_FIRST 0x070f01 /* Added in 7.15.1 */ |
|||
#define CURLOPT_FTP_RESPONSE_TIMEOUT_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLOPT_FTP_SKIP_PASV_IP_FIRST 0x070f00 /* Added in 7.15.0 */ |
|||
#define CURLOPT_FTP_SSL_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_FTP_SSL_CCC_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLOPT_FTP_USE_EPRT_FIRST 0x070a05 /* Added in 7.10.5 */ |
|||
#define CURLOPT_FTP_USE_EPSV_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURLOPT_FTP_USE_PRET_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_GSSAPI_DELEGATION_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURLOPT_HEADER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_HEADERDATA_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_HEADERFUNCTION_FIRST 0x070702 /* Added in 7.7.2 */ |
|||
#define CURLOPT_HEADEROPT_FIRST 0x072500 /* Added in 7.37.0 */ |
|||
#define CURLOPT_HTTP200ALIASES_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLOPT_HTTPAUTH_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLOPT_HTTPGET_FIRST 0x070801 /* Added in 7.8.1 */ |
|||
#define CURLOPT_HTTPHEADER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_HTTPPOST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_HTTPPROXYTUNNEL_FIRST 0x070300 /* Added in 7.3 */ |
|||
#define CURLOPT_HTTPREQUEST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_HTTPREQUEST_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_HTTP_CONTENT_DECODING_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLOPT_HTTP_TRANSFER_DECODING_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLOPT_HTTP_VERSION_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURLOPT_IGNORE_CONTENT_LENGTH_FIRST 0x070e01 /* Added in 7.14.1 */ |
|||
#define CURLOPT_INFILE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_INFILESIZE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_INFILESIZE_LARGE_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_INTERFACE_FIRST 0x070300 /* Added in 7.3 */ |
|||
#define CURLOPT_INTERLEAVEDATA_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_INTERLEAVEFUNCTION_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_IOCTLDATA_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLOPT_IOCTLFUNCTION_FIRST 0x070c03 /* Added in 7.12.3 */ |
|||
#define CURLOPT_IPRESOLVE_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLOPT_ISSUERCERT_FIRST 0x071300 /* Added in 7.19.0 */ |
|||
#define CURLOPT_KEYPASSWD_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLOPT_KRB4LEVEL_FIRST 0x070300 /* Added in 7.3 */ |
|||
#define CURLOPT_KRBLEVEL_FIRST 0x071004 /* Added in 7.16.4 */ |
|||
#define CURLOPT_LOCALPORT_FIRST 0x070f02 /* Added in 7.15.2 */ |
|||
#define CURLOPT_LOCALPORTRANGE_FIRST 0x070f02 /* Added in 7.15.2 */ |
|||
#define CURLOPT_LOGIN_OPTIONS_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLOPT_LOW_SPEED_LIMIT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_LOW_SPEED_TIME_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_MAIL_AUTH_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLOPT_MAIL_FROM_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_MAIL_RCPT_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_MAXCONNECTS_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_MAXFILESIZE_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURLOPT_MAXFILESIZE_LARGE_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_MAXREDIRS_FIRST 0x070500 /* Added in 7.5 */ |
|||
#define CURLOPT_MAX_RECV_SPEED_LARGE_FIRST 0x070f05 /* Added in 7.15.5 */ |
|||
#define CURLOPT_MAX_SEND_SPEED_LARGE_FIRST 0x070f05 /* Added in 7.15.5 */ |
|||
#define CURLOPT_MUTE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_MUTE_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_NETRC_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_NETRC_FILE_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_NEW_DIRECTORY_PERMS_FIRST 0x071004 /* Added in 7.16.4 */ |
|||
#define CURLOPT_NEW_FILE_PERMS_FIRST 0x071004 /* Added in 7.16.4 */ |
|||
#define CURLOPT_NOBODY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_NOPROGRESS_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_NOPROXY_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_NOSIGNAL_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_NOTHING_FIRST 0x070101 /* Added in 7.1.1 */ |
|||
#define CURLOPT_NOTHING_LAST 0x070b00 /* Last featured in 7.11.0 */ |
|||
#define CURLOPT_OPENSOCKETDATA_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLOPT_OPENSOCKETFUNCTION_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLOPT_PASSWDDATA_FIRST 0x070402 /* Added in 7.4.2 */ |
|||
#define CURLOPT_PASSWDDATA_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_PASSWDFUNCTION_FIRST 0x070402 /* Added in 7.4.2 */ |
|||
#define CURLOPT_PASSWDFUNCTION_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_PASSWORD_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_PASV_HOST_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_PASV_HOST_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_PINNEDPUBLICKEY_FIRST 0x072700 /* Added in 7.39.0 */ |
|||
#define CURLOPT_PORT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_POST_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_POST301_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLOPT_POSTFIELDS_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_POSTFIELDSIZE_FIRST 0x070200 /* Added in 7.2 */ |
|||
#define CURLOPT_POSTFIELDSIZE_LARGE_FIRST 0x070b01 /* Added in 7.11.1 */ |
|||
#define CURLOPT_POSTQUOTE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_POSTREDIR_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_PREQUOTE_FIRST 0x070905 /* Added in 7.9.5 */ |
|||
#define CURLOPT_PRIVATE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLOPT_PROGRESSDATA_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_PROGRESSFUNCTION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_PROTOCOLS_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_PROXY_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_PROXYAUTH_FIRST 0x070a07 /* Added in 7.10.7 */ |
|||
#define CURLOPT_PROXYHEADER_FIRST 0x072500 /* Added in 7.37.0 */ |
|||
#define CURLOPT_PROXYPASSWORD_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_PROXYPORT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_PROXYTYPE_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_PROXYUSERNAME_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_PROXYUSERPWD_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_PROXY_TRANSFER_MODE_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLOPT_PUT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_QUOTE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_RANDOM_FILE_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_RANGE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_READDATA_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURLOPT_READFUNCTION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_REDIR_PROTOCOLS_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_REFERER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_RESOLVE_FIRST 0x071503 /* Added in 7.21.3 */ |
|||
#define CURLOPT_RESUME_FROM_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_RESUME_FROM_LARGE_FIRST 0x070b00 /* Added in 7.11.0 */ |
|||
#define CURLOPT_RTSPHEADER_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_CLIENT_CSEQ_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_REQUEST_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_SERVER_CSEQ_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_SESSION_ID_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_STREAM_URI_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_RTSP_TRANSPORT_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_SASL_IR_FIRST 0x071f00 /* Added in 7.31.0 */ |
|||
#define CURLOPT_SEEKDATA_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLOPT_SEEKFUNCTION_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLOPT_SERVER_RESPONSE_TIMEOUT_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLOPT_SHARE_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLOPT_SOCKOPTDATA_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLOPT_SOCKOPTFUNCTION_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLOPT_SOCKS5_GSSAPI_NEC_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_SOCKS5_GSSAPI_SERVICE_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_SOURCE_HOST_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_HOST_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_PATH_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_PATH_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_PORT_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_PORT_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_POSTQUOTE_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_POSTQUOTE_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_PREQUOTE_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_PREQUOTE_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_QUOTE_FIRST 0x070d00 /* Added in 7.13.0 */ |
|||
#define CURLOPT_SOURCE_QUOTE_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_URL_FIRST 0x070d00 /* Added in 7.13.0 */ |
|||
#define CURLOPT_SOURCE_URL_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SOURCE_USERPWD_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURLOPT_SOURCE_USERPWD_LAST 0x070f05 /* Last featured in 7.15.5 */ |
|||
#define CURLOPT_SSH_AUTH_TYPES_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLOPT_SSH_HOST_PUBLIC_KEY_MD5_FIRST 0x071101 /* Added in 7.17.1 */ |
|||
#define CURLOPT_SSH_KEYDATA_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLOPT_SSH_KEYFUNCTION_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLOPT_SSH_KNOWNHOSTS_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURLOPT_SSH_PRIVATE_KEYFILE_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLOPT_SSH_PUBLIC_KEYFILE_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLOPT_SSLCERT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_SSLCERTPASSWD_FIRST 0x070101 /* Added in 7.1.1 */ |
|||
#define CURLOPT_SSLCERTTYPE_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLENGINE_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLENGINE_DEFAULT_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLKEY_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLKEYPASSWD_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLKEYTYPE_FIRST 0x070903 /* Added in 7.9.3 */ |
|||
#define CURLOPT_SSLVERSION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_SSL_CIPHER_LIST_FIRST 0x070900 /* Added in 7.9 */ |
|||
#define CURLOPT_SSL_CTX_DATA_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLOPT_SSL_CTX_FUNCTION_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURLOPT_SSL_ENABLE_ALPN_FIRST 0x072400 /* Added in 7.36.0 */ |
|||
#define CURLOPT_SSL_ENABLE_NPN_FIRST 0x072400 /* Added in 7.36.0 */ |
|||
#define CURLOPT_SSL_OPTIONS_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLOPT_SSL_SESSIONID_CACHE_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLOPT_SSL_VERIFYHOST_FIRST 0x070801 /* Added in 7.8.1 */ |
|||
#define CURLOPT_SSL_VERIFYPEER_FIRST 0x070402 /* Added in 7.4.2 */ |
|||
#define CURLOPT_SSL_VERIFYSTATUS_FIRST 0x072900 /* Added in 7.41.0 */ |
|||
#define CURLOPT_STDERR_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_TCP_KEEPALIVE_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLOPT_TCP_KEEPIDLE_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLOPT_TCP_KEEPINTVL_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLOPT_TCP_NODELAY_FIRST 0x070b02 /* Added in 7.11.2 */ |
|||
#define CURLOPT_TELNETOPTIONS_FIRST 0x070700 /* Added in 7.7 */ |
|||
#define CURLOPT_TFTP_BLKSIZE_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLOPT_TIMECONDITION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_TIMEOUT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_TIMEOUT_MS_FIRST 0x071002 /* Added in 7.16.2 */ |
|||
#define CURLOPT_TIMEVALUE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_TLSAUTH_PASSWORD_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURLOPT_TLSAUTH_TYPE_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURLOPT_TLSAUTH_USERNAME_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURLOPT_TRANSFERTEXT_FIRST 0x070101 /* Added in 7.1.1 */ |
|||
#define CURLOPT_TRANSFER_ENCODING_FIRST 0x071506 /* Added in 7.21.6 */ |
|||
#define CURLOPT_UNIX_SOCKET_PATH_FIRST 0x072800 /* Added in 7.40.0 */ |
|||
#define CURLOPT_UNRESTRICTED_AUTH_FIRST 0x070a04 /* Added in 7.10.4 */ |
|||
#define CURLOPT_UPLOAD_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_URL_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_USERAGENT_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_USERNAME_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURLOPT_USERPWD_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_USE_SSL_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLOPT_VERBOSE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_WILDCARDMATCH_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLOPT_WRITEDATA_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURLOPT_WRITEFUNCTION_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_WRITEHEADER_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_WRITEINFO_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURLOPT_XFERINFODATA_FIRST 0x072000 /* Added in 7.32.0 */ |
|||
#define CURLOPT_XFERINFOFUNCTION_FIRST 0x072000 /* Added in 7.32.0 */ |
|||
#define CURLOPT_XOAUTH2_BEARER_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURLPAUSE_ALL_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPAUSE_CONT_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPAUSE_RECV_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPAUSE_RECV_CONT_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPAUSE_SEND_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPAUSE_SEND_CONT_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPROTO_ALL_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_DICT_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_FILE_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_FTP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_FTPS_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_GOPHER_FIRST 0x071502 /* Added in 7.21.2 */ |
|||
#define CURLPROTO_HTTP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_HTTPS_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_IMAP_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_IMAPS_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_LDAP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_LDAPS_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_POP3_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_POP3S_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_RTMP_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTMPE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTMPS_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTMPT_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTMPTE_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTMPTS_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURLPROTO_RTSP_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_SCP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_SFTP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_SMB_FIRST 0x072800 /* Added in 7.40.0 */ |
|||
#define CURLPROTO_SMBS_FIRST 0x072800 /* Added in 7.40.0 */ |
|||
#define CURLPROTO_SMTP_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_SMTPS_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURLPROTO_TELNET_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROTO_TFTP_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROXY_HTTP_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLPROXY_HTTP_1_0_FIRST 0x071304 /* Added in 7.19.4 */ |
|||
#define CURLPROXY_SOCKS4_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLPROXY_SOCKS4A_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLPROXY_SOCKS5_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLPROXY_SOCKS5_HOSTNAME_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURLSHE_BAD_OPTION_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHE_INVALID_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHE_IN_USE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHE_NOMEM_FIRST 0x070c00 /* Added in 7.12.0 */ |
|||
#define CURLSHE_NOT_BUILT_IN_FIRST 0x071700 /* Added in 7.23.0 */ |
|||
#define CURLSHE_OK_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_LOCKFUNC_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_NONE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_SHARE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_UNLOCKFUNC_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_UNSHARE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSHOPT_USERDATA_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURLSOCKTYPE_ACCEPT_FIRST 0x071c00 /* Added in 7.28.0 */ |
|||
#define CURLSOCKTYPE_IPCXN_FIRST 0x071000 /* Added in 7.16.0 */ |
|||
#define CURLSSH_AUTH_AGENT_FIRST 0x071c00 /* Added in 7.28.0 */ |
|||
#define CURLSSH_AUTH_ANY_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_DEFAULT_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_HOST_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_KEYBOARD_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_NONE_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_PASSWORD_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSH_AUTH_PUBLICKEY_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLSSLBACKEND_AXTLS_FIRST 0x072600 /* Added in 7.38.0 */ |
|||
#define CURLSSLBACKEND_CYASSL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_DARWINSSL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_GNUTLS_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_GSKIT_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_NONE_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_NSS_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_OPENSSL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_POLARSSL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_QSOSSL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLBACKEND_QSOSSL_LAST 0x072601 /* Last featured in 7.38.1 */ |
|||
#define CURLSSLBACKEND_SCHANNEL_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURLSSLOPT_ALLOW_BEAST_FIRST 0x071900 /* Added in 7.25.0 */ |
|||
#define CURLUSESSL_ALL_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLUSESSL_CONTROL_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLUSESSL_NONE_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLUSESSL_TRY_FIRST 0x071100 /* Added in 7.17.0 */ |
|||
#define CURLVERSION_FIRST_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLVERSION_FOURTH_FIRST 0x071001 /* Added in 7.16.1 */ |
|||
#define CURLVERSION_NOW_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURLVERSION_SECOND_FIRST 0x070b01 /* Added in 7.11.1 */ |
|||
#define CURLVERSION_THIRD_FIRST 0x070c00 /* Added in 7.12.0 */ |
|||
#define CURL_CHUNK_BGN_FUNC_FAIL_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_CHUNK_BGN_FUNC_OK_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_CHUNK_BGN_FUNC_SKIP_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_CHUNK_END_FUNC_FAIL_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_CHUNK_END_FUNC_OK_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_CSELECT_ERR_FIRST 0x071003 /* Added in 7.16.3 */ |
|||
#define CURL_CSELECT_IN_FIRST 0x071003 /* Added in 7.16.3 */ |
|||
#define CURL_CSELECT_OUT_FIRST 0x071003 /* Added in 7.16.3 */ |
|||
#define CURL_EASY_NONE_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_EASY_NONE_LAST 0x070f04 /* Last featured in 7.15.4 */ |
|||
#define CURL_EASY_TIMEOUT_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_EASY_TIMEOUT_LAST 0x070f04 /* Last featured in 7.15.4 */ |
|||
#define CURL_ERROR_SIZE_FIRST 0x070100 /* Added in 7.1 */ |
|||
#define CURL_FNMATCHFUNC_FAIL_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_FNMATCHFUNC_MATCH_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_FNMATCHFUNC_NOMATCH_FIRST 0x071500 /* Added in 7.21.0 */ |
|||
#define CURL_FORMADD_DISABLED_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURL_FORMADD_ILLEGAL_ARRAY_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_INCOMPLETE_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_MEMORY_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_NULL_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_OK_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_OPTION_TWICE_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_FORMADD_UNKNOWN_OPTION_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_GLOBAL_ACK_EINTR_FIRST 0x071e00 /* Added in 7.30.0 */ |
|||
#define CURL_GLOBAL_ALL_FIRST 0x070800 /* Added in 7.8 */ |
|||
#define CURL_GLOBAL_DEFAULT_FIRST 0x070800 /* Added in 7.8 */ |
|||
#define CURL_GLOBAL_NOTHING_FIRST 0x070800 /* Added in 7.8 */ |
|||
#define CURL_GLOBAL_SSL_FIRST 0x070800 /* Added in 7.8 */ |
|||
#define CURL_GLOBAL_WIN32_FIRST 0x070801 /* Added in 7.8.1 */ |
|||
#define CURL_HTTP_VERSION_1_0_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURL_HTTP_VERSION_1_1_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURL_HTTP_VERSION_2_0_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURL_HTTP_VERSION_NONE_FIRST 0x070901 /* Added in 7.9.1 */ |
|||
#define CURL_IPRESOLVE_V4_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURL_IPRESOLVE_V6_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURL_IPRESOLVE_WHATEVER_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURL_LOCK_ACCESS_NONE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_ACCESS_SHARED_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_ACCESS_SINGLE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_DATA_CONNECT_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_DATA_COOKIE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_DATA_DNS_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_DATA_NONE_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_DATA_SHARE_FIRST 0x070a04 /* Added in 7.10.4 */ |
|||
#define CURL_LOCK_DATA_SSL_SESSION_FIRST 0x070a03 /* Added in 7.10.3 */ |
|||
#define CURL_LOCK_TYPE_CONNECT_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_LOCK_TYPE_CONNECT_LAST 0x070a02 /* Last featured in 7.10.2 */ |
|||
#define CURL_LOCK_TYPE_COOKIE_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_LOCK_TYPE_COOKIE_LAST 0x070a02 /* Last featured in 7.10.2 */ |
|||
#define CURL_LOCK_TYPE_DNS_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_LOCK_TYPE_DNS_LAST 0x070a02 /* Last featured in 7.10.2 */ |
|||
#define CURL_LOCK_TYPE_NONE_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_LOCK_TYPE_NONE_LAST 0x070a02 /* Last featured in 7.10.2 */ |
|||
#define CURL_LOCK_TYPE_SSL_SESSION_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_LOCK_TYPE_SSL_SESSION_LAST 0x070a02 /* Last featured in 7.10.2 */ |
|||
#define CURL_MAX_HTTP_HEADER_FIRST 0x071307 /* Added in 7.19.7 */ |
|||
#define CURL_MAX_WRITE_SIZE_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURL_NETRC_IGNORED_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_NETRC_OPTIONAL_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_NETRC_REQUIRED_FIRST 0x070908 /* Added in 7.9.8 */ |
|||
#define CURL_POLL_IN_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_POLL_INOUT_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_POLL_NONE_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_POLL_OUT_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_POLL_REMOVE_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_PROGRESS_BAR_FIRST 0x070101 /* Added in 7.1.1 */ |
|||
#define CURL_PROGRESS_BAR_LAST 0x070401 /* Last featured in 7.4.1 */ |
|||
#define CURL_PROGRESS_STATS_FIRST 0x070101 /* Added in 7.1.1 */ |
|||
#define CURL_PROGRESS_STATS_LAST 0x070401 /* Last featured in 7.4.1 */ |
|||
#define CURL_READFUNC_ABORT_FIRST 0x070c01 /* Added in 7.12.1 */ |
|||
#define CURL_READFUNC_PAUSE_FIRST 0x071200 /* Added in 7.18.0 */ |
|||
#define CURL_REDIR_GET_ALL_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURL_REDIR_POST_301_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURL_REDIR_POST_302_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURL_REDIR_POST_303_FIRST 0x071901 /* Added in 7.25.1 */ |
|||
#define CURL_REDIR_POST_ALL_FIRST 0x071301 /* Added in 7.19.1 */ |
|||
#define CURL_RTSPREQ_ANNOUNCE_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_DESCRIBE_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_GET_PARAMETER_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_NONE_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_OPTIONS_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_PAUSE_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_PLAY_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_RECEIVE_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_RECORD_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_SETUP_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_SET_PARAMETER_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_RTSPREQ_TEARDOWN_FIRST 0x071400 /* Added in 7.20.0 */ |
|||
#define CURL_SEEKFUNC_CANTSEEK_FIRST 0x071305 /* Added in 7.19.5 */ |
|||
#define CURL_SEEKFUNC_FAIL_FIRST 0x071305 /* Added in 7.19.5 */ |
|||
#define CURL_SEEKFUNC_OK_FIRST 0x071305 /* Added in 7.19.5 */ |
|||
#define CURL_SOCKET_BAD_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_SOCKET_TIMEOUT_FIRST 0x070e00 /* Added in 7.14.0 */ |
|||
#define CURL_SOCKOPT_ALREADY_CONNECTED_FIRST 0x071505 /* Added in 7.21.5 */ |
|||
#define CURL_SOCKOPT_ERROR_FIRST 0x071505 /* Added in 7.21.5 */ |
|||
#define CURL_SOCKOPT_OK_FIRST 0x071505 /* Added in 7.21.5 */ |
|||
#define CURL_SSLVERSION_DEFAULT_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURL_SSLVERSION_SSLv2_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURL_SSLVERSION_SSLv3_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURL_SSLVERSION_TLSv1_FIRST 0x070902 /* Added in 7.9.2 */ |
|||
#define CURL_SSLVERSION_TLSv1_0_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURL_SSLVERSION_TLSv1_1_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURL_SSLVERSION_TLSv1_2_FIRST 0x072200 /* Added in 7.34.0 */ |
|||
#define CURL_TIMECOND_IFMODSINCE_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURL_TIMECOND_IFUNMODSINCE_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURL_TIMECOND_LASTMOD_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURL_TIMECOND_NONE_FIRST 0x070907 /* Added in 7.9.7 */ |
|||
#define CURL_TLSAUTH_NONE_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURL_TLSAUTH_SRP_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURL_VERSION_ASYNCHDNS_FIRST 0x070a07 /* Added in 7.10.7 */ |
|||
#define CURL_VERSION_CONV_FIRST 0x070f04 /* Added in 7.15.4 */ |
|||
#define CURL_VERSION_CURLDEBUG_FIRST 0x071306 /* Added in 7.19.6 */ |
|||
#define CURL_VERSION_DEBUG_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURL_VERSION_GSSAPI_FIRST 0x072600 /* Added in 7.38.0 */ |
|||
#define CURL_VERSION_GSSNEGOTIATE_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURL_VERSION_HTTP2_FIRST 0x072100 /* Added in 7.33.0 */ |
|||
#define CURL_VERSION_IDN_FIRST 0x070c00 /* Added in 7.12.0 */ |
|||
#define CURL_VERSION_IPV6_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_VERSION_KERBEROS4_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_VERSION_KERBEROS5_FIRST 0x072800 /* Added in 7.40.0 */ |
|||
#define CURL_VERSION_LARGEFILE_FIRST 0x070b01 /* Added in 7.11.1 */ |
|||
#define CURL_VERSION_LIBZ_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_VERSION_NTLM_FIRST 0x070a06 /* Added in 7.10.6 */ |
|||
#define CURL_VERSION_NTLM_WB_FIRST 0x071600 /* Added in 7.22.0 */ |
|||
#define CURL_VERSION_SPNEGO_FIRST 0x070a08 /* Added in 7.10.8 */ |
|||
#define CURL_VERSION_SSL_FIRST 0x070a00 /* Added in 7.10 */ |
|||
#define CURL_VERSION_SSPI_FIRST 0x070d02 /* Added in 7.13.2 */ |
|||
#define CURL_VERSION_TLSAUTH_SRP_FIRST 0x071504 /* Added in 7.21.4 */ |
|||
#define CURL_VERSION_UNIX_SOCKETS_FIRST 0x072800 /* Added in 7.40.0 */ |
|||
#define CURL_WAIT_POLLIN_FIRST 0x071c00 /* Added in 7.28.0 */ |
|||
#define CURL_WAIT_POLLOUT_FIRST 0x071c00 /* Added in 7.28.0 */ |
|||
#define CURL_WAIT_POLLPRI_FIRST 0x071c00 /* Added in 7.28.0 */ |
|||
#define CURL_WRITEFUNC_PAUSE_FIRST 0x071200 /* Added in 7.18.0 */ |
@ -0,0 +1,81 @@ |
|||
#' 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}}. |
|||
#' |
|||
#' 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 \code{\link{isIncomplete}} to check if the download has completed |
|||
#' yet. |
|||
#' |
|||
#' @useDynLib curl R_curl_connection |
|||
#' @export |
|||
#' @param url character string. See examples. |
|||
#' @param open character string. How to open the connection if it should be opened |
|||
#' initially. Currently only "r" and "rb" are supported. |
|||
#' @param handle a curl handle object |
|||
#' @examples \dontrun{ |
|||
#' con <- curl("https://httpbin.org/get") |
|||
#' readLines(con) |
|||
#' |
|||
#' # Auto-opened connections can be recycled |
|||
#' open(con, "rb") |
|||
#' bin <- readBin(con, raw(), 999) |
|||
#' close(con) |
|||
#' rawToChar(bin) |
|||
#' |
|||
#' # HTTP error |
|||
#' curl("https://httpbin.org/status/418", "r") |
|||
#' |
|||
#' # Follow redirects |
|||
#' readLines(curl("https://httpbin.org/redirect/3")) |
|||
#' |
|||
#' # Error after redirect |
|||
#' curl("https://httpbin.org/redirect-to?url=http://httpbin.org/status/418", "r") |
|||
#' |
|||
#' # Auto decompress Accept-Encoding: gzip / deflate (rfc2616 #14.3) |
|||
#' readLines(curl("http://httpbin.org/gzip")) |
|||
#' readLines(curl("http://httpbin.org/deflate")) |
|||
#' |
|||
#' # Binary support |
|||
#' buf <- readBin(curl("http://httpbin.org/bytes/98765", "rb"), raw(), 1e5) |
|||
#' length(buf) |
|||
#' |
|||
#' # Read file from disk |
|||
#' test <- paste0("file://", system.file("DESCRIPTION")) |
|||
#' readLines(curl(test)) |
|||
#' |
|||
#' # Other protocols |
|||
#' read.csv(curl("ftp://cran.r-project.org/pub/R/CRAN_mirrors.csv")) |
|||
#' readLines(curl("ftps://test.rebex.net:990/readme.txt")) |
|||
#' readLines(curl("gopher://quux.org/1")) |
|||
#' |
|||
#' # Streaming data |
|||
#' con <- curl("http://jeroen.github.io/data/diamonds.json", "r") |
|||
#' while(length(x <- readLines(con, n = 5))){ |
|||
#' print(x) |
|||
#' } |
|||
#' |
|||
#' # Stream large dataset over https with gzip |
|||
#' library(jsonlite) |
|||
#' con <- gzcon(curl("https://jeroen.github.io/data/nycflights13.json.gz")) |
|||
#' nycflights <- stream_in(con) |
|||
#' } |
|||
#' |
|||
curl <- function(url = "http://httpbin.org/get", open = "", handle = new_handle()){ |
|||
curl_connection(url, open, handle) |
|||
} |
|||
|
|||
# 'stream' currently only used for non-blocking connections to prevent |
|||
# busy looping in curl_fetch_stream() |
|||
curl_connection <- function(url, mode, handle, partial = FALSE){ |
|||
con <- .Call(R_curl_connection, url, handle, partial) |
|||
if(!identical(mode, "")){ |
|||
withCallingHandlers(open(con, open = mode), error = function(err) { |
|||
close(con) |
|||
}) |
|||
} |
|||
return(con) |
|||
} |
@ -0,0 +1,289 @@ |
|||
/* *
|
|||
* Streaming interface to libcurl for R. (c) 2015 Jeroen Ooms. |
|||
* Source: https://github.com/jeroen/curl
|
|||
* Comments and contributions are welcome! |
|||
* Helpful libcurl examples: |
|||
* - http://curl.haxx.se/libcurl/c/getinmemory.html
|
|||
* - http://curl.haxx.se/libcurl/c/multi-single.html
|
|||
* Sparse documentation about Rconnection API: |
|||
* - https://github.com/wch/r-source/blob/trunk/src/include/R_ext/Connections.h
|
|||
* - http://biostatmatt.com/R/R-conn-ints/C-Structures.html
|
|||
* |
|||
* Notes: the close() function in R actually calls con->destroy. The con->close |
|||
* function is only used when a connection is recycled after auto-open. |
|||
*/ |
|||
#include "curl-common.h" |
|||
#include <Rconfig.h> |
|||
|
|||
/* Define BSWAP_32 on Big Endian systems */ |
|||
#ifdef WORDS_BIGENDIAN |
|||
#if (defined(__sun) && defined(__SVR4)) |
|||
#include <sys/byteorder.h> |
|||
#elif (defined(__APPLE__) && defined(__ppc__) || defined(__ppc64__)) |
|||
#include <libkern/OSByteOrder.h> |
|||
#define BSWAP_32 OSSwapInt32 |
|||
#elif (defined(__OpenBSD__)) |
|||
#define BSWAP_32(x) swap32(x) |
|||
#elif (defined(__GLIBC__)) |
|||
#include <byteswap.h> |
|||
#define BSWAP_32(x) bswap_32(x) |
|||
#elif (defined(_AIX)) |
|||
#define BSWAP_32(x) __builtin_bswap32(x) |
|||
#endif |
|||
#endif |
|||
|
|||
/* the RConnection API is experimental and subject to change */ |
|||
#include <R_ext/Connections.h> |
|||
#if ! defined(R_CONNECTIONS_VERSION) || R_CONNECTIONS_VERSION != 1 |
|||
#error "Unsupported connections API version" |
|||
#endif |
|||
|
|||
#define min(a, b) (((a) < (b)) ? (a) : (b)) |
|||
#define R_EOF -1 |
|||
|
|||
typedef struct { |
|||
char *url; |
|||
char *buf; |
|||
char *cur; |
|||
int has_data; |
|||
int has_more; |
|||
int used; |
|||
int partial; |
|||
size_t size; |
|||
size_t limit; |
|||
CURLM *manager; |
|||
CURL *handle; |
|||
reference *ref; |
|||
} request; |
|||
|
|||
/* callback function to store received data */ |
|||
static size_t push(void *contents, size_t sz, size_t nmemb, void *ctx) { |
|||
/* avoids compiler warning on windows */ |
|||
request* req = (request*) ctx; |
|||
req->has_data = 1; |
|||
|
|||
/* move existing data to front of buffer (if any) */ |
|||
memmove(req->buf, req->cur, req->size); |
|||
|
|||
/* allocate more space if required */ |
|||
size_t realsize = sz * nmemb; |
|||
size_t newsize = req->size + realsize; |
|||
while(newsize > req->limit) { |
|||
size_t newlimit = 2 * req->limit; |
|||
//Rprintf("Resizing buffer to %d.\n", newlimit);
|
|||
void *newbuf = realloc(req->buf, newlimit); |
|||
if(!newbuf) |
|||
error("Failure in realloc. Out of memory?"); |
|||
req->buf = newbuf; |
|||
req->limit = newlimit; |
|||
} |
|||
|
|||
/* append new data */ |
|||
memcpy(req->buf + req->size, contents, realsize); |
|||
req->size = newsize; |
|||
req->cur = req->buf; |
|||
return realsize; |
|||
} |
|||
|
|||
static size_t pop(void *target, size_t max, request *req){ |
|||
size_t copy_size = min(req->size, max); |
|||
memcpy(target, req->cur, copy_size); |
|||
req->cur += copy_size; |
|||
req->size -= copy_size; |
|||
//Rprintf("Requested %d bytes, popped %d bytes, new size %d bytes.\n", max, copy_size, req->size);
|
|||
return copy_size; |
|||
} |
|||
|
|||
void check_manager(CURLM *manager, reference *ref) { |
|||
for(int msg = 1; msg > 0;){ |
|||
CURLMsg *out = curl_multi_info_read(manager, &msg); |
|||
if(out) |
|||
assert_status(out->data.result, ref); |
|||
} |
|||
} |
|||
|
|||
//NOTE: renamed because the name 'fetch' caused crash/conflict on Solaris.
|
|||
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); |
|||
} |
|||
|
|||
/* 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); |
|||
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 |
|||
fetchdata(req); |
|||
total_size += pop((char*)target + total_size, (req_size-total_size), req); |
|||
|
|||
//return less than requested data for non-blocking connections, or curl_fetch_stream()
|
|||
if(!con->blocking || req->partial) |
|||
break; |
|||
} |
|||
con->incomplete = req->has_more || req->size; |
|||
return total_size; |
|||
} |
|||
|
|||
/* naive implementation of readLines */ |
|||
static int rcurl_fgetc(Rconnection con) { |
|||
int x = 0; |
|||
#ifdef WORDS_BIGENDIAN |
|||
return rcurl_read(&x, 1, 1, con) ? BSWAP_32(x) : R_EOF; |
|||
#else |
|||
return rcurl_read(&x, 1, 1, con) ? x : R_EOF; |
|||
#endif |
|||
} |
|||
|
|||
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); |
|||
ref->locked = 0; |
|||
|
|||
/* delayed finalizer cleanup */ |
|||
(ref->refCount)--; |
|||
clean_handle(ref); |
|||
|
|||
/* clean up connection */ |
|||
curl_multi_cleanup(req->manager); |
|||
free(req->buf); |
|||
free(req->url); |
|||
free(req); |
|||
} |
|||
|
|||
/* reset to pre-opened state */ |
|||
void reset(Rconnection con) { |
|||
//Rprintf("Resetting connection object.\n");
|
|||
request *req = (request*) con->private; |
|||
curl_multi_remove_handle(req->manager, req->handle); |
|||
req->ref->locked = 0; |
|||
con->isopen = FALSE; |
|||
con->text = TRUE; |
|||
con->incomplete = FALSE; |
|||
strcpy(con->mode, "r"); |
|||
} |
|||
|
|||
static Rboolean rcurl_open(Rconnection con) { |
|||
request *req = (request*) con->private; |
|||
|
|||
//same message as base::url()
|
|||
if (con->mode[0] != 'r' || strchr(con->mode, 'w')) |
|||
Rf_error("can only open URLs for reading"); |
|||
|
|||
if(req->ref->locked) |
|||
Rf_error("Handle is already in use elsewhere."); |
|||
|
|||
/* init a multi stack with callback */ |
|||
CURL *handle = req->handle; |
|||
assert(curl_easy_setopt(handle, CURLOPT_URL, req->url)); |
|||
assert(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, push)); |
|||
assert(curl_easy_setopt(handle, CURLOPT_WRITEDATA, req)); |
|||
|
|||
/* add the handle to the pool and lock it */ |
|||
massert(curl_multi_add_handle(req->manager, handle)); |
|||
req->ref->locked = 1; |
|||
|
|||
/* reset the state */ |
|||
req->handle = handle; |
|||
req->cur = req->buf; |
|||
req->size = 0; |
|||
req->used = 1; |
|||
req->has_data = 0; |
|||
req->has_more = 1; |
|||
|
|||
/* fully non-blocking has 's' in open mode */ |
|||
int block_open = strchr(con->mode, 's') == NULL; |
|||
int force_open = strchr(con->mode, 'f') != NULL; |
|||
|
|||
/* 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); |
|||
} |
|||
|
|||
/* 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; |
|||
con->isopen = TRUE; |
|||
con->incomplete = TRUE; |
|||
return TRUE; |
|||
} |
|||
|
|||
SEXP R_curl_connection(SEXP url, SEXP ptr, SEXP partial) { |
|||
if(!isString(url)) |
|||
error("Argument 'url' must be string."); |
|||
|
|||
/* create the R connection object, mimicking base::url() */ |
|||
Rconnection con; |
|||
|
|||
/* R wants description in native encoding, but we use UTF-8 URL below */ |
|||
SEXP rc = PROTECT(R_new_custom_connection(translateChar(STRING_ELT(url, 0)), "r", "curl", &con)); |
|||
|
|||
/* setup curl. These are the parts that are recycable. */ |
|||
request *req = malloc(sizeof(request)); |
|||
req->handle = get_handle(ptr); |
|||
req->ref = get_ref(ptr); |
|||
req->limit = CURL_MAX_WRITE_SIZE; |
|||
req->buf = malloc(req->limit); |
|||
req->manager = curl_multi_init(); |
|||
req->partial = asLogical(partial); //only for curl_fetch_stream()
|
|||
req->used = 0; |
|||
|
|||
/* allocate url string */ |
|||
req->url = malloc(strlen(translateCharUTF8(asChar(url))) + 1); |
|||
strcpy(req->url, translateCharUTF8(asChar(url))); |
|||
|
|||
/* set connection properties */ |
|||
con->incomplete = FALSE; |
|||
con->private = req; |
|||
con->canseek = FALSE; |
|||
con->canwrite = FALSE; |
|||
con->isopen = FALSE; |
|||
con->blocking = TRUE; |
|||
con->text = TRUE; |
|||
con->UTF8out = TRUE; |
|||
con->open = rcurl_open; |
|||
con->close = reset; |
|||
con->destroy = cleanup; |
|||
con->read = rcurl_read; |
|||
con->fgetc = rcurl_fgetc; |
|||
con->fgetc_internal = rcurl_fgetc; |
|||
|
|||
/* protect the handle */ |
|||
(req->ref->refCount)++; |
|||
|
|||
UNPROTECT(1); |
|||
return rc; |
|||
} |
@ -0,0 +1,39 @@ |
|||
#' Download file to disk |
|||
#' |
|||
#' Libcurl implementation of \code{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}}. |
|||
#' |
|||
#' 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 |
|||
#' \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. |
|||
#' |
|||
#' @useDynLib curl R_download_curl |
|||
#' @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 |
|||
#' 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"}. |
|||
#' @param handle a curl handle object |
|||
#' @return Path of downloaded file (invisibly). |
|||
#' @export |
|||
#' @examples \dontrun{download large file |
|||
#' url <- "http://www2.census.gov/acs2011_5yr/pums/csv_pus.zip" |
|||
#' tmp <- tempfile() |
|||
#' curl_download(url, tmp) |
|||
#' } |
|||
curl_download <- function(url, destfile, quiet = TRUE, mode = "wb", handle = new_handle()){ |
|||
destfile <- enc2native(normalizePath(destfile, mustWork = FALSE)) |
|||
nonblocking <- isTRUE(getOption("curl_interrupt", TRUE)) |
|||
tmp <- enc2native(paste0(destfile, ".curltmp")) |
|||
on.exit(unlink(tmp)) |
|||
.Call(R_download_curl, url, tmp, quiet, mode, handle, nonblocking) |
|||
file.rename(tmp, destfile) |
|||
invisible(destfile) |
|||
} |
@ -0,0 +1,51 @@ |
|||
/* *
|
|||
* Reimplementation of C_download (the "internal" method for download.file). |
|||
*/ |
|||
#include "curl-common.h" |
|||
|
|||
SEXP R_download_curl(SEXP url, SEXP destfile, SEXP quiet, SEXP mode, SEXP ptr, SEXP nonblocking) { |
|||
if(!isString(url)) |
|||
error("Argument 'url' must be string."); |
|||
|
|||
if(!isString(destfile)) |
|||
error("Argument 'destfile' must be string."); |
|||
|
|||
if(!isLogical(quiet)) |
|||
error("Argument 'quiet' must be TRUE/FALSE."); |
|||
|
|||
if(!isString(mode)) |
|||
error("Argument 'mode' must be string."); |
|||
|
|||
/* get the handle */ |
|||
CURL *handle = get_handle(ptr); |
|||
reset_errbuf(get_ref(ptr)); |
|||
|
|||
/* open file */ |
|||
FILE *dest = fopen(CHAR(asChar(destfile)), CHAR(asChar(mode))); |
|||
if(!dest) |
|||
error("Failed to open file %s.", CHAR(asChar(destfile))); |
|||
|
|||
/* set options */ |
|||
curl_easy_setopt(handle, CURLOPT_URL, translateCharUTF8(asChar(url))); |
|||
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, asLogical(quiet)); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, push_disk); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, dest); |
|||
|
|||
/* perform blocking request */ |
|||
CURLcode status = 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_WRITEFUNCTION, NULL); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); |
|||
fclose(dest); |
|||
|
|||
/* raise for curl errors */ |
|||
assert_status(status, get_ref(ptr)); |
|||
|
|||
/* check for success */ |
|||
stop_for_status(handle); |
|||
return ScalarInteger(0); |
|||
} |
@ -0,0 +1,126 @@ |
|||
#' Echo Service |
|||
#' |
|||
#' This function is only for testing purposes. It starts a local httpuv server to |
|||
#' echo the request body and content type in the response. |
|||
#' |
|||
#' @export |
|||
#' @param handle a curl handle object |
|||
#' @param port the port number on which to run httpuv server |
|||
#' @param progress show progress meter during http transfer |
|||
#' @param file path or connection to write body. Default returns body as raw vector. |
|||
#' @examples h <- new_handle(url = 'https://httpbin.org/post') |
|||
#' handle_setform(h, foo = "blabla", bar = charToRaw("test"), |
|||
#' myfile = form_file(system.file("DESCRIPTION"), "text/description")) |
|||
#' |
|||
#' # Echo the POST request data |
|||
#' formdata <- curl_echo(h) |
|||
#' |
|||
#' # Show the multipart body |
|||
#' cat(rawToChar(formdata$body)) |
|||
#' |
|||
#' # Parse multipart |
|||
#' webutils::parse_http(formdata$body, formdata$content_type) |
|||
curl_echo <- function(handle, port = 9359, progress = interactive(), file = NULL){ |
|||
progress <- isTRUE(progress) |
|||
if(!(is.null(file) || inherits(file, "connection") || is.character(file))) |
|||
stop("Argument 'file' must be a file path or connection object") |
|||
output <- NULL |
|||
echo_handler <- function(req){ |
|||
if(progress){ |
|||
cat("\nRequest Complete!\n", file = stderr()) |
|||
progress <<- FALSE |
|||
} |
|||
body <- if(tolower(req[["REQUEST_METHOD"]]) %in% c("post", "put")){ |
|||
if(!length(file)){ |
|||
req[["rook.input"]]$read() |
|||
} else { |
|||
write_to_file(req[["rook.input"]]$read, file) |
|||
} |
|||
} |
|||
|
|||
output <<- list( |
|||
method = req[["REQUEST_METHOD"]], |
|||
path = req[["PATH_INFO"]], |
|||
query = req[["QUERY_STRING"]], |
|||
content_type = req[["CONTENT_TYPE"]], |
|||
body = body, |
|||
headers = req[["HEADERS"]] |
|||
) |
|||
|
|||
# Dummy response |
|||
list( |
|||
status = 200, |
|||
body = "", |
|||
headers = c("Content-Type" = "text/plain") |
|||
) |
|||
} |
|||
|
|||
# Start httpuv |
|||
server_id <- httpuv::startServer("0.0.0.0", port, list(call = echo_handler)) |
|||
on.exit(httpuv::stopServer(server_id), add = TRUE) |
|||
|
|||
# Workaround bug in httpuv on windows that keeps protecting handler until next startServer() |
|||
on.exit(rm(handle), add = TRUE) |
|||
|
|||
# httpuv 1.3.4 supports non-blocking service() |
|||
waittime <- ifelse(utils::packageVersion('httpuv') > "1.3.3", NA, 1) |
|||
|
|||
# Post data from curl |
|||
xfer <- function(down, up){ |
|||
if(progress){ |
|||
if(up[1] == 0 && down[1] == 0){ |
|||
cat("\rConnecting...", file = stderr()) |
|||
} else { |
|||
cat(sprintf("\rUpload: %s (%d%%) ", format_size(up[2]), |
|||
as.integer(100 * up[2] / up[1])), file = stderr()) |
|||
} |
|||
} |
|||
# Need very low wait to prevent gridlocking! |
|||
httpuv::service(waittime) |
|||
TRUE |
|||
} |
|||
handle_setopt(handle, connecttimeout = 2, xferinfofunction = xfer, noprogress = FALSE) |
|||
if(progress) cat("\n", file = stderr()) |
|||
input_url <- handle_data(handle)$url |
|||
if(length(input_url) && nchar(input_url)){ |
|||
target_url <- replace_host(input_url, paste0("http://127.0.0.1:", port)) |
|||
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))) |
|||
} else { |
|||
target_url <- paste0("http://localhost:", port) |
|||
} |
|||
curl_fetch_memory(target_url, handle = handle) |
|||
output$url <- input_url |
|||
if(progress) cat("\n", file = stderr()) |
|||
return(output) |
|||
} |
|||
|
|||
replace_host <- function(url, new_host = 'http://localhost'){ |
|||
sub("[a-zA-Z]+://[^/]+", new_host, url) |
|||
} |
|||
|
|||
write_to_file <- function(readfun, filename){ |
|||
con <- if(inherits(filename, "connection")){ |
|||
filename |
|||
} else { |
|||
base::file(filename) |
|||
} |
|||
if(!isOpen(con)){ |
|||
open(con, "wb") |
|||
on.exit(close(con)) |
|||
} |
|||
while(length(buf <- readfun(1e6))){ |
|||
writeBin(buf, con) |
|||
} |
|||
return(filename) |
|||
} |
|||
|
|||
format_size <- function(x){ |
|||
if(x < 1024) |
|||
return(sprintf("%d b", x)) |
|||
if(x < 1048576) |
|||
return(sprintf("%.2f Kb", x / 1024)) |
|||
return(sprintf("%.2f Mb", x / 1048576)) |
|||
} |
@ -0,0 +1,63 @@ |
|||
#' Send email |
|||
#' |
|||
#' Use the curl SMTP client to send an email. The \code{message} argument must be |
|||
#' properly formatted RFC2822 email message with From/To/Subject headers and CRLF |
|||
#' line breaks. |
|||
#' |
|||
#' @export |
|||
#' @param mail_rcpt one or more recipient email addresses. Do not include names, |
|||
#' 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 address of the SMTP server without the \code{smtp://} part |
|||
#' @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} to authenticate |
|||
#' with the SMTP server. |
|||
#' @examples \donttest{# Message in RFC2822 format |
|||
#' message <- 'From: "Testbot" <jeroen@ocpu.io> |
|||
#' To: "Jeroen Ooms" <jeroenooms@gmail.com> |
|||
#' Subject: Hello there! |
|||
#' |
|||
#' Hi Jeroen, |
|||
#' I am sending this email using curl.' |
|||
#' |
|||
#' |
|||
#' # Actually send the email |
|||
#' recipients <- c('test@opencpu.org', 'jeroenooms@gmail.com') |
|||
#' sender <- 'test@ocpu.io' |
|||
#' username <- 'mail@ocpu.io' |
|||
#' password <- askpass::askpass(paste("SMTP server password for", username)) |
|||
#' send_mail(sender, recipients, smtp_server = 'smtp.mailgun.org', |
|||
#' message = message, username = username, password = password)} |
|||
send_mail <- function(mail_from, mail_rcpt, message, smtp_server = 'localhost', |
|||
verbose = TRUE, ...){ |
|||
if(is.character(message)) |
|||
message <- charToRaw(paste(message, collapse = '\r\n')) |
|||
con <- if(is.raw(message)){ |
|||
rawConnection(message) |
|||
} else if(inherits(message, "connection")){ |
|||
if(!isOpen(message)) |
|||
open(message, 'rb') |
|||
message |
|||
} else { |
|||
stop("Body must be a string, raw vector, or connection object") |
|||
} |
|||
on.exit(close(con)) |
|||
total_bytes <- 0 |
|||
h <- new_handle(upload = TRUE, readfunction = function(nbytes, ...) { |
|||
buf <- readBin(con, raw(), nbytes) |
|||
total_bytes <<- total_bytes + length(buf) |
|||
if(verbose){ |
|||
if(length(buf)){ |
|||
cat(sprintf("\rUploaded %d bytes...", total_bytes), file = stderr()) |
|||
} else { |
|||
cat(sprintf("\rUploaded %d bytes... all done!\n", total_bytes), file = stderr()) |
|||
} |
|||
} |
|||
return(buf) |
|||
}, mail_from = mail_from, mail_rcpt = mail_rcpt, verbose = verbose, ...) |
|||
url <- paste0('smtp://', smtp_server) |
|||
curl_fetch_memory(url, handle = h) |
|||
} |
@ -0,0 +1,26 @@ |
|||
#' URL encoding |
|||
#' |
|||
#' Escape all special characters (i.e. everything except for a-z, A-Z, 0-9, '-', |
|||
#' '.', '_' or '~') for use in URLs. |
|||
#' |
|||
#' @useDynLib curl R_curl_escape |
|||
#' @export |
|||
#' @param url A character vector (typically containing urls or parameters) to be |
|||
#' encoded/decoded |
|||
#' @examples # Escape strings |
|||
#' out <- curl_escape("foo = bar + 5") |
|||
#' curl_unescape(out) |
|||
#' |
|||
#' # All non-ascii characters are encoded |
|||
#' mu <- "\u00b5" |
|||
#' curl_escape(mu) |
|||
#' curl_unescape(curl_escape(mu)) |
|||
curl_escape <- function(url){ |
|||
.Call(R_curl_escape, enc2utf8(as.character(url)), FALSE); |
|||
} |
|||
|
|||
#' @rdname curl_escape |
|||
#' @export |
|||
curl_unescape <- function(url){ |
|||
.Call(R_curl_escape, enc2utf8(as.character(url)), TRUE); |
|||
} |
@ -0,0 +1,33 @@ |
|||
#include <curl/curl.h> |
|||
#include <Rinternals.h> |
|||
|
|||
SEXP R_curl_escape(SEXP url, SEXP unescape_) { |
|||
if (TYPEOF(url) != STRSXP) |
|||
error("`url` must be a character vector."); |
|||
|
|||
/* init curl */ |
|||
CURL *curl = curl_easy_init(); |
|||
if (!curl) |
|||
return(R_NilValue); |
|||
|
|||
int unescape = asLogical(unescape_); |
|||
int n = Rf_length(url); |
|||
SEXP output = PROTECT(allocVector(STRSXP, n)); |
|||
|
|||
for (int i = 0; i < n; ++i) { |
|||
const char *in = CHAR(STRING_ELT(url, i)); |
|||
char *out; |
|||
if (unescape) { |
|||
out = curl_easy_unescape(curl, in, 0, NULL); |
|||
} else { |
|||
out = curl_easy_escape(curl, in, 0); |
|||
} |
|||
|
|||
SET_STRING_ELT(output, i, mkCharCE(out, CE_UTF8)); |
|||
curl_free(out); |
|||
} |
|||
|
|||
curl_easy_cleanup(curl); |
|||
UNPROTECT(1); |
|||
return output; |
|||
} |
@ -0,0 +1,117 @@ |
|||
#' 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. |
|||
#' |
|||
#' 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 \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 |
|||
#' \code{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. |
|||
#' |
|||
#' @param url A character string naming the URL of a resource to be downloaded. |
|||
#' @param handle a curl handle object |
|||
#' @export |
|||
#' @rdname curl_fetch |
|||
#' @useDynLib curl R_curl_fetch_memory |
|||
#' @examples |
|||
#' # Load in memory |
|||
#' res <- curl_fetch_memory("http://httpbin.org/cookies/set?foo=123&bar=ftw") |
|||
#' res$content |
|||
#' |
|||
#' # Save to disk |
|||
#' res <- curl_fetch_disk("http://httpbin.org/stream/10", tempfile()) |
|||
#' res$content |
|||
#' readLines(res$content) |
|||
#' |
|||
#' # Stream with callback |
|||
#' res <- curl_fetch_stream("http://www.httpbin.org/drip?duration=5&numbytes=15&code=200", function(x){ |
|||
#' cat(rawToChar(x)) |
|||
#' }) |
|||
#' |
|||
#' # Async API |
|||
#' data <- list() |
|||
#' success <- function(res){ |
|||
#' cat("Request done! Status:", res$status, "\n") |
|||
#' data <<- c(data, list(res)) |
|||
#' } |
|||
#' failure <- function(msg){ |
|||
#' cat("Oh noes! Request failed!", msg, "\n") |
|||
#' } |
|||
#' curl_fetch_multi("http://httpbin.org/get", success, failure) |
|||
#' curl_fetch_multi("http://httpbin.org/status/418", success, failure) |
|||
#' curl_fetch_multi("https://urldoesnotexist.xyz", success, failure) |
|||
#' multi_run() |
|||
#' str(data) |
|||
curl_fetch_memory <- function(url, handle = new_handle()){ |
|||
nonblocking <- isTRUE(getOption("curl_interrupt", TRUE)) |
|||
output <- .Call(R_curl_fetch_memory, enc2utf8(url), handle, nonblocking) |
|||
res <- handle_data(handle) |
|||
res$content <- output |
|||
res |
|||
} |
|||
|
|||
#' @export |
|||
#' @param path Path to save results |
|||
#' @rdname curl_fetch |
|||
#' @useDynLib curl R_curl_fetch_disk |
|||
curl_fetch_disk <- function(url, path, handle = new_handle()){ |
|||
nonblocking <- isTRUE(getOption("curl_interrupt", TRUE)) |
|||
path <- enc2native(normalizePath(path, mustWork = FALSE)) |
|||
output <- .Call(R_curl_fetch_disk, enc2utf8(url), handle, path, "wb", nonblocking) |
|||
res <- handle_data(handle) |
|||
res$content <- output |
|||
res |
|||
} |
|||
|
|||
#' @export |
|||
#' @param fun Callback function. Should have one argument, which will be |
|||
#' a raw vector. |
|||
#' @rdname curl_fetch |
|||
#' @useDynLib curl R_curl_connection |
|||
curl_fetch_stream <- function(url, fun, handle = new_handle()){ |
|||
# Blocking = TRUE and partial = TRUE to prevent busy-waiting |
|||
con <- curl_connection(url, mode = "", handle = handle, partial = TRUE) |
|||
|
|||
# 'f' means: do not error for status code |
|||
open(con, "rbf") |
|||
on.exit(close(con)) |
|||
while(isIncomplete(con)){ |
|||
buf <- readBin(con, raw(), 32768L) |
|||
if(length(buf)) |
|||
fun(buf) |
|||
} |
|||
handle_data(handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @rdname curl_fetch |
|||
#' @inheritParams multi |
|||
#' @useDynLib curl R_curl_connection |
|||
curl_fetch_multi <- function(url, done = NULL, fail = NULL, pool = NULL, |
|||
data = NULL, handle = new_handle()){ |
|||
handle_setopt(handle, url = enc2utf8(url)) |
|||
multi_add(handle = handle, done = done, fail = fail, data = data, pool = pool) |
|||
invisible(handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @rdname curl_fetch |
|||
curl_fetch_echo <- function(url, handle = new_handle()){ |
|||
handle_setopt(handle, url = enc2utf8(url)) |
|||
curl_echo(handle) |
|||
} |
@ -0,0 +1,91 @@ |
|||
/* *
|
|||
* Blocking easy interfaces to libcurl for R. |
|||
* Example: http://curl.haxx.se/libcurl/c/getinmemory.html
|
|||
*/ |
|||
|
|||
#include "curl-common.h" |
|||
|
|||
SEXP R_curl_fetch_memory(SEXP url, SEXP ptr, SEXP nonblocking){ |
|||
if (!isString(url) || length(url) != 1) |
|||
error("Argument 'url' must be string."); |
|||
|
|||
/* get the handle */ |
|||
CURL *handle = get_handle(ptr); |
|||
|
|||
/* update the url */ |
|||
curl_easy_setopt(handle, CURLOPT_URL, CHAR(STRING_ELT(url, 0))); |
|||
|
|||
/* reset the response header buffer */ |
|||
reset_resheaders(get_ref(ptr)); |
|||
reset_errbuf(get_ref(ptr)); |
|||
|
|||
/* buffer body */ |
|||
memory body = {NULL, 0}; |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, append_buffer); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &body); |
|||
|
|||
/* perform blocking request */ |
|||
CURLcode status = asLogical(nonblocking) ? |
|||
curl_perform_with_interrupt(handle) : curl_easy_perform(handle); |
|||
|
|||
/* Reset for reuse */ |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); |
|||
|
|||
/* check for errors */ |
|||
if (status != CURLE_OK) { |
|||
free(body.buf); |
|||
assert_status(status, get_ref(ptr)); |
|||
} |
|||
|
|||
/* create output */ |
|||
SEXP out = PROTECT(allocVector(RAWSXP, body.size)); |
|||
|
|||
/* copy only if there is actual content */ |
|||
if(body.size) |
|||
memcpy(RAW(out), body.buf, body.size); |
|||
|
|||
/* cleanup and return */ |
|||
UNPROTECT(1); |
|||
free(body.buf); |
|||
return out; |
|||
} |
|||
|
|||
SEXP R_curl_fetch_disk(SEXP url, SEXP ptr, SEXP path, SEXP mode, SEXP nonblocking){ |
|||
if (!isString(url) || length(url) != 1) |
|||
error("Argument 'url' must be string."); |
|||
if (!isString(path) || length(path) != 1) |
|||
error("`path` must be string."); |
|||
|
|||
/* get the handle */ |
|||
CURL *handle = get_handle(ptr); |
|||
|
|||
/* update the url */ |
|||
curl_easy_setopt(handle, CURLOPT_URL, CHAR(STRING_ELT(url, 0))); |
|||
|
|||
/* reset the response header buffer */ |
|||
reset_resheaders(get_ref(ptr)); |
|||
reset_errbuf(get_ref(ptr)); |
|||
|
|||
/* open file */ |
|||
FILE *dest = fopen(CHAR(asChar(path)), CHAR(asChar(mode))); |
|||
if(!dest) |
|||
error("Failed to open file %s.", CHAR(asChar(path))); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, push_disk); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, dest); |
|||
|
|||
/* perform blocking request */ |
|||
CURLcode status = asLogical(nonblocking) ? |
|||
curl_perform_with_interrupt(handle): curl_easy_perform(handle); |
|||
|
|||
/* cleanup */ |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); |
|||
fclose(dest); |
|||
|
|||
/* check for errors */ |
|||
assert_status(status, get_ref(ptr)); |
|||
|
|||
/* return the file path */ |
|||
return path; |
|||
} |
@ -0,0 +1,48 @@ |
|||
#' 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 |
|||
#' with a custom content-type. |
|||
#' |
|||
#' @param path a string with a path to an existing file on disk |
|||
#' @param type MIME content-type of the file. |
|||
#' @export |
|||
#' @name multipart |
|||
#' @rdname multipart |
|||
form_file <- function(path, type = NULL){ |
|||
path <- enc2native(normalizePath(path[1], mustWork = TRUE)) |
|||
if(!is.null(type)){ |
|||
stopifnot(is.character(type)) |
|||
} |
|||
structure(list(path = path, type = type), class = "form_file") |
|||
} |
|||
|
|||
#' @export |
|||
#' @name multipart |
|||
#' @rdname multipart |
|||
#' @param value a character or raw vector to post |
|||
form_data <- function(value, type = NULL){ |
|||
if(is.character(value)) |
|||
value <- charToRaw(paste(enc2utf8(value), collapse = "\n")) |
|||
if(!is.raw(value)) |
|||
stop("Argument 'value' must be string or raw vector") |
|||
structure(list(value = value, type = type), class = "form_data") |
|||
} |
|||
|
|||
#' @export |
|||
print.form_file <- function(x, ...){ |
|||
txt <- paste("Form file:", basename(x$path)) |
|||
if(!is.null(x$type)){ |
|||
txt <- sprintf("%s (type: %s)", txt, x$type) |
|||
} |
|||
cat(txt, "\n") |
|||
} |
|||
|
|||
#' @export |
|||
print.form_data <- function(x, ...){ |
|||
txt <- paste(sprintf("Form data of length %d", length(x$value))) |
|||
if(!is.null(x$type)){ |
|||
txt <- sprintf("%s (type: %s)", txt, x$type) |
|||
} |
|||
cat(txt, "\n") |
|||
} |
@ -0,0 +1,46 @@ |
|||
#include "curl-common.h" |
|||
|
|||
struct curl_httppost* make_form(SEXP form){ |
|||
struct curl_httppost* post = NULL; |
|||
struct curl_httppost* last = NULL; |
|||
SEXP ln = PROTECT(getAttrib(form, R_NamesSymbol)); |
|||
for(int i = 0; i < length(form); i++){ |
|||
const char *name = translateCharUTF8(STRING_ELT(ln, i)); |
|||
SEXP val = VECTOR_ELT(form, i); |
|||
if(TYPEOF(val) == RAWSXP){ |
|||
long datalen = Rf_length(val); |
|||
if(datalen > 0){ |
|||
unsigned char * data = RAW(val); |
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, data, CURLFORM_CONTENTSLENGTH, datalen, CURLFORM_END); |
|||
} else { |
|||
//Note if 'CURLFORM_CONTENTLEN == 0' then libcurl assumes strlen() !
|
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, "", CURLFORM_END); |
|||
} |
|||
} else if(isVector(val) && Rf_length(val)){ |
|||
if(isString(VECTOR_ELT(val, 0))){ |
|||
//assume a form_file upload
|
|||
const char * path = CHAR(asChar(VECTOR_ELT(val, 0))); |
|||
if(isString(VECTOR_ELT(val, 1))){ |
|||
const char *content_type = CHAR(asChar(VECTOR_ELT(val, 1))); |
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_FILE, path, CURLFORM_CONTENTTYPE, content_type, CURLFORM_END); |
|||
} else { |
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_FILE, path, CURLFORM_END); |
|||
} |
|||
} else { |
|||
//assume a form_value upload
|
|||
unsigned char * data = RAW(VECTOR_ELT(val, 0)); |
|||
long datalen = Rf_length(VECTOR_ELT(val, 0)); |
|||
if(isString(VECTOR_ELT(val, 1))){ |
|||
const char * content_type = CHAR(asChar(VECTOR_ELT(val, 1))); |
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, data, CURLFORM_CONTENTSLENGTH, datalen, CURLFORM_CONTENTTYPE, content_type, CURLFORM_END); |
|||
} else { |
|||
curl_formadd(&post, &last, CURLFORM_COPYNAME, name, CURLFORM_COPYCONTENTS, data, CURLFORM_CONTENTSLENGTH, datalen, CURLFORM_END); |
|||
} |
|||
} |
|||
} else { |
|||
error("form value %s not supported", name); |
|||
} |
|||
} |
|||
UNPROTECT(1); |
|||
return post; |
|||
} |
@ -0,0 +1,17 @@ |
|||
#include <curl/curl.h> |
|||
#include <Rinternals.h> |
|||
|
|||
SEXP R_curl_getdate(SEXP datestring) { |
|||
if(!isString(datestring)) |
|||
error("Argument 'datestring' must be string."); |
|||
|
|||
int len = length(datestring); |
|||
SEXP out = PROTECT(allocVector(INTSXP, len)); |
|||
|
|||
for(int i = 0; i < len; i++){ |
|||
time_t date = curl_getdate(CHAR(STRING_ELT(datestring, i)), NULL); |
|||
INTEGER(out)[i] = date < 0 ? NA_INTEGER : (int) date; |
|||
} |
|||
UNPROTECT(1); |
|||
return out; |
|||
} |
@ -0,0 +1,209 @@ |
|||
#' Create and configure a curl handle |
|||
#' |
|||
#' 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 |
|||
#' 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} |
|||
#' 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. |
|||
#' |
|||
#' @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}} |
|||
#' @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. |
|||
#' @export |
|||
#' @name handle |
|||
#' @useDynLib curl R_new_handle |
|||
#' @rdname handle |
|||
#' @examples |
|||
#' h <- new_handle() |
|||
#' handle_setopt(h, customrequest = "PUT") |
|||
#' handle_setform(h, a = "1", b = "2") |
|||
#' r <- curl_fetch_memory("http://httpbin.org/put", h) |
|||
#' cat(rawToChar(r$content)) |
|||
#' |
|||
#' # Or use the list form |
|||
#' h <- new_handle() |
|||
#' handle_setopt(h, .list = list(customrequest = "PUT")) |
|||
#' handle_setform(h, .list = list(a = "1", b = "2")) |
|||
#' r <- curl_fetch_memory("http://httpbin.org/put", h) |
|||
#' cat(rawToChar(r$content)) |
|||
new_handle <- function(...){ |
|||
h <- .Call(R_new_handle) |
|||
handle_setopt(h, ...) |
|||
h |
|||
} |
|||
|
|||
#' @export |
|||
#' @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()}. |
|||
#' @rdname handle |
|||
handle_setopt <- function(handle, ..., .list = list()){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
values <- c(list(...), .list) |
|||
opt_names <- fix_options(tolower(names(values))) |
|||
keys <- as.integer(curl_options()[opt_names]) |
|||
na_keys <- is.na(keys) |
|||
if(any(na_keys)){ |
|||
bad_opts <- opt_names[na_keys] |
|||
stop("Unknown option", ifelse(length(bad_opts) > 1, "s: ", ": "), |
|||
paste(bad_opts, collapse=", ")) |
|||
} |
|||
stopifnot(length(keys) == length(values)) |
|||
.Call(R_handle_setopt, handle, keys, values) |
|||
invisible(handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_handle_setheaders |
|||
#' @rdname handle |
|||
handle_setheaders <- function(handle, ..., .list = list()){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
opts <- c(list(...), .list) |
|||
if(!all(vapply(opts, is.character, logical(1)))){ |
|||
stop("All headers must be strings.") |
|||
} |
|||
opts$Expect = "" |
|||
names <- names(opts) |
|||
values <- as.character(unlist(opts)) |
|||
vec <- paste0(names, ": ", values) |
|||
.Call(R_handle_setheaders, handle, vec) |
|||
invisible(handle) |
|||
} |
|||
|
|||
#' @useDynLib curl R_handle_getheaders |
|||
#' @rdname handle |
|||
handle_getheaders <- function(handle){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
.Call(R_handle_getheaders, handle) |
|||
} |
|||
|
|||
#' @useDynLib curl R_handle_getcustom |
|||
#' @rdname handle |
|||
handle_getcustom <- function(handle){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
.Call(R_handle_getcustom, handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_handle_setform |
|||
#' @rdname handle |
|||
handle_setform <- function(handle, ..., .list = list()){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
form <- c(list(...), .list) |
|||
for(i in seq_along(form)){ |
|||
val <- form[[i]]; |
|||
if(is.character(val)){ |
|||
form[[i]] <- charToRaw(enc2utf8(val)) |
|||
} else if(!is.raw(val) && !inherits(val, "form_file") && !inherits(val, "form_data")){ |
|||
stop("Unsupported value type for form field '", names(form[i]), "'.") |
|||
} |
|||
} |
|||
.Call(R_handle_setform, handle, form) |
|||
invisible(handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @rdname handle |
|||
#' @useDynLib curl R_handle_reset |
|||
handle_reset <- function(handle){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
.Call(R_handle_reset, handle) |
|||
invisible(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}. |
|||
#' |
|||
#' @useDynLib curl R_get_handle_cookies |
|||
#' @export |
|||
#' @param handle a curl handle object |
|||
#' @family handles |
|||
#' @examples |
|||
#' h <- new_handle() |
|||
#' handle_cookies(h) |
|||
#' |
|||
#' # Server sets cookies |
|||
#' req <- curl_fetch_memory("http://httpbin.org/cookies/set?foo=123&bar=ftw", handle = h) |
|||
#' handle_cookies(h) |
|||
#' |
|||
#' # Server deletes cookies |
|||
#' req <- curl_fetch_memory("http://httpbin.org/cookies/delete?foo", handle = h) |
|||
#' handle_cookies(h) |
|||
#' |
|||
#' # Cookies will survive a reset! |
|||
#' handle_reset(h) |
|||
#' handle_cookies(h) |
|||
handle_cookies <- function(handle){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
cookies <- .Call(R_get_handle_cookies, handle) |
|||
df <- if(length(cookies)){ |
|||
values <- lapply(strsplit(cookies, split="\t"), `[`, 1:7) |
|||
as.data.frame(do.call(rbind, values), stringsAsFactors = FALSE) |
|||
} else { |
|||
as.data.frame(matrix(ncol=7, nrow=0)) |
|||
} |
|||
names(df) <- c("domain", "flag", "path", "secure", "expiration", "name", "value") |
|||
df$flag <- as.logical(df$flag) |
|||
df$secure <- as.logical(df$secure) |
|||
expires <- as.numeric(df$expiration) |
|||
expires[expires==0] <- Inf |
|||
class(expires) = c("POSIXct", "POSIXt"); |
|||
df$expiration <- expires |
|||
df |
|||
|
|||
} |
|||
|
|||
#' @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 |
|||
} |
|||
|
|||
#' @export |
|||
print.curl_handle <- function(x, ...){ |
|||
stopifnot(inherits(x, "curl_handle")) |
|||
url <- handle_data(x)$url |
|||
if(!nchar(url)) url <- "empty" |
|||
cat(sprintf("<curl handle> (%s)\n", url)) |
|||
} |
|||
|
|||
# Only for testing memory leaks |
|||
#' @useDynLib curl R_total_handles |
|||
total_handles <- function(){ |
|||
.Call(R_total_handles) |
|||
} |
|||
|
|||
|
|||
## Some hacks for backward compatibilty |
|||
fix_options <- function(opt_names){ |
|||
# Recent libcurl should use xferinfo instead of progress |
|||
has_xferinfo <- length(curl_options("xferinfofunction")) |
|||
if(has_xferinfo){ |
|||
opt_names[opt_names == "progressfunction"] <- "xferinfofunction" |
|||
return(opt_names) |
|||
} else { |
|||
opt_names[opt_names == "xferinfofunction"] <- "progressfunction" |
|||
return(opt_names) |
|||
} |
|||
} |
@ -0,0 +1,484 @@ |
|||
#include "curl-common.h" |
|||
#include "callbacks.h" |
|||
|
|||
/* These are defined in typechecking.c */ |
|||
extern int r_curl_is_slist_option(CURLoption x); |
|||
extern int r_curl_is_long_option(CURLoption x); |
|||
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) : 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 |
|||
|
|||
char CA_BUNDLE[MAX_PATH]; |
|||
extern int windows_openssl; |
|||
|
|||
SEXP R_set_bundle(SEXP path){ |
|||
strcpy(CA_BUNDLE, CHAR(asChar(path))); |
|||
return mkString(CA_BUNDLE); |
|||
} |
|||
|
|||
SEXP R_get_bundle(){ |
|||
return mkString(CA_BUNDLE); |
|||
} |
|||
|
|||
int total_handles = 0; |
|||
|
|||
void clean_handle(reference *ref){ |
|||
if(ref->refCount == 0){ |
|||
if(ref->headers) |
|||
curl_slist_free_all(ref->headers); |
|||
if(ref->custom) |
|||
curl_slist_free_all(ref->custom); |
|||
if(ref->form) |
|||
curl_formfree(ref->form); |
|||
if(ref->handle) |
|||
curl_easy_cleanup(ref->handle); |
|||
if(ref->resheaders.buf) |
|||
free(ref->resheaders.buf); |
|||
free(ref); |
|||
total_handles--; |
|||
} |
|||
} |
|||
|
|||
void fin_handle(SEXP ptr){ |
|||
reference *ref = (reference*) R_ExternalPtrAddr(ptr); |
|||
|
|||
//this kind of strange but the multi finalizer needs the ptr value
|
|||
//if it is still pending
|
|||
ref->refCount--; |
|||
if(ref->refCount == 0) |
|||
R_ClearExternalPtr(ptr); |
|||
|
|||
//free stuff
|
|||
clean_handle(ref); |
|||
} |
|||
|
|||
/* 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){ |
|||
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; |
|||
static xftype ulprev = 0; |
|||
if(dlnow && dlnow != dlprev){ |
|||
dlprev = dlnow; |
|||
if(dltotal){ |
|||
int pct_dn = (100 * dlnow)/dltotal; |
|||
REprintf("\r [%d%%] Downloaded %.0lf bytes...", (double) dlnow, pct_dn); |
|||
if(dlnow == dltotal) |
|||
REprintf("\n"); |
|||
} else { |
|||
REprintf("\r Downloaded %.0lf bytes...", (double) dlnow); |
|||
} |
|||
} else if(ulnow && ulnow != ulprev){ |
|||
ulprev = ulnow; |
|||
int pct_up = (100 * ulnow)/ultotal; |
|||
REprintf("\r [%d%%] Uploaded %.0lf bytes...", (double) ulnow, pct_up); |
|||
if(ulnow == ultotal) |
|||
REprintf("\n"); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/* Set default headers here, these are only allocated once */ |
|||
static struct curl_slist * default_headers(){ |
|||
static struct curl_slist * headers = NULL; |
|||
if(headers == NULL){ |
|||
headers = curl_slist_append(headers, "Expect:"); |
|||
} |
|||
return headers; |
|||
} |
|||
|
|||
static void set_headers(reference *ref, struct curl_slist *newheaders){ |
|||
if(ref->headers) |
|||
curl_slist_free_all(ref->headers); |
|||
ref->headers = newheaders; |
|||
assert(curl_easy_setopt(ref->handle, CURLOPT_HTTPHEADER, |
|||
newheaders ? newheaders : default_headers())); |
|||
} |
|||
|
|||
/* These are defaulst that we always want to set */ |
|||
static void set_handle_defaults(reference *ref){ |
|||
|
|||
/* the actual curl handle */ |
|||
CURL *handle = ref->handle; |
|||
assert(curl_easy_setopt(handle, CURLOPT_PRIVATE, ref)); |
|||
|
|||
/* set the response header collector */ |
|||
reset_resheaders(ref); |
|||
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, append_buffer); |
|||
curl_easy_setopt(handle, CURLOPT_HEADERDATA, &(ref->resheaders)); |
|||
|
|||
#ifdef _WIN32 |
|||
if(windows_openssl == 1){ |
|||
if( CA_BUNDLE != NULL && strlen(CA_BUNDLE)){ |
|||
/* on windows a cert bundle is included with R version 3.2.0 */ |
|||
curl_easy_setopt(handle, CURLOPT_CAINFO, CA_BUNDLE); |
|||
} else { |
|||
/* disable cert validation for older versions of R */ |
|||
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); |
|||
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); |
|||
} |
|||
} |
|||
#endif |
|||
|
|||
/* needed to support compressed responses */ |
|||
assert(curl_easy_setopt(handle, CURLOPT_ENCODING, "")); |
|||
|
|||
/* follow redirect */ |
|||
assert(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L)); |
|||
assert(curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 10L)); |
|||
|
|||
/* a sensible timeout (10s) */ |
|||
assert(curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 10L)); |
|||
|
|||
/* 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 */ |
|||
SEXP agent = GetOption1(install("HTTPUserAgent")); |
|||
if(isString(agent) && Rf_length(agent)){ |
|||
assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, CHAR(STRING_ELT(agent, 0)))); |
|||
} else { |
|||
assert(curl_easy_setopt(handle, CURLOPT_USERAGENT, "r/curl/jeroen")); |
|||
} |
|||
|
|||
/* allow all authentication methods */ |
|||
assert(curl_easy_setopt(handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY)); |
|||
assert(curl_easy_setopt(handle, CURLOPT_UNRESTRICTED_AUTH, 1L)); |
|||
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)); |
|||
|
|||
/* dummy readfunction because default can freeze R */ |
|||
assert(curl_easy_setopt(handle, CURLOPT_READFUNCTION, dummy_read)); |
|||
|
|||
/* seems to be needed for native WinSSL */ |
|||
#ifdef _WIN32 |
|||
curl_easy_setopt(handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); |
|||
#endif |
|||
|
|||
/* 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 |
|||
} |
|||
|
|||
SEXP R_new_handle(){ |
|||
reference *ref = calloc(1, sizeof(reference)); |
|||
ref->refCount = 1; |
|||
ref->handle = curl_easy_init(); |
|||
total_handles++; |
|||
set_handle_defaults(ref); |
|||
SEXP prot = PROTECT(allocVector(VECSXP, 5)); //for protecting callback functions
|
|||
SEXP ptr = PROTECT(R_MakeExternalPtr(ref, R_NilValue, prot)); |
|||
R_RegisterCFinalizerEx(ptr, fin_handle, TRUE); |
|||
setAttrib(ptr, R_ClassSymbol, mkString("curl_handle")); |
|||
UNPROTECT(2); |
|||
ref->handleptr = ptr; |
|||
return ptr; |
|||
} |
|||
|
|||
SEXP R_handle_reset(SEXP ptr){ |
|||
//reset all fields
|
|||
reference *ref = get_ref(ptr); |
|||
set_form(ref, NULL); |
|||
reset_errbuf(ref); |
|||
curl_easy_reset(ref->handle); |
|||
|
|||
//clear custom vector field
|
|||
if(ref->custom){ |
|||
curl_slist_free_all(ref->custom); |
|||
ref->custom = NULL; |
|||
} |
|||
|
|||
//restore default settings
|
|||
set_handle_defaults(ref); |
|||
return ScalarLogical(1); |
|||
} |
|||
|
|||
SEXP R_handle_setheaders(SEXP ptr, SEXP vec){ |
|||
if(!isString(vec)) |
|||
error("header vector must be a string."); |
|||
set_headers(get_ref(ptr), vec_to_slist(vec)); |
|||
return ScalarLogical(1); |
|||
} |
|||
|
|||
SEXP R_handle_getheaders(SEXP ptr){ |
|||
reference *ref = get_ref(ptr); |
|||
return slist_to_vec(ref->headers); |
|||
} |
|||
|
|||
SEXP R_handle_getcustom(SEXP ptr){ |
|||
reference *ref = get_ref(ptr); |
|||
return slist_to_vec(ref->custom); |
|||
} |
|||
|
|||
SEXP R_handle_setopt(SEXP ptr, SEXP keys, SEXP values){ |
|||
reference *ref = get_ref(ptr); |
|||
CURL *handle = get_handle(ptr); |
|||
SEXP prot = R_ExternalPtrProtected(ptr); |
|||
SEXP optnames = PROTECT(getAttrib(values, R_NamesSymbol)); |
|||
|
|||
if(!isInteger(keys)) |
|||
error("keys` must be an integer"); |
|||
|
|||
if(!isVector(values)) |
|||
error("`values` must be a list"); |
|||
|
|||
for(int i = 0; i < length(keys); i++){ |
|||
int key = INTEGER(keys)[i]; |
|||
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 |
|||
} else if (key == CURLOPT_XFERINFOFUNCTION) { |
|||
if (TYPEOF(val) != CLOSXP) |
|||
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_VECTOR_ELT(prot, 1, val); //protect gc
|
|||
#endif |
|||
} else if (key == CURLOPT_PROGRESSFUNCTION) { |
|||
if (TYPEOF(val) != CLOSXP) |
|||
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_VECTOR_ELT(prot, 2, val); //protect gc
|
|||
} else if (key == CURLOPT_READFUNCTION) { |
|||
if (TYPEOF(val) != CLOSXP) |
|||
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_VECTOR_ELT(prot, 3, val); //protect gc
|
|||
} else if (key == CURLOPT_DEBUGFUNCTION) { |
|||
if (TYPEOF(val) != CLOSXP) |
|||
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_VECTOR_ELT(prot, 4, val); //protect gc
|
|||
} else if (key == CURLOPT_URL) { |
|||
/* always use utf-8 for urls */ |
|||
const char * url_utf8 = translateCharUTF8(STRING_ELT(val, 0)); |
|||
assert(curl_easy_setopt(handle, CURLOPT_URL, url_utf8)); |
|||
} else if(key == CURLOPT_HTTPHEADER){ |
|||
R_handle_setheaders(ptr, val); |
|||
} else if (r_curl_is_slist_option(key)) { |
|||
if(!isString(val)) |
|||
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)); |
|||
} else if(r_curl_is_long_option(key)){ |
|||
if(!isNumeric(val) || length(val) != 1) { |
|||
error("Value for option %s (%d) must be a number.", optname, key); |
|||
} |
|||
assert(curl_easy_setopt(handle, key, (long) asInteger(val))); |
|||
} else if(r_curl_is_off_t_option(key)){ |
|||
if(!isNumeric(val) || length(val) != 1) { |
|||
error("Value for option %s (%d) must be a number.", optname, key); |
|||
} |
|||
assert(curl_easy_setopt(handle, key, (curl_off_t) asReal(val))); |
|||
} else if(r_curl_is_string_option(key) || r_curl_is_postfields_option(key)){ |
|||
switch (TYPEOF(val)) { |
|||
case RAWSXP: |
|||
if(key == CURLOPT_POSTFIELDS || 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 (length(val) != 1) |
|||
error("Value for option %s (%d) must be length-1 string", optname, key); |
|||
assert(curl_easy_setopt(handle, key, CHAR(STRING_ELT(val, 0)))); |
|||
break; |
|||
default: |
|||
error("Value for option %s (%d) must be a string or raw vector.", optname, key); |
|||
} |
|||
} else { |
|||
error("Option %s (%d) has unknown or unsupported type.", optname, key); |
|||
} |
|||
} |
|||
UNPROTECT(1); |
|||
return ScalarLogical(1); |
|||
} |
|||
|
|||
SEXP R_handle_setform(SEXP ptr, SEXP form){ |
|||
if(!isVector(form)) |
|||
error("Form must be a list."); |
|||
set_form(get_ref(ptr), make_form(form)); |
|||
return ScalarLogical(1); |
|||
} |
|||
|
|||
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)); |
|||
assert(curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME, &time_connect)); |
|||
assert(curl_easy_getinfo(handle, CURLINFO_PRETRANSFER_TIME, &time_pre)); |
|||
assert(curl_easy_getinfo(handle, CURLINFO_STARTTRANSFER_TIME, &time_start)); |
|||
assert(curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &time_total)); |
|||
|
|||
SEXP result = PROTECT(allocVector(REALSXP, 6)); |
|||
REAL(result)[0] = time_redirect; |
|||
REAL(result)[1] = time_lookup; |
|||
REAL(result)[2] = time_connect; |
|||
REAL(result)[3] = time_pre; |
|||
REAL(result)[4] = time_start; |
|||
REAL(result)[5] = time_total; |
|||
|
|||
SEXP names = PROTECT(allocVector(STRSXP, 6)); |
|||
SET_STRING_ELT(names, 0, mkChar("redirect")); |
|||
SET_STRING_ELT(names, 1, mkChar("namelookup")); |
|||
SET_STRING_ELT(names, 2, mkChar("connect")); |
|||
SET_STRING_ELT(names, 3, mkChar("pretransfer")); |
|||
SET_STRING_ELT(names, 4, mkChar("starttransfer")); |
|||
SET_STRING_ELT(names, 5, mkChar("total")); |
|||
setAttrib(result, R_NamesSymbol, names); |
|||
UNPROTECT(2); |
|||
return result; |
|||
} |
|||
|
|||
/* Extract current cookies (state) from handle */ |
|||
SEXP make_cookievec(CURL *handle){ |
|||
/* linked list of strings */ |
|||
struct curl_slist *cookies; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies)); |
|||
SEXP out = slist_to_vec(cookies); |
|||
curl_slist_free_all(cookies); |
|||
return out; |
|||
} |
|||
|
|||
SEXP make_status(CURL *handle){ |
|||
long res_status; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res_status)); |
|||
return ScalarInteger(res_status); |
|||
} |
|||
|
|||
SEXP make_ctype(CURL *handle){ |
|||
char * ct; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &ct)); |
|||
return make_string(ct); |
|||
} |
|||
|
|||
SEXP make_url(CURL *handle){ |
|||
char *res_url; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &res_url)); |
|||
return ScalarString(mkCharCE(res_url, CE_UTF8)); |
|||
} |
|||
|
|||
SEXP make_filetime(CURL *handle){ |
|||
long filetime; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_FILETIME, &filetime)); |
|||
if(filetime < 0){ |
|||
filetime = NA_INTEGER; |
|||
} |
|||
|
|||
SEXP classes = PROTECT(allocVector(STRSXP, 2)); |
|||
SET_STRING_ELT(classes, 0, mkChar("POSIXct")); |
|||
SET_STRING_ELT(classes, 1, mkChar("POSIXt")); |
|||
|
|||
SEXP out = PROTECT(ScalarInteger(filetime)); |
|||
setAttrib(out, R_ClassSymbol, classes); |
|||
UNPROTECT(2); |
|||
return out; |
|||
} |
|||
|
|||
SEXP make_rawvec(unsigned char *ptr, size_t size){ |
|||
SEXP out = PROTECT(allocVector(RAWSXP, size)); |
|||
if(size > 0) |
|||
memcpy(RAW(out), ptr, size); |
|||
UNPROTECT(1); |
|||
return out; |
|||
} |
|||
|
|||
SEXP make_namesvec(){ |
|||
SEXP names = PROTECT(allocVector(STRSXP, 7)); |
|||
SET_STRING_ELT(names, 0, mkChar("url")); |
|||
SET_STRING_ELT(names, 1, mkChar("status_code")); |
|||
SET_STRING_ELT(names, 2, mkChar("type")); |
|||
SET_STRING_ELT(names, 3, mkChar("headers")); |
|||
SET_STRING_ELT(names, 4, mkChar("modified")); |
|||
SET_STRING_ELT(names, 5, mkChar("times")); |
|||
SET_STRING_ELT(names, 6, mkChar("content")); |
|||
UNPROTECT(1); |
|||
return names; |
|||
} |
|||
|
|||
SEXP R_get_handle_cookies(SEXP ptr){ |
|||
return make_cookievec(get_handle(ptr)); |
|||
} |
|||
|
|||
SEXP make_handle_response(reference *ref){ |
|||
CURL *handle = ref->handle; |
|||
SEXP res = PROTECT(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, R_NilValue); |
|||
setAttrib(res, R_NamesSymbol, make_namesvec()); |
|||
UNPROTECT(1); |
|||
return res; |
|||
} |
|||
|
|||
SEXP R_get_handle_response(SEXP ptr){ |
|||
/* get the handle */ |
|||
reference *ref = get_ref(ptr); |
|||
return make_handle_response(ref); |
|||
} |
|||
|
|||
SEXP R_total_handles(){ |
|||
return(ScalarInteger(total_handles)); |
|||
} |
@ -0,0 +1,177 @@ |
|||
#include <Rinternals.h> |
|||
|
|||
#ifdef _WIN32 |
|||
#include <Windows.h> |
|||
#include <Winhttp.h> |
|||
#include <stdlib.h> |
|||
|
|||
#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001 |
|||
#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002 |
|||
#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001 |
|||
#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002 |
|||
#define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000 |
|||
#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000 |
|||
|
|||
SEXP proxy_namesvec(){ |
|||
SEXP names = PROTECT(allocVector(STRSXP, 4)); |
|||
SET_STRING_ELT(names, 0, mkChar("AutoDetect")); |
|||
SET_STRING_ELT(names, 1, mkChar("AutoConfigUrl")); |
|||
SET_STRING_ELT(names, 2, mkChar("Proxy")); |
|||
SET_STRING_ELT(names, 3, mkChar("ProxyBypass")); |
|||
UNPROTECT(1); |
|||
return names; |
|||
} |
|||
|
|||
SEXP auto_namesvec(){ |
|||
SEXP names = PROTECT(allocVector(STRSXP, 3)); |
|||
SET_STRING_ELT(names, 0, mkChar("HasProxy")); |
|||
SET_STRING_ELT(names, 1, mkChar("Proxy")); |
|||
SET_STRING_ELT(names, 2, mkChar("ProxyBypass")); |
|||
UNPROTECT(1); |
|||
return names; |
|||
} |
|||
|
|||
SEXP R_proxy_info(){ |
|||
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG MyProxyConfig; |
|||
if(!WinHttpGetIEProxyConfigForCurrentUser(&MyProxyConfig)){ |
|||
return R_NilValue; |
|||
} |
|||
char buffer[500]; |
|||
SEXP vec = PROTECT(allocVector(VECSXP, 4)); |
|||
SET_VECTOR_ELT(vec, 0, ScalarLogical(MyProxyConfig.fAutoDetect)); |
|||
|
|||
if(MyProxyConfig.lpszAutoConfigUrl != NULL) { |
|||
wcstombs(buffer, MyProxyConfig.lpszAutoConfigUrl, 500); |
|||
SET_VECTOR_ELT(vec, 1, mkString(buffer)); |
|||
} |
|||
|
|||
if(MyProxyConfig.lpszProxy != NULL) { |
|||
wcstombs(buffer, MyProxyConfig.lpszProxy, 500); |
|||
SET_VECTOR_ELT(vec, 2, mkString(buffer)); |
|||
} |
|||
|
|||
if(MyProxyConfig.lpszProxyBypass != NULL) { |
|||
wcstombs(buffer, MyProxyConfig.lpszProxyBypass, 500); |
|||
SET_VECTOR_ELT(vec, 3, mkString(buffer)); |
|||
} |
|||
setAttrib(vec, R_NamesSymbol, proxy_namesvec()); |
|||
UNPROTECT(1); |
|||
return vec; |
|||
} |
|||
|
|||
SEXP R_get_proxy_for_url(SEXP target_url, SEXP auto_detect, SEXP autoproxy_url){ |
|||
// Convert char to windows strings
|
|||
wchar_t *longurl = (wchar_t *) calloc(10000, sizeof(int)); |
|||
mbstowcs(longurl, CHAR(STRING_ELT(target_url, 0)), LENGTH(STRING_ELT(target_url, 0))); |
|||
|
|||
// Some settings
|
|||
WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions; |
|||
WINHTTP_PROXY_INFO ProxyInfo; |
|||
|
|||
// Clear memory
|
|||
ZeroMemory( &AutoProxyOptions, sizeof(AutoProxyOptions) ); |
|||
ZeroMemory( &ProxyInfo, sizeof(ProxyInfo) ); |
|||
|
|||
// Create the WinHTTP session.
|
|||
HINTERNET hHttpSession = WinHttpOpen( L"WinHTTP AutoProxy Sample/1.0", |
|||
WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); |
|||
|
|||
// Exit if WinHttpOpen failed.
|
|||
if( !hHttpSession ) |
|||
error("Call to WinHttpOpen failed."); |
|||
|
|||
// Auto-detection doesn't work very well
|
|||
if(asLogical(auto_detect)){ |
|||
AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; |
|||
AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; |
|||
} |
|||
|
|||
// Use manual URL instead
|
|||
if(isString(autoproxy_url) && LENGTH(autoproxy_url)){ |
|||
wchar_t *autourl = (wchar_t *) calloc(10000, sizeof(int)); |
|||
mbstowcs(autourl, CHAR(STRING_ELT(autoproxy_url, 0)), LENGTH(STRING_ELT(autoproxy_url, 0))); |
|||
AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; |
|||
AutoProxyOptions.lpszAutoConfigUrl = autourl; |
|||
} |
|||
|
|||
// Use DHCP and DNS-based auto-detection.
|
|||
AutoProxyOptions.fAutoLogonIfChallenged = TRUE; |
|||
|
|||
// This downloads and runs the JavaScript to map the url to a proxy
|
|||
if(!WinHttpGetProxyForUrl( hHttpSession, longurl, &AutoProxyOptions, &ProxyInfo)){ |
|||
DWORD err = GetLastError(); |
|||
switch(err){ |
|||
case ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR: |
|||
error("ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR"); |
|||
case ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT: |
|||
error("ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT"); |
|||
case ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: |
|||
error("ERROR_WINHTTP_INCORRECT_HANDLE_TYPE"); |
|||
case ERROR_WINHTTP_INTERNAL_ERROR: |
|||
error("ERROR_WINHTTP_INTERNAL_ERROR"); |
|||
case ERROR_WINHTTP_INVALID_URL: |
|||
error("ERROR_WINHTTP_INVALID_URL"); |
|||
case ERROR_WINHTTP_LOGIN_FAILURE: |
|||
error("ERROR_WINHTTP_LOGIN_FAILURE"); |
|||
case ERROR_WINHTTP_OPERATION_CANCELLED: |
|||
error("ERROR_WINHTTP_OPERATION_CANCELLED"); |
|||
case ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT: |
|||
error("ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT"); |
|||
case ERROR_WINHTTP_UNRECOGNIZED_SCHEME: |
|||
error("ERROR_WINHTTP_UNRECOGNIZED_SCHEME"); |
|||
case ERROR_NOT_ENOUGH_MEMORY: |
|||
error("ERROR_NOT_ENOUGH_MEMORY"); |
|||
} |
|||
} |
|||
|
|||
//store output data
|
|||
char buffer[500]; |
|||
SEXP vec = PROTECT(allocVector(VECSXP, 3)); |
|||
SET_VECTOR_ELT(vec, 0, ScalarLogical( |
|||
ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY || |
|||
ProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_DEFAULT_PROXY)); |
|||
|
|||
if(ProxyInfo.lpszProxy != NULL) { |
|||
wcstombs(buffer, ProxyInfo.lpszProxy, 500); |
|||
SET_VECTOR_ELT(vec, 1, mkString(buffer)); |
|||
GlobalFree((void*) ProxyInfo.lpszProxy); |
|||
} |
|||
|
|||
if(ProxyInfo.lpszProxyBypass != NULL) { |
|||
wcstombs(buffer, ProxyInfo.lpszProxyBypass, 500); |
|||
SET_VECTOR_ELT(vec, 2, mkString(buffer)); |
|||
GlobalFree((void*) ProxyInfo.lpszProxyBypass ); |
|||
} |
|||
|
|||
//clean up
|
|||
WinHttpCloseHandle( hHttpSession ); |
|||
|
|||
//return
|
|||
setAttrib(vec, R_NamesSymbol, auto_namesvec()); |
|||
UNPROTECT(1); |
|||
return vec; |
|||
} |
|||
|
|||
SEXP R_windows_build(){ |
|||
DWORD dwBuild = 0; |
|||
DWORD dwVersion = GetVersion(); |
|||
if (dwVersion < 0x80000000) |
|||
dwBuild = (DWORD)(HIWORD(dwVersion)); |
|||
return ScalarInteger(dwBuild); |
|||
} |
|||
|
|||
#else //_WIN32
|
|||
|
|||
SEXP R_proxy_info(){ |
|||
return R_NilValue; |
|||
} |
|||
|
|||
SEXP R_get_proxy_for_url(SEXP target_url, SEXP auto_detect, SEXP autoproxy_url){ |
|||
return R_NilValue; |
|||
} |
|||
|
|||
SEXP R_windows_build(){ |
|||
return R_NilValue; |
|||
} |
|||
|
|||
#endif //_WIN32
|
@ -0,0 +1,96 @@ |
|||
#include <R.h> |
|||
#include <Rinternals.h> |
|||
#include <stdlib.h> // for NULL |
|||
#include <R_ext/Rdynload.h> |
|||
#include <R_ext/Visibility.h> |
|||
#include <curl/curl.h> |
|||
|
|||
/* .Call calls */ |
|||
extern SEXP R_curl_connection(SEXP, SEXP, 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); |
|||
extern SEXP R_curl_getdate(SEXP); |
|||
extern SEXP R_curl_version(); |
|||
extern SEXP R_download_curl(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); |
|||
extern SEXP R_get_bundle(); |
|||
extern SEXP R_get_handle_cookies(SEXP); |
|||
extern SEXP R_get_handle_response(SEXP); |
|||
extern SEXP R_get_proxy_for_url(SEXP, SEXP, SEXP); |
|||
extern SEXP R_handle_getcustom(SEXP); |
|||
extern SEXP R_handle_getheaders(SEXP); |
|||
extern SEXP R_handle_reset(SEXP); |
|||
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_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(); |
|||
extern SEXP R_multi_run(SEXP, SEXP, SEXP); |
|||
extern SEXP R_multi_setopt(SEXP, SEXP, SEXP, SEXP); |
|||
extern SEXP R_new_file_writer(SEXP); |
|||
extern SEXP R_new_handle(); |
|||
extern SEXP R_nslookup(SEXP, SEXP); |
|||
extern SEXP R_proxy_info(); |
|||
extern SEXP R_set_bundle(SEXP); |
|||
extern SEXP R_split_string(SEXP, SEXP); |
|||
extern SEXP R_total_handles(); |
|||
extern SEXP R_total_writers(); |
|||
extern SEXP R_windows_build(); |
|||
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_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}, |
|||
{"R_curl_getdate", (DL_FUNC) &R_curl_getdate, 1}, |
|||
{"R_curl_version", (DL_FUNC) &R_curl_version, 0}, |
|||
{"R_download_curl", (DL_FUNC) &R_download_curl, 6}, |
|||
{"R_get_bundle", (DL_FUNC) &R_get_bundle, 0}, |
|||
{"R_get_handle_cookies", (DL_FUNC) &R_get_handle_cookies, 1}, |
|||
{"R_get_handle_response", (DL_FUNC) &R_get_handle_response, 1}, |
|||
{"R_get_proxy_for_url", (DL_FUNC) &R_get_proxy_for_url, 3}, |
|||
{"R_handle_getcustom", (DL_FUNC) &R_handle_getcustom, 1}, |
|||
{"R_handle_getheaders", (DL_FUNC) &R_handle_getheaders, 1}, |
|||
{"R_handle_reset", (DL_FUNC) &R_handle_reset, 1}, |
|||
{"R_handle_setform", (DL_FUNC) &R_handle_setform, 2}, |
|||
{"R_handle_setheaders", (DL_FUNC) &R_handle_setheaders, 2}, |
|||
{"R_handle_setopt", (DL_FUNC) &R_handle_setopt, 3}, |
|||
{"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_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_proxy_info", (DL_FUNC) &R_proxy_info, 0}, |
|||
{"R_set_bundle", (DL_FUNC) &R_set_bundle, 1}, |
|||
{"R_split_string", (DL_FUNC) &R_split_string, 2}, |
|||
{"R_total_handles", (DL_FUNC) &R_total_handles, 0}, |
|||
{"R_total_writers", (DL_FUNC) &R_total_writers, 0}, |
|||
{"R_windows_build", (DL_FUNC) &R_windows_build, 0}, |
|||
{"R_write_file_writer", (DL_FUNC) &R_write_file_writer, 3}, |
|||
{NULL, NULL, 0} |
|||
}; |
|||
|
|||
void select_ssl_backend(); |
|||
CURLM *multi_handle = NULL; |
|||
|
|||
attribute_visible void R_init_curl(DllInfo *info) { |
|||
select_ssl_backend(); |
|||
curl_global_init(CURL_GLOBAL_DEFAULT); |
|||
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_global_cleanup();
|
|||
} |
@ -0,0 +1,69 @@ |
|||
/* Non-blocking drop-in replacement for curl_easy_perform with support for
|
|||
* R interruptions. Based on: https://curl.haxx.se/libcurl/c/multi-single.html
|
|||
*/ |
|||
|
|||
#include <Rinternals.h> |
|||
#include "curl-common.h" |
|||
|
|||
/* Check for interrupt without long jumping */ |
|||
void check_interrupt_fn(void *dummy) { |
|||
R_CheckUserInterrupt(); |
|||
} |
|||
|
|||
int pending_interrupt() { |
|||
return !(R_ToplevelExec(check_interrupt_fn, NULL)); |
|||
} |
|||
|
|||
/* created in init.c */ |
|||
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; |
|||
int still_running = 1; |
|||
|
|||
if(CURLM_OK != curl_multi_add_handle(multi_handle, handle)){ |
|||
return CURLE_FAILED_INIT; |
|||
} |
|||
|
|||
/* non blocking downloading */ |
|||
while(still_running) { |
|||
if(pending_interrupt()){ |
|||
status = CURLE_ABORTED_BY_CALLBACK; |
|||
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) |
|||
break; |
|||
} |
|||
|
|||
/* set status if handle has completed. This might be overkill */ |
|||
if(!still_running){ |
|||
int msgq = 0; |
|||
do { |
|||
CURLMsg *m = curl_multi_info_read(multi_handle, &msgq); |
|||
if(m && (m->msg == CURLMSG_DONE)){ |
|||
status = m->data.result; |
|||
break; |
|||
} |
|||
} while (msgq > 0); |
|||
} |
|||
|
|||
/* cleanup first */ |
|||
curl_multi_remove_handle(multi_handle, handle); |
|||
return status; |
|||
} |
@ -0,0 +1,202 @@ |
|||
#' Async Multi Download |
|||
#' |
|||
#' AJAX style concurrent requests, possibly using HTTP/2 multiplexing. |
|||
#' Results are only available via callback functions. Advanced use only! |
|||
#' |
|||
#' 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 \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 \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 \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. |
|||
#' |
|||
#' A single handle cannot be used for multiple simultaneous requests. However |
|||
#' it is possible to add new requests to a pool while it is running, so you |
|||
#' can re-use a handle within the callback of a request from that same handle. |
|||
#' 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. |
|||
#' It has no effect if the request was already completed or canceled. |
|||
#' |
|||
#' 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 \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. |
|||
#' |
|||
#' @name multi |
|||
#' @rdname multi |
|||
#' @useDynLib curl R_multi_add |
|||
#' @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 \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 \emph{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. |
|||
#' @export |
|||
#' @examples |
|||
#' results <- list() |
|||
#' success <- function(x){ |
|||
#' results <<- append(results, list(x)) |
|||
#' } |
|||
#' failure <- function(str){ |
|||
#' cat(paste("Failed request:", str), file = stderr()) |
|||
#' } |
|||
#' # This handle will take longest (3sec) |
|||
#' h1 <- new_handle(url = "https://eu.httpbin.org/delay/3") |
|||
#' multi_add(h1, done = success, fail = failure) |
|||
#' |
|||
#' # This handle writes data to a file |
|||
#' con <- file("output.txt") |
|||
#' h2 <- new_handle(url = "https://eu.httpbin.org/post", postfields = "bla bla") |
|||
#' multi_add(h2, done = success, fail = failure, data = con) |
|||
#' |
|||
#' # This handle raises an error |
|||
#' h3 <- new_handle(url = "https://urldoesnotexist.xyz") |
|||
#' multi_add(h3, done = success, fail = failure) |
|||
#' |
|||
#' # Actually perform the requests |
|||
#' multi_run(timeout = 2) |
|||
#' multi_run() |
|||
#' |
|||
#' # Check the file |
|||
#' readLines("output.txt") |
|||
#' unlink("output.txt") |
|||
multi_add <- function(handle, done = NULL, fail = NULL, data = NULL, pool = NULL){ |
|||
if(is.null(pool)) |
|||
pool <- multi_default() |
|||
if(inherits(data, "connection")){ |
|||
con <- data |
|||
if(isOpen(con) && identical(summary(con)$text, "text")){ |
|||
data <- function(x, finalize = FALSE){ |
|||
cat(rawToChar(x), file = con) |
|||
flush(con) |
|||
} |
|||
} else { |
|||
was_open <- isOpen(con) |
|||
data <- function(x, finalize = FALSE){ |
|||
if(!isOpen(con)) |
|||
open(con, 'wb') |
|||
writeBin(x, con = con) |
|||
if(isTRUE(finalize && !was_open)){ |
|||
close(con) |
|||
} else { |
|||
flush(con) |
|||
} |
|||
} |
|||
} |
|||
} else if(is_string(data)){ |
|||
data <- file_writer(path = data) |
|||
} |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
stopifnot(inherits(pool, "curl_multi")) |
|||
stopifnot(is.null(done) || is.function(done)) |
|||
stopifnot(is.null(fail) || is.function(fail)) |
|||
stopifnot(is.null(data) || is.function(data)) |
|||
.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 |
|||
#' waiting at all. |
|||
#' @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 |
|||
#' @rdname multi |
|||
multi_run <- function(timeout = Inf, poll = FALSE, pool = NULL){ |
|||
if(is.null(pool)) |
|||
pool <- multi_default() |
|||
stopifnot(is.numeric(timeout)) |
|||
stopifnot(inherits(pool, "curl_multi")) |
|||
.Call(R_multi_run, pool, timeout, as.integer(poll)) |
|||
} |
|||
|
|||
#' @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. |
|||
#' @export |
|||
#' @useDynLib curl R_multi_setopt |
|||
#' @rdname multi |
|||
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.logical(multiplex)) |
|||
.Call(R_multi_setopt, pool, total_con, host_con, multiplex) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_multi_list |
|||
#' @rdname multi |
|||
multi_list <- function(pool = NULL){ |
|||
if(is.null(pool)) |
|||
pool <- multi_default() |
|||
stopifnot(inherits(pool, "curl_multi")) |
|||
as.list(.Call(R_multi_list, pool)) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_multi_cancel |
|||
#' @rdname multi |
|||
multi_cancel <- function(handle){ |
|||
stopifnot(inherits(handle, "curl_handle")) |
|||
.Call(R_multi_cancel, handle) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_multi_new |
|||
#' @rdname multi |
|||
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, multiplex = multiplex) |
|||
} |
|||
|
|||
multi_default <- local({ |
|||
global_multi_handle <- NULL |
|||
function(){ |
|||
if(is.null(global_multi_handle)){ |
|||
global_multi_handle <<- new_pool() |
|||
} |
|||
stopifnot(inherits(global_multi_handle, "curl_multi")) |
|||
return(global_multi_handle) |
|||
} |
|||
}) |
|||
|
|||
#' @export |
|||
print.curl_multi <- function(x, ...){ |
|||
len <- length(multi_list(x)) |
|||
cat(sprintf("<curl multi-pool> (%d pending requests)\n", len)) |
|||
} |
|||
|
|||
#' @export |
|||
#' @useDynLib curl R_multi_fdset |
|||
#' @rdname multi |
|||
|
|||
multi_fdset <- function(pool = NULL){ |
|||
if(is.null(pool)) |
|||
pool <- multi_default() |
|||
stopifnot(inherits(pool, "curl_multi")) |
|||
.Call(R_multi_fdset, pool) |
|||
} |
@ -0,0 +1,316 @@ |
|||
#include "curl-common.h" |
|||
#include <time.h> |
|||
|
|||
/* Notes:
|
|||
* - First check for unhandled messages in curl_multi_info_read() before curl_multi_perform() |
|||
* - Use 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"); |
|||
multiref *mref = (multiref*) R_ExternalPtrAddr(ptr); |
|||
if(!mref) |
|||
Rf_error("multiref pointer is dead"); |
|||
return mref; |
|||
} |
|||
|
|||
void multi_release(reference *ref){ |
|||
/* Release the easy-handle */ |
|||
CURL *handle = ref->handle; |
|||
CURLM *multi = ref->async.mref->m; |
|||
massert(curl_multi_remove_handle(multi, handle)); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL); |
|||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL); |
|||
|
|||
/* Remove the curl handle from the handles list */ |
|||
ref->async.mref->handles = reflist_remove(ref->async.mref->handles, ref->handleptr); |
|||
R_SetExternalPtrProtected(ref->async.mref->multiptr, ref->async.mref->handles); |
|||
SET_VECTOR_ELT(R_ExternalPtrProtected(ref->handleptr), 0, R_NilValue); |
|||
|
|||
/* Reset multi state struct */ |
|||
if(ref->async.content.buf){ |
|||
free(ref->async.content.buf); |
|||
ref->async.content.buf = NULL; |
|||
ref->async.content.size = 0; |
|||
} |
|||
ref->async.mref = NULL; |
|||
ref->async.content.buf = NULL; |
|||
ref->async.content.size = 0; |
|||
ref->async.complete = NULL; |
|||
ref->async.error = NULL; |
|||
ref->async.data = NULL; |
|||
ref->async.node = NULL; |
|||
|
|||
/* Unlock handle (and cleanup if needed) */ |
|||
ref->locked = 0; |
|||
ref->refCount--; |
|||
clean_handle(ref); |
|||
} |
|||
|
|||
SEXP R_multi_cancel(SEXP handle_ptr){ |
|||
reference *ref = get_ref(handle_ptr); |
|||
if(ref->async.mref) |
|||
multi_release(ref); |
|||
return handle_ptr; |
|||
} |
|||
|
|||
SEXP R_multi_add(SEXP handle_ptr, SEXP cb_complete, SEXP cb_error, SEXP cb_data, SEXP pool_ptr){ |
|||
multiref *mref = get_multiref(pool_ptr); |
|||
CURLM *multi = mref->m; |
|||
|
|||
reference *ref = get_ref(handle_ptr); |
|||
if(ref->locked) |
|||
Rf_error("Handle is locked. Probably in use in a connection or async request."); |
|||
|
|||
/* placeholder body */ |
|||
if(Rf_isFunction(cb_data)){ |
|||
curl_easy_setopt(ref->handle, CURLOPT_WRITEFUNCTION, (curl_write_callback) data_callback); |
|||
curl_easy_setopt(ref->handle, CURLOPT_WRITEDATA, cb_data); |
|||
} else { |
|||
curl_easy_setopt(ref->handle, CURLOPT_WRITEFUNCTION, append_buffer); |
|||
curl_easy_setopt(ref->handle, CURLOPT_WRITEDATA, &(ref->async.content)); |
|||
} |
|||
|
|||
/* add to scheduler */ |
|||
massert(curl_multi_add_handle(multi, ref->handle)); |
|||
|
|||
/* create node in ref */ |
|||
ref->async.mref = mref; |
|||
mref->handles = reflist_add(mref->handles, handle_ptr); |
|||
R_SetExternalPtrProtected(pool_ptr, mref->handles); |
|||
|
|||
/* set multi callbacks */ |
|||
ref->async.complete = cb_complete; |
|||
ref->async.error = cb_error; |
|||
ref->async.data = cb_data; |
|||
SET_VECTOR_ELT(R_ExternalPtrProtected(handle_ptr), 0, |
|||
Rf_list3(cb_error, cb_complete, cb_data)); |
|||
|
|||
/* lock and protect handle */ |
|||
ref->refCount++; |
|||
ref->locked = 1; |
|||
return handle_ptr; |
|||
} |
|||
|
|||
SEXP R_multi_run(SEXP pool_ptr, SEXP timeout, SEXP max){ |
|||
multiref *mref = get_multiref(pool_ptr); |
|||
CURLM *multi = mref->m; |
|||
|
|||
int total_pending = -1; |
|||
int total_success = 0; |
|||
int total_fail = 0; |
|||
int result_max = asInteger(max); |
|||
double time_max = asReal(timeout); |
|||
time_t time_start = time(NULL); |
|||
|
|||
double seconds_elapsed = 0; |
|||
while(1) { |
|||
/* check for completed requests */ |
|||
int dirty = 0; |
|||
int msgq = 1; |
|||
while (msgq > 0) { |
|||
CURLMsg *m = curl_multi_info_read(multi, &msgq); |
|||
if(m && (m->msg == CURLMSG_DONE)){ |
|||
dirty = 1; |
|||
reference *ref = NULL; |
|||
CURL *handle = m->easy_handle; |
|||
CURLcode status = m->data.result; |
|||
assert(curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char**) &ref)); |
|||
|
|||
// prepare for callback
|
|||
SEXP cb_complete = PROTECT(ref->async.complete); |
|||
SEXP cb_error = PROTECT(ref->async.error); |
|||
SEXP cb_data = PROTECT(ref->async.data); |
|||
SEXP buf = PROTECT(allocVector(RAWSXP, ref->async.content.size)); |
|||
if(ref->async.content.buf && ref->async.content.size) |
|||
memcpy(RAW(buf), ref->async.content.buf, ref->async.content.size); |
|||
|
|||
//release handle for use by callbacks
|
|||
multi_release(ref); |
|||
|
|||
// Trigger a final 'data' with second argument to TRUE
|
|||
// This also ensures that a file is consistently created, even for empty responses
|
|||
if(Rf_isFunction(cb_data)){ |
|||
SEXP buf = PROTECT(Rf_allocVector(RAWSXP, 0)); |
|||
SEXP call = PROTECT(Rf_lang3(cb_data, buf, ScalarInteger(1))); |
|||
eval(call, R_GlobalEnv); |
|||
UNPROTECT(2); |
|||
} |
|||
|
|||
// callbacks must be trycatch! we should continue the loop
|
|||
if(status == CURLE_OK){ |
|||
total_success++; |
|||
if(Rf_isFunction(cb_complete)){ |
|||
int arglen = Rf_length(FORMALS(cb_complete)); |
|||
SEXP out = PROTECT(make_handle_response(ref)); |
|||
SET_VECTOR_ELT(out, 6, buf); |
|||
SEXP call = PROTECT(LCONS(cb_complete, arglen ? LCONS(out, R_NilValue) : R_NilValue)); |
|||
//R_tryEval(call, R_GlobalEnv, &cbfail);
|
|||
eval(call, R_GlobalEnv); //OK to error here
|
|||
UNPROTECT(2); |
|||
} |
|||
} else { |
|||
total_fail++; |
|||
if(Rf_isFunction(cb_error)){ |
|||
int arglen = Rf_length(FORMALS(cb_error)); |
|||
SEXP buf = PROTECT(mkString(strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(status))); |
|||
SEXP call = PROTECT(LCONS(cb_error, arglen ? LCONS(buf, R_NilValue) : R_NilValue)); |
|||
//R_tryEval(call, R_GlobalEnv, &cbfail);
|
|||
eval(call, R_GlobalEnv); //OK to error here
|
|||
UNPROTECT(2); |
|||
} |
|||
} |
|||
UNPROTECT(4); |
|||
} |
|||
R_CheckUserInterrupt(); |
|||
} |
|||
|
|||
/* check for user interruptions */ |
|||
//if(pending_interrupt()) break;
|
|||
R_CheckUserInterrupt(); |
|||
|
|||
/* check for timeout or max result*/ |
|||
if(result_max > 0 && total_success + total_fail >= result_max) |
|||
break; |
|||
if(time_max == 0 && total_pending != -1) |
|||
break; |
|||
if(time_max > 0){ |
|||
seconds_elapsed = (double) (time(NULL) - time_start); |
|||
if(seconds_elapsed > time_max) |
|||
break; |
|||
} |
|||
|
|||
/* check if we are done */ |
|||
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) |
|||
break; |
|||
} |
|||
|
|||
SEXP res = PROTECT(allocVector(VECSXP, 3)); |
|||
SET_VECTOR_ELT(res, 0, ScalarInteger(total_success)); |
|||
SET_VECTOR_ELT(res, 1, ScalarInteger(total_fail)); |
|||
SET_VECTOR_ELT(res, 2, ScalarInteger(total_pending)); |
|||
|
|||
SEXP names = PROTECT(allocVector(STRSXP, 3)); |
|||
SET_STRING_ELT(names, 0, mkChar("success")); |
|||
SET_STRING_ELT(names, 1, mkChar("error")); |
|||
SET_STRING_ELT(names, 2, mkChar("pending")); |
|||
setAttrib(res, R_NamesSymbol, names); |
|||
UNPROTECT(2); |
|||
return res; |
|||
} |
|||
|
|||
void fin_multi(SEXP ptr){ |
|||
multiref *mref = get_multiref(ptr); |
|||
SEXP handles = mref->handles; |
|||
while(handles != R_NilValue){ |
|||
multi_release(get_ref(CAR(handles))); |
|||
handles = CDR(handles); |
|||
} |
|||
curl_multi_cleanup(mref->m); |
|||
free(mref); |
|||
R_ClearExternalPtr(ptr); |
|||
} |
|||
|
|||
SEXP R_multi_new(){ |
|||
multiref *ref = calloc(1, sizeof(multiref)); |
|||
ref->m = curl_multi_init(); |
|||
ref->handles = reflist_init(); |
|||
SEXP ptr = PROTECT(R_MakeExternalPtr(ref, R_NilValue, ref->handles)); |
|||
ref->multiptr = ptr; |
|||
R_RegisterCFinalizerEx(ptr, fin_multi, 1); |
|||
setAttrib(ptr, R_ClassSymbol, mkString("curl_multi")); |
|||
UNPROTECT(1); |
|||
return ptr; |
|||
} |
|||
|
|||
SEXP R_multi_setopt(SEXP pool_ptr, SEXP total_con, SEXP host_con, SEXP multiplex){ |
|||
multiref *mref = get_multiref(pool_ptr); |
|||
CURLM *multi = mref->m; |
|||
|
|||
// NOTE: CURLPIPE_HTTP1 is unsafe for non idempotent requests
|
|||
#ifdef CURLPIPE_MULTIPLEX |
|||
massert(curl_multi_setopt(multi, CURLMOPT_PIPELINING, |
|||
asLogical(multiplex) ? CURLPIPE_MULTIPLEX : CURLPIPE_NOTHING)); |
|||
#endif |
|||
|
|||
#ifdef HAS_CURLMOPT_MAX_TOTAL_CONNECTIONS |
|||
massert(curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) asInteger(total_con))); |
|||
massert(curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, (long) asInteger(host_con))); |
|||
#endif |
|||
return pool_ptr; |
|||
} |
|||
|
|||
SEXP R_multi_list(SEXP pool_ptr){ |
|||
return get_multiref(pool_ptr)->handles; |
|||
} |
|||
|
|||
SEXP R_multi_fdset(SEXP 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; |
|||
long timeout; |
|||
SEXP result, names; |
|||
|
|||
FD_ZERO(&read_fd_set); |
|||
FD_ZERO(&write_fd_set); |
|||
FD_ZERO(&exc_fd_set); |
|||
|
|||
massert(curl_multi_fdset(multi, &read_fd_set, &write_fd_set, |
|||
&exc_fd_set, &max_fd)); |
|||
|
|||
massert(curl_multi_timeout(multi, &timeout)); |
|||
|
|||
for (i = 0; i <= max_fd; i++){ |
|||
if (FD_ISSET(i, &read_fd_set)) num_read++; |
|||
if (FD_ISSET(i, &write_fd_set)) num_write++; |
|||
if (FD_ISSET(i, &exc_fd_set)) num_exc++; |
|||
} |
|||
|
|||
result = PROTECT(allocVector(VECSXP, 4)); |
|||
SET_VECTOR_ELT(result, 0, allocVector(INTSXP, num_read)); |
|||
SET_VECTOR_ELT(result, 1, allocVector(INTSXP, num_write)); |
|||
SET_VECTOR_ELT(result, 2, allocVector(INTSXP, num_exc)); |
|||
SET_VECTOR_ELT(result, 3, ScalarReal((double) timeout)); |
|||
|
|||
names = PROTECT(allocVector(STRSXP, 4)); |
|||
SET_STRING_ELT(names, 0, mkChar("reads")); |
|||
SET_STRING_ELT(names, 1, mkChar("writes")); |
|||
SET_STRING_ELT(names, 2, mkChar("exceptions")); |
|||
SET_STRING_ELT(names, 3, mkChar("timeout")); |
|||
setAttrib(result, R_NamesSymbol, names); |
|||
|
|||
pread = INTEGER(VECTOR_ELT(result, 0)); |
|||
pwrite = INTEGER(VECTOR_ELT(result, 1)); |
|||
pexc = INTEGER(VECTOR_ELT(result, 2)); |
|||
|
|||
for (i = 0; i <= max_fd; i++){ |
|||
if (FD_ISSET(i, &read_fd_set)) *(pread++) = i; |
|||
if (FD_ISSET(i, &write_fd_set)) *(pwrite++) = i; |
|||
if (FD_ISSET(i, &exc_fd_set)) *(pexc++) = i; |
|||
} |
|||
|
|||
UNPROTECT(2); |
|||
return result; |
|||
} |
@ -0,0 +1,37 @@ |
|||
#' Lookup a hostname |
|||
#' |
|||
#' 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 \code{has_internet} function tests the internet |
|||
#' connection by resolving a random address. |
|||
#' |
|||
#' @export |
|||
#' @param host a string with a hostname |
|||
#' @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.r-project.org") |
|||
#' |
|||
#' # If your OS supports IPv6 |
|||
#' nslookup("ipv6.test-ipv6.com", ipv4_only = FALSE, error = FALSE) |
|||
nslookup <- function(host, ipv4_only = FALSE, multiple = FALSE, error = TRUE){ |
|||
stopifnot(is.character(host)) |
|||
host <- enc2utf8(host) |
|||
if(grepl("://", host, fixed = TRUE)) |
|||
stop("This looks like a URL, not a hostname") |
|||
out <- .Call(R_nslookup, host[1], as.logical(ipv4_only)) |
|||
if(isTRUE(error) && is.null(out)) |
|||
stop("Unable to resolve host: ", host) |
|||
if(isTRUE(multiple)) |
|||
return(unique(out)) |
|||
utils::head(out, 1) |
|||
} |
|||
|
|||
#' @export |
|||
#' @rdname nslookup |
|||
has_internet <- function(){ |
|||
!is.null(nslookup("google.com", error = FALSE)) |
|||
} |
@ -0,0 +1,100 @@ |
|||
//libcurl internal punycode converter
|
|||
#ifdef _WIN32 |
|||
int jeroen_win32_idn_to_ascii(const char *in, char **out); |
|||
#endif |
|||
|
|||
//getaddrinfo is an extension (not C99)
|
|||
#if !defined(_WIN32) && !defined(__sun) && !defined(_POSIX_C_SOURCE) |
|||
#define _POSIX_C_SOURCE 200112L |
|||
#endif |
|||
|
|||
#include <Rinternals.h> |
|||
#include <string.h> |
|||
|
|||
#ifdef _WIN32 |
|||
#include <winsock2.h> |
|||
#include <ws2tcpip.h> |
|||
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); |
|||
#else |
|||
#include <sys/socket.h> |
|||
#include <netinet/in.h> |
|||
#include <netdb.h> |
|||
#include <arpa/inet.h> |
|||
#endif |
|||
|
|||
SEXP R_nslookup(SEXP hostname, SEXP ipv4_only) { |
|||
/* Because gethostbyname() is deprecated */ |
|||
struct addrinfo hints = {0}; |
|||
if(asLogical(ipv4_only)) |
|||
hints.ai_family = AF_INET; //only allow ipv4
|
|||
struct addrinfo *addr; |
|||
const char * hoststr = CHAR(STRING_ELT(hostname, 0)); |
|||
#ifdef _WIN32 |
|||
if(Rf_getCharCE(STRING_ELT(hostname, 0)) == CE_UTF8){ |
|||
char * punycode; |
|||
if(jeroen_win32_idn_to_ascii(hoststr, &punycode)) |
|||
hoststr = punycode; |
|||
} |
|||
#endif |
|||
if(getaddrinfo(hoststr, NULL, &hints, &addr)) |
|||
return R_NilValue; |
|||
|
|||
// count number of hits
|
|||
int len = 0; |
|||
struct addrinfo * cur = addr; |
|||
while(cur != NULL){ |
|||
len++; |
|||
cur = cur->ai_next; |
|||
} |
|||
|
|||
//allocate output
|
|||
SEXP out = PROTECT(allocVector(STRSXP, len)); |
|||
|
|||
//extract the values
|
|||
cur = addr; |
|||
for(size_t i = 0; i < len; i++) { |
|||
struct sockaddr *sa = cur->ai_addr; |
|||
|
|||
/* IPv4 vs v6 */ |
|||
char ip[INET6_ADDRSTRLEN]; |
|||
if (sa->sa_family == AF_INET) { |
|||
struct sockaddr_in *sa_in = (struct sockaddr_in*) sa; |
|||
inet_ntop(AF_INET, &(sa_in->sin_addr), ip, INET_ADDRSTRLEN); |
|||
} else { |
|||
struct sockaddr_in6 *sa_in = (struct sockaddr_in6*) sa; |
|||
inet_ntop(AF_INET6, &(sa_in->sin6_addr), ip, INET6_ADDRSTRLEN); |
|||
} |
|||
SET_STRING_ELT(out, i, mkChar(ip)); |
|||
cur = cur->ai_next; |
|||
} |
|||
UNPROTECT(1); |
|||
freeaddrinfo(addr); |
|||
return out; |
|||
} |
|||
|
|||
/* Fallback implementation for inet_ntop in Win32 */ |
|||
|
|||
#if defined(_WIN32) && !defined(_WIN64) |
|||
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) |
|||
{ |
|||
struct sockaddr_storage ss; |
|||
unsigned long s = size; |
|||
|
|||
ZeroMemory(&ss, sizeof(ss)); |
|||
ss.ss_family = af; |
|||
|
|||
switch(af) { |
|||
case AF_INET: |
|||
((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; |
|||
break; |
|||
case AF_INET6: |
|||
((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; |
|||
break; |
|||
default: |
|||
return NULL; |
|||
} |
|||
/* cannot direclty use &size because of strict aliasing rules */ |
|||
return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)? |
|||
dst : NULL; |
|||
} |
|||
#endif |
@ -0,0 +1,27 @@ |
|||
.onLoad <- function(libname, pkgname){ |
|||
if (!grepl("mingw", R.Version()$platform)) |
|||
return() |
|||
|
|||
# Enable SSL on Windows if CA bundle is available (R >= 3.2.0) |
|||
bundle <- Sys.getenv("CURL_CA_BUNDLE", |
|||
file.path(R.home("etc"), "curl-ca-bundle.crt")) |
|||
if (bundle != "" && file.exists(bundle)) { |
|||
set_bundle(bundle) |
|||
} |
|||
} |
|||
|
|||
.onAttach <- function(libname, pkgname){ |
|||
if (grepl("mingw", R.Version()$platform) && !file.exists(get_bundle())){ |
|||
warning("No CA bundle found. SSL validation disabled.", call. = FALSE) |
|||
} |
|||
} |
|||
|
|||
#' @useDynLib curl R_set_bundle |
|||
set_bundle <- function(path){ |
|||
.Call(R_set_bundle, path) |
|||
} |
|||
|
|||
#' @useDynLib curl R_get_bundle |
|||
get_bundle <- function(){ |
|||
.Call(R_get_bundle) |
|||
} |
@ -0,0 +1,37 @@ |
|||
#' 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 |
|||
#' symbols (including options) provides more information about the symbols, |
|||
#' including when support was added/removed from libcurl. |
|||
#' |
|||
#' @export |
|||
#' @param filter string: only return options with string in name |
|||
#' @examples # Available options |
|||
#' curl_options() |
|||
#' |
|||
#' # List proxy options |
|||
#' curl_options("proxy") |
|||
#' |
|||
#' # Symbol table |
|||
#' curl_symbols("proxy") |
|||
curl_options <- function(filter = ""){ |
|||
m <- grep(filter, names(option_table), ignore.case = TRUE) |
|||
option_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))] |
|||
})() |
@ -0,0 +1,51 @@ |
|||
#' Parse response headers |
|||
#' |
|||
#' Parse response header data as returned by curl_fetch, either as a set of strings |
|||
#' or into a named list. |
|||
#' |
|||
#' The parse_headers_list function parses the headers into a normalized (lowercase |
|||
#' field names, trimmed whitespace) named list. |
|||
#' |
|||
#' If a request has followed redirects, the data can contain multiple sets of headers. |
|||
#' When multiple = TRUE, the function returns a list with the response headers |
|||
#' for each request. By default it only returns the headers of the final request. |
|||
#' |
|||
#' @param txt raw or character vector with the header data |
|||
#' @param multiple parse multiple sets of headers separated by a blank line. See details. |
|||
#' @export |
|||
#' @rdname parse_headers |
|||
#' @examples req <- curl_fetch_memory("https://httpbin.org/redirect/3") |
|||
#' parse_headers(req$headers) |
|||
#' parse_headers(req$headers, multiple = TRUE) |
|||
#' |
|||
#' # Parse into named list |
|||
#' parse_headers_list(req$headers) |
|||
parse_headers <- function(txt, multiple = FALSE){ |
|||
if(is.raw(txt)){ |
|||
txt <- rawToChar(txt) |
|||
} |
|||
stopifnot(is.character(txt)) |
|||
if(length(txt) > 1){ |
|||
txt <- paste(txt, collapse = "\n") |
|||
} |
|||
|
|||
# Allow for either "\r\n" line breaks or just "\r" or "\n" (i.e. windows servers) |
|||
sets <- strsplit(txt, "\\r\\n\\r\\n|\\n\\n|\\r\\r")[[1]] |
|||
headers <- strsplit(sets, "\\r\\n|\\n|\\r") |
|||
if(multiple){ |
|||
headers |
|||
} else { |
|||
headers[[length(headers)]] |
|||
} |
|||
} |
|||
|
|||
#' @export |
|||
#' @rdname parse_headers |
|||
parse_headers_list <- function(txt){ |
|||
headers <- grep(":", parse_headers(txt), fixed = TRUE, value = TRUE) |
|||
out <- lapply(headers, split_string, ":") |
|||
names <- tolower(vapply(out, `[[`, character(1), 1)) #names are case insensitive |
|||
values <- lapply(lapply(out, `[[`, 2), trimws) |
|||
names(values) <- names |
|||
values |
|||
} |
@ -0,0 +1,48 @@ |
|||
#' Internet Explorer proxy settings |
|||
#' |
|||
#' Lookup and mimic the system proxy settings on Windows as set by Internet |
|||
#' Explorer. This can be used to configure curl to use the same proxy server. |
|||
#' |
|||
#' The \code{ie_proxy_info} function looks |
|||
#' up your current proxy settings as configured in IE under "Internet Options" |
|||
#' > "Tab: Connections" > "LAN Settings". The \code{ie_get_proxy_for_url} |
|||
#' determines if and which proxy should be used to connect to a particular |
|||
#' URL. If your settings have an "automatic configuration script" this |
|||
#' involves downloading and executing a PAC file, which can take a while. |
|||
#' |
|||
#' @useDynLib curl R_proxy_info |
|||
#' @export |
|||
#' @rdname ie_proxy |
|||
#' @name ie_proxy |
|||
ie_proxy_info <- function(){ |
|||
.Call(R_proxy_info) |
|||
} |
|||
|
|||
#' @useDynLib curl R_get_proxy_for_url |
|||
#' @param target_url url with host for which to lookup the proxy server |
|||
#' @export |
|||
#' @rdname ie_proxy |
|||
ie_get_proxy_for_url <- function(target_url = "http://www.google.com"){ |
|||
stopifnot(is.character(target_url)) |
|||
info <- ie_proxy_info() |
|||
if(length(info$Proxy)){ |
|||
if(isTRUE(grepl("<local>", info$ProxyBypass, fixed = TRUE)) && |
|||
isTRUE(grepl("(://)[^./]+/", paste0(target_url, "/")))){ |
|||
return(NULL) |
|||
} else { |
|||
return(info$Proxy) |
|||
} |
|||
} |
|||
if(isTRUE(info$AutoDetect) || length(info$AutoConfigUrl)){ |
|||
out <- .Call(R_get_proxy_for_url, target_url, info$AutoDetect, info$AutoConfigUrl) |
|||
if(isTRUE(out$HasProxy)){ |
|||
return(out$Proxy) |
|||
} |
|||
} |
|||
return(NULL); |
|||
} |
|||
|
|||
#' @useDynLib curl R_windows_build |
|||
get_windows_build <- function(){ |
|||
.Call(R_windows_build) |
|||
} |
@ -0,0 +1,56 @@ |
|||
#include <Rinternals.h> |
|||
|
|||
SEXP reflist_init(){ |
|||
return R_NilValue; |
|||
} |
|||
|
|||
//note: you MUST use the return value for this object
|
|||
SEXP reflist_add(SEXP x, SEXP target){ |
|||
if(!Rf_isPairList(x)) |
|||
Rf_error("Not a LISTSXP"); |
|||
return(CONS(target, x)); |
|||
} |
|||
|
|||
SEXP reflist_has(SEXP x, SEXP target){ |
|||
if(!Rf_isPairList(x)) |
|||
Rf_error("Not a LISTSXP"); |
|||
while(x != R_NilValue){ |
|||
if(CAR(x) == target) |
|||
return(ScalarLogical(1)); |
|||
x = CDR(x); |
|||
} |
|||
return(ScalarLogical(0)); |
|||
} |
|||
|
|||
SEXP reflist_remove(SEXP x, SEXP target){ |
|||
if(!Rf_isPairList(x)) |
|||
Rf_error("Not a LISTSXP"); |
|||
|
|||
//drop head
|
|||
if(x != R_NilValue && CAR(x) == target) |
|||
return(CDR(x)); |
|||
SEXP prev = x; |
|||
SEXP current = CDR(x); |
|||
|
|||
//check inner nodes
|
|||
while(current != R_NilValue){ |
|||
if(CAR(current) == target){ |
|||
SETCDR(prev, CDR(current)); |
|||
return(x); |
|||
} |
|||
prev = current; |
|||
current = CDR(current); |
|||
} |
|||
Rf_error("Object not found in reflist!"); |
|||
} |
|||
|
|||
SEXP reflist_length(SEXP x) { |
|||
if(!Rf_isPairList(x)) |
|||
Rf_error("Not a LISTSXP"); |
|||
int i = 0; |
|||
while(x != R_NilValue){ |
|||
i++; |
|||
x = CDR(x); |
|||
} |
|||
return ScalarInteger(i); |
|||
} |
@ -0,0 +1,16 @@ |
|||
#include <Rinternals.h> |
|||
#include <string.h> |
|||
|
|||
SEXP R_split_string(SEXP string, SEXP split){ |
|||
const char * str = CHAR(STRING_ELT(string, 0)); |
|||
cetype_t enc = Rf_getCharCE(STRING_ELT(string, 0)); |
|||
const char * cut = CHAR(STRING_ELT(split, 0)); |
|||
char * out = strstr(str, cut); |
|||
if(!out) |
|||
return string; |
|||
SEXP res = PROTECT(allocVector(STRSXP, 2)); |
|||
SET_STRING_ELT(res, 0, mkCharLenCE(str, out - str, enc)); |
|||
SET_STRING_ELT(res, 1, mkCharCE(out + strlen(cut), enc)); |
|||
UNPROTECT(1); |
|||
return res; |
|||
} |
@ -0,0 +1,39 @@ |
|||
#include <curl/curl.h> |
|||
#include <Rinternals.h> |
|||
|
|||
#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 56) |
|||
#define HAS_MULTI_SSL 1 |
|||
#endif |
|||
|
|||
int windows_openssl = 0; |
|||
|
|||
/* Fall back on OpenSSL on Legacy Windows (Vista/2008) which do not support TLS 1.2 natively */ |
|||
void select_ssl_backend(){ |
|||
#if defined(_WIN32) && defined(HAS_MULTI_SSL) |
|||
DWORD dwBuild = 0; |
|||
DWORD dwVersion = GetVersion(); |
|||
if (dwVersion < 0x80000000) |
|||
dwBuild = (DWORD)(HIWORD(dwVersion)); |
|||
|
|||
/* TLS 1.2 requires at least Windows 7 or 2008-R2 */ |
|||
curl_sslbackend backend = dwBuild < 7600 ? CURLSSLBACKEND_OPENSSL : CURLSSLBACKEND_SCHANNEL; |
|||
|
|||
/* Try to set the backend */ |
|||
switch(curl_global_sslset(backend, NULL, NULL)){ |
|||
case CURLSSLSET_OK : |
|||
if(backend == CURLSSLBACKEND_OPENSSL) |
|||
windows_openssl = 1; |
|||
break; |
|||
case CURLSSLSET_TOO_LATE: |
|||
Rf_warning("Failed to set libcurl SSL: already initiated"); |
|||
break; |
|||
case CURLSSLSET_UNKNOWN_BACKEND: |
|||
Rf_warning("Failed to set libcurl SSL: unsupported backend"); |
|||
break; |
|||
default: |
|||
Rf_warning("Failed to set libcurl SSL: unknown error"); |
|||
break; |
|||
} |
|||
|
|||
#endif |
|||
} |
Binary file not shown.
@ -0,0 +1,39 @@ |
|||
/* Hack to get the GCC macros on all systems */ |
|||
#include <curl/curl.h> |
|||
#ifndef __CURL_TYPECHECK_GCC_H |
|||
#undef curl_easy_setopt |
|||
#undef curl_easy_getinfo |
|||
#undef curl_share_setopt |
|||
#undef curl_multi_setopt |
|||
|
|||
/* Remove the GNU extensions from typecheck-gcc.h */ |
|||
#ifndef __warning__ |
|||
#define __warning__(x) |
|||
#endif |
|||
#ifndef __unused__ |
|||
#define __unused__ |
|||
#endif |
|||
|
|||
/* Add the file */ |
|||
#include <curl/typecheck-gcc.h> |
|||
#endif |
|||
|
|||
int r_curl_is_slist_option(CURLoption x){ |
|||
return _curl_is_slist_option(x); |
|||
} |
|||
|
|||
int r_curl_is_long_option(CURLoption x){ |
|||
return _curl_is_long_option(x); |
|||
} |
|||
|
|||
int r_curl_is_off_t_option(CURLoption x){ |
|||
return _curl_is_off_t_option(x); |
|||
} |
|||
|
|||
int r_curl_is_string_option(CURLoption x){ |
|||
return _curl_is_string_option(x); |
|||
} |
|||
|
|||
int r_curl_is_postfields_option(CURLoption x){ |
|||
return _curl_is_postfields_option(x); |
|||
} |
@ -0,0 +1,49 @@ |
|||
#' 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. |
|||
#' 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 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}. |
|||
#' @examples \donttest{# Upload package to winbuilder: |
|||
#' curl_upload('mypkg_1.3.tar.gz', 'ftp://win-builder.r-project.org/R-devel/') |
|||
#' } |
|||
curl_upload <- function(file, url, verbose = TRUE, reuse = TRUE, ...) { |
|||
infilesize <- NA |
|||
con <- if(is.character(file)){ |
|||
file <- normalizePath(file, mustWork = TRUE) |
|||
infilesize <- file.info(file)$size |
|||
base::file(file, open = 'rb') |
|||
} else if(inherits(file, 'connection')){ |
|||
file |
|||
} else { |
|||
stop("Parameter 'file' must be a ") |
|||
} |
|||
on.exit(close(con)) |
|||
total_bytes <- 0 |
|||
h <- new_handle(upload = TRUE, filetime = FALSE, readfunction = function(n) { |
|||
buf <- readBin(con, raw(), n = n) |
|||
total_bytes <<- total_bytes + length(buf) |
|||
if(verbose){ |
|||
if(length(buf) == 0 || identical(total_bytes, infilesize)){ |
|||
cat(sprintf("\rUploaded %d bytes... all done!\n", total_bytes), file = stderr()) |
|||
} else { |
|||
cat(sprintf("\rUploaded %d bytes...", total_bytes), file = stderr()) |
|||
} |
|||
} |
|||
return(buf) |
|||
}, forbid_reuse = !isTRUE(reuse), verbose = verbose, ...) |
|||
if(!is.na(infilesize)){ |
|||
handle_setopt(h, infilesize_large = infilesize) |
|||
} |
|||
if(grepl('/$', url) && is.character(file)){ |
|||
url <- paste0(url, basename(file)) |
|||
} |
|||
curl_fetch_memory(url, handle = h) |
|||
} |
@ -0,0 +1,51 @@ |
|||
#' @rdname curl_options |
|||
#' @export |
|||
curl_symbols <- function(filter = ""){ |
|||
m <- grep(filter, curl_symbol_data$name, ignore.case = TRUE) |
|||
curl_symbol_data[m,] |
|||
} |
|||
|
|||
#' @useDynLib curl R_curl_version |
|||
#' @export |
|||
#' @rdname curl_options |
|||
#' @examples |
|||
#' # Curl/ssl version info |
|||
#' curl_version() |
|||
curl_version <- function(){ |
|||
.Call(R_curl_version); |
|||
} |
|||
|
|||
#' 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}} |
|||
#' might be easier. |
|||
#' |
|||
#' @param datestring a string consisting of a timestamp |
|||
#' @useDynLib curl R_curl_getdate |
|||
#' @export |
|||
#' @examples |
|||
#' # Parse dates in many formats |
|||
#' parse_date("Sunday, 06-Nov-94 08:49:37 GMT") |
|||
#' parse_date("06 Nov 1994 08:49:37") |
|||
#' parse_date("20040911 +0200") |
|||
parse_date <- function(datestring){ |
|||
out <- .Call(R_curl_getdate, datestring); |
|||
class(out) <- c("POSIXct", "POSIXt") |
|||
out |
|||
} |
|||
|
|||
|
|||
#' @useDynLib curl R_split_string |
|||
split_string <- function(x, split = ":"){ |
|||
.Call(R_split_string, x, split) |
|||
} |
|||
|
|||
trimws <- function(x) { |
|||
sub("\\s+$", "", sub("^\\s+", "", x)) |
|||
} |
|||
|
|||
is_string <- function(x){ |
|||
is.character(x) && length(x) |
|||
} |
@ -0,0 +1,147 @@ |
|||
#include "curl-common.h" |
|||
|
|||
CURL* get_handle(SEXP ptr){ |
|||
return get_ref(ptr)->handle; |
|||
} |
|||
|
|||
reference* get_ref(SEXP ptr){ |
|||
if(TYPEOF(ptr) != EXTPTRSXP || !Rf_inherits(ptr, "curl_handle")) |
|||
Rf_error("handle is not a curl_handle()"); |
|||
if(!R_ExternalPtrAddr(ptr)) |
|||
error("handle is dead"); |
|||
reference *ref = (reference*) R_ExternalPtrAddr(ptr); |
|||
return ref; |
|||
} |
|||
|
|||
void set_form(reference *ref, struct curl_httppost* newform){ |
|||
if(ref->form) |
|||
curl_formfree(ref->form); |
|||
ref->form = newform; |
|||
if(newform){ |
|||
assert(curl_easy_setopt(ref->handle, CURLOPT_HTTPPOST, ref->form)); |
|||
} else { |
|||
//CURLOPT_HTTPPOST has bug for empty forms. We probably want this:
|
|||
assert(curl_easy_setopt(ref->handle, CURLOPT_POSTFIELDS, "")); |
|||
} |
|||
} |
|||
|
|||
void reset_resheaders(reference *ref){ |
|||
if(ref->resheaders.buf) |
|||
free(ref->resheaders.buf); |
|||
ref->resheaders.buf = NULL; |
|||
ref->resheaders.size = 0; |
|||
} |
|||
|
|||
void reset_errbuf(reference *ref){ |
|||
memset(ref->errbuf, 0, CURL_ERROR_SIZE); |
|||
} |
|||
|
|||
void assert(CURLcode res){ |
|||
if(res != CURLE_OK) |
|||
error(curl_easy_strerror(res)); |
|||
} |
|||
|
|||
void assert_status(CURLcode res, reference *ref){ |
|||
if(res == CURLE_OPERATION_TIMEDOUT) |
|||
Rf_error("%s: %s", curl_easy_strerror(res), ref->errbuf); |
|||
if(res != CURLE_OK) |
|||
Rf_error("%s", strlen(ref->errbuf) ? ref->errbuf : curl_easy_strerror(res)); |
|||
} |
|||
|
|||
void massert(CURLMcode res){ |
|||
if(res != CURLM_OK) |
|||
error(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) |
|||
error("HTTP error %d.", status); |
|||
} |
|||
|
|||
/* make sure to call curl_slist_free_all on this object */ |
|||
struct curl_slist* vec_to_slist(SEXP vec){ |
|||
if(!isString(vec)) |
|||
error("vec is not a character vector"); |
|||
struct curl_slist *slist = NULL; |
|||
for(int i = 0; i < length(vec); i++){ |
|||
slist = curl_slist_append(slist, CHAR(STRING_ELT(vec, i))); |
|||
} |
|||
return slist; |
|||
} |
|||
|
|||
SEXP slist_to_vec(struct curl_slist *slist){ |
|||
/* linked list of strings */ |
|||
struct curl_slist *cursor = slist; |
|||
|
|||
/* count slist */ |
|||
int n = 0; |
|||
while (cursor) { |
|||
n++; |
|||
cursor = cursor->next; |
|||
} |
|||
|
|||
SEXP out = PROTECT(allocVector(STRSXP, n)); |
|||
cursor = slist; |
|||
for(int i = 0; i < n; i++){ |
|||
SET_STRING_ELT(out, i, mkChar(cursor->data)); |
|||
cursor = cursor->next; |
|||
} |
|||
UNPROTECT(1); |
|||
return out; |
|||
} |
|||
|
|||
size_t push_disk(void* contents, size_t sz, size_t nmemb, FILE *ctx) { |
|||
//if (pending_interrupt())
|
|||
// return 0;
|
|||
return fwrite(contents, sz, nmemb, ctx); |
|||
} |
|||
|
|||
static size_t round_up(size_t v){ |
|||
if(v == 0) |
|||
return 0; |
|||
v--; |
|||
v |= v >> 1; |
|||
v |= v >> 2; |
|||
v |= v >> 4; |
|||
v |= v >> 8; |
|||
v |= v >> 16; |
|||
if (sizeof(size_t) == 8) |
|||
v |= v >> 32; |
|||
return ++v; |
|||
} |
|||
|
|||
size_t append_buffer(void *contents, size_t sz, size_t nmemb, void *ctx) { |
|||
//if (pending_interrupt())
|
|||
// return 0;
|
|||
|
|||
/* avoids compiler warning on windows */ |
|||
size_t realsize = sz * nmemb; |
|||
memory *mem = (memory*) ctx; |
|||
|
|||
/* realloc can be slow, therefore increase buffer to nearest 2^n */ |
|||
mem->buf = realloc(mem->buf, round_up(mem->size + realsize)); |
|||
if (!mem->buf) |
|||
return 0; |
|||
|
|||
/* append data and increment size */ |
|||
memcpy(&(mem->buf[mem->size]), contents, realsize); |
|||
mem->size += realsize; |
|||
return realsize; |
|||
} |
|||
|
|||
size_t data_callback(void * data, size_t sz, size_t nmemb, SEXP fun) { |
|||
size_t size = sz * nmemb; |
|||
SEXP buf = PROTECT(allocVector(RAWSXP, size)); |
|||
memcpy(RAW(buf), data, size); |
|||
|
|||
/* call the R function */ |
|||
int err; |
|||
SEXP call = PROTECT(Rf_lang3(fun, buf, ScalarInteger(0))); |
|||
R_tryEval(call, R_GlobalEnv, &err); |
|||
UNPROTECT(2); |
|||
return err ? 0 : size; |
|||
} |
@ -0,0 +1,64 @@ |
|||
#include <curl/curl.h> |
|||
#include <Rinternals.h> |
|||
|
|||
#define make_string(x) x ? Rf_mkString(x) : ScalarString(NA_STRING) |
|||
|
|||
SEXP R_curl_version() { |
|||
/* retrieve info from curl */ |
|||
const curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); |
|||
|
|||
/* put stuff in a list */ |
|||
SEXP list = PROTECT(allocVector(VECSXP, 10)); |
|||
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)); |
|||
|
|||
/* create vector of protocols */ |
|||
int len = 0; |
|||
const char *const * temp = data->protocols; |
|||
while(*temp++) len++; |
|||
SEXP protocols = PROTECT(allocVector(STRSXP, len)); |
|||
for (int i = 0; i < len; i++){ |
|||
SET_STRING_ELT(protocols, i, mkChar(*(data->protocols + i))); |
|||
} |
|||
SET_VECTOR_ELT(list, 6, protocols); |
|||
|
|||
/* add list names */ |
|||
SEXP names = PROTECT(allocVector(STRSXP, 10)); |
|||
SET_STRING_ELT(names, 0, mkChar("version")); |
|||
SET_STRING_ELT(names, 1, mkChar("ssl_version")); |
|||
SET_STRING_ELT(names, 2, mkChar("libz_version")); |
|||
SET_STRING_ELT(names, 3, mkChar("libssh_version")); |
|||
SET_STRING_ELT(names, 4, mkChar("libidn_version")); |
|||
SET_STRING_ELT(names, 5, mkChar("host")); |
|||
SET_STRING_ELT(names, 6, mkChar("protocols")); |
|||
SET_STRING_ELT(names, 7, mkChar("ipv6")); |
|||
SET_STRING_ELT(names, 8, mkChar("http2")); |
|||
SET_STRING_ELT(names, 9, mkChar("idn")); |
|||
setAttrib(list, R_NamesSymbol, names); |
|||
|
|||
#ifdef CURL_VERSION_IPV6 |
|||
SET_VECTOR_ELT(list, 7, ScalarLogical(data->features & CURL_VERSION_IPV6)); |
|||
#else |
|||
SET_VECTOR_ELT(list, 7, ScalarLogical(0)); |
|||
#endif |
|||
|
|||
#ifdef CURL_VERSION_HTTP2 |
|||
SET_VECTOR_ELT(list, 8, ScalarLogical(data->features & CURL_VERSION_HTTP2)); |
|||
#else |
|||
SET_VECTOR_ELT(list, 8, ScalarLogical(0)); |
|||
#endif |
|||
|
|||
#ifdef CURL_VERSION_IDN |
|||
SET_VECTOR_ELT(list, 9, ScalarLogical(data->features & CURL_VERSION_IDN)); |
|||
#else |
|||
SET_VECTOR_ELT(list, 9, ScalarLogical(0)); |
|||
#endif |
|||
|
|||
/* return */ |
|||
UNPROTECT(3); |
|||
return list; |
|||
} |
@ -0,0 +1,70 @@ |
|||
/* IdnToAscii() requires at least vista to build */ |
|||
#define _WIN32_WINNT 0x0600 |
|||
#define WINVER 0x0600 |
|||
#define IDN_MAX_LENGTH 255 |
|||
|
|||
#ifdef _WIN32 |
|||
#include <Windows.h> |
|||
|
|||
wchar_t * jeroen_convert_UTF8_to_wchar(const char *str_utf8){ |
|||
wchar_t *str_w = NULL; |
|||
|
|||
if(str_utf8) { |
|||
int str_w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, |
|||
str_utf8, -1, NULL, 0); |
|||
if(str_w_len > 0) { |
|||
str_w = malloc(str_w_len * sizeof(wchar_t)); |
|||
if(str_w) { |
|||
if(MultiByteToWideChar(CP_UTF8, 0, str_utf8, -1, str_w, |
|||
str_w_len) == 0) { |
|||
free(str_w); |
|||
return NULL; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return str_w; |
|||
} |
|||
|
|||
char *jeroen_convert_wchar_to_UTF8(const wchar_t *str_w){ |
|||
char *str_utf8 = NULL; |
|||
|
|||
if(str_w) { |
|||
int str_utf8_len = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, NULL, |
|||
0, NULL, NULL); |
|||
if(str_utf8_len > 0) { |
|||
str_utf8 = malloc(str_utf8_len * sizeof(wchar_t)); |
|||
if(str_utf8) { |
|||
if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, str_utf8_len, |
|||
NULL, FALSE) == 0) { |
|||
free(str_utf8); |
|||
return NULL; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return str_utf8; |
|||
} |
|||
|
|||
int jeroen_win32_idn_to_ascii(const char *in, char **out){ |
|||
int success = FALSE; |
|||
wchar_t *in_w = jeroen_convert_UTF8_to_wchar(in); |
|||
if(in_w) { |
|||
wchar_t punycode[IDN_MAX_LENGTH]; |
|||
int chars = IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH); |
|||
free(in_w); |
|||
if(chars) { |
|||
*out = jeroen_convert_wchar_to_UTF8(punycode); |
|||
if(*out) |
|||
success = TRUE; |
|||
} |
|||
} |
|||
|
|||
return success; |
|||
} |
|||
|
|||
#else |
|||
void placeholder_to_avoid_stupid_warning(){} |
|||
#endif |
@ -0,0 +1,48 @@ |
|||
#' File Writer |
|||
#' |
|||
#' 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()}. |
|||
#' |
|||
#' @export |
|||
#' @param path file name or path on disk |
|||
#' @return Function with signature \code{writer(data = raw(), close = FALSE)} |
|||
#' @examples |
|||
#' # Doesn't open yet |
|||
#' tmp <- tempfile() |
|||
#' writer <- file_writer(tmp) |
|||
#' |
|||
#' # Now it opens |
|||
#' writer(charToRaw("Hello!\n")) |
|||
#' writer(charToRaw("How are you?\n")) |
|||
#' |
|||
#' # Close it! |
|||
#' writer(charToRaw("All done!\n"), close = TRUE) |
|||
#' |
|||
#' # Check it worked |
|||
#' readLines(tmp) |
|||
file_writer <- function(path){ |
|||
path <- enc2native(normalizePath(path, mustWork = FALSE)) |
|||
fp <- new_file_writer(path) |
|||
structure(function(data = raw(), close = FALSE){ |
|||
stopifnot(is.raw(data)) |
|||
write_file_writer(fp, data, as.logical(close)) |
|||
}, class = "file_writer") |
|||
} |
|||
|
|||
#' @useDynLib curl R_new_file_writer |
|||
new_file_writer <- function(path){ |
|||
.Call(R_new_file_writer, path) |
|||
} |
|||
|
|||
#' @useDynLib curl R_write_file_writer |
|||
write_file_writer <- function(fp, data, close){ |
|||
.Call(R_write_file_writer, fp, data, close) |
|||
} |
|||
|
|||
#' @useDynLib curl R_total_writers |
|||
total_writers <- function(){ |
|||
.Call(R_total_writers) |
|||
} |
@ -0,0 +1,43 @@ |
|||
#include <Rinternals.h> |
|||
|
|||
static int total_open_writers = 0; |
|||
|
|||
void fin_file_writer(SEXP ptr){ |
|||
FILE *fp = R_ExternalPtrAddr(ptr); |
|||
if(fp != NULL){ |
|||
fclose(fp); |
|||
R_ClearExternalPtr(ptr); |
|||
total_open_writers--; |
|||
} |
|||
} |
|||
|
|||
SEXP R_write_file_writer(SEXP ptr, SEXP buf, SEXP close){ |
|||
FILE *fp = R_ExternalPtrAddr(ptr); |
|||
if(fp == NULL){ |
|||
SEXP path = R_ExternalPtrTag(ptr); |
|||
fp = fopen(CHAR(STRING_ELT(path, 0)), "wb"); |
|||
if(!fp) |
|||
Rf_error("Failed to open file: %s", CHAR(STRING_ELT(path, 0))); |
|||
R_SetExternalPtrAddr(ptr, fp); |
|||
total_open_writers++; |
|||
} |
|||
size_t len = fwrite(RAW(buf), 1, Rf_xlength(buf), fp); |
|||
if(Rf_asLogical(close)){ |
|||
fin_file_writer(ptr); |
|||
} else if(Rf_length(buf)) { |
|||
fflush(fp); |
|||
} |
|||
return ScalarInteger(len); |
|||
} |
|||
|
|||
SEXP R_new_file_writer(SEXP path){ |
|||
SEXP ptr = PROTECT(R_MakeExternalPtr(NULL, path, R_NilValue)); |
|||
R_RegisterCFinalizerEx(ptr, fin_file_writer, TRUE); |
|||
setAttrib(ptr, R_ClassSymbol, mkString("file_writer")); |
|||
UNPROTECT(1); |
|||
return ptr; |
|||
} |
|||
|
|||
SEXP R_total_writers(){ |
|||
return(ScalarInteger(total_open_writers)); |
|||
} |
Loading…
Reference in new issue