run vfmt on http, net, sync, strconv

pull/3182/head
Alexander Medvednikov 2019-12-22 01:41:42 +03:00
parent 28ecfb231d
commit 848cd3cb3e
18 changed files with 523 additions and 404 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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