Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 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 : // HttpLog.h should generally be included first
8 : #include "HttpLog.h"
9 :
10 : // Log on level :5, instead of default :4.
11 : #undef LOG
12 : #define LOG(args) LOG5(args)
13 : #undef LOG_ENABLED
14 : #define LOG_ENABLED() LOG5_ENABLED()
15 :
16 : #include "Http2Compression.h"
17 : #include "Http2HuffmanIncoming.h"
18 : #include "Http2HuffmanOutgoing.h"
19 : #include "mozilla/StaticPtr.h"
20 : #include "nsCharSeparatedTokenizer.h"
21 : #include "nsHttpHandler.h"
22 :
23 : namespace mozilla {
24 : namespace net {
25 :
26 : static nsDeque *gStaticHeaders = nullptr;
27 :
28 : class HpackStaticTableReporter final : public nsIMemoryReporter
29 : {
30 : public:
31 : NS_DECL_THREADSAFE_ISUPPORTS
32 :
33 0 : HpackStaticTableReporter() {}
34 :
35 : NS_IMETHOD
36 0 : CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
37 : bool aAnonymize) override
38 : {
39 0 : MOZ_COLLECT_REPORT(
40 : "explicit/network/hpack/static-table", KIND_HEAP, UNITS_BYTES,
41 : gStaticHeaders->SizeOfIncludingThis(MallocSizeOf),
42 0 : "Memory usage of HPACK static table.");
43 :
44 0 : return NS_OK;
45 : }
46 :
47 : private:
48 0 : MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
49 :
50 0 : ~HpackStaticTableReporter() {}
51 : };
52 :
53 0 : NS_IMPL_ISUPPORTS(HpackStaticTableReporter, nsIMemoryReporter)
54 :
55 : class HpackDynamicTableReporter final : public nsIMemoryReporter
56 : {
57 : public:
58 : NS_DECL_THREADSAFE_ISUPPORTS
59 :
60 0 : explicit HpackDynamicTableReporter(Http2BaseCompressor* aCompressor)
61 0 : : mCompressor(aCompressor)
62 0 : {}
63 :
64 : NS_IMETHOD
65 0 : CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
66 : bool aAnonymize) override
67 : {
68 0 : if (mCompressor) {
69 0 : MOZ_COLLECT_REPORT(
70 : "explicit/network/hpack/dynamic-tables", KIND_HEAP, UNITS_BYTES,
71 : mCompressor->SizeOfExcludingThis(MallocSizeOf),
72 0 : "Aggregate memory usage of HPACK dynamic tables.");
73 : }
74 0 : return NS_OK;
75 : }
76 :
77 : private:
78 0 : MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
79 :
80 0 : ~HpackDynamicTableReporter() {}
81 :
82 : Http2BaseCompressor* mCompressor;
83 :
84 : friend class Http2BaseCompressor;
85 : };
86 :
87 0 : NS_IMPL_ISUPPORTS(HpackDynamicTableReporter, nsIMemoryReporter)
88 :
89 3 : StaticRefPtr<HpackStaticTableReporter> gStaticReporter;
90 :
91 : void
92 0 : Http2CompressionCleanup()
93 : {
94 : // this happens after the socket thread has been destroyed
95 0 : delete gStaticHeaders;
96 0 : gStaticHeaders = nullptr;
97 0 : UnregisterStrongMemoryReporter(gStaticReporter);
98 0 : gStaticReporter = nullptr;
99 0 : }
100 :
101 : static void
102 0 : AddStaticElement(const nsCString &name, const nsCString &value)
103 : {
104 0 : nvPair *pair = new nvPair(name, value);
105 0 : gStaticHeaders->Push(pair);
106 0 : }
107 :
108 : static void
109 0 : AddStaticElement(const nsCString &name)
110 : {
111 0 : AddStaticElement(name, EmptyCString());
112 0 : }
113 :
114 : static void
115 0 : InitializeStaticHeaders()
116 : {
117 0 : MOZ_ASSERT(OnSocketThread(), "not on socket thread");
118 0 : if (!gStaticHeaders) {
119 0 : gStaticHeaders = new nsDeque();
120 0 : gStaticReporter = new HpackStaticTableReporter();
121 0 : RegisterStrongMemoryReporter(gStaticReporter);
122 0 : AddStaticElement(NS_LITERAL_CSTRING(":authority"));
123 0 : AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("GET"));
124 0 : AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("POST"));
125 0 : AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/"));
126 0 : AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/index.html"));
127 0 : AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("http"));
128 0 : AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("https"));
129 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("200"));
130 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("204"));
131 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("206"));
132 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("304"));
133 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("400"));
134 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("404"));
135 0 : AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("500"));
136 0 : AddStaticElement(NS_LITERAL_CSTRING("accept-charset"));
137 0 : AddStaticElement(NS_LITERAL_CSTRING("accept-encoding"), NS_LITERAL_CSTRING("gzip, deflate"));
138 0 : AddStaticElement(NS_LITERAL_CSTRING("accept-language"));
139 0 : AddStaticElement(NS_LITERAL_CSTRING("accept-ranges"));
140 0 : AddStaticElement(NS_LITERAL_CSTRING("accept"));
141 0 : AddStaticElement(NS_LITERAL_CSTRING("access-control-allow-origin"));
142 0 : AddStaticElement(NS_LITERAL_CSTRING("age"));
143 0 : AddStaticElement(NS_LITERAL_CSTRING("allow"));
144 0 : AddStaticElement(NS_LITERAL_CSTRING("authorization"));
145 0 : AddStaticElement(NS_LITERAL_CSTRING("cache-control"));
146 0 : AddStaticElement(NS_LITERAL_CSTRING("content-disposition"));
147 0 : AddStaticElement(NS_LITERAL_CSTRING("content-encoding"));
148 0 : AddStaticElement(NS_LITERAL_CSTRING("content-language"));
149 0 : AddStaticElement(NS_LITERAL_CSTRING("content-length"));
150 0 : AddStaticElement(NS_LITERAL_CSTRING("content-location"));
151 0 : AddStaticElement(NS_LITERAL_CSTRING("content-range"));
152 0 : AddStaticElement(NS_LITERAL_CSTRING("content-type"));
153 0 : AddStaticElement(NS_LITERAL_CSTRING("cookie"));
154 0 : AddStaticElement(NS_LITERAL_CSTRING("date"));
155 0 : AddStaticElement(NS_LITERAL_CSTRING("etag"));
156 0 : AddStaticElement(NS_LITERAL_CSTRING("expect"));
157 0 : AddStaticElement(NS_LITERAL_CSTRING("expires"));
158 0 : AddStaticElement(NS_LITERAL_CSTRING("from"));
159 0 : AddStaticElement(NS_LITERAL_CSTRING("host"));
160 0 : AddStaticElement(NS_LITERAL_CSTRING("if-match"));
161 0 : AddStaticElement(NS_LITERAL_CSTRING("if-modified-since"));
162 0 : AddStaticElement(NS_LITERAL_CSTRING("if-none-match"));
163 0 : AddStaticElement(NS_LITERAL_CSTRING("if-range"));
164 0 : AddStaticElement(NS_LITERAL_CSTRING("if-unmodified-since"));
165 0 : AddStaticElement(NS_LITERAL_CSTRING("last-modified"));
166 0 : AddStaticElement(NS_LITERAL_CSTRING("link"));
167 0 : AddStaticElement(NS_LITERAL_CSTRING("location"));
168 0 : AddStaticElement(NS_LITERAL_CSTRING("max-forwards"));
169 0 : AddStaticElement(NS_LITERAL_CSTRING("proxy-authenticate"));
170 0 : AddStaticElement(NS_LITERAL_CSTRING("proxy-authorization"));
171 0 : AddStaticElement(NS_LITERAL_CSTRING("range"));
172 0 : AddStaticElement(NS_LITERAL_CSTRING("referer"));
173 0 : AddStaticElement(NS_LITERAL_CSTRING("refresh"));
174 0 : AddStaticElement(NS_LITERAL_CSTRING("retry-after"));
175 0 : AddStaticElement(NS_LITERAL_CSTRING("server"));
176 0 : AddStaticElement(NS_LITERAL_CSTRING("set-cookie"));
177 0 : AddStaticElement(NS_LITERAL_CSTRING("strict-transport-security"));
178 0 : AddStaticElement(NS_LITERAL_CSTRING("transfer-encoding"));
179 0 : AddStaticElement(NS_LITERAL_CSTRING("user-agent"));
180 0 : AddStaticElement(NS_LITERAL_CSTRING("vary"));
181 0 : AddStaticElement(NS_LITERAL_CSTRING("via"));
182 0 : AddStaticElement(NS_LITERAL_CSTRING("www-authenticate"));
183 : }
184 0 : }
185 :
186 0 : size_t nvPair::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
187 : {
188 0 : return mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
189 0 : mValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
190 : }
191 :
192 0 : size_t nvPair::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
193 : {
194 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
195 : }
196 :
197 0 : nvFIFO::nvFIFO()
198 : : mByteCount(0)
199 0 : , mTable()
200 : {
201 0 : InitializeStaticHeaders();
202 0 : }
203 :
204 0 : nvFIFO::~nvFIFO()
205 : {
206 0 : Clear();
207 0 : }
208 :
209 : void
210 0 : nvFIFO::AddElement(const nsCString &name, const nsCString &value)
211 : {
212 0 : mByteCount += name.Length() + value.Length() + 32;
213 0 : nvPair *pair = new nvPair(name, value);
214 0 : mTable.PushFront(pair);
215 0 : }
216 :
217 : void
218 0 : nvFIFO::AddElement(const nsCString &name)
219 : {
220 0 : AddElement(name, EmptyCString());
221 0 : }
222 :
223 : void
224 0 : nvFIFO::RemoveElement()
225 : {
226 0 : nvPair *pair = static_cast<nvPair *>(mTable.Pop());
227 0 : if (pair) {
228 0 : mByteCount -= pair->Size();
229 0 : delete pair;
230 : }
231 0 : }
232 :
233 : uint32_t
234 0 : nvFIFO::ByteCount() const
235 : {
236 0 : return mByteCount;
237 : }
238 :
239 : uint32_t
240 0 : nvFIFO::Length() const
241 : {
242 0 : return mTable.GetSize() + gStaticHeaders->GetSize();
243 : }
244 :
245 : uint32_t
246 0 : nvFIFO::VariableLength() const
247 : {
248 0 : return mTable.GetSize();
249 : }
250 :
251 : size_t
252 0 : nvFIFO::StaticLength() const
253 : {
254 0 : return gStaticHeaders->GetSize();
255 : }
256 :
257 : void
258 0 : nvFIFO::Clear()
259 : {
260 0 : mByteCount = 0;
261 0 : while (mTable.GetSize())
262 0 : delete static_cast<nvPair *>(mTable.Pop());
263 0 : }
264 :
265 : const nvPair *
266 0 : nvFIFO::operator[] (size_t index) const
267 : {
268 : // NWGH - ensure index > 0
269 : // NWGH - subtract 1 from index here
270 0 : if (index >= (mTable.GetSize() + gStaticHeaders->GetSize())) {
271 0 : MOZ_ASSERT(false);
272 : NS_WARNING("nvFIFO Table Out of Range");
273 : return nullptr;
274 : }
275 0 : if (index >= gStaticHeaders->GetSize()) {
276 0 : return static_cast<nvPair *>(mTable.ObjectAt(index - gStaticHeaders->GetSize()));
277 : }
278 0 : return static_cast<nvPair *>(gStaticHeaders->ObjectAt(index));
279 : }
280 :
281 0 : Http2BaseCompressor::Http2BaseCompressor()
282 : : mOutput(nullptr)
283 : , mMaxBuffer(kDefaultMaxBuffer)
284 : , mMaxBufferSetting(kDefaultMaxBuffer)
285 : , mSetInitialMaxBufferSizeAllowed(true)
286 : , mPeakSize(0)
287 0 : , mPeakCount(0)
288 : {
289 0 : mDynamicReporter = new HpackDynamicTableReporter(this);
290 0 : RegisterStrongMemoryReporter(mDynamicReporter);
291 0 : }
292 :
293 0 : Http2BaseCompressor::~Http2BaseCompressor()
294 : {
295 0 : if (mPeakSize) {
296 0 : Telemetry::Accumulate(mPeakSizeID, mPeakSize);
297 : }
298 0 : if (mPeakCount) {
299 0 : Telemetry::Accumulate(mPeakCountID, mPeakCount);
300 : }
301 0 : UnregisterStrongMemoryReporter(mDynamicReporter);
302 0 : mDynamicReporter->mCompressor = nullptr;
303 0 : mDynamicReporter = nullptr;
304 0 : }
305 :
306 : void
307 0 : Http2BaseCompressor::ClearHeaderTable()
308 : {
309 0 : mHeaderTable.Clear();
310 0 : }
311 :
312 : size_t
313 0 : Http2BaseCompressor::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
314 : {
315 0 : size_t size = 0;
316 0 : for (uint32_t i = mHeaderTable.StaticLength(); i < mHeaderTable.Length(); ++i) {
317 0 : size += mHeaderTable[i]->SizeOfIncludingThis(aMallocSizeOf);
318 : }
319 0 : return size;
320 : }
321 :
322 : void
323 0 : Http2BaseCompressor::MakeRoom(uint32_t amount, const char *direction)
324 : {
325 0 : uint32_t countEvicted = 0;
326 0 : uint32_t bytesEvicted = 0;
327 :
328 : // make room in the header table
329 0 : while (mHeaderTable.VariableLength() && ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
330 : // NWGH - remove the "- 1" here
331 0 : uint32_t index = mHeaderTable.Length() - 1;
332 0 : LOG(("HTTP %s header table index %u %s %s removed for size.\n",
333 : direction, index, mHeaderTable[index]->mName.get(),
334 : mHeaderTable[index]->mValue.get()));
335 0 : ++countEvicted;
336 0 : bytesEvicted += mHeaderTable[index]->Size();
337 0 : mHeaderTable.RemoveElement();
338 : }
339 :
340 0 : if (!strcmp(direction, "decompressor")) {
341 0 : Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_DECOMPRESSOR, countEvicted);
342 0 : Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_DECOMPRESSOR, bytesEvicted);
343 0 : Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_RATIO_DECOMPRESSOR, (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
344 : } else {
345 0 : Telemetry::Accumulate(Telemetry::HPACK_ELEMENTS_EVICTED_COMPRESSOR, countEvicted);
346 0 : Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_COMPRESSOR, bytesEvicted);
347 0 : Telemetry::Accumulate(Telemetry::HPACK_BYTES_EVICTED_RATIO_COMPRESSOR, (uint32_t)((100.0 * (double)bytesEvicted) / (double)amount));
348 : }
349 0 : }
350 :
351 : void
352 0 : Http2BaseCompressor::DumpState()
353 : {
354 0 : if (!LOG_ENABLED()) {
355 0 : return;
356 : }
357 :
358 0 : LOG(("Header Table"));
359 : uint32_t i;
360 0 : uint32_t length = mHeaderTable.Length();
361 0 : uint32_t staticLength = mHeaderTable.StaticLength();
362 : // NWGH - make i = 1; i <= length; ++i
363 0 : for (i = 0; i < length; ++i) {
364 0 : const nvPair *pair = mHeaderTable[i];
365 : // NWGH - make this <= staticLength
366 0 : LOG(("%sindex %u: %s %s", i < staticLength ? "static " : "", i,
367 : pair->mName.get(), pair->mValue.get()));
368 : }
369 : }
370 :
371 : void
372 0 : Http2BaseCompressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize)
373 : {
374 0 : MOZ_ASSERT(maxBufferSize <= mMaxBufferSetting);
375 :
376 0 : uint32_t removedCount = 0;
377 :
378 0 : LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called", maxBufferSize));
379 :
380 0 : while (mHeaderTable.VariableLength() && (mHeaderTable.ByteCount() > maxBufferSize)) {
381 0 : mHeaderTable.RemoveElement();
382 0 : ++removedCount;
383 : }
384 :
385 0 : mMaxBuffer = maxBufferSize;
386 0 : }
387 :
388 : nsresult
389 0 : Http2BaseCompressor::SetInitialMaxBufferSize(uint32_t maxBufferSize)
390 : {
391 0 : MOZ_ASSERT(mSetInitialMaxBufferSizeAllowed);
392 :
393 0 : if (mSetInitialMaxBufferSizeAllowed) {
394 0 : mMaxBufferSetting = maxBufferSize;
395 0 : return NS_OK;
396 : }
397 :
398 0 : return NS_ERROR_FAILURE;
399 : }
400 :
401 : nsresult
402 0 : Http2Decompressor::DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
403 : nsACString &output, bool isPush)
404 : {
405 0 : mSetInitialMaxBufferSizeAllowed = false;
406 0 : mOffset = 0;
407 0 : mData = data;
408 0 : mDataLen = datalen;
409 0 : mOutput = &output;
410 0 : mOutput->Truncate();
411 0 : mHeaderStatus.Truncate();
412 0 : mHeaderHost.Truncate();
413 0 : mHeaderScheme.Truncate();
414 0 : mHeaderPath.Truncate();
415 0 : mHeaderMethod.Truncate();
416 0 : mSeenNonColonHeader = false;
417 0 : mIsPush = isPush;
418 :
419 0 : nsresult rv = NS_OK;
420 0 : nsresult softfail_rv = NS_OK;
421 0 : while (NS_SUCCEEDED(rv) && (mOffset < datalen)) {
422 0 : bool modifiesTable = true;
423 0 : if (mData[mOffset] & 0x80) {
424 0 : rv = DoIndexed();
425 0 : LOG(("Decompressor state after indexed"));
426 0 : } else if (mData[mOffset] & 0x40) {
427 0 : rv = DoLiteralWithIncremental();
428 0 : LOG(("Decompressor state after literal with incremental"));
429 0 : } else if (mData[mOffset] & 0x20) {
430 0 : rv = DoContextUpdate();
431 0 : LOG(("Decompressor state after context update"));
432 0 : } else if (mData[mOffset] & 0x10) {
433 0 : modifiesTable = false;
434 0 : rv = DoLiteralNeverIndexed();
435 0 : LOG(("Decompressor state after literal never index"));
436 : } else {
437 0 : modifiesTable = false;
438 0 : rv = DoLiteralWithoutIndex();
439 0 : LOG(("Decompressor state after literal without index"));
440 : }
441 0 : DumpState();
442 0 : if (rv == NS_ERROR_ILLEGAL_VALUE) {
443 0 : if (modifiesTable) {
444 : // Unfortunately, we can't count on our peer now having the same state
445 : // as us, so let's terminate the session and we can try again later.
446 0 : return NS_ERROR_FAILURE;
447 : }
448 :
449 : // This is an http-level error that we can handle by resetting the stream
450 : // in the upper layers. Let's note that we saw this, then continue
451 : // decompressing until we either hit the end of the header block or find a
452 : // hard failure. That way we won't get an inconsistent compression state
453 : // with the server.
454 0 : softfail_rv = rv;
455 0 : rv = NS_OK;
456 0 : } else if (rv == NS_ERROR_NET_RESET) {
457 : // This happens when we detect connection-based auth being requested in
458 : // the response headers. We'll paper over it for now, and the session will
459 : // handle this as if it received RST_STREAM with HTTP_1_1_REQUIRED.
460 0 : softfail_rv = rv;
461 0 : rv = NS_OK;
462 : }
463 : }
464 :
465 0 : if (NS_FAILED(rv)) {
466 0 : return rv;
467 : }
468 :
469 0 : return softfail_rv;
470 : }
471 :
472 : nsresult
473 0 : Http2Decompressor::DecodeInteger(uint32_t prefixLen, uint32_t &accum)
474 : {
475 0 : accum = 0;
476 :
477 0 : if (prefixLen) {
478 0 : uint32_t mask = (1 << prefixLen) - 1;
479 :
480 0 : accum = mData[mOffset] & mask;
481 0 : ++mOffset;
482 :
483 0 : if (accum != mask) {
484 : // the simple case for small values
485 0 : return NS_OK;
486 : }
487 : }
488 :
489 0 : uint32_t factor = 1; // 128 ^ 0
490 :
491 : // we need a series of bytes. The high bit signifies if we need another one.
492 : // The first one is a a factor of 128 ^ 0, the next 128 ^1, the next 128 ^2, ..
493 :
494 0 : if (mOffset >= mDataLen) {
495 0 : NS_WARNING("Ran out of data to decode integer");
496 : // This is session-fatal.
497 0 : return NS_ERROR_FAILURE;
498 : }
499 0 : bool chainBit = mData[mOffset] & 0x80;
500 0 : accum += (mData[mOffset] & 0x7f) * factor;
501 :
502 0 : ++mOffset;
503 0 : factor = factor * 128;
504 :
505 0 : while (chainBit) {
506 : // really big offsets are just trawling for overflows
507 0 : if (accum >= 0x800000) {
508 0 : NS_WARNING("Decoding integer >= 0x800000");
509 : // This is not strictly fatal to the session, but given the fact that
510 : // the value is way to large to be reasonable, let's just tell our peer
511 : // to go away.
512 0 : return NS_ERROR_FAILURE;
513 : }
514 :
515 0 : if (mOffset >= mDataLen) {
516 0 : NS_WARNING("Ran out of data to decode integer");
517 : // This is session-fatal.
518 0 : return NS_ERROR_FAILURE;
519 : }
520 0 : chainBit = mData[mOffset] & 0x80;
521 0 : accum += (mData[mOffset] & 0x7f) * factor;
522 0 : ++mOffset;
523 0 : factor = factor * 128;
524 : }
525 0 : return NS_OK;
526 : }
527 :
528 : static bool
529 0 : HasConnectionBasedAuth(const nsACString& headerValue)
530 : {
531 0 : nsCCharSeparatedTokenizer t(headerValue, '\n');
532 0 : while (t.hasMoreTokens()) {
533 0 : const nsDependentCSubstring& authMethod = t.nextToken();
534 0 : if (authMethod.LowerCaseEqualsLiteral("ntlm")) {
535 0 : return true;
536 : }
537 0 : if (authMethod.LowerCaseEqualsLiteral("negotiate")) {
538 0 : return true;
539 : }
540 : }
541 :
542 0 : return false;
543 : }
544 :
545 : nsresult
546 0 : Http2Decompressor::OutputHeader(const nsACString &name, const nsACString &value)
547 : {
548 : // exclusions
549 0 : if (!mIsPush &&
550 0 : (name.EqualsLiteral("connection") ||
551 0 : name.EqualsLiteral("host") ||
552 0 : name.EqualsLiteral("keep-alive") ||
553 0 : name.EqualsLiteral("proxy-connection") ||
554 0 : name.EqualsLiteral("te") ||
555 0 : name.EqualsLiteral("transfer-encoding") ||
556 0 : name.EqualsLiteral("upgrade") ||
557 0 : name.Equals(("accept-encoding")))) {
558 0 : nsCString toLog(name);
559 0 : LOG(("HTTP Decompressor illegal response header found, not gatewaying: %s",
560 : toLog.get()));
561 0 : return NS_OK;
562 : }
563 :
564 : // Look for upper case characters in the name.
565 0 : for (const char *cPtr = name.BeginReading();
566 0 : cPtr && cPtr < name.EndReading();
567 : ++cPtr) {
568 0 : if (*cPtr <= 'Z' && *cPtr >= 'A') {
569 0 : nsCString toLog(name);
570 0 : LOG(("HTTP Decompressor upper case response header found. [%s]\n",
571 : toLog.get()));
572 0 : return NS_ERROR_ILLEGAL_VALUE;
573 : }
574 : }
575 :
576 : // Look for CR OR LF in value - could be smuggling Sec 10.3
577 : // can map to space safely
578 0 : for (const char *cPtr = value.BeginReading();
579 0 : cPtr && cPtr < value.EndReading();
580 : ++cPtr) {
581 0 : if (*cPtr == '\r' || *cPtr== '\n') {
582 0 : char *wPtr = const_cast<char *>(cPtr);
583 0 : *wPtr = ' ';
584 : }
585 : }
586 :
587 : // Status comes first
588 0 : if (name.EqualsLiteral(":status")) {
589 0 : nsAutoCString status(NS_LITERAL_CSTRING("HTTP/2.0 "));
590 0 : status.Append(value);
591 0 : status.AppendLiteral("\r\n");
592 0 : mOutput->Insert(status, 0);
593 0 : mHeaderStatus = value;
594 0 : } else if (name.EqualsLiteral(":authority")) {
595 0 : mHeaderHost = value;
596 0 : } else if (name.EqualsLiteral(":scheme")) {
597 0 : mHeaderScheme = value;
598 0 : } else if (name.EqualsLiteral(":path")) {
599 0 : mHeaderPath = value;
600 0 : } else if (name.EqualsLiteral(":method")) {
601 0 : mHeaderMethod = value;
602 : }
603 :
604 : // http/2 transport level headers shouldn't be gatewayed into http/1
605 0 : bool isColonHeader = false;
606 0 : for (const char *cPtr = name.BeginReading();
607 0 : cPtr && cPtr < name.EndReading();
608 : ++cPtr) {
609 0 : if (*cPtr == ':') {
610 0 : isColonHeader = true;
611 0 : break;
612 0 : } else if (*cPtr != ' ' && *cPtr != '\t') {
613 0 : isColonHeader = false;
614 0 : break;
615 : }
616 : }
617 :
618 0 : if (isColonHeader) {
619 : // :status is the only pseudo-header field allowed in received HEADERS frames, PUSH_PROMISE allows the other pseudo-header fields
620 0 : if (!name.EqualsLiteral(":status") && !mIsPush) {
621 0 : LOG(("HTTP Decompressor found illegal response pseudo-header %s", name.BeginReading()));
622 0 : return NS_ERROR_ILLEGAL_VALUE;
623 : }
624 0 : if (mSeenNonColonHeader) {
625 0 : LOG(("HTTP Decompressor found illegal : header %s", name.BeginReading()));
626 0 : return NS_ERROR_ILLEGAL_VALUE;
627 : }
628 0 : LOG(("HTTP Decompressor not gatewaying %s into http/1",
629 : name.BeginReading()));
630 0 : return NS_OK;
631 : }
632 :
633 0 : LOG(("Http2Decompressor::OutputHeader %s %s", name.BeginReading(),
634 : value.BeginReading()));
635 0 : mSeenNonColonHeader = true;
636 0 : mOutput->Append(name);
637 0 : mOutput->AppendLiteral(": ");
638 0 : mOutput->Append(value);
639 0 : mOutput->AppendLiteral("\r\n");
640 :
641 : // Need to check if the server is going to try to speak connection-based auth
642 : // with us. If so, we need to kill this via h2, and dial back with http/1.1.
643 : // Technically speaking, the server should've just reset or goaway'd us with
644 : // HTTP_1_1_REQUIRED, but there are some busted servers out there, so we need
645 : // to check on our own to work around them.
646 0 : if (name.EqualsLiteral("www-authenticate") ||
647 0 : name.EqualsLiteral("proxy-authenticate")) {
648 0 : if (HasConnectionBasedAuth(value)) {
649 0 : LOG3(("Http2Decompressor %p connection-based auth found in %s", this,
650 : name.BeginReading()));
651 0 : return NS_ERROR_NET_RESET;
652 : }
653 : }
654 0 : return NS_OK;
655 : }
656 :
657 : nsresult
658 0 : Http2Decompressor::OutputHeader(uint32_t index)
659 : {
660 : // NWGH - make this < index
661 : // bounds check
662 0 : if (mHeaderTable.Length() <= index) {
663 0 : LOG(("Http2Decompressor::OutputHeader index too large %u", index));
664 : // This is session-fatal.
665 0 : return NS_ERROR_FAILURE;
666 : }
667 :
668 0 : return OutputHeader(mHeaderTable[index]->mName,
669 0 : mHeaderTable[index]->mValue);
670 : }
671 :
672 : nsresult
673 0 : Http2Decompressor::CopyHeaderString(uint32_t index, nsACString &name)
674 : {
675 : // NWGH - make this < index
676 : // bounds check
677 0 : if (mHeaderTable.Length() <= index) {
678 : // This is session-fatal.
679 0 : return NS_ERROR_FAILURE;
680 : }
681 :
682 0 : name = mHeaderTable[index]->mName;
683 0 : return NS_OK;
684 : }
685 :
686 : nsresult
687 0 : Http2Decompressor::CopyStringFromInput(uint32_t bytes, nsACString &val)
688 : {
689 0 : if (mOffset + bytes > mDataLen) {
690 : // This is session-fatal.
691 0 : return NS_ERROR_FAILURE;
692 : }
693 :
694 0 : val.Assign(reinterpret_cast<const char *>(mData) + mOffset, bytes);
695 0 : mOffset += bytes;
696 0 : return NS_OK;
697 : }
698 :
699 : nsresult
700 0 : Http2Decompressor::DecodeFinalHuffmanCharacter(const HuffmanIncomingTable *table,
701 : uint8_t &c, uint8_t &bitsLeft)
702 : {
703 0 : uint8_t mask = (1 << bitsLeft) - 1;
704 0 : uint8_t idx = mData[mOffset - 1] & mask;
705 0 : idx <<= (8 - bitsLeft);
706 : // Don't update bitsLeft yet, because we need to check that value against the
707 : // number of bits used by our encoding later on. We'll update when we are sure
708 : // how many bits we've actually used.
709 :
710 0 : if (table->IndexHasANextTable(idx)) {
711 : // Can't chain to another table when we're all out of bits in the encoding
712 0 : LOG(("DecodeFinalHuffmanCharacter trying to chain when we're out of bits"));
713 0 : return NS_ERROR_FAILURE;
714 : }
715 :
716 0 : const HuffmanIncomingEntry *entry = table->Entry(idx);
717 :
718 0 : if (bitsLeft < entry->mPrefixLen) {
719 : // We don't have enough bits to actually make a match, this is some sort of
720 : // invalid coding
721 0 : LOG(("DecodeFinalHuffmanCharacter does't have enough bits to match"));
722 0 : return NS_ERROR_FAILURE;
723 : }
724 :
725 : // This is a character!
726 0 : if (entry->mValue == 256) {
727 : // EOS
728 0 : LOG(("DecodeFinalHuffmanCharacter actually decoded an EOS"));
729 0 : return NS_ERROR_FAILURE;
730 : }
731 0 : c = static_cast<uint8_t>(entry->mValue & 0xFF);
732 0 : bitsLeft -= entry->mPrefixLen;
733 :
734 0 : return NS_OK;
735 : }
736 :
737 : uint8_t
738 0 : Http2Decompressor::ExtractByte(uint8_t bitsLeft, uint32_t &bytesConsumed)
739 : {
740 : uint8_t rv;
741 :
742 0 : if (bitsLeft) {
743 : // Need to extract bitsLeft bits from the previous byte, and 8 - bitsLeft
744 : // bits from the current byte
745 0 : uint8_t mask = (1 << bitsLeft) - 1;
746 0 : rv = (mData[mOffset - 1] & mask) << (8 - bitsLeft);
747 0 : rv |= (mData[mOffset] & ~mask) >> bitsLeft;
748 : } else {
749 0 : rv = mData[mOffset];
750 : }
751 :
752 : // We always update these here, under the assumption that all 8 bits we got
753 : // here will be used. These may be re-adjusted later in the case that we don't
754 : // use up all 8 bits of the byte.
755 0 : ++mOffset;
756 0 : ++bytesConsumed;
757 :
758 0 : return rv;
759 : }
760 :
761 : nsresult
762 0 : Http2Decompressor::DecodeHuffmanCharacter(const HuffmanIncomingTable *table,
763 : uint8_t &c, uint32_t &bytesConsumed,
764 : uint8_t &bitsLeft)
765 : {
766 0 : uint8_t idx = ExtractByte(bitsLeft, bytesConsumed);
767 :
768 0 : if (table->IndexHasANextTable(idx)) {
769 0 : if (bytesConsumed >= mDataLen) {
770 0 : if (!bitsLeft || (bytesConsumed > mDataLen)) {
771 : // TODO - does this get me into trouble in the new world?
772 : // No info left in input to try to consume, we're done
773 0 : LOG(("DecodeHuffmanCharacter all out of bits to consume, can't chain"));
774 0 : return NS_ERROR_FAILURE;
775 : }
776 :
777 : // We might get lucky here!
778 0 : return DecodeFinalHuffmanCharacter(table->NextTable(idx), c, bitsLeft);
779 : }
780 :
781 : // We're sorry, Mario, but your princess is in another castle
782 0 : return DecodeHuffmanCharacter(table->NextTable(idx), c, bytesConsumed, bitsLeft);
783 : }
784 :
785 0 : const HuffmanIncomingEntry *entry = table->Entry(idx);
786 0 : if (entry->mValue == 256) {
787 0 : LOG(("DecodeHuffmanCharacter found an actual EOS"));
788 0 : return NS_ERROR_FAILURE;
789 : }
790 0 : c = static_cast<uint8_t>(entry->mValue & 0xFF);
791 :
792 : // Need to adjust bitsLeft (and possibly other values) because we may not have
793 : // consumed all of the bits of the byte we extracted.
794 0 : if (entry->mPrefixLen <= bitsLeft) {
795 0 : bitsLeft -= entry->mPrefixLen;
796 0 : --mOffset;
797 0 : --bytesConsumed;
798 : } else {
799 0 : bitsLeft = 8 - (entry->mPrefixLen - bitsLeft);
800 : }
801 0 : MOZ_ASSERT(bitsLeft < 8);
802 :
803 0 : return NS_OK;
804 : }
805 :
806 : nsresult
807 0 : Http2Decompressor::CopyHuffmanStringFromInput(uint32_t bytes, nsACString &val)
808 : {
809 0 : if (mOffset + bytes > mDataLen) {
810 0 : LOG(("CopyHuffmanStringFromInput not enough data"));
811 0 : return NS_ERROR_FAILURE;
812 : }
813 :
814 0 : uint32_t bytesRead = 0;
815 0 : uint8_t bitsLeft = 0;
816 0 : nsAutoCString buf;
817 : nsresult rv;
818 : uint8_t c;
819 :
820 0 : while (bytesRead < bytes) {
821 0 : uint32_t bytesConsumed = 0;
822 : rv = DecodeHuffmanCharacter(&HuffmanIncomingRoot, c, bytesConsumed,
823 0 : bitsLeft);
824 0 : if (NS_FAILED(rv)) {
825 0 : LOG(("CopyHuffmanStringFromInput failed to decode a character"));
826 0 : return rv;
827 : }
828 :
829 0 : bytesRead += bytesConsumed;
830 0 : buf.Append(c);
831 : }
832 :
833 0 : if (bytesRead > bytes) {
834 0 : LOG(("CopyHuffmanStringFromInput read more bytes than was allowed!"));
835 0 : return NS_ERROR_FAILURE;
836 : }
837 :
838 0 : if (bitsLeft) {
839 : // The shortest valid code is 4 bits, so we know there can be at most one
840 : // character left that our loop didn't decode. Check to see if that's the
841 : // case, and if so, add it to our output.
842 0 : rv = DecodeFinalHuffmanCharacter(&HuffmanIncomingRoot, c, bitsLeft);
843 0 : if (NS_SUCCEEDED(rv)) {
844 0 : buf.Append(c);
845 : }
846 : }
847 :
848 0 : if (bitsLeft > 7) {
849 0 : LOG(("CopyHuffmanStringFromInput more than 7 bits of padding"));
850 0 : return NS_ERROR_FAILURE;
851 : }
852 :
853 0 : if (bitsLeft) {
854 : // Any bits left at this point must belong to the EOS symbol, so make sure
855 : // they make sense (ie, are all ones)
856 0 : uint8_t mask = (1 << bitsLeft) - 1;
857 0 : uint8_t bits = mData[mOffset - 1] & mask;
858 0 : if (bits != mask) {
859 0 : LOG(("CopyHuffmanStringFromInput ran out of data but found possible "
860 : "non-EOS symbol"));
861 0 : return NS_ERROR_FAILURE;
862 : }
863 : }
864 :
865 0 : val = buf;
866 0 : LOG(("CopyHuffmanStringFromInput decoded a full string!"));
867 0 : return NS_OK;
868 : }
869 :
870 : nsresult
871 0 : Http2Decompressor::DoIndexed()
872 : {
873 : // this starts with a 1 bit pattern
874 0 : MOZ_ASSERT(mData[mOffset] & 0x80);
875 :
876 : // This is a 7 bit prefix
877 :
878 : uint32_t index;
879 0 : nsresult rv = DecodeInteger(7, index);
880 0 : if (NS_FAILED(rv)) {
881 0 : return rv;
882 : }
883 :
884 0 : LOG(("HTTP decompressor indexed entry %u\n", index));
885 :
886 0 : if (index == 0) {
887 0 : return NS_ERROR_FAILURE;
888 : }
889 : // NWGH - remove this line, since we'll keep everything 1-indexed
890 0 : index--; // Internally, we 0-index everything, since this is, y'know, C++
891 :
892 0 : return OutputHeader(index);
893 : }
894 :
895 : nsresult
896 0 : Http2Decompressor::DoLiteralInternal(nsACString &name, nsACString &value,
897 : uint32_t namePrefixLen)
898 : {
899 : // guts of doliteralwithoutindex and doliteralwithincremental
900 0 : MOZ_ASSERT(((mData[mOffset] & 0xF0) == 0x00) || // withoutindex
901 : ((mData[mOffset] & 0xF0) == 0x10) || // neverindexed
902 : ((mData[mOffset] & 0xC0) == 0x40)); // withincremental
903 :
904 : // first let's get the name
905 : uint32_t index;
906 0 : nsresult rv = DecodeInteger(namePrefixLen, index);
907 0 : if (NS_FAILED(rv)) {
908 0 : return rv;
909 : }
910 :
911 : bool isHuffmanEncoded;
912 :
913 0 : if (!index) {
914 : // name is embedded as a literal
915 : uint32_t nameLen;
916 0 : isHuffmanEncoded = mData[mOffset] & (1 << 7);
917 0 : rv = DecodeInteger(7, nameLen);
918 0 : if (NS_SUCCEEDED(rv)) {
919 0 : if (isHuffmanEncoded) {
920 0 : rv = CopyHuffmanStringFromInput(nameLen, name);
921 : } else {
922 0 : rv = CopyStringFromInput(nameLen, name);
923 : }
924 : }
925 0 : LOG(("Http2Decompressor::DoLiteralInternal literal name %s",
926 : name.BeginReading()));
927 : } else {
928 : // NWGH - make this index, not index - 1
929 : // name is from headertable
930 0 : rv = CopyHeaderString(index - 1, name);
931 0 : LOG(("Http2Decompressor::DoLiteralInternal indexed name %d %s",
932 : index, name.BeginReading()));
933 : }
934 0 : if (NS_FAILED(rv)) {
935 0 : return rv;
936 : }
937 :
938 : // now the value
939 : uint32_t valueLen;
940 0 : isHuffmanEncoded = mData[mOffset] & (1 << 7);
941 0 : rv = DecodeInteger(7, valueLen);
942 0 : if (NS_SUCCEEDED(rv)) {
943 0 : if (isHuffmanEncoded) {
944 0 : rv = CopyHuffmanStringFromInput(valueLen, value);
945 : } else {
946 0 : rv = CopyStringFromInput(valueLen, value);
947 : }
948 : }
949 0 : if (NS_FAILED(rv)) {
950 0 : return rv;
951 : }
952 :
953 0 : int32_t newline = 0;
954 0 : while ((newline = value.FindChar('\n', newline)) != -1) {
955 0 : if (value[newline + 1] == ' ' || value[newline + 1] == '\t') {
956 0 : LOG(("Http2Decompressor::Disallowing folded header value %s",
957 : value.BeginReading()));
958 0 : return NS_ERROR_ILLEGAL_VALUE;
959 : }
960 : // Increment this to avoid always finding the same newline and looping
961 : // forever
962 0 : ++newline;
963 : }
964 :
965 0 : LOG(("Http2Decompressor::DoLiteralInternal value %s", value.BeginReading()));
966 0 : return NS_OK;
967 : }
968 :
969 : nsresult
970 0 : Http2Decompressor::DoLiteralWithoutIndex()
971 : {
972 : // this starts with 0000 bit pattern
973 0 : MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x00);
974 :
975 0 : nsAutoCString name, value;
976 0 : nsresult rv = DoLiteralInternal(name, value, 4);
977 :
978 0 : LOG(("HTTP decompressor literal without index %s %s\n",
979 : name.get(), value.get()));
980 :
981 0 : if (NS_SUCCEEDED(rv)) {
982 0 : rv = OutputHeader(name, value);
983 : }
984 0 : return rv;
985 : }
986 :
987 : nsresult
988 0 : Http2Decompressor::DoLiteralWithIncremental()
989 : {
990 : // this starts with 01 bit pattern
991 0 : MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x40);
992 :
993 0 : nsAutoCString name, value;
994 0 : nsresult rv = DoLiteralInternal(name, value, 6);
995 0 : if (NS_SUCCEEDED(rv)) {
996 0 : rv = OutputHeader(name, value);
997 : }
998 : // Let NET_RESET continue on so that we don't get out of sync, as it is just
999 : // used to kill the stream, not the session.
1000 0 : if (NS_FAILED(rv) && rv != NS_ERROR_NET_RESET) {
1001 0 : return rv;
1002 : }
1003 :
1004 0 : uint32_t room = nvPair(name, value).Size();
1005 0 : if (room > mMaxBuffer) {
1006 0 : ClearHeaderTable();
1007 0 : LOG(("HTTP decompressor literal with index not inserted due to size %u %s %s\n",
1008 : room, name.get(), value.get()));
1009 0 : LOG(("Decompressor state after ClearHeaderTable"));
1010 0 : DumpState();
1011 0 : return rv;
1012 : }
1013 :
1014 0 : MakeRoom(room, "decompressor");
1015 :
1016 : // Incremental Indexing implicitly adds a row to the header table.
1017 0 : mHeaderTable.AddElement(name, value);
1018 :
1019 0 : uint32_t currentSize = mHeaderTable.ByteCount();
1020 0 : if (currentSize > mPeakSize) {
1021 0 : mPeakSize = currentSize;
1022 : }
1023 :
1024 0 : uint32_t currentCount = mHeaderTable.VariableLength();
1025 0 : if (currentCount > mPeakCount) {
1026 0 : mPeakCount = currentCount;
1027 : }
1028 :
1029 0 : LOG(("HTTP decompressor literal with index 0 %s %s\n",
1030 : name.get(), value.get()));
1031 :
1032 0 : return rv;
1033 : }
1034 :
1035 : nsresult
1036 0 : Http2Decompressor::DoLiteralNeverIndexed()
1037 : {
1038 : // This starts with 0001 bit pattern
1039 0 : MOZ_ASSERT((mData[mOffset] & 0xF0) == 0x10);
1040 :
1041 0 : nsAutoCString name, value;
1042 0 : nsresult rv = DoLiteralInternal(name, value, 4);
1043 :
1044 0 : LOG(("HTTP decompressor literal never indexed %s %s\n",
1045 : name.get(), value.get()));
1046 :
1047 0 : if (NS_SUCCEEDED(rv)) {
1048 0 : rv = OutputHeader(name, value);
1049 : }
1050 0 : return rv;
1051 : }
1052 :
1053 : nsresult
1054 0 : Http2Decompressor::DoContextUpdate()
1055 : {
1056 : // This starts with 001 bit pattern
1057 0 : MOZ_ASSERT((mData[mOffset] & 0xE0) == 0x20);
1058 :
1059 : // Getting here means we have to adjust the max table size, because the
1060 : // compressor on the other end has signaled to us through HPACK (not H2)
1061 : // that it's using a size different from the currently-negotiated size.
1062 : // This change could either come about because we've sent a
1063 : // SETTINGS_HEADER_TABLE_SIZE, or because the encoder has decided that
1064 : // the current negotiated size doesn't fit its needs (for whatever reason)
1065 : // and so it needs to change it (either up to the max allowed by our SETTING,
1066 : // or down to some value below that)
1067 : uint32_t newMaxSize;
1068 0 : nsresult rv = DecodeInteger(5, newMaxSize);
1069 0 : LOG(("Http2Decompressor::DoContextUpdate new maximum size %u", newMaxSize));
1070 0 : if (NS_FAILED(rv)) {
1071 0 : return rv;
1072 : }
1073 :
1074 0 : if (newMaxSize > mMaxBufferSetting) {
1075 : // This is fatal to the session - peer is trying to use a table larger
1076 : // than we have made available.
1077 0 : return NS_ERROR_FAILURE;
1078 : }
1079 :
1080 0 : SetMaxBufferSizeInternal(newMaxSize);
1081 :
1082 0 : return NS_OK;
1083 : }
1084 :
1085 : /////////////////////////////////////////////////////////////////
1086 :
1087 : nsresult
1088 0 : Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput,
1089 : const nsACString &method, const nsACString &path,
1090 : const nsACString &host, const nsACString &scheme,
1091 : bool connectForm, nsACString &output)
1092 : {
1093 0 : mSetInitialMaxBufferSizeAllowed = false;
1094 0 : mOutput = &output;
1095 0 : output.SetCapacity(1024);
1096 0 : output.Truncate();
1097 0 : mParsedContentLength = -1;
1098 :
1099 : // first thing's first - context size updates (if necessary)
1100 0 : if (mBufferSizeChangeWaiting) {
1101 0 : if (mLowestBufferSizeWaiting < mMaxBufferSetting) {
1102 0 : EncodeTableSizeChange(mLowestBufferSizeWaiting);
1103 : }
1104 0 : EncodeTableSizeChange(mMaxBufferSetting);
1105 0 : mBufferSizeChangeWaiting = false;
1106 : }
1107 :
1108 : // colon headers first
1109 0 : if (!connectForm) {
1110 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
1111 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path), true, false);
1112 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false, false);
1113 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme), false, false);
1114 : } else {
1115 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method), false, false);
1116 0 : ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host), false, false);
1117 : }
1118 :
1119 : // now the non colon headers
1120 0 : const char *beginBuffer = nvInput.BeginReading();
1121 :
1122 : // This strips off the HTTP/1 method+path+version
1123 0 : int32_t crlfIndex = nvInput.Find("\r\n");
1124 : while (true) {
1125 0 : int32_t startIndex = crlfIndex + 2;
1126 :
1127 0 : crlfIndex = nvInput.Find("\r\n", false, startIndex);
1128 0 : if (crlfIndex == -1) {
1129 0 : break;
1130 : }
1131 :
1132 0 : int32_t colonIndex = nvInput.Find(":", false, startIndex,
1133 0 : crlfIndex - startIndex);
1134 0 : if (colonIndex == -1) {
1135 0 : break;
1136 : }
1137 :
1138 : nsDependentCSubstring name = Substring(beginBuffer + startIndex,
1139 0 : beginBuffer + colonIndex);
1140 : // all header names are lower case in http/2
1141 0 : ToLowerCase(name);
1142 :
1143 : // exclusions
1144 0 : if (name.EqualsLiteral("connection") ||
1145 0 : name.EqualsLiteral("host") ||
1146 0 : name.EqualsLiteral("keep-alive") ||
1147 0 : name.EqualsLiteral("proxy-connection") ||
1148 0 : name.EqualsLiteral("te") ||
1149 0 : name.EqualsLiteral("transfer-encoding") ||
1150 0 : name.EqualsLiteral("upgrade")) {
1151 0 : continue;
1152 : }
1153 :
1154 : // colon headers are for http/2 and this is http/1 input, so that
1155 : // is probably a smuggling attack of some kind
1156 0 : bool isColonHeader = false;
1157 0 : for (const char *cPtr = name.BeginReading();
1158 0 : cPtr && cPtr < name.EndReading();
1159 : ++cPtr) {
1160 0 : if (*cPtr == ':') {
1161 0 : isColonHeader = true;
1162 0 : break;
1163 0 : } else if (*cPtr != ' ' && *cPtr != '\t') {
1164 0 : isColonHeader = false;
1165 0 : break;
1166 : }
1167 : }
1168 0 : if(isColonHeader) {
1169 0 : continue;
1170 : }
1171 :
1172 0 : int32_t valueIndex = colonIndex + 1;
1173 :
1174 0 : while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
1175 0 : ++valueIndex;
1176 :
1177 : nsDependentCSubstring value = Substring(beginBuffer + valueIndex,
1178 0 : beginBuffer + crlfIndex);
1179 :
1180 0 : if (name.EqualsLiteral("content-length")) {
1181 : int64_t len;
1182 0 : nsCString tmp(value);
1183 0 : if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) {
1184 0 : mParsedContentLength = len;
1185 : }
1186 : }
1187 :
1188 0 : if (name.EqualsLiteral("cookie")) {
1189 : // cookie crumbling
1190 0 : bool haveMoreCookies = true;
1191 0 : int32_t nextCookie = valueIndex;
1192 0 : while (haveMoreCookies) {
1193 0 : int32_t semiSpaceIndex = nvInput.Find("; ", false, nextCookie,
1194 0 : crlfIndex - nextCookie);
1195 0 : if (semiSpaceIndex == -1) {
1196 0 : haveMoreCookies = false;
1197 0 : semiSpaceIndex = crlfIndex;
1198 : }
1199 : nsDependentCSubstring cookie = Substring(beginBuffer + nextCookie,
1200 0 : beginBuffer + semiSpaceIndex);
1201 : // cookies less than 20 bytes are not indexed
1202 0 : ProcessHeader(nvPair(name, cookie), false, cookie.Length() < 20);
1203 0 : nextCookie = semiSpaceIndex + 2;
1204 : }
1205 : } else {
1206 : // allow indexing of every non-cookie except authorization
1207 0 : ProcessHeader(nvPair(name, value), false,
1208 0 : name.EqualsLiteral("authorization"));
1209 : }
1210 0 : }
1211 :
1212 0 : mOutput = nullptr;
1213 0 : LOG(("Compressor state after EncodeHeaderBlock"));
1214 0 : DumpState();
1215 0 : return NS_OK;
1216 : }
1217 :
1218 : void
1219 0 : Http2Compressor::DoOutput(Http2Compressor::outputCode code,
1220 : const class nvPair *pair, uint32_t index)
1221 : {
1222 : // start Byte needs to be calculated from the offset after
1223 : // the opcode has been written out in case the output stream
1224 : // buffer gets resized/relocated
1225 0 : uint32_t offset = mOutput->Length();
1226 : uint8_t *startByte;
1227 :
1228 0 : switch (code) {
1229 : case kNeverIndexedLiteral:
1230 0 : LOG(("HTTP compressor %p neverindex literal with name reference %u %s %s\n",
1231 : this, index, pair->mName.get(), pair->mValue.get()));
1232 :
1233 : // In this case, the index will have already been adjusted to be 1-based
1234 : // instead of 0-based.
1235 0 : EncodeInteger(4, index); // 0001 4 bit prefix
1236 0 : startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
1237 0 : *startByte = (*startByte & 0x0f) | 0x10;
1238 :
1239 0 : if (!index) {
1240 0 : HuffmanAppend(pair->mName);
1241 : }
1242 :
1243 0 : HuffmanAppend(pair->mValue);
1244 0 : break;
1245 :
1246 : case kPlainLiteral:
1247 0 : LOG(("HTTP compressor %p noindex literal with name reference %u %s %s\n",
1248 : this, index, pair->mName.get(), pair->mValue.get()));
1249 :
1250 : // In this case, the index will have already been adjusted to be 1-based
1251 : // instead of 0-based.
1252 0 : EncodeInteger(4, index); // 0000 4 bit prefix
1253 0 : startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
1254 0 : *startByte = *startByte & 0x0f;
1255 :
1256 0 : if (!index) {
1257 0 : HuffmanAppend(pair->mName);
1258 : }
1259 :
1260 0 : HuffmanAppend(pair->mValue);
1261 0 : break;
1262 :
1263 : case kIndexedLiteral:
1264 0 : LOG(("HTTP compressor %p literal with name reference %u %s %s\n",
1265 : this, index, pair->mName.get(), pair->mValue.get()));
1266 :
1267 : // In this case, the index will have already been adjusted to be 1-based
1268 : // instead of 0-based.
1269 0 : EncodeInteger(6, index); // 01 2 bit prefix
1270 0 : startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
1271 0 : *startByte = (*startByte & 0x3f) | 0x40;
1272 :
1273 0 : if (!index) {
1274 0 : HuffmanAppend(pair->mName);
1275 : }
1276 :
1277 0 : HuffmanAppend(pair->mValue);
1278 0 : break;
1279 :
1280 : case kIndex:
1281 0 : LOG(("HTTP compressor %p index %u %s %s\n",
1282 : this, index, pair->mName.get(), pair->mValue.get()));
1283 : // NWGH - make this plain old index instead of index + 1
1284 : // In this case, we are passed the raw 0-based C index, and need to
1285 : // increment to make it 1-based and comply with the spec
1286 0 : EncodeInteger(7, index + 1);
1287 0 : startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
1288 0 : *startByte = *startByte | 0x80; // 1 1 bit prefix
1289 0 : break;
1290 :
1291 : }
1292 0 : }
1293 :
1294 : // writes the encoded integer onto the output
1295 : void
1296 0 : Http2Compressor::EncodeInteger(uint32_t prefixLen, uint32_t val)
1297 : {
1298 0 : uint32_t mask = (1 << prefixLen) - 1;
1299 : uint8_t tmp;
1300 :
1301 0 : if (val < mask) {
1302 : // 1 byte encoding!
1303 0 : tmp = val;
1304 0 : mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
1305 0 : return;
1306 : }
1307 :
1308 0 : if (mask) {
1309 0 : val -= mask;
1310 0 : tmp = mask;
1311 0 : mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
1312 : }
1313 :
1314 : uint32_t q, r;
1315 0 : do {
1316 0 : q = val / 128;
1317 0 : r = val % 128;
1318 0 : tmp = r;
1319 0 : if (q) {
1320 0 : tmp |= 0x80; // chain bit
1321 : }
1322 0 : val = q;
1323 0 : mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
1324 0 : } while (q);
1325 : }
1326 :
1327 : void
1328 0 : Http2Compressor::HuffmanAppend(const nsCString &value)
1329 : {
1330 0 : nsAutoCString buf;
1331 0 : uint8_t bitsLeft = 8;
1332 0 : uint32_t length = value.Length();
1333 : uint32_t offset;
1334 : uint8_t *startByte;
1335 :
1336 0 : for (uint32_t i = 0; i < length; ++i) {
1337 0 : uint8_t idx = static_cast<uint8_t>(value[i]);
1338 0 : uint8_t huffLength = HuffmanOutgoing[idx].mLength;
1339 0 : uint32_t huffValue = HuffmanOutgoing[idx].mValue;
1340 :
1341 0 : if (bitsLeft < 8) {
1342 : // Fill in the least significant <bitsLeft> bits of the previous byte
1343 : // first
1344 : uint32_t val;
1345 0 : if (huffLength >= bitsLeft) {
1346 0 : val = huffValue & ~((1 << (huffLength - bitsLeft)) - 1);
1347 0 : val >>= (huffLength - bitsLeft);
1348 : } else {
1349 0 : val = huffValue << (bitsLeft - huffLength);
1350 : }
1351 0 : val &= ((1 << bitsLeft) - 1);
1352 0 : offset = buf.Length() - 1;
1353 0 : startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
1354 0 : *startByte = *startByte | static_cast<uint8_t>(val & 0xFF);
1355 0 : if (huffLength >= bitsLeft) {
1356 0 : huffLength -= bitsLeft;
1357 0 : bitsLeft = 8;
1358 : } else {
1359 0 : bitsLeft -= huffLength;
1360 0 : huffLength = 0;
1361 : }
1362 : }
1363 :
1364 0 : while (huffLength >= 8) {
1365 0 : uint32_t mask = ~((1 << (huffLength - 8)) - 1);
1366 0 : uint8_t val = ((huffValue & mask) >> (huffLength - 8)) & 0xFF;
1367 0 : buf.Append(reinterpret_cast<char *>(&val), 1);
1368 0 : huffLength -= 8;
1369 : }
1370 :
1371 0 : if (huffLength) {
1372 : // Fill in the most significant <huffLength> bits of the next byte
1373 0 : bitsLeft = 8 - huffLength;
1374 0 : uint8_t val = (huffValue & ((1 << huffLength) - 1)) << bitsLeft;
1375 0 : buf.Append(reinterpret_cast<char *>(&val), 1);
1376 : }
1377 : }
1378 :
1379 0 : if (bitsLeft != 8) {
1380 : // Pad the last <bitsLeft> bits with ones, which corresponds to the EOS
1381 : // encoding
1382 0 : uint8_t val = (1 << bitsLeft) - 1;
1383 0 : offset = buf.Length() - 1;
1384 0 : startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
1385 0 : *startByte = *startByte | val;
1386 : }
1387 :
1388 : // Now we know how long our encoded string is, we can fill in our length
1389 0 : uint32_t bufLength = buf.Length();
1390 0 : offset = mOutput->Length();
1391 0 : EncodeInteger(7, bufLength);
1392 0 : startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
1393 0 : *startByte = *startByte | 0x80;
1394 :
1395 : // Finally, we can add our REAL data!
1396 0 : mOutput->Append(buf);
1397 0 : LOG(("Http2Compressor::HuffmanAppend %p encoded %d byte original on %d "
1398 : "bytes.\n", this, length, bufLength));
1399 0 : }
1400 :
1401 : void
1402 0 : Http2Compressor::ProcessHeader(const nvPair inputPair, bool noLocalIndex,
1403 : bool neverIndex)
1404 : {
1405 0 : uint32_t newSize = inputPair.Size();
1406 0 : uint32_t headerTableSize = mHeaderTable.Length();
1407 0 : uint32_t matchedIndex = 0u;
1408 0 : uint32_t nameReference = 0u;
1409 0 : bool match = false;
1410 :
1411 0 : LOG(("Http2Compressor::ProcessHeader %s %s", inputPair.mName.get(),
1412 : inputPair.mValue.get()));
1413 :
1414 : // NWGH - make this index = 1; index <= headerTableSize; ++index
1415 0 : for (uint32_t index = 0; index < headerTableSize; ++index) {
1416 0 : if (mHeaderTable[index]->mName.Equals(inputPair.mName)) {
1417 : // NWGH - make this nameReference = index
1418 0 : nameReference = index + 1;
1419 0 : if (mHeaderTable[index]->mValue.Equals(inputPair.mValue)) {
1420 0 : match = true;
1421 0 : matchedIndex = index;
1422 0 : break;
1423 : }
1424 : }
1425 : }
1426 :
1427 : // We need to emit a new literal
1428 0 : if (!match || noLocalIndex || neverIndex) {
1429 0 : if (neverIndex) {
1430 0 : DoOutput(kNeverIndexedLiteral, &inputPair, nameReference);
1431 0 : LOG(("Compressor state after literal never index"));
1432 0 : DumpState();
1433 0 : return;
1434 : }
1435 :
1436 0 : if (noLocalIndex || (newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
1437 0 : DoOutput(kPlainLiteral, &inputPair, nameReference);
1438 0 : LOG(("Compressor state after literal without index"));
1439 0 : DumpState();
1440 0 : return;
1441 : }
1442 :
1443 : // make sure to makeroom() first so that any implied items
1444 : // get preserved.
1445 0 : MakeRoom(newSize, "compressor");
1446 0 : DoOutput(kIndexedLiteral, &inputPair, nameReference);
1447 :
1448 0 : mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
1449 0 : LOG(("HTTP compressor %p new literal placed at index 0\n",
1450 : this));
1451 0 : LOG(("Compressor state after literal with index"));
1452 0 : DumpState();
1453 0 : return;
1454 : }
1455 :
1456 : // emit an index
1457 0 : DoOutput(kIndex, &inputPair, matchedIndex);
1458 :
1459 0 : LOG(("Compressor state after index"));
1460 0 : DumpState();
1461 0 : return;
1462 : }
1463 :
1464 : void
1465 0 : Http2Compressor::EncodeTableSizeChange(uint32_t newMaxSize)
1466 : {
1467 0 : uint32_t offset = mOutput->Length();
1468 0 : EncodeInteger(5, newMaxSize);
1469 0 : uint8_t *startByte = reinterpret_cast<uint8_t *>(mOutput->BeginWriting()) + offset;
1470 0 : *startByte = *startByte | 0x20;
1471 0 : }
1472 :
1473 : void
1474 0 : Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize)
1475 : {
1476 0 : mMaxBufferSetting = maxBufferSize;
1477 0 : SetMaxBufferSizeInternal(maxBufferSize);
1478 0 : if (!mBufferSizeChangeWaiting) {
1479 0 : mBufferSizeChangeWaiting = true;
1480 0 : mLowestBufferSizeWaiting = maxBufferSize;
1481 0 : } else if (maxBufferSize < mLowestBufferSizeWaiting) {
1482 0 : mLowestBufferSizeWaiting = maxBufferSize;
1483 : }
1484 0 : }
1485 :
1486 : } // namespace net
1487 : } // namespace mozilla
|