Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/extensions/MatchPattern.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "mozilla/extensions/MatchPattern.h"
7
#include "mozilla/extensions/MatchGlob.h"
8
9
#include "mozilla/dom/ScriptSettings.h"
10
#include "mozilla/HoldDropJSObjects.h"
11
#include "mozilla/Unused.h"
12
13
#include "nsGkAtoms.h"
14
#include "nsIProtocolHandler.h"
15
#include "nsIURL.h"
16
#include "nsNetUtil.h"
17
18
namespace mozilla {
19
namespace extensions {
20
21
using namespace mozilla::dom;
22
23
24
/*****************************************************************************
25
 * AtomSet
26
 *****************************************************************************/
27
28
AtomSet::AtomSet(const nsTArray<nsString>& aElems)
29
0
{
30
0
  mElems.SetCapacity(aElems.Length());
31
0
32
0
  for (const auto& elem : aElems) {
33
0
    mElems.AppendElement(NS_AtomizeMainThread(elem));
34
0
  }
35
0
36
0
  SortAndUniquify();
37
0
}
38
39
AtomSet::AtomSet(const char** aElems)
40
0
{
41
0
  for (const char** elemp = aElems; *elemp; elemp++) {
42
0
    mElems.AppendElement(NS_Atomize(*elemp));
43
0
  }
44
0
45
0
  SortAndUniquify();
46
0
}
47
48
AtomSet::AtomSet(std::initializer_list<nsAtom*> aIL)
49
0
{
50
0
  mElems.SetCapacity(aIL.size());
51
0
52
0
  for (const auto& elem : aIL) {
53
0
    mElems.AppendElement(elem);
54
0
  }
55
0
56
0
  SortAndUniquify();
57
0
}
58
59
void
60
AtomSet::SortAndUniquify()
61
0
{
62
0
  mElems.Sort();
63
0
64
0
  nsAtom* prev = nullptr;
65
0
  mElems.RemoveElementsBy([&prev] (const RefPtr<nsAtom>& aAtom) {
66
0
    bool remove = aAtom == prev;
67
0
    prev = aAtom;
68
0
    return remove;
69
0
  });
70
0
71
0
  mElems.Compact();
72
0
}
73
74
bool
75
AtomSet::Intersects(const AtomSet& aOther) const
76
0
{
77
0
  for (const auto& atom : *this) {
78
0
    if (aOther.Contains(atom)) {
79
0
      return true;
80
0
    }
81
0
  }
82
0
  for (const auto& atom : aOther) {
83
0
    if (Contains(atom)) {
84
0
      return true;
85
0
    }
86
0
  }
87
0
  return false;
88
0
}
89
90
void
91
AtomSet::Add(nsAtom* aAtom)
92
0
{
93
0
  auto index = mElems.IndexOfFirstElementGt(aAtom);
94
0
  if (index == 0 || mElems[index - 1] != aAtom) {
95
0
    mElems.InsertElementAt(index, aAtom);
96
0
  }
97
0
}
98
99
void
100
AtomSet::Remove(nsAtom* aAtom)
101
0
{
102
0
  auto index = mElems.BinaryIndexOf(aAtom);
103
0
  if (index != ArrayType::NoIndex) {
104
0
    mElems.RemoveElementAt(index);
105
0
  }
106
0
}
107
108
109
/*****************************************************************************
110
 * URLInfo
111
 *****************************************************************************/
112
113
nsAtom*
114
URLInfo::Scheme() const
115
0
{
116
0
  if (!mScheme) {
117
0
    nsCString scheme;
118
0
    if (NS_SUCCEEDED(mURI->GetScheme(scheme))) {
119
0
      mScheme = NS_AtomizeMainThread(NS_ConvertASCIItoUTF16(scheme));
120
0
    }
121
0
  }
122
0
  return mScheme;
123
0
}
124
125
const nsCString&
126
URLInfo::Host() const
127
0
{
128
0
  if (mHost.IsVoid()) {
129
0
    Unused << mURI->GetHost(mHost);
130
0
  }
131
0
  return mHost;
132
0
}
133
134
const nsAtom*
135
URLInfo::HostAtom() const
136
0
{
137
0
  if (!mHostAtom) {
138
0
    mHostAtom = NS_Atomize(Host());
139
0
  }
140
0
  return mHostAtom;
141
0
}
142
143
const nsString&
144
URLInfo::FilePath() const
145
0
{
146
0
  if (mFilePath.IsEmpty()) {
147
0
    nsCString path;
148
0
    nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
149
0
    if (url && NS_SUCCEEDED(url->GetFilePath(path))) {
150
0
      AppendUTF8toUTF16(path, mFilePath);
151
0
    } else {
152
0
      mFilePath = Path();
153
0
    }
154
0
  }
155
0
  return mFilePath;
156
0
}
157
158
const nsString&
159
URLInfo::Path() const
160
0
{
161
0
  if (mPath.IsEmpty()) {
162
0
    nsCString path;
163
0
    if (NS_SUCCEEDED(URINoRef()->GetPathQueryRef(path))) {
164
0
      AppendUTF8toUTF16(path, mPath);
165
0
    }
166
0
  }
167
0
  return mPath;
168
0
}
169
170
const nsCString&
171
URLInfo::CSpec() const
172
0
{
173
0
  if (mCSpec.IsEmpty()) {
174
0
    Unused << URINoRef()->GetSpec(mCSpec);
175
0
  }
176
0
  return mCSpec;
177
0
}
178
179
const nsString&
180
URLInfo::Spec() const
181
0
{
182
0
  if (mSpec.IsEmpty()) {
183
0
    AppendUTF8toUTF16(CSpec(), mSpec);
184
0
  }
185
0
  return mSpec;
186
0
}
187
188
nsIURI*
189
URLInfo::URINoRef() const
190
0
{
191
0
  if (!mURINoRef) {
192
0
    if (NS_FAILED(NS_GetURIWithoutRef(mURI, getter_AddRefs(mURINoRef)))) {
193
0
      mURINoRef = mURI;
194
0
    }
195
0
  }
196
0
  return mURINoRef;
197
0
}
198
199
bool
200
URLInfo::InheritsPrincipal() const
201
0
{
202
0
  if (!mInheritsPrincipal.isSome()) {
203
0
    // For our purposes, about:blank and about:srcdoc are treated as URIs that
204
0
    // inherit principals.
205
0
    bool inherits = Spec().EqualsLiteral("about:blank") || Spec().EqualsLiteral("about:srcdoc");
206
0
207
0
    if (!inherits) {
208
0
      nsresult rv = NS_URIChainHasFlags(mURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
209
0
                                        &inherits);
210
0
      Unused << NS_WARN_IF(NS_FAILED(rv));
211
0
    }
212
0
213
0
    mInheritsPrincipal.emplace(inherits);
214
0
  }
215
0
  return mInheritsPrincipal.ref();
216
0
}
217
218
219
/*****************************************************************************
220
 * CookieInfo
221
 *****************************************************************************/
222
223
bool
224
CookieInfo::IsDomain() const
225
0
{
226
0
  if (mIsDomain.isNothing()) {
227
0
    mIsDomain.emplace(false);
228
0
    MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsDomain(mIsDomain.ptr()));
229
0
  }
230
0
  return mIsDomain.ref();
231
0
}
232
233
bool
234
CookieInfo::IsSecure() const
235
0
{
236
0
  if (mIsSecure.isNothing()) {
237
0
    mIsSecure.emplace(false);
238
0
    MOZ_ALWAYS_SUCCEEDS(mCookie->GetIsSecure(mIsSecure.ptr()));
239
0
  }
240
0
  return mIsSecure.ref();
241
0
}
242
243
const nsCString&
244
CookieInfo::Host() const
245
0
{
246
0
  if (mHost.IsEmpty()) {
247
0
    MOZ_ALWAYS_SUCCEEDS(mCookie->GetHost(mHost));
248
0
  }
249
0
  return mHost;
250
0
}
251
252
const nsCString&
253
CookieInfo::RawHost() const
254
0
{
255
0
  if (mRawHost.IsEmpty()) {
256
0
    MOZ_ALWAYS_SUCCEEDS(mCookie->GetRawHost(mRawHost));
257
0
  }
258
0
  return mRawHost;
259
0
}
260
261
262
/*****************************************************************************
263
 * MatchPattern
264
 *****************************************************************************/
265
266
const char* PERMITTED_SCHEMES[] = {"http", "https", "ws", "wss", "file", "ftp", "data", nullptr};
267
268
// Known schemes that are followed by "://" instead of ":".
269
const char* HOST_LOCATOR_SCHEMES[] = {"http", "https", "ws", "wss", "file", "ftp", "moz-extension", "chrome", "resource", "moz", "moz-icon", "moz-gio", nullptr};
270
271
const char* WILDCARD_SCHEMES[] = {"http", "https", "ws", "wss", nullptr};
272
273
/* static */ already_AddRefed<MatchPattern>
274
MatchPattern::Constructor(dom::GlobalObject& aGlobal,
275
                          const nsAString& aPattern,
276
                          const MatchPatternOptions& aOptions,
277
                          ErrorResult& aRv)
278
0
{
279
0
  RefPtr<MatchPattern> pattern = new MatchPattern(aGlobal.GetAsSupports());
280
0
  pattern->Init(aGlobal.Context(), aPattern, aOptions.mIgnorePath,
281
0
                aOptions.mRestrictSchemes, aRv);
282
0
  if (aRv.Failed()) {
283
0
    return nullptr;
284
0
  }
285
0
  return pattern.forget();
286
0
}
287
288
void
289
MatchPattern::Init(JSContext* aCx, const nsAString& aPattern, bool aIgnorePath,
290
                   bool aRestrictSchemes, ErrorResult& aRv)
291
0
{
292
0
  RefPtr<AtomSet> permittedSchemes = AtomSet::Get<PERMITTED_SCHEMES>();
293
0
294
0
  mPattern = aPattern;
295
0
296
0
  if (aPattern.EqualsLiteral("<all_urls>")) {
297
0
    mSchemes = permittedSchemes;
298
0
    mMatchSubdomain = true;
299
0
    return;
300
0
  }
301
0
302
0
  // The portion of the URL we're currently examining.
303
0
  uint32_t offset = 0;
304
0
  auto tail = Substring(aPattern, offset);
305
0
306
0
  /***************************************************************************
307
0
   * Scheme
308
0
   ***************************************************************************/
309
0
  int32_t index = aPattern.FindChar(':');
310
0
  if (index <= 0) {
311
0
    aRv.Throw(NS_ERROR_INVALID_ARG);
312
0
    return;
313
0
  }
314
0
315
0
  RefPtr<nsAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
316
0
  bool requireHostLocatorScheme = true;
317
0
  if (scheme == nsGkAtoms::_asterisk) {
318
0
    mSchemes = AtomSet::Get<WILDCARD_SCHEMES>();
319
0
  } else if (!aRestrictSchemes ||
320
0
             permittedSchemes->Contains(scheme) ||
321
0
             scheme == nsGkAtoms::moz_extension) {
322
0
    mSchemes = new AtomSet({scheme});
323
0
    RefPtr<AtomSet> hostLocatorSchemes = AtomSet::Get<HOST_LOCATOR_SCHEMES>();
324
0
    requireHostLocatorScheme = hostLocatorSchemes->Contains(scheme);
325
0
  } else {
326
0
    aRv.Throw(NS_ERROR_INVALID_ARG);
327
0
    return;
328
0
  }
329
0
330
0
  /***************************************************************************
331
0
   * Host
332
0
   ***************************************************************************/
333
0
  offset = index + 1;
334
0
  tail.Rebind(aPattern, offset);
335
0
336
0
  if (!requireHostLocatorScheme) {
337
0
    // Unrecognized schemes and some schemes such as about: and data: URIs
338
0
    // don't have hosts, so just match on the path.
339
0
    // And so, ignorePath doesn't make sense for these matchers.
340
0
    aIgnorePath = false;
341
0
  } else {
342
0
    if (!StringHead(tail, 2).EqualsLiteral("//")) {
343
0
      aRv.Throw(NS_ERROR_INVALID_ARG);
344
0
      return;
345
0
    }
346
0
347
0
    offset += 2;
348
0
    tail.Rebind(aPattern, offset);
349
0
    index = tail.FindChar('/');
350
0
    if (index < 0) {
351
0
      index = tail.Length();
352
0
    }
353
0
354
0
    auto host = StringHead(tail, index);
355
0
    if (host.IsEmpty() && scheme != nsGkAtoms::file) {
356
0
      aRv.Throw(NS_ERROR_INVALID_ARG);
357
0
      return;
358
0
    }
359
0
360
0
    offset += index;
361
0
    tail.Rebind(aPattern, offset);
362
0
363
0
    if (host.EqualsLiteral("*")) {
364
0
      mMatchSubdomain = true;
365
0
    } else if (StringHead(host, 2).EqualsLiteral("*.")) {
366
0
      mDomain = NS_ConvertUTF16toUTF8(Substring(host, 2));
367
0
      mMatchSubdomain = true;
368
0
    } else {
369
0
      mDomain = NS_ConvertUTF16toUTF8(host);
370
0
    }
371
0
  }
372
0
373
0
  /***************************************************************************
374
0
   * Path
375
0
   ***************************************************************************/
376
0
  if (aIgnorePath) {
377
0
    mPattern.Truncate(offset);
378
0
    mPattern.AppendLiteral("/*");
379
0
    return;
380
0
  }
381
0
382
0
  auto path = tail;
383
0
  if (path.IsEmpty()) {
384
0
    aRv.Throw(NS_ERROR_INVALID_ARG);
385
0
    return;
386
0
  }
387
0
388
0
  mPath = new MatchGlob(this);
389
0
  mPath->Init(aCx, path, false, aRv);
390
0
}
391
392
393
bool
394
MatchPattern::MatchesDomain(const nsACString& aDomain) const
395
0
{
396
0
  if (DomainIsWildcard() || mDomain == aDomain) {
397
0
    return true;
398
0
  }
399
0
400
0
  if (mMatchSubdomain) {
401
0
    int64_t offset = (int64_t)aDomain.Length() - mDomain.Length();
402
0
    if (offset > 0 && aDomain[offset - 1] == '.' &&
403
0
        Substring(aDomain, offset) == mDomain) {
404
0
      return true;
405
0
    }
406
0
  }
407
0
408
0
  return false;
409
0
}
410
411
bool
412
MatchPattern::Matches(const nsAString& aURL, bool aExplicit, ErrorResult& aRv) const
413
0
{
414
0
  nsCOMPtr<nsIURI> uri;
415
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
416
0
  if (NS_FAILED(rv)) {
417
0
    aRv.Throw(rv);
418
0
    return false;
419
0
  }
420
0
421
0
  return Matches(uri.get(), aExplicit);
422
0
}
423
424
bool
425
MatchPattern::Matches(const URLInfo& aURL, bool aExplicit) const
426
0
{
427
0
  if (aExplicit && mMatchSubdomain) {
428
0
    return false;
429
0
  }
430
0
431
0
  if (!mSchemes->Contains(aURL.Scheme())) {
432
0
    return false;
433
0
  }
434
0
435
0
  if (!MatchesDomain(aURL.Host())) {
436
0
    return false;
437
0
  }
438
0
439
0
  if (mPath && !mPath->IsWildcard() && !mPath->Matches(aURL.Path())) {
440
0
    return false;
441
0
  }
442
0
443
0
  return true;
444
0
}
445
446
bool
447
MatchPattern::MatchesCookie(const CookieInfo& aCookie) const
448
0
{
449
0
  if (!mSchemes->Contains(nsGkAtoms::https) &&
450
0
      (aCookie.IsSecure() || !mSchemes->Contains(nsGkAtoms::http))) {
451
0
    return false;
452
0
  }
453
0
454
0
  if (MatchesDomain(aCookie.RawHost())) {
455
0
    return true;
456
0
  }
457
0
458
0
  if (!aCookie.IsDomain()) {
459
0
    return false;
460
0
  }
461
0
462
0
  // Things get tricker for domain cookies. The extension needs to be able
463
0
  // to read any cookies that could be read by any host it has permissions
464
0
  // for. This means that our normal host matching checks won't work,
465
0
  // since the pattern "*://*.foo.example.com/" doesn't match ".example.com",
466
0
  // but it does match "bar.foo.example.com", which can read cookies
467
0
  // with the domain ".example.com".
468
0
  //
469
0
  // So, instead, we need to manually check our filters, and accept any
470
0
  // with hosts that end with our cookie's host.
471
0
472
0
  auto& host = aCookie.Host();
473
0
  return StringTail(mDomain, host.Length()) == host;
474
0
}
475
476
bool
477
MatchPattern::SubsumesDomain(const MatchPattern& aPattern) const
478
0
{
479
0
  if (!mMatchSubdomain && aPattern.mMatchSubdomain && aPattern.mDomain == mDomain) {
480
0
    return false;
481
0
  }
482
0
483
0
  return MatchesDomain(aPattern.mDomain);
484
0
}
485
486
bool
487
MatchPattern::Subsumes(const MatchPattern& aPattern) const
488
0
{
489
0
  for (auto& scheme : *aPattern.mSchemes) {
490
0
    if (!mSchemes->Contains(scheme)) {
491
0
      return false;
492
0
    }
493
0
  }
494
0
495
0
  return SubsumesDomain(aPattern);
496
0
}
497
498
bool
499
MatchPattern::Overlaps(const MatchPattern& aPattern) const
500
0
{
501
0
  if (!mSchemes->Intersects(*aPattern.mSchemes)) {
502
0
    return false;
503
0
  }
504
0
505
0
  return SubsumesDomain(aPattern) || aPattern.SubsumesDomain(*this);
506
0
}
507
508
509
JSObject*
510
MatchPattern::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
511
0
{
512
0
  return MatchPattern_Binding::Wrap(aCx, this, aGivenProto);
513
0
}
514
515
/* static */ bool
516
MatchPattern::MatchesAllURLs(const URLInfo& aURL)
517
0
{
518
0
  RefPtr<AtomSet> permittedSchemes = AtomSet::Get<PERMITTED_SCHEMES>();
519
0
  return permittedSchemes->Contains(aURL.Scheme());
520
0
}
521
522
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPattern, mPath, mParent)
523
524
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPattern)
525
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
526
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
527
0
NS_INTERFACE_MAP_END
528
529
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPattern)
530
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPattern)
531
532
533
/*****************************************************************************
534
 * MatchPatternSet
535
 *****************************************************************************/
536
537
/* static */ already_AddRefed<MatchPatternSet>
538
MatchPatternSet::Constructor(dom::GlobalObject& aGlobal,
539
                             const nsTArray<dom::OwningStringOrMatchPattern>& aPatterns,
540
                             const MatchPatternOptions& aOptions,
541
                             ErrorResult& aRv)
542
0
{
543
0
  ArrayType patterns;
544
0
545
0
  for (auto& elem : aPatterns) {
546
0
    if (elem.IsMatchPattern()) {
547
0
      patterns.AppendElement(elem.GetAsMatchPattern());
548
0
    } else {
549
0
      RefPtr<MatchPattern> pattern = MatchPattern::Constructor(
550
0
        aGlobal, elem.GetAsString(), aOptions, aRv);
551
0
552
0
      if (!pattern) {
553
0
        return nullptr;
554
0
      }
555
0
      patterns.AppendElement(std::move(pattern));
556
0
    }
557
0
  }
558
0
559
0
  RefPtr<MatchPatternSet> patternSet = new MatchPatternSet(aGlobal.GetAsSupports(),
560
0
                                                           std::move(patterns));
561
0
  return patternSet.forget();
562
0
}
563
564
565
bool
566
MatchPatternSet::Matches(const nsAString& aURL, bool aExplicit, ErrorResult& aRv) const
567
0
{
568
0
  nsCOMPtr<nsIURI> uri;
569
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
570
0
  if (NS_FAILED(rv)) {
571
0
    aRv.Throw(rv);
572
0
    return false;
573
0
  }
574
0
575
0
  return Matches(uri.get(), aExplicit);
576
0
}
577
578
bool
579
MatchPatternSet::Matches(const URLInfo& aURL, bool aExplicit) const
580
0
{
581
0
  for (const auto& pattern : mPatterns) {
582
0
    if (pattern->Matches(aURL, aExplicit)) {
583
0
      return true;
584
0
    }
585
0
  }
586
0
  return false;
587
0
}
588
589
bool
590
MatchPatternSet::MatchesCookie(const CookieInfo& aCookie) const
591
0
{
592
0
  for (const auto& pattern : mPatterns) {
593
0
    if (pattern->MatchesCookie(aCookie)) {
594
0
      return true;
595
0
    }
596
0
  }
597
0
  return false;
598
0
}
599
600
bool
601
MatchPatternSet::Subsumes(const MatchPattern& aPattern) const
602
0
{
603
0
  for (const auto& pattern : mPatterns) {
604
0
    if (pattern->Subsumes(aPattern)) {
605
0
      return true;
606
0
    }
607
0
  }
608
0
  return false;
609
0
}
610
611
bool
612
MatchPatternSet::Overlaps(const MatchPatternSet& aPatternSet) const
613
0
{
614
0
  for (const auto& pattern : aPatternSet.mPatterns) {
615
0
    if (Overlaps(*pattern)) {
616
0
      return true;
617
0
    }
618
0
  }
619
0
  return false;
620
0
}
621
622
bool
623
MatchPatternSet::Overlaps(const MatchPattern& aPattern) const
624
0
{
625
0
  for (const auto& pattern : mPatterns) {
626
0
    if (pattern->Overlaps(aPattern)) {
627
0
      return true;
628
0
    }
629
0
  }
630
0
  return false;
631
0
}
632
633
634
bool
635
MatchPatternSet::OverlapsAll(const MatchPatternSet& aPatternSet) const
636
0
{
637
0
  for (const auto& pattern : aPatternSet.mPatterns) {
638
0
    if (!Overlaps(*pattern)) {
639
0
      return false;
640
0
    }
641
0
  }
642
0
  return aPatternSet.mPatterns.Length() > 0;
643
0
}
644
645
646
JSObject*
647
MatchPatternSet::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
648
0
{
649
0
  return MatchPatternSet_Binding::Wrap(aCx, this, aGivenProto);
650
0
}
651
652
653
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MatchPatternSet, mPatterns, mParent)
654
655
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchPatternSet)
656
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
657
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
658
0
NS_INTERFACE_MAP_END
659
660
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchPatternSet)
661
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchPatternSet)
662
663
664
/*****************************************************************************
665
 * MatchGlob
666
 *****************************************************************************/
667
668
MatchGlob::~MatchGlob()
669
0
{
670
0
  mozilla::DropJSObjects(this);
671
0
}
672
673
/* static */ already_AddRefed<MatchGlob>
674
MatchGlob::Constructor(dom::GlobalObject& aGlobal,
675
                       const nsAString& aGlob,
676
                       bool aAllowQuestion,
677
                       ErrorResult& aRv)
678
0
{
679
0
  RefPtr<MatchGlob> glob = new MatchGlob(aGlobal.GetAsSupports());
680
0
  glob->Init(aGlobal.Context(), aGlob, aAllowQuestion, aRv);
681
0
  if (aRv.Failed()) {
682
0
    return nullptr;
683
0
  }
684
0
  return glob.forget();
685
0
}
686
687
void
688
MatchGlob::Init(JSContext* aCx, const nsAString& aGlob, bool aAllowQuestion, ErrorResult& aRv)
689
0
{
690
0
  mGlob = aGlob;
691
0
692
0
  // Check for a literal match with no glob metacharacters.
693
0
  auto index = mGlob.FindCharInSet(aAllowQuestion ? "*?" : "*");
694
0
  if (index < 0) {
695
0
    mPathLiteral = mGlob;
696
0
    return;
697
0
  }
698
0
699
0
  // Check for a prefix match, where the only glob metacharacter is a "*"
700
0
  // at the end of the string.
701
0
  if (index == (int32_t)mGlob.Length() - 1 && mGlob[index] == '*') {
702
0
    mPathLiteral = StringHead(mGlob, index);
703
0
    mIsPrefix = true;
704
0
    return;
705
0
  }
706
0
707
0
  // Fall back to the regexp slow path.
708
0
  NS_NAMED_LITERAL_CSTRING(metaChars, ".+*?^${}()|[]\\");
709
0
710
0
  nsAutoString escaped;
711
0
  escaped.Append('^');
712
0
713
0
  for (uint32_t i = 0; i < mGlob.Length(); i++) {
714
0
    auto c = mGlob[i];
715
0
    if (c == '*') {
716
0
      escaped.AppendLiteral(".*");
717
0
    } else if (c == '?' && aAllowQuestion) {
718
0
      escaped.Append('.');
719
0
    } else {
720
0
      if (metaChars.Contains(c)) {
721
0
        escaped.Append('\\');
722
0
      }
723
0
      escaped.Append(c);
724
0
    }
725
0
  }
726
0
727
0
  escaped.Append('$');
728
0
729
0
  // TODO: Switch to the Rust regexp crate, when Rust integration is easier.
730
0
  // It uses a much more efficient, linear time matching algorithm, and
731
0
  // doesn't require special casing for the literal and prefix cases.
732
0
  mRegExp = JS_NewUCRegExpObject(aCx, escaped.get(), escaped.Length(), 0);
733
0
  if (mRegExp) {
734
0
    mozilla::HoldJSObjects(this);
735
0
  } else {
736
0
    aRv.NoteJSContextException(aCx);
737
0
  }
738
0
}
739
740
bool
741
MatchGlob::Matches(const nsAString& aString) const
742
0
{
743
0
  if (mRegExp) {
744
0
    AutoJSAPI jsapi;
745
0
    jsapi.Init();
746
0
    JSContext* cx = jsapi.cx();
747
0
748
0
    JSAutoRealm ar(cx, mRegExp);
749
0
750
0
    JS::RootedObject regexp(cx, mRegExp);
751
0
    JS::RootedValue result(cx);
752
0
753
0
    nsString input(aString);
754
0
755
0
    size_t index = 0;
756
0
    if (!JS_ExecuteRegExpNoStatics(cx, regexp, input.BeginWriting(), aString.Length(),
757
0
                                   &index, true, &result)) {
758
0
      return false;
759
0
    }
760
0
761
0
    return result.isBoolean() && result.toBoolean();
762
0
  }
763
0
764
0
  if (mIsPrefix) {
765
0
    return mPathLiteral == StringHead(aString, mPathLiteral.Length());
766
0
  }
767
0
768
0
  return mPathLiteral == aString;
769
0
}
770
771
772
JSObject*
773
MatchGlob::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
774
0
{
775
0
  return MatchGlob_Binding::Wrap(aCx, this, aGivenProto);
776
0
}
777
778
779
NS_IMPL_CYCLE_COLLECTION_CLASS(MatchGlob)
780
781
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MatchGlob)
782
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
783
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
784
0
  tmp->mRegExp = nullptr;
785
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
786
787
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MatchGlob)
788
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
789
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
790
791
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MatchGlob)
792
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
793
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRegExp)
794
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
795
796
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MatchGlob)
797
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
798
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
799
0
NS_INTERFACE_MAP_END
800
801
NS_IMPL_CYCLE_COLLECTING_ADDREF(MatchGlob)
802
NS_IMPL_CYCLE_COLLECTING_RELEASE(MatchGlob)
803
804
805
/*****************************************************************************
806
 * MatchGlobSet
807
 *****************************************************************************/
808
809
bool
810
MatchGlobSet::Matches(const nsAString& aValue) const
811
0
{
812
0
  for (auto& glob : *this) {
813
0
    if (glob->Matches(aValue)) {
814
0
      return true;
815
0
    }
816
0
  }
817
0
  return false;
818
0
}
819
820
} // namespace extensions
821
} // namespace mozilla
822