/src/mozilla-central/netwerk/protocol/http/nsHttpHeaderArray.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim:set ts=4 sw=4 sts=4 ci et: */ |
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 | | // HttpLog.h should generally be included first |
8 | | #include "HttpLog.h" |
9 | | |
10 | | #include "nsHttpHeaderArray.h" |
11 | | #include "nsURLHelper.h" |
12 | | #include "nsIHttpHeaderVisitor.h" |
13 | | #include "nsHttpHandler.h" |
14 | | |
15 | | namespace mozilla { |
16 | | namespace net { |
17 | | |
18 | | //----------------------------------------------------------------------------- |
19 | | // nsHttpHeaderArray <public> |
20 | | //----------------------------------------------------------------------------- |
21 | | |
22 | | nsresult |
23 | | nsHttpHeaderArray::SetHeader(const nsACString &headerName, |
24 | | const nsACString &value, |
25 | | bool merge, |
26 | | nsHttpHeaderArray::HeaderVariety variety) |
27 | 0 | { |
28 | 0 | nsHttpAtom header = nsHttp::ResolveAtom(PromiseFlatCString(headerName).get()); |
29 | 0 | if (!header) { |
30 | 0 | NS_WARNING("failed to resolve atom"); |
31 | 0 | return NS_ERROR_NOT_AVAILABLE; |
32 | 0 | } |
33 | 0 | return SetHeader(header, headerName, value, merge, variety); |
34 | 0 | } |
35 | | |
36 | | nsresult |
37 | | nsHttpHeaderArray::SetHeader(nsHttpAtom header, |
38 | | const nsACString &value, |
39 | | bool merge, |
40 | | nsHttpHeaderArray::HeaderVariety variety) |
41 | 0 | { |
42 | 0 | return SetHeader(header, EmptyCString(), value, merge, variety); |
43 | 0 | } |
44 | | |
45 | | nsresult |
46 | | nsHttpHeaderArray::SetHeader(nsHttpAtom header, |
47 | | const nsACString &headerName, |
48 | | const nsACString &value, |
49 | | bool merge, |
50 | | nsHttpHeaderArray::HeaderVariety variety) |
51 | 0 | { |
52 | 0 | MOZ_ASSERT((variety == eVarietyResponse) || |
53 | 0 | (variety == eVarietyRequestDefault) || |
54 | 0 | (variety == eVarietyRequestOverride), |
55 | 0 | "Net original headers can only be set using SetHeader_internal()."); |
56 | 0 |
|
57 | 0 | nsEntry *entry = nullptr; |
58 | 0 | int32_t index; |
59 | 0 |
|
60 | 0 | index = LookupEntry(header, &entry); |
61 | 0 |
|
62 | 0 | // If an empty value is passed in, then delete the header entry... |
63 | 0 | // unless we are merging, in which case this function becomes a NOP. |
64 | 0 | if (value.IsEmpty()) { |
65 | 0 | if (!merge && entry) { |
66 | 0 | if (entry->variety == eVarietyResponseNetOriginalAndResponse) { |
67 | 0 | MOZ_ASSERT(variety == eVarietyResponse); |
68 | 0 | entry->variety = eVarietyResponseNetOriginal; |
69 | 0 | } else { |
70 | 0 | mHeaders.RemoveElementAt(index); |
71 | 0 | } |
72 | 0 | } |
73 | 0 | return NS_OK; |
74 | 0 | } |
75 | 0 |
|
76 | 0 | MOZ_ASSERT(!entry || variety != eVarietyRequestDefault, |
77 | 0 | "Cannot set default entry which overrides existing entry!"); |
78 | 0 | if (!entry) { |
79 | 0 | return SetHeader_internal(header, headerName, value, variety); |
80 | 0 | } else if (merge && !IsSingletonHeader(header)) { |
81 | 0 | return MergeHeader(header, entry, value, variety); |
82 | 0 | } else if (!IsIgnoreMultipleHeader(header)) { |
83 | 0 | // Replace the existing string with the new value |
84 | 0 | if (entry->variety == eVarietyResponseNetOriginalAndResponse) { |
85 | 0 | MOZ_ASSERT(variety == eVarietyResponse); |
86 | 0 | entry->variety = eVarietyResponseNetOriginal; |
87 | 0 | return SetHeader_internal(header, headerName, value, variety); |
88 | 0 | } |
89 | 0 | entry->value = value; |
90 | 0 | entry->variety = variety; |
91 | 0 | } |
92 | 0 |
|
93 | 0 | return NS_OK; |
94 | 0 | } |
95 | | |
96 | | nsresult |
97 | | nsHttpHeaderArray::SetHeader_internal(nsHttpAtom header, |
98 | | const nsACString &headerName, |
99 | | const nsACString &value, |
100 | | nsHttpHeaderArray::HeaderVariety variety) |
101 | 0 | { |
102 | 0 | nsEntry *entry = mHeaders.AppendElement(); |
103 | 0 | if (!entry) { |
104 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
105 | 0 | } |
106 | 0 | entry->header = header; |
107 | 0 | // Only save original form of a header if it is different than the header |
108 | 0 | // atom string. |
109 | 0 | if (!headerName.Equals(header.get())) { |
110 | 0 | entry->headerNameOriginal = headerName; |
111 | 0 | } |
112 | 0 | entry->value = value; |
113 | 0 | entry->variety = variety; |
114 | 0 | return NS_OK; |
115 | 0 | } |
116 | | |
117 | | nsresult |
118 | | nsHttpHeaderArray::SetEmptyHeader(const nsACString &headerName, |
119 | | HeaderVariety variety) |
120 | 0 | { |
121 | 0 | nsHttpAtom header = nsHttp::ResolveAtom(PromiseFlatCString(headerName).get()); |
122 | 0 | if (!header) { |
123 | 0 | NS_WARNING("failed to resolve atom"); |
124 | 0 | return NS_ERROR_NOT_AVAILABLE; |
125 | 0 | } |
126 | 0 |
|
127 | 0 | MOZ_ASSERT((variety == eVarietyResponse) || |
128 | 0 | (variety == eVarietyRequestDefault) || |
129 | 0 | (variety == eVarietyRequestOverride), |
130 | 0 | "Original headers can only be set using SetHeader_internal()."); |
131 | 0 | nsEntry *entry = nullptr; |
132 | 0 |
|
133 | 0 | LookupEntry(header, &entry); |
134 | 0 |
|
135 | 0 | if (entry && |
136 | 0 | entry->variety != eVarietyResponseNetOriginalAndResponse) { |
137 | 0 | entry->value.Truncate(); |
138 | 0 | return NS_OK; |
139 | 0 | } else if (entry) { |
140 | 0 | MOZ_ASSERT(variety == eVarietyResponse); |
141 | 0 | entry->variety = eVarietyResponseNetOriginal; |
142 | 0 | } |
143 | 0 |
|
144 | 0 | return SetHeader_internal(header, headerName, EmptyCString(), variety); |
145 | 0 | } |
146 | | |
147 | | nsresult |
148 | | nsHttpHeaderArray::SetHeaderFromNet(nsHttpAtom header, |
149 | | const nsACString &headerNameOriginal, |
150 | | const nsACString &value, |
151 | | bool response) |
152 | 0 | { |
153 | 0 | // mHeader holds the consolidated (merged or updated) headers. |
154 | 0 | // mHeader for response header will keep the original heades as well. |
155 | 0 | nsEntry *entry = nullptr; |
156 | 0 |
|
157 | 0 | LookupEntry(header, &entry); |
158 | 0 |
|
159 | 0 | if (!entry) { |
160 | 0 | HeaderVariety variety = eVarietyRequestOverride; |
161 | 0 | if (response) { |
162 | 0 | variety = eVarietyResponseNetOriginalAndResponse; |
163 | 0 | } |
164 | 0 | return SetHeader_internal(header, headerNameOriginal, value, variety); |
165 | 0 |
|
166 | 0 | } else if (!IsSingletonHeader(header)) { |
167 | 0 | HeaderVariety variety = eVarietyRequestOverride; |
168 | 0 | if (response) { |
169 | 0 | variety = eVarietyResponse; |
170 | 0 | } |
171 | 0 | nsresult rv = MergeHeader(header, entry, value, variety); |
172 | 0 | if (NS_FAILED(rv)) { |
173 | 0 | return rv; |
174 | 0 | } |
175 | 0 | if (response) { |
176 | 0 | rv = SetHeader_internal(header, headerNameOriginal, value, |
177 | 0 | eVarietyResponseNetOriginal); |
178 | 0 | } |
179 | 0 | return rv; |
180 | 0 | } else if (!IsIgnoreMultipleHeader(header)) { |
181 | 0 | // Multiple instances of non-mergeable header received from network |
182 | 0 | // - ignore if same value |
183 | 0 | if (!entry->value.Equals(value)) { |
184 | 0 | if (IsSuspectDuplicateHeader(header)) { |
185 | 0 | // reply may be corrupt/hacked (ex: CLRF injection attacks) |
186 | 0 | return NS_ERROR_CORRUPTED_CONTENT; |
187 | 0 | } // else silently drop value: keep value from 1st header seen |
188 | 0 | LOG(("Header %s silently dropped as non mergeable header\n", |
189 | 0 | header.get())); |
190 | 0 |
|
191 | 0 | } |
192 | 0 | if (response) { |
193 | 0 | return SetHeader_internal(header, headerNameOriginal, value, |
194 | 0 | eVarietyResponseNetOriginal); |
195 | 0 | } |
196 | 0 | } |
197 | 0 | |
198 | 0 | return NS_OK; |
199 | 0 | } |
200 | | |
201 | | nsresult |
202 | | nsHttpHeaderArray::SetResponseHeaderFromCache(nsHttpAtom header, |
203 | | const nsACString &headerNameOriginal, |
204 | | const nsACString &value, |
205 | | nsHttpHeaderArray::HeaderVariety variety) |
206 | 0 | { |
207 | 0 | MOZ_ASSERT((variety == eVarietyResponse) || |
208 | 0 | (variety == eVarietyResponseNetOriginal), |
209 | 0 | "Headers from cache can only be eVarietyResponse and " |
210 | 0 | "eVarietyResponseNetOriginal"); |
211 | 0 |
|
212 | 0 | if (variety == eVarietyResponseNetOriginal) { |
213 | 0 | return SetHeader_internal(header, headerNameOriginal, value, |
214 | 0 | eVarietyResponseNetOriginal); |
215 | 0 | } |
216 | 0 | nsTArray<nsEntry>::index_type index = 0; |
217 | 0 | do { |
218 | 0 | index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader()); |
219 | 0 | if (index != mHeaders.NoIndex) { |
220 | 0 | nsEntry &entry = mHeaders[index]; |
221 | 0 | if (value.Equals(entry.value)) { |
222 | 0 | MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginal) || |
223 | 0 | (entry.variety == eVarietyResponseNetOriginalAndResponse), |
224 | 0 | "This array must contain only eVarietyResponseNetOriginal" |
225 | 0 | " and eVarietyResponseNetOriginalAndRespons headers!"); |
226 | 0 | entry.variety = eVarietyResponseNetOriginalAndResponse; |
227 | 0 | return NS_OK; |
228 | 0 | } |
229 | 0 | index++; |
230 | 0 | } |
231 | 0 | } while (index != mHeaders.NoIndex); |
232 | 0 | // If we are here, we have not found an entry so add a new one. |
233 | 0 | return SetHeader_internal(header, headerNameOriginal, value, eVarietyResponse); |
234 | 0 | } |
235 | | |
236 | | void |
237 | | nsHttpHeaderArray::ClearHeader(nsHttpAtom header) |
238 | 0 | { |
239 | 0 | nsEntry *entry = nullptr; |
240 | 0 | int32_t index = LookupEntry(header, &entry); |
241 | 0 | if (entry) { |
242 | 0 | if (entry->variety == eVarietyResponseNetOriginalAndResponse) { |
243 | 0 | entry->variety = eVarietyResponseNetOriginal; |
244 | 0 | } else { |
245 | 0 | mHeaders.RemoveElementAt(index); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | | |
250 | | const char * |
251 | | nsHttpHeaderArray::PeekHeader(nsHttpAtom header) const |
252 | 0 | { |
253 | 0 | const nsEntry *entry = nullptr; |
254 | 0 | LookupEntry(header, &entry); |
255 | 0 | return entry ? entry->value.get() : nullptr; |
256 | 0 | } |
257 | | |
258 | | nsresult |
259 | | nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const |
260 | 0 | { |
261 | 0 | const nsEntry *entry = nullptr; |
262 | 0 | LookupEntry(header, &entry); |
263 | 0 | if (!entry) |
264 | 0 | return NS_ERROR_NOT_AVAILABLE; |
265 | 0 | result = entry->value; |
266 | 0 | return NS_OK; |
267 | 0 | } |
268 | | |
269 | | nsresult |
270 | | nsHttpHeaderArray::GetOriginalHeader(nsHttpAtom aHeader, |
271 | | nsIHttpHeaderVisitor *aVisitor) |
272 | 0 | { |
273 | 0 | NS_ENSURE_ARG_POINTER(aVisitor); |
274 | 0 | uint32_t index = 0; |
275 | 0 | nsresult rv = NS_ERROR_NOT_AVAILABLE; |
276 | 0 | while (true) { |
277 | 0 | index = mHeaders.IndexOf(aHeader, index, nsEntry::MatchHeader()); |
278 | 0 | if (index != UINT32_MAX) { |
279 | 0 | const nsEntry &entry = mHeaders[index]; |
280 | 0 |
|
281 | 0 | MOZ_ASSERT((entry.variety == eVarietyResponseNetOriginalAndResponse) || |
282 | 0 | (entry.variety == eVarietyResponseNetOriginal) || |
283 | 0 | (entry.variety == eVarietyResponse), |
284 | 0 | "This must be a response header."); |
285 | 0 | index++; |
286 | 0 | if (entry.variety == eVarietyResponse) { |
287 | 0 | continue; |
288 | 0 | } |
289 | 0 | |
290 | 0 | nsAutoCString hdr; |
291 | 0 | if (entry.headerNameOriginal.IsEmpty()) { |
292 | 0 | hdr = nsDependentCString(entry.header); |
293 | 0 | } else { |
294 | 0 | hdr = entry.headerNameOriginal; |
295 | 0 | } |
296 | 0 |
|
297 | 0 | rv = NS_OK; |
298 | 0 | if (NS_FAILED(aVisitor->VisitHeader(hdr, |
299 | 0 | entry.value))) { |
300 | 0 | break; |
301 | 0 | } |
302 | 0 | } else { |
303 | 0 | // if there is no such a header, it will return |
304 | 0 | // NS_ERROR_NOT_AVAILABLE or NS_OK otherwise. |
305 | 0 | return rv; |
306 | 0 | } |
307 | 0 | } |
308 | 0 | return NS_OK; |
309 | 0 | } |
310 | | |
311 | | bool |
312 | | nsHttpHeaderArray::HasHeader(nsHttpAtom header) const |
313 | 0 | { |
314 | 0 | const nsEntry *entry = nullptr; |
315 | 0 | LookupEntry(header, &entry); |
316 | 0 | return entry; |
317 | 0 | } |
318 | | |
319 | | nsresult |
320 | | nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray::VisitorFilter filter) |
321 | 0 | { |
322 | 0 | NS_ENSURE_ARG_POINTER(visitor); |
323 | 0 | nsresult rv; |
324 | 0 |
|
325 | 0 | uint32_t i, count = mHeaders.Length(); |
326 | 0 | for (i = 0; i < count; ++i) { |
327 | 0 | const nsEntry &entry = mHeaders[i]; |
328 | 0 | if (filter == eFilterSkipDefault && entry.variety == eVarietyRequestDefault) { |
329 | 0 | continue; |
330 | 0 | } else if (filter == eFilterResponse && entry.variety == eVarietyResponseNetOriginal) { |
331 | 0 | continue; |
332 | 0 | } else if (filter == eFilterResponseOriginal && entry.variety == eVarietyResponse) { |
333 | 0 | continue; |
334 | 0 | } |
335 | 0 | |
336 | 0 | nsAutoCString hdr; |
337 | 0 | if (entry.headerNameOriginal.IsEmpty()) { |
338 | 0 | hdr = nsDependentCString(entry.header); |
339 | 0 | } else { |
340 | 0 | hdr = entry.headerNameOriginal; |
341 | 0 | } |
342 | 0 | rv = visitor->VisitHeader(hdr, entry.value); |
343 | 0 | if (NS_FAILED(rv)) { |
344 | 0 | return rv; |
345 | 0 | } |
346 | 0 | } |
347 | 0 | return NS_OK; |
348 | 0 | } |
349 | | |
350 | | /*static*/ nsresult |
351 | | nsHttpHeaderArray::ParseHeaderLine(const nsACString& line, |
352 | | nsHttpAtom *hdr, |
353 | | nsACString *headerName, |
354 | | nsACString *val) |
355 | 0 | { |
356 | 0 | // |
357 | 0 | // BNF from section 4.2 of RFC 2616: |
358 | 0 | // |
359 | 0 | // message-header = field-name ":" [ field-value ] |
360 | 0 | // field-name = token |
361 | 0 | // field-value = *( field-content | LWS ) |
362 | 0 | // field-content = <the OCTETs making up the field-value |
363 | 0 | // and consisting of either *TEXT or combinations |
364 | 0 | // of token, separators, and quoted-string> |
365 | 0 | // |
366 | 0 |
|
367 | 0 | // We skip over mal-formed headers in the hope that we'll still be able to |
368 | 0 | // do something useful with the response. |
369 | 0 | int32_t split = line.FindChar(':'); |
370 | 0 |
|
371 | 0 | if (split == kNotFound) { |
372 | 0 | LOG(("malformed header [%s]: no colon\n", |
373 | 0 | PromiseFlatCString(line).get())); |
374 | 0 | return NS_ERROR_FAILURE; |
375 | 0 | } |
376 | 0 |
|
377 | 0 | const nsACString& sub = Substring(line, 0, split); |
378 | 0 | const nsACString& sub2 = Substring( |
379 | 0 | line, split + 1, line.Length() - split - 1); |
380 | 0 |
|
381 | 0 | // make sure we have a valid token for the field-name |
382 | 0 | if (!nsHttp::IsValidToken(sub)) { |
383 | 0 | LOG(("malformed header [%s]: field-name not a token\n", |
384 | 0 | PromiseFlatCString(line).get())); |
385 | 0 | return NS_ERROR_FAILURE; |
386 | 0 | } |
387 | 0 |
|
388 | 0 | nsHttpAtom atom = nsHttp::ResolveAtom(sub); |
389 | 0 | if (!atom) { |
390 | 0 | LOG(("failed to resolve atom [%s]\n", PromiseFlatCString(line).get())); |
391 | 0 | return NS_ERROR_FAILURE; |
392 | 0 | } |
393 | 0 |
|
394 | 0 | // skip over whitespace |
395 | 0 | char *p = net_FindCharNotInSet( |
396 | 0 | sub2.BeginReading(), sub2.EndReading(), HTTP_LWS); |
397 | 0 |
|
398 | 0 | // trim trailing whitespace - bug 86608 |
399 | 0 | char *p2 = net_RFindCharNotInSet(p, sub2.EndReading(), HTTP_LWS); |
400 | 0 |
|
401 | 0 | // assign return values |
402 | 0 | if (hdr) *hdr = atom; |
403 | 0 | if (val) val->Assign(p, p2 - p + 1); |
404 | 0 | if (headerName) headerName->Assign(sub); |
405 | 0 |
|
406 | 0 | return NS_OK; |
407 | 0 | } |
408 | | |
409 | | void |
410 | | nsHttpHeaderArray::Flatten(nsACString &buf, bool pruneProxyHeaders, |
411 | | bool pruneTransients) |
412 | 0 | { |
413 | 0 | uint32_t i, count = mHeaders.Length(); |
414 | 0 | for (i = 0; i < count; ++i) { |
415 | 0 | const nsEntry &entry = mHeaders[i]; |
416 | 0 | // Skip original header. |
417 | 0 | if (entry.variety == eVarietyResponseNetOriginal) { |
418 | 0 | continue; |
419 | 0 | } |
420 | 0 | // prune proxy headers if requested |
421 | 0 | if (pruneProxyHeaders && |
422 | 0 | ((entry.header == nsHttp::Proxy_Authorization) || |
423 | 0 | (entry.header == nsHttp::Proxy_Connection))) { |
424 | 0 | continue; |
425 | 0 | } |
426 | 0 | if (pruneTransients && |
427 | 0 | (entry.value.IsEmpty() || |
428 | 0 | entry.header == nsHttp::Connection || |
429 | 0 | entry.header == nsHttp::Proxy_Connection || |
430 | 0 | entry.header == nsHttp::Keep_Alive || |
431 | 0 | entry.header == nsHttp::WWW_Authenticate || |
432 | 0 | entry.header == nsHttp::Proxy_Authenticate || |
433 | 0 | entry.header == nsHttp::Trailer || |
434 | 0 | entry.header == nsHttp::Transfer_Encoding || |
435 | 0 | entry.header == nsHttp::Upgrade || |
436 | 0 | // XXX this will cause problems when we start honoring |
437 | 0 | // Cache-Control: no-cache="set-cookie", what to do? |
438 | 0 | entry.header == nsHttp::Set_Cookie)) { |
439 | 0 | continue; |
440 | 0 | } |
441 | 0 | |
442 | 0 | if (entry.headerNameOriginal.IsEmpty()) { |
443 | 0 | buf.Append(entry.header); |
444 | 0 | } else { |
445 | 0 | buf.Append(entry.headerNameOriginal); |
446 | 0 | } |
447 | 0 | buf.AppendLiteral(": "); |
448 | 0 | buf.Append(entry.value); |
449 | 0 | buf.AppendLiteral("\r\n"); |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | | void |
454 | | nsHttpHeaderArray::FlattenOriginalHeader(nsACString &buf) |
455 | 0 | { |
456 | 0 | uint32_t i, count = mHeaders.Length(); |
457 | 0 | for (i = 0; i < count; ++i) { |
458 | 0 | const nsEntry &entry = mHeaders[i]; |
459 | 0 | // Skip changed header. |
460 | 0 | if (entry.variety == eVarietyResponse) { |
461 | 0 | continue; |
462 | 0 | } |
463 | 0 | |
464 | 0 | if (entry.headerNameOriginal.IsEmpty()) { |
465 | 0 | buf.Append(entry.header); |
466 | 0 | } else { |
467 | 0 | buf.Append(entry.headerNameOriginal); |
468 | 0 | } |
469 | 0 |
|
470 | 0 | buf.AppendLiteral(": "); |
471 | 0 | buf.Append(entry.value); |
472 | 0 | buf.AppendLiteral("\r\n"); |
473 | 0 | } |
474 | 0 | } |
475 | | |
476 | | const char * |
477 | | nsHttpHeaderArray::PeekHeaderAt(uint32_t index, nsHttpAtom &header, |
478 | | nsACString &headerNameOriginal) const |
479 | 0 | { |
480 | 0 | const nsEntry &entry = mHeaders[index]; |
481 | 0 |
|
482 | 0 | header = entry.header; |
483 | 0 | headerNameOriginal = entry.headerNameOriginal; |
484 | 0 | return entry.value.get(); |
485 | 0 | } |
486 | | |
487 | | void |
488 | | nsHttpHeaderArray::Clear() |
489 | 0 | { |
490 | 0 | mHeaders.Clear(); |
491 | 0 | } |
492 | | |
493 | | } // namespace net |
494 | | } // namespace mozilla |