netwerk/protocol/http/Http2Compression.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set sw=2 ts=8 et tw=80 : */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // HttpLog.h should generally be included first
michael@0 8 #include "HttpLog.h"
michael@0 9
michael@0 10 // Log on level :5, instead of default :4.
michael@0 11 #undef LOG
michael@0 12 #define LOG(args) LOG5(args)
michael@0 13 #undef LOG_ENABLED
michael@0 14 #define LOG_ENABLED() LOG5_ENABLED()
michael@0 15
michael@0 16 #include "Http2Compression.h"
michael@0 17 #include "Http2HuffmanIncoming.h"
michael@0 18 #include "Http2HuffmanOutgoing.h"
michael@0 19
michael@0 20 extern PRThread *gSocketThread;
michael@0 21
michael@0 22 namespace mozilla {
michael@0 23 namespace net {
michael@0 24
michael@0 25 static nsDeque *gStaticHeaders = nullptr;
michael@0 26
michael@0 27 void
michael@0 28 Http2CompressionCleanup()
michael@0 29 {
michael@0 30 // this happens after the socket thread has been destroyed
michael@0 31 delete gStaticHeaders;
michael@0 32 gStaticHeaders = nullptr;
michael@0 33 }
michael@0 34
michael@0 35 static void
michael@0 36 AddStaticElement(const nsCString &name, const nsCString &value)
michael@0 37 {
michael@0 38 nvPair *pair = new nvPair(name, value);
michael@0 39 gStaticHeaders->Push(pair);
michael@0 40 }
michael@0 41
michael@0 42 static void
michael@0 43 AddStaticElement(const nsCString &name)
michael@0 44 {
michael@0 45 AddStaticElement(name, EmptyCString());
michael@0 46 }
michael@0 47
michael@0 48 static void
michael@0 49 InitializeStaticHeaders()
michael@0 50 {
michael@0 51 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 52 if (!gStaticHeaders) {
michael@0 53 gStaticHeaders = new nsDeque();
michael@0 54 AddStaticElement(NS_LITERAL_CSTRING(":authority"));
michael@0 55 AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("GET"));
michael@0 56 AddStaticElement(NS_LITERAL_CSTRING(":method"), NS_LITERAL_CSTRING("POST"));
michael@0 57 AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/"));
michael@0 58 AddStaticElement(NS_LITERAL_CSTRING(":path"), NS_LITERAL_CSTRING("/index.html"));
michael@0 59 AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("http"));
michael@0 60 AddStaticElement(NS_LITERAL_CSTRING(":scheme"), NS_LITERAL_CSTRING("https"));
michael@0 61 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("200"));
michael@0 62 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("500"));
michael@0 63 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("404"));
michael@0 64 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("403"));
michael@0 65 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("400"));
michael@0 66 AddStaticElement(NS_LITERAL_CSTRING(":status"), NS_LITERAL_CSTRING("401"));
michael@0 67 AddStaticElement(NS_LITERAL_CSTRING("accept-charset"));
michael@0 68 AddStaticElement(NS_LITERAL_CSTRING("accept-encoding"));
michael@0 69 AddStaticElement(NS_LITERAL_CSTRING("accept-language"));
michael@0 70 AddStaticElement(NS_LITERAL_CSTRING("accept-ranges"));
michael@0 71 AddStaticElement(NS_LITERAL_CSTRING("accept"));
michael@0 72 AddStaticElement(NS_LITERAL_CSTRING("access-control-allow-origin"));
michael@0 73 AddStaticElement(NS_LITERAL_CSTRING("age"));
michael@0 74 AddStaticElement(NS_LITERAL_CSTRING("allow"));
michael@0 75 AddStaticElement(NS_LITERAL_CSTRING("authorization"));
michael@0 76 AddStaticElement(NS_LITERAL_CSTRING("cache-control"));
michael@0 77 AddStaticElement(NS_LITERAL_CSTRING("content-disposition"));
michael@0 78 AddStaticElement(NS_LITERAL_CSTRING("content-encoding"));
michael@0 79 AddStaticElement(NS_LITERAL_CSTRING("content-language"));
michael@0 80 AddStaticElement(NS_LITERAL_CSTRING("content-length"));
michael@0 81 AddStaticElement(NS_LITERAL_CSTRING("content-location"));
michael@0 82 AddStaticElement(NS_LITERAL_CSTRING("content-range"));
michael@0 83 AddStaticElement(NS_LITERAL_CSTRING("content-type"));
michael@0 84 AddStaticElement(NS_LITERAL_CSTRING("cookie"));
michael@0 85 AddStaticElement(NS_LITERAL_CSTRING("date"));
michael@0 86 AddStaticElement(NS_LITERAL_CSTRING("etag"));
michael@0 87 AddStaticElement(NS_LITERAL_CSTRING("expect"));
michael@0 88 AddStaticElement(NS_LITERAL_CSTRING("expires"));
michael@0 89 AddStaticElement(NS_LITERAL_CSTRING("from"));
michael@0 90 AddStaticElement(NS_LITERAL_CSTRING("host"));
michael@0 91 AddStaticElement(NS_LITERAL_CSTRING("if-match"));
michael@0 92 AddStaticElement(NS_LITERAL_CSTRING("if-modified-since"));
michael@0 93 AddStaticElement(NS_LITERAL_CSTRING("if-none-match"));
michael@0 94 AddStaticElement(NS_LITERAL_CSTRING("if-range"));
michael@0 95 AddStaticElement(NS_LITERAL_CSTRING("if-unmodified-since"));
michael@0 96 AddStaticElement(NS_LITERAL_CSTRING("last-modified"));
michael@0 97 AddStaticElement(NS_LITERAL_CSTRING("link"));
michael@0 98 AddStaticElement(NS_LITERAL_CSTRING("location"));
michael@0 99 AddStaticElement(NS_LITERAL_CSTRING("max-forwards"));
michael@0 100 AddStaticElement(NS_LITERAL_CSTRING("proxy-authenticate"));
michael@0 101 AddStaticElement(NS_LITERAL_CSTRING("proxy-authorization"));
michael@0 102 AddStaticElement(NS_LITERAL_CSTRING("range"));
michael@0 103 AddStaticElement(NS_LITERAL_CSTRING("referer"));
michael@0 104 AddStaticElement(NS_LITERAL_CSTRING("refresh"));
michael@0 105 AddStaticElement(NS_LITERAL_CSTRING("retry-after"));
michael@0 106 AddStaticElement(NS_LITERAL_CSTRING("server"));
michael@0 107 AddStaticElement(NS_LITERAL_CSTRING("set-cookie"));
michael@0 108 AddStaticElement(NS_LITERAL_CSTRING("strict-transport-security"));
michael@0 109 AddStaticElement(NS_LITERAL_CSTRING("transfer-encoding"));
michael@0 110 AddStaticElement(NS_LITERAL_CSTRING("user-agent"));
michael@0 111 AddStaticElement(NS_LITERAL_CSTRING("vary"));
michael@0 112 AddStaticElement(NS_LITERAL_CSTRING("via"));
michael@0 113 AddStaticElement(NS_LITERAL_CSTRING("www-authenticate"));
michael@0 114 }
michael@0 115 }
michael@0 116
michael@0 117 nvFIFO::nvFIFO()
michael@0 118 : mByteCount(0)
michael@0 119 , mTable()
michael@0 120 {
michael@0 121 InitializeStaticHeaders();
michael@0 122 }
michael@0 123
michael@0 124 nvFIFO::~nvFIFO()
michael@0 125 {
michael@0 126 Clear();
michael@0 127 }
michael@0 128
michael@0 129 void
michael@0 130 nvFIFO::AddElement(const nsCString &name, const nsCString &value)
michael@0 131 {
michael@0 132 mByteCount += name.Length() + value.Length() + 32;
michael@0 133 nvPair *pair = new nvPair(name, value);
michael@0 134 mTable.PushFront(pair);
michael@0 135 }
michael@0 136
michael@0 137 void
michael@0 138 nvFIFO::AddElement(const nsCString &name)
michael@0 139 {
michael@0 140 AddElement(name, EmptyCString());
michael@0 141 }
michael@0 142
michael@0 143 void
michael@0 144 nvFIFO::RemoveElement()
michael@0 145 {
michael@0 146 nvPair *pair = static_cast<nvPair *>(mTable.Pop());
michael@0 147 if (pair) {
michael@0 148 mByteCount -= pair->Size();
michael@0 149 delete pair;
michael@0 150 }
michael@0 151 }
michael@0 152
michael@0 153 uint32_t
michael@0 154 nvFIFO::ByteCount() const
michael@0 155 {
michael@0 156 return mByteCount;
michael@0 157 }
michael@0 158
michael@0 159 uint32_t
michael@0 160 nvFIFO::Length() const
michael@0 161 {
michael@0 162 return mTable.GetSize() + gStaticHeaders->GetSize();
michael@0 163 }
michael@0 164
michael@0 165 uint32_t
michael@0 166 nvFIFO::VariableLength() const
michael@0 167 {
michael@0 168 return mTable.GetSize();
michael@0 169 }
michael@0 170
michael@0 171 void
michael@0 172 nvFIFO::Clear()
michael@0 173 {
michael@0 174 mByteCount = 0;
michael@0 175 while (mTable.GetSize())
michael@0 176 delete static_cast<nvPair *>(mTable.Pop());
michael@0 177 }
michael@0 178
michael@0 179 const nvPair *
michael@0 180 nvFIFO::operator[] (int32_t index) const
michael@0 181 {
michael@0 182 if (index >= (mTable.GetSize() + gStaticHeaders->GetSize())) {
michael@0 183 MOZ_ASSERT(false);
michael@0 184 NS_WARNING("nvFIFO Table Out of Range");
michael@0 185 return nullptr;
michael@0 186 }
michael@0 187 if (index >= mTable.GetSize()) {
michael@0 188 return static_cast<nvPair *>(gStaticHeaders->ObjectAt(index - mTable.GetSize()));
michael@0 189 }
michael@0 190 return static_cast<nvPair *>(mTable.ObjectAt(index));
michael@0 191 }
michael@0 192
michael@0 193 Http2BaseCompressor::Http2BaseCompressor()
michael@0 194 : mOutput(nullptr)
michael@0 195 , mMaxBuffer(kDefaultMaxBuffer)
michael@0 196 {
michael@0 197 }
michael@0 198
michael@0 199 void
michael@0 200 Http2BaseCompressor::ClearHeaderTable()
michael@0 201 {
michael@0 202 uint32_t dynamicCount = mHeaderTable.VariableLength();
michael@0 203 mHeaderTable.Clear();
michael@0 204
michael@0 205 for (int32_t i = mReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 206 if (mReferenceSet[i] < dynamicCount) {
michael@0 207 mReferenceSet.RemoveElementAt(i);
michael@0 208 } else {
michael@0 209 mReferenceSet[i] -= dynamicCount;
michael@0 210 }
michael@0 211 }
michael@0 212
michael@0 213 for (int32_t i = mAlternateReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 214 if (mAlternateReferenceSet[i] < dynamicCount) {
michael@0 215 mAlternateReferenceSet.RemoveElementAt(i);
michael@0 216 } else {
michael@0 217 mAlternateReferenceSet[i] -= dynamicCount;
michael@0 218 }
michael@0 219 }
michael@0 220 }
michael@0 221
michael@0 222 void
michael@0 223 Http2BaseCompressor::UpdateReferenceSet(int32_t delta)
michael@0 224 {
michael@0 225 if (!delta)
michael@0 226 return;
michael@0 227
michael@0 228 uint32_t headerTableSize = mHeaderTable.VariableLength();
michael@0 229 uint32_t oldHeaderTableSize = headerTableSize + delta;
michael@0 230
michael@0 231 for (int32_t i = mReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 232 uint32_t indexRef = mReferenceSet[i];
michael@0 233 if (indexRef >= headerTableSize) {
michael@0 234 if (indexRef < oldHeaderTableSize) {
michael@0 235 // This one got dropped
michael@0 236 LOG3(("HTTP base compressor reference to index %u removed.\n",
michael@0 237 indexRef));
michael@0 238 mReferenceSet.RemoveElementAt(i);
michael@0 239 } else {
michael@0 240 // This pointed to the static table, need to adjust
michael@0 241 uint32_t newRef = indexRef - delta;
michael@0 242 LOG3(("HTTP base compressor reference to index %u changed to %d (%s)\n",
michael@0 243 mReferenceSet[i], newRef, mHeaderTable[newRef]->mName.get()));
michael@0 244 mReferenceSet[i] = newRef;
michael@0 245 }
michael@0 246 }
michael@0 247 }
michael@0 248
michael@0 249 for (int32_t i = mAlternateReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 250 uint32_t indexRef = mAlternateReferenceSet[i];
michael@0 251 if (indexRef >= headerTableSize) {
michael@0 252 if (indexRef < oldHeaderTableSize) {
michael@0 253 // This one got dropped
michael@0 254 LOG3(("HTTP base compressor new reference to index %u removed.\n",
michael@0 255 indexRef));
michael@0 256 mAlternateReferenceSet.RemoveElementAt(i);
michael@0 257 } else {
michael@0 258 // This pointed to the static table, need to adjust
michael@0 259 uint32_t newRef = indexRef - delta;
michael@0 260 LOG3(("HTTP base compressor new reference to index %u changed to %d (%s)\n",
michael@0 261 mAlternateReferenceSet[i], newRef, mHeaderTable[newRef]->mName.get()));
michael@0 262 mAlternateReferenceSet[i] = newRef;
michael@0 263 }
michael@0 264 }
michael@0 265 }
michael@0 266 }
michael@0 267
michael@0 268 void
michael@0 269 Http2BaseCompressor::IncrementReferenceSetIndices()
michael@0 270 {
michael@0 271 for (int32_t i = mReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 272 mReferenceSet[i] = mReferenceSet[i] + 1;
michael@0 273 }
michael@0 274
michael@0 275 for (int32_t i = mAlternateReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 276 mAlternateReferenceSet[i] = mAlternateReferenceSet[i] + 1;
michael@0 277 }
michael@0 278 }
michael@0 279
michael@0 280 nsresult
michael@0 281 Http2Decompressor::DecodeHeaderBlock(const uint8_t *data, uint32_t datalen,
michael@0 282 nsACString &output)
michael@0 283 {
michael@0 284 mAlternateReferenceSet.Clear();
michael@0 285 mOffset = 0;
michael@0 286 mData = data;
michael@0 287 mDataLen = datalen;
michael@0 288 mOutput = &output;
michael@0 289 mOutput->Truncate();
michael@0 290 mHeaderStatus.Truncate();
michael@0 291 mHeaderHost.Truncate();
michael@0 292 mHeaderScheme.Truncate();
michael@0 293 mHeaderPath.Truncate();
michael@0 294 mHeaderMethod.Truncate();
michael@0 295
michael@0 296 nsresult rv = NS_OK;
michael@0 297 while (NS_SUCCEEDED(rv) && (mOffset < datalen)) {
michael@0 298 if (mData[mOffset] & 0x80) {
michael@0 299 rv = DoIndexed();
michael@0 300 } else if (mData[mOffset] & 0x40) {
michael@0 301 rv = DoLiteralWithoutIndex();
michael@0 302 } else {
michael@0 303 rv = DoLiteralWithIncremental();
michael@0 304 }
michael@0 305 }
michael@0 306
michael@0 307 // after processing the input the decompressor comapres the alternate
michael@0 308 // set to the inherited reference set and generates headers for
michael@0 309 // anything implicit in reference - alternate.
michael@0 310
michael@0 311 uint32_t setLen = mReferenceSet.Length();
michael@0 312 for (uint32_t index = 0; index < setLen; ++index) {
michael@0 313 if (!mAlternateReferenceSet.Contains(mReferenceSet[index])) {
michael@0 314 LOG3(("HTTP decompressor carryover in reference set with index %u %s %s\n",
michael@0 315 mReferenceSet[index],
michael@0 316 mHeaderTable[mReferenceSet[index]]->mName.get(),
michael@0 317 mHeaderTable[mReferenceSet[index]]->mValue.get()));
michael@0 318 OutputHeader(mReferenceSet[index]);
michael@0 319 }
michael@0 320 }
michael@0 321
michael@0 322 mAlternateReferenceSet.Clear();
michael@0 323 return rv;
michael@0 324 }
michael@0 325
michael@0 326 nsresult
michael@0 327 Http2Decompressor::DecodeInteger(uint32_t prefixLen, uint32_t &accum)
michael@0 328 {
michael@0 329 accum = 0;
michael@0 330
michael@0 331 if (prefixLen) {
michael@0 332 uint32_t mask = (1 << prefixLen) - 1;
michael@0 333
michael@0 334 accum = mData[mOffset] & mask;
michael@0 335 ++mOffset;
michael@0 336
michael@0 337 if (accum != mask) {
michael@0 338 // the simple case for small values
michael@0 339 return NS_OK;
michael@0 340 }
michael@0 341 }
michael@0 342
michael@0 343 uint32_t factor = 1; // 128 ^ 0
michael@0 344
michael@0 345 // we need a series of bytes. The high bit signifies if we need another one.
michael@0 346 // The first one is a a factor of 128 ^ 0, the next 128 ^1, the next 128 ^2, ..
michael@0 347
michael@0 348 if (mOffset >= mDataLen) {
michael@0 349 NS_WARNING("Ran out of data to decode integer");
michael@0 350 return NS_ERROR_ILLEGAL_VALUE;
michael@0 351 }
michael@0 352 bool chainBit = mData[mOffset] & 0x80;
michael@0 353 accum += (mData[mOffset] & 0x7f) * factor;
michael@0 354
michael@0 355 ++mOffset;
michael@0 356 factor = factor * 128;
michael@0 357
michael@0 358 while (chainBit) {
michael@0 359 // really big offsets are just trawling for overflows
michael@0 360 if (accum >= 0x800000) {
michael@0 361 NS_WARNING("Decoding integer >= 0x800000");
michael@0 362 return NS_ERROR_ILLEGAL_VALUE;
michael@0 363 }
michael@0 364
michael@0 365 if (mOffset >= mDataLen) {
michael@0 366 NS_WARNING("Ran out of data to decode integer");
michael@0 367 return NS_ERROR_ILLEGAL_VALUE;
michael@0 368 }
michael@0 369 chainBit = mData[mOffset] & 0x80;
michael@0 370 accum += (mData[mOffset] & 0x7f) * factor;
michael@0 371 ++mOffset;
michael@0 372 factor = factor * 128;
michael@0 373 }
michael@0 374 return NS_OK;
michael@0 375 }
michael@0 376
michael@0 377 nsresult
michael@0 378 Http2Decompressor::OutputHeader(const nsACString &name, const nsACString &value)
michael@0 379 {
michael@0 380 // exclusions
michael@0 381 if (name.Equals(NS_LITERAL_CSTRING("connection")) ||
michael@0 382 name.Equals(NS_LITERAL_CSTRING("host")) ||
michael@0 383 name.Equals(NS_LITERAL_CSTRING("keep-alive")) ||
michael@0 384 name.Equals(NS_LITERAL_CSTRING("proxy-connection")) ||
michael@0 385 name.Equals(NS_LITERAL_CSTRING("te")) ||
michael@0 386 name.Equals(NS_LITERAL_CSTRING("transfer-encoding")) ||
michael@0 387 name.Equals(NS_LITERAL_CSTRING("upgrade")) ||
michael@0 388 name.Equals(("accept-encoding"))) {
michael@0 389 nsCString toLog(name);
michael@0 390 LOG3(("HTTP Decompressor illegal response header found : %s",
michael@0 391 toLog.get()));
michael@0 392 return NS_ERROR_ILLEGAL_VALUE;
michael@0 393 }
michael@0 394
michael@0 395 // Look for upper case characters in the name.
michael@0 396 for (const char *cPtr = name.BeginReading();
michael@0 397 cPtr && cPtr < name.EndReading();
michael@0 398 ++cPtr) {
michael@0 399 if (*cPtr <= 'Z' && *cPtr >= 'A') {
michael@0 400 nsCString toLog(name);
michael@0 401 LOG3(("HTTP Decompressor upper case response header found. [%s]\n",
michael@0 402 toLog.get()));
michael@0 403 return NS_ERROR_ILLEGAL_VALUE;
michael@0 404 }
michael@0 405 }
michael@0 406
michael@0 407 // Look for CR OR LF in value - could be smuggling Sec 10.3
michael@0 408 // can map to space safely
michael@0 409 for (const char *cPtr = value.BeginReading();
michael@0 410 cPtr && cPtr < value.EndReading();
michael@0 411 ++cPtr) {
michael@0 412 if (*cPtr == '\r' || *cPtr== '\n') {
michael@0 413 char *wPtr = const_cast<char *>(cPtr);
michael@0 414 *wPtr = ' ';
michael@0 415 }
michael@0 416 }
michael@0 417
michael@0 418 // Status comes first
michael@0 419 if (name.Equals(NS_LITERAL_CSTRING(":status"))) {
michael@0 420 nsAutoCString status(NS_LITERAL_CSTRING("HTTP/2.0 "));
michael@0 421 status.Append(value);
michael@0 422 status.Append(NS_LITERAL_CSTRING("\r\n"));
michael@0 423 mOutput->Insert(status, 0);
michael@0 424 mHeaderStatus = value;
michael@0 425 } else if (name.Equals(NS_LITERAL_CSTRING(":authority"))) {
michael@0 426 mHeaderHost = value;
michael@0 427 } else if (name.Equals(NS_LITERAL_CSTRING(":scheme"))) {
michael@0 428 mHeaderScheme = value;
michael@0 429 } else if (name.Equals(NS_LITERAL_CSTRING(":path"))) {
michael@0 430 mHeaderPath = value;
michael@0 431 } else if (name.Equals(NS_LITERAL_CSTRING(":method"))) {
michael@0 432 mHeaderMethod = value;
michael@0 433 }
michael@0 434
michael@0 435 // http/2 transport level headers shouldn't be gatewayed into http/1
michael@0 436 if(*(name.BeginReading()) == ':') {
michael@0 437 LOG3(("HTTP Decompressor not gatewaying %s into http/1",
michael@0 438 name.BeginReading()));
michael@0 439 return NS_OK;
michael@0 440 }
michael@0 441
michael@0 442 mOutput->Append(name);
michael@0 443 mOutput->Append(NS_LITERAL_CSTRING(": "));
michael@0 444 // Special handling for set-cookie according to the spec
michael@0 445 bool isSetCookie = name.Equals(NS_LITERAL_CSTRING("set-cookie"));
michael@0 446 int32_t valueLen = value.Length();
michael@0 447 for (int32_t i = 0; i < valueLen; ++i) {
michael@0 448 if (value[i] == '\0') {
michael@0 449 if (isSetCookie) {
michael@0 450 mOutput->Append(NS_LITERAL_CSTRING("\r\n"));
michael@0 451 mOutput->Append(name);
michael@0 452 mOutput->Append(NS_LITERAL_CSTRING(": "));
michael@0 453 } else {
michael@0 454 mOutput->Append(NS_LITERAL_CSTRING(", "));
michael@0 455 }
michael@0 456 } else {
michael@0 457 mOutput->Append(value[i]);
michael@0 458 }
michael@0 459 }
michael@0 460 mOutput->Append(NS_LITERAL_CSTRING("\r\n"));
michael@0 461 return NS_OK;
michael@0 462 }
michael@0 463
michael@0 464 nsresult
michael@0 465 Http2Decompressor::OutputHeader(uint32_t index)
michael@0 466 {
michael@0 467 // bounds check
michael@0 468 if (mHeaderTable.Length() <= index)
michael@0 469 return NS_ERROR_ILLEGAL_VALUE;
michael@0 470
michael@0 471 return OutputHeader(mHeaderTable[index]->mName,
michael@0 472 mHeaderTable[index]->mValue);
michael@0 473 }
michael@0 474
michael@0 475 nsresult
michael@0 476 Http2Decompressor::CopyHeaderString(uint32_t index, nsACString &name)
michael@0 477 {
michael@0 478 // bounds check
michael@0 479 if (mHeaderTable.Length() <= index)
michael@0 480 return NS_ERROR_ILLEGAL_VALUE;
michael@0 481
michael@0 482 name = mHeaderTable[index]->mName;
michael@0 483 return NS_OK;
michael@0 484 }
michael@0 485
michael@0 486 nsresult
michael@0 487 Http2Decompressor::CopyStringFromInput(uint32_t bytes, nsACString &val)
michael@0 488 {
michael@0 489 if (mOffset + bytes > mDataLen)
michael@0 490 return NS_ERROR_ILLEGAL_VALUE;
michael@0 491
michael@0 492 val.Assign(reinterpret_cast<const char *>(mData) + mOffset, bytes);
michael@0 493 mOffset += bytes;
michael@0 494 return NS_OK;
michael@0 495 }
michael@0 496
michael@0 497 nsresult
michael@0 498 Http2Decompressor::DecodeFinalHuffmanCharacter(HuffmanIncomingTable *table,
michael@0 499 uint8_t &c, uint8_t &bitsLeft)
michael@0 500 {
michael@0 501 uint8_t mask = (1 << bitsLeft) - 1;
michael@0 502 uint8_t idx = mData[mOffset - 1] & mask;
michael@0 503 idx <<= (8 - bitsLeft);
michael@0 504 // Don't update bitsLeft yet, because we need to check that value against the
michael@0 505 // number of bits used by our encoding later on. We'll update when we are sure
michael@0 506 // how many bits we've actually used.
michael@0 507
michael@0 508 HuffmanIncomingEntry *entry = &(table->mEntries[idx]);
michael@0 509
michael@0 510 if (entry->mPtr) {
michael@0 511 // Can't chain to another table when we're all out of bits in the encoding
michael@0 512 LOG3(("DecodeFinalHuffmanCharacter trying to chain when we're out of bits"));
michael@0 513 return NS_ERROR_ILLEGAL_VALUE;
michael@0 514 }
michael@0 515
michael@0 516 if (bitsLeft < entry->mPrefixLen) {
michael@0 517 // We don't have enough bits to actually make a match, this is some sort of
michael@0 518 // invalid coding
michael@0 519 LOG3(("DecodeFinalHuffmanCharacter does't have enough bits to match"));
michael@0 520 return NS_ERROR_ILLEGAL_VALUE;
michael@0 521 }
michael@0 522
michael@0 523 // This is a character!
michael@0 524 if (entry->mValue == 256) {
michael@0 525 // EOS
michael@0 526 LOG3(("DecodeFinalHuffmanCharacter actually decoded an EOS"));
michael@0 527 return NS_ERROR_ILLEGAL_VALUE;
michael@0 528 }
michael@0 529 c = static_cast<uint8_t>(entry->mValue & 0xFF);
michael@0 530 bitsLeft -= entry->mPrefixLen;
michael@0 531
michael@0 532 return NS_OK;
michael@0 533 }
michael@0 534
michael@0 535 uint8_t
michael@0 536 Http2Decompressor::ExtractByte(uint8_t bitsLeft, uint32_t &bytesConsumed)
michael@0 537 {
michael@0 538 uint8_t rv;
michael@0 539
michael@0 540 if (bitsLeft) {
michael@0 541 // Need to extract bitsLeft bits from the previous byte, and 8 - bitsLeft
michael@0 542 // bits from the current byte
michael@0 543 uint8_t mask = (1 << bitsLeft) - 1;
michael@0 544 rv = (mData[mOffset - 1] & mask) << (8 - bitsLeft);
michael@0 545 rv |= (mData[mOffset] & ~mask) >> bitsLeft;
michael@0 546 } else {
michael@0 547 rv = mData[mOffset];
michael@0 548 }
michael@0 549
michael@0 550 // We always update these here, under the assumption that all 8 bits we got
michael@0 551 // here will be used. These may be re-adjusted later in the case that we don't
michael@0 552 // use up all 8 bits of the byte.
michael@0 553 ++mOffset;
michael@0 554 ++bytesConsumed;
michael@0 555
michael@0 556 return rv;
michael@0 557 }
michael@0 558
michael@0 559 nsresult
michael@0 560 Http2Decompressor::DecodeHuffmanCharacter(HuffmanIncomingTable *table,
michael@0 561 uint8_t &c, uint32_t &bytesConsumed,
michael@0 562 uint8_t &bitsLeft)
michael@0 563 {
michael@0 564 uint8_t idx = ExtractByte(bitsLeft, bytesConsumed);
michael@0 565 HuffmanIncomingEntry *entry = &(table->mEntries[idx]);
michael@0 566
michael@0 567 if (entry->mPtr) {
michael@0 568 if (bytesConsumed >= mDataLen) {
michael@0 569 if (!bitsLeft || (bytesConsumed > mDataLen)) {
michael@0 570 // TODO - does this get me into trouble in the new world?
michael@0 571 // No info left in input to try to consume, we're done
michael@0 572 LOG3(("DecodeHuffmanCharacter all out of bits to consume, can't chain"));
michael@0 573 return NS_ERROR_ILLEGAL_VALUE;
michael@0 574 }
michael@0 575
michael@0 576 // We might get lucky here!
michael@0 577 return DecodeFinalHuffmanCharacter(entry->mPtr, c, bitsLeft);
michael@0 578 }
michael@0 579
michael@0 580 // We're sorry, Mario, but your princess is in another castle
michael@0 581 return DecodeHuffmanCharacter(entry->mPtr, c, bytesConsumed, bitsLeft);
michael@0 582 }
michael@0 583
michael@0 584 if (entry->mValue == 256) {
michael@0 585 LOG3(("DecodeHuffmanCharacter found an actual EOS"));
michael@0 586 return NS_ERROR_ILLEGAL_VALUE;
michael@0 587 }
michael@0 588 c = static_cast<uint8_t>(entry->mValue & 0xFF);
michael@0 589
michael@0 590 // Need to adjust bitsLeft (and possibly other values) because we may not have
michael@0 591 // consumed all of the bits of the byte we extracted.
michael@0 592 if (entry->mPrefixLen <= bitsLeft) {
michael@0 593 bitsLeft -= entry->mPrefixLen;
michael@0 594 --mOffset;
michael@0 595 --bytesConsumed;
michael@0 596 } else {
michael@0 597 bitsLeft = 8 - (entry->mPrefixLen - bitsLeft);
michael@0 598 }
michael@0 599 MOZ_ASSERT(bitsLeft < 8);
michael@0 600
michael@0 601 return NS_OK;
michael@0 602 }
michael@0 603
michael@0 604 nsresult
michael@0 605 Http2Decompressor::CopyHuffmanStringFromInput(uint32_t bytes, nsACString &val)
michael@0 606 {
michael@0 607 if (mOffset + bytes > mDataLen) {
michael@0 608 LOG3(("CopyHuffmanStringFromInput not enough data"));
michael@0 609 return NS_ERROR_ILLEGAL_VALUE;
michael@0 610 }
michael@0 611
michael@0 612 uint32_t bytesRead = 0;
michael@0 613 uint8_t bitsLeft = 0;
michael@0 614 nsAutoCString buf;
michael@0 615 nsresult rv;
michael@0 616 uint8_t c;
michael@0 617
michael@0 618 while (bytesRead < bytes) {
michael@0 619 uint32_t bytesConsumed = 0;
michael@0 620 rv = DecodeHuffmanCharacter(&HuffmanIncomingRoot, c, bytesConsumed,
michael@0 621 bitsLeft);
michael@0 622 if (NS_FAILED(rv)) {
michael@0 623 LOG3(("CopyHuffmanStringFromInput failed to decode a character"));
michael@0 624 return rv;
michael@0 625 }
michael@0 626
michael@0 627 bytesRead += bytesConsumed;
michael@0 628 buf.Append(c);
michael@0 629 }
michael@0 630
michael@0 631 if (bytesRead > bytes) {
michael@0 632 LOG3(("CopyHuffmanStringFromInput read more bytes than was allowed!"));
michael@0 633 return NS_ERROR_ILLEGAL_VALUE;
michael@0 634 }
michael@0 635
michael@0 636 if (bitsLeft) {
michael@0 637 // The shortest valid code is 4 bits, so we know there can be at most one
michael@0 638 // character left that our loop didn't decode. Check to see if that's the
michael@0 639 // case, and if so, add it to our output.
michael@0 640 rv = DecodeFinalHuffmanCharacter(&HuffmanIncomingRoot, c, bitsLeft);
michael@0 641 if (NS_SUCCEEDED(rv)) {
michael@0 642 buf.Append(c);
michael@0 643 }
michael@0 644 }
michael@0 645
michael@0 646 if (bitsLeft) {
michael@0 647 // Any bits left at this point must belong to the EOS symbol, so make sure
michael@0 648 // they make sense (ie, are all ones)
michael@0 649 uint8_t mask = (1 << bitsLeft) - 1;
michael@0 650 uint8_t bits = mData[mOffset - 1] & mask;
michael@0 651 if (bits != mask) {
michael@0 652 LOG3(("CopyHuffmanStringFromInput ran out of data but found possible "
michael@0 653 "non-EOS symbol"));
michael@0 654 return NS_ERROR_ILLEGAL_VALUE;
michael@0 655 }
michael@0 656 }
michael@0 657
michael@0 658 val = buf;
michael@0 659 LOG3(("CopyHuffmanStringFromInput decoded a full string!"));
michael@0 660 return NS_OK;
michael@0 661 }
michael@0 662
michael@0 663 void
michael@0 664 Http2Decompressor::MakeRoom(uint32_t amount)
michael@0 665 {
michael@0 666 // make room in the header table
michael@0 667 uint32_t removedCount = 0;
michael@0 668 while (mHeaderTable.VariableLength() && ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
michael@0 669 uint32_t index = mHeaderTable.VariableLength() - 1;
michael@0 670 mHeaderTable.RemoveElement();
michael@0 671 ++removedCount;
michael@0 672 LOG3(("HTTP decompressor header table index %u removed for size.\n",
michael@0 673 index));
michael@0 674 }
michael@0 675
michael@0 676 // adjust references to header table
michael@0 677 UpdateReferenceSet(removedCount);
michael@0 678 }
michael@0 679
michael@0 680 nsresult
michael@0 681 Http2Decompressor::DoIndexed()
michael@0 682 {
michael@0 683 // this starts with a 1 bit pattern
michael@0 684 MOZ_ASSERT(mData[mOffset] & 0x80);
michael@0 685
michael@0 686 // Indexed entries toggle the reference set
michael@0 687 // This is a 7 bit prefix
michael@0 688
michael@0 689 uint32_t index;
michael@0 690 nsresult rv = DecodeInteger(7, index);
michael@0 691 if (NS_FAILED(rv))
michael@0 692 return rv;
michael@0 693
michael@0 694 LOG3(("HTTP decompressor indexed entry %u\n", index));
michael@0 695
michael@0 696 if (index == 0) {
michael@0 697 // Index 0 is a special case - it has extra data tacked on the end to
michael@0 698 // determine what kind of change to make to the encoding context.
michael@0 699 //
michael@0 700 if (mData[mOffset] & 0x80) {
michael@0 701 // This means we have to clear out the reference set
michael@0 702 mReferenceSet.Clear();
michael@0 703 mAlternateReferenceSet.Clear();
michael@0 704 ++mOffset;
michael@0 705 return NS_OK;
michael@0 706 }
michael@0 707
michael@0 708 // Getting here means we have to adjust the max table size
michael@0 709 uint32_t newMaxSize;
michael@0 710 rv = DecodeInteger(7, newMaxSize);
michael@0 711 if (NS_FAILED(rv))
michael@0 712 return rv;
michael@0 713 return mCompressor->SetMaxBufferSizeInternal(newMaxSize);
michael@0 714 }
michael@0 715 index--; // Internally, we 0-index everything, since this is, y'know, C++
michael@0 716
michael@0 717 // Toggle this in the reference set..
michael@0 718 // if its not currently in the reference set then add it and
michael@0 719 // also emit it. If it is currently in the reference set then just
michael@0 720 // remove it from there.
michael@0 721 if (mReferenceSet.RemoveElement(index)) {
michael@0 722 mAlternateReferenceSet.RemoveElement(index);
michael@0 723 return NS_OK;
michael@0 724 }
michael@0 725
michael@0 726 rv = OutputHeader(index);
michael@0 727 if (index >= mHeaderTable.VariableLength()) {
michael@0 728 const nvPair *pair = mHeaderTable[index];
michael@0 729 uint32_t room = pair->Size();
michael@0 730
michael@0 731 if (room > mMaxBuffer) {
michael@0 732 ClearHeaderTable();
michael@0 733 LOG3(("HTTP decompressor index not referenced due to size %u %s\n",
michael@0 734 room, pair->mName.get()));
michael@0 735 return rv;
michael@0 736 }
michael@0 737
michael@0 738 MakeRoom(room);
michael@0 739 mHeaderTable.AddElement(pair->mName, pair->mValue);
michael@0 740 IncrementReferenceSetIndices();
michael@0 741 index = 0;
michael@0 742 }
michael@0 743
michael@0 744 mReferenceSet.AppendElement(index);
michael@0 745 mAlternateReferenceSet.AppendElement(index);
michael@0 746 return rv;
michael@0 747 }
michael@0 748
michael@0 749 nsresult
michael@0 750 Http2Decompressor::DoLiteralInternal(nsACString &name, nsACString &value)
michael@0 751 {
michael@0 752 // guts of doliteralwithoutindex and doliteralwithincremental
michael@0 753 MOZ_ASSERT(((mData[mOffset] & 0xC0) == 0x40) || // withoutindex
michael@0 754 ((mData[mOffset] & 0xC0) == 0x00)); // withincremental
michael@0 755
michael@0 756 // first let's get the name
michael@0 757 uint32_t index;
michael@0 758 nsresult rv = DecodeInteger(6, index);
michael@0 759 if (NS_FAILED(rv))
michael@0 760 return rv;
michael@0 761
michael@0 762 bool isHuffmanEncoded;
michael@0 763
michael@0 764 if (!index) {
michael@0 765 // name is embedded as a literal
michael@0 766 uint32_t nameLen;
michael@0 767 isHuffmanEncoded = mData[mOffset] & (1 << 7);
michael@0 768 rv = DecodeInteger(7, nameLen);
michael@0 769 if (NS_SUCCEEDED(rv)) {
michael@0 770 if (isHuffmanEncoded) {
michael@0 771 rv = CopyHuffmanStringFromInput(nameLen, name);
michael@0 772 } else {
michael@0 773 rv = CopyStringFromInput(nameLen, name);
michael@0 774 }
michael@0 775 }
michael@0 776 } else {
michael@0 777 // name is from headertable
michael@0 778 rv = CopyHeaderString(index - 1, name);
michael@0 779 }
michael@0 780 if (NS_FAILED(rv))
michael@0 781 return rv;
michael@0 782
michael@0 783 // now the value
michael@0 784 uint32_t valueLen;
michael@0 785 isHuffmanEncoded = mData[mOffset] & (1 << 7);
michael@0 786 rv = DecodeInteger(7, valueLen);
michael@0 787 if (NS_SUCCEEDED(rv)) {
michael@0 788 if (isHuffmanEncoded) {
michael@0 789 rv = CopyHuffmanStringFromInput(valueLen, value);
michael@0 790 } else {
michael@0 791 rv = CopyStringFromInput(valueLen, value);
michael@0 792 }
michael@0 793 }
michael@0 794 if (NS_FAILED(rv))
michael@0 795 return rv;
michael@0 796 return NS_OK;
michael@0 797 }
michael@0 798
michael@0 799 nsresult
michael@0 800 Http2Decompressor::DoLiteralWithoutIndex()
michael@0 801 {
michael@0 802 // this starts with 01 bit pattern
michael@0 803 MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x40);
michael@0 804
michael@0 805 // This is not indexed so there is no adjustment to the
michael@0 806 // persistent reference set
michael@0 807 nsAutoCString name, value;
michael@0 808 nsresult rv = DoLiteralInternal(name, value);
michael@0 809
michael@0 810 LOG3(("HTTP decompressor literal without index %s %s\n",
michael@0 811 name.get(), value.get()));
michael@0 812
michael@0 813 // Output the header now because we don't keep void
michael@0 814 // indicies in the reference set
michael@0 815 if (NS_SUCCEEDED(rv))
michael@0 816 rv = OutputHeader(name, value);
michael@0 817 return rv;
michael@0 818 }
michael@0 819
michael@0 820 nsresult
michael@0 821 Http2Decompressor::DoLiteralWithIncremental()
michael@0 822 {
michael@0 823 // this starts with 00 bit pattern
michael@0 824 MOZ_ASSERT((mData[mOffset] & 0xC0) == 0x00);
michael@0 825
michael@0 826 nsAutoCString name, value;
michael@0 827 nsresult rv = DoLiteralInternal(name, value);
michael@0 828 if (NS_SUCCEEDED(rv))
michael@0 829 rv = OutputHeader(name, value);
michael@0 830 if (NS_FAILED(rv))
michael@0 831 return rv;
michael@0 832
michael@0 833 uint32_t room = nvPair(name, value).Size();
michael@0 834 if (room > mMaxBuffer) {
michael@0 835 ClearHeaderTable();
michael@0 836 LOG3(("HTTP decompressor literal with index not referenced due to size %u %s\n",
michael@0 837 room, name.get()));
michael@0 838 return NS_OK;
michael@0 839 }
michael@0 840
michael@0 841 MakeRoom(room);
michael@0 842
michael@0 843 // Incremental Indexing implicitly adds a row to the header table.
michael@0 844 // It also adds the new row to the Reference Set
michael@0 845 mHeaderTable.AddElement(name, value);
michael@0 846 IncrementReferenceSetIndices();
michael@0 847 mReferenceSet.AppendElement(0);
michael@0 848 mAlternateReferenceSet.AppendElement(0);
michael@0 849
michael@0 850 LOG3(("HTTP decompressor literal with index 0 %s %s\n",
michael@0 851 name.get(), value.get()));
michael@0 852
michael@0 853 return NS_OK;
michael@0 854 }
michael@0 855
michael@0 856 /////////////////////////////////////////////////////////////////
michael@0 857
michael@0 858 nsresult
michael@0 859 Http2Compressor::EncodeHeaderBlock(const nsCString &nvInput,
michael@0 860 const nsACString &method, const nsACString &path,
michael@0 861 const nsACString &host, const nsACString &scheme,
michael@0 862 nsACString &output)
michael@0 863 {
michael@0 864 mAlternateReferenceSet.Clear();
michael@0 865 mImpliedReferenceSet.Clear();
michael@0 866 mOutput = &output;
michael@0 867 output.SetCapacity(1024);
michael@0 868 output.Truncate();
michael@0 869 mParsedContentLength = -1;
michael@0 870
michael@0 871 // colon headers first
michael@0 872 ProcessHeader(nvPair(NS_LITERAL_CSTRING(":method"), method));
michael@0 873 ProcessHeader(nvPair(NS_LITERAL_CSTRING(":path"), path));
michael@0 874 ProcessHeader(nvPair(NS_LITERAL_CSTRING(":authority"), host));
michael@0 875 ProcessHeader(nvPair(NS_LITERAL_CSTRING(":scheme"), scheme));
michael@0 876
michael@0 877 // now the non colon headers
michael@0 878 const char *beginBuffer = nvInput.BeginReading();
michael@0 879
michael@0 880 int32_t crlfIndex = nvInput.Find("\r\n");
michael@0 881 while (true) {
michael@0 882 int32_t startIndex = crlfIndex + 2;
michael@0 883
michael@0 884 crlfIndex = nvInput.Find("\r\n", false, startIndex);
michael@0 885 if (crlfIndex == -1)
michael@0 886 break;
michael@0 887
michael@0 888 int32_t colonIndex = nvInput.Find(":", false, startIndex,
michael@0 889 crlfIndex - startIndex);
michael@0 890 if (colonIndex == -1)
michael@0 891 break;
michael@0 892
michael@0 893 nsDependentCSubstring name = Substring(beginBuffer + startIndex,
michael@0 894 beginBuffer + colonIndex);
michael@0 895 // all header names are lower case in http/2
michael@0 896 ToLowerCase(name);
michael@0 897
michael@0 898 // exclusions
michael@0 899 if (name.Equals("connection") ||
michael@0 900 name.Equals("host") ||
michael@0 901 name.Equals("keep-alive") ||
michael@0 902 name.Equals("proxy-connection") ||
michael@0 903 name.Equals("te") ||
michael@0 904 name.Equals("transfer-encoding") ||
michael@0 905 name.Equals("upgrade") ||
michael@0 906 name.Equals("accept-encoding")) {
michael@0 907 continue;
michael@0 908 }
michael@0 909
michael@0 910 // colon headers are for http/2 and this is http/1 input, so that
michael@0 911 // is probably a smuggling attack of some kind
michael@0 912 if(*(name.BeginReading()) == ':') {
michael@0 913 continue;
michael@0 914 }
michael@0 915
michael@0 916 int32_t valueIndex = colonIndex + 1;
michael@0 917
michael@0 918 // if we have Expect: *100-continue,*" redact the 100-continue
michael@0 919 // as we don't have a good mechanism for clients to make use of it
michael@0 920 // anyhow
michael@0 921 if (name.Equals("expect")) {
michael@0 922 const char *continueHeader =
michael@0 923 nsHttp::FindToken(beginBuffer + valueIndex, "100-continue",
michael@0 924 HTTP_HEADER_VALUE_SEPS);
michael@0 925 if (continueHeader) {
michael@0 926 char *writableVal = const_cast<char *>(continueHeader);
michael@0 927 memset(writableVal, 0, 12);
michael@0 928 writableVal += 12;
michael@0 929 // this will terminate safely because CRLF EOL has been confirmed
michael@0 930 while ((*writableVal == ' ') || (*writableVal == '\t') ||
michael@0 931 (*writableVal == ',')) {
michael@0 932 *writableVal = ' ';
michael@0 933 ++writableVal;
michael@0 934 }
michael@0 935 }
michael@0 936 }
michael@0 937
michael@0 938 while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
michael@0 939 ++valueIndex;
michael@0 940
michael@0 941 nsDependentCSubstring value = Substring(beginBuffer + valueIndex,
michael@0 942 beginBuffer + crlfIndex);
michael@0 943
michael@0 944 if (name.Equals("content-length")) {
michael@0 945 int64_t len;
michael@0 946 nsCString tmp(value);
michael@0 947 if (nsHttp::ParseInt64(tmp.get(), nullptr, &len))
michael@0 948 mParsedContentLength = len;
michael@0 949 }
michael@0 950
michael@0 951 if (name.Equals("cookie")) {
michael@0 952 // cookie crumbling
michael@0 953 bool haveMoreCookies = true;
michael@0 954 int32_t nextCookie = valueIndex;
michael@0 955 while (haveMoreCookies) {
michael@0 956 int32_t semiSpaceIndex = nvInput.Find("; ", false, nextCookie,
michael@0 957 crlfIndex - nextCookie);
michael@0 958 if (semiSpaceIndex == -1) {
michael@0 959 haveMoreCookies = false;
michael@0 960 semiSpaceIndex = crlfIndex;
michael@0 961 }
michael@0 962 nsDependentCSubstring cookie = Substring(beginBuffer + nextCookie,
michael@0 963 beginBuffer + semiSpaceIndex);
michael@0 964 ProcessHeader(nvPair(name, cookie));
michael@0 965 nextCookie = semiSpaceIndex + 2;
michael@0 966 }
michael@0 967 } else {
michael@0 968 ProcessHeader(nvPair(name, value));
michael@0 969 }
michael@0 970 }
michael@0 971
michael@0 972 // iterate mreference set and if !alternate.contains(old[i])
michael@0 973 // toggle off
michael@0 974 uint32_t setLen = mReferenceSet.Length();
michael@0 975 for (uint32_t index = 0; index < setLen; ++index) {
michael@0 976 if (!mAlternateReferenceSet.Contains(mReferenceSet[index])) {
michael@0 977 DoOutput(kToggleOff, mHeaderTable[mReferenceSet[index]],
michael@0 978 mReferenceSet[index]);
michael@0 979 }
michael@0 980 }
michael@0 981
michael@0 982 mReferenceSet = mAlternateReferenceSet;
michael@0 983 mAlternateReferenceSet.Clear();
michael@0 984 mImpliedReferenceSet.Clear();
michael@0 985 mOutput = nullptr;
michael@0 986 return NS_OK;
michael@0 987 }
michael@0 988
michael@0 989 void
michael@0 990 Http2Compressor::DoOutput(Http2Compressor::outputCode code,
michael@0 991 const class nvPair *pair, uint32_t index)
michael@0 992 {
michael@0 993 // start Byte needs to be calculated from the offset after
michael@0 994 // the opcode has been written out in case the output stream
michael@0 995 // buffer gets resized/relocated
michael@0 996 uint32_t offset = mOutput->Length();
michael@0 997 uint8_t *startByte;
michael@0 998
michael@0 999 switch (code) {
michael@0 1000 case kPlainLiteral:
michael@0 1001 LOG3(("HTTP compressor %p noindex literal with name reference %u %s: %s\n",
michael@0 1002 this, index, pair->mName.get(), pair->mValue.get()));
michael@0 1003
michael@0 1004 // In this case, the index will have already been adjusted to be 1-based
michael@0 1005 // instead of 0-based.
michael@0 1006 EncodeInteger(6, index); // 01 2 bit prefix
michael@0 1007 startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
michael@0 1008 *startByte = (*startByte & 0x3f) | 0x40;
michael@0 1009
michael@0 1010 if (!index) {
michael@0 1011 HuffmanAppend(pair->mName);
michael@0 1012 }
michael@0 1013
michael@0 1014 HuffmanAppend(pair->mValue);
michael@0 1015 break;
michael@0 1016
michael@0 1017 case kIndexedLiteral:
michael@0 1018 LOG3(("HTTP compressor %p literal with name reference %u %s: %s\n",
michael@0 1019 this, index, pair->mName.get(), pair->mValue.get()));
michael@0 1020
michael@0 1021 // In this case, the index will have already been adjusted to be 1-based
michael@0 1022 // instead of 0-based.
michael@0 1023 EncodeInteger(6, index); // 00 2 bit prefix
michael@0 1024 startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
michael@0 1025 *startByte = *startByte & 0x3f;
michael@0 1026
michael@0 1027 if (!index) {
michael@0 1028 HuffmanAppend(pair->mName);
michael@0 1029 }
michael@0 1030
michael@0 1031 HuffmanAppend(pair->mValue);
michael@0 1032 break;
michael@0 1033
michael@0 1034 case kToggleOff:
michael@0 1035 case kToggleOn:
michael@0 1036 LOG3(("HTTP compressor %p toggle %s index %u %s\n",
michael@0 1037 this, (code == kToggleOff) ? "off" : "on",
michael@0 1038 index, pair->mName.get()));
michael@0 1039 // In this case, we are passed the raw 0-based C index, and need to
michael@0 1040 // increment to make it 1-based and comply with the spec
michael@0 1041 EncodeInteger(7, index + 1);
michael@0 1042 startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
michael@0 1043 *startByte = *startByte | 0x80; // 1 1 bit prefix
michael@0 1044 break;
michael@0 1045
michael@0 1046 case kNop:
michael@0 1047 LOG3(("HTTP compressor %p implied in reference set index %u %s\n",
michael@0 1048 this, index, pair->mName.get()));
michael@0 1049 break;
michael@0 1050 }
michael@0 1051 }
michael@0 1052
michael@0 1053 // writes the encoded integer onto the output
michael@0 1054 void
michael@0 1055 Http2Compressor::EncodeInteger(uint32_t prefixLen, uint32_t val)
michael@0 1056 {
michael@0 1057 uint32_t mask = (1 << prefixLen) - 1;
michael@0 1058 uint8_t tmp;
michael@0 1059
michael@0 1060 if (val < mask) {
michael@0 1061 // 1 byte encoding!
michael@0 1062 tmp = val;
michael@0 1063 mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
michael@0 1064 return;
michael@0 1065 }
michael@0 1066
michael@0 1067 if (mask) {
michael@0 1068 val -= mask;
michael@0 1069 tmp = mask;
michael@0 1070 mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
michael@0 1071 }
michael@0 1072
michael@0 1073 uint32_t q, r;
michael@0 1074 do {
michael@0 1075 q = val / 128;
michael@0 1076 r = val % 128;
michael@0 1077 tmp = r;
michael@0 1078 if (q)
michael@0 1079 tmp |= 0x80; // chain bit
michael@0 1080 val = q;
michael@0 1081 mOutput->Append(reinterpret_cast<char *>(&tmp), 1);
michael@0 1082 } while (q);
michael@0 1083 }
michael@0 1084
michael@0 1085 void
michael@0 1086 Http2Compressor::ClearHeaderTable()
michael@0 1087 {
michael@0 1088 uint32_t dynamicCount = mHeaderTable.VariableLength();
michael@0 1089
michael@0 1090 Http2BaseCompressor::ClearHeaderTable();
michael@0 1091
michael@0 1092 for (int32_t i = mImpliedReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 1093 if (mImpliedReferenceSet[i] < dynamicCount) {
michael@0 1094 mImpliedReferenceSet.RemoveElementAt(i);
michael@0 1095 } else {
michael@0 1096 mImpliedReferenceSet[i] -= dynamicCount;
michael@0 1097 }
michael@0 1098 }
michael@0 1099 }
michael@0 1100
michael@0 1101
michael@0 1102 void
michael@0 1103 Http2Compressor::UpdateReferenceSet(int32_t delta)
michael@0 1104 {
michael@0 1105 if (!delta)
michael@0 1106 return;
michael@0 1107
michael@0 1108 Http2BaseCompressor::UpdateReferenceSet(delta);
michael@0 1109
michael@0 1110 uint32_t headerTableSize = mHeaderTable.VariableLength();
michael@0 1111 uint32_t oldHeaderTableSize = headerTableSize + delta;
michael@0 1112
michael@0 1113 for (int32_t i = mImpliedReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 1114 uint32_t indexRef = mImpliedReferenceSet[i];
michael@0 1115 if (indexRef >= headerTableSize) {
michael@0 1116 if (indexRef < oldHeaderTableSize) {
michael@0 1117 // This one got dropped
michael@0 1118 LOG3(("HTTP compressor implied reference to index %u removed.\n",
michael@0 1119 indexRef));
michael@0 1120 mImpliedReferenceSet.RemoveElementAt(i);
michael@0 1121 } else {
michael@0 1122 // This pointed to the static table, need to adjust
michael@0 1123 uint32_t newRef = indexRef - delta;
michael@0 1124 LOG3(("HTTP compressor implied reference to index %u changed to %d (%s)\n",
michael@0 1125 mImpliedReferenceSet[i], newRef, mHeaderTable[newRef]->mName.get()));
michael@0 1126 mImpliedReferenceSet[i] = newRef;
michael@0 1127 }
michael@0 1128 }
michael@0 1129 }
michael@0 1130 }
michael@0 1131
michael@0 1132 void
michael@0 1133 Http2Compressor::IncrementReferenceSetIndices()
michael@0 1134 {
michael@0 1135 Http2BaseCompressor::IncrementReferenceSetIndices();
michael@0 1136
michael@0 1137 for (int32_t i = mImpliedReferenceSet.Length() - 1; i >= 0; --i) {
michael@0 1138 mImpliedReferenceSet[i] = mImpliedReferenceSet[i] + 1;
michael@0 1139 }
michael@0 1140 }
michael@0 1141
michael@0 1142 void
michael@0 1143 Http2Compressor::MakeRoom(uint32_t amount)
michael@0 1144 {
michael@0 1145 // make room in the header table
michael@0 1146 uint32_t removedCount = 0;
michael@0 1147 while (mHeaderTable.VariableLength() && ((mHeaderTable.ByteCount() + amount) > mMaxBuffer)) {
michael@0 1148
michael@0 1149 // if there is a reference to removedCount (~0) in the implied reference set we need,
michael@0 1150 // to toggle it off/on so that the implied reference is not lost when the
michael@0 1151 // table is trimmed
michael@0 1152 uint32_t index = mHeaderTable.VariableLength() - 1;
michael@0 1153 if (mImpliedReferenceSet.Contains(index) ) {
michael@0 1154 LOG3(("HTTP compressor header table index %u %s about to be "
michael@0 1155 "removed for size but has an implied reference. Will Toggle.\n",
michael@0 1156 index, mHeaderTable[index]->mName.get()));
michael@0 1157
michael@0 1158 DoOutput(kToggleOff, mHeaderTable[index], index);
michael@0 1159 DoOutput(kToggleOn, mHeaderTable[index], index);
michael@0 1160 }
michael@0 1161
michael@0 1162 LOG3(("HTTP compressor header table index %u %s removed for size.\n",
michael@0 1163 index, mHeaderTable[index]->mName.get()));
michael@0 1164 mHeaderTable.RemoveElement();
michael@0 1165 ++removedCount;
michael@0 1166 }
michael@0 1167
michael@0 1168 // adjust references to header table
michael@0 1169 UpdateReferenceSet(removedCount);
michael@0 1170 }
michael@0 1171
michael@0 1172 void
michael@0 1173 Http2Compressor::HuffmanAppend(const nsCString &value)
michael@0 1174 {
michael@0 1175 nsAutoCString buf;
michael@0 1176 uint8_t bitsLeft = 8;
michael@0 1177 uint32_t length = value.Length();
michael@0 1178 uint32_t offset;
michael@0 1179 uint8_t *startByte;
michael@0 1180
michael@0 1181 for (uint32_t i = 0; i < length; ++i) {
michael@0 1182 uint8_t idx = static_cast<uint8_t>(value[i]);
michael@0 1183 uint8_t huffLength = HuffmanOutgoing[idx].mLength;
michael@0 1184 uint32_t huffValue = HuffmanOutgoing[idx].mValue;
michael@0 1185
michael@0 1186 if (bitsLeft < 8) {
michael@0 1187 // Fill in the least significant <bitsLeft> bits of the previous byte
michael@0 1188 // first
michael@0 1189 uint32_t val;
michael@0 1190 if (huffLength >= bitsLeft) {
michael@0 1191 val = huffValue & ~((1 << (huffLength - bitsLeft)) - 1);
michael@0 1192 val >>= (huffLength - bitsLeft);
michael@0 1193 } else {
michael@0 1194 val = huffValue << (bitsLeft - huffLength);
michael@0 1195 }
michael@0 1196 val &= ((1 << bitsLeft) - 1);
michael@0 1197 offset = buf.Length() - 1;
michael@0 1198 startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
michael@0 1199 *startByte = *startByte | static_cast<uint8_t>(val & 0xFF);
michael@0 1200 if (huffLength >= bitsLeft) {
michael@0 1201 huffLength -= bitsLeft;
michael@0 1202 bitsLeft = 8;
michael@0 1203 } else {
michael@0 1204 bitsLeft -= huffLength;
michael@0 1205 huffLength = 0;
michael@0 1206 }
michael@0 1207 }
michael@0 1208
michael@0 1209 while (huffLength > 8) {
michael@0 1210 uint32_t mask = ~((1 << (huffLength - 8)) - 1);
michael@0 1211 uint8_t val = ((huffValue & mask) >> (huffLength - 8)) & 0xFF;
michael@0 1212 buf.Append(reinterpret_cast<char *>(&val), 1);
michael@0 1213 huffLength -= 8;
michael@0 1214 }
michael@0 1215
michael@0 1216 if (huffLength) {
michael@0 1217 // Fill in the most significant <huffLength> bits of the next byte
michael@0 1218 bitsLeft = 8 - huffLength;
michael@0 1219 uint8_t val = (huffValue & ((1 << huffLength) - 1)) << bitsLeft;
michael@0 1220 buf.Append(reinterpret_cast<char *>(&val), 1);
michael@0 1221 }
michael@0 1222 }
michael@0 1223
michael@0 1224 if (bitsLeft != 8) {
michael@0 1225 // Pad the last <bitsLeft> bits with ones, which corresponds to the EOS
michael@0 1226 // encoding
michael@0 1227 uint8_t val = (1 << bitsLeft) - 1;
michael@0 1228 offset = buf.Length() - 1;
michael@0 1229 startByte = reinterpret_cast<unsigned char *>(buf.BeginWriting()) + offset;
michael@0 1230 *startByte = *startByte | val;
michael@0 1231 }
michael@0 1232
michael@0 1233 // Now we know how long our encoded string is, we can fill in our length
michael@0 1234 uint32_t bufLength = buf.Length();
michael@0 1235 offset = mOutput->Length();
michael@0 1236 EncodeInteger(7, bufLength);
michael@0 1237 startByte = reinterpret_cast<unsigned char *>(mOutput->BeginWriting()) + offset;
michael@0 1238 *startByte = *startByte | 0x80;
michael@0 1239
michael@0 1240 // Finally, we can add our REAL data!
michael@0 1241 mOutput->Append(buf);
michael@0 1242 }
michael@0 1243
michael@0 1244 void
michael@0 1245 Http2Compressor::ProcessHeader(const nvPair inputPair)
michael@0 1246 {
michael@0 1247 uint32_t newSize = inputPair.Size();
michael@0 1248 uint32_t headerTableSize = mHeaderTable.Length();
michael@0 1249 uint32_t matchedIndex;
michael@0 1250 uint32_t nameReference = 0;
michael@0 1251 bool match = false;
michael@0 1252
michael@0 1253 for (uint32_t index = 0; index < headerTableSize; ++index) {
michael@0 1254 if (mHeaderTable[index]->mName.Equals(inputPair.mName)) {
michael@0 1255 nameReference = index + 1;
michael@0 1256 if (mHeaderTable[index]->mValue.Equals(inputPair.mValue)) {
michael@0 1257 match = true;
michael@0 1258 matchedIndex = index;
michael@0 1259 break;
michael@0 1260 }
michael@0 1261 }
michael@0 1262 }
michael@0 1263
michael@0 1264 // We need to emit a new literal
michael@0 1265 if (!match) {
michael@0 1266 if ((newSize > (mMaxBuffer / 2)) || (mMaxBuffer < 128)) {
michael@0 1267 DoOutput(kPlainLiteral, &inputPair, nameReference);
michael@0 1268 return;
michael@0 1269 }
michael@0 1270
michael@0 1271 // make sure to makeroom() first so that any implied items
michael@0 1272 // get preserved.
michael@0 1273 MakeRoom(newSize);
michael@0 1274 DoOutput(kIndexedLiteral, &inputPair, nameReference);
michael@0 1275
michael@0 1276 mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
michael@0 1277 IncrementReferenceSetIndices();
michael@0 1278 LOG3(("HTTP compressor %p new literal placed at index 0\n",
michael@0 1279 this));
michael@0 1280 mAlternateReferenceSet.AppendElement(0);
michael@0 1281 return;
michael@0 1282 }
michael@0 1283
michael@0 1284 // It is in the reference set. just check to see if it is
michael@0 1285 // a duplicate for output purposes
michael@0 1286 if (mReferenceSet.Contains(matchedIndex)) {
michael@0 1287 if (mAlternateReferenceSet.Contains(matchedIndex)) {
michael@0 1288 DoOutput(kToggleOff, &inputPair, matchedIndex);
michael@0 1289 DoOutput(kToggleOn, &inputPair, matchedIndex);
michael@0 1290 } else {
michael@0 1291 DoOutput(kNop, &inputPair, matchedIndex);
michael@0 1292 if (!mImpliedReferenceSet.Contains(matchedIndex))
michael@0 1293 mImpliedReferenceSet.AppendElement(matchedIndex);
michael@0 1294 mAlternateReferenceSet.AppendElement(matchedIndex);
michael@0 1295 }
michael@0 1296 return;
michael@0 1297 }
michael@0 1298
michael@0 1299 // emit an index to add to reference set
michael@0 1300 DoOutput(kToggleOn, &inputPair, matchedIndex);
michael@0 1301 if (matchedIndex >= mHeaderTable.VariableLength()) {
michael@0 1302 MakeRoom(newSize);
michael@0 1303 mHeaderTable.AddElement(inputPair.mName, inputPair.mValue);
michael@0 1304 IncrementReferenceSetIndices();
michael@0 1305 mAlternateReferenceSet.AppendElement(0);
michael@0 1306 } else {
michael@0 1307 mAlternateReferenceSet.AppendElement(matchedIndex);
michael@0 1308 }
michael@0 1309 return;
michael@0 1310 }
michael@0 1311
michael@0 1312 void
michael@0 1313 Http2Compressor::SetMaxBufferSize(uint32_t maxBufferSize)
michael@0 1314 {
michael@0 1315 mMaxBufferSetting = maxBufferSize;
michael@0 1316 SetMaxBufferSizeInternal(maxBufferSize);
michael@0 1317 }
michael@0 1318
michael@0 1319 nsresult
michael@0 1320 Http2Compressor::SetMaxBufferSizeInternal(uint32_t maxBufferSize)
michael@0 1321 {
michael@0 1322 if (maxBufferSize > mMaxBufferSetting) {
michael@0 1323 return NS_ERROR_ILLEGAL_VALUE;
michael@0 1324 }
michael@0 1325
michael@0 1326 uint32_t removedCount = 0;
michael@0 1327
michael@0 1328 LOG3(("Http2Compressor::SetMaxBufferSizeInternal %u called", maxBufferSize));
michael@0 1329
michael@0 1330 while (mHeaderTable.VariableLength() && (mHeaderTable.ByteCount() > maxBufferSize)) {
michael@0 1331 mHeaderTable.RemoveElement();
michael@0 1332 ++removedCount;
michael@0 1333 }
michael@0 1334 UpdateReferenceSet(removedCount);
michael@0 1335
michael@0 1336 mMaxBuffer = maxBufferSize;
michael@0 1337
michael@0 1338 return NS_OK;
michael@0 1339 }
michael@0 1340
michael@0 1341 } // namespace mozilla::net
michael@0 1342 } // namespace mozilla

mercurial