Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "URLSearchParams.h"
8 : #include "mozilla/dom/URLSearchParamsBinding.h"
9 : #include "mozilla/Encoding.h"
10 : #include "nsDOMString.h"
11 : #include "nsIInputStream.h"
12 : #include "nsStringStream.h"
13 :
14 : namespace mozilla {
15 : namespace dom {
16 :
17 : bool
18 0 : URLParams::Has(const nsAString& aName)
19 : {
20 0 : for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
21 0 : if (mParams[i].mKey.Equals(aName)) {
22 0 : return true;
23 : }
24 : }
25 :
26 0 : return false;
27 : }
28 :
29 : void
30 0 : URLParams::Get(const nsAString& aName, nsString& aRetval)
31 : {
32 0 : SetDOMStringToNull(aRetval);
33 :
34 0 : for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
35 0 : if (mParams[i].mKey.Equals(aName)) {
36 0 : aRetval.Assign(mParams[i].mValue);
37 0 : break;
38 : }
39 : }
40 0 : }
41 :
42 : void
43 0 : URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
44 : {
45 0 : aRetval.Clear();
46 :
47 0 : for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
48 0 : if (mParams[i].mKey.Equals(aName)) {
49 0 : aRetval.AppendElement(mParams[i].mValue);
50 : }
51 : }
52 0 : }
53 :
54 : void
55 0 : URLParams::Append(const nsAString& aName, const nsAString& aValue)
56 : {
57 0 : Param* param = mParams.AppendElement();
58 0 : param->mKey = aName;
59 0 : param->mValue = aValue;
60 0 : }
61 :
62 : void
63 11 : URLParams::Set(const nsAString& aName, const nsAString& aValue)
64 : {
65 11 : Param* param = nullptr;
66 11 : for (uint32_t i = 0, len = mParams.Length(); i < len;) {
67 0 : if (!mParams[i].mKey.Equals(aName)) {
68 0 : ++i;
69 0 : continue;
70 : }
71 0 : if (!param) {
72 0 : param = &mParams[i];
73 0 : ++i;
74 0 : continue;
75 : }
76 : // Remove duplicates.
77 0 : mParams.RemoveElementAt(i);
78 0 : --len;
79 : }
80 :
81 11 : if (!param) {
82 11 : param = mParams.AppendElement();
83 11 : param->mKey = aName;
84 : }
85 :
86 11 : param->mValue = aValue;
87 11 : }
88 :
89 : bool
90 0 : URLParams::Delete(const nsAString& aName)
91 : {
92 0 : bool found = false;
93 0 : for (uint32_t i = 0; i < mParams.Length();) {
94 0 : if (mParams[i].mKey.Equals(aName)) {
95 0 : mParams.RemoveElementAt(i);
96 0 : found = true;
97 : } else {
98 0 : ++i;
99 : }
100 : }
101 :
102 0 : return found;
103 : }
104 :
105 : void
106 0 : URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput)
107 : {
108 0 : if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aInput, aOutput))) {
109 0 : MOZ_CRASH("Out of memory when converting URL params.");
110 : }
111 0 : }
112 :
113 : void
114 0 : URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
115 : {
116 0 : nsACString::const_iterator start, end;
117 0 : aInput.BeginReading(start);
118 0 : aInput.EndReading(end);
119 :
120 0 : nsCString unescaped;
121 :
122 0 : while (start != end) {
123 : // replace '+' with U+0020
124 0 : if (*start == '+') {
125 0 : unescaped.Append(' ');
126 0 : ++start;
127 0 : continue;
128 : }
129 :
130 : // Percent decode algorithm
131 0 : if (*start == '%') {
132 0 : nsACString::const_iterator first(start);
133 0 : ++first;
134 :
135 0 : nsACString::const_iterator second(first);
136 0 : ++second;
137 :
138 : #define ASCII_HEX_DIGIT( x ) \
139 : ((x >= 0x41 && x <= 0x46) || \
140 : (x >= 0x61 && x <= 0x66) || \
141 : (x >= 0x30 && x <= 0x39))
142 :
143 : #define HEX_DIGIT( x ) \
144 : (*x >= 0x30 && *x <= 0x39 \
145 : ? *x - 0x30 \
146 : : (*x >= 0x41 && *x <= 0x46 \
147 : ? *x - 0x37 \
148 : : *x - 0x57))
149 :
150 0 : if (first != end && second != end &&
151 0 : ASCII_HEX_DIGIT(*first) && ASCII_HEX_DIGIT(*second)) {
152 0 : unescaped.Append(HEX_DIGIT(first) * 16 + HEX_DIGIT(second));
153 0 : start = ++second;
154 0 : continue;
155 :
156 : } else {
157 0 : unescaped.Append('%');
158 0 : ++start;
159 0 : continue;
160 : }
161 : }
162 :
163 0 : unescaped.Append(*start);
164 0 : ++start;
165 : }
166 :
167 0 : ConvertString(unescaped, aOutput);
168 0 : }
169 :
170 : void
171 0 : URLParams::ParseInput(const nsACString& aInput)
172 : {
173 : // Remove all the existing data before parsing a new input.
174 0 : DeleteAll();
175 :
176 0 : nsACString::const_iterator start, end;
177 0 : aInput.BeginReading(start);
178 0 : aInput.EndReading(end);
179 0 : nsACString::const_iterator iter(start);
180 :
181 0 : while (start != end) {
182 0 : nsAutoCString string;
183 :
184 0 : if (FindCharInReadable('&', iter, end)) {
185 0 : string.Assign(Substring(start, iter));
186 0 : start = ++iter;
187 : } else {
188 0 : string.Assign(Substring(start, end));
189 0 : start = end;
190 : }
191 :
192 0 : if (string.IsEmpty()) {
193 0 : continue;
194 : }
195 :
196 0 : nsACString::const_iterator eqStart, eqEnd;
197 0 : string.BeginReading(eqStart);
198 0 : string.EndReading(eqEnd);
199 0 : nsACString::const_iterator eqIter(eqStart);
200 :
201 0 : nsAutoCString name;
202 0 : nsAutoCString value;
203 :
204 0 : if (FindCharInReadable('=', eqIter, eqEnd)) {
205 0 : name.Assign(Substring(eqStart, eqIter));
206 :
207 0 : ++eqIter;
208 0 : value.Assign(Substring(eqIter, eqEnd));
209 : } else {
210 0 : name.Assign(string);
211 : }
212 :
213 0 : nsAutoString decodedName;
214 0 : DecodeString(name, decodedName);
215 :
216 0 : nsAutoString decodedValue;
217 0 : DecodeString(value, decodedValue);
218 :
219 0 : Append(decodedName, decodedValue);
220 : }
221 0 : }
222 :
223 : namespace {
224 :
225 22 : void SerializeString(const nsCString& aInput, nsAString& aValue)
226 : {
227 22 : const unsigned char* p = (const unsigned char*) aInput.get();
228 22 : const unsigned char* end = p + aInput.Length();
229 :
230 1628 : while (p != end) {
231 : // ' ' to '+'
232 803 : if (*p == 0x20) {
233 0 : aValue.Append(0x2B);
234 : // Percent Encode algorithm
235 1540 : } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E ||
236 1925 : (*p >= 0x30 && *p <= 0x39) ||
237 1331 : (*p >= 0x41 && *p <= 0x5A) || *p == 0x5F ||
238 858 : (*p >= 0x61 && *p <= 0x7A)) {
239 803 : aValue.Append(*p);
240 : } else {
241 0 : aValue.AppendPrintf("%%%.2X", *p);
242 : }
243 :
244 803 : ++p;
245 : }
246 22 : }
247 :
248 : } // namespace
249 :
250 : void
251 549 : URLParams::Serialize(nsAString& aValue) const
252 : {
253 549 : aValue.Truncate();
254 549 : bool first = true;
255 :
256 560 : for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
257 11 : if (first) {
258 11 : first = false;
259 : } else {
260 0 : aValue.Append('&');
261 : }
262 :
263 11 : SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
264 11 : aValue.Append('=');
265 11 : SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
266 : }
267 549 : }
268 :
269 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mParent, mObserver)
270 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
271 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
272 :
273 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
274 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
275 0 : NS_INTERFACE_MAP_ENTRY(nsIXHRSendable)
276 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
277 0 : NS_INTERFACE_MAP_END
278 :
279 0 : URLSearchParams::URLSearchParams(nsISupports* aParent,
280 0 : URLSearchParamsObserver* aObserver)
281 0 : : mParams(new URLParams())
282 : , mParent(aParent)
283 0 : , mObserver(aObserver)
284 : {
285 0 : }
286 :
287 0 : URLSearchParams::~URLSearchParams()
288 : {
289 0 : DeleteAll();
290 0 : }
291 :
292 : JSObject*
293 0 : URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
294 : {
295 0 : return URLSearchParamsBinding::Wrap(aCx, this, aGivenProto);
296 : }
297 :
298 : /* static */ already_AddRefed<URLSearchParams>
299 0 : URLSearchParams::Constructor(const GlobalObject& aGlobal,
300 : const USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString& aInit,
301 : ErrorResult& aRv)
302 : {
303 : RefPtr<URLSearchParams> sp =
304 0 : new URLSearchParams(aGlobal.GetAsSupports(), nullptr);
305 :
306 0 : if (aInit.IsUSVString()) {
307 0 : NS_ConvertUTF16toUTF8 input(aInit.GetAsUSVString());
308 0 : if (StringBeginsWith(input, NS_LITERAL_CSTRING("?"))) {
309 0 : sp->ParseInput(Substring(input, 1, input.Length() - 1));
310 : } else {
311 0 : sp->ParseInput(input);
312 : }
313 0 : } else if (aInit.IsUSVStringSequenceSequence()) {
314 : const Sequence<Sequence<nsString>>& list =
315 0 : aInit.GetAsUSVStringSequenceSequence();
316 0 : for (uint32_t i = 0; i < list.Length(); ++i) {
317 0 : const Sequence<nsString>& item = list[i];
318 0 : if (item.Length() != 2) {
319 0 : aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
320 0 : return nullptr;
321 : }
322 0 : sp->Append(item[0], item[1]);
323 : }
324 0 : } else if (aInit.IsUSVStringUSVStringRecord()) {
325 : const Record<nsString, nsString>& record =
326 0 : aInit.GetAsUSVStringUSVStringRecord();
327 0 : for (auto& entry : record.Entries()) {
328 0 : sp->Append(entry.mKey, entry.mValue);
329 : }
330 : } else {
331 0 : MOZ_CRASH("This should not happen.");
332 : }
333 :
334 0 : return sp.forget();
335 : }
336 :
337 : void
338 0 : URLSearchParams::ParseInput(const nsACString& aInput)
339 : {
340 0 : mParams->ParseInput(aInput);
341 0 : }
342 :
343 : void
344 0 : URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
345 : {
346 0 : return mParams->Get(aName, aRetval);
347 : }
348 :
349 : void
350 0 : URLSearchParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
351 : {
352 0 : return mParams->GetAll(aName, aRetval);
353 : }
354 :
355 : void
356 0 : URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
357 : {
358 0 : mParams->Set(aName, aValue);
359 0 : NotifyObserver();
360 0 : }
361 :
362 : void
363 0 : URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
364 : {
365 0 : mParams->Append(aName, aValue);
366 0 : NotifyObserver();
367 0 : }
368 :
369 : bool
370 0 : URLSearchParams::Has(const nsAString& aName)
371 : {
372 0 : return mParams->Has(aName);
373 : }
374 :
375 : void
376 0 : URLSearchParams::Delete(const nsAString& aName)
377 : {
378 0 : if (mParams->Delete(aName)) {
379 0 : NotifyObserver();
380 : }
381 0 : }
382 :
383 : void
384 0 : URLSearchParams::DeleteAll()
385 : {
386 0 : mParams->DeleteAll();
387 0 : }
388 :
389 : void
390 0 : URLSearchParams::Serialize(nsAString& aValue) const
391 : {
392 0 : mParams->Serialize(aValue);
393 0 : }
394 :
395 : void
396 0 : URLSearchParams::NotifyObserver()
397 : {
398 0 : if (mObserver) {
399 0 : mObserver->URLSearchParamsUpdated(this);
400 : }
401 0 : }
402 :
403 : uint32_t
404 0 : URLSearchParams::GetIterableLength() const
405 : {
406 0 : return mParams->Length();
407 : }
408 :
409 : const nsAString&
410 0 : URLSearchParams::GetKeyAtIndex(uint32_t aIndex) const
411 : {
412 0 : return mParams->GetKeyAtIndex(aIndex);
413 : }
414 :
415 : const nsAString&
416 0 : URLSearchParams::GetValueAtIndex(uint32_t aIndex) const
417 : {
418 0 : return mParams->GetValueAtIndex(aIndex);
419 : }
420 :
421 : void
422 0 : URLSearchParams::Sort(ErrorResult& aRv)
423 : {
424 0 : aRv = mParams->Sort();
425 0 : if (!aRv.Failed()) {
426 0 : NotifyObserver();
427 : }
428 0 : }
429 :
430 : // Helper functions for structured cloning
431 : inline bool
432 0 : ReadString(JSStructuredCloneReader* aReader, nsString& aString)
433 : {
434 0 : MOZ_ASSERT(aReader);
435 :
436 : bool read;
437 : uint32_t nameLength, zero;
438 0 : read = JS_ReadUint32Pair(aReader, &nameLength, &zero);
439 0 : if (!read) {
440 0 : return false;
441 : }
442 0 : MOZ_ASSERT(zero == 0);
443 0 : aString.SetLength(nameLength);
444 0 : size_t charSize = sizeof(nsString::char_type);
445 0 : read = JS_ReadBytes(aReader, (void*) aString.BeginWriting(),
446 0 : nameLength * charSize);
447 0 : if (!read) {
448 0 : return false;
449 : }
450 :
451 0 : return true;
452 : }
453 :
454 : nsresult
455 0 : URLParams::Sort()
456 : {
457 : // Unfortunately we cannot use nsTArray<>.Sort() because it doesn't keep the
458 : // correct order of the values for equal keys.
459 :
460 : // Let's sort the keys, without duplicates.
461 0 : FallibleTArray<nsString> keys;
462 0 : for (const Param& param : mParams) {
463 0 : if (!keys.Contains(param.mKey) &&
464 0 : !keys.InsertElementSorted(param.mKey, fallible)) {
465 0 : return NS_ERROR_OUT_OF_MEMORY;
466 : }
467 : }
468 :
469 0 : FallibleTArray<Param> params;
470 :
471 : // Here we recreate the array starting from the sorted keys.
472 0 : for (uint32_t keyId = 0, keysLength = keys.Length(); keyId < keysLength;
473 : ++keyId) {
474 0 : const nsString& key = keys[keyId];
475 0 : for (const Param& param : mParams) {
476 0 : if (param.mKey.Equals(key) &&
477 0 : !params.AppendElement(param, fallible)) {
478 0 : return NS_ERROR_OUT_OF_MEMORY;
479 : }
480 : }
481 : }
482 :
483 0 : mParams.SwapElements(params);
484 0 : return NS_OK;
485 : }
486 :
487 : inline bool
488 0 : WriteString(JSStructuredCloneWriter* aWriter, const nsString& aString)
489 : {
490 0 : MOZ_ASSERT(aWriter);
491 :
492 0 : size_t charSize = sizeof(nsString::char_type);
493 0 : return JS_WriteUint32Pair(aWriter, aString.Length(), 0) &&
494 0 : JS_WriteBytes(aWriter, aString.get(), aString.Length() * charSize);
495 : }
496 :
497 : bool
498 0 : URLParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
499 : {
500 0 : const uint32_t& nParams = mParams.Length();
501 0 : if (!JS_WriteUint32Pair(aWriter, nParams, 0)) {
502 0 : return false;
503 : }
504 0 : for (uint32_t i = 0; i < nParams; ++i) {
505 0 : if (!WriteString(aWriter, mParams[i].mKey) ||
506 0 : !WriteString(aWriter, mParams[i].mValue)) {
507 0 : return false;
508 : }
509 : }
510 0 : return true;
511 : }
512 :
513 : bool
514 0 : URLParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
515 : {
516 0 : MOZ_ASSERT(aReader);
517 :
518 0 : DeleteAll();
519 :
520 : uint32_t nParams, zero;
521 0 : nsAutoString key, value;
522 0 : if (!JS_ReadUint32Pair(aReader, &nParams, &zero)) {
523 0 : return false;
524 : }
525 0 : MOZ_ASSERT(zero == 0);
526 0 : for (uint32_t i = 0; i < nParams; ++i) {
527 0 : if (!ReadString(aReader, key) || !ReadString(aReader, value)) {
528 0 : return false;
529 : }
530 0 : Append(key, value);
531 : }
532 0 : return true;
533 : }
534 :
535 : bool
536 0 : URLSearchParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
537 : {
538 0 : return mParams->WriteStructuredClone(aWriter);
539 : }
540 :
541 : bool
542 0 : URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
543 : {
544 0 : return mParams->ReadStructuredClone(aReader);
545 : }
546 :
547 : NS_IMETHODIMP
548 0 : URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
549 : nsACString& aContentTypeWithCharset,
550 : nsACString& aCharset)
551 : {
552 0 : aContentTypeWithCharset.AssignLiteral("application/x-www-form-urlencoded;charset=UTF-8");
553 0 : aCharset.AssignLiteral("UTF-8");
554 :
555 0 : nsAutoString serialized;
556 0 : Serialize(serialized);
557 0 : NS_ConvertUTF16toUTF8 converted(serialized);
558 0 : *aContentLength = converted.Length();
559 0 : return NS_NewCStringInputStream(aBody, converted);
560 : }
561 :
562 : } // namespace dom
563 : } // namespace mozilla
|