Coverage Report

Created: 2018-09-25 14:53

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