Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/fetch/InternalHeaders.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 "mozilla/dom/InternalHeaders.h"
8
9
#include "mozilla/dom/FetchTypes.h"
10
#include "mozilla/ErrorResult.h"
11
12
#include "nsCharSeparatedTokenizer.h"
13
#include "nsContentUtils.h"
14
#include "nsIHttpHeaderVisitor.h"
15
#include "nsNetUtil.h"
16
#include "nsReadableUtils.h"
17
18
namespace mozilla {
19
namespace dom {
20
21
InternalHeaders::InternalHeaders(const nsTArray<Entry>&& aHeaders,
22
                                 HeadersGuardEnum aGuard)
23
  : mGuard(aGuard)
24
  , mList(aHeaders)
25
  , mListDirty(true)
26
0
{
27
0
}
28
29
InternalHeaders::InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList,
30
                                 HeadersGuardEnum aGuard)
31
  : mGuard(aGuard)
32
  , mListDirty(true)
33
0
{
34
0
  for (const HeadersEntry& headersEntry : aHeadersEntryList) {
35
0
    mList.AppendElement(Entry(headersEntry.name(), headersEntry.value()));
36
0
  }
37
0
}
38
39
void
40
InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders,
41
                       HeadersGuardEnum& aGuard)
42
0
{
43
0
  aGuard = mGuard;
44
0
45
0
  aIPCHeaders.Clear();
46
0
  for (Entry& entry : mList) {
47
0
    aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue));
48
0
  }
49
0
}
50
51
void
52
InternalHeaders::Append(const nsACString& aName, const nsACString& aValue,
53
                        ErrorResult& aRv)
54
0
{
55
0
  nsAutoCString lowerName;
56
0
  ToLowerCase(aName, lowerName);
57
0
  nsAutoCString trimValue;
58
0
  NS_TrimHTTPWhitespace(aValue, trimValue);
59
0
60
0
  if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
61
0
    return;
62
0
  }
63
0
64
0
  SetListDirty();
65
0
66
0
  mList.AppendElement(Entry(lowerName, trimValue));
67
0
}
68
69
void
70
InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv)
71
0
{
72
0
  nsAutoCString lowerName;
73
0
  ToLowerCase(aName, lowerName);
74
0
75
0
  if (IsInvalidMutableHeader(lowerName, aRv)) {
76
0
    return;
77
0
  }
78
0
79
0
  SetListDirty();
80
0
81
0
  // remove in reverse order to minimize copying
82
0
  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
83
0
    if (lowerName == mList[i].mName) {
84
0
      mList.RemoveElementAt(i);
85
0
    }
86
0
  }
87
0
}
88
89
void
90
InternalHeaders::Get(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const
91
0
{
92
0
  nsAutoCString lowerName;
93
0
  ToLowerCase(aName, lowerName);
94
0
95
0
  if (IsInvalidName(lowerName, aRv)) {
96
0
    return;
97
0
  }
98
0
99
0
  const char* delimiter = ", ";
100
0
  bool firstValueFound = false;
101
0
102
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
103
0
    if (lowerName == mList[i].mName) {
104
0
      if (firstValueFound) {
105
0
        aValue += delimiter;
106
0
      }
107
0
      aValue += mList[i].mValue;
108
0
      firstValueFound = true;
109
0
    }
110
0
  }
111
0
112
0
  // No value found, so return null to content
113
0
  if (!firstValueFound) {
114
0
    aValue.SetIsVoid(true);
115
0
  }
116
0
}
117
118
void
119
InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const
120
0
{
121
0
  nsAutoCString lowerName;
122
0
  ToLowerCase(aName, lowerName);
123
0
124
0
  if (IsInvalidName(lowerName, aRv)) {
125
0
    return;
126
0
  }
127
0
128
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
129
0
    if (lowerName == mList[i].mName) {
130
0
      aValue = mList[i].mValue;
131
0
      return;
132
0
    }
133
0
  }
134
0
135
0
  // No value found, so return null to content
136
0
  aValue.SetIsVoid(true);
137
0
}
138
139
bool
140
InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const
141
0
{
142
0
  nsAutoCString lowerName;
143
0
  ToLowerCase(aName, lowerName);
144
0
145
0
  if (IsInvalidName(lowerName, aRv)) {
146
0
    return false;
147
0
  }
148
0
149
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
150
0
    if (lowerName == mList[i].mName) {
151
0
      return true;
152
0
    }
153
0
  }
154
0
  return false;
155
0
}
156
157
void
158
InternalHeaders::Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv)
159
0
{
160
0
  nsAutoCString lowerName;
161
0
  ToLowerCase(aName, lowerName);
162
0
  nsAutoCString trimValue;
163
0
  NS_TrimHTTPWhitespace(aValue, trimValue);
164
0
165
0
  if (IsInvalidMutableHeader(lowerName, trimValue, aRv)) {
166
0
    return;
167
0
  }
168
0
169
0
  SetListDirty();
170
0
171
0
  int32_t firstIndex = INT32_MAX;
172
0
173
0
  // remove in reverse order to minimize copying
174
0
  for (int32_t i = mList.Length() - 1; i >= 0; --i) {
175
0
    if (lowerName == mList[i].mName) {
176
0
      firstIndex = std::min(firstIndex, i);
177
0
      mList.RemoveElementAt(i);
178
0
    }
179
0
  }
180
0
181
0
  if (firstIndex < INT32_MAX) {
182
0
    Entry* entry = mList.InsertElementAt(firstIndex);
183
0
    entry->mName = lowerName;
184
0
    entry->mValue = trimValue;
185
0
  } else {
186
0
    mList.AppendElement(Entry(lowerName, trimValue));
187
0
  }
188
0
}
189
190
void
191
InternalHeaders::Clear()
192
0
{
193
0
  SetListDirty();
194
0
  mList.Clear();
195
0
}
196
197
void
198
InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
199
0
{
200
0
  // The guard is only checked during ::Set() and ::Append() in the spec.  It
201
0
  // does not require revalidating headers already set.
202
0
  mGuard = aGuard;
203
0
}
204
205
InternalHeaders::~InternalHeaders()
206
0
{
207
0
}
208
209
// static
210
bool
211
InternalHeaders::IsSimpleHeader(const nsACString& aName, const nsACString& aValue)
212
0
{
213
0
  // Note, we must allow a null content-type value here to support
214
0
  // get("content-type"), but the IsInvalidValue() check will prevent null
215
0
  // from being set or appended.
216
0
  return aName.EqualsLiteral("accept") ||
217
0
         aName.EqualsLiteral("accept-language") ||
218
0
         aName.EqualsLiteral("content-language") ||
219
0
         (aName.EqualsLiteral("content-type") &&
220
0
          nsContentUtils::IsAllowedNonCorsContentType(aValue));
221
0
}
222
223
// static
224
bool
225
InternalHeaders::IsRevalidationHeader(const nsACString& aName)
226
0
{
227
0
  return aName.EqualsLiteral("if-modified-since") ||
228
0
         aName.EqualsLiteral("if-none-match") ||
229
0
         aName.EqualsLiteral("if-unmodified-since") ||
230
0
         aName.EqualsLiteral("if-match") ||
231
0
         aName.EqualsLiteral("if-range");
232
0
}
233
234
//static
235
bool
236
InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv)
237
0
{
238
0
  if (!NS_IsValidHTTPToken(aName)) {
239
0
    NS_ConvertUTF8toUTF16 label(aName);
240
0
    aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(label);
241
0
    return true;
242
0
  }
243
0
244
0
  return false;
245
0
}
246
247
// static
248
bool
249
InternalHeaders::IsInvalidValue(const nsACString& aValue, ErrorResult& aRv)
250
0
{
251
0
  if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
252
0
    NS_ConvertUTF8toUTF16 label(aValue);
253
0
    aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(label);
254
0
    return true;
255
0
  }
256
0
  return false;
257
0
}
258
259
bool
260
InternalHeaders::IsImmutable(ErrorResult& aRv) const
261
0
{
262
0
  if (mGuard == HeadersGuardEnum::Immutable) {
263
0
    aRv.ThrowTypeError<MSG_HEADERS_IMMUTABLE>();
264
0
    return true;
265
0
  }
266
0
  return false;
267
0
}
268
269
bool
270
InternalHeaders::IsForbiddenRequestHeader(const nsACString& aName) const
271
0
{
272
0
  return mGuard == HeadersGuardEnum::Request &&
273
0
         nsContentUtils::IsForbiddenRequestHeader(aName);
274
0
}
275
276
bool
277
InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName) const
278
0
{
279
0
  return mGuard == HeadersGuardEnum::Request_no_cors &&
280
0
         !IsSimpleHeader(aName, EmptyCString());
281
0
}
282
283
bool
284
InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName,
285
                                                const nsACString& aValue) const
286
0
{
287
0
  return mGuard == HeadersGuardEnum::Request_no_cors &&
288
0
         !IsSimpleHeader(aName, aValue);
289
0
}
290
291
bool
292
InternalHeaders::IsForbiddenResponseHeader(const nsACString& aName) const
293
0
{
294
0
  return mGuard == HeadersGuardEnum::Response &&
295
0
         nsContentUtils::IsForbiddenResponseHeader(aName);
296
0
}
297
298
void
299
InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv)
300
0
{
301
0
  const nsTArray<Entry>& list = aInit.mList;
302
0
  for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) {
303
0
    const Entry& entry = list[i];
304
0
    Append(entry.mName, entry.mValue, aRv);
305
0
  }
306
0
}
307
308
void
309
InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv)
310
0
{
311
0
  for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) {
312
0
    const Sequence<nsCString>& tuple = aInit[i];
313
0
    if (tuple.Length() != 2) {
314
0
      aRv.ThrowTypeError<MSG_INVALID_HEADER_SEQUENCE>();
315
0
      return;
316
0
    }
317
0
    Append(tuple[0], tuple[1], aRv);
318
0
  }
319
0
}
320
321
void
322
InternalHeaders::Fill(const Record<nsCString, nsCString>& aInit, ErrorResult& aRv)
323
0
{
324
0
  for (auto& entry : aInit.Entries()) {
325
0
    Append(entry.mKey, entry.mValue, aRv);
326
0
    if (aRv.Failed()) {
327
0
      return;
328
0
    }
329
0
  }
330
0
}
331
332
namespace {
333
334
class FillHeaders final : public nsIHttpHeaderVisitor
335
{
336
  RefPtr<InternalHeaders> mInternalHeaders;
337
338
0
  ~FillHeaders() = default;
339
340
public:
341
  NS_DECL_ISUPPORTS
342
343
  explicit FillHeaders(InternalHeaders* aInternalHeaders)
344
    : mInternalHeaders(aInternalHeaders)
345
0
  {
346
0
    MOZ_DIAGNOSTIC_ASSERT(mInternalHeaders);
347
0
  }
348
349
  NS_IMETHOD
350
  VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
351
0
  {
352
0
    mInternalHeaders->Append(aHeader, aValue, IgnoreErrors());
353
0
    return NS_OK;
354
0
  }
355
};
356
357
NS_IMPL_ISUPPORTS(FillHeaders, nsIHttpHeaderVisitor)
358
359
} // namespace
360
361
void
362
InternalHeaders::FillResponseHeaders(nsIRequest* aRequest)
363
0
{
364
0
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
365
0
  if (!httpChannel) {
366
0
    return;
367
0
  }
368
0
369
0
  RefPtr<FillHeaders> visitor = new FillHeaders(this);
370
0
  nsresult rv = httpChannel->VisitResponseHeaders(visitor);
371
0
  if (NS_FAILED(rv)) {
372
0
    NS_WARNING("failed to fill headers");
373
0
  }
374
0
}
375
376
bool
377
InternalHeaders::HasOnlySimpleHeaders() const
378
0
{
379
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
380
0
    if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) {
381
0
      return false;
382
0
    }
383
0
  }
384
0
385
0
  return true;
386
0
}
387
388
bool
389
InternalHeaders::HasRevalidationHeaders() const
390
0
{
391
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
392
0
    if (IsRevalidationHeader(mList[i].mName)) {
393
0
      return true;
394
0
    }
395
0
  }
396
0
397
0
  return false;
398
0
}
399
400
// static
401
already_AddRefed<InternalHeaders>
402
InternalHeaders::BasicHeaders(InternalHeaders* aHeaders)
403
0
{
404
0
  RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders);
405
0
  ErrorResult result;
406
0
  // The Set-Cookie headers cannot be invalid mutable headers, so the Delete
407
0
  // must succeed.
408
0
  basic->Delete(NS_LITERAL_CSTRING("Set-Cookie"), result);
409
0
  MOZ_ASSERT(!result.Failed());
410
0
  basic->Delete(NS_LITERAL_CSTRING("Set-Cookie2"), result);
411
0
  MOZ_ASSERT(!result.Failed());
412
0
  return basic.forget();
413
0
}
414
415
// static
416
already_AddRefed<InternalHeaders>
417
InternalHeaders::CORSHeaders(InternalHeaders* aHeaders)
418
0
{
419
0
  RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
420
0
  ErrorResult result;
421
0
422
0
  nsAutoCString acExposedNames;
423
0
  aHeaders->GetFirst(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), acExposedNames, result);
424
0
  MOZ_ASSERT(!result.Failed());
425
0
426
0
  AutoTArray<nsCString, 5> exposeNamesArray;
427
0
  nsCCharSeparatedTokenizer exposeTokens(acExposedNames, ',');
428
0
  while (exposeTokens.hasMoreTokens()) {
429
0
    const nsDependentCSubstring& token = exposeTokens.nextToken();
430
0
    if (token.IsEmpty()) {
431
0
      continue;
432
0
    }
433
0
434
0
    if (!NS_IsValidHTTPToken(token)) {
435
0
      NS_WARNING("Got invalid HTTP token in Access-Control-Expose-Headers. Header value is:");
436
0
      NS_WARNING(acExposedNames.get());
437
0
      exposeNamesArray.Clear();
438
0
      break;
439
0
    }
440
0
441
0
    exposeNamesArray.AppendElement(token);
442
0
  }
443
0
444
0
  nsCaseInsensitiveCStringArrayComparator comp;
445
0
  for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
446
0
    const Entry& entry = aHeaders->mList[i];
447
0
    if (entry.mName.EqualsASCII("cache-control") ||
448
0
        entry.mName.EqualsASCII("content-language") ||
449
0
        entry.mName.EqualsASCII("content-type") ||
450
0
        entry.mName.EqualsASCII("expires") ||
451
0
        entry.mName.EqualsASCII("last-modified") ||
452
0
        entry.mName.EqualsASCII("pragma") ||
453
0
        exposeNamesArray.Contains(entry.mName, comp)) {
454
0
      cors->Append(entry.mName, entry.mValue, result);
455
0
      MOZ_ASSERT(!result.Failed());
456
0
    }
457
0
  }
458
0
459
0
  return cors.forget();
460
0
}
461
462
void
463
InternalHeaders::GetEntries(nsTArray<InternalHeaders::Entry>& aEntries) const
464
0
{
465
0
  MOZ_ASSERT(aEntries.IsEmpty());
466
0
  aEntries.AppendElements(mList);
467
0
}
468
469
void
470
InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const
471
0
{
472
0
  MOZ_ASSERT(aNames.IsEmpty());
473
0
  for (uint32_t i = 0; i < mList.Length(); ++i) {
474
0
    const Entry& header = mList[i];
475
0
    if (!InternalHeaders::IsSimpleHeader(header.mName, header.mValue)) {
476
0
      aNames.AppendElement(header.mName);
477
0
    }
478
0
  }
479
0
}
480
481
void
482
InternalHeaders::MaybeSortList()
483
0
{
484
0
  class Comparator {
485
0
  public:
486
0
    bool Equals(const Entry& aA, const Entry& aB) const
487
0
    {
488
0
       return aA.mName == aB.mName;
489
0
    }
490
0
491
0
    bool LessThan(const Entry& aA, const Entry& aB) const
492
0
    {
493
0
      return aA.mName < aB.mName;
494
0
    }
495
0
  };
496
0
497
0
  if (!mListDirty) {
498
0
    return;
499
0
  }
500
0
501
0
  mListDirty = false;
502
0
503
0
  Comparator comparator;
504
0
505
0
  mSortedList.Clear();
506
0
  for (const Entry& entry : mList) {
507
0
    bool found = false;
508
0
    for (Entry& sortedEntry : mSortedList) {
509
0
      if (sortedEntry.mName == entry.mName) {
510
0
        sortedEntry.mValue += ", ";
511
0
        sortedEntry.mValue += entry.mValue;
512
0
        found = true;
513
0
        break;
514
0
      }
515
0
    }
516
0
517
0
    if (!found) {
518
0
      mSortedList.InsertElementSorted(entry, comparator);
519
0
    }
520
0
  }
521
0
}
522
523
void
524
InternalHeaders::SetListDirty()
525
0
{
526
0
  mSortedList.Clear();
527
0
  mListDirty = true;
528
0
}
529
530
} // namespace dom
531
} // namespace mozilla