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