/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 |