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