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.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
import http
import json
import sync
@ -12,15 +11,15 @@ const (
struct Story {
title string
url string
url string
}
struct Fetcher {
mut:
mu &sync.Mutex
ids []int
cursor int
wg &sync.WaitGroup
mu &sync.Mutex
ids []int
cursor int
wg &sync.WaitGroup
}
fn (f mut Fetcher) fetch() {
@ -37,7 +36,7 @@ fn (f mut Fetcher) fetch() {
println('failed to fetch data from /v0/item/${id}.json')
exit(1)
}
story := json.decode(Story, resp.text) or {
story := json.decode(Story,resp.text) or {
println('failed to decode a story')
exit(1)
}
@ -52,22 +51,25 @@ fn main() {
println('failed to fetch data from /v0/topstories.json')
return
}
mut ids := json.decode([]int, resp.text) or {
mut ids := json.decode([]int,resp.text) or {
println('failed to decode topstories.json')
return
}
if ids.len > 10 {
// ids = ids[:10]
mut tmp := [0].repeat(10)
for i := 0 ; i < 10 ; i++ {
for i := 0; i < 10; i++ {
tmp[i] = ids[i]
}
ids = tmp
}
wg := sync.new_waitgroup()
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.wg = &wg
fetcher.wg.add(ids.len)

View File

@ -181,9 +181,10 @@ fn (p mut Parser) fnext() {
comment := comment_token.lit
// Newline before the comment, but not between two // comments,
// 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 &&
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()
}
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.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module http
import strings
// On linux, prefer a localy build openssl, because it is
// much more likely for it to be newer, than the system
// openssl from libssl-dev. If there is no local openssl,
// the next flag is harmless, since it will still use the
// (older) system openssl.
#flag linux -I/usr/local/include/openssl -L/usr/local/lib
#flag -l ssl -l crypto
// MacPorts
#flag darwin -I/opt/local/include
@ -20,40 +17,74 @@ import strings
// Brew
#flag darwin -I/usr/local/opt/openssl/include
#flag darwin -L/usr/local/opt/openssl/lib
#include <openssl/ssl.h>
struct C.SSL {
}
fn C.SSL_library_init()
fn C.TLSv1_2_method() voidptr
fn C.SSL_CTX_set_options()
fn C.SSL_CTX_new() voidptr
fn C.SSL_CTX_set_verify_depth()
fn C.SSL_CTX_load_verify_locations() int
fn C.BIO_new_ssl_connect() voidptr
fn C.BIO_set_conn_hostname() int
fn C.BIO_get_ssl()
fn C.SSL_set_cipher_list() int
fn C.BIO_do_connect() int
fn C.BIO_do_handshake() int
fn C.SSL_get_peer_certificate() int
fn C.SSL_get_verify_result() int
fn C.SSL_set_tlsext_host_name() int
fn C.BIO_puts()
fn C.BIO_read()
fn C.BIO_free_all()
fn C.SSL_CTX_free()
fn init() int {
C.SSL_library_init()
return 1
}
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()
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)
C.SSL_get_peer_certificate(ssl)
res = C.SSL_get_verify_result(ssl)
///////
// /////
s := req.build_request_headers(method, host_name, path)
C.BIO_puts(web, s.str)
mut sb := strings.new_builder(100)
for {
buff := [1536]byte
len := int(C.BIO_read(web, buff, 1536) )
len := int(C.BIO_read(web, buff, 1536))
if len > 0 {
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) {
C.SSL_CTX_free(ctx)
}
return parse_response(sb.str())
}

View File

@ -1,27 +1,29 @@
module chunked
import strings
// See: https://en.wikipedia.org/wiki/Chunked_transfer_encoding
///////////////////////////////////////////////////////////////
// The chunk size is transferred as a hexadecimal number
// followed by \r\n as a line separator,
// /////////////////////////////////////////////////////////////
// The chunk size is transferred as a hexadecimal number
// followed by \r\n as a line separator,
// followed by a chunk of data of the given size.
// The end is marked with a chunk with size 0.
struct ChunkScanner {
mut:
pos int
pos int
text string
}
fn (s mut ChunkScanner) read_chunk_size() int {
mut n := 0
for {
if s.pos >= s.text.len { break }
if s.pos >= s.text.len {
break
}
c := s.text[s.pos]
if !c.is_hex_digit() { break }
n = n << 4
if !c.is_hex_digit() {
break
}
n = n<<4
n += int(unhex(c))
s.pos++
}
@ -29,9 +31,15 @@ fn (s mut ChunkScanner) read_chunk_size() int {
}
fn unhex(c byte) byte {
if `0` <= c && c <= `9` { return c - `0` }
else if `a` <= c && c <= `f` { return c - `a` + 10 }
else if `A` <= c && c <= `F` { return c - `A` + 10 }
if `0` <= c && c <= `9` {
return c - `0`
}
else if `a` <= c && c <= `f` {
return c - `a` + 10
}
else if `A` <= c && c <= `F` {
return c - `A` + 10
}
return 0
}
@ -47,17 +55,20 @@ fn (s mut ChunkScanner) read_chunk(chunksize int) string {
pub fn decode(text string) string {
mut sb := strings.new_builder(100)
mut cscanner := ChunkScanner {
mut cscanner := ChunkScanner{
pos: 0
text: text
}
for {
csize := cscanner.read_chunk_size()
if 0 == csize { break }
if 0 == csize {
break
}
cscanner.skip_crlf()
sb.write( cscanner.read_chunk(csize) )
sb.write(cscanner.read_chunk(csize))
cscanner.skip_crlf()
}
cscanner.skip_crlf()
return sb.str()
}

View File

@ -1,14 +1,16 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module http
import os
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)
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.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module http
type downloadfn fn (written int)
type download_finished_fn fn ()
type downloadfn fn(written int)type download_finished_fn fn()
/*
struct DownloadStruct {
mut:
@ -15,9 +12,8 @@ mut:
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)
written := C.fwrite(ptr, size, nmemb, data.stream)
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()) {
/*
/*
curl := C.curl_easy_init()
if isnil(curl) {
return
@ -52,6 +48,5 @@ pub fn download_file_with_progress(url, out string, cb downloadfn, cb_finished f
}
fn empty() {
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module http
import net.urllib
@ -13,19 +12,19 @@ const (
pub struct Request {
pub:
headers map[string]string
method string
headers map[string]string
method string
// cookies map[string]string
h string
cmd string
typ string // GET POST
data string
url string
verbose bool
h string
cmd string
typ string // GET POST
data string
url string
verbose bool
user_agent string
mut:
user_ptr voidptr
ws_func voidptr
user_ptr voidptr
ws_func voidptr
}
pub struct Response {
@ -67,7 +66,7 @@ pub fn new_request(typ, _url, _data string) ?Request {
url = '$url?$data'
data = ''
}
return Request {
return Request{
typ: typ
url: url
data: data
@ -79,7 +78,9 @@ pub fn new_request(typ, _url, _data string) ?Request {
}
pub fn get_text(url string) string {
resp := get(url) or { return '' }
resp := get(url) or {
return ''
}
return resp.text
}
@ -116,18 +117,29 @@ pub fn (req &Request) do() ?Response {
if req.typ == 'POST' {
// 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 resp := Response{}
mut resp := Response{
}
mut no_redirects := 0
for {
if no_redirects == max_redirects { 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) }
if no_redirects == max_redirects {
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
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
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
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' }
mut nport := url.port().int()
if nport == 0 {
if scheme == 'http' { nport = 80 }
if scheme == 'https' { nport = 443 }
if scheme == 'http' {
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' {
//println('ssl_do( $nport, $method, $host_name, $path )')
res := req.ssl_do( nport, method, host_name, path ) or {
// println('ssl_do( $nport, $method, $host_name, $path )')
res := req.ssl_do(nport, method, host_name, path) or {
return error(err)
}
return res
} else if scheme == 'http' {
//println('http_do( $nport, $method, $host_name, $path )')
res := req.http_do(nport, method, host_name, path ) or {
}
else if scheme == 'http' {
// println('http_do( $nport, $method, $host_name, $path )')
res := req.http_do(nport, method, host_name, path) or {
return error(err)
}
return res
@ -175,7 +192,7 @@ fn parse_response(resp string) Response {
mut i := 1
for {
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 {
break
}
@ -189,19 +206,17 @@ fn parse_response(resp string) Response {
pos := h.index(':') or {
continue
}
//if h.contains('Content-Type') {
//continue
//}
// if h.contains('Content-Type') {
// continue
// }
key := h[..pos]
val := h[pos+2..]
val := h[pos + 2..]
headers[key] = val.trim_space()
}
if headers['Transfer-Encoding'] == 'chunked' {
text = chunked.decode( text )
text = chunked.decode(text)
}
return Response {
return Response{
status_code: status_code
headers: headers
text: text
@ -217,12 +232,7 @@ fn (req &Request) build_request_headers(method, host_name, path string) string {
if req.data.len > 0 {
uheaders << 'Content-Length: ${req.data.len}\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
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
}
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()')
}
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
mut sb := strings.new_builder(100)
s := req.build_request_headers(method, host_name, path)
client := net.dial( host_name, port) or { return error(err) }
client.send( s.str, s.len ) or {}
for {
readbytes := client.crecv( rbuffer, bufsize )
if readbytes < 0 { return error('http.request.http_do: error reading response. readbytes=$readbytes') }
if readbytes == 0 { break }
sb.write( tos(rbuffer, readbytes) )
client := net.dial(host_name, port) or {
return error(err)
}
client.send(s.str, s.len) or {
}
for {
readbytes := client.crecv(rbuffer, bufsize)
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())
}

View File

@ -1,13 +1,11 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module json
#flag -I @VROOT/thirdparty/cJSON
#flag @VROOT/thirdparty/cJSON/cJSON.o
#include "cJSON.h"
struct C.cJSON {
valueint int
valuedouble f32
@ -39,7 +37,7 @@ fn jsdecode_i64(root &C.cJSON) i64 {
if isnil(root) {
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 {
@ -84,7 +82,6 @@ fn jsdecode_f64(root &C.cJSON) f64 {
return f64(root.valuedouble)
}
fn jsdecode_string(root &C.cJSON) string {
if isnil(root) {
return ''
@ -94,16 +91,27 @@ fn jsdecode_string(root &C.cJSON) string {
}
// println('jsdecode string valuestring="$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_CreateNumber() &C.cJSON
fn C.cJSON_CreateBool() &C.cJSON
fn C.cJSON_CreateString() &C.cJSON
fn C.cJSON_Parse() &C.cJSON
fn C.cJSON_PrintUnformatted() byteptr
fn jsdecode_bool(root &C.cJSON) bool {
if isnil(root) {
return false
@ -161,7 +169,6 @@ fn jsencode_string(val string) &C.cJSON {
return C.cJSON_CreateString(clone.str)
// return C.cJSON_CreateString2(val.str, val.len)
}
// ///////////////////////
// user := decode_User(json_parse(js_string_var))
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) {
// #cJSON_ArrayForEach (val ,root)
// }

View File

@ -5,11 +5,11 @@ module net
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
fn error_code() int {
return C.errno
return C.errno
}
pub const (
MSG_NOSIGNAL = 0x4000
)

View File

@ -1,16 +1,15 @@
module net
fn C.gethostname() int
// hostname returns the host name reported by the kernel.
pub fn hostname() ?string {
mut name := [256]byte
mut name := [256]byte
// https://www.ietf.org/rfc/rfc1035.txt
// The host name is returned as a null-terminated string.
res := C.gethostname(&name, 256)
if res != 0 {
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:
sockfd int
family int
_type int
proto int
_type int
proto int
}
struct C.in_addr {
mut:
s_addr int
@ -19,52 +18,82 @@ mut:
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
sin_port int
sin_addr C.in_addr
}
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
ai_next voidptr
}
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
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
// create socket
pub fn new_socket(family int, _type int, proto int) ?Socket {
sockfd := C.socket(family, _type, proto)
one:=1
one := 1
// This is needed so that there are no problems with reusing the
// same port after the application exits.
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(int))
if sockfd == 0 {
return error('net.socket: failed')
}
s := Socket {
s := Socket{
sockfd: sockfd
family: family
_type: _type
@ -88,7 +117,8 @@ pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
// bind socket to port
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_port = C.htons(port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
@ -148,13 +178,14 @@ pub fn (s Socket) accept() ?Socket {
$if debug {
println('accept()')
}
addr := C.sockaddr_storage{}
addr := C.sockaddr_storage{
}
size := 128 // sizeof(sockaddr_storage)
sockfd := C.accept(s.sockfd, &addr, &size)
if sockfd < 0 {
return error('net.accept: failed with $sockfd')
}
c := Socket {
c := Socket{
sockfd: sockfd
family: s.family
_type: s._type
@ -165,7 +196,8 @@ pub fn (s Socket) accept() ?Socket {
// connect to given addrress and port
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_socktype = s._type
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_addr = C.NULL
hints.ai_next = C.NULL
info := &C.addrinfo(0)
sport := '$port'
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
for {
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
if dlen <= 0 { break }
if dlen <= 0 {
break
}
dptr += sbytes
}
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)
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
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
pub fn (s Socket) recv(bufsize int) (byteptr,int) {
buf := malloc(bufsize)
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
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)
}
// 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.
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)
}
@ -243,27 +278,23 @@ pub fn (s Socket) close() ?int {
mut shutdown_res := 0
$if windows {
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
}
$else {
} $else {
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
}
// TODO: should shutdown throw an error? close will
// continue even if shutdown failed
// if shutdown_res < 0 {
// return error('net.close: shutdown failed with $shutdown_res')
// }
// if shutdown_res < 0 {
// return error('net.close: shutdown failed with $shutdown_res')
// }
mut res := 0
$if windows {
res = C.closesocket(s.sockfd)
}
$else {
} $else {
res = C.close(s.sockfd)
}
if res < 0 {
return error('net.close: failed with $res')
}
return 0
}
@ -272,12 +303,13 @@ pub const (
MAX_READ = 400
MSG_PEEK = 0x02
)
// write - write a string with CRLF after it over the socket s
pub fn (s Socket) write(str string) ?int {
line := '$str$CRLF'
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
}
@ -287,9 +319,13 @@ pub fn (s Socket) read_line() string {
mut res := '' // The final result, including the ending \n.
for {
mut line := '' // The current line. Can be a partial without \n in it.
n := C.recv(s.sockfd, buf, MAX_READ-1, MSG_PEEK)
if n == -1 { return res }
if n == 0 { return res }
n := C.recv(s.sockfd, buf, MAX_READ - 1, MSG_PEEK)
if n == -1 {
return res
}
if n == 0 {
return res
}
buf[n] = `\0`
mut eol_idx := -1
for i := 0; i < n; i++ {
@ -298,7 +334,7 @@ pub fn (s Socket) read_line() string {
// Ensure that tos_clone(buf) later,
// will return *only* the first line (including \n),
// and ignore the rest
buf[i+1] = `\0`
buf[i + 1] = `\0`
break
}
}
@ -309,7 +345,7 @@ pub fn (s Socket) read_line() string {
// Ensure that the block till the first \n (including it)
// is removed from the socket's receive queue, so that it does
// not get read again.
C.recv(s.sockfd, buf, eol_idx+1, 0)
C.recv(s.sockfd, buf, eol_idx + 1, 0)
res += line
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 res := '' // The final result, including the ending \n.
for {
n := C.recv(s.sockfd, buf, MAX_READ-1, 0)
if n == -1 { return res }
if n == 0 { return res }
n := C.recv(s.sockfd, buf, MAX_READ - 1, 0)
if n == -1 {
return res
}
if n == 0 {
return res
}
res += tos_clone(buf)
}
return res
}
pub fn (s Socket) get_port() int {
mut addr := C.sockaddr_in {}
mut addr := C.sockaddr_in{
}
size := 16 // sizeof(sockaddr_in)
C.getsockname(s.sockfd, &addr, &size)
return C.ntohs(addr.sin_port)
}

View File

@ -1,14 +1,11 @@
// urllib parses URLs and implements query escaping.
// See RFC 3986. This module generally follows RFC 3986, except where
// it deviates for compatibility reasons.
// Based off: https://github.com/golang/go/blob/master/src/net/url/url.go
// Last commit: https://github.com/golang/go/commit/fe2ed5054176935d4adcf13e891715ccf2ee3cce
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
module urllib
import strings
@ -25,12 +22,14 @@ enum EncodingMode {
const (
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 {
mut msg := 'net.urllib.$message'
if val != '' { msg = '$msg ($val)' }
if val != '' {
msg = '$msg ($val)'
}
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`) {
return false
}
if mode == .encode_host || mode == .encode_zone {
// §3.2.2 host allows
// sub-delims = `!` / `$` / `&` / ``` / `(` / `)` / `*` / `+` / `,` / `;` / `=`
// sub-delims = `!` / `$` / `&` / ``` / `(` / `)` / `*` / `+` / `,` / `;` / `=`
// as part of reg-name.
// We add : because we include :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
// escape them (because hosts can`t use %-encoding for
// ASCII bytes).
if c in [`!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`,
`:`, `[`, `]`, `<`, `>`, `"`]
{
if c in [`!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `:`, `[`, `]`, `<`, `>`, `"`] {
return false
}
}
match c {
`-`, `_`, `.`, `~` { // §2.3 Unreserved characters (mark)
`-`, `_`, `.`, `~` {
// §2.3 Unreserved characters (mark)
return false
}
`$`, `&`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@` { // §2.2 Reserved characters (reserved)
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
match mode {
.encode_path { // §3.3
// The RFC allows : @ & = + $ but saves / ; , for assigning
// meaning to individual path segments. This package
// only manipulates the path as a whole, so we allow those
// last three as well. That leaves only ? to escape.
return c == `?`
`$`, `&`, `+`, `,`, `/`, `:`, `;`, `=`, `?`, `@` {
// §2.2 Reserved characters (reserved)
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
match mode {
.encode_path {
// §3.3
// The RFC allows : @ & = + $ but saves / ; , for assigning
// meaning to individual path segments. This package
// only manipulates the path as a whole, so we allow those
// 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 {
}}
}
.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 {}
}
else {
}}
if mode == .encode_fragment {
// 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
@ -116,13 +113,12 @@ fn should_escape(c byte, mode EncodingMode) bool {
// escape single quote to avoid breaking callers that had previously assumed that
// single quotes would be escaped. See issue #19917.
match c {
`!`, `(`, `)`, `*`{
`!`, `(`, `)`, `*` {
return false
}
else {}
}
else {
}}
}
// Everything else must be escaped.
return true
}
@ -154,7 +150,7 @@ fn unescape(s_ string, mode EncodingMode) ?string {
// Count %, check that they're well-formed.
mut n := 0
mut has_plus := false
for i := 0; i < s.len; {
for i := 0; i < s.len; {
x := s[i]
match x {
`%` {
@ -162,7 +158,7 @@ fn unescape(s_ string, mode EncodingMode) ?string {
break
}
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..]
if s.len > 3 {
s = s[..3]
@ -175,8 +171,8 @@ fn unescape(s_ string, mode EncodingMode) ?string {
// But https://tools.ietf.org/html/rfc6874#section-2
// introduces %25 being allowed to escape a percent sign
// in IPv6 scoped-address literals. Yay.
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]))
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]))
}
if mode == .encode_zone {
// 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
// to introduce bytes you couldn't just write directly.
// But Windows puts spaces here! Yay.
v := (unhex(s[i+1])<<byte(4) | unhex(s[i+2]))
if s[i..i+3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
error(error_msg(err_msg_escape, s[i..i+3]))
v := (unhex(s[i + 1])<<byte(4) | unhex(s[i + 2]))
if s[i..i + 3] != '%25' && v != ` ` && should_escape(v, .encode_host) {
error(error_msg(err_msg_escape, s[i..i + 3]))
}
}
i += 3
}
`+`{
`+` {
has_plus = mode == .encode_query_component
i++
} else {
}
else {
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++
}
}
}}
}
if n == 0 && !has_plus {
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++ {
x := s[i]
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
}
`+` {
if mode == .encode_query_component {
t.write(' ')
} else {
}
else {
t.write('+')
}
} else {
t.write(s[i].str())
}
}
else {
t.write(s[i].str())
}}
}
return t.str()
}
@ -252,26 +247,24 @@ fn escape(s string, mode EncodingMode) string {
if should_escape(c, mode) {
if c == ` ` && mode == .encode_query_component {
space_count++
} else {
}
else {
hex_count++
}
}
}
if space_count == 0 && hex_count == 0 {
return s
}
buf := [byte(0)].repeat(64)
mut t := []byte
required := s.len + 2*hex_count
required := s.len + 2 * hex_count
if required <= buf.len {
t = buf[..required]
} else {
}
else {
t = [byte(0)].repeat(required)
}
if hex_count == 0 {
copy(t, s.bytes())
for i := 0; i < s.len; i++ {
@ -279,9 +272,8 @@ fn escape(s string, mode EncodingMode) string {
t[i] = `+`
}
}
return string(t, t.len)
return string(t,t.len)
}
upperhex := '0123456789ABCDEF'
mut j := 0
for i := 0; i < s.len; i++ {
@ -289,28 +281,30 @@ fn escape(s string, mode EncodingMode) string {
if c1 == ` ` && mode == .encode_query_component {
t[j] = `+`
j++
} else if should_escape(c1, mode) {
}
else if should_escape(c1, mode) {
t[j] = `%`
t[j+1] = upperhex[c1>>4]
t[j+2] = upperhex[c1&15]
t[j + 1] = upperhex[c1>>4]
t[j + 2] = upperhex[c1 & 15]
j += 3
} else {
}
else {
t[j] = s[i]
j++
}
}
return string(t, t.len)
return string(t,t.len)
}
// A URL represents a parsed URL (technically, a URI reference).
//
// 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:
//
// scheme:opaque[?query][#fragment]
// scheme:opaque[?query][#fragment]
//
// 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
@ -323,22 +317,21 @@ fn escape(s string, mode EncodingMode) string {
pub struct URL {
pub mut:
scheme string
opaque string // encoded opaque data
opaque string // encoded opaque data
user &Userinfo // username and password information
host string // host or host:port
path string // path (relative paths may omit leading slash)
raw_path string // encoded path hint (see escaped_path method)
force_query bool // append a query ('?') even if raw_query is empty
raw_query string // encoded query values, without '?'
fragment string // fragment for references, without '#'
host string // host or host:port
path string // path (relative paths may omit leading slash)
raw_path string // encoded path hint (see escaped_path method)
force_query bool // append a query ('?') even if raw_query is empty
raw_query string // encoded query values, without '?'
fragment string // fragment for references, without '#'
}
// user returns a Userinfo containing the provided username
// and no password set.
pub fn user(username string) &Userinfo {
return &Userinfo{
username: username,
password: '',
username: username
password: ''
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
// security risk in almost every case where it has been used.''
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
@ -401,7 +395,7 @@ fn split_by_scheme(rawurl string) ?[]string {
if i == 0 {
return error(error_msg('split_by_scheme: missing protocol scheme', ''))
}
return [rawurl[..i], rawurl[i+1..]]
return [rawurl[..i], rawurl[i + 1..]]
}
else {
// 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
// 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.
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)
if i < 0 {
return s, ''
return s,''
}
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.
@ -441,7 +435,7 @@ fn split(s string, sep byte, cutc bool) (string, string) {
// error, due to parsing ambiguities.
pub fn parse(rawurl string) ?URL {
// Cut off #frag
u, frag := split(rawurl, `#`, true)
u,frag := split(rawurl, `#`, true)
mut url := parse_url(u, false) or {
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) {
return error(error_msg('parse_url: invalid control character in URL', rawurl))
}
if rawurl == '' && via_request {
return error(error_msg('parse_url: empty URL', rawurl))
}
mut url := URL{user:0}
mut url := URL{
user: 0
}
if rawurl == '*' {
url.path = '*'
return url
}
// Split off possible leading 'http:', 'mailto:', etc.
// Cannot contain escaped characters.
p := split_by_scheme(rawurl) or {
@ -491,17 +484,16 @@ fn parse_url(rawurl string, via_request bool) ?URL {
url.scheme = p[0]
mut rest := p[1]
url.scheme = url.scheme.to_lower()
// if rest.ends_with('?') && strings.count(rest, '?') == 1 {
if rest.ends_with('?') && !rest[..1].contains('?') {
url.force_query = true
rest = rest[..rest.len-1]
} else {
r, raw_query := split(rest, `?`, true)
rest = rest[..rest.len - 1]
}
else {
r,raw_query := split(rest, `?`, true)
rest = r
url.raw_query = raw_query
}
if !rest.starts_with('/') {
if url.scheme != '' {
// 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 {
return error(error_msg('parse_url: invalid URI for request', ''))
}
// Avoid confusion with malformed schemes, like cache_object:foo/bar.
// See golang.org/issue/16822.
//
// RFC 3986, §3.3:
// 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.
colon := rest.index(':') or { return error('there should be a : in the URL') }
slash := rest.index('/') or { return error('there should be a / in the URL') }
colon := rest.index(':') or {
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) {
// First path segment has colon. Not allowed in relative URL.
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('//') {
authority, r := split(rest[2..], `/`, false)
authority,r := split(rest[2..], `/`, false)
rest = r
a := parse_authority(authority) or {
return error(err)
@ -551,7 +545,9 @@ struct ParseAuthorityRes {
}
fn parse_authority(authority string) ?ParseAuthorityRes {
i := authority.last_index('@') or { -1 }
i := authority.last_index('@') or {
-1
}
mut host := ''
mut zuser := user('')
if i < 0 {
@ -559,14 +555,18 @@ fn parse_authority(authority string) ?ParseAuthorityRes {
return error(err)
}
host = h
} else {
h := parse_host(authority[i+1..]) or {
}
else {
h := parse_host(authority[i + 1..]) or {
return error(err)
}
host = h
}
if i < 0 {
return ParseAuthorityRes{host: host, user: zuser}
return ParseAuthorityRes{
host: host
user: zuser
}
}
mut userinfo := authority[..i]
if !valid_userinfo(userinfo) {
@ -578,8 +578,9 @@ fn parse_authority(authority string) ?ParseAuthorityRes {
}
userinfo = u
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 {
return error(err)
}
@ -603,20 +604,19 @@ fn parse_host(host string) ?string {
// parse an IP-Literal in RFC 3986 and RFC 6874.
// E.g., '[fe80::1]', '[fe80::1%25en0]', '[fe80::1]:80'.
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) {
return error(error_msg('parse_host: invalid port $colon_port after host ', ''))
}
// RFC 6874 defines that %25 (%-encoded percent) introduces
// the zone identifier, and the zone identifier can use basically
// any %-encoding it likes. That's different from the host, which
// can only %-encode non-ASCII bytes.
// We do impose some restrictions on the zone, to avoid stupidity
// like newlines.
if zone := host[..i].index('%25') {
if zone:=host[..i].index('%25'){
host1 := unescape(host[..zone], .encode_host) or {
return err
}
@ -628,7 +628,7 @@ fn parse_host(host string) ?string {
}
return host1 + host2 + host3
}
if idx := host.last_index(':') {
if idx:=host.last_index(':'){
colon_port = host[idx..]
if !valid_optional_port(colon_port) {
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 h
//host = h
//return host
// host = h
// return host
}
// 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
// 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 {
// Default encoding is fine.
u.raw_path = ''
} else {
}
else {
u.raw_path = p
}
return true
@ -677,7 +677,9 @@ fn (u mut URL) set_path(p string) ?bool {
// reading u.raw_path directly.
fn (u &URL) escaped_path() string {
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
}
if u.path == '*' {
@ -705,12 +707,12 @@ fn valid_encoded_path(s string) bool {
}
`%` {
// ok - percent encoded, will decode
} else {
}
else {
if should_escape(s[i], .encode_path) {
return false
}
}
}
}}
}
return true
}
@ -735,8 +737,8 @@ fn valid_optional_port(port string) bool {
// str reassembles the URL into a valid URL string.
// The general form of the result is one of:
//
// scheme:opaque?query#fragment
// scheme://userinfo@host/path?query#fragment
// scheme:opaque?query#fragment
// scheme://userinfo@host/path?query#fragment
//
// If u.opaque is non-empty, String uses the first 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().
//
// In the second form, the following rules apply:
// - if u.scheme is empty, scheme: is omitted.
// - if u.user is nil, userinfo@ is omitted.
// - if u.host is empty, host/ is omitted.
// - if u.scheme and u.host are empty and u.user is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.host is non-empty and u.path begins with a /,
// the form host/path does not add its own /.
// - if u.raw_query is empty, ?query is omitted.
// - if u.fragment is empty, #fragment is omitted.
// - if u.scheme is empty, scheme: is omitted.
// - if u.user is nil, userinfo@ is omitted.
// - if u.host is empty, host/ is omitted.
// - if u.scheme and u.host are empty and u.user is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.host is non-empty and u.path begins with a /,
// the form host/path does not add its own /.
// - if u.raw_query is empty, ?query is omitted.
// - if u.fragment is empty, #fragment is omitted.
pub fn (u &URL) str() string {
mut buf := strings.new_builder(200)
if u.scheme != '' {
@ -761,7 +763,8 @@ pub fn (u &URL) str() string {
}
if u.opaque != '' {
buf.write(u.opaque)
} else {
}
else {
if u.scheme != '' || u.host != '' || !u.user.empty() {
if u.host != '' || u.path != '' || !u.user.empty() {
buf.write('//')
@ -807,8 +810,6 @@ pub fn (u &URL) str() string {
// It is typically used for query parameters and form values.
// Unlike in the http.Header map, the keys in a Values map
// are case-sensitive.
// parseQuery parses the URL-encoded query string and returns
// a map listing the values specified for each key.
// 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 i := key.index_any('&;')
if i >= 0 {
q = key[i+1..]
q = key[i + 1..]
key = key[..i]
} else {
}
else {
q = ''
}
if key == '' {
continue
}
mut value := ''
if idx := key.index('=') {
if idx:=key.index('='){
i = idx
value = key[i+1..]
value = key[i + 1..]
key = key[..i]
}
k := query_unescape(key) or {
@ -860,7 +862,6 @@ fn parse_query_values(m mut Values, query string) ?bool {
continue
}
key = k
v := query_unescape(value) or {
had_error = true
continue
@ -907,10 +908,14 @@ fn resolve_path(base, ref string) string {
mut full := ''
if ref == '' {
full = base
} else if ref[0] != `/` {
i := base.last_index('/') or { -1 }
full = base[..i+1] + ref
} else {
}
else if ref[0] != `/` {
i := base.last_index('/') or {
-1
}
full = base[..i + 1] + ref
}
else {
full = ref
}
if full == '' {
@ -925,17 +930,17 @@ fn resolve_path(base, ref string) string {
}
'..' {
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 == '..' {
// Add final slash to the joined path.
dst << ''
dst << ''
}
return '/' + dst.join('/').trim_left('/')
}
@ -971,7 +976,9 @@ pub fn (u &URL) resolve_reference(ref &URL) ?URL {
// The 'absoluteURI' or 'net_path' cases.
// We can ignore the error from set_path since we know we provided a
// 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
}
if ref.opaque != '' {
@ -989,7 +996,9 @@ pub fn (u &URL) resolve_reference(ref &URL) ?URL {
// The 'abs_path' or 'rel_path' cases.
url.host = u.host
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
}
@ -1010,7 +1019,8 @@ pub fn (u &URL) request_uri() string {
if result == '' {
result = '/'
}
} else {
}
else {
if result.starts_with('//') {
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,
// the square brackets are removed from the result.
pub fn (u &URL) hostname() string {
host, _ := split_host_port(u.host)
host,_ := split_host_port(u.host)
return host
}
// 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.
pub fn (u &URL) port() string {
_, port := split_host_port(u.host)
_,port := split_host_port(u.host)
return port
}
// 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.
// 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 port := ''
colon := host.last_index_byte(`:`)
if colon != -1 && valid_optional_port(host[colon..]) {
port = host[colon+1..]
port = host[colon + 1..]
host = host[..colon]
}
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
// Section 3.2.1:
// userinfo = *( unreserved / pct-encoded / sub-delims / ':' )
// unreserved = ALPHA / DIGIT / '-' / '.' / '_' / '~'
// sub-delims = '!' / '$' / '&' / ''' / '(' / ')'
// / '*' / '+' / ',' / ';' / '='
// userinfo = *( unreserved / pct-encoded / sub-delims / ':' )
// unreserved = ALPHA / DIGIT / '-' / '.' / '_' / '~'
// sub-delims = '!' / '$' / '&' / ''' / '(' / ')'
// / '*' / '+' / ',' / ';' / '='
//
// It doesn't validate pct-encoded. The caller does that via fn unescape.
pub fn valid_userinfo(s string) bool {
@ -1077,13 +1084,12 @@ pub fn valid_userinfo(s string) bool {
continue
}
match r {
`-`, `.`, `_`, `:`, `~`, `!`, `$`, `&`, `\\`,
`(`, `)`, `*`, `+`, `,`, `;`, `=`, `%`, `@` {
continue
} else {
return false
}
}
`-`, `.`, `_`, `:`, `~`, `!`, `$`, `&`, `\\`, `(`, `)`, `*`, `+`, `,`, `;`, `=`, `%`, `@` {
continue
}
else {
return false
}}
}
return true
}
@ -1102,9 +1108,11 @@ fn string_contains_ctl_byte(s string) bool {
pub fn ishex(c byte) bool {
if `0` <= c && c <= `9` {
return true
} else if `a` <= c && c <= `f` {
}
else if `a` <= c && c <= `f` {
return true
} else if `A` <= c && c <= `F` {
}
else if `A` <= c && c <= `F` {
return true
}
return false
@ -1113,10 +1121,13 @@ pub fn ishex(c byte) bool {
fn unhex(c byte) byte {
if `0` <= c && c <= `9` {
return c - `0`
} else if `a` <= c && c <= `f` {
}
else if `a` <= c && c <= `f` {
return c - `a` + 10
} else if `A` <= c && c <= `F` {
}
else if `A` <= c && c <= `F` {
return c - `A` + 10
}
return 0
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module urllib
struct Value {
@ -86,3 +85,4 @@ pub fn (v mut Values) del(key string) {
v.data.delete(key)
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')
}
}
os.cp(source_path, adjasted_path)or{
os.cp(source_path, adjasted_path) or {
return error(err)
}
return true
@ -186,18 +186,18 @@ pub fn cp_r(osource_path, odest_path string, overwrite bool) ?bool {
if !os.is_dir(dest_path) {
return error('Destination path is not a valid directory')
}
files := os.ls(source_path)or{
files := os.ls(source_path) or {
return error(err)
}
for file in files {
sp := filepath.join(source_path,file)
dp := filepath.join(dest_path,file)
if os.is_dir(sp) {
os.mkdir(dp)or{
os.mkdir(dp) or {
panic(err)
}
}
cp_r(sp, dp, overwrite)or{
cp_r(sp, dp, overwrite) or {
os.rmdir(dp)
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 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 {
os.cp(source, target)or{
os.cp(source, target) or {
return error(err)
}
os.rm(source)
@ -259,7 +259,7 @@ pub fn read_lines(path string) ?[]string {
}
fn read_ulines(path string) ?[]ustring {
lines := read_lines(path)or{
lines := read_lines(path) or {
return err
}
// 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`.
pub fn write_file(path, text string) {
mut f := os.create(path)or{
mut f := os.create(path) or {
return
}
f.write(text)
@ -966,7 +966,7 @@ pub fn walk_ext(path, ext string) []string {
if !os.is_dir(path) {
return []
}
mut files := os.ls(path)or{
mut files := os.ls(path) or {
panic(err)
}
mut res := []string
@ -992,7 +992,7 @@ pub fn walk(path string, fnc fn(path string)) {
if !os.is_dir(path) {
return
}
mut files := os.ls(path)or{
mut files := os.ls(path) or {
panic(err)
}
for file in files {
@ -1072,7 +1072,7 @@ pub fn mkdir_all(path string) {
for subdir in path.split(os.path_separator) {
p += subdir + os.path_separator
if !os.is_dir(p) {
os.mkdir(p)or{
os.mkdir(p) or {
panic(err)
}
}

View File

@ -489,7 +489,6 @@ fn converter(pn mut PrepNumber) u64 {
}
s1 = s1 & check_round_mask
s0 = u32(0)
// recheck normalization
if s2 & (mask28<<u32(1)) != 0 {
// C.printf("Renormalize!!")
@ -500,7 +499,6 @@ fn converter(pn mut PrepNumber) u64 {
s0 = q0
}
}
// 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("Tmp result: %016x\n",tmp)
@ -518,7 +516,8 @@ fn converter(pn mut PrepNumber) u64 {
else if binexp < 1 {
if pn.negative {
result = DOUBLE_MINUS_ZERO
} else {
}
else {
result = DOUBLE_PLUS_ZERO
}
}

View File

@ -1,24 +1,25 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module sync
#include <pthread.h>
fn C.pthread_mutex_init()
fn C.pthread_mutex_lock()
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 {
mutex C.pthread_mutex_t
}
pub fn new_mutex() Mutex {
m := Mutex{}
C.pthread_mutex_init( &m.mutex, C.NULL)
m := Mutex{
}
C.pthread_mutex_init(&m.mutex, C.NULL)
return m
}

View File

@ -1,18 +1,17 @@
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
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 {
mut:
mu Mutex
mu Mutex
active int
}
pub fn new_waitgroup() WaitGroup {
mut w := WaitGroup{}
mut w := WaitGroup{
}
w.mu = sync.new_mutex()
return w
}
@ -41,3 +40,4 @@ pub fn (wg &WaitGroup) wait() {
}
}
}