URLSearchParams.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/QuickSort.h>
  8. #include <AK/StringBuilder.h>
  9. #include <AK/Utf8View.h>
  10. #include <LibTextCodec/Decoder.h>
  11. #include <LibTextCodec/Encoder.h>
  12. #include <LibURL/Parser.h>
  13. #include <LibWeb/Bindings/ExceptionOrUtils.h>
  14. #include <LibWeb/Bindings/Intrinsics.h>
  15. #include <LibWeb/Bindings/URLSearchParamsPrototype.h>
  16. #include <LibWeb/DOMURL/DOMURL.h>
  17. #include <LibWeb/DOMURL/URLSearchParams.h>
  18. namespace Web::DOMURL {
  19. JS_DEFINE_ALLOCATOR(URLSearchParams);
  20. URLSearchParams::URLSearchParams(JS::Realm& realm, Vector<QueryParam> list)
  21. : PlatformObject(realm)
  22. , m_list(move(list))
  23. {
  24. }
  25. URLSearchParams::~URLSearchParams() = default;
  26. void URLSearchParams::initialize(JS::Realm& realm)
  27. {
  28. Base::initialize(realm);
  29. WEB_SET_PROTOTYPE_FOR_INTERFACE(URLSearchParams);
  30. }
  31. void URLSearchParams::visit_edges(Cell::Visitor& visitor)
  32. {
  33. Base::visit_edges(visitor);
  34. visitor.visit(m_url);
  35. }
  36. // https://url.spec.whatwg.org/#concept-urlencoded-serializer
  37. // The application/x-www-form-urlencoded serializer takes a list of name-value tuples tuples, with an optional encoding encoding (default UTF-8), and then runs these steps. They return an ASCII string.
  38. String url_encode(Vector<QueryParam> const& tuples, StringView encoding)
  39. {
  40. // 1. Set encoding to the result of getting an output encoding from encoding.
  41. encoding = TextCodec::get_output_encoding(encoding);
  42. auto encoder = TextCodec::encoder_for(encoding);
  43. if (!encoder.has_value()) {
  44. // NOTE: Fallback to default utf-8 encoder.
  45. encoder = TextCodec::encoder_for("utf-8"sv);
  46. }
  47. // 2. Let output be the empty string.
  48. StringBuilder output;
  49. // 3. For each tuple of tuples:
  50. for (auto const& tuple : tuples) {
  51. // 1. Assert: tuple’s name and tuple’s value are scalar value strings.
  52. // 2. Let name be the result of running percent-encode after encoding with encoding, tuple’s name, the application/x-www-form-urlencoded percent-encode set, and true.
  53. auto name = URL::Parser::percent_encode_after_encoding(*encoder, tuple.name, URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded, true);
  54. // 3. Let value be the result of running percent-encode after encoding with encoding, tuple’s value, the application/x-www-form-urlencoded percent-encode set, and true.
  55. auto value = URL::Parser::percent_encode_after_encoding(*encoder, tuple.value, URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded, true);
  56. // 4. If output is not the empty string, then append U+0026 (&) to output.
  57. if (!output.is_empty())
  58. output.append('&');
  59. // 5. Append name, followed by U+003D (=), followed by value, to output.
  60. output.append(name);
  61. output.append('=');
  62. output.append(value);
  63. }
  64. // 4. Return output.
  65. return MUST(output.to_string());
  66. }
  67. // https://url.spec.whatwg.org/#concept-urlencoded-parser
  68. // The application/x-www-form-urlencoded parser takes a byte sequence input, and then runs these steps:
  69. Vector<QueryParam> url_decode(StringView input)
  70. {
  71. // 1. Let sequences be the result of splitting input on 0x26 (&).
  72. auto sequences = input.split_view('&');
  73. // 2. Let output be an initially empty list of name-value tuples where both name and value hold a string.
  74. Vector<QueryParam> output;
  75. // 3. For each byte sequence bytes in sequences:
  76. for (auto bytes : sequences) {
  77. // 1. If bytes is the empty byte sequence, then continue.
  78. if (bytes.is_empty())
  79. continue;
  80. StringView name;
  81. StringView value;
  82. // 2. If bytes contains a 0x3D (=), then let name be the bytes from the start of bytes up to but excluding its first 0x3D (=), and let value be the bytes, if any, after the first 0x3D (=) up to the end of bytes. If 0x3D (=) is the first byte, then name will be the empty byte sequence. If it is the last, then value will be the empty byte sequence.
  83. if (auto index = bytes.find('='); index.has_value()) {
  84. name = bytes.substring_view(0, *index);
  85. value = bytes.substring_view(*index + 1);
  86. }
  87. // 3. Otherwise, let name have the value of bytes and let value be the empty byte sequence.
  88. else {
  89. name = bytes;
  90. value = ""sv;
  91. }
  92. // 4. Replace any 0x2B (+) in name and value with 0x20 (SP).
  93. auto space_decoded_name = name.replace("+"sv, " "sv, ReplaceMode::All);
  94. auto space_decoded_value = value.replace("+"sv, " "sv, ReplaceMode::All);
  95. // 5. Let nameString and valueString be the result of running UTF-8 decode without BOM on the percent-decoding of name and value, respectively.
  96. auto name_string = String::from_utf8_with_replacement_character(URL::percent_decode(space_decoded_name), String::WithBOMHandling::No);
  97. auto value_string = String::from_utf8_with_replacement_character(URL::percent_decode(space_decoded_value), String::WithBOMHandling::No);
  98. output.empend(move(name_string), move(value_string));
  99. }
  100. return output;
  101. }
  102. JS::NonnullGCPtr<URLSearchParams> URLSearchParams::create(JS::Realm& realm, Vector<QueryParam> list)
  103. {
  104. return realm.heap().allocate<URLSearchParams>(realm, realm, move(list));
  105. }
  106. // https://url.spec.whatwg.org/#urlsearchparams-initialize
  107. JS::NonnullGCPtr<URLSearchParams> URLSearchParams::create(JS::Realm& realm, StringView init)
  108. {
  109. // NOTE: We skip the other steps since we know it is a string at this point.
  110. // b. Set query’s list to the result of parsing init.
  111. return URLSearchParams::create(realm, url_decode(init));
  112. }
  113. // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  114. // https://url.spec.whatwg.org/#urlsearchparams-initialize
  115. WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> URLSearchParams::construct_impl(JS::Realm& realm, Variant<Vector<Vector<String>>, OrderedHashMap<String, String>, String> const& init)
  116. {
  117. auto& vm = realm.vm();
  118. // 1. If init is a string and starts with U+003F (?), then remove the first code point from init.
  119. // NOTE: We do this when we know that it's a string on step 3 of initialization.
  120. // 2. Initialize this with init.
  121. // URLSearchParams init from this point forward
  122. // 1. If init is a sequence, then for each pair in init:
  123. if (init.has<Vector<Vector<String>>>()) {
  124. auto const& init_sequence = init.get<Vector<Vector<String>>>();
  125. Vector<QueryParam> list;
  126. list.ensure_capacity(init_sequence.size());
  127. for (auto const& pair : init_sequence) {
  128. // a. If pair does not contain exactly two items, then throw a TypeError.
  129. if (pair.size() != 2)
  130. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, TRY_OR_THROW_OOM(vm, String::formatted("Expected only 2 items in pair, got {}", pair.size())) };
  131. // b. Append a new name-value pair whose name is pair’s first item, and value is pair’s second item, to query’s list.
  132. list.append(QueryParam { .name = pair[0], .value = pair[1] });
  133. }
  134. return URLSearchParams::create(realm, move(list));
  135. }
  136. // 2. Otherwise, if init is a record, then for each name → value of init, append a new name-value pair whose name is name and value is value, to query’s list.
  137. if (init.has<OrderedHashMap<String, String>>()) {
  138. auto const& init_record = init.get<OrderedHashMap<String, String>>();
  139. Vector<QueryParam> list;
  140. list.ensure_capacity(init_record.size());
  141. for (auto const& pair : init_record)
  142. list.append(QueryParam { .name = pair.key, .value = pair.value });
  143. return URLSearchParams::create(realm, move(list));
  144. }
  145. // 3. Otherwise:
  146. // a. Assert: init is a string.
  147. // NOTE: `get` performs `VERIFY(has<T>())`
  148. auto const& init_string = init.get<String>();
  149. // See NOTE at the start of this function.
  150. auto init_string_view = init_string.bytes_as_string_view();
  151. auto stripped_init = init_string_view.substring_view(init_string_view.starts_with('?'));
  152. // b. Set query’s list to the result of parsing init.
  153. return URLSearchParams::create(realm, stripped_init);
  154. }
  155. // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  156. size_t URLSearchParams::size() const
  157. {
  158. // The size getter steps are to return this’s list’s size.
  159. return m_list.size();
  160. }
  161. // https://url.spec.whatwg.org/#dom-urlsearchparams-append
  162. void URLSearchParams::append(String const& name, String const& value)
  163. {
  164. // 1. Append a new name-value pair whose name is name and value is value, to list.
  165. m_list.empend(name, value);
  166. // 2. Update this.
  167. update();
  168. }
  169. void URLSearchParams::update()
  170. {
  171. // 1. If query’s URL object is null, then return.
  172. if (!m_url)
  173. return;
  174. // 2. Let serializedQuery be the serialization of query’s list.
  175. auto serialized_query = to_string();
  176. // 3. If serializedQuery is the empty string, then set serializedQuery to null.
  177. if (serialized_query.is_empty())
  178. serialized_query = {};
  179. // 4. Set query’s URL object’s URL’s query to serializedQuery.
  180. m_url->set_query({}, move(serialized_query));
  181. }
  182. // https://url.spec.whatwg.org/#dom-urlsearchparams-delete
  183. void URLSearchParams::delete_(String const& name, Optional<String> const& value)
  184. {
  185. // 1. If value is given, then remove all tuples whose name is name and value is value from this’s list.
  186. if (value.has_value()) {
  187. m_list.remove_all_matching([&name, &value](auto& entry) {
  188. return entry.name == name && entry.value == value.value();
  189. });
  190. }
  191. // 2. Otherwise, remove all tuples whose name is name from this’s list.
  192. else {
  193. m_list.remove_all_matching([&name](auto& entry) {
  194. return entry.name == name;
  195. });
  196. }
  197. // 2. Update this.
  198. update();
  199. }
  200. Optional<String> URLSearchParams::get(String const& name)
  201. {
  202. // return the value of the first name-value pair whose name is name in this’s list, if there is such a pair, and null otherwise.
  203. auto result = m_list.find_if([&name](auto& entry) {
  204. return entry.name == name;
  205. });
  206. if (result.is_end())
  207. return {};
  208. return result->value;
  209. }
  210. // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  211. Vector<String> URLSearchParams::get_all(String const& name)
  212. {
  213. // return the values of all name-value pairs whose name is name, in this’s list, in list order, and the empty sequence otherwise.
  214. Vector<String> values;
  215. for (auto& entry : m_list) {
  216. if (entry.name == name)
  217. values.append(entry.value);
  218. }
  219. return values;
  220. }
  221. // https://url.spec.whatwg.org/#dom-urlsearchparams-has
  222. bool URLSearchParams::has(String const& name, Optional<String> const& value)
  223. {
  224. // 1. If value is given and there is a tuple whose name is name and value is value in this’s list, then return true.
  225. if (value.has_value()) {
  226. if (!m_list.find_if([&name, &value](auto& entry) {
  227. return entry.name == name && entry.value == value.value();
  228. })
  229. .is_end()) {
  230. return true;
  231. }
  232. }
  233. // 2. If value is not given and there is a tuple whose name is name in this’s list, then return true.
  234. else {
  235. if (!m_list.find_if([&name](auto& entry) {
  236. return entry.name == name;
  237. })
  238. .is_end()) {
  239. return true;
  240. }
  241. }
  242. // 3. Return false.
  243. return false;
  244. }
  245. void URLSearchParams::set(String const& name, String const& value)
  246. {
  247. // 1. If this’s list contains any name-value pairs whose name is name, then set the value of the first such name-value pair to value and remove the others.
  248. auto existing = m_list.find_if([&name](auto& entry) {
  249. return entry.name == name;
  250. });
  251. if (!existing.is_end()) {
  252. existing->value = value;
  253. m_list.remove_all_matching([&name, &existing](auto& entry) {
  254. return &entry != &*existing && entry.name == name;
  255. });
  256. }
  257. // 2. Otherwise, append a new name-value pair whose name is name and value is value, to this’s list.
  258. else {
  259. m_list.empend(name, value);
  260. }
  261. // 3. Update this.
  262. update();
  263. }
  264. void URLSearchParams::sort()
  265. {
  266. // 1. Sort all name-value pairs, if any, by their names. Sorting must be done by comparison of code units. The relative order between name-value pairs with equal names must be preserved.
  267. quick_sort(m_list.begin(), m_list.end(), [](auto& a, auto& b) {
  268. Utf8View a_code_points { a.name };
  269. Utf8View b_code_points { b.name };
  270. if (a_code_points.starts_with(b_code_points))
  271. return false;
  272. if (b_code_points.starts_with(a_code_points))
  273. return true;
  274. for (auto k = a_code_points.begin(), l = b_code_points.begin();
  275. k != a_code_points.end() && l != b_code_points.end();
  276. ++k, ++l) {
  277. if (*k != *l) {
  278. return *k < *l;
  279. }
  280. }
  281. VERIFY_NOT_REACHED();
  282. });
  283. // 2. Update this.
  284. update();
  285. }
  286. String URLSearchParams::to_string() const
  287. {
  288. // return the serialization of this’s list.
  289. return url_encode(m_list);
  290. }
  291. JS::ThrowCompletionOr<void> URLSearchParams::for_each(ForEachCallback callback)
  292. {
  293. for (auto i = 0u; i < m_list.size(); ++i) {
  294. auto& query_param = m_list[i]; // We are explicitly iterating over the indices here as the callback might delete items from the list
  295. TRY(callback(query_param.name, query_param.value));
  296. }
  297. return {};
  298. }
  299. }