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