123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- package net
- /*
- Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
- For other protocols and their features, see subdirectories of this package.
- */
- /*
- Copyright 2022 Tetralux <[email protected]>
- Copyright 2022 Colin Davidson <[email protected]>
- Copyright 2022 Jeroen van Rijn <[email protected]>.
- Made available under Odin's BSD-3 license.
- List of contributors:
- Tetralux: Initial implementation
- Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
- Jeroen van Rijn: Cross platform unification, code style, documentation
- */
- import "core:strings"
- import "core:strconv"
- import "core:unicode/utf8"
- import "core:encoding/hex"
- split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string, fragment: string) {
- s := url
- i := strings.index(s, "://")
- if i >= 0 {
- scheme = s[:i]
- s = s[i+3:]
- }
- i = strings.index(s, "#")
- if i != -1 {
- fragment = s[i+1:]
- s = s[:i]
- }
- i = strings.index(s, "?")
- if i != -1 {
- query_str := s[i+1:]
- s = s[:i]
- if query_str != "" {
- queries_parts := strings.split(query_str, "&")
- defer delete(queries_parts)
- queries = make(map[string]string, len(queries_parts), allocator)
- for q in queries_parts {
- parts := strings.split(q, "=")
- defer delete(parts)
- switch len(parts) {
- case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
- case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
- case: break
- }
- }
- }
- }
- i = strings.index(s, "/")
- if i == -1 {
- host = s
- path = "/"
- } else {
- host = s[:i]
- path = s[i:]
- }
- return
- }
- join_url :: proc(scheme, host, path: string, queries: map[string]string, fragment: string, allocator := context.allocator) -> string {
- b := strings.builder_make(allocator)
- strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
- strings.write_string(&b, scheme)
- strings.write_string(&b, "://")
- strings.write_string(&b, strings.trim_space(host))
- if path != "" {
- if path[0] != '/' {
- strings.write_string(&b, "/")
- }
- strings.write_string(&b, strings.trim_space(path))
- }
- query_length := len(queries)
- if query_length > 0 {
- strings.write_string(&b, "?")
- }
- i := 0
- for query_name, query_value in queries {
- strings.write_string(&b, query_name)
- if query_value != "" {
- strings.write_string(&b, "=")
- strings.write_string(&b, query_value)
- }
- if i < query_length - 1 {
- strings.write_string(&b, "&")
- }
- i += 1
- }
- if fragment != "" {
- if fragment[0] != '#' {
- strings.write_string(&b, "#")
- }
- strings.write_string(&b, strings.trim_space(fragment))
- }
- return strings.to_string(b)
- }
- percent_encode :: proc(s: string, allocator := context.allocator) -> string {
- b := strings.builder_make(allocator)
- strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
- for ch in s {
- switch ch {
- case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
- strings.write_rune(&b, ch)
- case:
- bytes, n := utf8.encode_rune(ch)
- for byte in bytes[:n] {
- buf: [2]u8 = ---
- t := strconv.append_int(buf[:], i64(byte), 16)
- strings.write_rune(&b, '%')
- strings.write_string(&b, t)
- }
- }
- }
- return strings.to_string(b)
- }
- percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
- b := strings.builder_make(allocator)
- strings.builder_grow(&b, len(encoded_string))
- defer if !ok {
- strings.builder_destroy(&b)
- }
- s := encoded_string
- for len(s) > 0 {
- i := strings.index_byte(s, '%')
- if i == -1 {
- strings.write_string(&b, s) // no '%'s; the string is already decoded
- break
- }
- strings.write_string(&b, s[:i])
- s = s[i:]
- if len(s) == 0 {
- return // percent without anything after it
- }
- s = s[1:]
- if s[0] == '%' {
- strings.write_byte(&b, '%')
- s = s[1:]
- continue
- }
- if len(s) < 2 {
- return // percent without encoded value
- }
- val := hex.decode_sequence(s[:2]) or_return
- strings.write_byte(&b, val)
- s = s[2:]
- }
- ok = true
- decoded_string = strings.to_string(b)
- return
- }
- //
- // TODO: encoding/base64 is broken...
- //
- // // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
- // // make a table for this ... sigh - so this'll do for now.
- /*
- base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
- out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
- for b, i in out {
- switch b {
- case '+': out[i] = '-';
- case '/': out[i] = '_';
- }
- }
- i := len(out)-1;
- for ; i >= 0; i -= 1 {
- if out[i] != '=' {
- break;
- }
- }
- return string(out[:i+1]);
- }
- base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
- size := len(s);
- padding := 0;
- for size % 4 != 0 {
- size += 1; // TODO: SPEED
- padding += 1;
- }
- temp := make([]byte, size, context.temp_allocator);
- copy(temp, transmute([]byte) s);
- for b, i in temp {
- switch b {
- case '-': temp[i] = '+';
- case '_': temp[i] = '/';
- }
- }
- for in 0..padding-1 {
- temp[len(temp)-1] = '=';
- }
- return base64.decode(string(temp), base64.DEC_TABLE, allocator);
- }
- */
|