URLSearchParams.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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/URL/URL.h>
  10. #include <LibWeb/URL/URLSearchParams.h>
  11. namespace Web::URL {
  12. String url_encode(const Vector<QueryParam>& pairs, AK::URL::PercentEncodeSet percent_encode_set)
  13. {
  14. StringBuilder builder;
  15. for (size_t i = 0; i < pairs.size(); ++i) {
  16. builder.append(AK::URL::percent_encode(pairs[i].name, percent_encode_set));
  17. builder.append('=');
  18. builder.append(AK::URL::percent_encode(pairs[i].value, percent_encode_set));
  19. if (i != pairs.size() - 1)
  20. builder.append('&');
  21. }
  22. return builder.to_string();
  23. }
  24. Vector<QueryParam> url_decode(StringView const& input)
  25. {
  26. // 1. Let sequences be the result of splitting input on 0x26 (&).
  27. auto sequences = input.split_view('&');
  28. // 2. Let output be an initially empty list of name-value tuples where both name and value hold a string.
  29. Vector<QueryParam> output;
  30. // 3. For each byte sequence bytes in sequences:
  31. for (auto bytes : sequences) {
  32. // 1. If bytes is the empty byte sequence, then continue.
  33. if (bytes.is_empty())
  34. continue;
  35. StringView name;
  36. StringView value;
  37. // 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.
  38. if (auto index = bytes.find('='); index.has_value()) {
  39. name = bytes.substring_view(0, *index);
  40. value = bytes.substring_view(*index + 1);
  41. }
  42. // 3. Otherwise, let name have the value of bytes and let value be the empty byte sequence.
  43. else {
  44. name = bytes;
  45. value = ""sv;
  46. }
  47. // 4. Replace any 0x2B (+) in name and value with 0x20 (SP).
  48. auto space_decoded_name = name.replace("+"sv, " "sv, true);
  49. // 5. Let nameString and valueString be the result of running UTF-8 decode without BOM on the percent-decoding of name and value, respectively.
  50. auto name_string = AK::URL::percent_decode(space_decoded_name);
  51. auto value_string = AK::URL::percent_decode(value);
  52. output.empend(move(name_string), move(value_string));
  53. }
  54. return output;
  55. }
  56. DOM::ExceptionOr<NonnullRefPtr<URLSearchParams>> URLSearchParams::create_with_global_object(Bindings::WindowObject&, String const& init)
  57. {
  58. // 1. If init is a string and starts with U+003F (?), then remove the first code point from init.
  59. StringView stripped_init = init.substring_view(init.starts_with('?'));
  60. // 2. Initialize this with init.
  61. // URLSearchParams init from this point forward
  62. // TODO
  63. // 1. If init is a sequence, then for each pair in init:
  64. // a. If pair does not contain exactly two items, then throw a TypeError.
  65. // 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.
  66. // TODO
  67. // 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.
  68. // 3. Otherwise:
  69. // a. Assert: init is a string.
  70. // b. Set query’s list to the result of parsing init.
  71. return URLSearchParams::create(url_decode(stripped_init));
  72. }
  73. void URLSearchParams::append(String const& name, String const& value)
  74. {
  75. // 1. Append a new name-value pair whose name is name and value is value, to list.
  76. m_list.empend(name, value);
  77. // 2. Update this.
  78. update();
  79. }
  80. void URLSearchParams::update()
  81. {
  82. // 1. If query’s URL object is null, then return.
  83. if (m_url.is_null())
  84. return;
  85. // 2. Let serializedQuery be the serialization of query’s list.
  86. auto serialized_query = to_string();
  87. // 3. If serializedQuery is the empty string, then set serializedQuery to null.
  88. if (serialized_query.is_empty())
  89. serialized_query = {};
  90. // 4. Set query’s URL object’s URL’s query to serializedQuery.
  91. m_url->set_query({}, move(serialized_query));
  92. }
  93. void URLSearchParams::delete_(String const& name)
  94. {
  95. // 1. Remove all name-value pairs whose name is name from list.
  96. m_list.remove_all_matching([&name](auto& entry) {
  97. return entry.name == name;
  98. });
  99. // 2. Update this.
  100. update();
  101. }
  102. String URLSearchParams::get(String const& name)
  103. {
  104. // 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.
  105. auto result = m_list.find_if([&name](auto& entry) {
  106. return entry.name == name;
  107. });
  108. if (result.is_end())
  109. return {};
  110. return result->value;
  111. }
  112. bool URLSearchParams::has(String const& name)
  113. {
  114. // return true if there is a name-value pair whose name is name in this’s list, and false otherwise.
  115. return !m_list.find_if([&name](auto& entry) {
  116. return entry.name == name;
  117. })
  118. .is_end();
  119. }
  120. void URLSearchParams::set(const String& name, const String& value)
  121. {
  122. // 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.
  123. auto existing = m_list.find_if([&name](auto& entry) {
  124. return entry.name == name;
  125. });
  126. if (!existing.is_end()) {
  127. existing->value = value;
  128. m_list.remove_all_matching([&name, &existing](auto& entry) {
  129. return &entry != &*existing && entry.name == name;
  130. });
  131. }
  132. // 2. Otherwise, append a new name-value pair whose name is name and value is value, to this’s list.
  133. else {
  134. m_list.empend(name, value);
  135. }
  136. // 3. Update this.
  137. update();
  138. }
  139. void URLSearchParams::sort()
  140. {
  141. // 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.
  142. quick_sort(m_list.begin(), m_list.end(), [](auto& a, auto& b) {
  143. Utf8View a_code_points { a.name };
  144. Utf8View b_code_points { b.name };
  145. if (a_code_points.starts_with(b_code_points))
  146. return false;
  147. if (b_code_points.starts_with(a_code_points))
  148. return true;
  149. for (auto k = a_code_points.begin(), l = b_code_points.begin();
  150. k != a_code_points.end() && l != b_code_points.end();
  151. ++k, ++l) {
  152. if (*k != *l) {
  153. if (*k < *l) {
  154. return true;
  155. } else {
  156. return false;
  157. }
  158. }
  159. }
  160. VERIFY_NOT_REACHED();
  161. });
  162. // 2. Update this.
  163. update();
  164. }
  165. String URLSearchParams::to_string()
  166. {
  167. // return the serialization of this’s list.
  168. return url_encode(m_list, AK::URL::PercentEncodeSet::ApplicationXWWWFormUrlencoded);
  169. }
  170. }