run vfmt on http, net, sync, strconv
parent
28ecfb231d
commit
848cd3cb3e
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
import http
|
import http
|
||||||
import json
|
import json
|
||||||
import sync
|
import sync
|
||||||
|
@ -12,15 +11,15 @@ const (
|
||||||
|
|
||||||
struct Story {
|
struct Story {
|
||||||
title string
|
title string
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Fetcher {
|
struct Fetcher {
|
||||||
mut:
|
mut:
|
||||||
mu &sync.Mutex
|
mu &sync.Mutex
|
||||||
ids []int
|
ids []int
|
||||||
cursor int
|
cursor int
|
||||||
wg &sync.WaitGroup
|
wg &sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (f mut Fetcher) fetch() {
|
fn (f mut Fetcher) fetch() {
|
||||||
|
@ -37,7 +36,7 @@ fn (f mut Fetcher) fetch() {
|
||||||
println('failed to fetch data from /v0/item/${id}.json')
|
println('failed to fetch data from /v0/item/${id}.json')
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
story := json.decode(Story, resp.text) or {
|
story := json.decode(Story,resp.text) or {
|
||||||
println('failed to decode a story')
|
println('failed to decode a story')
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
@ -52,22 +51,25 @@ fn main() {
|
||||||
println('failed to fetch data from /v0/topstories.json')
|
println('failed to fetch data from /v0/topstories.json')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mut ids := json.decode([]int, resp.text) or {
|
mut ids := json.decode([]int,resp.text) or {
|
||||||
println('failed to decode topstories.json')
|
println('failed to decode topstories.json')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ids.len > 10 {
|
if ids.len > 10 {
|
||||||
// ids = ids[:10]
|
// ids = ids[:10]
|
||||||
mut tmp := [0].repeat(10)
|
mut tmp := [0].repeat(10)
|
||||||
for i := 0 ; i < 10 ; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
tmp[i] = ids[i]
|
tmp[i] = ids[i]
|
||||||
}
|
}
|
||||||
ids = tmp
|
ids = tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.new_waitgroup()
|
wg := sync.new_waitgroup()
|
||||||
mtx := sync.new_mutex()
|
mtx := sync.new_mutex()
|
||||||
mut fetcher := &Fetcher{ids: ids mu: 0 wg: 0}
|
mut fetcher := &Fetcher{
|
||||||
|
ids: ids
|
||||||
|
mu: 0
|
||||||
|
wg: 0
|
||||||
|
}
|
||||||
fetcher.mu = &mtx
|
fetcher.mu = &mtx
|
||||||
fetcher.wg = &wg
|
fetcher.wg = &wg
|
||||||
fetcher.wg.add(ids.len)
|
fetcher.wg.add(ids.len)
|
||||||
|
|
|
@ -181,9 +181,10 @@ fn (p mut Parser) fnext() {
|
||||||
comment := comment_token.lit
|
comment := comment_token.lit
|
||||||
// Newline before the comment, but not between two // comments,
|
// Newline before the comment, but not between two // comments,
|
||||||
// and not right after `{`, there's already a newline there
|
// and not right after `{`, there's already a newline there
|
||||||
if i > 0 && p.tokens[i-1].tok != .line_comment &&
|
if i > 0 && ((p.tokens[i-1].tok != .line_comment &&
|
||||||
p.tokens[i-1].tok != .lcbr &&
|
p.tokens[i-1].tok != .lcbr &&
|
||||||
comment_token.line_nr > p.tokens[i-1].line_nr {
|
comment_token.line_nr > p.tokens[i-1].line_nr) ||
|
||||||
|
p.tokens[i-1].tok == .hash) { // TODO not sure why this is needed, newline wasn't added after a hash
|
||||||
p.fgen_nl()
|
p.fgen_nl()
|
||||||
}
|
}
|
||||||
if i > 0 && p.tokens[i-1].tok == .rcbr && p.scanner.fmt_indent == 0 {
|
if i > 0 && p.tokens[i-1].tok == .rcbr && p.scanner.fmt_indent == 0 {
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module http
|
module http
|
||||||
|
|
||||||
import strings
|
import strings
|
||||||
|
|
||||||
// On linux, prefer a localy build openssl, because it is
|
// On linux, prefer a localy build openssl, because it is
|
||||||
// much more likely for it to be newer, than the system
|
// much more likely for it to be newer, than the system
|
||||||
// openssl from libssl-dev. If there is no local openssl,
|
// openssl from libssl-dev. If there is no local openssl,
|
||||||
// the next flag is harmless, since it will still use the
|
// the next flag is harmless, since it will still use the
|
||||||
// (older) system openssl.
|
// (older) system openssl.
|
||||||
#flag linux -I/usr/local/include/openssl -L/usr/local/lib
|
#flag linux -I/usr/local/include/openssl -L/usr/local/lib
|
||||||
|
|
||||||
#flag -l ssl -l crypto
|
#flag -l ssl -l crypto
|
||||||
// MacPorts
|
// MacPorts
|
||||||
#flag darwin -I/opt/local/include
|
#flag darwin -I/opt/local/include
|
||||||
|
@ -20,40 +17,74 @@ import strings
|
||||||
// Brew
|
// Brew
|
||||||
#flag darwin -I/usr/local/opt/openssl/include
|
#flag darwin -I/usr/local/opt/openssl/include
|
||||||
#flag darwin -L/usr/local/opt/openssl/lib
|
#flag darwin -L/usr/local/opt/openssl/lib
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
struct C.SSL {
|
struct C.SSL {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn C.SSL_library_init()
|
fn C.SSL_library_init()
|
||||||
|
|
||||||
|
|
||||||
fn C.TLSv1_2_method() voidptr
|
fn C.TLSv1_2_method() voidptr
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_CTX_set_options()
|
fn C.SSL_CTX_set_options()
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_CTX_new() voidptr
|
fn C.SSL_CTX_new() voidptr
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_CTX_set_verify_depth()
|
fn C.SSL_CTX_set_verify_depth()
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_CTX_load_verify_locations() int
|
fn C.SSL_CTX_load_verify_locations() int
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_new_ssl_connect() voidptr
|
fn C.BIO_new_ssl_connect() voidptr
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_set_conn_hostname() int
|
fn C.BIO_set_conn_hostname() int
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_get_ssl()
|
fn C.BIO_get_ssl()
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_set_cipher_list() int
|
fn C.SSL_set_cipher_list() int
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_do_connect() int
|
fn C.BIO_do_connect() int
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_do_handshake() int
|
fn C.BIO_do_handshake() int
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_get_peer_certificate() int
|
fn C.SSL_get_peer_certificate() int
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_get_verify_result() int
|
fn C.SSL_get_verify_result() int
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_set_tlsext_host_name() int
|
fn C.SSL_set_tlsext_host_name() int
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_puts()
|
fn C.BIO_puts()
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_read()
|
fn C.BIO_read()
|
||||||
|
|
||||||
|
|
||||||
fn C.BIO_free_all()
|
fn C.BIO_free_all()
|
||||||
|
|
||||||
|
|
||||||
fn C.SSL_CTX_free()
|
fn C.SSL_CTX_free()
|
||||||
|
|
||||||
|
|
||||||
fn init() int {
|
fn init() int {
|
||||||
C.SSL_library_init()
|
C.SSL_library_init()
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (req &Request) ssl_do(port int, method, host_name, path string) ?Response {
|
fn (req &Request) ssl_do(port int, method, host_name, path string) ?Response {
|
||||||
//ssl_method := C.SSLv23_method()
|
// ssl_method := C.SSLv23_method()
|
||||||
ssl_method := C.TLSv1_2_method()
|
ssl_method := C.TLSv1_2_method()
|
||||||
if isnil(method) {
|
if isnil(method) {
|
||||||
}
|
}
|
||||||
|
@ -89,13 +120,13 @@ fn (req &Request) ssl_do(port int, method, host_name, path string) ?Response {
|
||||||
res = C.BIO_do_handshake(web)
|
res = C.BIO_do_handshake(web)
|
||||||
C.SSL_get_peer_certificate(ssl)
|
C.SSL_get_peer_certificate(ssl)
|
||||||
res = C.SSL_get_verify_result(ssl)
|
res = C.SSL_get_verify_result(ssl)
|
||||||
///////
|
// /////
|
||||||
s := req.build_request_headers(method, host_name, path)
|
s := req.build_request_headers(method, host_name, path)
|
||||||
C.BIO_puts(web, s.str)
|
C.BIO_puts(web, s.str)
|
||||||
mut sb := strings.new_builder(100)
|
mut sb := strings.new_builder(100)
|
||||||
for {
|
for {
|
||||||
buff := [1536]byte
|
buff := [1536]byte
|
||||||
len := int(C.BIO_read(web, buff, 1536) )
|
len := int(C.BIO_read(web, buff, 1536))
|
||||||
if len > 0 {
|
if len > 0 {
|
||||||
sb.write(tos(buff, len))
|
sb.write(tos(buff, len))
|
||||||
}
|
}
|
||||||
|
@ -109,6 +140,6 @@ fn (req &Request) ssl_do(port int, method, host_name, path string) ?Response {
|
||||||
if !isnil(ctx) {
|
if !isnil(ctx) {
|
||||||
C.SSL_CTX_free(ctx)
|
C.SSL_CTX_free(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parse_response(sb.str())
|
return parse_response(sb.str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
module chunked
|
module chunked
|
||||||
|
|
||||||
import strings
|
import strings
|
||||||
|
|
||||||
// See: https://en.wikipedia.org/wiki/Chunked_transfer_encoding
|
// See: https://en.wikipedia.org/wiki/Chunked_transfer_encoding
|
||||||
///////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////
|
||||||
// The chunk size is transferred as a hexadecimal number
|
// The chunk size is transferred as a hexadecimal number
|
||||||
// followed by \r\n as a line separator,
|
// followed by \r\n as a line separator,
|
||||||
// followed by a chunk of data of the given size.
|
// followed by a chunk of data of the given size.
|
||||||
// The end is marked with a chunk with size 0.
|
// The end is marked with a chunk with size 0.
|
||||||
|
|
||||||
struct ChunkScanner {
|
struct ChunkScanner {
|
||||||
mut:
|
mut:
|
||||||
pos int
|
pos int
|
||||||
text string
|
text string
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (s mut ChunkScanner) read_chunk_size() int {
|
fn (s mut ChunkScanner) read_chunk_size() int {
|
||||||
mut n := 0
|
mut n := 0
|
||||||
for {
|
for {
|
||||||
if s.pos >= s.text.len { break }
|
if s.pos >= s.text.len {
|
||||||
|
break
|
||||||
|
}
|
||||||
c := s.text[s.pos]
|
c := s.text[s.pos]
|
||||||
if !c.is_hex_digit() { break }
|
if !c.is_hex_digit() {
|
||||||
n = n << 4
|
break
|
||||||
|
}
|
||||||
|
n = n<<4
|
||||||
n += int(unhex(c))
|
n += int(unhex(c))
|
||||||
s.pos++
|
s.pos++
|
||||||
}
|
}
|
||||||
|
@ -29,9 +31,15 @@ fn (s mut ChunkScanner) read_chunk_size() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unhex(c byte) byte {
|
fn unhex(c byte) byte {
|
||||||
if `0` <= c && c <= `9` { return c - `0` }
|
if `0` <= c && c <= `9` {
|
||||||
else if `a` <= c && c <= `f` { return c - `a` + 10 }
|
return c - `0`
|
||||||
else if `A` <= c && c <= `F` { return c - `A` + 10 }
|
}
|
||||||
|
else if `a` <= c && c <= `f` {
|
||||||
|
return c - `a` + 10
|
||||||
|
}
|
||||||
|
else if `A` <= c && c <= `F` {
|
||||||
|
return c - `A` + 10
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,17 +55,20 @@ fn (s mut ChunkScanner) read_chunk(chunksize int) string {
|
||||||
|
|
||||||
pub fn decode(text string) string {
|
pub fn decode(text string) string {
|
||||||
mut sb := strings.new_builder(100)
|
mut sb := strings.new_builder(100)
|
||||||
mut cscanner := ChunkScanner {
|
mut cscanner := ChunkScanner{
|
||||||
pos: 0
|
pos: 0
|
||||||
text: text
|
text: text
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
csize := cscanner.read_chunk_size()
|
csize := cscanner.read_chunk_size()
|
||||||
if 0 == csize { break }
|
if 0 == csize {
|
||||||
|
break
|
||||||
|
}
|
||||||
cscanner.skip_crlf()
|
cscanner.skip_crlf()
|
||||||
sb.write( cscanner.read_chunk(csize) )
|
sb.write(cscanner.read_chunk(csize))
|
||||||
cscanner.skip_crlf()
|
cscanner.skip_crlf()
|
||||||
}
|
}
|
||||||
cscanner.skip_crlf()
|
cscanner.skip_crlf()
|
||||||
return sb.str()
|
return sb.str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module http
|
module http
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
pub fn download_file(url, out string) bool {
|
pub fn download_file(url, out string) bool {
|
||||||
s := http.get(url) or { return false }
|
s := http.get(url) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
os.write_file(out, s.text)
|
os.write_file(out, s.text)
|
||||||
return true
|
return true
|
||||||
//download_file_with_progress(url, out, empty, empty)
|
// download_file_with_progress(url, out, empty, empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module http
|
module http
|
||||||
|
|
||||||
type downloadfn fn (written int)
|
type downloadfn fn(written int)type download_finished_fn fn()
|
||||||
type download_finished_fn fn ()
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
struct DownloadStruct {
|
struct DownloadStruct {
|
||||||
mut:
|
mut:
|
||||||
|
@ -15,9 +12,8 @@ mut:
|
||||||
cb downloadfn
|
cb downloadfn
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
fn download_cb(ptr voidptr, size, nmemb size_t, userp voidptr) {
|
||||||
fn download_cb(ptr voidptr, size, nmemb size_t, userp voidptr) {
|
/*
|
||||||
/*
|
|
||||||
mut data := &DownloadStruct(userp)
|
mut data := &DownloadStruct(userp)
|
||||||
written := C.fwrite(ptr, size, nmemb, data.stream)
|
written := C.fwrite(ptr, size, nmemb, data.stream)
|
||||||
data.written += written
|
data.written += written
|
||||||
|
@ -28,7 +24,7 @@ fn download_cb(ptr voidptr, size, nmemb size_t, userp voidptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn download_file_with_progress(url, out string, cb downloadfn, cb_finished fn()) {
|
pub fn download_file_with_progress(url, out string, cb downloadfn, cb_finished fn()) {
|
||||||
/*
|
/*
|
||||||
curl := C.curl_easy_init()
|
curl := C.curl_easy_init()
|
||||||
if isnil(curl) {
|
if isnil(curl) {
|
||||||
return
|
return
|
||||||
|
@ -52,6 +48,5 @@ pub fn download_file_with_progress(url, out string, cb downloadfn, cb_finished f
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty() {
|
fn empty() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module http
|
module http
|
||||||
|
|
||||||
import net.urllib
|
import net.urllib
|
||||||
|
@ -13,19 +12,19 @@ const (
|
||||||
|
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
pub:
|
pub:
|
||||||
headers map[string]string
|
headers map[string]string
|
||||||
method string
|
method string
|
||||||
// cookies map[string]string
|
// cookies map[string]string
|
||||||
h string
|
h string
|
||||||
cmd string
|
cmd string
|
||||||
typ string // GET POST
|
typ string // GET POST
|
||||||
data string
|
data string
|
||||||
url string
|
url string
|
||||||
verbose bool
|
verbose bool
|
||||||
user_agent string
|
user_agent string
|
||||||
mut:
|
mut:
|
||||||
user_ptr voidptr
|
user_ptr voidptr
|
||||||
ws_func voidptr
|
ws_func voidptr
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
|
@ -67,7 +66,7 @@ pub fn new_request(typ, _url, _data string) ?Request {
|
||||||
url = '$url?$data'
|
url = '$url?$data'
|
||||||
data = ''
|
data = ''
|
||||||
}
|
}
|
||||||
return Request {
|
return Request{
|
||||||
typ: typ
|
typ: typ
|
||||||
url: url
|
url: url
|
||||||
data: data
|
data: data
|
||||||
|
@ -79,7 +78,9 @@ pub fn new_request(typ, _url, _data string) ?Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text(url string) string {
|
pub fn get_text(url string) string {
|
||||||
resp := get(url) or { return '' }
|
resp := get(url) or {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return resp.text
|
return resp.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,18 +117,29 @@ pub fn (req &Request) do() ?Response {
|
||||||
if req.typ == 'POST' {
|
if req.typ == 'POST' {
|
||||||
// req.headers << 'Content-Type: application/x-www-form-urlencoded'
|
// req.headers << 'Content-Type: application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
url := urllib.parse(req.url) or { return error('http.request.do: invalid URL "$req.url"') }
|
url := urllib.parse(req.url) or {
|
||||||
|
return error('http.request.do: invalid URL "$req.url"')
|
||||||
|
}
|
||||||
mut rurl := url
|
mut rurl := url
|
||||||
mut resp := Response{}
|
mut resp := Response{
|
||||||
|
}
|
||||||
mut no_redirects := 0
|
mut no_redirects := 0
|
||||||
for {
|
for {
|
||||||
if no_redirects == max_redirects { return error('http.request.do: maximum number of redirects reached ($max_redirects)') }
|
if no_redirects == max_redirects {
|
||||||
qresp := req.method_and_url_to_response( req.typ, rurl ) or { return error(err) }
|
return error('http.request.do: maximum number of redirects reached ($max_redirects)')
|
||||||
|
}
|
||||||
|
qresp := req.method_and_url_to_response(req.typ, rurl) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
resp = qresp
|
resp = qresp
|
||||||
if ! (resp.status_code in [301, 302, 303, 307, 308]) { break }
|
if !(resp.status_code in [301, 302, 303, 307, 308]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
// follow any redirects
|
// follow any redirects
|
||||||
redirect_url := resp.headers['Location']
|
redirect_url := resp.headers['Location']
|
||||||
qrurl := urllib.parse( redirect_url ) or { return error('http.request.do: invalid URL in redirect "$redirect_url"') }
|
qrurl := urllib.parse(redirect_url) or {
|
||||||
|
return error('http.request.do: invalid URL in redirect "$redirect_url"')
|
||||||
|
}
|
||||||
rurl = qrurl
|
rurl = qrurl
|
||||||
no_redirects++
|
no_redirects++
|
||||||
}
|
}
|
||||||
|
@ -141,19 +153,24 @@ fn (req &Request) method_and_url_to_response(method string, url net_dot_urllib.U
|
||||||
path := if url.query().size > 0 { '/$p?${url.query().encode()}' } else { '/$p' }
|
path := if url.query().size > 0 { '/$p?${url.query().encode()}' } else { '/$p' }
|
||||||
mut nport := url.port().int()
|
mut nport := url.port().int()
|
||||||
if nport == 0 {
|
if nport == 0 {
|
||||||
if scheme == 'http' { nport = 80 }
|
if scheme == 'http' {
|
||||||
if scheme == 'https' { nport = 443 }
|
nport = 80
|
||||||
|
}
|
||||||
|
if scheme == 'https' {
|
||||||
|
nport = 443
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//println('fetch $method, $scheme, $host_name, $nport, $path ')
|
// println('fetch $method, $scheme, $host_name, $nport, $path ')
|
||||||
if scheme == 'https' {
|
if scheme == 'https' {
|
||||||
//println('ssl_do( $nport, $method, $host_name, $path )')
|
// println('ssl_do( $nport, $method, $host_name, $path )')
|
||||||
res := req.ssl_do( nport, method, host_name, path ) or {
|
res := req.ssl_do(nport, method, host_name, path) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
} else if scheme == 'http' {
|
}
|
||||||
//println('http_do( $nport, $method, $host_name, $path )')
|
else if scheme == 'http' {
|
||||||
res := req.http_do(nport, method, host_name, path ) or {
|
// println('http_do( $nport, $method, $host_name, $path )')
|
||||||
|
res := req.http_do(nport, method, host_name, path) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
|
@ -175,7 +192,7 @@ fn parse_response(resp string) Response {
|
||||||
mut i := 1
|
mut i := 1
|
||||||
for {
|
for {
|
||||||
old_pos := nl_pos
|
old_pos := nl_pos
|
||||||
nl_pos = resp.index_after('\n', nl_pos+1)
|
nl_pos = resp.index_after('\n', nl_pos + 1)
|
||||||
if nl_pos == -1 {
|
if nl_pos == -1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -189,19 +206,17 @@ fn parse_response(resp string) Response {
|
||||||
pos := h.index(':') or {
|
pos := h.index(':') or {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//if h.contains('Content-Type') {
|
// if h.contains('Content-Type') {
|
||||||
//continue
|
// continue
|
||||||
//}
|
// }
|
||||||
key := h[..pos]
|
key := h[..pos]
|
||||||
val := h[pos+2..]
|
val := h[pos + 2..]
|
||||||
headers[key] = val.trim_space()
|
headers[key] = val.trim_space()
|
||||||
}
|
}
|
||||||
|
|
||||||
if headers['Transfer-Encoding'] == 'chunked' {
|
if headers['Transfer-Encoding'] == 'chunked' {
|
||||||
text = chunked.decode( text )
|
text = chunked.decode(text)
|
||||||
}
|
}
|
||||||
|
return Response{
|
||||||
return Response {
|
|
||||||
status_code: status_code
|
status_code: status_code
|
||||||
headers: headers
|
headers: headers
|
||||||
text: text
|
text: text
|
||||||
|
@ -217,12 +232,7 @@ fn (req &Request) build_request_headers(method, host_name, path string) string {
|
||||||
if req.data.len > 0 {
|
if req.data.len > 0 {
|
||||||
uheaders << 'Content-Length: ${req.data.len}\r\n'
|
uheaders << 'Content-Length: ${req.data.len}\r\n'
|
||||||
}
|
}
|
||||||
return '$method $path HTTP/1.1\r\n' +
|
return '$method $path HTTP/1.1\r\n' + 'Host: $host_name\r\n' + 'User-Agent: $ua\r\n' + uheaders.join('') + 'Connection: close\r\n\r\n' + req.data
|
||||||
'Host: $host_name\r\n' +
|
|
||||||
'User-Agent: $ua\r\n' +
|
|
||||||
uheaders.join('') +
|
|
||||||
'Connection: close\r\n\r\n' +
|
|
||||||
req.data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unescape_url(s string) string {
|
pub fn unescape_url(s string) string {
|
||||||
|
@ -241,4 +251,5 @@ pub fn escape(s string) string {
|
||||||
panic('http.escape() was replaced with http.escape_url()')
|
panic('http.escape() was replaced with http.escape_url()')
|
||||||
}
|
}
|
||||||
|
|
||||||
type wsfn fn (s string, ptr voidptr)
|
type wsfn fn(s string, ptr voidptr)
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,23 @@ fn (req &Request) http_do(port int, method, host_name, path string) ?Response {
|
||||||
rbuffer := [512]byte
|
rbuffer := [512]byte
|
||||||
mut sb := strings.new_builder(100)
|
mut sb := strings.new_builder(100)
|
||||||
s := req.build_request_headers(method, host_name, path)
|
s := req.build_request_headers(method, host_name, path)
|
||||||
|
client := net.dial(host_name, port) or {
|
||||||
client := net.dial( host_name, port) or { return error(err) }
|
return error(err)
|
||||||
client.send( s.str, s.len ) or {}
|
}
|
||||||
for {
|
client.send(s.str, s.len) or {
|
||||||
readbytes := client.crecv( rbuffer, bufsize )
|
}
|
||||||
if readbytes < 0 { return error('http.request.http_do: error reading response. readbytes=$readbytes') }
|
for {
|
||||||
if readbytes == 0 { break }
|
readbytes := client.crecv(rbuffer, bufsize)
|
||||||
sb.write( tos(rbuffer, readbytes) )
|
if readbytes < 0 {
|
||||||
|
return error('http.request.http_do: error reading response. readbytes=$readbytes')
|
||||||
|
}
|
||||||
|
if readbytes == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sb.write(tos(rbuffer, readbytes))
|
||||||
|
}
|
||||||
|
client.close() or {
|
||||||
}
|
}
|
||||||
client.close() or {}
|
|
||||||
return parse_response(sb.str())
|
return parse_response(sb.str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module json
|
module json
|
||||||
|
|
||||||
#flag -I @VROOT/thirdparty/cJSON
|
#flag -I @VROOT/thirdparty/cJSON
|
||||||
#flag @VROOT/thirdparty/cJSON/cJSON.o
|
#flag @VROOT/thirdparty/cJSON/cJSON.o
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
|
||||||
struct C.cJSON {
|
struct C.cJSON {
|
||||||
valueint int
|
valueint int
|
||||||
valuedouble f32
|
valuedouble f32
|
||||||
|
@ -39,7 +37,7 @@ fn jsdecode_i64(root &C.cJSON) i64 {
|
||||||
if isnil(root) {
|
if isnil(root) {
|
||||||
return i64(0)
|
return i64(0)
|
||||||
}
|
}
|
||||||
return i64(root.valuedouble) //i64 is double in C
|
return i64(root.valuedouble) // i64 is double in C
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jsdecode_byte(root &C.cJSON) byte {
|
fn jsdecode_byte(root &C.cJSON) byte {
|
||||||
|
@ -84,7 +82,6 @@ fn jsdecode_f64(root &C.cJSON) f64 {
|
||||||
return f64(root.valuedouble)
|
return f64(root.valuedouble)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn jsdecode_string(root &C.cJSON) string {
|
fn jsdecode_string(root &C.cJSON) string {
|
||||||
if isnil(root) {
|
if isnil(root) {
|
||||||
return ''
|
return ''
|
||||||
|
@ -94,16 +91,27 @@ fn jsdecode_string(root &C.cJSON) string {
|
||||||
}
|
}
|
||||||
// println('jsdecode string valuestring="$root.valuestring"')
|
// println('jsdecode string valuestring="$root.valuestring"')
|
||||||
// return tos(root.valuestring, _strlen(root.valuestring))
|
// return tos(root.valuestring, _strlen(root.valuestring))
|
||||||
return tos_clone(root.valuestring)// , _strlen(root.valuestring))
|
return tos_clone(root.valuestring) // , _strlen(root.valuestring))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn C.cJSON_IsTrue() bool
|
fn C.cJSON_IsTrue() bool
|
||||||
|
|
||||||
|
|
||||||
fn C.cJSON_CreateNumber() &C.cJSON
|
fn C.cJSON_CreateNumber() &C.cJSON
|
||||||
|
|
||||||
|
|
||||||
fn C.cJSON_CreateBool() &C.cJSON
|
fn C.cJSON_CreateBool() &C.cJSON
|
||||||
|
|
||||||
|
|
||||||
fn C.cJSON_CreateString() &C.cJSON
|
fn C.cJSON_CreateString() &C.cJSON
|
||||||
|
|
||||||
|
|
||||||
fn C.cJSON_Parse() &C.cJSON
|
fn C.cJSON_Parse() &C.cJSON
|
||||||
|
|
||||||
|
|
||||||
fn C.cJSON_PrintUnformatted() byteptr
|
fn C.cJSON_PrintUnformatted() byteptr
|
||||||
|
|
||||||
|
|
||||||
fn jsdecode_bool(root &C.cJSON) bool {
|
fn jsdecode_bool(root &C.cJSON) bool {
|
||||||
if isnil(root) {
|
if isnil(root) {
|
||||||
return false
|
return false
|
||||||
|
@ -161,7 +169,6 @@ fn jsencode_string(val string) &C.cJSON {
|
||||||
return C.cJSON_CreateString(clone.str)
|
return C.cJSON_CreateString(clone.str)
|
||||||
// return C.cJSON_CreateString2(val.str, val.len)
|
// return C.cJSON_CreateString2(val.str, val.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ///////////////////////
|
// ///////////////////////
|
||||||
// user := decode_User(json_parse(js_string_var))
|
// user := decode_User(json_parse(js_string_var))
|
||||||
fn json_parse(s string) &C.cJSON {
|
fn json_parse(s string) &C.cJSON {
|
||||||
|
@ -178,3 +185,4 @@ fn json_print(json &C.cJSON) string {
|
||||||
// fn json_array_for_each(val, root &C.cJSON) {
|
// fn json_array_for_each(val, root &C.cJSON) {
|
||||||
// #cJSON_ArrayForEach (val ,root)
|
// #cJSON_ArrayForEach (val ,root)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@ module net
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
fn error_code() int {
|
fn error_code() int {
|
||||||
return C.errno
|
return C.errno
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const (
|
pub const (
|
||||||
MSG_NOSIGNAL = 0x4000
|
MSG_NOSIGNAL = 0x4000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
module net
|
module net
|
||||||
|
|
||||||
fn C.gethostname() int
|
fn C.gethostname() int
|
||||||
|
|
||||||
// hostname returns the host name reported by the kernel.
|
// hostname returns the host name reported by the kernel.
|
||||||
pub fn hostname() ?string {
|
pub fn hostname() ?string {
|
||||||
mut name := [256]byte
|
mut name := [256]byte
|
||||||
// https://www.ietf.org/rfc/rfc1035.txt
|
// https://www.ietf.org/rfc/rfc1035.txt
|
||||||
// The host name is returned as a null-terminated string.
|
// The host name is returned as a null-terminated string.
|
||||||
res := C.gethostname(&name, 256)
|
res := C.gethostname(&name, 256)
|
||||||
if res != 0 {
|
if res != 0 {
|
||||||
return error('net.hostname: failed with $res')
|
return error('net.hostname: failed with $res')
|
||||||
}
|
}
|
||||||
return tos_clone(name)
|
return tos_clone(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,10 @@ pub struct Socket {
|
||||||
pub:
|
pub:
|
||||||
sockfd int
|
sockfd int
|
||||||
family int
|
family int
|
||||||
_type int
|
_type int
|
||||||
proto int
|
proto int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct C.in_addr {
|
struct C.in_addr {
|
||||||
mut:
|
mut:
|
||||||
s_addr int
|
s_addr int
|
||||||
|
@ -19,52 +18,82 @@ mut:
|
||||||
struct C.sockaddr_in {
|
struct C.sockaddr_in {
|
||||||
mut:
|
mut:
|
||||||
sin_family int
|
sin_family int
|
||||||
sin_port int
|
sin_port int
|
||||||
sin_addr C.in_addr
|
sin_addr C.in_addr
|
||||||
}
|
}
|
||||||
|
|
||||||
struct C.addrinfo {
|
struct C.addrinfo {
|
||||||
mut:
|
mut:
|
||||||
ai_family int
|
ai_family int
|
||||||
ai_socktype int
|
ai_socktype int
|
||||||
ai_flags int
|
ai_flags int
|
||||||
ai_protocol int
|
ai_protocol int
|
||||||
ai_addrlen int
|
ai_addrlen int
|
||||||
ai_addr voidptr
|
ai_addr voidptr
|
||||||
ai_canonname voidptr
|
ai_canonname voidptr
|
||||||
ai_next voidptr
|
ai_next voidptr
|
||||||
}
|
}
|
||||||
|
|
||||||
struct C.sockaddr_storage {}
|
struct C.sockaddr_storage {
|
||||||
fn C.socket() int
|
}
|
||||||
fn C.setsockopt() int
|
|
||||||
fn C.htonl() int
|
|
||||||
fn C.htons() int
|
|
||||||
fn C.bind() int
|
|
||||||
fn C.listen() int
|
|
||||||
fn C.accept() int
|
|
||||||
fn C.getaddrinfo() int
|
|
||||||
fn C.connect() int
|
|
||||||
fn C.send() int
|
|
||||||
fn C.recv() int
|
|
||||||
fn C.read() int
|
|
||||||
fn C.shutdown() int
|
|
||||||
fn C.close() int
|
|
||||||
fn C.ntohs() int
|
|
||||||
fn C.getsockname() int
|
|
||||||
|
|
||||||
|
fn C.socket() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.setsockopt() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.htonl() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.htons() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.bind() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.listen() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.accept() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.getaddrinfo() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.connect() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.send() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.recv() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.read() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.shutdown() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.close() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.ntohs() int
|
||||||
|
|
||||||
|
|
||||||
|
fn C.getsockname() int
|
||||||
// create socket
|
// create socket
|
||||||
pub fn new_socket(family int, _type int, proto int) ?Socket {
|
pub fn new_socket(family int, _type int, proto int) ?Socket {
|
||||||
|
|
||||||
sockfd := C.socket(family, _type, proto)
|
sockfd := C.socket(family, _type, proto)
|
||||||
one:=1
|
one := 1
|
||||||
// This is needed so that there are no problems with reusing the
|
// This is needed so that there are no problems with reusing the
|
||||||
// same port after the application exits.
|
// same port after the application exits.
|
||||||
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(int))
|
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(int))
|
||||||
if sockfd == 0 {
|
if sockfd == 0 {
|
||||||
return error('net.socket: failed')
|
return error('net.socket: failed')
|
||||||
}
|
}
|
||||||
s := Socket {
|
s := Socket{
|
||||||
sockfd: sockfd
|
sockfd: sockfd
|
||||||
family: family
|
family: family
|
||||||
_type: _type
|
_type: _type
|
||||||
|
@ -88,7 +117,8 @@ pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
|
||||||
|
|
||||||
// bind socket to port
|
// bind socket to port
|
||||||
pub fn (s Socket) bind(port int) ?int {
|
pub fn (s Socket) bind(port int) ?int {
|
||||||
mut addr := C.sockaddr_in{}
|
mut addr := C.sockaddr_in{
|
||||||
|
}
|
||||||
addr.sin_family = s.family
|
addr.sin_family = s.family
|
||||||
addr.sin_port = C.htons(port)
|
addr.sin_port = C.htons(port)
|
||||||
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
|
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
|
||||||
|
@ -148,13 +178,14 @@ pub fn (s Socket) accept() ?Socket {
|
||||||
$if debug {
|
$if debug {
|
||||||
println('accept()')
|
println('accept()')
|
||||||
}
|
}
|
||||||
addr := C.sockaddr_storage{}
|
addr := C.sockaddr_storage{
|
||||||
|
}
|
||||||
size := 128 // sizeof(sockaddr_storage)
|
size := 128 // sizeof(sockaddr_storage)
|
||||||
sockfd := C.accept(s.sockfd, &addr, &size)
|
sockfd := C.accept(s.sockfd, &addr, &size)
|
||||||
if sockfd < 0 {
|
if sockfd < 0 {
|
||||||
return error('net.accept: failed with $sockfd')
|
return error('net.accept: failed with $sockfd')
|
||||||
}
|
}
|
||||||
c := Socket {
|
c := Socket{
|
||||||
sockfd: sockfd
|
sockfd: sockfd
|
||||||
family: s.family
|
family: s.family
|
||||||
_type: s._type
|
_type: s._type
|
||||||
|
@ -165,7 +196,8 @@ pub fn (s Socket) accept() ?Socket {
|
||||||
|
|
||||||
// connect to given addrress and port
|
// connect to given addrress and port
|
||||||
pub fn (s Socket) connect(address string, port int) ?int {
|
pub fn (s Socket) connect(address string, port int) ?int {
|
||||||
mut hints := C.addrinfo{}
|
mut hints := C.addrinfo{
|
||||||
|
}
|
||||||
hints.ai_family = s.family
|
hints.ai_family = s.family
|
||||||
hints.ai_socktype = s._type
|
hints.ai_socktype = s._type
|
||||||
hints.ai_flags = C.AI_PASSIVE
|
hints.ai_flags = C.AI_PASSIVE
|
||||||
|
@ -174,8 +206,6 @@ pub fn (s Socket) connect(address string, port int) ?int {
|
||||||
hints.ai_canonname = C.NULL
|
hints.ai_canonname = C.NULL
|
||||||
hints.ai_addr = C.NULL
|
hints.ai_addr = C.NULL
|
||||||
hints.ai_next = C.NULL
|
hints.ai_next = C.NULL
|
||||||
|
|
||||||
|
|
||||||
info := &C.addrinfo(0)
|
info := &C.addrinfo(0)
|
||||||
sport := '$port'
|
sport := '$port'
|
||||||
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
|
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
|
||||||
|
@ -208,9 +238,13 @@ pub fn (s Socket) send(buf byteptr, len int) ?int {
|
||||||
mut dlen := len
|
mut dlen := len
|
||||||
for {
|
for {
|
||||||
sbytes := C.send(s.sockfd, dptr, dlen, MSG_NOSIGNAL)
|
sbytes := C.send(s.sockfd, dptr, dlen, MSG_NOSIGNAL)
|
||||||
if sbytes < 0 { return error('net.send: failed with $sbytes') }
|
if sbytes < 0 {
|
||||||
|
return error('net.send: failed with $sbytes')
|
||||||
|
}
|
||||||
dlen -= sbytes
|
dlen -= sbytes
|
||||||
if dlen <= 0 { break }
|
if dlen <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
dptr += sbytes
|
dptr += sbytes
|
||||||
}
|
}
|
||||||
return len
|
return len
|
||||||
|
@ -218,23 +252,24 @@ pub fn (s Socket) send(buf byteptr, len int) ?int {
|
||||||
|
|
||||||
// send string data to socket (when you have a v string)
|
// send string data to socket (when you have a v string)
|
||||||
pub fn (s Socket) send_string(sdata string) ?int {
|
pub fn (s Socket) send_string(sdata string) ?int {
|
||||||
return s.send( sdata.str, sdata.len )
|
return s.send(sdata.str, sdata.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive string data from socket
|
// receive string data from socket
|
||||||
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
|
pub fn (s Socket) recv(bufsize int) (byteptr,int) {
|
||||||
buf := malloc(bufsize)
|
buf := malloc(bufsize)
|
||||||
res := C.recv(s.sockfd, buf, bufsize, 0)
|
res := C.recv(s.sockfd, buf, bufsize, 0)
|
||||||
return buf, res
|
return buf,res
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
|
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
|
||||||
pub fn (s Socket) cread( buffer byteptr, buffersize int ) int {
|
pub fn (s Socket) cread(buffer byteptr, buffersize int) int {
|
||||||
return C.read(s.sockfd, buffer, buffersize)
|
return C.read(s.sockfd, buffer, buffersize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive a message from the socket, and place it in a preallocated buffer buf,
|
// Receive a message from the socket, and place it in a preallocated buffer buf,
|
||||||
// with maximum message size bufsize. Returns the length of the received message.
|
// with maximum message size bufsize. Returns the length of the received message.
|
||||||
pub fn (s Socket) crecv( buffer byteptr, buffersize int ) int {
|
pub fn (s Socket) crecv(buffer byteptr, buffersize int) int {
|
||||||
return C.recv(s.sockfd, buffer, buffersize, 0)
|
return C.recv(s.sockfd, buffer, buffersize, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,27 +278,23 @@ pub fn (s Socket) close() ?int {
|
||||||
mut shutdown_res := 0
|
mut shutdown_res := 0
|
||||||
$if windows {
|
$if windows {
|
||||||
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
|
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
|
||||||
}
|
} $else {
|
||||||
$else {
|
|
||||||
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
|
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
|
||||||
}
|
}
|
||||||
// TODO: should shutdown throw an error? close will
|
// TODO: should shutdown throw an error? close will
|
||||||
// continue even if shutdown failed
|
// continue even if shutdown failed
|
||||||
// if shutdown_res < 0 {
|
// if shutdown_res < 0 {
|
||||||
// return error('net.close: shutdown failed with $shutdown_res')
|
// return error('net.close: shutdown failed with $shutdown_res')
|
||||||
// }
|
// }
|
||||||
|
|
||||||
mut res := 0
|
mut res := 0
|
||||||
$if windows {
|
$if windows {
|
||||||
res = C.closesocket(s.sockfd)
|
res = C.closesocket(s.sockfd)
|
||||||
}
|
} $else {
|
||||||
$else {
|
|
||||||
res = C.close(s.sockfd)
|
res = C.close(s.sockfd)
|
||||||
}
|
}
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
return error('net.close: failed with $res')
|
return error('net.close: failed with $res')
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,12 +303,13 @@ pub const (
|
||||||
MAX_READ = 400
|
MAX_READ = 400
|
||||||
MSG_PEEK = 0x02
|
MSG_PEEK = 0x02
|
||||||
)
|
)
|
||||||
|
|
||||||
// write - write a string with CRLF after it over the socket s
|
// write - write a string with CRLF after it over the socket s
|
||||||
pub fn (s Socket) write(str string) ?int {
|
pub fn (s Socket) write(str string) ?int {
|
||||||
line := '$str$CRLF'
|
line := '$str$CRLF'
|
||||||
res := C.send(s.sockfd, line.str, line.len, MSG_NOSIGNAL)
|
res := C.send(s.sockfd, line.str, line.len, MSG_NOSIGNAL)
|
||||||
if res < 0 { return error('net.write: failed with $res') }
|
if res < 0 {
|
||||||
|
return error('net.write: failed with $res')
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,9 +319,13 @@ pub fn (s Socket) read_line() string {
|
||||||
mut res := '' // The final result, including the ending \n.
|
mut res := '' // The final result, including the ending \n.
|
||||||
for {
|
for {
|
||||||
mut line := '' // The current line. Can be a partial without \n in it.
|
mut line := '' // The current line. Can be a partial without \n in it.
|
||||||
n := C.recv(s.sockfd, buf, MAX_READ-1, MSG_PEEK)
|
n := C.recv(s.sockfd, buf, MAX_READ - 1, MSG_PEEK)
|
||||||
if n == -1 { return res }
|
if n == -1 {
|
||||||
if n == 0 { return res }
|
return res
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
buf[n] = `\0`
|
buf[n] = `\0`
|
||||||
mut eol_idx := -1
|
mut eol_idx := -1
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
|
@ -298,7 +334,7 @@ pub fn (s Socket) read_line() string {
|
||||||
// Ensure that tos_clone(buf) later,
|
// Ensure that tos_clone(buf) later,
|
||||||
// will return *only* the first line (including \n),
|
// will return *only* the first line (including \n),
|
||||||
// and ignore the rest
|
// and ignore the rest
|
||||||
buf[i+1] = `\0`
|
buf[i + 1] = `\0`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,7 +345,7 @@ pub fn (s Socket) read_line() string {
|
||||||
// Ensure that the block till the first \n (including it)
|
// Ensure that the block till the first \n (including it)
|
||||||
// is removed from the socket's receive queue, so that it does
|
// is removed from the socket's receive queue, so that it does
|
||||||
// not get read again.
|
// not get read again.
|
||||||
C.recv(s.sockfd, buf, eol_idx+1, 0)
|
C.recv(s.sockfd, buf, eol_idx + 1, 0)
|
||||||
res += line
|
res += line
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -327,19 +363,23 @@ pub fn (s Socket) read_all() string {
|
||||||
mut buf := [MAX_READ]byte // where C.recv will store the network data
|
mut buf := [MAX_READ]byte // where C.recv will store the network data
|
||||||
mut res := '' // The final result, including the ending \n.
|
mut res := '' // The final result, including the ending \n.
|
||||||
for {
|
for {
|
||||||
n := C.recv(s.sockfd, buf, MAX_READ-1, 0)
|
n := C.recv(s.sockfd, buf, MAX_READ - 1, 0)
|
||||||
if n == -1 { return res }
|
if n == -1 {
|
||||||
if n == 0 { return res }
|
return res
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
res += tos_clone(buf)
|
res += tos_clone(buf)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn (s Socket) get_port() int {
|
pub fn (s Socket) get_port() int {
|
||||||
mut addr := C.sockaddr_in {}
|
mut addr := C.sockaddr_in{
|
||||||
|
}
|
||||||
size := 16 // sizeof(sockaddr_in)
|
size := 16 // sizeof(sockaddr_in)
|
||||||
C.getsockname(s.sockfd, &addr, &size)
|
C.getsockname(s.sockfd, &addr, &size)
|
||||||
return C.ntohs(addr.sin_port)
|
return C.ntohs(addr.sin_port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
// urllib parses URLs and implements query escaping.
|
// urllib parses URLs and implements query escaping.
|
||||||
|
|
||||||
// See RFC 3986. This module generally follows RFC 3986, except where
|
// See RFC 3986. This module generally follows RFC 3986, except where
|
||||||
// it deviates for compatibility reasons.
|
// it deviates for compatibility reasons.
|
||||||
|
|
||||||
// Based off: https://github.com/golang/go/blob/master/src/net/url/url.go
|
// Based off: https://github.com/golang/go/blob/master/src/net/url/url.go
|
||||||
// Last commit: https://github.com/golang/go/commit/fe2ed5054176935d4adcf13e891715ccf2ee3cce
|
// Last commit: https://github.com/golang/go/commit/fe2ed5054176935d4adcf13e891715ccf2ee3cce
|
||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
module urllib
|
module urllib
|
||||||
|
|
||||||
import strings
|
import strings
|
||||||
|
@ -25,12 +22,14 @@ enum EncodingMode {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
err_msg_escape = 'unescape: invalid URL escape'
|
err_msg_escape = 'unescape: invalid URL escape'
|
||||||
err_msg_parse = 'parse: failed parsing url'
|
err_msg_parse = 'parse: failed parsing url'
|
||||||
)
|
)
|
||||||
|
|
||||||
fn error_msg(message, val string) string {
|
fn error_msg(message, val string) string {
|
||||||
mut msg := 'net.urllib.$message'
|
mut msg := 'net.urllib.$message'
|
||||||
if val != '' { msg = '$msg ($val)' }
|
if val != '' {
|
||||||
|
msg = '$msg ($val)'
|
||||||
|
}
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +43,9 @@ fn should_escape(c byte, mode EncodingMode) bool {
|
||||||
if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) || (`0` <= c && c <= `9`) {
|
if (`a` <= c && c <= `z`) || (`A` <= c && c <= `Z`) || (`0` <= c && c <= `9`) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == .encode_host || mode == .encode_zone {
|
if mode == .encode_host || mode == .encode_zone {
|
||||||
// §3.2.2 host allows
|
// §3.2.2 host allows
|
||||||
// sub-delims = `!` / `$` / `&` / ``` / `(` / `)` / `*` / `+` / `,` / `;` / `=`
|
// sub-delims = `!` / `$` / `&` / ``` / `(` / `)` / `*` / `+` / `,` / `;` / `=`
|
||||||
// as part of reg-name.
|
// as part of reg-name.
|
||||||
// We add : because we include :port as part of host.
|
// We add : because we include :port as part of host.
|
||||||
// We add [ ] because we include [ipv6]:port as part of host.
|
// We add [ ] because we include [ipv6]:port as part of host.
|
||||||
|
@ -55,59 +53,58 @@ fn should_escape(c byte, mode EncodingMode) bool {
|
||||||
// we could possibly allow, and parse will reject them if we
|
// we could possibly allow, and parse will reject them if we
|
||||||
// escape them (because hosts can`t use %-encoding for
|
// escape them (because hosts can`t use %-encoding for
|
||||||
// ASCII bytes).
|
// ASCII bytes).
|
||||||
if c in [`!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`,
|
if c in [`!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `:`, `[`, `]`, `<`, `>`, `"`] {
|
||||||
`:`, `[`, `]`, `<`, `>`, `"`]
|
|
||||||
{
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match c {
|
match c {
|
||||||
`-`, `_`, `.`, `~` { // §2.3 Unreserved characters (mark)
|
`-`, `_`, `.`, `~` {
|
||||||
|
// §2.3 Unreserved characters (mark)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
`$`, `&`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@` {
|
||||||
`$`, `&`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@` { // §2.2 Reserved characters (reserved)
|
// §2.2 Reserved characters (reserved)
|
||||||
// Different sections of the URL allow a few of
|
// Different sections of the URL allow a few of
|
||||||
// the reserved characters to appear unescaped.
|
// the reserved characters to appear unescaped.
|
||||||
match mode {
|
match mode {
|
||||||
.encode_path { // §3.3
|
.encode_path {
|
||||||
// The RFC allows : @ & = + $ but saves / ; , for assigning
|
// §3.3
|
||||||
// meaning to individual path segments. This package
|
// The RFC allows : @ & = + $ but saves / ; , for assigning
|
||||||
// only manipulates the path as a whole, so we allow those
|
// meaning to individual path segments. This package
|
||||||
// last three as well. That leaves only ? to escape.
|
// only manipulates the path as a whole, so we allow those
|
||||||
return c == `?`
|
// last three as well. That leaves only ? to escape.
|
||||||
|
return c == `?`
|
||||||
|
}
|
||||||
|
.encode_path_segment {
|
||||||
|
// §3.3
|
||||||
|
// The RFC allows : @ & = + $ but saves / ; , for assigning
|
||||||
|
// meaning to individual path segments.
|
||||||
|
return c == `/` || c == `;` || c == `,` || c == `?`
|
||||||
|
}
|
||||||
|
.encode_user_password {
|
||||||
|
// §3.2.1
|
||||||
|
// The RFC allows `;`, `:`, `&`, `=`, `+`, `$`, and `,` in
|
||||||
|
// userinfo, so we must escape only `@`, `/`, and `?`.
|
||||||
|
// The parsing of userinfo treats `:` as special so we must escape
|
||||||
|
// that too.
|
||||||
|
return c == `@` || c == `/` || c == `?` || c == `:`
|
||||||
|
}
|
||||||
|
.encode_query_component {
|
||||||
|
// §3.4
|
||||||
|
// The RFC reserves (so we must escape) everything.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
.encode_fragment {
|
||||||
|
// §4.1
|
||||||
|
// The RFC text is silent but the grammar allows
|
||||||
|
// everything, so escape nothing.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
.encode_path_segment { // §3.3
|
}}
|
||||||
// The RFC allows : @ & = + $ but saves / ; , for assigning
|
|
||||||
// meaning to individual path segments.
|
|
||||||
return c == `/` || c == `;` || c == `,` || c == `?`
|
|
||||||
}
|
|
||||||
|
|
||||||
.encode_user_password { // §3.2.1
|
|
||||||
// The RFC allows `;`, `:`, `&`, `=`, `+`, `$`, and `,` in
|
|
||||||
// userinfo, so we must escape only `@`, `/`, and `?`.
|
|
||||||
// The parsing of userinfo treats `:` as special so we must escape
|
|
||||||
// that too.
|
|
||||||
return c == `@` || c == `/` || c == `?` || c == `:`
|
|
||||||
}
|
|
||||||
|
|
||||||
.encode_query_component { // §3.4
|
|
||||||
// The RFC reserves (so we must escape) everything.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
.encode_fragment { // §4.1
|
|
||||||
// The RFC text is silent but the grammar allows
|
|
||||||
// everything, so escape nothing.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
else {}
|
|
||||||
}
|
|
||||||
} else {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode == .encode_fragment {
|
if mode == .encode_fragment {
|
||||||
// RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are
|
// RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are
|
||||||
// included in reserved from RFC 2396 §2.2. The remaining sub-delims do not
|
// included in reserved from RFC 2396 §2.2. The remaining sub-delims do not
|
||||||
|
@ -116,13 +113,12 @@ fn should_escape(c byte, mode EncodingMode) bool {
|
||||||
// escape single quote to avoid breaking callers that had previously assumed that
|
// escape single quote to avoid breaking callers that had previously assumed that
|
||||||
// single quotes would be escaped. See issue #19917.
|
// single quotes would be escaped. See issue #19917.
|
||||||
match c {
|
match c {
|
||||||
`!`, `(`, `)`, `*`{
|
`!`, `(`, `)`, `*` {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
else {}
|
else {
|
||||||
}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything else must be escaped.
|
// Everything else must be escaped.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -154,7 +150,7 @@ fn unescape(s_ string, mode EncodingMode) ?string {
|
||||||
// Count %, check that they're well-formed.
|
// Count %, check that they're well-formed.
|
||||||
mut n := 0
|
mut n := 0
|
||||||
mut has_plus := false
|
mut has_plus := false
|
||||||
for i := 0; i < s.len; {
|
for i := 0; i < s.len; {
|
||||||
x := s[i]
|
x := s[i]
|
||||||
match x {
|
match x {
|
||||||
`%` {
|
`%` {
|
||||||
|
@ -162,7 +158,7 @@ fn unescape(s_ string, mode EncodingMode) ?string {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
n++
|
n++
|
||||||
if i+2 >= s.len || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
if i + 2 >= s.len || !ishex(s[i + 1]) || !ishex(s[i + 2]) {
|
||||||
s = s[i..]
|
s = s[i..]
|
||||||
if s.len > 3 {
|
if s.len > 3 {
|
||||||
s = s[..3]
|
s = s[..3]
|
||||||
|
@ -175,8 +171,8 @@ fn unescape(s_ string, mode EncodingMode) ?string {
|
||||||
// But https://tools.ietf.org/html/rfc6874#section-2
|
// But https://tools.ietf.org/html/rfc6874#section-2
|
||||||
// introduces %25 being allowed to escape a percent sign
|
// introduces %25 being allowed to escape a percent sign
|
||||||
// in IPv6 scoped-address literals. Yay.
|
// in IPv6 scoped-address literals. Yay.
|
||||||
if mode == .encode_host && unhex(s[i+1]) < 8 && s[i..i+3] != '%25' {
|
if mode == .encode_host && unhex(s[i + 1]) < 8 && s[i..i + 3] != '%25' {
|
||||||
return error(error_msg(err_msg_escape, s[i..i+3]))
|
return error(error_msg(err_msg_escape, s[i..i + 3]))
|
||||||
}
|
}
|
||||||
if mode == .encode_zone {
|
if mode == .encode_zone {
|
||||||
// RFC 6874 says basically 'anything goes' for zone identifiers
|
// RFC 6874 says basically 'anything goes' for zone identifiers
|
||||||
|
@ -186,47 +182,46 @@ fn unescape(s_ string, mode EncodingMode) ?string {
|
||||||
// That is, you can use escaping in the zone identifier but not
|
// That is, you can use escaping in the zone identifier but not
|
||||||
// to introduce bytes you couldn't just write directly.
|
// to introduce bytes you couldn't just write directly.
|
||||||
// But Windows puts spaces here! Yay.
|
// But Windows puts spaces here! Yay.
|
||||||
v := (unhex(s[i+1])<<byte(4) | unhex(s[i+2]))
|
v := (unhex(s[i + 1])<<byte(4) | unhex(s[i + 2]))
|
||||||
if s[i..i+3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
|
if s[i..i + 3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
|
||||||
error(error_msg(err_msg_escape, s[i..i+3]))
|
error(error_msg(err_msg_escape, s[i..i + 3]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += 3
|
i += 3
|
||||||
}
|
}
|
||||||
`+`{
|
`+` {
|
||||||
has_plus = mode == .encode_query_component
|
has_plus = mode == .encode_query_component
|
||||||
i++
|
i++
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (mode == .encode_host || mode == .encode_zone) && s[i] < 0x80 && should_escape(s[i], mode) {
|
if (mode == .encode_host || mode == .encode_zone) && s[i] < 0x80 && should_escape(s[i], mode) {
|
||||||
error(error_msg('unescape: invalid character in host name', s[i..i+1]))
|
error(error_msg('unescape: invalid character in host name', s[i..i + 1]))
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 0 && !has_plus {
|
if n == 0 && !has_plus {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
mut t := strings.new_builder(s.len - 2 * n)
|
||||||
mut t := strings.new_builder(s.len - 2*n)
|
|
||||||
for i := 0; i < s.len; i++ {
|
for i := 0; i < s.len; i++ {
|
||||||
x := s[i]
|
x := s[i]
|
||||||
match x {
|
match x {
|
||||||
`%` {
|
`%` {
|
||||||
t.write((unhex(s[i+1])<<byte(4) | unhex(s[i+2])).str() )
|
t.write((unhex(s[i + 1])<<byte(4) | unhex(s[i + 2])).str())
|
||||||
i += 2
|
i += 2
|
||||||
}
|
}
|
||||||
`+` {
|
`+` {
|
||||||
if mode == .encode_query_component {
|
if mode == .encode_query_component {
|
||||||
t.write(' ')
|
t.write(' ')
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
t.write('+')
|
t.write('+')
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
t.write(s[i].str())
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
|
t.write(s[i].str())
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
return t.str()
|
return t.str()
|
||||||
}
|
}
|
||||||
|
@ -252,26 +247,24 @@ fn escape(s string, mode EncodingMode) string {
|
||||||
if should_escape(c, mode) {
|
if should_escape(c, mode) {
|
||||||
if c == ` ` && mode == .encode_query_component {
|
if c == ` ` && mode == .encode_query_component {
|
||||||
space_count++
|
space_count++
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
hex_count++
|
hex_count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if space_count == 0 && hex_count == 0 {
|
if space_count == 0 && hex_count == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := [byte(0)].repeat(64)
|
buf := [byte(0)].repeat(64)
|
||||||
mut t := []byte
|
mut t := []byte
|
||||||
|
required := s.len + 2 * hex_count
|
||||||
required := s.len + 2*hex_count
|
|
||||||
if required <= buf.len {
|
if required <= buf.len {
|
||||||
t = buf[..required]
|
t = buf[..required]
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
t = [byte(0)].repeat(required)
|
t = [byte(0)].repeat(required)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hex_count == 0 {
|
if hex_count == 0 {
|
||||||
copy(t, s.bytes())
|
copy(t, s.bytes())
|
||||||
for i := 0; i < s.len; i++ {
|
for i := 0; i < s.len; i++ {
|
||||||
|
@ -279,9 +272,8 @@ fn escape(s string, mode EncodingMode) string {
|
||||||
t[i] = `+`
|
t[i] = `+`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string(t, t.len)
|
return string(t,t.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
upperhex := '0123456789ABCDEF'
|
upperhex := '0123456789ABCDEF'
|
||||||
mut j := 0
|
mut j := 0
|
||||||
for i := 0; i < s.len; i++ {
|
for i := 0; i < s.len; i++ {
|
||||||
|
@ -289,28 +281,30 @@ fn escape(s string, mode EncodingMode) string {
|
||||||
if c1 == ` ` && mode == .encode_query_component {
|
if c1 == ` ` && mode == .encode_query_component {
|
||||||
t[j] = `+`
|
t[j] = `+`
|
||||||
j++
|
j++
|
||||||
} else if should_escape(c1, mode) {
|
}
|
||||||
|
else if should_escape(c1, mode) {
|
||||||
t[j] = `%`
|
t[j] = `%`
|
||||||
t[j+1] = upperhex[c1>>4]
|
t[j + 1] = upperhex[c1>>4]
|
||||||
t[j+2] = upperhex[c1&15]
|
t[j + 2] = upperhex[c1 & 15]
|
||||||
j += 3
|
j += 3
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
t[j] = s[i]
|
t[j] = s[i]
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string(t, t.len)
|
return string(t,t.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A URL represents a parsed URL (technically, a URI reference).
|
// A URL represents a parsed URL (technically, a URI reference).
|
||||||
//
|
//
|
||||||
// The general form represented is:
|
// The general form represented is:
|
||||||
//
|
//
|
||||||
// [scheme:][//[userinfo@]host][/]path[?query][#fragment]
|
// [scheme:][//[userinfo@]host][/]path[?query][#fragment]
|
||||||
//
|
//
|
||||||
// URLs that do not start with a slash after the scheme are interpreted as:
|
// URLs that do not start with a slash after the scheme are interpreted as:
|
||||||
//
|
//
|
||||||
// scheme:opaque[?query][#fragment]
|
// scheme:opaque[?query][#fragment]
|
||||||
//
|
//
|
||||||
// Note that the path field is stored in decoded form: /%47%6f%2f becomes /Go/.
|
// Note that the path field is stored in decoded form: /%47%6f%2f becomes /Go/.
|
||||||
// A consequence is that it is impossible to tell which slashes in the path were
|
// A consequence is that it is impossible to tell which slashes in the path were
|
||||||
|
@ -323,22 +317,21 @@ fn escape(s string, mode EncodingMode) string {
|
||||||
pub struct URL {
|
pub struct URL {
|
||||||
pub mut:
|
pub mut:
|
||||||
scheme string
|
scheme string
|
||||||
opaque string // encoded opaque data
|
opaque string // encoded opaque data
|
||||||
user &Userinfo // username and password information
|
user &Userinfo // username and password information
|
||||||
host string // host or host:port
|
host string // host or host:port
|
||||||
path string // path (relative paths may omit leading slash)
|
path string // path (relative paths may omit leading slash)
|
||||||
raw_path string // encoded path hint (see escaped_path method)
|
raw_path string // encoded path hint (see escaped_path method)
|
||||||
force_query bool // append a query ('?') even if raw_query is empty
|
force_query bool // append a query ('?') even if raw_query is empty
|
||||||
raw_query string // encoded query values, without '?'
|
raw_query string // encoded query values, without '?'
|
||||||
fragment string // fragment for references, without '#'
|
fragment string // fragment for references, without '#'
|
||||||
}
|
}
|
||||||
|
|
||||||
// user returns a Userinfo containing the provided username
|
// user returns a Userinfo containing the provided username
|
||||||
// and no password set.
|
// and no password set.
|
||||||
pub fn user(username string) &Userinfo {
|
pub fn user(username string) &Userinfo {
|
||||||
return &Userinfo{
|
return &Userinfo{
|
||||||
username: username,
|
username: username
|
||||||
password: '',
|
password: ''
|
||||||
password_set: false
|
password_set: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,7 +345,8 @@ pub fn user(username string) &Userinfo {
|
||||||
// information in clear text (such as URI) has proven to be a
|
// information in clear text (such as URI) has proven to be a
|
||||||
// security risk in almost every case where it has been used.''
|
// security risk in almost every case where it has been used.''
|
||||||
fn user_password(username, password string) &Userinfo {
|
fn user_password(username, password string) &Userinfo {
|
||||||
return &Userinfo{username, password, true}
|
return &Userinfo{
|
||||||
|
username,password,true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Userinfo type is an immutable encapsulation of username and
|
// The Userinfo type is an immutable encapsulation of username and
|
||||||
|
@ -401,7 +395,7 @@ fn split_by_scheme(rawurl string) ?[]string {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return error(error_msg('split_by_scheme: missing protocol scheme', ''))
|
return error(error_msg('split_by_scheme: missing protocol scheme', ''))
|
||||||
}
|
}
|
||||||
return [rawurl[..i], rawurl[i+1..]]
|
return [rawurl[..i], rawurl[i + 1..]]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// we have encountered an invalid character,
|
// we have encountered an invalid character,
|
||||||
|
@ -422,15 +416,15 @@ fn get_scheme(rawurl string) ?string {
|
||||||
// split slices s into two substrings separated by the first occurence of
|
// split slices s into two substrings separated by the first occurence of
|
||||||
// sep. If cutc is true then sep is included with the second substring.
|
// sep. If cutc is true then sep is included with the second substring.
|
||||||
// If sep does not occur in s then s and the empty string is returned.
|
// If sep does not occur in s then s and the empty string is returned.
|
||||||
fn split(s string, sep byte, cutc bool) (string, string) {
|
fn split(s string, sep byte, cutc bool) (string,string) {
|
||||||
i := s.index_byte(sep)
|
i := s.index_byte(sep)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return s, ''
|
return s,''
|
||||||
}
|
}
|
||||||
if cutc {
|
if cutc {
|
||||||
return s[..i], s[i+1..]
|
return s[..i],s[i + 1..]
|
||||||
}
|
}
|
||||||
return s[..i], s[i..]
|
return s[..i],s[i..]
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parses rawurl into a URL structure.
|
// parse parses rawurl into a URL structure.
|
||||||
|
@ -441,7 +435,7 @@ fn split(s string, sep byte, cutc bool) (string, string) {
|
||||||
// error, due to parsing ambiguities.
|
// error, due to parsing ambiguities.
|
||||||
pub fn parse(rawurl string) ?URL {
|
pub fn parse(rawurl string) ?URL {
|
||||||
// Cut off #frag
|
// Cut off #frag
|
||||||
u, frag := split(rawurl, `#`, true)
|
u,frag := split(rawurl, `#`, true)
|
||||||
mut url := parse_url(u, false) or {
|
mut url := parse_url(u, false) or {
|
||||||
return error(error_msg(err_msg_parse, u))
|
return error(error_msg(err_msg_parse, u))
|
||||||
}
|
}
|
||||||
|
@ -472,17 +466,16 @@ fn parse_url(rawurl string, via_request bool) ?URL {
|
||||||
if string_contains_ctl_byte(rawurl) {
|
if string_contains_ctl_byte(rawurl) {
|
||||||
return error(error_msg('parse_url: invalid control character in URL', rawurl))
|
return error(error_msg('parse_url: invalid control character in URL', rawurl))
|
||||||
}
|
}
|
||||||
|
|
||||||
if rawurl == '' && via_request {
|
if rawurl == '' && via_request {
|
||||||
return error(error_msg('parse_url: empty URL', rawurl))
|
return error(error_msg('parse_url: empty URL', rawurl))
|
||||||
}
|
}
|
||||||
mut url := URL{user:0}
|
mut url := URL{
|
||||||
|
user: 0
|
||||||
|
}
|
||||||
if rawurl == '*' {
|
if rawurl == '*' {
|
||||||
url.path = '*'
|
url.path = '*'
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split off possible leading 'http:', 'mailto:', etc.
|
// Split off possible leading 'http:', 'mailto:', etc.
|
||||||
// Cannot contain escaped characters.
|
// Cannot contain escaped characters.
|
||||||
p := split_by_scheme(rawurl) or {
|
p := split_by_scheme(rawurl) or {
|
||||||
|
@ -491,17 +484,16 @@ fn parse_url(rawurl string, via_request bool) ?URL {
|
||||||
url.scheme = p[0]
|
url.scheme = p[0]
|
||||||
mut rest := p[1]
|
mut rest := p[1]
|
||||||
url.scheme = url.scheme.to_lower()
|
url.scheme = url.scheme.to_lower()
|
||||||
|
|
||||||
// if rest.ends_with('?') && strings.count(rest, '?') == 1 {
|
// if rest.ends_with('?') && strings.count(rest, '?') == 1 {
|
||||||
if rest.ends_with('?') && !rest[..1].contains('?') {
|
if rest.ends_with('?') && !rest[..1].contains('?') {
|
||||||
url.force_query = true
|
url.force_query = true
|
||||||
rest = rest[..rest.len-1]
|
rest = rest[..rest.len - 1]
|
||||||
} else {
|
}
|
||||||
r, raw_query := split(rest, `?`, true)
|
else {
|
||||||
|
r,raw_query := split(rest, `?`, true)
|
||||||
rest = r
|
rest = r
|
||||||
url.raw_query = raw_query
|
url.raw_query = raw_query
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rest.starts_with('/') {
|
if !rest.starts_with('/') {
|
||||||
if url.scheme != '' {
|
if url.scheme != '' {
|
||||||
// We consider rootless paths per RFC 3986 as opaque.
|
// We consider rootless paths per RFC 3986 as opaque.
|
||||||
|
@ -511,23 +503,25 @@ fn parse_url(rawurl string, via_request bool) ?URL {
|
||||||
if via_request {
|
if via_request {
|
||||||
return error(error_msg('parse_url: invalid URI for request', ''))
|
return error(error_msg('parse_url: invalid URI for request', ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid confusion with malformed schemes, like cache_object:foo/bar.
|
// Avoid confusion with malformed schemes, like cache_object:foo/bar.
|
||||||
// See golang.org/issue/16822.
|
// See golang.org/issue/16822.
|
||||||
//
|
//
|
||||||
// RFC 3986, §3.3:
|
// RFC 3986, §3.3:
|
||||||
// In addition, a URI reference (Section 4.1) may be a relative-path reference,
|
// In addition, a URI reference (Section 4.1) may be a relative-path reference,
|
||||||
// in which case the first path segment cannot contain a colon (':') character.
|
// in which case the first path segment cannot contain a colon (':') character.
|
||||||
colon := rest.index(':') or { return error('there should be a : in the URL') }
|
colon := rest.index(':') or {
|
||||||
slash := rest.index('/') or { return error('there should be a / in the URL') }
|
return error('there should be a : in the URL')
|
||||||
|
}
|
||||||
|
slash := rest.index('/') or {
|
||||||
|
return error('there should be a / in the URL')
|
||||||
|
}
|
||||||
if colon >= 0 && (slash < 0 || colon < slash) {
|
if colon >= 0 && (slash < 0 || colon < slash) {
|
||||||
// First path segment has colon. Not allowed in relative URL.
|
// First path segment has colon. Not allowed in relative URL.
|
||||||
return error(error_msg('parse_url: first path segment in URL cannot contain colon', ''))
|
return error(error_msg('parse_url: first path segment in URL cannot contain colon', ''))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((url.scheme != '' || !via_request) && !rest.starts_with('///')) && rest.starts_with('//') {
|
if ((url.scheme != '' || !via_request) && !rest.starts_with('///')) && rest.starts_with('//') {
|
||||||
authority, r := split(rest[2..], `/`, false)
|
authority,r := split(rest[2..], `/`, false)
|
||||||
rest = r
|
rest = r
|
||||||
a := parse_authority(authority) or {
|
a := parse_authority(authority) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
|
@ -551,7 +545,9 @@ struct ParseAuthorityRes {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_authority(authority string) ?ParseAuthorityRes {
|
fn parse_authority(authority string) ?ParseAuthorityRes {
|
||||||
i := authority.last_index('@') or { -1 }
|
i := authority.last_index('@') or {
|
||||||
|
-1
|
||||||
|
}
|
||||||
mut host := ''
|
mut host := ''
|
||||||
mut zuser := user('')
|
mut zuser := user('')
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
|
@ -559,14 +555,18 @@ fn parse_authority(authority string) ?ParseAuthorityRes {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
host = h
|
host = h
|
||||||
} else {
|
}
|
||||||
h := parse_host(authority[i+1..]) or {
|
else {
|
||||||
|
h := parse_host(authority[i + 1..]) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
host = h
|
host = h
|
||||||
}
|
}
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return ParseAuthorityRes{host: host, user: zuser}
|
return ParseAuthorityRes{
|
||||||
|
host: host
|
||||||
|
user: zuser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mut userinfo := authority[..i]
|
mut userinfo := authority[..i]
|
||||||
if !valid_userinfo(userinfo) {
|
if !valid_userinfo(userinfo) {
|
||||||
|
@ -578,8 +578,9 @@ fn parse_authority(authority string) ?ParseAuthorityRes {
|
||||||
}
|
}
|
||||||
userinfo = u
|
userinfo = u
|
||||||
zuser = user(userinfo)
|
zuser = user(userinfo)
|
||||||
} else {
|
}
|
||||||
mut username, mut password := split(userinfo, `:`, true)
|
else {
|
||||||
|
mut username,mut password := split(userinfo, `:`, true)
|
||||||
u := unescape(username, .encode_user_password) or {
|
u := unescape(username, .encode_user_password) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
|
@ -603,20 +604,19 @@ fn parse_host(host string) ?string {
|
||||||
// parse an IP-Literal in RFC 3986 and RFC 6874.
|
// parse an IP-Literal in RFC 3986 and RFC 6874.
|
||||||
// E.g., '[fe80::1]', '[fe80::1%25en0]', '[fe80::1]:80'.
|
// E.g., '[fe80::1]', '[fe80::1%25en0]', '[fe80::1]:80'.
|
||||||
mut i := host.last_index(']') or {
|
mut i := host.last_index(']') or {
|
||||||
return error(error_msg('parse_host: missing \']\' in host', ''))
|
return error(error_msg("parse_host: missing \']\' in host", ''))
|
||||||
}
|
}
|
||||||
mut colon_port := host[i+1..]
|
mut colon_port := host[i + 1..]
|
||||||
if !valid_optional_port(colon_port) {
|
if !valid_optional_port(colon_port) {
|
||||||
return error(error_msg('parse_host: invalid port $colon_port after host ', ''))
|
return error(error_msg('parse_host: invalid port $colon_port after host ', ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RFC 6874 defines that %25 (%-encoded percent) introduces
|
// RFC 6874 defines that %25 (%-encoded percent) introduces
|
||||||
// the zone identifier, and the zone identifier can use basically
|
// the zone identifier, and the zone identifier can use basically
|
||||||
// any %-encoding it likes. That's different from the host, which
|
// any %-encoding it likes. That's different from the host, which
|
||||||
// can only %-encode non-ASCII bytes.
|
// can only %-encode non-ASCII bytes.
|
||||||
// We do impose some restrictions on the zone, to avoid stupidity
|
// We do impose some restrictions on the zone, to avoid stupidity
|
||||||
// like newlines.
|
// like newlines.
|
||||||
if zone := host[..i].index('%25') {
|
if zone:=host[..i].index('%25'){
|
||||||
host1 := unescape(host[..zone], .encode_host) or {
|
host1 := unescape(host[..zone], .encode_host) or {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -628,7 +628,7 @@ fn parse_host(host string) ?string {
|
||||||
}
|
}
|
||||||
return host1 + host2 + host3
|
return host1 + host2 + host3
|
||||||
}
|
}
|
||||||
if idx := host.last_index(':') {
|
if idx:=host.last_index(':'){
|
||||||
colon_port = host[idx..]
|
colon_port = host[idx..]
|
||||||
if !valid_optional_port(colon_port) {
|
if !valid_optional_port(colon_port) {
|
||||||
return error(error_msg('parse_host: invalid port $colon_port after host ', ''))
|
return error(error_msg('parse_host: invalid port $colon_port after host ', ''))
|
||||||
|
@ -639,10 +639,9 @@ fn parse_host(host string) ?string {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
//host = h
|
// host = h
|
||||||
//return host
|
// return host
|
||||||
}
|
}
|
||||||
|
|
||||||
// set_path sets the path and raw_path fields of the URL based on the provided
|
// set_path sets the path and raw_path fields of the URL based on the provided
|
||||||
// escaped path p. It maintains the invariant that raw_path is only specified
|
// escaped path p. It maintains the invariant that raw_path is only specified
|
||||||
// when it differs from the default encoding of the path.
|
// when it differs from the default encoding of the path.
|
||||||
|
@ -660,7 +659,8 @@ fn (u mut URL) set_path(p string) ?bool {
|
||||||
if p == escp {
|
if p == escp {
|
||||||
// Default encoding is fine.
|
// Default encoding is fine.
|
||||||
u.raw_path = ''
|
u.raw_path = ''
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
u.raw_path = p
|
u.raw_path = p
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -677,7 +677,9 @@ fn (u mut URL) set_path(p string) ?bool {
|
||||||
// reading u.raw_path directly.
|
// reading u.raw_path directly.
|
||||||
fn (u &URL) escaped_path() string {
|
fn (u &URL) escaped_path() string {
|
||||||
if u.raw_path != '' && valid_encoded_path(u.raw_path) {
|
if u.raw_path != '' && valid_encoded_path(u.raw_path) {
|
||||||
unescape(u.raw_path, .encode_path) or { return '' }
|
unescape(u.raw_path, .encode_path) or {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return u.raw_path
|
return u.raw_path
|
||||||
}
|
}
|
||||||
if u.path == '*' {
|
if u.path == '*' {
|
||||||
|
@ -705,12 +707,12 @@ fn valid_encoded_path(s string) bool {
|
||||||
}
|
}
|
||||||
`%` {
|
`%` {
|
||||||
// ok - percent encoded, will decode
|
// ok - percent encoded, will decode
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if should_escape(s[i], .encode_path) {
|
if should_escape(s[i], .encode_path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -735,8 +737,8 @@ fn valid_optional_port(port string) bool {
|
||||||
// str reassembles the URL into a valid URL string.
|
// str reassembles the URL into a valid URL string.
|
||||||
// The general form of the result is one of:
|
// The general form of the result is one of:
|
||||||
//
|
//
|
||||||
// scheme:opaque?query#fragment
|
// scheme:opaque?query#fragment
|
||||||
// scheme://userinfo@host/path?query#fragment
|
// scheme://userinfo@host/path?query#fragment
|
||||||
//
|
//
|
||||||
// If u.opaque is non-empty, String uses the first form;
|
// If u.opaque is non-empty, String uses the first form;
|
||||||
// otherwise it uses the second form.
|
// otherwise it uses the second form.
|
||||||
|
@ -744,15 +746,15 @@ fn valid_optional_port(port string) bool {
|
||||||
// To obtain the path, String uses u.escaped_path().
|
// To obtain the path, String uses u.escaped_path().
|
||||||
//
|
//
|
||||||
// In the second form, the following rules apply:
|
// In the second form, the following rules apply:
|
||||||
// - if u.scheme is empty, scheme: is omitted.
|
// - if u.scheme is empty, scheme: is omitted.
|
||||||
// - if u.user is nil, userinfo@ is omitted.
|
// - if u.user is nil, userinfo@ is omitted.
|
||||||
// - if u.host is empty, host/ is omitted.
|
// - if u.host is empty, host/ is omitted.
|
||||||
// - if u.scheme and u.host are empty and u.user is nil,
|
// - if u.scheme and u.host are empty and u.user is nil,
|
||||||
// the entire scheme://userinfo@host/ is omitted.
|
// the entire scheme://userinfo@host/ is omitted.
|
||||||
// - if u.host is non-empty and u.path begins with a /,
|
// - if u.host is non-empty and u.path begins with a /,
|
||||||
// the form host/path does not add its own /.
|
// the form host/path does not add its own /.
|
||||||
// - if u.raw_query is empty, ?query is omitted.
|
// - if u.raw_query is empty, ?query is omitted.
|
||||||
// - if u.fragment is empty, #fragment is omitted.
|
// - if u.fragment is empty, #fragment is omitted.
|
||||||
pub fn (u &URL) str() string {
|
pub fn (u &URL) str() string {
|
||||||
mut buf := strings.new_builder(200)
|
mut buf := strings.new_builder(200)
|
||||||
if u.scheme != '' {
|
if u.scheme != '' {
|
||||||
|
@ -761,7 +763,8 @@ pub fn (u &URL) str() string {
|
||||||
}
|
}
|
||||||
if u.opaque != '' {
|
if u.opaque != '' {
|
||||||
buf.write(u.opaque)
|
buf.write(u.opaque)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if u.scheme != '' || u.host != '' || !u.user.empty() {
|
if u.scheme != '' || u.host != '' || !u.user.empty() {
|
||||||
if u.host != '' || u.path != '' || !u.user.empty() {
|
if u.host != '' || u.path != '' || !u.user.empty() {
|
||||||
buf.write('//')
|
buf.write('//')
|
||||||
|
@ -807,8 +810,6 @@ pub fn (u &URL) str() string {
|
||||||
// It is typically used for query parameters and form values.
|
// It is typically used for query parameters and form values.
|
||||||
// Unlike in the http.Header map, the keys in a Values map
|
// Unlike in the http.Header map, the keys in a Values map
|
||||||
// are case-sensitive.
|
// are case-sensitive.
|
||||||
|
|
||||||
|
|
||||||
// parseQuery parses the URL-encoded query string and returns
|
// parseQuery parses the URL-encoded query string and returns
|
||||||
// a map listing the values specified for each key.
|
// a map listing the values specified for each key.
|
||||||
// parseQuery always returns a non-nil map containing all the
|
// parseQuery always returns a non-nil map containing all the
|
||||||
|
@ -841,18 +842,19 @@ fn parse_query_values(m mut Values, query string) ?bool {
|
||||||
mut key := q
|
mut key := q
|
||||||
mut i := key.index_any('&;')
|
mut i := key.index_any('&;')
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
q = key[i+1..]
|
q = key[i + 1..]
|
||||||
key = key[..i]
|
key = key[..i]
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
q = ''
|
q = ''
|
||||||
}
|
}
|
||||||
if key == '' {
|
if key == '' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mut value := ''
|
mut value := ''
|
||||||
if idx := key.index('=') {
|
if idx:=key.index('='){
|
||||||
i = idx
|
i = idx
|
||||||
value = key[i+1..]
|
value = key[i + 1..]
|
||||||
key = key[..i]
|
key = key[..i]
|
||||||
}
|
}
|
||||||
k := query_unescape(key) or {
|
k := query_unescape(key) or {
|
||||||
|
@ -860,7 +862,6 @@ fn parse_query_values(m mut Values, query string) ?bool {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key = k
|
key = k
|
||||||
|
|
||||||
v := query_unescape(value) or {
|
v := query_unescape(value) or {
|
||||||
had_error = true
|
had_error = true
|
||||||
continue
|
continue
|
||||||
|
@ -907,10 +908,14 @@ fn resolve_path(base, ref string) string {
|
||||||
mut full := ''
|
mut full := ''
|
||||||
if ref == '' {
|
if ref == '' {
|
||||||
full = base
|
full = base
|
||||||
} else if ref[0] != `/` {
|
}
|
||||||
i := base.last_index('/') or { -1 }
|
else if ref[0] != `/` {
|
||||||
full = base[..i+1] + ref
|
i := base.last_index('/') or {
|
||||||
} else {
|
-1
|
||||||
|
}
|
||||||
|
full = base[..i + 1] + ref
|
||||||
|
}
|
||||||
|
else {
|
||||||
full = ref
|
full = ref
|
||||||
}
|
}
|
||||||
if full == '' {
|
if full == '' {
|
||||||
|
@ -925,17 +930,17 @@ fn resolve_path(base, ref string) string {
|
||||||
}
|
}
|
||||||
'..' {
|
'..' {
|
||||||
if dst.len > 0 {
|
if dst.len > 0 {
|
||||||
dst = dst[..dst.len-1]
|
dst = dst[..dst.len - 1]
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dst << elem
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
|
dst << elem
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
last := src[src.len-1]
|
last := src[src.len - 1]
|
||||||
if last == '.' || last == '..' {
|
if last == '.' || last == '..' {
|
||||||
// Add final slash to the joined path.
|
// Add final slash to the joined path.
|
||||||
dst << ''
|
dst << ''
|
||||||
}
|
}
|
||||||
return '/' + dst.join('/').trim_left('/')
|
return '/' + dst.join('/').trim_left('/')
|
||||||
}
|
}
|
||||||
|
@ -971,7 +976,9 @@ pub fn (u &URL) resolve_reference(ref &URL) ?URL {
|
||||||
// The 'absoluteURI' or 'net_path' cases.
|
// The 'absoluteURI' or 'net_path' cases.
|
||||||
// We can ignore the error from set_path since we know we provided a
|
// We can ignore the error from set_path since we know we provided a
|
||||||
// validly-escaped path.
|
// validly-escaped path.
|
||||||
url.set_path(resolve_path(ref.escaped_path(), '')) or {return error(err)}
|
url.set_path(resolve_path(ref.escaped_path(), '')) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
if ref.opaque != '' {
|
if ref.opaque != '' {
|
||||||
|
@ -989,7 +996,9 @@ pub fn (u &URL) resolve_reference(ref &URL) ?URL {
|
||||||
// The 'abs_path' or 'rel_path' cases.
|
// The 'abs_path' or 'rel_path' cases.
|
||||||
url.host = u.host
|
url.host = u.host
|
||||||
url.user = u.user
|
url.user = u.user
|
||||||
url.set_path(resolve_path(u.escaped_path(), ref.escaped_path())) or { return error(err) }
|
url.set_path(resolve_path(u.escaped_path(), ref.escaped_path())) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1010,7 +1019,8 @@ pub fn (u &URL) request_uri() string {
|
||||||
if result == '' {
|
if result == '' {
|
||||||
result = '/'
|
result = '/'
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if result.starts_with('//') {
|
if result.starts_with('//') {
|
||||||
result = u.scheme + ':' + result
|
result = u.scheme + ':' + result
|
||||||
}
|
}
|
||||||
|
@ -1026,43 +1036,40 @@ pub fn (u &URL) request_uri() string {
|
||||||
// If the result is enclosed in square brackets, as literal IPv6 addresses are,
|
// If the result is enclosed in square brackets, as literal IPv6 addresses are,
|
||||||
// the square brackets are removed from the result.
|
// the square brackets are removed from the result.
|
||||||
pub fn (u &URL) hostname() string {
|
pub fn (u &URL) hostname() string {
|
||||||
host, _ := split_host_port(u.host)
|
host,_ := split_host_port(u.host)
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
// port returns the port part of u.host, without the leading colon.
|
// port returns the port part of u.host, without the leading colon.
|
||||||
// If u.host doesn't contain a port, port returns an empty string.
|
// If u.host doesn't contain a port, port returns an empty string.
|
||||||
pub fn (u &URL) port() string {
|
pub fn (u &URL) port() string {
|
||||||
_, port := split_host_port(u.host)
|
_,port := split_host_port(u.host)
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
// split_host_port separates host and port. If the port is not valid, it returns
|
// split_host_port separates host and port. If the port is not valid, it returns
|
||||||
// the entire input as host, and it doesn't check the validity of the host.
|
// the entire input as host, and it doesn't check the validity of the host.
|
||||||
// Per RFC 3986, it requires ports to be numeric.
|
// Per RFC 3986, it requires ports to be numeric.
|
||||||
fn split_host_port(hostport string) (string, string) {
|
fn split_host_port(hostport string) (string,string) {
|
||||||
mut host := hostport
|
mut host := hostport
|
||||||
mut port := ''
|
mut port := ''
|
||||||
|
|
||||||
colon := host.last_index_byte(`:`)
|
colon := host.last_index_byte(`:`)
|
||||||
if colon != -1 && valid_optional_port(host[colon..]) {
|
if colon != -1 && valid_optional_port(host[colon..]) {
|
||||||
port = host[colon+1..]
|
port = host[colon + 1..]
|
||||||
host = host[..colon]
|
host = host[..colon]
|
||||||
}
|
}
|
||||||
|
|
||||||
if host.starts_with('[') && host.ends_with(']') {
|
if host.starts_with('[') && host.ends_with(']') {
|
||||||
host = host[1..host.len-1]
|
host = host[1..host.len - 1]
|
||||||
}
|
}
|
||||||
|
return host,port
|
||||||
return host, port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// valid_userinfo reports whether s is a valid userinfo string per RFC 3986
|
// valid_userinfo reports whether s is a valid userinfo string per RFC 3986
|
||||||
// Section 3.2.1:
|
// Section 3.2.1:
|
||||||
// userinfo = *( unreserved / pct-encoded / sub-delims / ':' )
|
// userinfo = *( unreserved / pct-encoded / sub-delims / ':' )
|
||||||
// unreserved = ALPHA / DIGIT / '-' / '.' / '_' / '~'
|
// unreserved = ALPHA / DIGIT / '-' / '.' / '_' / '~'
|
||||||
// sub-delims = '!' / '$' / '&' / ''' / '(' / ')'
|
// sub-delims = '!' / '$' / '&' / ''' / '(' / ')'
|
||||||
// / '*' / '+' / ',' / ';' / '='
|
// / '*' / '+' / ',' / ';' / '='
|
||||||
//
|
//
|
||||||
// It doesn't validate pct-encoded. The caller does that via fn unescape.
|
// It doesn't validate pct-encoded. The caller does that via fn unescape.
|
||||||
pub fn valid_userinfo(s string) bool {
|
pub fn valid_userinfo(s string) bool {
|
||||||
|
@ -1077,13 +1084,12 @@ pub fn valid_userinfo(s string) bool {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
match r {
|
match r {
|
||||||
`-`, `.`, `_`, `:`, `~`, `!`, `$`, `&`, `\\`,
|
`-`, `.`, `_`, `:`, `~`, `!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `%`, `@` {
|
||||||
`(`, `)`, `*`, `+`, `,`, `;`, `=`, `%`, `@` {
|
continue
|
||||||
continue
|
}
|
||||||
} else {
|
else {
|
||||||
return false
|
return false
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1102,9 +1108,11 @@ fn string_contains_ctl_byte(s string) bool {
|
||||||
pub fn ishex(c byte) bool {
|
pub fn ishex(c byte) bool {
|
||||||
if `0` <= c && c <= `9` {
|
if `0` <= c && c <= `9` {
|
||||||
return true
|
return true
|
||||||
} else if `a` <= c && c <= `f` {
|
}
|
||||||
|
else if `a` <= c && c <= `f` {
|
||||||
return true
|
return true
|
||||||
} else if `A` <= c && c <= `F` {
|
}
|
||||||
|
else if `A` <= c && c <= `F` {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -1113,10 +1121,13 @@ pub fn ishex(c byte) bool {
|
||||||
fn unhex(c byte) byte {
|
fn unhex(c byte) byte {
|
||||||
if `0` <= c && c <= `9` {
|
if `0` <= c && c <= `9` {
|
||||||
return c - `0`
|
return c - `0`
|
||||||
} else if `a` <= c && c <= `f` {
|
}
|
||||||
|
else if `a` <= c && c <= `f` {
|
||||||
return c - `a` + 10
|
return c - `a` + 10
|
||||||
} else if `A` <= c && c <= `F` {
|
}
|
||||||
|
else if `A` <= c && c <= `F` {
|
||||||
return c - `A` + 10
|
return c - `A` + 10
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module urllib
|
module urllib
|
||||||
|
|
||||||
struct Value {
|
struct Value {
|
||||||
|
@ -86,3 +85,4 @@ pub fn (v mut Values) del(key string) {
|
||||||
v.data.delete(key)
|
v.data.delete(key)
|
||||||
v.size = v.data.size
|
v.size = v.data.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
vlib/os/os.v
20
vlib/os/os.v
|
@ -178,7 +178,7 @@ pub fn cp_r(osource_path, odest_path string, overwrite bool) ?bool {
|
||||||
return error('Destination file path already exist')
|
return error('Destination file path already exist')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.cp(source_path, adjasted_path)or{
|
os.cp(source_path, adjasted_path) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -186,18 +186,18 @@ pub fn cp_r(osource_path, odest_path string, overwrite bool) ?bool {
|
||||||
if !os.is_dir(dest_path) {
|
if !os.is_dir(dest_path) {
|
||||||
return error('Destination path is not a valid directory')
|
return error('Destination path is not a valid directory')
|
||||||
}
|
}
|
||||||
files := os.ls(source_path)or{
|
files := os.ls(source_path) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
for file in files {
|
for file in files {
|
||||||
sp := filepath.join(source_path,file)
|
sp := filepath.join(source_path,file)
|
||||||
dp := filepath.join(dest_path,file)
|
dp := filepath.join(dest_path,file)
|
||||||
if os.is_dir(sp) {
|
if os.is_dir(sp) {
|
||||||
os.mkdir(dp)or{
|
os.mkdir(dp) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cp_r(sp, dp, overwrite)or{
|
cp_r(sp, dp, overwrite) or {
|
||||||
os.rmdir(dp)
|
os.rmdir(dp)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ pub fn cp_r(osource_path, odest_path string, overwrite bool) ?bool {
|
||||||
// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file.
|
// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file.
|
||||||
// mv_by_cp may be used when you are not sure that the source and target are on the same mount/partition.
|
// mv_by_cp may be used when you are not sure that the source and target are on the same mount/partition.
|
||||||
pub fn mv_by_cp(source string, target string) ?bool {
|
pub fn mv_by_cp(source string, target string) ?bool {
|
||||||
os.cp(source, target)or{
|
os.cp(source, target) or {
|
||||||
return error(err)
|
return error(err)
|
||||||
}
|
}
|
||||||
os.rm(source)
|
os.rm(source)
|
||||||
|
@ -259,7 +259,7 @@ pub fn read_lines(path string) ?[]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_ulines(path string) ?[]ustring {
|
fn read_ulines(path string) ?[]ustring {
|
||||||
lines := read_lines(path)or{
|
lines := read_lines(path) or {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// mut ulines := new_array(0, lines.len, sizeof(ustring))
|
// mut ulines := new_array(0, lines.len, sizeof(ustring))
|
||||||
|
@ -768,7 +768,7 @@ pub fn home_dir() string {
|
||||||
|
|
||||||
// write_file writes `text` data to a file in `path`.
|
// write_file writes `text` data to a file in `path`.
|
||||||
pub fn write_file(path, text string) {
|
pub fn write_file(path, text string) {
|
||||||
mut f := os.create(path)or{
|
mut f := os.create(path) or {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.write(text)
|
f.write(text)
|
||||||
|
@ -966,7 +966,7 @@ pub fn walk_ext(path, ext string) []string {
|
||||||
if !os.is_dir(path) {
|
if !os.is_dir(path) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
mut files := os.ls(path)or{
|
mut files := os.ls(path) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
mut res := []string
|
mut res := []string
|
||||||
|
@ -992,7 +992,7 @@ pub fn walk(path string, fnc fn(path string)) {
|
||||||
if !os.is_dir(path) {
|
if !os.is_dir(path) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mut files := os.ls(path)or{
|
mut files := os.ls(path) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for file in files {
|
for file in files {
|
||||||
|
@ -1072,7 +1072,7 @@ pub fn mkdir_all(path string) {
|
||||||
for subdir in path.split(os.path_separator) {
|
for subdir in path.split(os.path_separator) {
|
||||||
p += subdir + os.path_separator
|
p += subdir + os.path_separator
|
||||||
if !os.is_dir(p) {
|
if !os.is_dir(p) {
|
||||||
os.mkdir(p)or{
|
os.mkdir(p) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,7 +489,6 @@ fn converter(pn mut PrepNumber) u64 {
|
||||||
}
|
}
|
||||||
s1 = s1 & check_round_mask
|
s1 = s1 & check_round_mask
|
||||||
s0 = u32(0)
|
s0 = u32(0)
|
||||||
|
|
||||||
// recheck normalization
|
// recheck normalization
|
||||||
if s2 & (mask28<<u32(1)) != 0 {
|
if s2 & (mask28<<u32(1)) != 0 {
|
||||||
// C.printf("Renormalize!!")
|
// C.printf("Renormalize!!")
|
||||||
|
@ -500,7 +499,6 @@ fn converter(pn mut PrepNumber) u64 {
|
||||||
s0 = q0
|
s0 = q0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmp := ( u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8)
|
// tmp := ( u64(s2 & ~mask28) << 24) | ((u64(s1) + u64(128)) >> 8)
|
||||||
// C.printf("mantissa after rounding : %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp)
|
// C.printf("mantissa after rounding : %08x%08x%08x binexp: %d \n", s2,s1,s0,binexp)
|
||||||
// C.printf("Tmp result: %016x\n",tmp)
|
// C.printf("Tmp result: %016x\n",tmp)
|
||||||
|
@ -518,7 +516,8 @@ fn converter(pn mut PrepNumber) u64 {
|
||||||
else if binexp < 1 {
|
else if binexp < 1 {
|
||||||
if pn.negative {
|
if pn.negative {
|
||||||
result = DOUBLE_MINUS_ZERO
|
result = DOUBLE_MINUS_ZERO
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
result = DOUBLE_PLUS_ZERO
|
result = DOUBLE_PLUS_ZERO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module sync
|
module sync
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
fn C.pthread_mutex_init()
|
fn C.pthread_mutex_init()
|
||||||
|
|
||||||
|
|
||||||
fn C.pthread_mutex_lock()
|
fn C.pthread_mutex_lock()
|
||||||
|
|
||||||
|
|
||||||
fn C.pthread_mutex_unlock()
|
fn C.pthread_mutex_unlock()
|
||||||
|
// [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function.
|
||||||
|
|
||||||
//[init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function.
|
|
||||||
pub struct Mutex {
|
pub struct Mutex {
|
||||||
mutex C.pthread_mutex_t
|
mutex C.pthread_mutex_t
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_mutex() Mutex {
|
pub fn new_mutex() Mutex {
|
||||||
m := Mutex{}
|
m := Mutex{
|
||||||
C.pthread_mutex_init( &m.mutex, C.NULL)
|
}
|
||||||
|
C.pthread_mutex_init(&m.mutex, C.NULL)
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
||||||
// Use of this source code is governed by an MIT license
|
// Use of this source code is governed by an MIT license
|
||||||
// that can be found in the LICENSE file.
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
module sync
|
module sync
|
||||||
|
// [init_with=new_waitgroup] // TODO: implement support for init_with struct attribute, and disallow WaitGroup{} from outside the sync.new_waitgroup() function.
|
||||||
//[init_with=new_waitgroup] // TODO: implement support for init_with struct attribute, and disallow WaitGroup{} from outside the sync.new_waitgroup() function.
|
|
||||||
pub struct WaitGroup {
|
pub struct WaitGroup {
|
||||||
mut:
|
mut:
|
||||||
mu Mutex
|
mu Mutex
|
||||||
active int
|
active int
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_waitgroup() WaitGroup {
|
pub fn new_waitgroup() WaitGroup {
|
||||||
mut w := WaitGroup{}
|
mut w := WaitGroup{
|
||||||
|
}
|
||||||
w.mu = sync.new_mutex()
|
w.mu = sync.new_mutex()
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
@ -41,3 +40,4 @@ pub fn (wg &WaitGroup) wait() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue