url.odin 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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, fragment: 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. fragment = s[i+1:]
  30. s = s[:i]
  31. }
  32. i = strings.index(s, "?")
  33. if i != -1 {
  34. query_str := s[i+1:]
  35. s = s[:i]
  36. if query_str != "" {
  37. queries_parts := strings.split(query_str, "&")
  38. defer delete(queries_parts)
  39. queries = make(map[string]string, len(queries_parts), allocator)
  40. for q in queries_parts {
  41. parts := strings.split(q, "=")
  42. defer delete(parts)
  43. switch len(parts) {
  44. case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
  45. case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
  46. case: break
  47. }
  48. }
  49. }
  50. }
  51. i = strings.index(s, "/")
  52. if i == -1 {
  53. host = s
  54. path = "/"
  55. } else {
  56. host = s[:i]
  57. path = s[i:]
  58. }
  59. return
  60. }
  61. join_url :: proc(scheme, host, path: string, queries: map[string]string, fragment: string, allocator := context.allocator) -> string {
  62. b := strings.builder_make(allocator)
  63. strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
  64. strings.write_string(&b, scheme)
  65. strings.write_string(&b, "://")
  66. strings.write_string(&b, strings.trim_space(host))
  67. if path != "" {
  68. if path[0] != '/' {
  69. strings.write_string(&b, "/")
  70. }
  71. strings.write_string(&b, strings.trim_space(path))
  72. }
  73. query_length := len(queries)
  74. if query_length > 0 {
  75. strings.write_string(&b, "?")
  76. }
  77. i := 0
  78. for query_name, query_value in queries {
  79. strings.write_string(&b, query_name)
  80. if query_value != "" {
  81. strings.write_string(&b, "=")
  82. strings.write_string(&b, query_value)
  83. }
  84. if i < query_length - 1 {
  85. strings.write_string(&b, "&")
  86. }
  87. i += 1
  88. }
  89. if fragment != "" {
  90. if fragment[0] != '#' {
  91. strings.write_string(&b, "#")
  92. }
  93. strings.write_string(&b, strings.trim_space(fragment))
  94. }
  95. return strings.to_string(b)
  96. }
  97. percent_encode :: proc(s: string, allocator := context.allocator) -> string {
  98. b := strings.builder_make(allocator)
  99. strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
  100. for ch in s {
  101. switch ch {
  102. case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
  103. strings.write_rune(&b, ch)
  104. case:
  105. bytes, n := utf8.encode_rune(ch)
  106. for byte in bytes[:n] {
  107. buf: [2]u8 = ---
  108. t := strconv.append_int(buf[:], i64(byte), 16)
  109. strings.write_rune(&b, '%')
  110. strings.write_string(&b, t)
  111. }
  112. }
  113. }
  114. return strings.to_string(b)
  115. }
  116. percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
  117. b := strings.builder_make(allocator)
  118. strings.builder_grow(&b, len(encoded_string))
  119. defer if !ok {
  120. strings.builder_destroy(&b)
  121. }
  122. s := encoded_string
  123. for len(s) > 0 {
  124. i := strings.index_byte(s, '%')
  125. if i == -1 {
  126. strings.write_string(&b, s) // no '%'s; the string is already decoded
  127. break
  128. }
  129. strings.write_string(&b, s[:i])
  130. s = s[i:]
  131. if len(s) == 0 {
  132. return // percent without anything after it
  133. }
  134. s = s[1:]
  135. if s[0] == '%' {
  136. strings.write_byte(&b, '%')
  137. s = s[1:]
  138. continue
  139. }
  140. if len(s) < 2 {
  141. return // percent without encoded value
  142. }
  143. val := hex.decode_sequence(s[:2]) or_return
  144. strings.write_byte(&b, val)
  145. s = s[2:]
  146. }
  147. ok = true
  148. decoded_string = strings.to_string(b)
  149. return
  150. }
  151. //
  152. // TODO: encoding/base64 is broken...
  153. //
  154. // // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
  155. // // make a table for this ... sigh - so this'll do for now.
  156. /*
  157. base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
  158. out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
  159. for b, i in out {
  160. switch b {
  161. case '+': out[i] = '-';
  162. case '/': out[i] = '_';
  163. }
  164. }
  165. i := len(out)-1;
  166. for ; i >= 0; i -= 1 {
  167. if out[i] != '=' {
  168. break;
  169. }
  170. }
  171. return string(out[:i+1]);
  172. }
  173. base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
  174. size := len(s);
  175. padding := 0;
  176. for size % 4 != 0 {
  177. size += 1; // TODO: SPEED
  178. padding += 1;
  179. }
  180. temp := make([]byte, size, context.temp_allocator);
  181. copy(temp, transmute([]byte) s);
  182. for b, i in temp {
  183. switch b {
  184. case '-': temp[i] = '+';
  185. case '_': temp[i] = '/';
  186. }
  187. }
  188. for in 0..padding-1 {
  189. temp[len(temp)-1] = '=';
  190. }
  191. return base64.decode(string(temp), base64.DEC_TABLE, allocator);
  192. }
  193. */