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