1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set sw=4 ts=8 et 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 #ifndef nsHttpHeaderArray_h__
8 #define nsHttpHeaderArray_h__
9 
10 #include "nsHttp.h"
11 #include "nsTArray.h"
12 #include "nsString.h"
13 
14 class nsIHttpHeaderVisitor;
15 
16 // This needs to be forward declared here so we can include only this header
17 // without also including PHttpChannelParams.h
18 namespace IPC {
19 template <typename>
20 struct ParamTraits;
21 }  // namespace IPC
22 
23 namespace mozilla {
24 namespace net {
25 
26 class nsHttpHeaderArray {
27  public:
28   const char *PeekHeader(nsHttpAtom header) const;
29 
30   // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
31   // headers as they come from the network and the parse headers used in
32   // firefox.
33   // If the original and the firefox header are the same, we will keep just
34   // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
35   // If firefox header representation changes a header coming from the
36   // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
37   // header has been changed by SetHeader method, we will keep the original
38   // header as eVarietyResponseNetOriginal and make a copy for the new header
39   // and mark it as eVarietyResponse.
40   enum HeaderVariety {
41     eVarietyUnknown,
42     // Used only for request header.
43     eVarietyRequestOverride,
44     eVarietyRequestDefault,
45     // Used only for response header.
46     eVarietyResponseNetOriginalAndResponse,
47     eVarietyResponseNetOriginal,
48     eVarietyResponse
49   };
50 
51   // Used by internal setters: to set header from network use SetHeaderFromNet
52   MOZ_MUST_USE nsresult SetHeader(const nsACString &headerName,
53                                   const nsACString &value, bool merge,
54                                   HeaderVariety variety);
55   MOZ_MUST_USE nsresult SetHeader(nsHttpAtom header, const nsACString &value,
56                                   bool merge, HeaderVariety variety);
57   MOZ_MUST_USE nsresult SetHeader(nsHttpAtom header,
58                                   const nsACString &headerName,
59                                   const nsACString &value, bool merge,
60                                   HeaderVariety variety);
61 
62   // Used by internal setters to set an empty header
63   MOZ_MUST_USE nsresult SetEmptyHeader(const nsACString &headerName,
64                                        HeaderVariety variety);
65 
66   // Merges supported headers. For other duplicate values, determines if error
67   // needs to be thrown or 1st value kept.
68   // For the response header we keep the original headers as well.
69   MOZ_MUST_USE nsresult SetHeaderFromNet(nsHttpAtom header,
70                                          const nsACString &headerNameOriginal,
71                                          const nsACString &value,
72                                          bool response);
73 
74   MOZ_MUST_USE nsresult SetResponseHeaderFromCache(
75       nsHttpAtom header, const nsACString &headerNameOriginal,
76       const nsACString &value, HeaderVariety variety);
77 
78   MOZ_MUST_USE nsresult GetHeader(nsHttpAtom header, nsACString &value) const;
79   MOZ_MUST_USE nsresult GetOriginalHeader(nsHttpAtom aHeader,
80                                           nsIHttpHeaderVisitor *aVisitor);
81   void ClearHeader(nsHttpAtom h);
82 
83   // Find the location of the given header value, or null if none exists.
FindHeaderValue(nsHttpAtom header,const char * value)84   const char *FindHeaderValue(nsHttpAtom header, const char *value) const {
85     return nsHttp::FindToken(PeekHeader(header), value, HTTP_HEADER_VALUE_SEPS);
86   }
87 
88   // Determine if the given header value exists.
HasHeaderValue(nsHttpAtom header,const char * value)89   bool HasHeaderValue(nsHttpAtom header, const char *value) const {
90     return FindHeaderValue(header, value) != nullptr;
91   }
92 
93   bool HasHeader(nsHttpAtom header) const;
94 
95   enum VisitorFilter {
96     eFilterAll,
97     eFilterSkipDefault,
98     eFilterResponse,
99     eFilterResponseOriginal
100   };
101 
102   MOZ_MUST_USE nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor,
103                                      VisitorFilter filter = eFilterAll);
104 
105   // parse a header line, return the header atom and a pointer to the
106   // header value (the substring of the header line -- do not free).
107   static MOZ_MUST_USE nsresult ParseHeaderLine(
108       const nsACString &line, nsHttpAtom *header = nullptr,
109       nsACString *headerNameOriginal = nullptr, nsACString *value = nullptr);
110 
111   void Flatten(nsACString &, bool pruneProxyHeaders, bool pruneTransients);
112   void FlattenOriginalHeader(nsACString &);
113 
Count()114   uint32_t Count() const { return mHeaders.Length(); }
115 
116   const char *PeekHeaderAt(uint32_t i, nsHttpAtom &header,
117                            nsACString &headerNameOriginal) const;
118 
119   void Clear();
120 
121   // Must be copy-constructable and assignable
122   struct nsEntry {
123     nsHttpAtom header;
124     nsCString headerNameOriginal;
125     nsCString value;
126     HeaderVariety variety = eVarietyUnknown;
127 
128     struct MatchHeader {
EqualsnsEntry::MatchHeader129       bool Equals(const nsEntry &aEntry, const nsHttpAtom &aHeader) const {
130         return aEntry.header == aHeader;
131       }
132     };
133 
134     bool operator==(const nsEntry &aOther) const {
135       return header == aOther.header && value == aOther.value;
136     }
137   };
138 
139   bool operator==(const nsHttpHeaderArray &aOther) const {
140     return mHeaders == aOther.mHeaders;
141   }
142 
143  private:
144   // LookupEntry function will never return eVarietyResponseNetOriginal.
145   // It will ignore original headers from the network.
146   int32_t LookupEntry(nsHttpAtom header, const nsEntry **) const;
147   int32_t LookupEntry(nsHttpAtom header, nsEntry **);
148   MOZ_MUST_USE nsresult MergeHeader(nsHttpAtom header, nsEntry *entry,
149                                     const nsACString &value,
150                                     HeaderVariety variety);
151   MOZ_MUST_USE nsresult SetHeader_internal(nsHttpAtom header,
152                                            const nsACString &headeName,
153                                            const nsACString &value,
154                                            HeaderVariety variety);
155 
156   // Header cannot be merged: only one value possible
157   bool IsSingletonHeader(nsHttpAtom header);
158   // Header cannot be merged, and subsequent values should be ignored
159   bool IsIgnoreMultipleHeader(nsHttpAtom header);
160 
161   // Subset of singleton headers: should never see multiple, different
162   // instances of these, else something fishy may be going on (like CLRF
163   // injection)
164   bool IsSuspectDuplicateHeader(nsHttpAtom header);
165 
166   // All members must be copy-constructable and assignable
167   nsTArray<nsEntry> mHeaders;
168 
169   friend struct IPC::ParamTraits<nsHttpHeaderArray>;
170   friend class nsHttpRequestHead;
171 };
172 
173 //-----------------------------------------------------------------------------
174 // nsHttpHeaderArray <private>: inline functions
175 //-----------------------------------------------------------------------------
176 
177 inline int32_t nsHttpHeaderArray::LookupEntry(nsHttpAtom header,
178                                               const nsEntry **entry) const {
179   uint32_t index = 0;
180   while (index != UINT32_MAX) {
181     index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
182     if (index != UINT32_MAX) {
183       if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
184         *entry = &mHeaders[index];
185         return index;
186       }
187       index++;
188     }
189   }
190 
191   return index;
192 }
193 
194 inline int32_t nsHttpHeaderArray::LookupEntry(nsHttpAtom header,
195                                               nsEntry **entry) {
196   uint32_t index = 0;
197   while (index != UINT32_MAX) {
198     index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
199     if (index != UINT32_MAX) {
200       if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
201         *entry = &mHeaders[index];
202         return index;
203       }
204       index++;
205     }
206   }
207   return index;
208 }
209 
210 inline bool nsHttpHeaderArray::IsSingletonHeader(nsHttpAtom header) {
211   return header == nsHttp::Content_Type ||
212          header == nsHttp::Content_Disposition ||
213          header == nsHttp::Content_Length || header == nsHttp::User_Agent ||
214          header == nsHttp::Referer || header == nsHttp::Host ||
215          header == nsHttp::Authorization ||
216          header == nsHttp::Proxy_Authorization ||
217          header == nsHttp::If_Modified_Since ||
218          header == nsHttp::If_Unmodified_Since || header == nsHttp::From ||
219          header == nsHttp::Location || header == nsHttp::Max_Forwards ||
220          // Ignore-multiple-headers are singletons in the sense that they
221          // shouldn't be merged.
222          IsIgnoreMultipleHeader(header);
223 }
224 
225 // These are headers for which, in the presence of multiple values, we only
226 // consider the first.
227 inline bool nsHttpHeaderArray::IsIgnoreMultipleHeader(nsHttpAtom header) {
228   // https://tools.ietf.org/html/rfc6797#section-8:
229   //
230   //     If a UA receives more than one STS header field in an HTTP
231   //     response message over secure transport, then the UA MUST process
232   //     only the first such header field.
233   return header == nsHttp::Strict_Transport_Security;
234 }
235 
236 inline MOZ_MUST_USE nsresult nsHttpHeaderArray::MergeHeader(
237     nsHttpAtom header, nsEntry *entry, const nsACString &value,
238     nsHttpHeaderArray::HeaderVariety variety) {
239   if (value.IsEmpty()) return NS_OK;  // merge of empty header = no-op
240 
241   nsCString newValue = entry->value;
242   if (!newValue.IsEmpty()) {
243     // Append the new value to the existing value
244     if (header == nsHttp::Set_Cookie || header == nsHttp::WWW_Authenticate ||
245         header == nsHttp::Proxy_Authenticate) {
246       // Special case these headers and use a newline delimiter to
247       // delimit the values from one another as commas may appear
248       // in the values of these headers contrary to what the spec says.
249       newValue.Append('\n');
250     } else {
251       // Delimit each value from the others using a comma (per HTTP spec)
252       newValue.AppendLiteral(", ");
253     }
254   }
255 
256   newValue.Append(value);
257   if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
258     MOZ_ASSERT(variety == eVarietyResponse);
259     entry->variety = eVarietyResponseNetOriginal;
260     // Copy entry->headerNameOriginal because in SetHeader_internal we are going
261     // to a new one and a realocation can happen.
262     nsCString headerNameOriginal = entry->headerNameOriginal;
263     nsresult rv = SetHeader_internal(header, headerNameOriginal, newValue,
264                                      eVarietyResponse);
265     if (NS_FAILED(rv)) {
266       return rv;
267     }
268   } else {
269     entry->value = newValue;
270     entry->variety = variety;
271   }
272   return NS_OK;
273 }
274 
275 inline bool nsHttpHeaderArray::IsSuspectDuplicateHeader(nsHttpAtom header) {
276   bool retval = header == nsHttp::Content_Length ||
277                 header == nsHttp::Content_Disposition ||
278                 header == nsHttp::Location;
279 
280   MOZ_ASSERT(!retval || IsSingletonHeader(header),
281              "Only non-mergeable headers should be in this list\n");
282 
283   return retval;
284 }
285 
286 }  // namespace net
287 }  // namespace mozilla
288 
289 #endif
290