tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Http2Compression.cpp (44964B)


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