Browse Source

jeroen's build system

master
boB Rudis 5 years ago
parent
commit
37b6c6a5ff
No known key found for this signature in database GPG Key ID: 1D7529BE14E2BBA9
  1. 2
      .gitlab-ci.yml
  2. 44
      DESCRIPTION
  3. 2
      LICENSE
  4. 2
      NAMESPACE
  5. 0
      NEWS
  6. 3
      R/Z-domain.R
  7. 9
      R/curlparse-package.R
  8. 11
      R/utils-pipe.R
  9. 58
      README.Rmd
  10. 182
      README.md
  11. 10
      README_cache/gfm/__packages
  12. BIN
      README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.RData
  13. BIN
      README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.rdb
  14. BIN
      README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.rdx
  15. 49
      appveyor.yml
  16. 2
      configure
  17. 4
      curlparse.Rproj
  18. 67
      inst/tinytest/test_curlparse.R
  19. 11
      man/curlparse.Rd
  20. BIN
      man/figures/README-unnamed-chunk-4-1.png
  21. BIN
      man/figures/README-unnamed-chunk-5-1.png
  22. 12
      man/pipe.Rd
  23. 4
      man/scheme.Rd
  24. 3
      src/Makevars
  25. 3
      src/Makevars.in
  26. 7
      src/Makevars.win
  27. 37
      src/winhttp32.def.in
  28. 37
      src/winhttp64.def.in
  29. 5
      tests/tinytest.R
  30. 82
      tmp/callbacks.c
  31. 9
      tmp/callbacks.h
  32. 67
      tmp/curl-common.h
  33. 779
      tmp/curl-symbols.h
  34. 81
      tmp/curl.R
  35. 289
      tmp/curl.c
  36. 39
      tmp/download.R
  37. 51
      tmp/download.c
  38. 126
      tmp/echo.R
  39. 63
      tmp/email.R
  40. 26
      tmp/escape.R
  41. 33
      tmp/escape.c
  42. 117
      tmp/fetch.R
  43. 91
      tmp/fetch.c
  44. 48
      tmp/form.R
  45. 46
      tmp/form.c
  46. 17
      tmp/getdate.c
  47. 209
      tmp/handle.R
  48. 484
      tmp/handle.c
  49. 177
      tmp/ieproxy.c
  50. 96
      tmp/init.c
  51. 69
      tmp/interrupt.c
  52. 202
      tmp/multi.R
  53. 316
      tmp/multi.c
  54. 37
      tmp/nslookup.R
  55. 100
      tmp/nslookup.c
  56. 27
      tmp/onload.R
  57. 37
      tmp/options.R
  58. 51
      tmp/parse_headers.R
  59. 48
      tmp/proxy.R
  60. 56
      tmp/reflist.c
  61. 16
      tmp/split.c
  62. 39
      tmp/ssl.c
  63. BIN
      tmp/sysdata.rda
  64. 39
      tmp/typechecking.c
  65. 49
      tmp/upload.R
  66. 51
      tmp/utilities.R
  67. 147
      tmp/utils.c
  68. 64
      tmp/version.c
  69. 70
      tmp/winidn.c
  70. 48
      tmp/writer.R
  71. 43
      tmp/writer.c
  72. 87
      tools/symbols-in-versions
  73. 7
      tools/symbols.R

2
.gitlab-ci.yml

@ -1,5 +1,5 @@
image: rocker/tidyverse
test:
test:
script:
- R -e 'install.packages(c("testthat", "covr", "Rcpp", "stringi", "knitr", "rmarkdown"))'
- R CMD build . --no-build-vignettes --no-manual

44
DESCRIPTION

@ -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

2
LICENSE

@ -1,2 +1,2 @@
YEAR: 2018
YEAR: 2019
COPYRIGHT HOLDER: Bob Rudis

2
NAMESPACE

@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand
export("%>%")
export(domain)
export(fragment)
export(host)
@ -14,6 +15,7 @@ export(url_options)
export(url_parse)
export(user)
importFrom(Rcpp,sourceCpp)
importFrom(magrittr,"%>%")
importFrom(stringi,stri_detect_regex)
importFrom(stringi,stri_opts_regex)
useDynLib(curlparse)

0
NEWS

3
R/Z-domain.R

@ -0,0 +1,3 @@
#' @rdname scheme
#' @export
domain <- host

9
R/curlparse-package.R

@ -3,13 +3,6 @@
#' As of version 7.62.0 'libcurl' has exposed its 'URL' parser. Tools are
#' provided to parse 'URLs' using this new parser feature.
#'
#' **UNTIL `curl`/`libcurl` general release at the end of October you _must_
#' use the development version which can be cloned and built from
#' <https://github.com/curl/curl>.
#'
#' - URL: <https://gitlab.com/hrbrmstr/curlparse>
#' - BugReports: <https://gitlab.com/hrbrmstr/curlparse/issues>
#'
#' @md
#' @name curlparse
#' @docType package
@ -17,4 +10,4 @@
#' @importFrom stringi stri_detect_regex stri_opts_regex
#' @importFrom Rcpp sourceCpp
#' @useDynLib curlparse
NULL
"_PACKAGE"

11
R/utils-pipe.R

@ -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

58
README.Rmd

@ -1,12 +1,12 @@
---
output: rmarkdown::github_document
editor_options:
chunk_output_type: console
---
```{r include=FALSE}
knitr::opts_chunk$set(
message=FALSE, warning=FALSE, collapse = TRUE, fig.retina=2
)
```{r pkg-knitr-opts, include=FALSE}
hrbrpkghelpr::global_opts()
```
```{r badges, results='asis', echo=FALSE, cache=FALSE}
hrbrpkghelpr::stinking_badges()
```
# curlparse
@ -14,40 +14,20 @@ Parse 'URLs' with 'libcurl'
## Description
As of version 7.62.0 'libcurl' has exposed its 'URL' parser. Tools are provided to parse 'URLs' using this new parser feature.
**UNTIL `curl`/`libcurl` general release at the end of October you _must_ use the development version which can be cloned and built from <https://github.com/curl/curl>.**
Tools are provided to parse URLs using the modern 'libcurl' built-in parser.
## What's Inside The Tin
Core function to turn a vector of URLs into a named `list` of component parts (which can easily be turned into a data frame)
- `parse_curl`: Parse a character vector of URLs into component parts (deliberately named soas not to conflict with `httr::parse_url()`)
The following functions are implemented:
URL validation:
- `is_valid_url`: Test if a URL is valid (Ref: <https://mathiasbynens.be/demo/url-regex>)
URL component extractors:
- `scheme`: Extract member components from a URL string
- `user`: Extract member components from a URL string
- `password`: Extract member components from a URL string
- `host`/`domain`: Extract member components from a URL string
- `port`: Extract member components from a URL string
- `path`: Extract member components from a URL string
- `query`: Extract member components from a URL string
- `url_options`: Extract member components from a URL string (deliberately named soas not to conflict with `base::options()`)
- `fragment`: Extract member components from a URL string
```{r ingredients, results='asis', echo=FALSE, cache=FALSE}
hrbrpkghelpr::describe_ingredients()
```
## Installation
```{r eval=FALSE}
devtools::install_github("hrbrmstr/curlparse")
```
```{r message=FALSE, warning=FALSE, error=FALSE, include=FALSE}
options(width=120)
```{r install-ex, results='asis', echo = FALSE}
hrbrpkghelpr::install_block()
```
## Usage
@ -199,3 +179,15 @@ all(
)
)
```
## curlparse Metrics
```{r cloc, echo=FALSE}
cloc::cloc_pkg_md()
```
## Code of Conduct
Please note that this project is released with a Contributor Code of Conduct.
By participating in this project you agree to abide by its terms.

182
README.md

@ -1,49 +1,51 @@
[![Project Status: Active – The project has reached a stable, usable
state and is being actively
developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![Signed
by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr)
![Signed commit
%](https://img.shields.io/badge/Signed_Commits-100%25-lightgrey.svg)
[![Linux build
Status](https://travis-ci.org/hrbrmstr/curlparse.svg?branch=master)](https://travis-ci.org/hrbrmstr/curlparse)
[![Coverage
Status](https://codecov.io/gh/hrbrmstr/curlparse/branch/master/graph/badge.svg)](https://codecov.io/gh/hrbrmstr/curlparse)
![Minimal R
Version](https://img.shields.io/badge/R%3E%3D-3.2.0-blue.svg)
![License](https://img.shields.io/badge/License-MIT-blue.svg)
# curlparse
Parse ‘URLs’ with ‘libcurl’
## Description
As of version 7.62.0 ‘libcurl’ has exposed its ‘URL’ parser. Tools are
provided to parse ‘URLs’ using this new parser feature.
**UNTIL `curl`/`libcurl` general release at the end of October you
*must* use the development version which can be cloned and built from
<https://github.com/curl/curl>.**
Tools are provided to parse URLs using the modern ‘libcurl’ built-in
parser.
## What’s Inside The Tin
Core function to turn a vector of URLs into a named `list` of component
parts (which can easily be turned into a data frame)
The following functions are implemented:
- `is_valid_url`: Test if a URL is a valid URL
- `parse_curl`: Parse a character vector of URLs into component parts
(deliberately named soas not to conflict with `httr::parse_url()`)
URL validation:
- `is_valid_url`: Test if a URL is valid (Ref:
<https://mathiasbynens.be/demo/url-regex>)
URL component extractors:
- `scheme`: Extract member components from a URL string
- `user`: Extract member components from a URL string
- `password`: Extract member components from a URL string
- `host`/`domain`: Extract member components from a URL string
- `port`: Extract member components from a URL string
- `path`: Extract member components from a URL string
- `query`: Extract member components from a URL string
- `url_options`: Extract member components from a URL string
(deliberately named soas not to conflict with `base::options()`)
- `fragment`: Extract member components from a URL string
- `url_parse`: Parse a character vector of URLs into component parts
(urltools compatibility function)
## Installation
``` r
devtools::install_github("hrbrmstr/curlparse")
remotes::install_git("https://git.sr.ht/~hrbrmstr/curlparse")
# or
remotes::install_gitlab("hrbrmstr/curlparse")
# or
remotes::install_github("hrbrmstr/curlparse")
```
NOTE: To use the ‘remotes’ install options you will need to have the
[{remotes} package](https://github.com/r-lib/remotes) installed.
## Usage
``` r
@ -51,7 +53,7 @@ library(curlparse)
# current verison
packageVersion("curlparse")
## [1] '0.1.0'
## [1] '0.2.0'
```
### Process Some URLs
@ -71,34 +73,39 @@ read_html("https://www.r-bloggers.com/blogs-list/") %>%
``` r
(parsed <- parse_curl(blog_urls))
## # A tibble: 794 x 9
## # A tibble: 977 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http <NA> <NA> dmlc.ml 80 / <NA> <NA> <NA>
## 2 https <NA> <NA> lionel-.github.io 443 / <NA> <NA> <NA>
## 3 https <NA> <NA> jean9208.github.io 443 /rss-R.xml <NA> <NA> <NA>
## 4 https <NA> <NA> ryouready.wordpress.com 443 / <NA> <NA> <NA>
## 5 https <NA> <NA> rveryday.wordpress.com 443 / <NA> <NA> <NA>
## 6 http <NA> <NA> www.talyarkoni.org 80 /blog <NA> <NA> <NA>
## 7 https <NA> <NA> rtricks.wordpress.com 443 / <NA> <NA> <NA>
## 8 http <NA> <NA> mgritts.com 80 /feed.r.xml <NA> <NA> <NA>
## 9 http <NA> <NA> blog.ambodi.com 80 / <NA> <NA> <NA>
## 10 https <NA> <NA> xcafebabe.blogspot.com 443 /search/label/R <NA> <NA> <NA>
## # ... with 784 more rows
## 1 http <NA> <NA> reichlab.io 80 / <NA> <NA> <NA>
## 2 http <NA> <NA> dmlc.ml 80 / <NA> <NA> <NA>
## 3 https <NA> <NA> lionel-.github.io 443 / <NA> <NA> <NA>
## 4 https <NA> <NA> jean9208.github.io 443 /rss-R.xml <NA> <NA> <NA>
## 5 https <NA> <NA> ryouready.wordpress.com 443 / <NA> <NA> <NA>
## 6 https <NA> <NA> rveryday.wordpress.com 443 / <NA> <NA> <NA>
## 7 http <NA> <NA> www.talyarkoni.org 80 /blog <NA> <NA> <NA>
## 8 https <NA> <NA> rtricks.wordpress.com 443 / <NA> <NA> <NA>
## 9 https <NA> <NA> xcafebabe.blogspot.com 443 /search/label/R <NA> <NA> <NA>
## 10 http <NA> <NA> 4dpiecharts.com 80 / <NA> <NA> <NA>
## # … with 967 more rows
count(parsed, scheme, sort=TRUE)
## # A tibble: 2 x 2
## scheme n
## <chr> <int>
## 1 https 467
## 2 http 327
## 1 https 618
## 2 http 359
filter(parsed, !is.na(query))
## # A tibble: 2 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 https <NA> <NA> blog.datazar.com 443 /tagged/r-language <NA> source=rss----e2c7e6e1c75--r_lan… <NA>
## 2 https <NA> <NA> kevinkuang.net 443 /tagged/r-programming <NA> source=rss----a1ff9aea4bf1--r_pr… <NA>
## # A tibble: 7 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http <NA> <NA> freakonometrics.blog.… 80 /index.php <NA> "" <NA>
## 2 http <NA> <NA> ludvigolsen.dk 80 / <NA> lang=en <NA>
## 3 https <NA> <NA> medium.com 443 /principles-0/tag… <NA> source=rss----489c2dec8959--r <NA>
## 4 https <NA> <NA> medium.com 443 /tim-black/tagged… <NA> source=rss----d71cf9ecf7ec--r <NA>
## 5 https <NA> <NA> kevinkuang.net 443 /tagged/r-program… <NA> source=rss----a1ff9aea4bf1--r_… <NA>
## 6 https <NA> <NA> medium.com 443 /@MattOldach_65321 <NA> source=rss-459e62b88a2a------2 <NA>
## 7 https <NA> <NA> medium.com 443 /@zappingseb <NA> source=rss-dbc9f652035a------2 <NA>
```
### Benchmark
@ -108,7 +115,7 @@ this package for current users of `urltools::url_parse()` since it
provides the same API and same results back (including it being a
regular data frame and not a `tbl`).
Spoiler alert: `urltools::url_parse()` is faster by ~100µs (per-100
Spoiler alert: `urltools::url_parse()` is faster by \~100µs (per-100
URLs) for “good” URLs (if there’s a mix of gnarly/bad URLs and valid
ones they get closer to being on-par). The aim was not to try to beat
it, though.
@ -151,14 +158,14 @@ microbenchmark(
mb
## Unit: microseconds
## expr min lq mean median uq max neval
## curlparse 753.914 831.5750 896.4327 859.1640 896.8245 4597.547 500
## urltools 647.077 710.7115 768.3054 734.9985 766.3750 4163.394 500
## expr min lq mean median uq max neval cld
## curlparse 810.579 855.6420 906.5393 890.2775 929.2810 4404.267 500 b
## urltools 667.676 711.7175 766.3760 735.4835 771.1215 4704.806 500 a
autoplot(mb)
```
<img src="README_files/figure-gfm/unnamed-chunk-7-1.png" width="672" />
<img src="man/figures/README-unnamed-chunk-4-1.png" width="672" />
The individual handlers are a bit more on-par but mostly still slower
(except for `fragment()`). Note that `urltools` has no equivalent
@ -190,7 +197,7 @@ bind_rows(
theme(panel.spacing.y=unit(0, "lines"))
```
<img src="README_files/figure-gfm/unnamed-chunk-8-1.png" width="576" />
<img src="man/figures/README-unnamed-chunk-5-1.png" width="576" />
### Stress Test
@ -225,10 +232,10 @@ c(
## 8 http user pass foo.com 80 / <NA> <NA> <NA>
## 9 file <NA> <NA> <NA> 0 /tmp/junk.txt <NA> <NA> <NA>
## 10 imap <NA> <NA> mail.python.org 143 /mbox1 <NA> <NA> <NA>
## # ... with 22 more rows
## # with 22 more rows
filter(u_parsed, !is.na(scheme))
## # A tibble: 13 x 9
## # A tibble: 14 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http <NA> <NA> foo.com 80 /path <NA> <NA> <NA>
@ -238,12 +245,13 @@ filter(u_parsed, !is.na(scheme))
## 5 imap <NA> <NA> mail.python.org 143 /mbox1 <NA> <NA> <NA>
## 6 http <NA> <NA> WWW.PYTHON.ORG 80 /doc/ <NA> <NA> frag
## 7 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> example.com 80 / <NA> <NA> <NA>
## 9 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 10 http user <NA> example.com 8080 /path;param <NA> query fragment
## 11 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 12 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 13 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 9 http <NA> <NA> example.com 80 / <NA> "" <NA>
## 10 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 11 http user <NA> example.com 8080 /path;param <NA> query fragment
## 12 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 13 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 14 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
filter(u_parsed, !is.na(user))
## # A tibble: 2 x 9
@ -259,7 +267,7 @@ filter(u_parsed, !is.na(password))
## 1 http user pass foo.com 80 / <NA> <NA> <NA>
filter(u_parsed, !is.na(host))
## # A tibble: 12 x 9
## # A tibble: 13 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http <NA> <NA> foo.com 80 /path <NA> <NA> <NA>
@ -268,15 +276,16 @@ filter(u_parsed, !is.na(host))
## 4 imap <NA> <NA> mail.python.org 143 /mbox1 <NA> <NA> <NA>
## 5 http <NA> <NA> WWW.PYTHON.ORG 80 /doc/ <NA> <NA> frag
## 6 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 7 http <NA> <NA> example.com 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 9 http user <NA> example.com 8080 /path;param <NA> query fragment
## 10 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 11 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 12 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
## 7 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> example.com 80 / <NA> "" <NA>
## 9 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 10 http user <NA> example.com 8080 /path;param <NA> query fragment
## 11 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 12 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 13 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
filter(u_parsed, !is.na(path))
## # A tibble: 13 x 9
## # A tibble: 14 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http <NA> <NA> foo.com 80 /path <NA> <NA> <NA>
@ -286,18 +295,20 @@ filter(u_parsed, !is.na(path))
## 5 imap <NA> <NA> mail.python.org 143 /mbox1 <NA> <NA> <NA>
## 6 http <NA> <NA> WWW.PYTHON.ORG 80 /doc/ <NA> <NA> frag
## 7 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> example.com 80 / <NA> <NA> <NA>
## 9 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 10 http user <NA> example.com 8080 /path;param <NA> query fragment
## 11 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 12 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 13 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
## 8 http <NA> <NA> www.python.org 80 / <NA> <NA> <NA>
## 9 http <NA> <NA> example.com 80 / <NA> "" <NA>
## 10 http <NA> <NA> example.com 80 /; <NA> <NA> <NA>
## 11 http user <NA> example.com 8080 /path;param <NA> query fragment
## 12 http <NA> <NA> . 80 / <NA> <NA> <NA>
## 13 http <NA> <NA> foo..com 80 / <NA> <NA> <NA>
## 14 http <NA> <NA> foo.. 80 / <NA> <NA> <NA>
filter(u_parsed, !is.na(query))
## # A tibble: 1 x 9
## # A tibble: 2 x 9
## scheme user password host port path options query fragment
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 http user <NA> example.com 8080 /path;param <NA> query fragment
## 1 http <NA> <NA> example.com 80 / <NA> "" <NA>
## 2 http user <NA> example.com 8080 /path;param <NA> query fragment
filter(u_parsed, !is.na(fragment))
## # A tibble: 2 x 9
@ -324,3 +335,20 @@ all(
)
## [1] TRUE
```
## curlparse Metrics
| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :----------- | -------: | ---: | ---: | ---: | ----------: | ---: | -------: | ---: |
| C | 21 | 0.41 | 1773 | 0.49 | 336 | 0.63 | 216 | 0.21 |
| C/C++ Header | 3 | 0.06 | 844 | 0.23 | 10 | 0.02 | 1 | 0.00 |
| R | 23 | 0.45 | 612 | 0.17 | 75 | 0.14 | 699 | 0.68 |
| C++ | 2 | 0.04 | 285 | 0.08 | 65 | 0.12 | 58 | 0.06 |
| Rmd | 1 | 0.02 | 85 | 0.02 | 49 | 0.09 | 59 | 0.06 |
| Bourne Shell | 1 | 0.02 | 2 | 0.00 | 0 | 0.00 | 0 | 0.00 |
## Code of Conduct
Please note that this project is released with a Contributor Code of
Conduct. By participating in this project you agree to abide by its
terms.

10
README_cache/gfm/__packages

@ -1,5 +1,14 @@
base
methods
bit
utils
stats
bit64
datasets
grDevices
graphics
curlparse
urltools
xml2
rvest
tidyverse
@ -11,4 +20,3 @@ purrr
dplyr
stringr
forcats
urltools

BIN
README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.RData

Binary file not shown.

BIN
README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.rdb

Binary file not shown.

BIN
README_cache/gfm/unnamed-chunk-2_5090db04e31e59a3df3b03229995ed1d.rdx

Binary file not shown.

49
appveyor.yml

@ -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

2
configure

@ -1,4 +1,4 @@
# Anticonf (tm) script by Jeroen Ooms (2015)
# Anticonf (tm) script by Jeroen Ooms (2019)
# This script will query 'pkg-config' for the required cflags and ldflags.
# If pkg-config is unavailable or does not find the library, try setting
# INCLUDE_DIR and LIB_DIR manually via e.g:

4
curlparse.Rproj

@ -12,10 +12,10 @@ Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX
AutoAppendNewline: Yes
StripTrailingWhitespace: Yes
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageBuildArgs: --resave-data
PackageInstallArgs: --no-multiarch --with-keep.source --install-tests
PackageRoxygenize: rd,collate,namespace

67
inst/tinytest/test_curlparse.R

@ -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))
)
)
)
)

11
man/curlparse.Rd

@ -9,14 +9,13 @@
As of version 7.62.0 'libcurl' has exposed its 'URL' parser. Tools are
provided to parse 'URLs' using this new parser feature.
}
\details{
**UNTIL \code{curl}/\code{libcurl} general release at the end of October you \emph{must}
use the development version which can be cloned and built from
\url{https://github.com/curl/curl}.
\seealso{
Useful links:
\itemize{
\item URL: \url{https://gitlab.com/hrbrmstr/curlparse}
\item BugReports: \url{https://gitlab.com/hrbrmstr/curlparse/issues}
\item \url{https://gitlab.com/hrbrmstr/curlparse}
\item Report bugs at \url{https://gitlab.com/hrbrmstr/curlparse/issues}
}
}
\author{
Bob Rudis (bob@rud.is)

BIN
man/figures/README-unnamed-chunk-4-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
man/figures/README-unnamed-chunk-5-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

12
man/pipe.Rd

@ -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}

4
man/scheme.Rd

@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/RcppExports.R, R/domain.R
% Please edit documentation in R/RcppExports.R, R/Z-domain.R, R/domain.R
\name{scheme}
\alias{scheme}
\alias{user}
@ -32,6 +32,8 @@ query(urls)
fragment(urls)
domain(urls)
domain(urls)
}
\arguments{
\item{urls}{character vector of URLs}

3
src/Makevars

@ -1,4 +1,5 @@
PKG_CPPFLAGS=-I/usr/local/include
PKG_CFLAGS=$(C_VISIBILITY)
PKG_CPPFLAGS=-I/usr/local/include -DSTRICT_R_HEADERS
PKG_LIBS=-L/usr/local/lib -lcurl
all: clean

3
src/Makevars.in

@ -1,4 +1,5 @@
PKG_CPPFLAGS=@cflags@
PKG_CFLAGS=$(C_VISIBILITY)
PKG_CPPFLAGS=@cflags@ -DSTRICT_R_HEADERS
PKG_LIBS=@libs@
all: clean

7
src/Makevars.win

@ -1,9 +1,9 @@
VERSION=7.62.0
VERSION=7.64.1
PKG_LIBS= -L../windows/libcurl-$(VERSION)/lib${R_ARCH} \
-L. -lwinhttp -lcurl-dualssl -lssh2 -lz -lssl -lcrypto -lgdi32 -lws2_32 -lcrypt32 -lwldap32
-L. -lwinhttp -lcurl -lssh2 -lz -lssl -lcrypto -lgdi32 -lws2_32 -lcrypt32 -lwldap32
PKG_CPPFLAGS= \
-I../windows/libcurl-$(VERSION)/include -DCURL_STATICLIB
-I../windows/libcurl-$(VERSION)/include -DCURL_STATICLIB -DSTRICT_R_HEADERS
all: clean winlibs libwinhttp.dll.a
@ -18,4 +18,3 @@ winhttp.def:
cp winhttp$(WIN).def.in winhttp.def
.PHONY: all winlibs clean

37
src/winhttp32.def.in

@ -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

37
src/winhttp64.def.in

@ -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

5
tests/tinytest.R

@ -0,0 +1,5 @@
if ( requireNamespace("tinytest", quietly=TRUE) ){
tinytest::test_package("curlparse")
}

82
tmp/callbacks.c

@ -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);
}

9
tmp/callbacks.h

@ -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);

67
tmp/curl-common.h

@ -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);

779
tmp/curl-symbols.h

@ -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 */

81
tmp/curl.R

@ -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)
}

289
tmp/curl.c

@ -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;
}

39
tmp/download.R

@ -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)
}

51
tmp/download.c

@ -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);
}

126
tmp/echo.R

@ -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))
}

63
tmp/email.R

@ -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)
}

26
tmp/escape.R

@ -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);
}

33
tmp/escape.c

@ -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;
}

117
tmp/fetch.R

@ -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)
}

91
tmp/fetch.c

@ -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;
}

48
tmp/form.R

@ -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")
}

46
tmp/form.c

@ -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;
}

17
tmp/getdate.c

@ -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;
}

209
tmp/handle.R

@ -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)
}
}

484
tmp/handle.c

@ -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));
}

177
tmp/ieproxy.c

@ -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

96
tmp/init.c

@ -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();
}

69
tmp/interrupt.c

@ -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;
}

202
tmp/multi.R

@ -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)
}

316
tmp/multi.c

@ -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;
}

37
tmp/nslookup.R

@ -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))
}

100
tmp/nslookup.c

@ -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

27
tmp/onload.R

@ -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)
}

37
tmp/options.R

@ -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))]
})()

51
tmp/parse_headers.R

@ -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
}

48
tmp/proxy.R

@ -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)
}

56
tmp/reflist.c

@ -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);
}

16
tmp/split.c

@ -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;
}

39
tmp/ssl.c

@ -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
}

BIN
tmp/sysdata.rda

Binary file not shown.

39
tmp/typechecking.c

@ -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);
}

49
tmp/upload.R

@ -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)
}

51
tmp/utilities.R

@ -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)
}

147
tmp/utils.c

@ -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;
}

64
tmp/version.c

@ -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;
}

70
tmp/winidn.c

@ -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

48
tmp/writer.R

@ -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)
}

43
tmp/writer.c

@ -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));
}

87
tools/symbols-in-versions

@ -12,9 +12,16 @@
Name Introduced Deprecated Removed
CURLALTSVC_ALTUSED 7.64.1
CURLALTSVC_H1 7.64.1
CURLALTSVC_H2 7.64.1
CURLALTSVC_H3 7.64.1
CURLALTSVC_IMMEDIATELY 7.64.1
CURLALTSVC_READONLYFILE 7.64.1
CURLAUTH_ANY 7.10.6
CURLAUTH_ANYSAFE 7.10.6
CURLAUTH_BASIC 7.10.6
CURLAUTH_BEARER 7.61.0
CURLAUTH_DIGEST 7.10.6
CURLAUTH_DIGEST_IE 7.19.3
CURLAUTH_GSSAPI 7.55.0
@ -100,8 +107,8 @@ CURLE_PEER_FAILED_VERIFICATION 7.17.1
CURLE_QUOTE_ERROR 7.17.0
CURLE_RANGE_ERROR 7.17.0
CURLE_READ_ERROR 7.1
CURLE_RECV_ERROR 7.10
CURLE_RECURSIVE_API_CALL 7.59.0
CURLE_RECV_ERROR 7.10
CURLE_REMOTE_ACCESS_DENIED 7.17.0
CURLE_REMOTE_DISK_FULL 7.17.0
CURLE_REMOTE_FILE_EXISTS 7.17.0
@ -112,7 +119,7 @@ CURLE_SEND_ERROR 7.10
CURLE_SEND_FAIL_REWIND 7.12.3
CURLE_SHARE_IN_USE 7.9.6 7.17.0
CURLE_SSH 7.16.1
CURLE_SSL_CACERT 7.10
CURLE_SSL_CACERT 7.10 7.62.0
CURLE_SSL_CACERT_BADFILE 7.16.0
CURLE_SSL_CERTPROBLEM 7.10
CURLE_SSL_CIPHER 7.10
@ -206,9 +213,11 @@ CURLHEADER_SEPARATE 7.37.0
CURLHEADER_UNIFIED 7.37.0
CURLINFO_ACTIVESOCKET 7.45.0
CURLINFO_APPCONNECT_TIME 7.19.0
CURLINFO_APPCONNECT_TIME_T 7.61.0
CURLINFO_CERTINFO 7.19.1
CURLINFO_CONDITION_UNMET 7.19.4
CURLINFO_CONNECT_TIME 7.4.1
CURLINFO_CONNECT_TIME_T 7.61.0
CURLINFO_CONTENT_LENGTH_DOWNLOAD 7.6.1
CURLINFO_CONTENT_LENGTH_DOWNLOAD_T 7.55.0
CURLINFO_CONTENT_LENGTH_UPLOAD 7.6.1
@ -237,11 +246,13 @@ CURLINFO_LOCAL_PORT 7.21.0
CURLINFO_LONG 7.4.1
CURLINFO_MASK 7.4.1
CURLINFO_NAMELOOKUP_TIME 7.4.1
CURLINFO_NAMELOOKUP_TIME_T 7.61.0
CURLINFO_NONE 7.4.1
CURLINFO_NUM_CONNECTS 7.12.3
CURLINFO_OFF_T 7.55.0
CURLINFO_OS_ERRNO 7.12.2
CURLINFO_PRETRANSFER_TIME 7.4.1
CURLINFO_PRETRANSFER_TIME_T 7.61.0
CURLINFO_PRIMARY_IP 7.19.0
CURLINFO_PRIMARY_PORT 7.21.0
CURLINFO_PRIVATE 7.10.3
@ -251,6 +262,7 @@ CURLINFO_PROXY_SSL_VERIFYRESULT 7.52.0
CURLINFO_PTR 7.54.1
CURLINFO_REDIRECT_COUNT 7.9.7
CURLINFO_REDIRECT_TIME 7.9.7
CURLINFO_REDIRECT_TIME_T 7.61.0
CURLINFO_REDIRECT_URL 7.18.2
CURLINFO_REQUEST_SIZE 7.4.1
CURLINFO_RESPONSE_CODE 7.10.8
@ -274,11 +286,13 @@ CURLINFO_SSL_DATA_OUT 7.12.1
CURLINFO_SSL_ENGINES 7.12.3
CURLINFO_SSL_VERIFYRESULT 7.5
CURLINFO_STARTTRANSFER_TIME 7.9.2
CURLINFO_STARTTRANSFER_TIME_T 7.61.0
CURLINFO_STRING 7.4.1
CURLINFO_TEXT 7.9.6
CURLINFO_TLS_SESSION 7.34.0 7.48.0
CURLINFO_TLS_SSL_PTR 7.48.0
CURLINFO_TOTAL_TIME 7.4.1
CURLINFO_TOTAL_TIME_T 7.61.0
CURLINFO_TYPEMASK 7.4.1
CURLIOCMD_NOP 7.12.3
CURLIOCMD_RESTARTREAD 7.12.3
@ -335,6 +349,8 @@ CURLOPT_ABSTRACT_UNIX_SOCKET 7.53.0
CURLOPT_ACCEPTTIMEOUT_MS 7.24.0
CURLOPT_ACCEPT_ENCODING 7.21.6
CURLOPT_ADDRESS_SCOPE 7.19.0
CURLOPT_ALTSVC 7.64.1
CURLOPT_ALTSVC_CTRL 7.64.1
CURLOPT_APPEND 7.17.0
CURLOPT_AUTOREFERER 7.1
CURLOPT_BUFFERSIZE 7.10
@ -363,17 +379,21 @@ CURLOPT_COOKIESESSION 7.9.7
CURLOPT_COPYPOSTFIELDS 7.17.1
CURLOPT_CRLF 7.1
CURLOPT_CRLFILE 7.19.0
CURLOPT_CURLU 7.63.0
CURLOPT_CUSTOMREQUEST 7.1
CURLOPT_DEBUGDATA 7.9.6
CURLOPT_DEBUGFUNCTION 7.9.6
CURLOPT_DEFAULT_PROTOCOL 7.45.0
CURLOPT_DIRLISTONLY 7.17.0
CURLOPT_DISALLOW_USERNAME_IN_URL 7.61.0
CURLOPT_DNS_CACHE_TIMEOUT 7.9.3
CURLOPT_DNS_INTERFACE 7.33.0
CURLOPT_DNS_LOCAL_IP4 7.33.0
CURLOPT_DNS_LOCAL_IP6 7.33.0
CURLOPT_DNS_SERVERS 7.24.0
CURLOPT_DNS_SHUFFLE_ADDRESSES 7.60.0
CURLOPT_DNS_USE_GLOBAL_CACHE 7.9.3 7.11.1
CURLOPT_DOH_URL 7.62.0
CURLOPT_EGDSOCKET 7.7
CURLOPT_ENCODING 7.10
CURLOPT_ERRORBUFFER 7.1
@ -404,10 +424,12 @@ CURLOPT_FTP_USE_EPSV 7.9.2
CURLOPT_FTP_USE_PRET 7.20.0
CURLOPT_GSSAPI_DELEGATION 7.22.0
CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS 7.59.0
CURLOPT_HAPROXYPROTOCOL 7.60.0
CURLOPT_HEADER 7.1
CURLOPT_HEADERDATA 7.10
CURLOPT_HEADERFUNCTION 7.7.2
CURLOPT_HEADEROPT 7.37.0
CURLOPT_HTTP09_ALLOWED 7.64.0
CURLOPT_HTTP200ALIASES 7.10.3
CURLOPT_HTTPAUTH 7.10.6
CURLOPT_HTTPGET 7.8.1
@ -441,6 +463,7 @@ CURLOPT_LOW_SPEED_TIME 7.1
CURLOPT_MAIL_AUTH 7.25.0
CURLOPT_MAIL_FROM 7.20.0
CURLOPT_MAIL_RCPT 7.20.0
CURLOPT_MAXAGE_CONN 7.65.0
CURLOPT_MAXCONNECTS 7.7
CURLOPT_MAXFILESIZE 7.10.8
CURLOPT_MAXFILESIZE_LARGE 7.11.0
@ -504,6 +527,7 @@ CURLOPT_PROXY_SSL_CIPHER_LIST 7.52.0
CURLOPT_PROXY_SSL_OPTIONS 7.52.0
CURLOPT_PROXY_SSL_VERIFYHOST 7.52.0
CURLOPT_PROXY_SSL_VERIFYPEER 7.52.0
CURLOPT_PROXY_TLS13_CIPHERS 7.61.0
CURLOPT_PROXY_TLSAUTH_PASSWORD 7.52.0
CURLOPT_PROXY_TLSAUTH_TYPE 7.52.0
CURLOPT_PROXY_TLSAUTH_USERNAME 7.52.0
@ -518,6 +542,8 @@ CURLOPT_REDIR_PROTOCOLS 7.19.4
CURLOPT_REFERER 7.1
CURLOPT_REQUEST_TARGET 7.55.0
CURLOPT_RESOLVE 7.21.3
CURLOPT_RESOLVER_START_DATA 7.59.0
CURLOPT_RESOLVER_START_FUNCTION 7.59.0
CURLOPT_RESUME_FROM 7.1
CURLOPT_RESUME_FROM_LARGE 7.11.0
CURLOPT_RTSPHEADER 7.20.0
@ -592,16 +618,19 @@ CURLOPT_TIMEOUT 7.1
CURLOPT_TIMEOUT_MS 7.16.2
CURLOPT_TIMEVALUE 7.1
CURLOPT_TIMEVALUE_LARGE 7.59.0
CURLOPT_TLS13_CIPHERS 7.61.0
CURLOPT_TLSAUTH_PASSWORD 7.21.4
CURLOPT_TLSAUTH_TYPE 7.21.4
CURLOPT_TLSAUTH_USERNAME 7.21.4
CURLOPT_TRAILERDATA 7.64.0
CURLOPT_TRAILERFUNCTION 7.64.0
CURLOPT_TRANSFERTEXT 7.1.1
CURLOPT_TRANSFER_ENCODING 7.21.6
CURLOPT_RESOLVER_START_FUNCTION 7.59.0
CURLOPT_RESOLVER_START_DATA 7.59.0
CURLOPT_UNIX_SOCKET_PATH 7.40.0
CURLOPT_UNRESTRICTED_AUTH 7.10.4
CURLOPT_UPKEEP_INTERVAL_MS 7.62.0
CURLOPT_UPLOAD 7.1
CURLOPT_UPLOAD_BUFFERSIZE 7.62.0
CURLOPT_URL 7.1
CURLOPT_USERAGENT 7.1
CURLOPT_USERNAME 7.19.1
@ -684,20 +713,22 @@ CURLSSH_AUTH_KEYBOARD 7.16.1
CURLSSH_AUTH_NONE 7.16.1
CURLSSH_AUTH_PASSWORD 7.16.1
CURLSSH_AUTH_PUBLICKEY 7.16.1
CURLSSLBACKEND_AXTLS 7.38.0
CURLSSLBACKEND_AXTLS 7.38.0 7.61.0
CURLSSLBACKEND_BORINGSSL 7.49.0
CURLSSLBACKEND_CYASSL 7.34.0
CURLSSLBACKEND_DARWINSSL 7.34.0
CURLSSLBACKEND_DARWINSSL 7.34.0 7.64.1
CURLSSLBACKEND_GNUTLS 7.34.0
CURLSSLBACKEND_GSKIT 7.34.0
CURLSSLBACKEND_LIBRESSL 7.49.0
CURLSSLBACKEND_MBEDTLS 7.46.0
CURLSSLBACKEND_MESALINK 7.62.0
CURLSSLBACKEND_NONE 7.34.0
CURLSSLBACKEND_NSS 7.34.0
CURLSSLBACKEND_OPENSSL 7.34.0
CURLSSLBACKEND_POLARSSL 7.34.0
CURLSSLBACKEND_QSOSSL 7.34.0 - 7.38.1
CURLSSLBACKEND_SCHANNEL 7.34.0
CURLSSLBACKEND_SECURETRANSPORT 7.64.1
CURLSSLBACKEND_WOLFSSL 7.49.0
CURLSSLOPT_ALLOW_BEAST 7.25.0
CURLSSLOPT_NO_REVOKE 7.44.0
@ -705,10 +736,49 @@ CURLSSLSET_NO_BACKENDS 7.56.0
CURLSSLSET_OK 7.56.0
CURLSSLSET_TOO_LATE 7.56.0
CURLSSLSET_UNKNOWN_BACKEND 7.56.0
CURLUE_BAD_HANDLE 7.62.0
CURLUE_BAD_PARTPOINTER 7.62.0
CURLUE_BAD_PORT_NUMBER 7.62.0
CURLUE_MALFORMED_INPUT 7.62.0
CURLUE_NO_FRAGMENT 7.62.0
CURLUE_NO_HOST 7.62.0
CURLUE_NO_OPTIONS 7.62.0
CURLUE_NO_PASSWORD 7.62.0
CURLUE_NO_PORT 7.62.0
CURLUE_NO_QUERY 7.62.0
CURLUE_NO_SCHEME 7.62.0
CURLUE_NO_USER 7.62.0
CURLUE_OK 7.62.0
CURLUE_OUT_OF_MEMORY 7.62.0
CURLUE_UNKNOWN_PART 7.62.0
CURLUE_UNSUPPORTED_SCHEME 7.62.0
CURLUE_URLDECODE 7.62.0
CURLUE_USER_NOT_ALLOWED 7.62.0
CURLUPART_FRAGMENT 7.62.0
CURLUPART_HOST 7.62.0
CURLUPART_OPTIONS 7.62.0
CURLUPART_PASSWORD 7.62.0
CURLUPART_PATH 7.62.0
CURLUPART_PORT 7.62.0
CURLUPART_QUERY 7.62.0
CURLUPART_SCHEME 7.62.0
CURLUPART_URL 7.62.0
CURLUPART_USER 7.62.0
CURLUPART_ZONEID 7.65.0
CURLUSESSL_ALL 7.17.0
CURLUSESSL_CONTROL 7.17.0
CURLUSESSL_NONE 7.17.0
CURLUSESSL_TRY 7.17.0
CURLU_APPENDQUERY 7.62.0
CURLU_DEFAULT_PORT 7.62.0
CURLU_DEFAULT_SCHEME 7.62.0
CURLU_DISALLOW_USER 7.62.0
CURLU_GUESS_SCHEME 7.62.0
CURLU_NON_SUPPORT_SCHEME 7.62.0
CURLU_NO_DEFAULT_PORT 7.62.0
CURLU_PATH_AS_IS 7.62.0
CURLU_URLDECODE 7.62.0
CURLU_URLENCODE 7.62.0
CURLVERSION_FIFTH 7.57.0
CURLVERSION_FIRST 7.10
CURLVERSION_FOURTH 7.16.1
@ -770,6 +840,7 @@ CURL_LOCK_DATA_CONNECT 7.10.3
CURL_LOCK_DATA_COOKIE 7.10.3
CURL_LOCK_DATA_DNS 7.10.3
CURL_LOCK_DATA_NONE 7.10.3
CURL_LOCK_DATA_PSL 7.61.0
CURL_LOCK_DATA_SHARE 7.10.4
CURL_LOCK_DATA_SSL_SESSION 7.10.3
CURL_LOCK_TYPE_CONNECT 7.10 - 7.10.2
@ -840,6 +911,10 @@ CURL_TIMECOND_LASTMOD 7.9.7
CURL_TIMECOND_NONE 7.9.7
CURL_TLSAUTH_NONE 7.21.4
CURL_TLSAUTH_SRP 7.21.4
CURL_TRAILERFUNC_ABORT 7.64.0
CURL_TRAILERFUNC_OK 7.64.0
CURL_UPKEEP_INTERVAL_DEFAULT 7.62.0
CURL_VERSION_ALTSVC 7.64.1
CURL_VERSION_ASYNCHDNS 7.10.7
CURL_VERSION_BROTLI 7.57.0
CURL_VERSION_CONV 7.15.4

7
tools/symbols.R

@ -45,6 +45,7 @@ option <- grepl("CURLOPT", symbols$name)
symbols$type[!option] <- NA
# Save as lazy data
curl_symbols <- symbols[order(symbols$name), ]
row.names(curl_symbols) = NULL
devtools::use_data(curl_symbols, overwrite = TRUE)
curl_symbol_data <- symbols[order(symbols$name), ]
row.names(curl_symbol_data) = NULL
save(curl_symbol_data = curl_symbol_data, file = 'R/sysdata.rda', version = 2)
#usethis::use_data(curl_symbol_data, overwrite = TRUE)

Loading…
Cancel
Save