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