Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/url/URLSearchParams.cpp
Line
Count
Source (jump to first uncovered line)
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
URLParams::Has(const nsAString& aName)
19
0
{
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
0
    }
24
0
  }
25
0
26
0
  return false;
27
0
}
28
29
void
30
URLParams::Get(const nsAString& aName, nsString& aRetval)
31
0
{
32
0
  SetDOMStringToNull(aRetval);
33
0
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
0
    }
39
0
  }
40
0
}
41
42
void
43
URLParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
44
0
{
45
0
  aRetval.Clear();
46
0
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
0
    }
51
0
  }
52
0
}
53
54
void
55
URLParams::Append(const nsAString& aName, const nsAString& aValue)
56
0
{
57
0
  Param* param = mParams.AppendElement();
58
0
  param->mKey = aName;
59
0
  param->mValue = aValue;
60
0
}
61
62
void
63
URLParams::Set(const nsAString& aName, const nsAString& aValue)
64
0
{
65
0
  Param* param = nullptr;
66
0
  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
0
    }
71
0
    if (!param) {
72
0
      param = &mParams[i];
73
0
      ++i;
74
0
      continue;
75
0
    }
76
0
    // Remove duplicates.
77
0
    mParams.RemoveElementAt(i);
78
0
    --len;
79
0
  }
80
0
81
0
  if (!param) {
82
0
    param = mParams.AppendElement();
83
0
    param->mKey = aName;
84
0
  }
85
0
86
0
  param->mValue = aValue;
87
0
}
88
89
void
90
URLParams::Delete(const nsAString& aName)
91
0
{
92
0
  for (uint32_t i = 0; i < mParams.Length();) {
93
0
    if (mParams[i].mKey.Equals(aName)) {
94
0
      mParams.RemoveElementAt(i);
95
0
    } else {
96
0
      ++i;
97
0
    }
98
0
  }
99
0
}
100
101
/* static */ void
102
URLParams::ConvertString(const nsACString& aInput, nsAString& aOutput)
103
0
{
104
0
  if (NS_FAILED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aInput, aOutput))) {
105
0
    MOZ_CRASH("Out of memory when converting URL params.");
106
0
  }
107
0
}
108
109
/* static */ void
110
URLParams::DecodeString(const nsACString& aInput, nsAString& aOutput)
111
0
{
112
0
  nsACString::const_iterator start, end;
113
0
  aInput.BeginReading(start);
114
0
  aInput.EndReading(end);
115
0
116
0
  nsCString unescaped;
117
0
118
0
  while (start != end) {
119
0
    // replace '+' with U+0020
120
0
    if (*start == '+') {
121
0
      unescaped.Append(' ');
122
0
      ++start;
123
0
      continue;
124
0
    }
125
0
126
0
    // Percent decode algorithm
127
0
    if (*start == '%') {
128
0
      nsACString::const_iterator first(start);
129
0
      ++first;
130
0
131
0
      nsACString::const_iterator second(first);
132
0
      ++second;
133
0
134
0
#define ASCII_HEX_DIGIT( x )    \
135
0
  ((x >= 0x41 && x <= 0x46) ||  \
136
0
   (x >= 0x61 && x <= 0x66) ||  \
137
0
   (x >= 0x30 && x <= 0x39))
138
0
139
0
#define HEX_DIGIT( x )              \
140
0
   (*x >= 0x30 && *x <= 0x39        \
141
0
     ? *x - 0x30                    \
142
0
     : (*x >= 0x41 && *x <= 0x46    \
143
0
        ? *x - 0x37                 \
144
0
        : *x - 0x57))
145
0
146
0
      if (first != end && second != end &&
147
0
          ASCII_HEX_DIGIT(*first) && ASCII_HEX_DIGIT(*second)) {
148
0
        unescaped.Append(HEX_DIGIT(first) * 16 + HEX_DIGIT(second));
149
0
        start = ++second;
150
0
        continue;
151
0
152
0
      } else {
153
0
        unescaped.Append('%');
154
0
        ++start;
155
0
        continue;
156
0
      }
157
0
    }
158
0
159
0
    unescaped.Append(*start);
160
0
    ++start;
161
0
  }
162
0
163
0
  ConvertString(unescaped, aOutput);
164
0
}
165
166
/* static */ bool
167
URLParams::Parse(const nsACString& aInput, ForEachIterator& aIterator)
168
0
{
169
0
  nsACString::const_iterator start, end;
170
0
  aInput.BeginReading(start);
171
0
  aInput.EndReading(end);
172
0
  nsACString::const_iterator iter(start);
173
0
174
0
  while (start != end) {
175
0
    nsAutoCString string;
176
0
177
0
    if (FindCharInReadable('&', iter, end)) {
178
0
      string.Assign(Substring(start, iter));
179
0
      start = ++iter;
180
0
    } else {
181
0
      string.Assign(Substring(start, end));
182
0
      start = end;
183
0
    }
184
0
185
0
    if (string.IsEmpty()) {
186
0
      continue;
187
0
    }
188
0
189
0
    nsACString::const_iterator eqStart, eqEnd;
190
0
    string.BeginReading(eqStart);
191
0
    string.EndReading(eqEnd);
192
0
    nsACString::const_iterator eqIter(eqStart);
193
0
194
0
    nsAutoCString name;
195
0
    nsAutoCString value;
196
0
197
0
    if (FindCharInReadable('=', eqIter, eqEnd)) {
198
0
      name.Assign(Substring(eqStart, eqIter));
199
0
200
0
      ++eqIter;
201
0
      value.Assign(Substring(eqIter, eqEnd));
202
0
    } else {
203
0
      name.Assign(string);
204
0
    }
205
0
206
0
    nsAutoString decodedName;
207
0
    DecodeString(name, decodedName);
208
0
209
0
    nsAutoString decodedValue;
210
0
    DecodeString(value, decodedValue);
211
0
212
0
    if (!aIterator.URLParamsIterator(decodedName, decodedValue)) {
213
0
      return false;
214
0
    }
215
0
  }
216
0
  return true;
217
0
}
218
219
class MOZ_STACK_CLASS ExtractURLParam final
220
  : public URLParams::ForEachIterator
221
{
222
public:
223
  explicit ExtractURLParam(const nsAString& aName, nsAString& aValue)
224
    : mName(aName), mValue(aValue)
225
0
  {}
226
227
  bool URLParamsIterator(const nsAString& aName,
228
                         const nsAString& aValue) override
229
0
  {
230
0
    if (mName == aName) {
231
0
      mValue = aValue;
232
0
      return false;
233
0
    }
234
0
    return true;
235
0
  }
236
237
private:
238
  const nsAString& mName;
239
  nsAString& mValue;
240
};
241
242
243
/**
244
 * Extracts the first form-urlencoded parameter named `aName` from `aInput`.
245
 * @param aRange The input to parse.
246
 * @param aName The name of the parameter to extract.
247
 * @param aValue The value of the extracted parameter, void if not found.
248
 * @return Whether the parameter was found in the form-urlencoded.
249
 */
250
/* static */ bool
251
URLParams::Extract(const nsACString& aInput,
252
                   const nsAString& aName,
253
                   nsAString& aValue)
254
0
{
255
0
  aValue.SetIsVoid(true);
256
0
  ExtractURLParam iterator(aName, aValue);
257
0
  return !URLParams::Parse(aInput, iterator);
258
0
}
259
260
class MOZ_STACK_CLASS PopulateIterator final
261
  : public URLParams::ForEachIterator
262
{
263
public:
264
  explicit PopulateIterator(URLParams* aParams)
265
    : mParams(aParams)
266
0
  {
267
0
    MOZ_ASSERT(aParams);
268
0
  }
269
270
  bool URLParamsIterator(const nsAString& aName,
271
                         const nsAString& aValue) override
272
0
  {
273
0
    mParams->Append(aName, aValue);
274
0
    return true;
275
0
  }
276
277
private:
278
  URLParams* mParams;
279
};
280
281
void
282
URLParams::ParseInput(const nsACString& aInput)
283
0
{
284
0
  // Remove all the existing data before parsing a new input.
285
0
  DeleteAll();
286
0
287
0
  PopulateIterator iter(this);
288
0
  URLParams::Parse(aInput, iter);
289
0
}
290
291
namespace {
292
293
void SerializeString(const nsCString& aInput, nsAString& aValue)
294
0
{
295
0
  const unsigned char* p = (const unsigned char*) aInput.get();
296
0
  const unsigned char* end = p + aInput.Length();
297
0
298
0
  while (p != end) {
299
0
    // ' ' to '+'
300
0
    if (*p == 0x20) {
301
0
      aValue.Append(0x2B);
302
0
    // Percent Encode algorithm
303
0
    } else if (*p == 0x2A || *p == 0x2D || *p == 0x2E ||
304
0
               (*p >= 0x30 && *p <= 0x39) ||
305
0
               (*p >= 0x41 && *p <= 0x5A) || *p == 0x5F ||
306
0
               (*p >= 0x61 && *p <= 0x7A)) {
307
0
      aValue.Append(*p);
308
0
    } else {
309
0
      aValue.AppendPrintf("%%%.2X", *p);
310
0
    }
311
0
312
0
    ++p;
313
0
  }
314
0
}
315
316
} // namespace
317
318
void
319
URLParams::Serialize(nsAString& aValue) const
320
5.78k
{
321
5.78k
  aValue.Truncate();
322
5.78k
  bool first = true;
323
5.78k
324
5.78k
  for (uint32_t i = 0, len = mParams.Length(); i < len; ++i) {
325
0
    if (first) {
326
0
      first = false;
327
0
    } else {
328
0
      aValue.Append('&');
329
0
    }
330
0
331
0
    SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
332
0
    aValue.Append('=');
333
0
    SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
334
0
  }
335
5.78k
}
336
337
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URLSearchParams, mParent, mObserver)
338
NS_IMPL_CYCLE_COLLECTING_ADDREF(URLSearchParams)
339
NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
340
341
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
342
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
343
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
344
0
NS_INTERFACE_MAP_END
345
346
URLSearchParams::URLSearchParams(nsISupports* aParent,
347
                                 URLSearchParamsObserver* aObserver)
348
  : mParams(new URLParams())
349
  , mParent(aParent)
350
  , mObserver(aObserver)
351
0
{
352
0
}
353
354
URLSearchParams::~URLSearchParams()
355
0
{
356
0
  DeleteAll();
357
0
}
358
359
JSObject*
360
URLSearchParams::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
361
0
{
362
0
  return URLSearchParams_Binding::Wrap(aCx, this, aGivenProto);
363
0
}
364
365
/* static */ already_AddRefed<URLSearchParams>
366
URLSearchParams::Constructor(const GlobalObject& aGlobal,
367
                             const USVStringSequenceSequenceOrUSVStringUSVStringRecordOrUSVString& aInit,
368
                             ErrorResult& aRv)
369
0
{
370
0
  RefPtr<URLSearchParams> sp =
371
0
    new URLSearchParams(aGlobal.GetAsSupports(), nullptr);
372
0
373
0
  if (aInit.IsUSVString()) {
374
0
    NS_ConvertUTF16toUTF8 input(aInit.GetAsUSVString());
375
0
    if (StringBeginsWith(input, NS_LITERAL_CSTRING("?"))) {
376
0
      sp->ParseInput(Substring(input, 1, input.Length() - 1));
377
0
    } else {
378
0
      sp->ParseInput(input);
379
0
    }
380
0
  } else if (aInit.IsUSVStringSequenceSequence()) {
381
0
    const Sequence<Sequence<nsString>>& list =
382
0
      aInit.GetAsUSVStringSequenceSequence();
383
0
    for (uint32_t i = 0; i < list.Length(); ++i) {
384
0
      const Sequence<nsString>& item = list[i];
385
0
      if (item.Length() != 2) {
386
0
        aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
387
0
        return nullptr;
388
0
      }
389
0
      sp->Append(item[0], item[1]);
390
0
    }
391
0
  } else if (aInit.IsUSVStringUSVStringRecord()) {
392
0
    const Record<nsString, nsString>& record =
393
0
      aInit.GetAsUSVStringUSVStringRecord();
394
0
    for (auto& entry : record.Entries()) {
395
0
      sp->Append(entry.mKey, entry.mValue);
396
0
    }
397
0
  } else {
398
0
    MOZ_CRASH("This should not happen.");
399
0
  }
400
0
401
0
  return sp.forget();
402
0
}
403
404
void
405
URLSearchParams::ParseInput(const nsACString& aInput)
406
0
{
407
0
  mParams->ParseInput(aInput);
408
0
}
409
410
void
411
URLSearchParams::Get(const nsAString& aName, nsString& aRetval)
412
0
{
413
0
  return mParams->Get(aName, aRetval);
414
0
}
415
416
void
417
URLSearchParams::GetAll(const nsAString& aName, nsTArray<nsString>& aRetval)
418
0
{
419
0
  return mParams->GetAll(aName, aRetval);
420
0
}
421
422
void
423
URLSearchParams::Set(const nsAString& aName, const nsAString& aValue)
424
0
{
425
0
  mParams->Set(aName, aValue);
426
0
  NotifyObserver();
427
0
}
428
429
void
430
URLSearchParams::Append(const nsAString& aName, const nsAString& aValue)
431
0
{
432
0
  mParams->Append(aName, aValue);
433
0
  NotifyObserver();
434
0
}
435
436
bool
437
URLSearchParams::Has(const nsAString& aName)
438
0
{
439
0
  return mParams->Has(aName);
440
0
}
441
442
void
443
URLSearchParams::Delete(const nsAString& aName)
444
0
{
445
0
  mParams->Delete(aName);
446
0
  NotifyObserver();
447
0
}
448
449
void
450
URLSearchParams::DeleteAll()
451
0
{
452
0
  mParams->DeleteAll();
453
0
}
454
455
void
456
URLSearchParams::Serialize(nsAString& aValue) const
457
0
{
458
0
  mParams->Serialize(aValue);
459
0
}
460
461
void
462
URLSearchParams::NotifyObserver()
463
0
{
464
0
  if (mObserver) {
465
0
    mObserver->URLSearchParamsUpdated(this);
466
0
  }
467
0
}
468
469
uint32_t
470
URLSearchParams::GetIterableLength() const
471
0
{
472
0
  return mParams->Length();
473
0
}
474
475
const nsAString&
476
URLSearchParams::GetKeyAtIndex(uint32_t aIndex) const
477
0
{
478
0
  return mParams->GetKeyAtIndex(aIndex);
479
0
}
480
481
const nsAString&
482
URLSearchParams::GetValueAtIndex(uint32_t aIndex) const
483
0
{
484
0
  return mParams->GetValueAtIndex(aIndex);
485
0
}
486
487
void
488
URLSearchParams::Sort(ErrorResult& aRv)
489
0
{
490
0
  aRv = mParams->Sort();
491
0
  if (!aRv.Failed()) {
492
0
    NotifyObserver();
493
0
  }
494
0
}
495
496
// Helper functions for structured cloning
497
inline bool
498
ReadString(JSStructuredCloneReader* aReader, nsString& aString)
499
{
500
  MOZ_ASSERT(aReader);
501
502
  bool read;
503
  uint32_t nameLength, zero;
504
  read = JS_ReadUint32Pair(aReader, &nameLength, &zero);
505
  if (!read) {
506
    return false;
507
  }
508
  MOZ_ASSERT(zero == 0);
509
  if (NS_WARN_IF(!aString.SetLength(nameLength, fallible))) {
510
    return false;
511
  }
512
  size_t charSize = sizeof(nsString::char_type);
513
  read = JS_ReadBytes(aReader, (void*) aString.BeginWriting(),
514
                      nameLength * charSize);
515
  if (!read) {
516
    return false;
517
  }
518
519
  return true;
520
}
521
522
nsresult
523
URLParams::Sort()
524
0
{
525
0
  // Unfortunately we cannot use nsTArray<>.Sort() because it doesn't keep the
526
0
  // correct order of the values for equal keys.
527
0
528
0
  // Let's sort the keys, without duplicates.
529
0
  FallibleTArray<nsString> keys;
530
0
  for (const Param& param : mParams) {
531
0
    if (!keys.Contains(param.mKey) &&
532
0
        !keys.InsertElementSorted(param.mKey, fallible)) {
533
0
      return NS_ERROR_OUT_OF_MEMORY;
534
0
    }
535
0
  }
536
0
537
0
  FallibleTArray<Param> params;
538
0
539
0
  // Here we recreate the array starting from the sorted keys.
540
0
  for (uint32_t keyId = 0, keysLength = keys.Length(); keyId < keysLength;
541
0
       ++keyId) {
542
0
    const nsString& key = keys[keyId];
543
0
    for (const Param& param : mParams) {
544
0
      if (param.mKey.Equals(key) &&
545
0
          !params.AppendElement(param, fallible)) {
546
0
        return NS_ERROR_OUT_OF_MEMORY;
547
0
      }
548
0
    }
549
0
  }
550
0
551
0
  mParams.SwapElements(params);
552
0
  return NS_OK;
553
0
}
554
555
inline bool
556
WriteString(JSStructuredCloneWriter* aWriter, const nsString& aString)
557
{
558
  MOZ_ASSERT(aWriter);
559
560
  size_t charSize = sizeof(nsString::char_type);
561
  return JS_WriteUint32Pair(aWriter, aString.Length(), 0) &&
562
         JS_WriteBytes(aWriter, aString.get(), aString.Length() * charSize);
563
}
564
565
bool
566
URLParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
567
0
{
568
0
  const uint32_t& nParams = mParams.Length();
569
0
  if (!JS_WriteUint32Pair(aWriter, nParams, 0)) {
570
0
    return false;
571
0
  }
572
0
  for (uint32_t i = 0; i < nParams; ++i) {
573
0
    if (!WriteString(aWriter, mParams[i].mKey) ||
574
0
        !WriteString(aWriter, mParams[i].mValue)) {
575
0
      return false;
576
0
    }
577
0
  }
578
0
  return true;
579
0
}
580
581
bool
582
URLParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
583
0
{
584
0
  MOZ_ASSERT(aReader);
585
0
586
0
  DeleteAll();
587
0
588
0
  uint32_t nParams, zero;
589
0
  nsAutoString key, value;
590
0
  if (!JS_ReadUint32Pair(aReader, &nParams, &zero)) {
591
0
    return false;
592
0
  }
593
0
  MOZ_ASSERT(zero == 0);
594
0
  for (uint32_t i = 0; i < nParams; ++i) {
595
0
    if (!ReadString(aReader, key) || !ReadString(aReader, value)) {
596
0
      return false;
597
0
    }
598
0
    Append(key, value);
599
0
  }
600
0
  return true;
601
0
}
602
603
bool
604
URLSearchParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
605
0
{
606
0
  return mParams->WriteStructuredClone(aWriter);
607
0
}
608
609
bool
610
URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
611
0
{
612
0
 return mParams->ReadStructuredClone(aReader);
613
0
}
614
615
// contentTypeWithCharset can be set to the contentType or
616
// contentType+charset based on what the spec says.
617
// See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
618
nsresult
619
URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
620
                             nsACString& aContentTypeWithCharset,
621
                             nsACString& aCharset) const
622
0
{
623
0
  aContentTypeWithCharset.AssignLiteral("application/x-www-form-urlencoded;charset=UTF-8");
624
0
  aCharset.AssignLiteral("UTF-8");
625
0
626
0
  nsAutoString serialized;
627
0
  Serialize(serialized);
628
0
  NS_ConvertUTF16toUTF8 converted(serialized);
629
0
  *aContentLength = converted.Length();
630
0
  return NS_NewCStringInputStream(aBody, std::move(converted));
631
0
}
632
633
} // namespace dom
634
} // namespace mozilla