URLSearchParams.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/QuickSort.h>
  7. #include <AK/StringBuilder.h>
  8. #include <AK/Utf8View.h>
  9. #include <LibWeb/Bindings/Intrinsics.h>
  10. #include <LibWeb/URL/URL.h>
  11. #include <LibWeb/URL/URLSearchParams.h>
  12. namespace Web::URL {
  13. URLSearchParams::URLSearchParams(JS::Realm& realm, Vector<QueryParam> list)
  14. : PlatformObject(realm)
  15. , m_list(move(list))
  16. {
  17. }
  18. URLSearchParams::~URLSearchParams() = default;
  19. JS::ThrowCompletionOr<void> URLSearchParams::initialize(JS::Realm& realm)
  20. {
  21. MUST_OR_THROW_OOM(Base::initialize(realm));
  22. set_prototype(&Bindings::ensure_web_prototype<Bindings::URLSearchParamsPrototype>(realm, "URLSearchParams"));
  23. return {};
  24. }
  25. void URLSearchParams::visit_edges(Cell::Visitor& visitor)
  26. {
  27. Base::visit_edges(visitor);
  28. visitor.visit(m_url);
  29. }
  30. DeprecatedString url_encode(Vector<QueryParam> const& pairs, AK::URL::PercentEncodeSet percent_encode_set)
  31. {
  32. StringBuilder builder;
  33. for (size_t i = 0; i < pairs.size(); ++i) {
  34. builder.append(AK::URL::percent_encode(pairs[i].name, percent_encode_set, AK::URL::SpaceAsPlus::Yes));
  35. builder.append('=');
  36. builder.append(AK::URL::percent_encode(pairs[i].value, percent_encode_set, AK::URL::SpaceAsPlus::Yes));
  37. if (i != pairs.size() - 1)
  38. builder.append('&');
  39. }
  40. return builder.to_deprecated_string();
  41. }
  42. Vector<QueryParam> url_decode(StringView input)
  43. {
  44. // 1. Let sequences be the result of splitting input on 0x26 (&).
  45. auto sequences = input.split_view('&');
  46. // 2. Let output be an initially empty list of name-value tuples where both name and value hold a string.
  47. Vector<QueryParam> output;
  48. // 3. For each byte sequence bytes in sequences:
  49. for (auto bytes : sequences) {
  50. // 1. If bytes is the empty byte sequence, then continue.
  51. if (bytes.is_empty())
  52. continue;
  53. StringView name;
  54. StringView value;
  55. // 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.
  56. if (auto index = bytes.find('='); index.has_value()) {
  57. name = bytes.substring_view(0, *index);
  58. value = bytes.substring_view(*index + 1);
  59. }
  60. // 3. Otherwise, let name have the value of bytes and let value be the empty byte sequence.
  61. else {
  62. name = bytes;
  63. value = ""sv;
  64. }
  65. // 4. Replace any 0x2B (+) in name and value with 0x20 (SP).
  66. auto space_decoded_name = name.replace("+"sv, " "sv, ReplaceMode::All);
  67. // 5. Let nameString and valueString be the result of running UTF-8 decode without BOM on the percent-decoding of name and value, respectively.
  68. auto name_string = AK::URL::percent_decode(space_decoded_name);
  69. auto value_string = AK::URL::percent_decode(value);
  70. output.empend(move(name_string), move(value_string));
  71. }
  72. return output;
  73. }
  74. WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> URLSearchParams::create(JS::Realm& realm, Vector<QueryParam> list)
  75. {
  76. return MUST_OR_THROW_OOM(realm.heap().allocate<URLSearchParams>(realm, realm, move(list)));
  77. }
  78. // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
  79. // https://url.spec.whatwg.org/#urlsearchparams-initialize
  80. WebIDL::ExceptionOr<JS::NonnullGCPtr<URLSearchParams>> URLSearchParams::construct_impl(JS::Realm& realm, Variant<Vector<Vector<DeprecatedString>>, OrderedHashMap<DeprecatedString, DeprecatedString>, DeprecatedString> const& init)
  81. {
  82. // 1. If init is a string and starts with U+003F (?), then remove the first code point from init.
  83. // NOTE: We do this when we know that it's a string on step 3 of initialization.
  84. // 2. Initialize this with init.
  85. // URLSearchParams init from this point forward
  86. // 1. If init is a sequence, then for each pair in init:
  87. if (init.has<Vector<Vector<DeprecatedString>>>()) {
  88. auto const& init_sequence = init.get<Vector<Vector<DeprecatedString>>>();
  89. Vector<QueryParam> list;
  90. list.ensure_capacity(init_sequence.size());
  91. for (auto const& pair : init_sequence) {
  92. // a. If pair does not contain exactly two items, then throw a TypeError.
  93. if (pair.size() != 2)
  94. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, DeprecatedString::formatted("Expected only 2 items in pair, got {}", pair.size()) };
  95. // 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.
  96. list.append(QueryParam { .name = pair[0], .value = pair[1] });
  97. }
  98. return URLSearchParams::create(realm, move(list));
  99. }
  100. // 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.
  101. if (init.has<OrderedHashMap<DeprecatedString, DeprecatedString>>()) {
  102. auto const& init_record = init.get<OrderedHashMap<DeprecatedString, DeprecatedString>>();
  103. Vector<QueryParam> list;
  104. list.ensure_capacity(init_record.size());
  105. for (auto const& pair : init_record)
  106. list.append(QueryParam { .name = pair.key, .value = pair.value });
  107. return URLSearchParams::create(realm, move(list));
  108. }
  109. // 3. Otherwise:
  110. // a. Assert: init is a string.
  111. // NOTE: `get` performs `VERIFY(has<T>())`
  112. auto const& init_string = init.get<DeprecatedString>();
  113. // See NOTE at the start of this function.
  114. StringView stripped_init = init_string.substring_view(init_string.starts_with('?'));
  115. // b. Set query’s list to the result of parsing init.
  116. return URLSearchParams::create(realm, url_decode(stripped_init));
  117. }
  118. // https://url.spec.whatwg.org/#dom-urlsearchparams-size
  119. size_t URLSearchParams::size() const
  120. {
  121. // The size getter steps are to return this’s list’s size.
  122. return m_list.size();
  123. }
  124. void URLSearchParams::append(DeprecatedString const& name, DeprecatedString const& value)
  125. {
  126. // 1. Append a new name-value pair whose name is name and value is value, to list.
  127. m_list.empend(name, value);
  128. // 2. Update this.
  129. update();
  130. }
  131. void URLSearchParams::update()
  132. {
  133. // 1. If query’s URL object is null, then return.
  134. if (!m_url)
  135. return;
  136. // 2. Let serializedQuery be the serialization of query’s list.
  137. auto serialized_query = to_deprecated_string();
  138. // 3. If serializedQuery is the empty string, then set serializedQuery to null.
  139. if (serialized_query.is_empty())
  140. serialized_query = {};
  141. // 4. Set query’s URL object’s URL’s query to serializedQuery.
  142. m_url->set_query({}, move(serialized_query));
  143. }
  144. void URLSearchParams::delete_(DeprecatedString const& name)
  145. {
  146. // 1. Remove all name-value pairs whose name is name from list.
  147. m_list.remove_all_matching([&name](auto& entry) {
  148. return entry.name == name;
  149. });
  150. // 2. Update this.
  151. update();
  152. }
  153. DeprecatedString URLSearchParams::get(DeprecatedString const& name)
  154. {
  155. // 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.
  156. auto result = m_list.find_if([&name](auto& entry) {
  157. return entry.name == name;
  158. });
  159. if (result.is_end())
  160. return {};
  161. return result->value;
  162. }
  163. // https://url.spec.whatwg.org/#dom-urlsearchparams-getall
  164. Vector<DeprecatedString> URLSearchParams::get_all(DeprecatedString const& name)
  165. {
  166. // return the values of all name-value pairs whose name is name, in this’s list, in list order, and the empty sequence otherwise.
  167. Vector<DeprecatedString> values;
  168. for (auto& entry : m_list) {
  169. if (entry.name == name)
  170. values.append(entry.value);
  171. }
  172. return values;
  173. }
  174. bool URLSearchParams::has(DeprecatedString const& name)
  175. {
  176. // return true if there is a name-value pair whose name is name in this’s list, and false otherwise.
  177. return !m_list.find_if([&name](auto& entry) {
  178. return entry.name == name;
  179. })
  180. .is_end();
  181. }
  182. void URLSearchParams::set(DeprecatedString const& name, DeprecatedString const& value)
  183. {
  184. // 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.
  185. auto existing = m_list.find_if([&name](auto& entry) {
  186. return entry.name == name;
  187. });
  188. if (!existing.is_end()) {
  189. existing->value = value;
  190. m_list.remove_all_matching([&name, &existing](auto& entry) {
  191. return &entry != &*existing && entry.name == name;
  192. });
  193. }
  194. // 2. Otherwise, append a new name-value pair whose name is name and value is value, to this’s list.
  195. else {
  196. m_list.empend(name, value);
  197. }
  198. // 3. Update this.
  199. update();
  200. }
  201. void URLSearchParams::sort()
  202. {
  203. // 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.
  204. quick_sort(m_list.begin(), m_list.end(), [](auto& a, auto& b) {
  205. Utf8View a_code_points { a.name };
  206. Utf8View b_code_points { b.name };
  207. if (a_code_points.starts_with(b_code_points))
  208. return false;
  209. if (b_code_points.starts_with(a_code_points))
  210. return true;
  211. for (auto k = a_code_points.begin(), l = b_code_points.begin();
  212. k != a_code_points.end() && l != b_code_points.end();
  213. ++k, ++l) {
  214. if (*k != *l) {
  215. if (*k < *l) {
  216. return true;
  217. } else {
  218. return false;
  219. }
  220. }
  221. }
  222. VERIFY_NOT_REACHED();
  223. });
  224. // 2. Update this.
  225. update();
  226. }
  227. DeprecatedString URLSearchParams::to_deprecated_string() const
  228. {
  229. // return the serialization of this’s list.
  230. return url_encode(m_list, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded);
  231. }
  232. JS::ThrowCompletionOr<void> URLSearchParams::for_each(ForEachCallback callback)
  233. {
  234. for (auto i = 0u; i < m_list.size(); ++i) {
  235. auto& query_param = m_list[i]; // We are explicitly iterating over the indices here as the callback might delete items from the list
  236. TRY(callback(query_param.name, query_param.value));
  237. }
  238. return {};
  239. }
  240. }