summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/nsHttpHeaderArray.h
blob: 622c97ace7eeb5fd328147c6043287115c039ba3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsHttpHeaderArray_h__
#define nsHttpHeaderArray_h__

#include "nsHttp.h"
#include "nsTArray.h"
#include "nsString.h"

class nsIHttpHeaderVisitor;

// This needs to be forward declared here so we can include only this header
// without also including PHttpChannelParams.h
namespace IPC {
template <typename>
struct ParamTraits;
}  // namespace IPC

namespace mozilla {
namespace net {

class nsHttpHeaderArray {
 public:
  const char* PeekHeader(const nsHttpAtom& header) const;

  // For nsHttpResponseHead nsHttpHeaderArray will keep track of the original
  // headers as they come from the network and the parse headers used in
  // firefox.
  // If the original and the firefox header are the same, we will keep just
  // one copy and marked it as eVarietyResponseNetOriginalAndResponse.
  // If firefox header representation changes a header coming from the
  // network (e.g. merged it) or a eVarietyResponseNetOriginalAndResponse
  // header has been changed by SetHeader method, we will keep the original
  // header as eVarietyResponseNetOriginal and make a copy for the new header
  // and mark it as eVarietyResponse.
  enum HeaderVariety {
    eVarietyUnknown,
    // Used only for request header.
    eVarietyRequestOverride,
    eVarietyRequestDefault,
    // Used only for response header.
    eVarietyResponseNetOriginalAndResponse,
    eVarietyResponseNetOriginal,
    eVarietyResponse
  };

  // Used by internal setters: to set header from network use SetHeaderFromNet
  [[nodiscard]] nsresult SetHeader(const nsACString& headerName,
                                   const nsACString& value, bool merge,
                                   HeaderVariety variety);
  [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
                                   const nsACString& value, bool merge,
                                   HeaderVariety variety);
  [[nodiscard]] nsresult SetHeader(const nsHttpAtom& header,
                                   const nsACString& headerName,
                                   const nsACString& value, bool merge,
                                   HeaderVariety variety);

  // Used by internal setters to set an empty header
  [[nodiscard]] nsresult SetEmptyHeader(const nsACString& headerName,
                                        HeaderVariety variety);

  // Merges supported headers. For other duplicate values, determines if error
  // needs to be thrown or 1st value kept.
  // For the response header we keep the original headers as well.
  [[nodiscard]] nsresult SetHeaderFromNet(const nsHttpAtom& header,
                                          const nsACString& headerNameOriginal,
                                          const nsACString& value,
                                          bool response);

  [[nodiscard]] nsresult SetResponseHeaderFromCache(
      const nsHttpAtom& header, const nsACString& headerNameOriginal,
      const nsACString& value, HeaderVariety variety);

  [[nodiscard]] nsresult GetHeader(const nsHttpAtom& header,
                                   nsACString& result) const;
  [[nodiscard]] nsresult GetOriginalHeader(const nsHttpAtom& aHeader,
                                           nsIHttpHeaderVisitor* aVisitor);
  void ClearHeader(const nsHttpAtom& h);

  // Find the location of the given header value, or null if none exists.
  const char* FindHeaderValue(const nsHttpAtom& header,
                              const char* value) const {
    return nsHttp::FindToken(PeekHeader(header), value, HTTP_HEADER_VALUE_SEPS);
  }

  // Determine if the given header value exists.
  bool HasHeaderValue(const nsHttpAtom& header, const char* value) const {
    return FindHeaderValue(header, value) != nullptr;
  }

  bool HasHeader(const nsHttpAtom& header) const;

  enum VisitorFilter {
    eFilterAll,
    eFilterSkipDefault,
    eFilterResponse,
    eFilterResponseOriginal
  };

  [[nodiscard]] nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
                                      VisitorFilter filter = eFilterAll);

  // parse a header line, return the header atom and a pointer to the
  // header value (the substring of the header line -- do not free).
  [[nodiscard]] static nsresult ParseHeaderLine(
      const nsACString& line, nsHttpAtom* hdr = nullptr,
      nsACString* headerNameOriginal = nullptr, nsACString* value = nullptr);

  void Flatten(nsACString&, bool pruneProxyHeaders, bool pruneTransients);
  void FlattenOriginalHeader(nsACString&);

  uint32_t Count() const { return mHeaders.Length(); }

  const char* PeekHeaderAt(uint32_t i, nsHttpAtom& header,
                           nsACString& headerNameOriginal) const;

  void Clear();

  // Must be copy-constructable and assignable
  struct nsEntry {
    nsHttpAtom header;
    nsCString headerNameOriginal;
    nsCString value;
    HeaderVariety variety = eVarietyUnknown;

    struct MatchHeader {
      bool Equals(const nsEntry& aEntry, const nsHttpAtom& aHeader) const {
        return aEntry.header == aHeader;
      }
    };

    bool operator==(const nsEntry& aOther) const {
      return header == aOther.header && value == aOther.value;
    }
  };

  bool operator==(const nsHttpHeaderArray& aOther) const {
    return mHeaders == aOther.mHeaders;
  }

 private:
  // LookupEntry function will never return eVarietyResponseNetOriginal.
  // It will ignore original headers from the network.
  int32_t LookupEntry(const nsHttpAtom& header, const nsEntry**) const;
  int32_t LookupEntry(const nsHttpAtom& header, nsEntry**);
  [[nodiscard]] nsresult MergeHeader(const nsHttpAtom& header, nsEntry* entry,
                                     const nsACString& value,
                                     HeaderVariety variety);
  [[nodiscard]] nsresult SetHeader_internal(const nsHttpAtom& header,
                                            const nsACString& headerName,
                                            const nsACString& value,
                                            HeaderVariety variety);

  // Header cannot be merged: only one value possible
  bool IsSingletonHeader(const nsHttpAtom& header);
  // Header cannot be merged, and subsequent values should be ignored
  bool IsIgnoreMultipleHeader(const nsHttpAtom& header);

  // Subset of singleton headers: should never see multiple, different
  // instances of these, else something fishy may be going on (like CLRF
  // injection)
  bool IsSuspectDuplicateHeader(const nsHttpAtom& header);

  // Removes duplicate header values entries
  // Will return unmodified header value if the header values contains
  // non-duplicate entries
  void RemoveDuplicateHeaderValues(const nsACString& aHeaderValue,
                                   nsACString& aResult);

  // All members must be copy-constructable and assignable
  CopyableTArray<nsEntry> mHeaders;

  friend struct IPC::ParamTraits<nsHttpHeaderArray>;
  friend class nsHttpRequestHead;
};

//-----------------------------------------------------------------------------
// nsHttpHeaderArray <private>: inline functions
//-----------------------------------------------------------------------------

inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
                                              const nsEntry** entry) const {
  uint32_t index = 0;
  while (index != UINT32_MAX) {
    index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
    if (index != UINT32_MAX) {
      if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
        *entry = &mHeaders[index];
        return index;
      }
      index++;
    }
  }

  return index;
}

inline int32_t nsHttpHeaderArray::LookupEntry(const nsHttpAtom& header,
                                              nsEntry** entry) {
  uint32_t index = 0;
  while (index != UINT32_MAX) {
    index = mHeaders.IndexOf(header, index, nsEntry::MatchHeader());
    if (index != UINT32_MAX) {
      if ((&mHeaders[index])->variety != eVarietyResponseNetOriginal) {
        *entry = &mHeaders[index];
        return index;
      }
      index++;
    }
  }
  return index;
}

inline bool nsHttpHeaderArray::IsSingletonHeader(const nsHttpAtom& header) {
  return header == nsHttp::Content_Type ||
         header == nsHttp::Content_Disposition ||
         header == nsHttp::Content_Length || header == nsHttp::User_Agent ||
         header == nsHttp::Referer || header == nsHttp::Host ||
         header == nsHttp::Authorization ||
         header == nsHttp::Proxy_Authorization ||
         header == nsHttp::If_Modified_Since ||
         header == nsHttp::If_Unmodified_Since || header == nsHttp::From ||
         header == nsHttp::Location || header == nsHttp::Max_Forwards ||
         // Ignore-multiple-headers are singletons in the sense that they
         // shouldn't be merged.
         IsIgnoreMultipleHeader(header);
}

// These are headers for which, in the presence of multiple values, we only
// consider the first.
inline bool nsHttpHeaderArray::IsIgnoreMultipleHeader(
    const nsHttpAtom& header) {
  // https://tools.ietf.org/html/rfc6797#section-8:
  //
  //     If a UA receives more than one STS header field in an HTTP
  //     response message over secure transport, then the UA MUST process
  //     only the first such header field.
  return header == nsHttp::Strict_Transport_Security;
}

[[nodiscard]] inline nsresult nsHttpHeaderArray::MergeHeader(
    const nsHttpAtom& header, nsEntry* entry, const nsACString& value,
    nsHttpHeaderArray::HeaderVariety variety) {
  if (value.IsEmpty()) return NS_OK;  // merge of empty header = no-op

  nsCString newValue = entry->value;
  if (!newValue.IsEmpty()) {
    // Append the new value to the existing value
    if (header == nsHttp::Set_Cookie || header == nsHttp::WWW_Authenticate ||
        header == nsHttp::Proxy_Authenticate) {
      // Special case these headers and use a newline delimiter to
      // delimit the values from one another as commas may appear
      // in the values of these headers contrary to what the spec says.
      newValue.Append('\n');
    } else {
      // Delimit each value from the others using a comma (per HTTP spec)
      newValue.AppendLiteral(", ");
    }
  }

  newValue.Append(value);
  if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
    MOZ_ASSERT(variety == eVarietyResponse);
    entry->variety = eVarietyResponseNetOriginal;
    // Copy entry->headerNameOriginal because in SetHeader_internal we are going
    // to a new one and a realocation can happen.
    nsCString headerNameOriginal = entry->headerNameOriginal;
    nsresult rv = SetHeader_internal(header, headerNameOriginal, newValue,
                                     eVarietyResponse);
    if (NS_FAILED(rv)) {
      return rv;
    }
  } else {
    entry->value = newValue;
    entry->variety = variety;
  }
  return NS_OK;
}

inline bool nsHttpHeaderArray::IsSuspectDuplicateHeader(
    const nsHttpAtom& header) {
  bool retval = header == nsHttp::Content_Length ||
                header == nsHttp::Content_Disposition ||
                header == nsHttp::Location;

  MOZ_ASSERT(!retval || IsSingletonHeader(header),
             "Only non-mergeable headers should be in this list\n");

  return retval;
}

inline void nsHttpHeaderArray::RemoveDuplicateHeaderValues(
    const nsACString& aHeaderValue, nsACString& aResult) {
  mozilla::Maybe<nsAutoCString> result;
  for (const nsACString& token :
       nsCCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
    if (result.isNothing()) {
      // assign the first value
      result.emplace(token);
      continue;
    }
    if (*result != token) {
      // non-identical header values. Do not change the header values
      result.reset();
      break;
    }
  }

  if (result.isSome()) {
    aResult = *result;
  } else {
    // either header values do not have multiple values or
    // has unequal multiple values
    // for both the cases restore the original header value
    aResult = aHeaderValue;
  }
}

}  // namespace net
}  // namespace mozilla

#endif