Compare commits
No commits in common. 'batman' and 'master' have entirely different histories.
37 changed files with 0 additions and 3499 deletions
@ -1,26 +0,0 @@ |
|||
^.vscode$ |
|||
^.*\.Rproj$ |
|||
^\.Rproj\.user$ |
|||
^\.travis\.yml$ |
|||
^README\.*Rmd$ |
|||
^README\.*html$ |
|||
^NOTES\.*Rmd$ |
|||
^NOTES\.*html$ |
|||
^\.codecov\.yml$ |
|||
^README_files$ |
|||
^doc$ |
|||
^docs$ |
|||
^tmp$ |
|||
^notes$ |
|||
^CONDUCT.*$ |
|||
^CODE.*$ |
|||
^\.gitlab-ci\.yml$ |
|||
^\.vscode$ |
|||
^CRAN-RELEASE$ |
|||
^appveyor\.yml$ |
|||
^tools$ |
|||
^LICENSE\.md$ |
|||
^bld$ |
|||
^node_modules^ |
|||
^package-lock\.json$ |
|||
^\.github$ |
@ -1 +0,0 @@ |
|||
comment: false |
@ -1 +0,0 @@ |
|||
*.html |
@ -1,26 +0,0 @@ |
|||
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. |
|||
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions |
|||
on: |
|||
push: |
|||
branches: |
|||
- batman |
|||
pull_request: |
|||
branches: |
|||
- batman |
|||
|
|||
name: R-CMD-check |
|||
|
|||
jobs: |
|||
R-CMD-check: |
|||
runs-on: macOS-latest |
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
- uses: r-lib/actions/setup-r@master |
|||
- name: Install dependencies |
|||
run: | |
|||
install.packages(c("remotes", "rcmdcheck")) |
|||
remotes::install_deps(dependencies = TRUE) |
|||
shell: Rscript {0} |
|||
- name: Check |
|||
run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "error") |
|||
shell: Rscript {0} |
@ -1,9 +0,0 @@ |
|||
.DS_Store |
|||
.Rproj.user |
|||
.Rhistory |
|||
.RData |
|||
.Rproj |
|||
README_cache |
|||
src/*.o |
|||
src/*.so |
|||
src/*.dll |
@ -1,12 +0,0 @@ |
|||
language: R |
|||
sudo: false |
|||
cache: packages |
|||
|
|||
before_install: |
|||
- echo "options(repos = c(CRAN = 'https://packagemanager.rstudio.com/all/__linux__/xenial/latest'))" >> ~/.Rprofile.site |
|||
- echo "options(HTTPUserAgent = paste0('R/', getRversion(), ' R (', |
|||
paste(getRversion(), R.version['platform'], R.version['arch'], R.version['os']), |
|||
')'))" >> ~/.Rprofile.site |
|||
|
|||
after_success: |
|||
- Rscript -e 'covr::codecov()' |
@ -1,25 +0,0 @@ |
|||
# Contributor Code of Conduct |
|||
|
|||
As contributors and maintainers of this project, we pledge to respect all people who |
|||
contribute through reporting issues, posting feature requests, updating documentation, |
|||
submitting pull requests or patches, and other activities. |
|||
|
|||
We are committed to making participation in this project a harassment-free experience for |
|||
everyone, regardless of level of experience, gender, gender identity and expression, |
|||
sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. |
|||
|
|||
Examples of unacceptable behavior by participants include the use of sexual language or |
|||
imagery, derogatory comments or personal attacks, trolling, public or private harassment, |
|||
insults, or other unprofessional conduct. |
|||
|
|||
Project maintainers have the right and responsibility to remove, edit, or reject comments, |
|||
commits, code, wiki edits, issues, and other contributions that are not aligned to this |
|||
Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed |
|||
from the project team. |
|||
|
|||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by |
|||
opening an issue or contacting one or more of the project maintainers. |
|||
|
|||
This Code of Conduct is adapted from the Contributor Covenant |
|||
(http:contributor-covenant.org), version 1.0.0, available at |
|||
http://contributor-covenant.org/version/1/0/0/ |
@ -1,32 +0,0 @@ |
|||
Package: construe |
|||
Type: Package |
|||
Title: HTTP Request, Response and URL Parser |
|||
Version: 0.1.0 |
|||
Date: 2020-08-28 |
|||
Authors@R: c( |
|||
person("Bob", "Rudis", email = "bob@rud.is", role = c("aut", "cre"), |
|||
comment = c(ORCID = "0000-0001-5670-2640")), |
|||
person("Alex", "Nekipelov", email = "alex@nekipelov.net", role = "aut", |
|||
comment = "httpparser C++ library"), |
|||
person("Ícaro", "Dantas de Araújo Lima", role = "ctb", |
|||
comment = "httpparser C++ library") |
|||
) |
|||
Maintainer: Bob Rudis <bob@rud.is> |
|||
Description: A simple and fast HTTP request, response and URL parser based on the C++ 'httpparser' library |
|||
by Alex Nekipelov (<https://github.com/nekipelov/httpparser>). |
|||
URL: https://git.rud.is/hrbrmstr/construe |
|||
BugReports: https://git.rud.is/hrbrmstr/construe/issues |
|||
SystemRequirements: C++11 |
|||
Encoding: UTF-8 |
|||
License: MIT + file LICENSE |
|||
Suggests: |
|||
covr, tinytest |
|||
Depends: |
|||
R (>= 3.6.0) |
|||
Imports: |
|||
Rcpp, |
|||
magrittr |
|||
Roxygen: list(markdown = TRUE) |
|||
RoxygenNote: 7.1.1 |
|||
LinkingTo: |
|||
Rcpp |
@ -1,2 +0,0 @@ |
|||
YEAR: 2020 |
|||
COPYRIGHT HOLDER: Bob Rudis |
@ -1,21 +0,0 @@ |
|||
# MIT License |
|||
|
|||
Copyright (c) 2020 Bob Rudis |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -1,12 +0,0 @@ |
|||
# Generated by roxygen2: do not edit by hand |
|||
|
|||
export("%>%") |
|||
export(parse_request) |
|||
export(parse_request_raw) |
|||
export(parse_response) |
|||
export(parse_response_raw) |
|||
export(parse_url) |
|||
export(read_file_raw) |
|||
importFrom(Rcpp,sourceCpp) |
|||
importFrom(magrittr,"%>%") |
|||
useDynLib(construe, .registration = TRUE) |
@ -1,2 +0,0 @@ |
|||
0.1.0 |
|||
* Initial release |
@ -1,94 +0,0 @@ |
|||
# Generated by using Rcpp::compileAttributes() -> do not edit by hand |
|||
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 |
|||
|
|||
#' Parse an HTTP request |
|||
#' |
|||
#' You can use the non- `_raw` version on input you know for sure is plain text |
|||
#' |
|||
#' @param req HTTP request character string |
|||
#' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame |
|||
#' element are converted to lower case |
|||
#' @export |
|||
#' @examples |
|||
#' paste0(c( |
|||
#' "GET /uri.cgi HTTP/1.1\r\n", |
|||
#' "User-Agent: Mozilla/5.0\r\n", |
|||
#' "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n", |
|||
#' "Host: 127.0.0.1\r\n", "\r\n" |
|||
#' ), collapse = "") -> req |
|||
#' |
|||
#' res <- parse_request(req) |
|||
#' res <- parse_request_raw(charToRaw(req)) |
|||
parse_request <- function(req, headers_lowercase = TRUE) { |
|||
.Call(`_construe_parse_request`, req, headers_lowercase) |
|||
} |
|||
|
|||
#' @rdname parse_request |
|||
#' @param req HTTP request character string |
|||
#' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame |
|||
#' element are converted to lower case |
|||
#' @export |
|||
parse_request_raw <- function(req, headers_lowercase = TRUE) { |
|||
.Call(`_construe_parse_request_raw`, req, headers_lowercase) |
|||
} |
|||
|
|||
#' Parse an HTTP response |
|||
#' |
|||
#' You can use the non- `_raw` version on input you know for sure is plain text |
|||
#' |
|||
#' @param resp HTTP response character string |
|||
#' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame |
|||
#' element are converted to lower case |
|||
#' @export |
|||
#' @examples |
|||
#' paste0(c( |
|||
#' "HTTP/1.1 200 OK\r\n", |
|||
#' "Server: nginx/1.2.1\r\n", |
|||
#' "Content-Type: text/html\r\n", |
|||
#' "Content-Length: 8\r\n", |
|||
#' "Connection: keep-alive\r\n", |
|||
#' "\r\n", |
|||
#' "<html />" |
|||
#' ), collapse = "") -> resp |
|||
#' |
|||
#' res <- parse_response(resp) |
|||
#' res <- parse_response_raw(charToRaw(resp)) |
|||
parse_response <- function(resp, headers_lowercase = TRUE) { |
|||
.Call(`_construe_parse_response`, resp, headers_lowercase) |
|||
} |
|||
|
|||
#' @rdname parse_response |
|||
#' @param resp HTTP request character string |
|||
#' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame |
|||
#' element are converted to lower case |
|||
#' @export |
|||
parse_response_raw <- function(resp, headers_lowercase = TRUE) { |
|||
.Call(`_construe_parse_response_raw`, resp, headers_lowercase) |
|||
} |
|||
|
|||
#' Parse URLs |
|||
#' |
|||
#' @param urls character vector of URLs |
|||
#' @export |
|||
#' @examples |
|||
#' URL <- "http://www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment" |
|||
#' parse_url(URL) |
|||
parse_url <- function(urls) { |
|||
.Call(`_construe_parse_url`, urls) |
|||
} |
|||
|
|||
#' Read in a file, fast and raw |
|||
#' |
|||
#' @param fil file to read in (no path expansion is performed) |
|||
#' @param buffer_size larger buffer sizes may speed up reading of |
|||
#' very large files. It can also hurt performance, and this |
|||
#' function reads in the entire file into memory, so a |
|||
#' large buffer size also means more (temporary) memory will |
|||
#' be allocated. |
|||
#' @export |
|||
#' @examples |
|||
#' read_file_raw(system.file("extdat", "example.hdr", package = "construe")) |
|||
read_file_raw <- function(fil, buffer_size = 16384L) { |
|||
.Call(`_construe_read_file_raw`, fil, buffer_size) |
|||
} |
|||
|
@ -1,12 +0,0 @@ |
|||
#' HTTP Request, Response and URL Parser |
|||
#' |
|||
#' A simple and fast HTTP request, response and URL parser based on the C++ 'httpparser' library |
|||
#' by Alex Nekipelov (<https://github.com/nekipelov/httpparser>) |
|||
#' |
|||
#' @md |
|||
#' @name construe |
|||
#' @keywords internal |
|||
#' @author Bob Rudis (bob@@rud.is) |
|||
#' @importFrom Rcpp sourceCpp |
|||
#' @useDynLib construe, .registration = TRUE |
|||
"_PACKAGE" |
@ -1,11 +0,0 @@ |
|||
#' Pipe operator |
|||
#' |
|||
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. |
|||
#' |
|||
#' @name %>% |
|||
#' @rdname pipe |
|||
#' @keywords internal |
|||
#' @export |
|||
#' @importFrom magrittr %>% |
|||
#' @usage lhs \%>\% rhs |
|||
NULL |
@ -1,185 +0,0 @@ |
|||
--- |
|||
output: rmarkdown::github_document |
|||
editor_options: |
|||
chunk_output_type: console |
|||
--- |
|||
```{r pkg-knitr-opts, include=FALSE} |
|||
hrbrpkghelpr::global_opts() |
|||
``` |
|||
|
|||
```{r badges, results='asis', echo=FALSE, cache=FALSE} |
|||
hrbrpkghelpr::stinking_badges(branch = "batman") |
|||
``` |
|||
|
|||
```{r description, results='asis', echo=FALSE, cache=FALSE} |
|||
hrbrpkghelpr::yank_title_and_description() |
|||
``` |
|||
|
|||
## What's Inside The Tin |
|||
|
|||
The following functions are implemented: |
|||
|
|||
```{r ingredients, results='asis', echo=FALSE, cache=FALSE} |
|||
hrbrpkghelpr::describe_ingredients() |
|||
``` |
|||
|
|||
## Installation |
|||
|
|||
```{r install-ex, results='asis', echo=FALSE, cache=FALSE} |
|||
hrbrpkghelpr::install_block() |
|||
``` |
|||
|
|||
## Usage |
|||
|
|||
```{r lib-ex} |
|||
library(construe) |
|||
|
|||
# current version |
|||
packageVersion("construe") |
|||
|
|||
``` |
|||
|
|||
### Requests |
|||
|
|||
```{r ex01} |
|||
paste0(c( |
|||
"GET /uri.cgi HTTP/1.1\r\n", |
|||
"User-Agent: Mozilla/5.0\r\n", |
|||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n", |
|||
"Host: 127.0.0.1\r\n", "\r\n" |
|||
), collapse = "") -> req |
|||
|
|||
req_raw <- charToRaw(req) |
|||
|
|||
parse_request(req) |
|||
|
|||
parse_request_raw(req_raw) |
|||
|
|||
microbenchmark::microbenchmark( |
|||
parse_request = parse_request(req), |
|||
parse_request_raw = parse_request_raw(req_raw) |
|||
) |
|||
``` |
|||
|
|||
|
|||
### Responses |
|||
|
|||
```{r ex02} |
|||
paste0(c( |
|||
"HTTP/1.1 200 OK\r\n", |
|||
"Server: nginx/1.2.1\r\n", |
|||
"Content-Type: text/html\r\n", |
|||
"Content-Length: 8\r\n", |
|||
"Connection: keep-alive\r\n", |
|||
"\r\n", |
|||
"<html />" |
|||
), collapse = "") -> resp |
|||
|
|||
resp_raw <- charToRaw(resp) |
|||
|
|||
parse_response(resp) |
|||
|
|||
parse_response_raw(resp_raw) |
|||
|
|||
microbenchmark::microbenchmark( |
|||
parse_response = parse_response(resp), |
|||
parse_response_raw = parse_response_raw(resp_raw) |
|||
) |
|||
``` |
|||
|
|||
### curl output example |
|||
|
|||
`HEAD` request: |
|||
|
|||
```{r curl-01} |
|||
sys::exec_internal( |
|||
cmd = "curl", |
|||
args = c("--include", "--head", "--silent", "https://httpbin.org/") |
|||
) -> res |
|||
|
|||
str(parse_response(rawToChar(res$stdout)), 2) |
|||
|
|||
curl::curl_fetch_memory( |
|||
"https://httpbin.org/", |
|||
handle = curl::new_handle( |
|||
nobody = TRUE |
|||
) |
|||
) -> res |
|||
|
|||
str(construe::parse_response_raw(res$headers), 2) |
|||
|
|||
curl::curl_fetch_memory( |
|||
"http://rud.is/b", |
|||
handle = curl::new_handle( |
|||
nobody = TRUE, |
|||
followlocation = TRUE |
|||
) |
|||
) -> res |
|||
|
|||
rawToChar(res$headers) %>% |
|||
strsplit("(?m)\r\n\r\n", perl = TRUE) %>% |
|||
unlist() %>% |
|||
lapply(construe::parse_response) %>% |
|||
str(2) |
|||
``` |
|||
|
|||
`GET` request: |
|||
|
|||
```{r curl-02} |
|||
sys::exec_internal( |
|||
cmd = "curl", |
|||
args = c("--include", "--silent", "https://httpbin.org/") |
|||
) -> res |
|||
|
|||
str(parse_response_raw(res$stdout), 2) |
|||
|
|||
res <- curl::curl_fetch_memory("https://httpbin.org/") |
|||
|
|||
str(construe::parse_response_raw(res$headers), 2) |
|||
``` |
|||
|
|||
### URLs |
|||
|
|||
```{r ex03} |
|||
c( |
|||
"git+ssh://example.com/path/file", |
|||
"https://example.com/path/file", |
|||
"http://www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://www.example.com", |
|||
"http://username@www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://username:passwd@www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://www.example.com:8080/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://username:passwd@www.example.com:8080/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"ftp://username:passwd@ftp.example.com/dir/filename.ext", |
|||
"mailto:username@example.com", |
|||
"svn+ssh://hostname-01.org/path/to/file", |
|||
"xddp::://///blah.wat/?" |
|||
) -> turls |
|||
|
|||
parse_url(turls) |
|||
|
|||
microbenchmark::microbenchmark( |
|||
parse_url = parse_url(turls[1]) |
|||
) |
|||
``` |
|||
|
|||
### Parse headers from Palo Alto `HEAD` requests |
|||
|
|||
```{r why} |
|||
hdr <- read_file_raw(system.file("extdat", "example.hdr", package = "construe")) |
|||
|
|||
cat(rawToChar(hdr)) |
|||
|
|||
parse_response_raw(hdr) |
|||
``` |
|||
|
|||
## construe 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. |
@ -1,52 +0,0 @@ |
|||
# 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 |
|||
|
|||
cache: |
|||
- C:\RLibrary |
|||
|
|||
environment: |
|||
NOT_CRAN: true |
|||
# env vars that may need to be set, at least temporarily, from time to time |
|||
# see https://github.com/krlmlr/r-appveyor#readme for details |
|||
# USE_RTOOLS: true |
|||
# R_REMOTES_STANDALONE: true |
|||
|
|||
# Adapt as necessary starting from here |
|||
|
|||
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 |
@ -1,21 +0,0 @@ |
|||
Version: 1.0 |
|||
|
|||
RestoreWorkspace: Default |
|||
SaveWorkspace: Default |
|||
AlwaysSaveHistory: Default |
|||
|
|||
EnableCodeIndexing: Yes |
|||
UseSpacesForTab: Yes |
|||
NumSpacesForTab: 2 |
|||
Encoding: UTF-8 |
|||
|
|||
RnwWeave: Sweave |
|||
LaTeX: pdfLaTeX |
|||
|
|||
StripTrailingWhitespace: Yes |
|||
|
|||
BuildType: Package |
|||
PackageUseDevtools: Yes |
|||
PackageInstallArgs: --no-multiarch --with-keep.source |
|||
PackageBuildArgs: --resave-data |
|||
PackageRoxygenize: rd,collate,namespace |
@ -1,20 +0,0 @@ |
|||
HTTP/1.1 200 OK |
|||
Date: Mon, 13 Jul 2020 11:23:49 GMT |
|||
Content-Type: text/html; charset=UTF-8 |
|||
Content-Length: 11757 |
|||
Connection: keep-alive |
|||
ETag: "6e185d1cea69" |
|||
Pragma: no-cache |
|||
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 |
|||
Expires: Thu, 19 Nov 1981 08:52:00 GMT |
|||
X-FRAME-OPTIONS: DENY |
|||
Set-Cookie: PHPSESSID=d242c76e2e6ad2e71dd0c524c63e66d0; path=/; secure; HttpOnly |
|||
Set-Cookie: PHPSESSID=d242c76e2e6ad2e71dd0c524c63e66d0; path=/; secure; HttpOnly |
|||
Set-Cookie: PHPSESSID=d242c76e2e6ad2e71dd0c524c63e66d0; path=/; secure; HttpOnly |
|||
Set-Cookie: PHPSESSID=d242c76e2e6ad2e71dd0c524c63e66d0; path=/; secure; HttpOnly |
|||
Set-Cookie: PHPSESSID=d242c76e2e6ad2e71dd0c524c63e66d0; path=/; secure; HttpOnly |
|||
Strict-Transport-Security: max-age=31536000; |
|||
X-XSS-Protection: 1; mode=block; |
|||
X-Content-Type-Options: nosniff |
|||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src * data:; style-src 'self' 'unsafe-inline'; |
|||
|
@ -1,64 +0,0 @@ |
|||
library(construe) |
|||
|
|||
paste0(c( |
|||
"GET /uri.cgi HTTP/1.1\r\n", |
|||
"User-Agent: Mozilla/5.0\r\n", |
|||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n", |
|||
"Host: 127.0.0.1\r\n", "\r\n" |
|||
), collapse = "") -> req |
|||
|
|||
res <- parse_request(req) |
|||
res <- parse_request_raw(charToRaw(req)) |
|||
|
|||
expect_true(res$method[1] == "GET") |
|||
expect_true(res$keepalive[1] == TRUE) |
|||
expect_true("host" %in% res$headers$name) |
|||
|
|||
paste0(c( |
|||
"HTTP/1.1 200 OK\r\n", |
|||
"Server: nginx/1.2.1\r\n", |
|||
"Content-Type: text/html\r\n", |
|||
"Content-Length: 8\r\n", |
|||
"Connection: keep-alive\r\n", |
|||
"\r\n", |
|||
"<html />" |
|||
), collapse = "") -> resp |
|||
|
|||
res <- parse_response(resp) |
|||
res <- parse_response_raw(charToRaw(resp)) |
|||
|
|||
expect_true(res$status_msg[1] == "OK") |
|||
expect_true(res$keepalive[1] == TRUE) |
|||
expect_true("server" %in% res$headers$name) |
|||
|
|||
c( |
|||
"git+ssh://example.com/path/file", |
|||
"https://example.com/path/file", |
|||
"http://www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://www.example.com", |
|||
"http://username@www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://username:passwd@www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://www.example.com:8080/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"http://username:passwd@www.example.com:8080/dir/subdir?param=1¶m=2;param%20=%20#fragment", |
|||
"ftp://username:passwd@ftp.example.com/dir/filename.ext", |
|||
"mailto:username@example.com", |
|||
"svn+ssh://hostname-01.org/path/to/file", |
|||
"xddp::://///blah.wat/?" |
|||
) -> turls |
|||
|
|||
res <- parse_url(turls) |
|||
|
|||
expect_true(is.na(res$scheme[12])) |
|||
expect_true(res$scheme[1] == "git+ssh") |
|||
|
|||
parse_response_raw( |
|||
read_file_raw( |
|||
system.file("extdat", "example.hdr", package = "construe") |
|||
) |
|||
) -> res |
|||
|
|||
expect_true(res$headers$name[[5]] == "etag") |
|||
|
|||
|
|||
|
|||
|
@ -1,23 +0,0 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/construe-package.R |
|||
\docType{package} |
|||
\name{construe} |
|||
\alias{construe} |
|||
\alias{construe-package} |
|||
\title{HTTP Request, Response and URL Parser} |
|||
\description{ |
|||
A simple and fast HTTP request, response and URL parser based on the C++ 'httpparser' library |
|||
by Alex Nekipelov (\url{https://github.com/nekipelov/httpparser}) |
|||
} |
|||
\seealso{ |
|||
Useful links: |
|||
\itemize{ |
|||
\item \url{https://git.rud.is/hrbrmstr/construe} |
|||
\item Report bugs at \url{https://git.rud.is/hrbrmstr/construe/issues} |
|||
} |
|||
|
|||
} |
|||
\author{ |
|||
Bob Rudis (bob@rud.is) |
|||
} |
|||
\keyword{internal} |
@ -1,31 +0,0 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/RcppExports.R |
|||
\name{parse_request} |
|||
\alias{parse_request} |
|||
\alias{parse_request_raw} |
|||
\title{Parse an HTTP request} |
|||
\usage{ |
|||
parse_request(req, headers_lowercase = TRUE) |
|||
|
|||
parse_request_raw(req, headers_lowercase = TRUE) |
|||
} |
|||
\arguments{ |
|||
\item{req}{HTTP request character string} |
|||
|
|||
\item{headers_lowercase}{if \code{TRUE} (the default) names in the \code{headers} data frame |
|||
element are converted to lower case} |
|||
} |
|||
\description{ |
|||
You can use the non- \verb{_raw} version on input you know for sure is plain text |
|||
} |
|||
\examples{ |
|||
paste0(c( |
|||
"GET /uri.cgi HTTP/1.1\r\n", |
|||
"User-Agent: Mozilla/5.0\r\n", |
|||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n", |
|||
"Host: 127.0.0.1\r\n", "\r\n" |
|||
), collapse = "") -> req |
|||
|
|||
res <- parse_request(req) |
|||
res <- parse_request_raw(charToRaw(req)) |
|||
} |
@ -1,34 +0,0 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/RcppExports.R |
|||
\name{parse_response} |
|||
\alias{parse_response} |
|||
\alias{parse_response_raw} |
|||
\title{Parse an HTTP response} |
|||
\usage{ |
|||
parse_response(resp, headers_lowercase = TRUE) |
|||
|
|||
parse_response_raw(resp, headers_lowercase = TRUE) |
|||
} |
|||
\arguments{ |
|||
\item{resp}{HTTP request character string} |
|||
|
|||
\item{headers_lowercase}{if \code{TRUE} (the default) names in the \code{headers} data frame |
|||
element are converted to lower case} |
|||
} |
|||
\description{ |
|||
You can use the non- \verb{_raw} version on input you know for sure is plain text |
|||
} |
|||
\examples{ |
|||
paste0(c( |
|||
"HTTP/1.1 200 OK\r\n", |
|||
"Server: nginx/1.2.1\r\n", |
|||
"Content-Type: text/html\r\n", |
|||
"Content-Length: 8\r\n", |
|||
"Connection: keep-alive\r\n", |
|||
"\r\n", |
|||
"<html />" |
|||
), collapse = "") -> resp |
|||
|
|||
res <- parse_response(resp) |
|||
res <- parse_response_raw(charToRaw(resp)) |
|||
} |
@ -1,18 +0,0 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/RcppExports.R |
|||
\name{parse_url} |
|||
\alias{parse_url} |
|||
\title{Parse URLs} |
|||
\usage{ |
|||
parse_url(urls) |
|||
} |
|||
\arguments{ |
|||
\item{urls}{character vector of URLs} |
|||
} |
|||
\description{ |
|||
Parse URLs |
|||
} |
|||
\examples{ |
|||
URL <- "http://www.example.com/dir/subdir?param=1¶m=2;param\%20=\%20#fragment" |
|||
parse_url(URL) |
|||
} |
@ -1,12 +0,0 @@ |
|||
% 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} |
@ -1,23 +0,0 @@ |
|||
% Generated by roxygen2: do not edit by hand |
|||
% Please edit documentation in R/RcppExports.R |
|||
\name{read_file_raw} |
|||
\alias{read_file_raw} |
|||
\title{Read in a file, fast and raw} |
|||
\usage{ |
|||
read_file_raw(fil, buffer_size = 16384L) |
|||
} |
|||
\arguments{ |
|||
\item{fil}{file to read in (no path expansion is performed)} |
|||
|
|||
\item{buffer_size}{larger buffer sizes may speed up reading of |
|||
very large files. It can also hurt performance, and this |
|||
function reads in the entire file into memory, so a |
|||
large buffer size also means more (temporary) memory will |
|||
be allocated.} |
|||
} |
|||
\description{ |
|||
Read in a file, fast and raw |
|||
} |
|||
\examples{ |
|||
read_file_raw(system.file("extdat", "example.hdr", package = "construe")) |
|||
} |
@ -1,3 +0,0 @@ |
|||
*.o |
|||
*.so |
|||
*.dll |
@ -1,2 +0,0 @@ |
|||
CXX_STD = CXX11 |
|||
PKG_LIBS = -L. -lz -lpthread -pthread -std=c++11 |
@ -1,93 +0,0 @@ |
|||
// Generated by using Rcpp::compileAttributes() -> do not edit by hand
|
|||
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
|
|||
|
|||
#include <Rcpp.h> |
|||
|
|||
using namespace Rcpp; |
|||
|
|||
// parse_request
|
|||
List parse_request(String req, bool headers_lowercase); |
|||
RcppExport SEXP _construe_parse_request(SEXP reqSEXP, SEXP headers_lowercaseSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< String >::type req(reqSEXP); |
|||
Rcpp::traits::input_parameter< bool >::type headers_lowercase(headers_lowercaseSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(parse_request(req, headers_lowercase)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
// parse_request_raw
|
|||
List parse_request_raw(RawVector req, bool headers_lowercase); |
|||
RcppExport SEXP _construe_parse_request_raw(SEXP reqSEXP, SEXP headers_lowercaseSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< RawVector >::type req(reqSEXP); |
|||
Rcpp::traits::input_parameter< bool >::type headers_lowercase(headers_lowercaseSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(parse_request_raw(req, headers_lowercase)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
// parse_response
|
|||
List parse_response(String resp, bool headers_lowercase); |
|||
RcppExport SEXP _construe_parse_response(SEXP respSEXP, SEXP headers_lowercaseSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< String >::type resp(respSEXP); |
|||
Rcpp::traits::input_parameter< bool >::type headers_lowercase(headers_lowercaseSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(parse_response(resp, headers_lowercase)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
// parse_response_raw
|
|||
List parse_response_raw(RawVector resp, bool headers_lowercase); |
|||
RcppExport SEXP _construe_parse_response_raw(SEXP respSEXP, SEXP headers_lowercaseSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< RawVector >::type resp(respSEXP); |
|||
Rcpp::traits::input_parameter< bool >::type headers_lowercase(headers_lowercaseSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(parse_response_raw(resp, headers_lowercase)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
// parse_url
|
|||
DataFrame parse_url(std::vector < std::string > urls); |
|||
RcppExport SEXP _construe_parse_url(SEXP urlsSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< std::vector < std::string > >::type urls(urlsSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(parse_url(urls)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
// read_file_raw
|
|||
RawVector read_file_raw(CharacterVector fil, int buffer_size); |
|||
RcppExport SEXP _construe_read_file_raw(SEXP filSEXP, SEXP buffer_sizeSEXP) { |
|||
BEGIN_RCPP |
|||
Rcpp::RObject rcpp_result_gen; |
|||
Rcpp::RNGScope rcpp_rngScope_gen; |
|||
Rcpp::traits::input_parameter< CharacterVector >::type fil(filSEXP); |
|||
Rcpp::traits::input_parameter< int >::type buffer_size(buffer_sizeSEXP); |
|||
rcpp_result_gen = Rcpp::wrap(read_file_raw(fil, buffer_size)); |
|||
return rcpp_result_gen; |
|||
END_RCPP |
|||
} |
|||
|
|||
static const R_CallMethodDef CallEntries[] = { |
|||
{"_construe_parse_request", (DL_FUNC) &_construe_parse_request, 2}, |
|||
{"_construe_parse_request_raw", (DL_FUNC) &_construe_parse_request_raw, 2}, |
|||
{"_construe_parse_response", (DL_FUNC) &_construe_parse_response, 2}, |
|||
{"_construe_parse_response_raw", (DL_FUNC) &_construe_parse_response_raw, 2}, |
|||
{"_construe_parse_url", (DL_FUNC) &_construe_parse_url, 1}, |
|||
{"_construe_read_file_raw", (DL_FUNC) &_construe_read_file_raw, 2}, |
|||
{NULL, NULL, 0} |
|||
}; |
|||
|
|||
RcppExport void R_init_construe(DllInfo *dll) { |
|||
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); |
|||
R_useDynamicSymbols(dll, FALSE); |
|||
} |
@ -1,385 +0,0 @@ |
|||
#include <locale> |
|||
#include "request.h" |
|||
#include "httprequestparser.h" |
|||
|
|||
#include "response.h" |
|||
#include "httpresponseparser.h" |
|||
|
|||
#include "urlparser.h" |
|||
|
|||
#include <fstream> |
|||
#include <string> |
|||
#include <sys/stat.h> |
|||
#include <stdlib.h> |
|||
#include <Rcpp.h> |
|||
|
|||
using namespace Rcpp; |
|||
using namespace httpparser; |
|||
|
|||
std::string str_tolower(std::string str) { |
|||
std::transform( |
|||
str.begin(), str.end(), str.begin(), |
|||
[](unsigned char c){ return(std::tolower(c)); } |
|||
); |
|||
return(str); |
|||
} |
|||
|
|||
//' Parse an HTTP request
|
|||
//'
|
|||
//' You can use the non- `_raw` version on input you know for sure is plain text
|
|||
//'
|
|||
//' @param req HTTP request character string
|
|||
//' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame
|
|||
//' element are converted to lower case
|
|||
//' @export
|
|||
//' @examples
|
|||
//' paste0(c(
|
|||
//' "GET /uri.cgi HTTP/1.1\r\n",
|
|||
//' "User-Agent: Mozilla/5.0\r\n",
|
|||
//' "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n",
|
|||
//' "Host: 127.0.0.1\r\n", "\r\n"
|
|||
//' ), collapse = "") -> req
|
|||
//'
|
|||
//' res <- parse_request(req)
|
|||
//' res <- parse_request_raw(charToRaw(req))
|
|||
// [[Rcpp::export]]
|
|||
List parse_request(String req, bool headers_lowercase = true) { |
|||
|
|||
List l; |
|||
|
|||
Request request; |
|||
HttpRequestParser parser; |
|||
|
|||
const char *text = req.get_cstring(); |
|||
|
|||
HttpRequestParser::ParseResult res = parser.parse(request, (const unsigned char *)text, (const unsigned char *)(text + strlen(text))); |
|||
|
|||
if ((res == HttpRequestParser::ParsingCompleted) || (res == HttpRequestParser::ParsingIncompleted)) { |
|||
|
|||
StringVector names(request.headers.size()); |
|||
StringVector vals(request.headers.size()); |
|||
|
|||
R_xlen_t idx = 0; |
|||
for (std::vector<Request::HeaderItem>::const_iterator it = request.headers.begin(); it != request.headers.end(); ++it) { |
|||
names[idx] = headers_lowercase ? str_tolower(it->name) : it->name; |
|||
vals[idx++] = it->value; |
|||
} |
|||
|
|||
DataFrame headers = DataFrame::create( |
|||
_["name"] = names, |
|||
_["value"] = vals |
|||
); |
|||
|
|||
RawVector content(request.content.begin(), request.content.end()); |
|||
|
|||
l = List::create( |
|||
_["method"] = request.method, |
|||
_["uri"] = request.uri, |
|||
_["vers_maj"] = request.versionMajor, |
|||
_["vers_min"] = request.versionMinor, |
|||
_["keepalive"] = request.keepAlive, |
|||
_["headers"] = headers, |
|||
_["content"] = content |
|||
); |
|||
|
|||
l.attr("class") = CharacterVector::create("http_request", "list"); |
|||
|
|||
} else { |
|||
Rf_error("Parse error."); |
|||
} |
|||
|
|||
return(l); |
|||
|
|||
} |
|||
|
|||
//' @rdname parse_request
|
|||
//' @param req HTTP request character string
|
|||
//' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame
|
|||
//' element are converted to lower case
|
|||
//' @export
|
|||
// [[Rcpp::export]]
|
|||
List parse_request_raw(RawVector req, bool headers_lowercase = true) { |
|||
|
|||
List l; |
|||
|
|||
Request request; |
|||
HttpRequestParser parser; |
|||
|
|||
HttpRequestParser::ParseResult res = parser.parse(request, req.begin(), req.end()); |
|||
|
|||
if ((res == HttpRequestParser::ParsingCompleted) || (res == HttpRequestParser::ParsingIncompleted)) { |
|||
|
|||
StringVector names(request.headers.size()); |
|||
StringVector vals(request.headers.size()); |
|||
|
|||
R_xlen_t idx = 0; |
|||
for (std::vector<Request::HeaderItem>::const_iterator it = request.headers.begin(); it != request.headers.end(); ++it) { |
|||
names[idx] = headers_lowercase ? str_tolower(it->name) : it->name; |
|||
vals[idx++] = it->value; |
|||
} |
|||
|
|||
DataFrame headers = DataFrame::create( |
|||
_["name"] = names, |
|||
_["value"] = vals |
|||
); |
|||
|
|||
RawVector content(request.content.begin(), request.content.end()); |
|||
|
|||
l = List::create( |
|||
_["method"] = request.method, |
|||
_["uri"] = request.uri, |
|||
_["vers_maj"] = request.versionMajor, |
|||
_["vers_min"] = request.versionMinor, |
|||
_["keepalive"] = request.keepAlive, |
|||
_["headers"] = headers, |
|||
_["content"] = content |
|||
); |
|||
|
|||
l.attr("class") = CharacterVector::create("http_request", "list"); |
|||
|
|||
} else { |
|||
Rf_error("Parse error."); |
|||
} |
|||
|
|||
return(l); |
|||
|
|||
} |
|||
|
|||
//' Parse an HTTP response
|
|||
//'
|
|||
//' You can use the non- `_raw` version on input you know for sure is plain text
|
|||
//'
|
|||
//' @param resp HTTP response character string
|
|||
//' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame
|
|||
//' element are converted to lower case
|
|||
//' @export
|
|||
//' @examples
|
|||
//' paste0(c(
|
|||
//' "HTTP/1.1 200 OK\r\n",
|
|||
//' "Server: nginx/1.2.1\r\n",
|
|||
//' "Content-Type: text/html\r\n",
|
|||
//' "Content-Length: 8\r\n",
|
|||
//' "Connection: keep-alive\r\n",
|
|||
//' "\r\n",
|
|||
//' "<html />"
|
|||
//' ), collapse = "") -> resp
|
|||
//'
|
|||
//' res <- parse_response(resp)
|
|||
//' res <- parse_response_raw(charToRaw(resp))
|
|||
// [[Rcpp::export]]
|
|||
List parse_response(String resp, bool headers_lowercase = true) { |
|||
|
|||
List l; |
|||
|
|||
Response response; |
|||
HttpResponseParser parser; |
|||
|
|||
const char *text = resp.get_cstring(); |
|||
|
|||
HttpResponseParser::ParseResult res = parser.parse(response, (const unsigned char *)text, (const unsigned char *)(text + strlen(text))); |
|||
|
|||
if ((res == HttpResponseParser::ParsingCompleted) || (res == HttpResponseParser::ParsingIncompleted)) { |
|||
|
|||
StringVector names(response.headers.size()); |
|||
StringVector vals(response.headers.size()); |
|||
|
|||
R_xlen_t idx = 0; |
|||
for (std::vector<Response::HeaderItem>::const_iterator it = response.headers.begin(); it != response.headers.end(); ++it) { |
|||
names[idx] = headers_lowercase ? str_tolower(it->name) : it->name; |
|||
vals[idx++] = it->value; |
|||
} |
|||
|
|||
DataFrame headers = DataFrame::create( |
|||
_["name"] = names, |
|||
_["value"] = vals |
|||
); |
|||
|
|||
RawVector content(response.content.begin(), response.content.end()); |
|||
|
|||
l = List::create( |
|||
_["status_msg"] = response.status, |
|||
_["status_code"] = response.statusCode, |
|||
_["vers_maj"] = response.versionMajor, |
|||
_["vers_min"] = response.versionMinor, |
|||
_["keepalive"] = response.keepAlive, |
|||
_["headers"] = headers, |
|||
_["content"] = content |
|||
); |
|||
|
|||
l.attr("class") = CharacterVector::create("http_response", "list"); |
|||
|
|||
} else { |
|||
Rf_error("Parse error."); |
|||
} |
|||
|
|||
return(l); |
|||
|
|||
} |
|||
|
|||
//' @rdname parse_response
|
|||
//' @param resp HTTP request character string
|
|||
//' @param headers_lowercase if `TRUE` (the default) names in the `headers` data frame
|
|||
//' element are converted to lower case
|
|||
//' @export
|
|||
// [[Rcpp::export]]
|
|||
List parse_response_raw(RawVector resp, bool headers_lowercase = true) { |
|||
|
|||
List l; |
|||
|
|||
Response response; |
|||
HttpResponseParser parser; |
|||
|
|||
HttpResponseParser::ParseResult res = parser.parse(response, resp.begin(), resp.end()); |
|||
|
|||
if ((res == HttpResponseParser::ParsingCompleted) || (res == HttpResponseParser::ParsingIncompleted)) { |
|||
|
|||
StringVector names(response.headers.size()); |
|||
StringVector vals(response.headers.size()); |
|||
|
|||
R_xlen_t idx = 0; |
|||
for (std::vector<Response::HeaderItem>::const_iterator it = response.headers.begin(); it != response.headers.end(); ++it) { |
|||
names[idx] = headers_lowercase ? str_tolower(it->name) : it->name; |
|||
vals[idx++] = it->value; |
|||
} |
|||
|
|||
DataFrame headers = DataFrame::create( |
|||
_["name"] = names, |
|||
_["value"] = vals |
|||
); |
|||
|
|||
RawVector content(response.content.begin(), response.content.end()); |
|||
|
|||
l = List::create( |
|||
_["status_msg"] = response.status, |
|||
_["status_code"] = response.statusCode, |
|||
_["vers_maj"] = response.versionMajor, |
|||
_["vers_min"] = response.versionMinor, |
|||
_["keepalive"] = response.keepAlive, |
|||
_["headers"] = headers, |
|||
_["content"] = content |
|||
); |
|||
|
|||
l.attr("class") = CharacterVector::create("http_response", "list"); |
|||
|
|||
} else { |
|||
Rf_error("Parse error."); |
|||
} |
|||
|
|||
return(l); |
|||
|
|||
} |
|||
|
|||
//' Parse URLs
|
|||
//'
|
|||
//' @param urls character vector of URLs
|
|||
//' @export
|
|||
//' @examples
|
|||
//' URL <- "http://www.example.com/dir/subdir?param=1¶m=2;param%20=%20#fragment"
|
|||
//' parse_url(URL)
|
|||
// [[Rcpp::export]]
|
|||
DataFrame parse_url(std::vector < std::string > urls) { |
|||
|
|||
UrlParser parser; |
|||
|
|||
StringVector scheme(urls.size()); |
|||
StringVector username(urls.size()); |
|||
StringVector password(urls.size()); |
|||
StringVector hostname(urls.size()); |
|||
StringVector port(urls.size()); |
|||
StringVector path(urls.size()); |
|||
StringVector query(urls.size()); |
|||
StringVector fragment(urls.size()); |
|||
|
|||
UrlParser u; |
|||
|
|||
for (R_xlen_t idx=0; idx<(R_xlen_t)urls.size(); idx++) { |
|||
|
|||
int res = u.parse(urls[idx].c_str()); |
|||
|
|||
if (res) { |
|||
|
|||
scheme[idx] = u.url.scheme; |
|||
username[idx] = u.url.username; |
|||
password[idx] = u.url.password; |
|||
hostname[idx] = u.url.hostname; |
|||
port[idx] = u.url.port; |
|||
path[idx] = u.url.path; |
|||
query[idx] = u.url.query; |
|||
fragment[idx] = u.url.fragment; |
|||
port[idx] = u.url.port; |
|||
|
|||
} else { |
|||
|
|||
scheme[idx] = NA_STRING; |
|||
username[idx] = NA_STRING; |
|||
password[idx] = NA_STRING; |
|||
hostname[idx] = NA_STRING; |
|||
port[idx] = NA_STRING; |
|||
path[idx] = NA_STRING; |
|||
query[idx] = NA_STRING; |
|||
fragment[idx] = NA_STRING; |
|||
port[idx] = NA_STRING; |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
return(DataFrame::create( |
|||
_["scheme"] = scheme, |
|||
_["username"] = username, |
|||
_["password"] = password, |
|||
_["hostname"] = hostname, |
|||
_["port"] = port, |
|||
_["path"] = path, |
|||
_["query"] = query, |
|||
_["fragment"] = fragment |
|||
)); |
|||
|
|||
} |
|||
|
|||
//' Read in a file, fast and raw
|
|||
//'
|
|||
//' @param fil file to read in (no path expansion is performed)
|
|||
//' @param buffer_size larger buffer sizes may speed up reading of
|
|||
//' very large files. It can also hurt performance, and this
|
|||
//' function reads in the entire file into memory, so a
|
|||
//' large buffer size also means more (temporary) memory will
|
|||
//' be allocated.
|
|||
//' @export
|
|||
//' @examples
|
|||
//' read_file_raw(system.file("extdat", "example.hdr", package = "construe"))
|
|||
// [[Rcpp::export]]
|
|||
RawVector read_file_raw(CharacterVector fil, int buffer_size = 16384) { |
|||
|
|||
char buf[buffer_size]; |
|||
std::ifstream in; |
|||
in.rdbuf()->pubsetbuf(buf, sizeof buf); |
|||
|
|||
in.open(fil[0], std::ios::in | std::ios::binary); |
|||
|
|||
if (in) { |
|||
|
|||
#ifdef _WIN32 |
|||
std::string f = std::string(fil[0]); |
|||
wchar_t wfil[f.length()*2]; |
|||
std::mbstowcs(&wfil[0], f.c_str(), f.length()*2); |
|||
struct _stati64 st; |
|||
_wstati64(&wfil[0], &st); |
|||
#else |
|||
struct stat st; |
|||
stat(fil[0].begin(), &st); |
|||
#endif |
|||
|
|||
RawVector out(st.st_size); |
|||
|
|||
in.seekg(0, std::ios::beg); |
|||
in.read((char *)(out.begin()), st.st_size); |
|||
in.close(); |
|||
|
|||
return(out); |
|||
|
|||
} else { |
|||
return(R_NilValue); |
|||
} |
|||
|
|||
} |
@ -1,625 +0,0 @@ |
|||
/*
|
|||
* Copyright (C) Alex Nekipelov (alex@nekipelov.net) |
|||
* License: MIT |
|||
*/ |
|||
|
|||
#ifndef HTTPPARSER_REQUESTPARSER_H |
|||
#define HTTPPARSER_REQUESTPARSER_H |
|||
|
|||
#include <algorithm> |
|||
|
|||
#include <string.h> |
|||
#include <stdlib.h> |
|||
|
|||
#include "request.h" |
|||
|
|||
namespace httpparser |
|||
{ |
|||
|
|||
class HttpRequestParser |
|||
{ |
|||
public: |
|||
HttpRequestParser() |
|||
: state(RequestMethodStart), contentSize(0), |
|||
chunkSize(0), chunked(false) |
|||
|
|||
{ |
|||
} |
|||
|
|||
enum ParseResult { |
|||
ParsingCompleted, |
|||
ParsingIncompleted, |
|||
ParsingError |
|||
}; |
|||
|
|||
ParseResult parse(Request &req, const unsigned char *begin, const unsigned char *end) |
|||
{ |
|||
return consume(req, begin, end); |
|||
} |
|||
|
|||
private: |
|||
static bool checkIfConnection(const Request::HeaderItem &item) |
|||
{ |
|||
return strcasecmp(item.name.c_str(), "Connection") == 0; |
|||
} |
|||
|
|||
ParseResult consume(Request &req, const unsigned char *begin, const unsigned char *end) |
|||
{ |
|||
while( begin != end ) |
|||
{ |
|||
char input = *begin++; |
|||
|
|||
switch (state) |
|||
{ |
|||
case RequestMethodStart: |
|||
if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = RequestMethod; |
|||
req.method.push_back(input); |
|||
} |
|||
break; |
|||
case RequestMethod: |
|||
if( input == ' ' ) |
|||
{ |
|||
state = RequestUriStart; |
|||
} |
|||
else if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
req.method.push_back(input); |
|||
} |
|||
break; |
|||
case RequestUriStart: |
|||
if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = RequestUri; |
|||
req.uri.push_back(input); |
|||
} |
|||
break; |
|||
case RequestUri: |
|||
if( input == ' ' ) |
|||
{ |
|||
state = RequestHttpVersion_h; |
|||
} |
|||
else if (input == '\r') |
|||
{ |
|||
req.versionMajor = 0; |
|||
req.versionMinor = 9; |
|||
|
|||
return ParsingCompleted; |
|||
} |
|||
else if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
req.uri.push_back(input); |
|||
} |
|||
break; |
|||
case RequestHttpVersion_h: |
|||
if( input == 'H' ) |
|||
{ |
|||
state = RequestHttpVersion_ht; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_ht: |
|||
if( input == 'T' ) |
|||
{ |
|||
state = RequestHttpVersion_htt; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_htt: |
|||
if( input == 'T' ) |
|||
{ |
|||
state = RequestHttpVersion_http; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_http: |
|||
if( input == 'P' ) |
|||
{ |
|||
state = RequestHttpVersion_slash; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_slash: |
|||
if( input == '/' ) |
|||
{ |
|||
req.versionMajor = 0; |
|||
req.versionMinor = 0; |
|||
state = RequestHttpVersion_majorStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_majorStart: |
|||
if( isDigit(input) ) |
|||
{ |
|||
req.versionMajor = input - '0'; |
|||
state = RequestHttpVersion_major; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_major: |
|||
if( input == '.' ) |
|||
{ |
|||
state = RequestHttpVersion_minorStart; |
|||
} |
|||
else if (isDigit(input)) |
|||
{ |
|||
req.versionMajor = req.versionMajor * 10 + input - '0'; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_minorStart: |
|||
if( isDigit(input) ) |
|||
{ |
|||
req.versionMinor = input - '0'; |
|||
state = RequestHttpVersion_minor; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case RequestHttpVersion_minor: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ResponseHttpVersion_newLine; |
|||
} |
|||
else if( isDigit(input) ) |
|||
{ |
|||
req.versionMinor = req.versionMinor * 10 + input - '0'; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_newLine: |
|||
if( input == '\n' ) |
|||
{ |
|||
state = HeaderLineStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case HeaderLineStart: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ExpectingNewline_3; |
|||
} |
|||
else if( !req.headers.empty() && (input == ' ' || input == '\t') ) |
|||
{ |
|||
state = HeaderLws; |
|||
} |
|||
else if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
req.headers.push_back(Request::HeaderItem()); |
|||
req.headers.back().name.reserve(16); |
|||
req.headers.back().value.reserve(16); |
|||
req.headers.back().name.push_back(input); |
|||
state = HeaderName; |
|||
} |
|||
break; |
|||
case HeaderLws: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ExpectingNewline_2; |
|||
} |
|||
else if( input == ' ' || input == '\t' ) |
|||
{ |
|||
} |
|||
else if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = HeaderValue; |
|||
req.headers.back().value.push_back(input); |
|||
} |
|||
break; |
|||
case HeaderName: |
|||
if( input == ':' ) |
|||
{ |
|||
state = SpaceBeforeHeaderValue; |
|||
} |
|||
else if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
req.headers.back().name.push_back(input); |
|||
} |
|||
break; |
|||
case SpaceBeforeHeaderValue: |
|||
if( input == ' ' ) |
|||
{ |
|||
state = HeaderValue; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case HeaderValue: |
|||
if( input == '\r' ) |
|||
{ |
|||
if( req.method == "POST" || req.method == "PUT" ) |
|||
{ |
|||
Request::HeaderItem &h = req.headers.back(); |
|||
|
|||
if( strcasecmp(h.name.c_str(), "Content-Length") == 0 ) |
|||
{ |
|||
contentSize = atoi(h.value.c_str()); |
|||
req.content.reserve( contentSize ); |
|||
} |
|||
else if( strcasecmp(h.name.c_str(), "Transfer-Encoding") == 0 ) |
|||
{ |
|||
if(strcasecmp(h.value.c_str(), "chunked") == 0) |
|||
chunked = true; |
|||
} |
|||
} |
|||
state = ExpectingNewline_2; |
|||
} |
|||
else if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
req.headers.back().value.push_back(input); |
|||
} |
|||
break; |
|||
case ExpectingNewline_2: |
|||
if( input == '\n' ) |
|||
{ |
|||
state = HeaderLineStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ExpectingNewline_3: { |
|||
std::vector<Request::HeaderItem>::iterator it = std::find_if(req.headers.begin(), |
|||
req.headers.end(), |
|||
checkIfConnection); |
|||
|
|||
if( it != req.headers.end() ) |
|||
{ |
|||
if( strcasecmp(it->value.c_str(), "Keep-Alive") == 0 ) |
|||
{ |
|||
req.keepAlive = true; |
|||
} |
|||
else // == Close
|
|||
{ |
|||
req.keepAlive = false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if( req.versionMajor > 1 || (req.versionMajor == 1 && req.versionMinor == 1) ) |
|||
req.keepAlive = true; |
|||
} |
|||
|
|||
if( chunked ) |
|||
{ |
|||
state = ChunkSize; |
|||
} |
|||
else if( contentSize == 0 ) |
|||
{ |
|||
if( input == '\n') |
|||
return ParsingCompleted; |
|||
else |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = Post; |
|||
} |
|||
break; |
|||
} |
|||
case Post: |
|||
--contentSize; |
|||
req.content.push_back( input ); |
|||
|
|||
if( contentSize == 0 ) |
|||
{ |
|||
return ParsingCompleted; |
|||
} |
|||
break; |
|||
case ChunkSize: |
|||
if( isalnum(input) ) |
|||
{ |
|||
chunkSizeStr.push_back(input); |
|||
} |
|||
else if( input == ';' ) |
|||
{ |
|||
state = ChunkExtensionName; |
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkExtensionName: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '=' ) |
|||
{ |
|||
state = ChunkExtensionValue; |
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkExtensionValue: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine: |
|||
if( input == '\n' ) |
|||
{ |
|||
chunkSize = strtol(chunkSizeStr.c_str(), NULL, 16); |
|||
chunkSizeStr.clear(); |
|||
req.content.reserve(req.content.size() + chunkSize); |
|||
|
|||
if( chunkSize == 0 ) |
|||
state = ChunkSizeNewLine_2; |
|||
else |
|||
state = ChunkData; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine_2: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine_3; |
|||
} |
|||
else if( isalpha(input) ) |
|||
{ |
|||
state = ChunkTrailerName; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine_3: |
|||
if( input == '\n' ) |
|||
{ |
|||
return ParsingCompleted; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkTrailerName: |
|||
if( isalnum(input) ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == ':' ) |
|||
{ |
|||
state = ChunkTrailerValue; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkTrailerValue: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkData: |
|||
req.content.push_back(input); |
|||
|
|||
if( --chunkSize == 0 ) |
|||
{ |
|||
state = ChunkDataNewLine_1; |
|||
} |
|||
break; |
|||
case ChunkDataNewLine_1: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ChunkDataNewLine_2; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkDataNewLine_2: |
|||
if( input == '\n' ) |
|||
{ |
|||
state = ChunkSize; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
default: |
|||
return ParsingError; |
|||
} |
|||
} |
|||
|
|||
return ParsingIncompleted; |
|||
} |
|||
|
|||
// Check if a byte is an HTTP character.
|
|||
inline bool isChar(int c) |
|||
{ |
|||
return c >= 0 && c <= 127; |
|||
} |
|||
|
|||
// Check if a byte is an HTTP control character.
|
|||
inline bool isControl(int c) |
|||
{ |
|||
return (c >= 0 && c <= 31) || (c == 127); |
|||
} |
|||
|
|||
// Check if a byte is defined as an HTTP special character.
|
|||
inline bool isSpecial(int c) |
|||
{ |
|||
switch (c) |
|||
{ |
|||
case '(': case ')': case '<': case '>': case '@': |
|||
case ',': case ';': case ':': case '\\': case '"': |
|||
case '/': case '[': case ']': case '?': case '=': |
|||
case '{': case '}': case ' ': case '\t': |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// Check if a byte is a digit.
|
|||
inline bool isDigit(int c) |
|||
{ |
|||
return c >= '0' && c <= '9'; |
|||
} |
|||
|
|||
// The current state of the parser.
|
|||
enum State |
|||
{ |
|||
RequestMethodStart, |
|||
RequestMethod, |
|||
RequestUriStart, |
|||
RequestUri, |
|||
RequestHttpVersion_h, |
|||
RequestHttpVersion_ht, |
|||
RequestHttpVersion_htt, |
|||
RequestHttpVersion_http, |
|||
RequestHttpVersion_slash, |
|||
RequestHttpVersion_majorStart, |
|||
RequestHttpVersion_major, |
|||
RequestHttpVersion_minorStart, |
|||
RequestHttpVersion_minor, |
|||
|
|||
ResponseStatusStart, |
|||
ResponseHttpVersion_ht, |
|||
ResponseHttpVersion_htt, |
|||
ResponseHttpVersion_http, |
|||
ResponseHttpVersion_slash, |
|||
ResponseHttpVersion_majorStart, |
|||
ResponseHttpVersion_major, |
|||
ResponseHttpVersion_minorStart, |
|||
ResponseHttpVersion_minor, |
|||
ResponseHttpVersion_spaceAfterVersion, |
|||
ResponseHttpVersion_statusCodeStart, |
|||
ResponseHttpVersion_spaceAfterStatusCode, |
|||
ResponseHttpVersion_statusTextStart, |
|||
ResponseHttpVersion_newLine, |
|||
|
|||
HeaderLineStart, |
|||
HeaderLws, |
|||
HeaderName, |
|||
SpaceBeforeHeaderValue, |
|||
HeaderValue, |
|||
ExpectingNewline_2, |
|||
ExpectingNewline_3, |
|||
|
|||
Post, |
|||
ChunkSize, |
|||
ChunkExtensionName, |
|||
ChunkExtensionValue, |
|||
ChunkSizeNewLine, |
|||
ChunkSizeNewLine_2, |
|||
ChunkSizeNewLine_3, |
|||
ChunkTrailerName, |
|||
ChunkTrailerValue, |
|||
|
|||
ChunkDataNewLine_1, |
|||
ChunkDataNewLine_2, |
|||
ChunkData, |
|||
} state; |
|||
|
|||
size_t contentSize; |
|||
std::string chunkSizeStr; |
|||
size_t chunkSize; |
|||
bool chunked; |
|||
}; |
|||
|
|||
} // namespace httpparser
|
|||
|
|||
#endif // LIBAHTTP_REQUESTPARSER_H
|
@ -1,618 +0,0 @@ |
|||
/*
|
|||
* Copyright (C) Alex Nekipelov (alex@nekipelov.net) |
|||
* License: MIT |
|||
*/ |
|||
|
|||
#ifndef HTTPPARSER_RESPONSEPARSER_H |
|||
#define HTTPPARSER_RESPONSEPARSER_H |
|||
|
|||
#include <algorithm> |
|||
|
|||
#include <string.h> |
|||
#include <stdlib.h> |
|||
|
|||
#include "response.h" |
|||
|
|||
namespace httpparser |
|||
{ |
|||
|
|||
class HttpResponseParser |
|||
{ |
|||
public: |
|||
HttpResponseParser() |
|||
: state(ResponseStatusStart), |
|||
contentSize(0), |
|||
chunkSize(0), |
|||
chunked(false) |
|||
{ |
|||
} |
|||
|
|||
enum ParseResult { |
|||
ParsingCompleted, |
|||
ParsingIncompleted, |
|||
ParsingError |
|||
}; |
|||
|
|||
ParseResult parse(Response &resp, const unsigned char *begin, const unsigned char *end) |
|||
{ |
|||
return consume(resp, begin, end); |
|||
} |
|||
|
|||
private: |
|||
static bool checkIfConnection(const Response::HeaderItem &item) |
|||
{ |
|||
return strcasecmp(item.name.c_str(), "Connection") == 0; |
|||
} |
|||
|
|||
ParseResult consume(Response &resp, const unsigned char *begin, const unsigned char *end) |
|||
{ |
|||
while( begin != end ) |
|||
{ |
|||
char input = *begin++; |
|||
|
|||
switch (state) |
|||
{ |
|||
case ResponseStatusStart: |
|||
if( input != 'H' ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = ResponseHttpVersion_ht; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_ht: |
|||
if( input == 'T' ) |
|||
{ |
|||
state = ResponseHttpVersion_htt; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_htt: |
|||
if( input == 'T' ) |
|||
{ |
|||
state = ResponseHttpVersion_http; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_http: |
|||
if( input == 'P' ) |
|||
{ |
|||
state = ResponseHttpVersion_slash; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_slash: |
|||
if( input == '/' ) |
|||
{ |
|||
resp.versionMajor = 0; |
|||
resp.versionMinor = 0; |
|||
state = ResponseHttpVersion_majorStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_majorStart: |
|||
if( isDigit(input) ) |
|||
{ |
|||
resp.versionMajor = input - '0'; |
|||
state = ResponseHttpVersion_major; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_major: |
|||
if( input == ' ' ) { |
|||
resp.versionMinor = 0; |
|||
state = ResponseHttpVersion_statusCodeStart; |
|||
} else if( input == '.' ) { |
|||
state = ResponseHttpVersion_minorStart; |
|||
} else if( isDigit(input) ) { |
|||
resp.versionMajor = resp.versionMajor * 10 + input - '0'; |
|||
} else { |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_minorStart: |
|||
if( input == ' ' ) { |
|||
resp.versionMinor = 0; |
|||
state = ResponseHttpVersion_statusCodeStart; |
|||
} else if( isDigit(input) ) { |
|||
resp.versionMinor = input - '0'; |
|||
state = ResponseHttpVersion_minor; |
|||
} else{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_minor: |
|||
if( input == ' ') { |
|||
state = ResponseHttpVersion_statusCodeStart; |
|||
resp.versionMinor = 0; |
|||
} |
|||
else if( isDigit(input) ) |
|||
{ |
|||
resp.versionMinor = resp.versionMinor * 10 + input - '0'; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_statusCodeStart: |
|||
// printf("ResponseHttpVersion_statusCodeStart\n\n");
|
|||
if( isDigit(input) ) |
|||
{ |
|||
// printf(" - digit - ResponseHttpVersion_statusCodeStart\n\n");
|
|||
|
|||
resp.statusCode = input - '0'; |
|||
state = ResponseHttpVersion_statusCode; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_statusCode: |
|||
// printf("ResponseHttpVersion_statusCode\n\n");
|
|||
if( isDigit(input) ) |
|||
{ |
|||
// printf(" - digit - ResponseHttpVersion_statusCode\n\n");
|
|||
|
|||
resp.statusCode = resp.statusCode * 10 + input - '0'; |
|||
} |
|||
else |
|||
{ |
|||
if( resp.statusCode < 100 || resp.statusCode > 999 ) { |
|||
return ParsingError; |
|||
} else if( input == ' ' ) { |
|||
// printf(" - SPACE - ResponseHttpVersion_statusCode\n\n");
|
|||
state = ResponseHttpVersion_statusTextStart; |
|||
} else if( input == '\r' ) { |
|||
// printf(" - CR - ResponseHttpVersion_statusCode\n\n");
|
|||
resp.status = ""; |
|||
state = ResponseHttpVersion_newLine; |
|||
} else { |
|||
return ParsingError; |
|||
} |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_statusTextStart: |
|||
// printf("ResponseHttpVersion_statusTextStart\n\n");
|
|||
if( input == '\r' ) { |
|||
// printf(" - CR - ResponseHttpVersion_statusTextStart\n\n");
|
|||
resp.status = ""; |
|||
state = ResponseHttpVersion_newLine; |
|||
} else if( isChar(input) ) { |
|||
// printf(" - char - ResponseHttpVersion_statusTextStart\n\n");
|
|||
resp.status += input; |
|||
state = ResponseHttpVersion_statusText; |
|||
} else { |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_statusText: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ResponseHttpVersion_newLine; |
|||
} |
|||
else if( isChar(input) ) |
|||
{ |
|||
resp.status += input; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ResponseHttpVersion_newLine: |
|||
// printf("ResponseHttpVersion_newLine\n\n");
|
|||
if( input == '\n' ) |
|||
{ |
|||
// printf(" - NL - ResponseHttpVersion_newLine\n\n");
|
|||
state = HeaderLineStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case HeaderLineStart: |
|||
// printf("HeaderLineStart\n\n");
|
|||
if( input == '\r' ) |
|||
{ |
|||
state = ExpectingNewline_3; |
|||
} |
|||
else if( !resp.headers.empty() && (input == ' ' || input == '\t') ) |
|||
{ |
|||
state = HeaderLws; |
|||
} |
|||
else if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
resp.headers.push_back(Response::HeaderItem()); |
|||
resp.headers.back().name.reserve(16); |
|||
resp.headers.back().value.reserve(16); |
|||
resp.headers.back().name.push_back(input); |
|||
state = HeaderName; |
|||
} |
|||
break; |
|||
case HeaderLws: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ExpectingNewline_2; |
|||
} |
|||
else if( input == ' ' || input == '\t' ) |
|||
{ |
|||
} |
|||
else if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
state = HeaderValue; |
|||
resp.headers.back().value.push_back(input); |
|||
} |
|||
break; |
|||
case HeaderName: |
|||
if( input == ':' ) |
|||
{ |
|||
state = SpaceBeforeHeaderValue; |
|||
} |
|||
else if( !isChar(input) || isControl(input) || isSpecial(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
resp.headers.back().name.push_back(input); |
|||
} |
|||
break; |
|||
case SpaceBeforeHeaderValue: |
|||
if( input == ' ' ) |
|||
{ |
|||
state = HeaderValue; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case HeaderValue: |
|||
if( input == '\r' ) |
|||
{ |
|||
Response::HeaderItem &h = resp.headers.back(); |
|||
|
|||
if( strcasecmp(h.name.c_str(), "Content-Length") == 0 ) |
|||
{ |
|||
contentSize = atoi(h.value.c_str()); |
|||
resp.content.reserve( contentSize ); |
|||
} |
|||
else if( strcasecmp(h.name.c_str(), "Transfer-Encoding") == 0 ) |
|||
{ |
|||
if(strcasecmp(h.value.c_str(), "chunked") == 0) |
|||
chunked = true; |
|||
} |
|||
state = ExpectingNewline_2; |
|||
} |
|||
else if( isControl(input) ) |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
else |
|||
{ |
|||
resp.headers.back().value.push_back(input); |
|||
} |
|||
break; |
|||
case ExpectingNewline_2: |
|||
if( input == '\n' ) |
|||
{ |
|||
state = HeaderLineStart; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ExpectingNewline_3: { |
|||
std::vector<Response::HeaderItem>::iterator it = std::find_if(resp.headers.begin(), |
|||
resp.headers.end(), |
|||
checkIfConnection); |
|||
|
|||
if( it != resp.headers.end() ) |
|||
{ |
|||
if( strcasecmp(it->value.c_str(), "Keep-Alive") == 0 ) |
|||
{ |
|||
resp.keepAlive = true; |
|||
} |
|||
else // == Close
|
|||
{ |
|||
resp.keepAlive = false; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if( resp.versionMajor > 1 || (resp.versionMajor == 1 && resp.versionMinor == 1) ) |
|||
resp.keepAlive = true; |
|||
} |
|||
|
|||
if( chunked ) |
|||
{ |
|||
state = ChunkSize; |
|||
} |
|||
else if( contentSize == 0 ) |
|||
{ |
|||
if( input == '\n') |
|||
return ParsingCompleted; |
|||
else |
|||
return ParsingError; |
|||
} |
|||
|
|||
else |
|||
{ |
|||
state = Post; |
|||
} |
|||
break; |
|||
} |
|||
case Post: |
|||
--contentSize; |
|||
resp.content.push_back(input); |
|||
|
|||
if( contentSize == 0 ) |
|||
{ |
|||
return ParsingCompleted; |
|||
} |
|||
break; |
|||
case ChunkSize: |
|||
if( isalnum(input) ) |
|||
{ |
|||
chunkSizeStr.push_back(input); |
|||
} |
|||
else if( input == ';' ) |
|||
{ |
|||
state = ChunkExtensionName; |
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkExtensionName: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '=' ) |
|||
{ |
|||
state = ChunkExtensionValue; |
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkExtensionValue: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine: |
|||
if( input == '\n' ) |
|||
{ |
|||
chunkSize = strtol(chunkSizeStr.c_str(), NULL, 16); |
|||
chunkSizeStr.clear(); |
|||
resp.content.reserve(resp.content.size() + chunkSize); |
|||
|
|||
if( chunkSize == 0 ) |
|||
state = ChunkSizeNewLine_2; |
|||
else |
|||
state = ChunkData; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine_2: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine_3; |
|||
} |
|||
else if( isalpha(input) ) |
|||
{ |
|||
state = ChunkTrailerName; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkSizeNewLine_3: |
|||
if( input == '\n' ) |
|||
{ |
|||
return ParsingCompleted; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkTrailerName: |
|||
if( isalnum(input) ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == ':' ) |
|||
{ |
|||
state = ChunkTrailerValue; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkTrailerValue: |
|||
if( isalnum(input) || input == ' ' ) |
|||
{ |
|||
// skip
|
|||
} |
|||
else if( input == '\r' ) |
|||
{ |
|||
state = ChunkSizeNewLine; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkData: |
|||
resp.content.push_back(input); |
|||
|
|||
if( --chunkSize == 0 ) |
|||
{ |
|||
state = ChunkDataNewLine_1; |
|||
} |
|||
break; |
|||
case ChunkDataNewLine_1: |
|||
if( input == '\r' ) |
|||
{ |
|||
state = ChunkDataNewLine_2; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
case ChunkDataNewLine_2: |
|||
if( input == '\n' ) |
|||
{ |
|||
state = ChunkSize; |
|||
} |
|||
else |
|||
{ |
|||
return ParsingError; |
|||
} |
|||
break; |
|||
default: |
|||
return ParsingError; |
|||
} |
|||
} |
|||
|
|||
return ParsingIncompleted; |
|||
} |
|||
|
|||
// Check if a byte is an HTTP character.
|
|||
inline bool isChar(int c) |
|||
{ |
|||
return c >= 0 && c <= 127; |
|||
} |
|||
|
|||
// Check if a byte is an HTTP control character.
|
|||
inline bool isControl(int c) |
|||
{ |
|||
return (c >= 0 && c <= 31) || (c == 127); |
|||
} |
|||
|
|||
// Check if a byte is defined as an HTTP special character.
|
|||
inline bool isSpecial(int c) |
|||
{ |
|||
switch (c) |
|||
{ |
|||
case '(': case ')': case '<': case '>': case '@': |
|||
case ',': case ';': case ':': case '\\': case '"': |
|||
case '/': case '[': case ']': case '?': case '=': |
|||
case '{': case '}': case ' ': case '\t': |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// Check if a byte is a digit.
|
|||
inline bool isDigit(int c) |
|||
{ |
|||
return c >= '0' && c <= '9'; |
|||
} |
|||
|
|||
// The current state of the parser.
|
|||
enum State |
|||
{ |
|||
ResponseStatusStart, |
|||
ResponseHttpVersion_ht, |
|||
ResponseHttpVersion_htt, |
|||
ResponseHttpVersion_http, |
|||
ResponseHttpVersion_slash, |
|||
ResponseHttpVersion_majorStart, |
|||
ResponseHttpVersion_major, |
|||
ResponseHttpVersion_minorStart, |
|||
ResponseHttpVersion_minor, |
|||
ResponseHttpVersion_statusCodeStart, |
|||
ResponseHttpVersion_statusCode, |
|||
ResponseHttpVersion_statusTextStart, |
|||
ResponseHttpVersion_statusText, |
|||
ResponseHttpVersion_newLine, |
|||
HeaderLineStart, |
|||
HeaderLws, |
|||
HeaderName, |
|||
SpaceBeforeHeaderValue, |
|||
HeaderValue, |
|||
ExpectingNewline_2, |
|||
ExpectingNewline_3, |
|||
Post, |
|||
ChunkSize, |
|||
ChunkExtensionName, |
|||
ChunkExtensionValue, |
|||
ChunkSizeNewLine, |
|||
ChunkSizeNewLine_2, |
|||
ChunkSizeNewLine_3, |
|||
ChunkTrailerName, |
|||
ChunkTrailerValue, |
|||
|
|||
ChunkDataNewLine_1, |
|||
ChunkDataNewLine_2, |
|||
ChunkData, |
|||
} state; |
|||
|
|||
size_t contentSize; |
|||
std::string chunkSizeStr; |
|||
size_t chunkSize; |
|||
bool chunked; |
|||
}; |
|||
|
|||
} // namespace httpparser
|
|||
|
|||
#endif // HTTPPARSER_RESPONSEPARSER_H
|
@ -1,57 +0,0 @@ |
|||
/*
|
|||
* Copyright (C) Alex Nekipelov (alex@nekipelov.net) |
|||
* License: MIT |
|||
*/ |
|||
|
|||
#ifndef HTTPPARSER_REQUEST_H |
|||
#define HTTPPARSER_REQUEST_H |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
#include <sstream> |
|||
|
|||
namespace httpparser |
|||
{ |
|||
|
|||
struct Request { |
|||
Request() |
|||
: versionMajor(0), versionMinor(0), keepAlive(false) |
|||
{} |
|||
|
|||
struct HeaderItem |
|||
{ |
|||
std::string name; |
|||
std::string value; |
|||
}; |
|||
|
|||
std::string method; |
|||
std::string uri; |
|||
int versionMajor; |
|||
int versionMinor; |
|||
std::vector<HeaderItem> headers; |
|||
std::vector<unsigned char> content; |
|||
bool keepAlive; |
|||
|
|||
std::string inspect() const |
|||
{ |
|||
std::stringstream stream; |
|||
stream << method << " " << uri << " HTTP/" |
|||
<< versionMajor << "." << versionMinor << "\n"; |
|||
|
|||
for(std::vector<Request::HeaderItem>::const_iterator it = headers.begin(); |
|||
it != headers.end(); ++it) |
|||
{ |
|||
stream << it->name << ": " << it->value << "\n"; |
|||
} |
|||
|
|||
std::string data(content.begin(), content.end()); |
|||
stream << data << "\n"; |
|||
stream << "+ keep-alive: " << keepAlive << "\n";; |
|||
return stream.str(); |
|||
} |
|||
}; |
|||
|
|||
} // namespace httpparser
|
|||
|
|||
|
|||
#endif // HTTPPARSER_REQUEST_H
|
@ -1,57 +0,0 @@ |
|||
/*
|
|||
* Copyright (C) Alex Nekipelov (alex@nekipelov.net) |
|||
* License: MIT |
|||
*/ |
|||
|
|||
#ifndef HTTPPARSER_RESPONSE_H |
|||
#define HTTPPARSER_RESPONSE_H |
|||
|
|||
#include <string> |
|||
#include <vector> |
|||
#include <sstream> |
|||
|
|||
namespace httpparser |
|||
{ |
|||
|
|||
struct Response { |
|||
Response() |
|||
: versionMajor(0), versionMinor(0), keepAlive(false), statusCode(0) |
|||
{} |
|||
|
|||
struct HeaderItem |
|||
{ |
|||
std::string name; |
|||
std::string value; |
|||
}; |
|||
|
|||
int versionMajor; |
|||
int versionMinor; |
|||
std::vector<HeaderItem> headers; |
|||
std::vector<unsigned char> content; |
|||
bool keepAlive; |
|||
|
|||
unsigned int statusCode; |
|||
std::string status; |
|||
|
|||
std::string inspect() const |
|||
{ |
|||
std::stringstream stream; |
|||
stream << "HTTP/" << versionMajor << "." << versionMinor |
|||
<< " " << statusCode << " " << status << "\n"; |
|||
|
|||
for(std::vector<Response::HeaderItem>::const_iterator it = headers.begin(); |
|||
it != headers.end(); ++it) |
|||
{ |
|||
stream << it->name << ": " << it->value << "\n"; |
|||
} |
|||
|
|||
std::string data(content.begin(), content.end()); |
|||
stream << data << "\n"; |
|||
return stream.str(); |
|||
} |
|||
}; |
|||
|
|||
} // namespace httpparser
|
|||
|
|||
#endif // HTTPPARSER_RESPONSE_H
|
|||
|
@ -1,387 +0,0 @@ |
|||
/*
|
|||
* Copyright (C) Alex Nekipelov (alex@nekipelov.net) |
|||
* License: MIT |
|||
*/ |
|||
|
|||
#ifndef HTTPPARSER_URLPARSER_H |
|||
#define HTTPPARSER_URLPARSER_H |
|||
|
|||
#include <string> |
|||
#include <stdlib.h> |
|||
#include <stdint.h> |
|||
#include <assert.h> |
|||
|
|||
namespace httpparser |
|||
{ |
|||
|
|||
class UrlParser |
|||
{ |
|||
public: |
|||
UrlParser() |
|||
: valid(false) |
|||
{ |
|||
} |
|||
|
|||
explicit UrlParser(const std::string &url) |
|||
: valid(true) |
|||
{ |
|||
parse(url); |
|||
} |
|||
|
|||
bool parse(const std::string &str) |
|||
{ |
|||
url = Url(); |
|||
parse_(str); |
|||
|
|||
return isValid(); |
|||
} |
|||
|
|||
bool isValid() const |
|||
{ |
|||
return valid; |
|||
} |
|||
|
|||
std::string scheme() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.scheme; |
|||
} |
|||
|
|||
std::string username() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.username; |
|||
} |
|||
|
|||
std::string password() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.password; |
|||
} |
|||
|
|||
std::string hostname() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.hostname; |
|||
} |
|||
|
|||
std::string port() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.port; |
|||
} |
|||
|
|||
std::string path() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.path; |
|||
} |
|||
|
|||
std::string query() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.query; |
|||
} |
|||
|
|||
std::string fragment() const |
|||
{ |
|||
assert( isValid() ); |
|||
return url.fragment; |
|||
} |
|||
|
|||
uint16_t httpPort() const |
|||
{ |
|||
const uint16_t defaultHttpPort = 80; |
|||
const uint16_t defaultHttpsPort = 443; |
|||
|
|||
assert( isValid() ); |
|||
|
|||
if( url.port.empty() ) |
|||
{ |
|||
if( scheme() == "https" ) |
|||
return defaultHttpsPort; |
|||
else |
|||
return defaultHttpPort; |
|||
} |
|||
else |
|||
{ |
|||
return url.integerPort; |
|||
} |
|||
} |
|||
|
|||
struct Url |
|||
{ |
|||
Url() : integerPort(0) |
|||
{} |
|||
|
|||
std::string scheme; |
|||
std::string username; |
|||
std::string password; |
|||
std::string hostname; |
|||
std::string port; |
|||
std::string path; |
|||
std::string query; |
|||
std::string fragment; |
|||
uint16_t integerPort; |
|||
} url; |
|||
|
|||
private: |
|||
bool isUnreserved(char ch) const |
|||
{ |
|||
if( isalnum(ch) ) |
|||
return true; |
|||
|
|||
switch(ch) |
|||
{ |
|||
case '-': |
|||
case '.': |
|||
case '_': |
|||
case '~': |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
void parse_(const std::string &str) |
|||
{ |
|||
enum { |
|||
Scheme, |
|||
SlashAfterScheme1, |
|||
SlashAfterScheme2, |
|||
UsernameOrHostname, |
|||
Password, |
|||
Hostname, |
|||
IPV6Hostname, |
|||
PortOrPassword, |
|||
Port, |
|||
Path, |
|||
Query, |
|||
Fragment |
|||
} state = Scheme; |
|||
|
|||
std::string usernameOrHostname; |
|||
std::string portOrPassword; |
|||
|
|||
valid = true; |
|||
url.path = "/"; |
|||
url.integerPort = 0; |
|||
|
|||
for(size_t i = 0; i < str.size() && valid; ++i) |
|||
{ |
|||
char ch = str[i]; |
|||
|
|||
switch(state) |
|||
{ |
|||
case Scheme: |
|||
if( isalnum(ch) || ch == '+' || ch == '-' || ch == '.') |
|||
{ |
|||
url.scheme += ch; |
|||
} |
|||
else if( ch == ':' ) |
|||
{ |
|||
state = SlashAfterScheme1; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case SlashAfterScheme1: |
|||
if( ch == '/' ) |
|||
{ |
|||
state = SlashAfterScheme2; |
|||
} |
|||
else if( isalnum(ch) ) |
|||
{ |
|||
usernameOrHostname = ch; |
|||
state = UsernameOrHostname; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case SlashAfterScheme2: |
|||
if( ch == '/' ) |
|||
{ |
|||
state = UsernameOrHostname; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case UsernameOrHostname: |
|||
if( isUnreserved(ch) || ch == '%' ) |
|||
{ |
|||
usernameOrHostname += ch; |
|||
} |
|||
else if( ch == ':' ) |
|||
{ |
|||
state = PortOrPassword; |
|||
} |
|||
else if( ch == '@' ) |
|||
{ |
|||
state = Hostname; |
|||
std::swap(url.username, usernameOrHostname); |
|||
} |
|||
else if( ch == '/' ) |
|||
{ |
|||
state = Path; |
|||
std::swap(url.hostname, usernameOrHostname); |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case Password: |
|||
if( isalnum(ch) || ch == '%' ) |
|||
{ |
|||
url.password += ch; |
|||
} |
|||
else if( ch == '@' ) |
|||
{ |
|||
state = Hostname; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case Hostname: |
|||
if( ch == '[' && url.hostname.empty() ) |
|||
{ |
|||
state = IPV6Hostname; |
|||
} |
|||
else if(isUnreserved(ch) || ch == '%') |
|||
{ |
|||
url.hostname += ch; |
|||
} |
|||
else if(ch == ':') |
|||
{ |
|||
state = Port; |
|||
} |
|||
else if(ch == '/') |
|||
{ |
|||
state = Path; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case IPV6Hostname: |
|||
break; |
|||
case PortOrPassword: |
|||
if( isdigit(ch) ) |
|||
{ |
|||
portOrPassword += ch; |
|||
} |
|||
else if( ch == '/' ) |
|||
{ |
|||
std::swap(url.hostname, usernameOrHostname); |
|||
std::swap(url.port, portOrPassword); |
|||
url.integerPort = atoi(url.port.c_str()); |
|||
state = Path; |
|||
} |
|||
else if( isalnum(ch) || ch == '%' ) |
|||
{ |
|||
std::swap(url.username, usernameOrHostname); |
|||
std::swap(url.password, portOrPassword); |
|||
url.password += ch; |
|||
state = Password; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case Port: |
|||
if( isdigit(ch) ) |
|||
{ |
|||
portOrPassword += ch; |
|||
} |
|||
else if(ch == '/') |
|||
{ |
|||
std::swap(url.port, portOrPassword); |
|||
url.integerPort = atoi(url.port.c_str()); |
|||
state = Path; |
|||
} |
|||
else |
|||
{ |
|||
valid = false; |
|||
url = Url(); |
|||
} |
|||
break; |
|||
case Path: |
|||
if( ch == '#') |
|||
{ |
|||
state = Fragment; |
|||
} |
|||
else if( ch == '?' ) |
|||
{ |
|||
state = Query; |
|||
} |
|||
else |
|||
{ |
|||
url.path += ch; |
|||
} |
|||
break; |
|||
case Query: |
|||
if( ch == '#') |
|||
{ |
|||
state = Fragment; |
|||
} |
|||
else if( ch == '?' ) |
|||
{ |
|||
state = Query; |
|||
} |
|||
else |
|||
{ |
|||
url.query += ch; |
|||
} |
|||
break; |
|||
case Fragment: |
|||
url.fragment += ch; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
assert(portOrPassword.empty()); |
|||
|
|||
if( !usernameOrHostname.empty() ) |
|||
url.hostname = usernameOrHostname; |
|||
} |
|||
|
|||
|
|||
bool valid; |
|||
//
|
|||
// struct Url
|
|||
// {
|
|||
// Url() : integerPort(0)
|
|||
// {}
|
|||
//
|
|||
// std::string scheme;
|
|||
// std::string username;
|
|||
// std::string password;
|
|||
// std::string hostname;
|
|||
// std::string port;
|
|||
// std::string path;
|
|||
// std::string query;
|
|||
// std::string fragment;
|
|||
// uint16_t integerPort;
|
|||
// } url;
|
|||
}; |
|||
|
|||
} // namespace httpparser
|
|||
|
|||
#endif // HTTPPARSER_URLPARSER_H
|
@ -1,5 +0,0 @@ |
|||
|
|||
if ( requireNamespace("tinytest", quietly=TRUE) ){ |
|||
tinytest::test_package("construe") |
|||
} |
|||
|
Loading…
Reference in new issue