url.odin 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package net
  2. /*
  3. Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
  4. For other protocols and their features, see subdirectories of this package.
  5. */
  6. /*
  7. Copyright 2022 Tetralux <[email protected]>
  8. Copyright 2022 Colin Davidson <[email protected]>
  9. Copyright 2022 Jeroen van Rijn <[email protected]>.
  10. Made available under Odin's BSD-3 license.
  11. List of contributors:
  12. Tetralux: Initial implementation
  13. Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
  14. Jeroen van Rijn: Cross platform unification, code style, documentation
  15. */
  16. import "core:strings"
  17. import "core:strconv"
  18. import "core:unicode/utf8"
  19. import "core:encoding/hex"
  20. split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
  21. s := url
  22. i := strings.index(s, "://")
  23. if i >= 0 {
  24. scheme = s[:i]
  25. s = s[i+3:]
  26. }
  27. i = strings.index(s, "?")
  28. if i != -1 {
  29. query_str := s[i+1:]
  30. s = s[:i]
  31. if query_str != "" {
  32. queries_parts := strings.split(query_str, "&")
  33. defer delete(queries_parts)
  34. queries = make(map[string]string, len(queries_parts), allocator)
  35. for q in queries_parts {
  36. parts := strings.split(q, "=")
  37. defer delete(parts)
  38. switch len(parts) {
  39. case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
  40. case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
  41. case: break
  42. }
  43. }
  44. }
  45. }
  46. i = strings.index(s, "/")
  47. if i == -1 {
  48. host = s
  49. path = "/"
  50. } else {
  51. host = s[:i]
  52. path = s[i:]
  53. }
  54. return
  55. }
  56. join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
  57. b := strings.builder_make(allocator)
  58. strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
  59. strings.write_string(&b, scheme)
  60. strings.write_string(&b, "://")
  61. strings.write_string(&b, strings.trim_space(host))
  62. if path != "" {
  63. if path[0] != '/' {
  64. strings.write_string(&b, "/")
  65. }
  66. strings.write_string(&b, strings.trim_space(path))
  67. }
  68. query_length := len(queries)
  69. if query_length > 0 {
  70. strings.write_string(&b, "?")
  71. }
  72. i := 0
  73. for query_name, query_value in queries {
  74. strings.write_string(&b, query_name)
  75. if query_value != "" {
  76. strings.write_string(&b, "=")
  77. strings.write_string(&b, query_value)
  78. }
  79. if i < query_length - 1 {
  80. strings.write_string(&b, "&")
  81. }
  82. i += 1
  83. }
  84. return strings.to_string(b)
  85. }
  86. percent_encode :: proc(s: string, allocator := context.allocator) -> string {
  87. b := strings.builder_make(allocator)
  88. strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
  89. for ch in s {
  90. switch ch {
  91. case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
  92. strings.write_rune(&b, ch)
  93. case:
  94. bytes, n := utf8.encode_rune(ch)
  95. for byte in bytes[:n] {
  96. buf: [2]u8 = ---
  97. t := strconv.append_int(buf[:], i64(byte), 16)
  98. strings.write_rune(&b, '%')
  99. strings.write_string(&b, t)
  100. }
  101. }
  102. }
  103. return strings.to_string(b)
  104. }
  105. percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
  106. b := strings.builder_make(allocator)
  107. strings.builder_grow(&b, len(encoded_string))
  108. defer if !ok {
  109. strings.builder_destroy(&b)
  110. }
  111. s := encoded_string
  112. for len(s) > 0 {
  113. i := strings.index_byte(s, '%')
  114. if i == -1 {
  115. strings.write_string(&b, s) // no '%'s; the string is already decoded
  116. break
  117. }
  118. strings.write_string(&b, s[:i])
  119. s = s[i:]
  120. if len(s) == 0 {
  121. return // percent without anything after it
  122. }
  123. s = s[1:]
  124. if s[0] == '%' {
  125. strings.write_byte(&b, '%')
  126. s = s[1:]
  127. continue
  128. }
  129. if len(s) < 2 {
  130. return // percent without encoded value
  131. }
  132. val := hex.decode_sequence(s[:2]) or_return
  133. strings.write_byte(&b, val)
  134. s = s[2:]
  135. }
  136. ok = true
  137. decoded_string = strings.to_string(b)
  138. return
  139. }
  140. //
  141. // TODO: encoding/base64 is broken...
  142. //
  143. // // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
  144. // // make a table for this ... sigh - so this'll do for now.
  145. /*
  146. base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
  147. out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
  148. for b, i in out {
  149. switch b {
  150. case '+': out[i] = '-';
  151. case '/': out[i] = '_';
  152. }
  153. }
  154. i := len(out)-1;
  155. for ; i >= 0; i -= 1 {
  156. if out[i] != '=' {
  157. break;
  158. }
  159. }
  160. return string(out[:i+1]);
  161. }
  162. base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
  163. size := len(s);
  164. padding := 0;
  165. for size % 4 != 0 {
  166. size += 1; // TODO: SPEED
  167. padding += 1;
  168. }
  169. temp := make([]byte, size, context.temp_allocator);
  170. copy(temp, transmute([]byte) s);
  171. for b, i in temp {
  172. switch b {
  173. case '-': temp[i] = '+';
  174. case '_': temp[i] = '/';
  175. }
  176. }
  177. for in 0..padding-1 {
  178. temp[len(temp)-1] = '=';
  179. }
  180. return base64.decode(string(temp), base64.DEC_TABLE, allocator);
  181. }
  182. */