url.odin 5.3 KB

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