Unverified Commit e52d0aff authored by justcoding121's avatar justcoding121 Committed by GitHub

Merge branch 'master' into beta

parents 099b187c 693694f7
......@@ -13,6 +13,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http2;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
......
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
using System.Text;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class Decoder
{
private readonly DynamicTable dynamicTable;
private readonly int maxHeaderSize;
private int maxDynamicTableSize;
private int encoderMaxDynamicTableSize;
private bool maxDynamicTableSizeChangeRequired;
private long headerSize;
private State state;
private HpackUtil.IndexType indexType;
private int index;
private bool huffmanEncoded;
private int skipLength;
private int nameLength;
private int valueLength;
private string name;
private enum State
{
ReadHeaderRepresentation,
ReadMaxDynamicTableSize,
ReadIndexedHeader,
ReadIndexedHeaderName,
ReadLiteralHeaderNameLengthPrefix,
ReadLiteralHeaderNameLength,
ReadLiteralHeaderName,
SkipLiteralHeaderName,
ReadLiteralHeaderValueLengthPrefix,
ReadLiteralHeaderValueLength,
ReadLiteralHeaderValue,
SkipLiteralHeaderValue
}
/// <summary>
/// Initializes a new instance of the <see cref="Decoder"/> class.
/// </summary>
/// <param name="maxHeaderSize">Max header size.</param>
/// <param name="maxHeaderTableSize">Max header table size.</param>
public Decoder(int maxHeaderSize, int maxHeaderTableSize)
{
dynamicTable = new DynamicTable(maxHeaderTableSize);
this.maxHeaderSize = maxHeaderSize;
maxDynamicTableSize = maxHeaderTableSize;
encoderMaxDynamicTableSize = maxHeaderTableSize;
maxDynamicTableSizeChangeRequired = false;
Reset();
}
private void Reset()
{
headerSize = 0;
state = State.ReadHeaderRepresentation;
indexType = HpackUtil.IndexType.None;
}
/// <summary>
/// Decode the header block into header fields.
/// </summary>
/// <param name="input">Input.</param>
/// <param name="headerListener">Header listener.</param>
public void Decode(BinaryReader input, IHeaderListener headerListener)
{
while (input.BaseStream.Length - input.BaseStream.Position > 0)
{
switch (state)
{
case State.ReadHeaderRepresentation:
sbyte b = input.ReadSByte();
if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20)
{
// Encoder MUST signal maximum dynamic table size change
throw new IOException("max dynamic table size change required");
}
if (b < 0)
{
// Indexed Header Field
index = b & 0x7F;
if (index == 0)
{
throw new IOException("illegal index value (" + index + ")");
}
else if (index == 0x7F)
{
state = State.ReadIndexedHeader;
}
else
{
IndexHeader(index, headerListener);
}
}
else if ((b & 0x40) == 0x40)
{
// Literal Header Field with Incremental Indexing
indexType = HpackUtil.IndexType.Incremental;
index = b & 0x3F;
if (index == 0)
{
state = State.ReadLiteralHeaderNameLengthPrefix;
}
else if (index == 0x3F)
{
state = State.ReadIndexedHeaderName;
}
else
{
// Index was stored as the prefix
ReadName(index);
state = State.ReadLiteralHeaderValueLengthPrefix;
}
}
else if ((b & 0x20) == 0x20)
{
// Dynamic Table Size Update
index = b & 0x1F;
if (index == 0x1F)
{
state = State.ReadMaxDynamicTableSize;
}
else
{
SetDynamicTableSize(index);
state = State.ReadHeaderRepresentation;
}
}
else
{
// Literal Header Field without Indexing / never Indexed
indexType = ((b & 0x10) == 0x10) ? HpackUtil.IndexType.Never : HpackUtil.IndexType.None;
index = b & 0x0F;
if (index == 0)
{
state = State.ReadLiteralHeaderNameLengthPrefix;
}
else if (index == 0x0F)
{
state = State.ReadIndexedHeaderName;
}
else
{
// Index was stored as the prefix
ReadName(index);
state = State.ReadLiteralHeaderValueLengthPrefix;
}
}
break;
case State.ReadMaxDynamicTableSize:
int maxSize = DecodeULE128(input);
if (maxSize == -1)
{
return;
}
// Check for numerical overflow
if (maxSize > int.MaxValue - index)
{
throw new IOException("decompression failure");
}
SetDynamicTableSize(index + maxSize);
state = State.ReadHeaderRepresentation;
break;
case State.ReadIndexedHeader:
int headerIndex = DecodeULE128(input);
if (headerIndex == -1)
{
return;
}
// Check for numerical overflow
if (headerIndex > int.MaxValue - index)
{
throw new IOException("decompression failure");
}
IndexHeader(index + headerIndex, headerListener);
state = State.ReadHeaderRepresentation;
break;
case State.ReadIndexedHeaderName:
// Header Name matches an entry in the Header Table
int nameIndex = DecodeULE128(input);
if (nameIndex == -1)
{
return;
}
// Check for numerical overflow
if (nameIndex > int.MaxValue - index)
{
throw new IOException("decompression failure");
}
ReadName(index + nameIndex);
state = State.ReadLiteralHeaderValueLengthPrefix;
break;
case State.ReadLiteralHeaderNameLengthPrefix:
b = input.ReadSByte();
huffmanEncoded = (b & 0x80) == 0x80;
index = b & 0x7F;
if (index == 0x7f)
{
state = State.ReadLiteralHeaderNameLength;
}
else
{
nameLength = index;
// Disallow empty names -- they cannot be represented in HTTP/1.x
if (nameLength == 0)
{
throw new IOException("decompression failure");
}
// Check name length against max header size
if (ExceedsMaxHeaderSize(nameLength))
{
if (indexType == HpackUtil.IndexType.None)
{
// Name is unused so skip bytes
name = string.Empty;
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
}
// Check name length against max dynamic table size
if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
name = string.Empty;
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
}
}
state = State.ReadLiteralHeaderName;
}
break;
case State.ReadLiteralHeaderNameLength:
// Header Name is a Literal String
nameLength = DecodeULE128(input);
if (nameLength == -1)
{
return;
}
// Check for numerical overflow
if (nameLength > int.MaxValue - index)
{
throw new IOException("decompression failure");
}
nameLength += index;
// Check name length against max header size
if (ExceedsMaxHeaderSize(nameLength))
{
if (indexType == HpackUtil.IndexType.None)
{
// Name is unused so skip bytes
name = string.Empty;
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
}
// Check name length against max dynamic table size
if (nameLength + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
name = string.Empty;
skipLength = nameLength;
state = State.SkipLiteralHeaderName;
break;
}
}
state = State.ReadLiteralHeaderName;
break;
case State.ReadLiteralHeaderName:
// Wait until entire name is readable
if (input.BaseStream.Length - input.BaseStream.Position < nameLength)
{
return;
}
name = ReadStringLiteral(input, nameLength);
state = State.ReadLiteralHeaderValueLengthPrefix;
break;
case State.SkipLiteralHeaderName:
skipLength -= (int)input.BaseStream.Seek(skipLength, SeekOrigin.Current);
if (skipLength < 0)
{
skipLength = 0;
}
if (skipLength == 0)
{
state = State.ReadLiteralHeaderValueLengthPrefix;
}
break;
case State.ReadLiteralHeaderValueLengthPrefix:
b = input.ReadSByte();
huffmanEncoded = (b & 0x80) == 0x80;
index = b & 0x7F;
if (index == 0x7f)
{
state = State.ReadLiteralHeaderValueLength;
}
else
{
valueLength = index;
// Check new header size against max header size
long newHeaderSize1 = (long)nameLength + valueLength;
if (ExceedsMaxHeaderSize(newHeaderSize1))
{
// truncation will be reported during endHeaderBlock
headerSize = maxHeaderSize + 1;
if (indexType == HpackUtil.IndexType.None)
{
// Value is unused so skip bytes
state = State.SkipLiteralHeaderValue;
break;
}
// Check new header size against max dynamic table size
if (newHeaderSize1 + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
state = State.SkipLiteralHeaderValue;
break;
}
}
if (valueLength == 0)
{
InsertHeader(headerListener, name, string.Empty, indexType);
state = State.ReadHeaderRepresentation;
}
else
{
state = State.ReadLiteralHeaderValue;
}
}
break;
case State.ReadLiteralHeaderValueLength:
// Header Value is a Literal String
valueLength = DecodeULE128(input);
if (valueLength == -1)
{
return;
}
// Check for numerical overflow
if (valueLength > int.MaxValue - index)
{
throw new IOException("decompression failure");
}
valueLength += index;
// Check new header size against max header size
long newHeaderSize2 = (long)nameLength + valueLength;
if (newHeaderSize2 + headerSize > maxHeaderSize)
{
// truncation will be reported during endHeaderBlock
headerSize = maxHeaderSize + 1;
if (indexType == HpackUtil.IndexType.None)
{
// Value is unused so skip bytes
state = State.SkipLiteralHeaderValue;
break;
}
// Check new header size against max dynamic table size
if (newHeaderSize2 + HttpHeader.HttpHeaderOverhead > dynamicTable.Capacity)
{
dynamicTable.Clear();
state = State.SkipLiteralHeaderValue;
break;
}
}
state = State.ReadLiteralHeaderValue;
break;
case State.ReadLiteralHeaderValue:
// Wait until entire value is readable
if (input.BaseStream.Length - input.BaseStream.Position < valueLength)
{
return;
}
var value = ReadStringLiteral(input, valueLength);
InsertHeader(headerListener, name, value, indexType);
state = State.ReadHeaderRepresentation;
break;
case State.SkipLiteralHeaderValue:
valueLength -= (int)input.BaseStream.Seek(valueLength, SeekOrigin.Current);
if (valueLength < 0)
{
valueLength = 0;
}
if (valueLength == 0)
{
state = State.ReadHeaderRepresentation;
}
break;
default:
throw new Exception("should not reach here");
}
}
}
/// <summary>
/// End the current header block. Returns if the header field has been truncated.
/// This must be called after the header block has been completely decoded.
/// </summary>
/// <returns><c>true</c>, if header block was ended, <c>false</c> otherwise.</returns>
public bool EndHeaderBlock()
{
bool truncated = headerSize > maxHeaderSize;
Reset();
return truncated;
}
/// <summary>
/// Set the maximum table size.
/// If this is below the maximum size of the dynamic table used by the encoder,
/// the beginning of the next header block MUST signal this change.
/// </summary>
/// <param name="maxHeaderTableSize">Max header table size.</param>
public void SetMaxHeaderTableSize(int maxHeaderTableSize)
{
maxDynamicTableSize = maxHeaderTableSize;
if (maxDynamicTableSize < encoderMaxDynamicTableSize)
{
// decoder requires less space than encoder
// encoder MUST signal this change
maxDynamicTableSizeChangeRequired = true;
dynamicTable.SetCapacity(maxDynamicTableSize);
}
}
/// <summary>
/// Return the maximum table size.
/// This is the maximum size allowed by both the encoder and the decoder.
/// </summary>
/// <returns>The max header table size.</returns>
public int GetMaxHeaderTableSize()
{
return dynamicTable.Capacity;
}
private void SetDynamicTableSize(int dynamicTableSize)
{
if (dynamicTableSize > maxDynamicTableSize)
{
throw new IOException("invalid max dynamic table size");
}
encoderMaxDynamicTableSize = dynamicTableSize;
maxDynamicTableSizeChangeRequired = false;
dynamicTable.SetCapacity(dynamicTableSize);
}
private HttpHeader GetHeaderField(int index)
{
if (index <= StaticTable.Length)
{
var headerField = StaticTable.Get(index);
return headerField;
}
else if (index - StaticTable.Length <= dynamicTable.Length())
{
var headerField = dynamicTable.GetEntry(index - StaticTable.Length);
return headerField;
}
else
{
throw new IOException("illegal index value (" + index + ")");
}
}
private void ReadName(int index)
{
name = GetHeaderField(index).Name;
}
private void IndexHeader(int index, IHeaderListener headerListener)
{
var headerField = GetHeaderField(index);
AddHeader(headerListener, headerField.Name, headerField.Value, false);
}
private void InsertHeader(IHeaderListener headerListener, string name, string value, HpackUtil.IndexType indexType)
{
AddHeader(headerListener, name, value, indexType == HpackUtil.IndexType.Never);
switch (indexType)
{
case HpackUtil.IndexType.None:
case HpackUtil.IndexType.Never:
break;
case HpackUtil.IndexType.Incremental:
dynamicTable.Add(new HttpHeader(name, value));
break;
default:
throw new Exception("should not reach here");
}
}
private void AddHeader(IHeaderListener headerListener, string name, string value, bool sensitive)
{
if (name.Length == 0)
{
throw new ArgumentException("name is empty");
}
long newSize = headerSize + name.Length + value.Length;
if (newSize <= maxHeaderSize)
{
headerListener.AddHeader(name, value, sensitive);
headerSize = (int)newSize;
}
else
{
// truncation will be reported during endHeaderBlock
headerSize = maxHeaderSize + 1;
}
}
private bool ExceedsMaxHeaderSize(long size)
{
// Check new header size against max header size
if (size + headerSize <= maxHeaderSize)
{
return false;
}
// truncation will be reported during endHeaderBlock
headerSize = maxHeaderSize + 1;
return true;
}
private string ReadStringLiteral(BinaryReader input, int length)
{
var buf = new byte[length];
int lengthToRead = length;
if (input.BaseStream.Length - input.BaseStream.Position < length)
{
lengthToRead = (int)input.BaseStream.Length - (int)input.BaseStream.Position;
}
int readBytes = input.Read(buf, 0, lengthToRead);
if (readBytes != length)
{
throw new IOException("decompression failure");
}
return huffmanEncoded ? HuffmanDecoder.Instance.Decode(buf) : Encoding.UTF8.GetString(buf);
}
// Unsigned Little Endian Base 128 Variable-Length Integer Encoding
private static int DecodeULE128(BinaryReader input)
{
long markedPosition = input.BaseStream.Position;
int result = 0;
int shift = 0;
while (shift < 32)
{
if (input.BaseStream.Length - input.BaseStream.Position == 0)
{
// Buffer does not contain entire integer,
// reset reader index and return -1.
input.BaseStream.Position = markedPosition;
return -1;
}
sbyte b = input.ReadSByte();
if (shift == 28 && (b & 0xF8) != 0)
{
break;
}
result |= (b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
return result;
}
shift += 7;
}
// Value exceeds Integer.MAX_VALUE
input.BaseStream.Position = markedPosition;
throw new IOException("decompression failure");
}
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class DynamicTable
{
// a circular queue of header fields
HttpHeader[] headerFields;
int head;
int tail;
/// <summary>
/// Return the maximum allowable size of the dynamic table.
/// </summary>
/// <value>
/// The capacity.
/// </value>
// ensure setCapacity creates the array
public int Capacity { get; private set; } = -1;
/// <summary>
/// Return the current size of the dynamic table.
/// This is the sum of the size of the entries.
/// </summary>
/// <value>
/// The size.
/// </value>
public int Size { get; private set; }
/// <summary>
/// Creates a new dynamic table with the specified initial capacity.
/// </summary>
/// <param name="initialCapacity">Initial capacity.</param>
public DynamicTable(int initialCapacity)
{
SetCapacity(initialCapacity);
}
/// <summary>
/// Return the number of header fields in the dynamic table.
/// </summary>
public int Length()
{
int length;
if (head < tail)
{
length = headerFields.Length - tail + head;
}
else
{
length = head - tail;
}
return length;
}
/// <summary>
/// Return the header field at the given index.
/// The first and newest entry is always at index 1,
/// and the oldest entry is at the index length().
/// </summary>
/// <returns>The entry.</returns>
/// <param name="index">Index.</param>
public HttpHeader GetEntry(int index)
{
if (index <= 0 || index > Length())
{
throw new IndexOutOfRangeException();
}
int i = head - index;
if (i < 0)
{
return headerFields[i + headerFields.Length];
}
return headerFields[i];
}
/// <summary>
/// Add the header field to the dynamic table.
/// Entries are evicted from the dynamic table until the size of the table
/// and the new header field is less than or equal to the table's capacity.
/// If the size of the new entry is larger than the table's capacity,
/// the dynamic table will be cleared.
/// </summary>
/// <param name="header">Header.</param>
public void Add(HttpHeader header)
{
int headerSize = header.Size;
if (headerSize > Capacity)
{
Clear();
return;
}
while (Size + headerSize > Capacity)
{
Remove();
}
headerFields[head++] = header;
Size += header.Size;
if (head == headerFields.Length)
{
head = 0;
}
}
/// <summary>
/// Remove and return the oldest header field from the dynamic table.
/// </summary>
public HttpHeader Remove()
{
var removed = headerFields[tail];
if (removed == null)
{
return null;
}
Size -= removed.Size;
headerFields[tail++] = null;
if (tail == headerFields.Length)
{
tail = 0;
}
return removed;
}
/// <summary>
/// Remove all entries from the dynamic table.
/// </summary>
public void Clear()
{
while (tail != head)
{
headerFields[tail++] = null;
if (tail == headerFields.Length)
{
tail = 0;
}
}
head = 0;
tail = 0;
Size = 0;
}
/// <summary>
/// Set the maximum size of the dynamic table.
/// Entries are evicted from the dynamic table until the size of the table
/// is less than or equal to the maximum size.
/// </summary>
/// <param name="capacity">Capacity.</param>
public void SetCapacity(int capacity)
{
if (capacity < 0)
{
throw new ArgumentException("Illegal Capacity: " + capacity);
}
// initially capacity will be -1 so init won't return here
if (Capacity == capacity)
{
return;
}
Capacity = capacity;
if (capacity == 0)
{
Clear();
}
else
{
// initially size will be 0 so remove won't be called
while (Size > capacity)
{
Remove();
}
}
int maxEntries = capacity / HttpHeader.HttpHeaderOverhead;
if (capacity % HttpHeader.HttpHeaderOverhead != 0)
{
maxEntries++;
}
// check if capacity change requires us to reallocate the array
if (headerFields != null && headerFields.Length == maxEntries)
{
return;
}
var tmp = new HttpHeader[maxEntries];
// initially length will be 0 so there will be no copy
int len = Length();
int cursor = tail;
for (int i = 0; i < len; i++)
{
var entry = headerFields[cursor++];
tmp[i] = entry;
if (cursor == headerFields.Length)
{
cursor = 0;
}
}
tail = 0;
head = tail + len;
headerFields = tmp;
}
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
using System.Text;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class Encoder
{
private const int bucketSize = 17;
// a linked hash map of header fields
private readonly HeaderEntry[] headerFields = new HeaderEntry[bucketSize];
private readonly HeaderEntry head = new HeaderEntry(-1, string.Empty, string.Empty, int.MaxValue, null);
private int size;
/// <summary>
/// Gets the the maximum table size.
/// </summary>
/// <value>
/// The max header table size.
/// </value>
public int MaxHeaderTableSize { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Encoder"/> class.
/// </summary>
/// <param name="maxHeaderTableSize">Max header table size.</param>
public Encoder(int maxHeaderTableSize)
{
if (maxHeaderTableSize < 0)
{
throw new ArgumentException("Illegal Capacity: " + maxHeaderTableSize);
}
MaxHeaderTableSize = maxHeaderTableSize;
head.Before = head.After = head;
}
/// <summary>
/// Encode the header field into the header block.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
/// <param name="sensitive">If set to <c>true</c> sensitive.</param>
public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false)
{
// If the header value is sensitive then it must never be indexed
if (sensitive)
{
int nameIndex = GetNameIndex(name);
EncodeLiteral(output, name, value, HpackUtil.IndexType.Never, nameIndex);
return;
}
// If the peer will only use the static table
if (MaxHeaderTableSize == 0)
{
int staticTableIndex = StaticTable.GetIndex(name, value);
if (staticTableIndex == -1)
{
int nameIndex = StaticTable.GetIndex(name);
EncodeLiteral(output, name, value, HpackUtil.IndexType.None, nameIndex);
}
else
{
EncodeInteger(output, 0x80, 7, staticTableIndex);
}
return;
}
int headerSize = HttpHeader.SizeOf(name, value);
// If the headerSize is greater than the max table size then it must be encoded literally
if (headerSize > MaxHeaderTableSize)
{
int nameIndex = GetNameIndex(name);
EncodeLiteral(output, name, value, HpackUtil.IndexType.None, nameIndex);
return;
}
var headerField = GetEntry(name, value);
if (headerField != null)
{
int index = GetIndex(headerField.Index) + StaticTable.Length;
// Section 6.1. Indexed Header Field Representation
EncodeInteger(output, 0x80, 7, index);
}
else
{
int staticTableIndex = StaticTable.GetIndex(name, value);
if (staticTableIndex != -1)
{
// Section 6.1. Indexed Header Field Representation
EncodeInteger(output, 0x80, 7, staticTableIndex);
}
else
{
int nameIndex = GetNameIndex(name);
EnsureCapacity(headerSize);
var indexType = HpackUtil.IndexType.Incremental;
EncodeLiteral(output, name, value, indexType, nameIndex);
Add(name, value);
}
}
}
/// <summary>
/// Set the maximum table size.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="maxHeaderTableSize">Max header table size.</param>
public void SetMaxHeaderTableSize(BinaryWriter output, int maxHeaderTableSize)
{
if (maxHeaderTableSize < 0)
{
throw new ArgumentException("Illegal Capacity", nameof(maxHeaderTableSize));
}
if (MaxHeaderTableSize == maxHeaderTableSize)
{
return;
}
MaxHeaderTableSize = maxHeaderTableSize;
EnsureCapacity(0);
EncodeInteger(output, 0x20, 5, maxHeaderTableSize);
}
/// <summary>
/// Encode integer according to Section 5.1.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="mask">Mask.</param>
/// <param name="n">N.</param>
/// <param name="i">The index.</param>
private static void EncodeInteger(BinaryWriter output, int mask, int n, int i)
{
if (n < 0 || n > 8)
{
throw new ArgumentException("N: " + n);
}
int nbits = 0xFF >> (8 - n);
if (i < nbits)
{
output.Write((byte)(mask | i));
}
else
{
output.Write((byte)(mask | nbits));
int length = i - nbits;
while (true)
{
if ((length & ~0x7F) == 0)
{
output.Write((byte)length);
return;
}
output.Write((byte)((length & 0x7F) | 0x80));
length >>= 7;
}
}
}
/// <summary>
/// Encode string literal according to Section 5.2.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="stringLiteral">String literal.</param>
private void EncodeStringLiteral(BinaryWriter output, string stringLiteral)
{
var stringData = Encoding.UTF8.GetBytes(stringLiteral);
int huffmanLength = HuffmanEncoder.Instance.GetEncodedLength(stringData);
if (huffmanLength < stringLiteral.Length)
{
EncodeInteger(output, 0x80, 7, huffmanLength);
HuffmanEncoder.Instance.Encode(output, stringData);
}
else
{
EncodeInteger(output, 0x00, 7, stringData.Length);
output.Write(stringData, 0, stringData.Length);
}
}
/// <summary>
/// Encode literal header field according to Section 6.2.
/// </summary>
/// <param name="output">Output.</param>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
/// <param name="indexType">Index type.</param>
/// <param name="nameIndex">Name index.</param>
private void EncodeLiteral(BinaryWriter output, string name, string value, HpackUtil.IndexType indexType,
int nameIndex)
{
int mask;
int prefixBits;
switch (indexType)
{
case HpackUtil.IndexType.Incremental:
mask = 0x40;
prefixBits = 6;
break;
case HpackUtil.IndexType.None:
mask = 0x00;
prefixBits = 4;
break;
case HpackUtil.IndexType.Never:
mask = 0x10;
prefixBits = 4;
break;
default:
throw new Exception("should not reach here");
}
EncodeInteger(output, mask, prefixBits, nameIndex == -1 ? 0 : nameIndex);
if (nameIndex == -1)
{
EncodeStringLiteral(output, name);
}
EncodeStringLiteral(output, value);
}
private int GetNameIndex(string name)
{
int index = StaticTable.GetIndex(name);
if (index == -1)
{
index = GetIndex(name);
if (index >= 0)
{
index += StaticTable.Length;
}
}
return index;
}
/// <summary>
/// Ensure that the dynamic table has enough room to hold 'headerSize' more bytes.
/// Removes the oldest entry from the dynamic table until sufficient space is available.
/// </summary>
/// <param name="headerSize">Header size.</param>
private void EnsureCapacity(int headerSize)
{
while (size + headerSize > MaxHeaderTableSize)
{
int index = Length();
if (index == 0)
{
break;
}
Remove();
}
}
/// <summary>
/// Return the number of header fields in the dynamic table.
/// </summary>
private int Length()
{
return size == 0 ? 0 : head.After.Index - head.Before.Index + 1;
}
/// <summary>
/// Returns the header entry with the lowest index value for the header field.
/// Returns null if header field is not in the dynamic table.
/// </summary>
/// <returns>The entry.</returns>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
private HeaderEntry GetEntry(string name, string value)
{
if (Length() == 0 || name == null || value == null)
{
return null;
}
int h = Hash(name);
int i = Index(h);
for (var e = headerFields[i]; e != null; e = e.Next)
{
if (e.Hash == h && Equals(name, e.Name) && Equals(value, e.Value))
{
return e;
}
}
return null;
}
/// <summary>
/// Returns the lowest index value for the header field name in the dynamic table.
/// Returns -1 if the header field name is not in the dynamic table.
/// </summary>
/// <returns>The index.</returns>
/// <param name="name">Name.</param>
private int GetIndex(string name)
{
if (Length() == 0 || name == null)
{
return -1;
}
int h = Hash(name);
int i = Index(h);
int index = -1;
for (var e = headerFields[i]; e != null; e = e.Next)
{
if (e.Hash == h && HpackUtil.Equals(name, e.Name))
{
index = e.Index;
break;
}
}
return GetIndex(index);
}
/// <summary>
/// Compute the index into the dynamic table given the index in the header entry.
/// </summary>
/// <returns>The index.</returns>
/// <param name="index">Index.</param>
private int GetIndex(int index)
{
if (index == -1)
{
return index;
}
return index - head.Before.Index + 1;
}
/// <summary>
/// Add the header field to the dynamic table.
/// Entries are evicted from the dynamic table until the size of the table
/// and the new header field is less than the table's capacity.
/// If the size of the new entry is larger than the table's capacity,
/// the dynamic table will be cleared.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
private void Add(string name, string value)
{
int headerSize = HttpHeader.SizeOf(name, value);
// Clear the table if the header field size is larger than the capacity.
if (headerSize > MaxHeaderTableSize)
{
Clear();
return;
}
// Evict oldest entries until we have enough capacity.
while (size + headerSize > MaxHeaderTableSize)
{
Remove();
}
int h = Hash(name);
int i = Index(h);
var old = headerFields[i];
var e = new HeaderEntry(h, name, value, head.Before.Index - 1, old);
headerFields[i] = e;
e.AddBefore(head);
size += headerSize;
}
/// <summary>
/// Remove and return the oldest header field from the dynamic table.
/// </summary>
private HttpHeader Remove()
{
if (size == 0)
{
return null;
}
var eldest = head.After;
int h = eldest.Hash;
int i = Index(h);
var prev = headerFields[i];
var e = prev;
while (e != null)
{
var next = e.Next;
if (e == eldest)
{
if (prev == eldest)
{
headerFields[i] = next;
}
else
{
prev.Next = next;
}
eldest.Remove();
size -= eldest.Size;
return eldest;
}
prev = e;
e = next;
}
return null;
}
/// <summary>
/// Remove all entries from the dynamic table.
/// </summary>
private void Clear()
{
for (int i = 0; i < headerFields.Length; i++)
{
headerFields[i] = null;
}
head.Before = head.After = head;
size = 0;
}
/// <summary>
/// Returns the hash code for the given header field name.
/// </summary>
/// <returns><c>true</c> if hash name; otherwise, <c>false</c>.</returns>
/// <param name="name">Name.</param>
private static int Hash(string name)
{
int h = 0;
for (int i = 0; i < name.Length; i++)
{
h = 31 * h + name[i];
}
if (h > 0)
{
return h;
}
if (h == int.MinValue)
{
return int.MaxValue;
}
return -h;
}
/// <summary>
/// Returns the index into the hash table for the hash code h.
/// </summary>
/// <param name="h">The height.</param>
private static int Index(int h)
{
return h % bucketSize;
}
/// <summary>
/// A linked hash map HeaderField entry.
/// </summary>
private class HeaderEntry : HttpHeader
{
// This is used to compute the index in the dynamic table.
// These properties comprise the doubly linked list used for iteration.
public HeaderEntry Before { get; set; }
public HeaderEntry After { get; set; }
// These fields comprise the chained list for header fields with the same hash.
public HeaderEntry Next { get; set; }
public int Hash { get; }
public int Index { get; }
/// <summary>
/// Creates new entry.
/// </summary>
/// <param name="hash">Hash.</param>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
/// <param name="index">Index.</param>
/// <param name="next">Next.</param>
public HeaderEntry(int hash, string name, string value, int index, HeaderEntry next) : base(name, value)
{
Index = index;
Hash = hash;
Next = next;
}
/// <summary>
/// Removes this entry from the linked list.
/// </summary>
public void Remove()
{
Before.After = After;
After.Before = Before;
}
/// <summary>
/// Inserts this entry before the specified existing entry in the list.
/// </summary>
/// <param name="existingEntry">Existing entry.</param>
public void AddBefore(HeaderEntry existingEntry)
{
After = existingEntry;
Before = existingEntry.Before;
Before.After = this;
After.Before = this;
}
}
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public static class HpackUtil
{
// Section 6.2. Literal Header Field Representation
public enum IndexType
{
Incremental, // Section 6.2.1. Literal Header Field with Incremental Indexing
None, // Section 6.2.2. Literal Header Field without Indexing
Never // Section 6.2.3. Literal Header Field never Indexed
}
// Appendix B: Huffman Codes
// http://tools.ietf.org/html/rfc7541#appendix-B
public static readonly int[] HuffmanCodes = {
0x1ff8,
0x7fffd8,
0xfffffe2,
0xfffffe3,
0xfffffe4,
0xfffffe5,
0xfffffe6,
0xfffffe7,
0xfffffe8,
0xffffea,
0x3ffffffc,
0xfffffe9,
0xfffffea,
0x3ffffffd,
0xfffffeb,
0xfffffec,
0xfffffed,
0xfffffee,
0xfffffef,
0xffffff0,
0xffffff1,
0xffffff2,
0x3ffffffe,
0xffffff3,
0xffffff4,
0xffffff5,
0xffffff6,
0xffffff7,
0xffffff8,
0xffffff9,
0xffffffa,
0xffffffb,
0x14,
0x3f8,
0x3f9,
0xffa,
0x1ff9,
0x15,
0xf8,
0x7fa,
0x3fa,
0x3fb,
0xf9,
0x7fb,
0xfa,
0x16,
0x17,
0x18,
0x0,
0x1,
0x2,
0x19,
0x1a,
0x1b,
0x1c,
0x1d,
0x1e,
0x1f,
0x5c,
0xfb,
0x7ffc,
0x20,
0xffb,
0x3fc,
0x1ffa,
0x21,
0x5d,
0x5e,
0x5f,
0x60,
0x61,
0x62,
0x63,
0x64,
0x65,
0x66,
0x67,
0x68,
0x69,
0x6a,
0x6b,
0x6c,
0x6d,
0x6e,
0x6f,
0x70,
0x71,
0x72,
0xfc,
0x73,
0xfd,
0x1ffb,
0x7fff0,
0x1ffc,
0x3ffc,
0x22,
0x7ffd,
0x3,
0x23,
0x4,
0x24,
0x5,
0x25,
0x26,
0x27,
0x6,
0x74,
0x75,
0x28,
0x29,
0x2a,
0x7,
0x2b,
0x76,
0x2c,
0x8,
0x9,
0x2d,
0x77,
0x78,
0x79,
0x7a,
0x7b,
0x7ffe,
0x7fc,
0x3ffd,
0x1ffd,
0xffffffc,
0xfffe6,
0x3fffd2,
0xfffe7,
0xfffe8,
0x3fffd3,
0x3fffd4,
0x3fffd5,
0x7fffd9,
0x3fffd6,
0x7fffda,
0x7fffdb,
0x7fffdc,
0x7fffdd,
0x7fffde,
0xffffeb,
0x7fffdf,
0xffffec,
0xffffed,
0x3fffd7,
0x7fffe0,
0xffffee,
0x7fffe1,
0x7fffe2,
0x7fffe3,
0x7fffe4,
0x1fffdc,
0x3fffd8,
0x7fffe5,
0x3fffd9,
0x7fffe6,
0x7fffe7,
0xffffef,
0x3fffda,
0x1fffdd,
0xfffe9,
0x3fffdb,
0x3fffdc,
0x7fffe8,
0x7fffe9,
0x1fffde,
0x7fffea,
0x3fffdd,
0x3fffde,
0xfffff0,
0x1fffdf,
0x3fffdf,
0x7fffeb,
0x7fffec,
0x1fffe0,
0x1fffe1,
0x3fffe0,
0x1fffe2,
0x7fffed,
0x3fffe1,
0x7fffee,
0x7fffef,
0xfffea,
0x3fffe2,
0x3fffe3,
0x3fffe4,
0x7ffff0,
0x3fffe5,
0x3fffe6,
0x7ffff1,
0x3ffffe0,
0x3ffffe1,
0xfffeb,
0x7fff1,
0x3fffe7,
0x7ffff2,
0x3fffe8,
0x1ffffec,
0x3ffffe2,
0x3ffffe3,
0x3ffffe4,
0x7ffffde,
0x7ffffdf,
0x3ffffe5,
0xfffff1,
0x1ffffed,
0x7fff2,
0x1fffe3,
0x3ffffe6,
0x7ffffe0,
0x7ffffe1,
0x3ffffe7,
0x7ffffe2,
0xfffff2,
0x1fffe4,
0x1fffe5,
0x3ffffe8,
0x3ffffe9,
0xffffffd,
0x7ffffe3,
0x7ffffe4,
0x7ffffe5,
0xfffec,
0xfffff3,
0xfffed,
0x1fffe6,
0x3fffe9,
0x1fffe7,
0x1fffe8,
0x7ffff3,
0x3fffea,
0x3fffeb,
0x1ffffee,
0x1ffffef,
0xfffff4,
0xfffff5,
0x3ffffea,
0x7ffff4,
0x3ffffeb,
0x7ffffe6,
0x3ffffec,
0x3ffffed,
0x7ffffe7,
0x7ffffe8,
0x7ffffe9,
0x7ffffea,
0x7ffffeb,
0xffffffe,
0x7ffffec,
0x7ffffed,
0x7ffffee,
0x7ffffef,
0x7fffff0,
0x3ffffee,
0x3fffffff // EOS
};
public static readonly byte[] HuffmanCodeLengths = {
13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28,
28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28,
6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6,
5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10,
13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6,
15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5,
6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28,
20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23,
24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24,
22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23,
21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23,
26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25,
19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27,
20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23,
26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26,
30 // EOS
};
public const int HuffmanEos = 256;
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
using System.Text;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class HuffmanDecoder
{
/// <summary>
/// Huffman Decoder
/// </summary>
public static readonly HuffmanDecoder Instance = new HuffmanDecoder();
private readonly Node root;
/// <summary>
/// Creates a new Huffman decoder with the specified Huffman coding.
/// </summary>
private HuffmanDecoder()
{
// the Huffman codes indexed by symbol
var codes = HpackUtil.HuffmanCodes;
// the length of each Huffman code
var lengths = HpackUtil.HuffmanCodeLengths;
if (codes.Length != 257 || codes.Length != lengths.Length)
{
throw new ArgumentException("invalid Huffman coding");
}
root = BuildTree(codes, lengths);
}
/// <summary>
/// Decompresses the given Huffman coded string literal.
/// </summary>
/// <param name="buf">the string literal to be decoded</param>
/// <returns>the output stream for the compressed data</returns>
/// <exception cref="IOException">throws IOException if an I/O error occurs. In particular, an <code>IOException</code> may be thrown if the output stream has been closed.</exception>
public string Decode(byte[] buf)
{
var resultBuf = new byte[buf.Length * 2];
int resultSize = 0;
var node = root;
int current = 0;
int bits = 0;
for (int i = 0; i < buf.Length; i++)
{
int b = buf[i];
current = (current << 8) | b;
bits += 8;
while (bits >= 8)
{
int c = (current >> (bits - 8)) & 0xFF;
node = node.Children[c];
bits -= node.Bits;
if (node.IsTerminal)
{
if (node.Symbol == HpackUtil.HuffmanEos)
{
throw new IOException("EOS Decoded");
}
resultBuf[resultSize++] = (byte)node.Symbol;
node = root;
}
}
}
while (bits > 0)
{
int c = (current << (8 - bits)) & 0xFF;
node = node.Children[c];
if (node.IsTerminal && node.Bits <= bits)
{
bits -= node.Bits;
resultBuf[resultSize++] = (byte)node.Symbol;
node = root;
}
else
{
break;
}
}
// Section 5.2. String Literal Representation
// Padding not corresponding to the most significant bits of the code
// for the EOS symbol (0xFF) MUST be treated as a decoding error.
int mask = (1 << bits) - 1;
if ((current & mask) != mask)
{
throw new IOException("Invalid Padding");
}
return Encoding.UTF8.GetString(resultBuf, 0, resultSize);
}
private class Node
{
// terminal nodes have a symbol
public int Symbol { get; }
// number of bits matched by the node
public int Bits { get; }
// internal nodes have children
public Node[] Children { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanDecoder"/> class.
/// </summary>
public Node()
{
Symbol = 0;
Bits = 8;
Children = new Node[256];
}
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanDecoder"/> class.
/// </summary>
/// <param name="symbol">the symbol the node represents</param>
/// <param name="bits">the number of bits matched by this node</param>
public Node(int symbol, int bits)
{
//assert(bits > 0 && bits <= 8);
Symbol = symbol;
Bits = bits;
Children = null;
}
public bool IsTerminal => Children == null;
}
private static Node BuildTree(int[] codes, byte[] lengths)
{
var root = new Node();
for (int i = 0; i < codes.Length; i++)
{
Insert(root, i, codes[i], lengths[i]);
}
return root;
}
private static void Insert(Node root, int symbol, int code, byte length)
{
// traverse tree using the most significant bytes of code
var current = root;
while (length > 8)
{
if (current.IsTerminal)
{
throw new InvalidDataException("invalid Huffman code: prefix not unique");
}
length -= 8;
int i = (code >> length) & 0xFF;
if (current.Children[i] == null)
{
current.Children[i] = new Node();
}
current = current.Children[i];
}
var terminal = new Node(symbol, length);
int shift = 8 - length;
int start = (code << shift) & 0xFF;
int end = 1 << shift;
for (int i = start; i < start + end; i++)
{
current.Children[i] = terminal;
}
}
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public class HuffmanEncoder
{
/// <summary>
/// Huffman Encoder
/// </summary>
public static readonly HuffmanEncoder Instance = new HuffmanEncoder();
/// <summary>
/// the Huffman codes indexed by symbol
/// </summary>
private readonly int[] codes = HpackUtil.HuffmanCodes;
/// <summary>
/// the length of each Huffman code
/// </summary>
private readonly byte[] lengths = HpackUtil.HuffmanCodeLengths;
/// <summary>
/// Compresses the input string literal using the Huffman coding.
/// </summary>
/// <param name="output">the output stream for the compressed data</param>
/// <param name="data">the string literal to be Huffman encoded</param>
/// <exception cref="IOException">if an I/O error occurs.</exception>
/// <see cref="Encode(BinaryWriter,byte[],int,int)"/>
public void Encode(BinaryWriter output, byte[] data)
{
Encode(output, data, 0, data.Length);
}
/// <summary>
/// Compresses the input string literal using the Huffman coding.
/// </summary>
/// <param name="output">the output stream for the compressed data</param>
/// <param name="data">the string literal to be Huffman encoded</param>
/// <param name="off">the start offset in the data</param>
/// <param name="len">the number of bytes to encode</param>
/// <exception cref="IOException">if an I/O error occurs. In particular, an <code>IOException</code> may be thrown if the output stream has been closed.</exception>
public void Encode(BinaryWriter output, byte[] data, int off, int len)
{
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
if (off < 0 || len < 0 || (off + len) < 0 || off > data.Length || (off + len) > data.Length)
{
throw new IndexOutOfRangeException();
}
if (len == 0)
{
return;
}
long current = 0L;
int n = 0;
for (int i = 0; i < len; i++)
{
int b = data[off + i] & 0xFF;
uint code = (uint)codes[b];
int nbits = lengths[b];
current <<= nbits;
current |= code;
n += nbits;
while (n >= 8)
{
n -= 8;
output.Write(((byte)(current >> n)));
}
}
if (n > 0)
{
current <<= (8 - n);
current |= (uint)(0xFF >> n); // this should be EOS symbol
output.Write((byte)current);
}
}
/// <summary>
/// Returns the number of bytes required to Huffman encode the input string literal.
/// </summary>
/// <returns>the number of bytes required to Huffman encode <code>data</code></returns>
/// <param name="data">the string literal to be Huffman encoded</param>
public int GetEncodedLength(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
long len = 0L;
foreach (byte b in data)
{
len += lengths[b];
}
return (int)((len + 7) >> 3);
}
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Titanium.Web.Proxy.Http2.Hpack
{
public interface IHeaderListener
{
/// <summary>
/// EmitHeader is called by the decoder during header field emission.
/// The name and value byte arrays must not be modified.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
/// <param name="sensitive">If set to <c>true</c> sensitive.</param>
void AddHeader(string name, string value, bool sensitive);
}
}
/*
* Copyright 2014 Twitter, Inc
* This file is a derivative work modified by Ringo Leese
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Text;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Http2.Hpack
{
public static class StaticTable
{
/// <summary>
/// Appendix A: Static Table Definition
/// </summary>
/// <see cref="http://tools.ietf.org/html/rfc7541#appendix-A"/>
private static readonly List<HttpHeader> staticTable = new List<HttpHeader>()
{
/* 1 */
new HttpHeader(":authority", string.Empty),
/* 2 */
new HttpHeader(":method", "GET"),
/* 3 */
new HttpHeader(":method", "POST"),
/* 4 */
new HttpHeader(":path", "/"),
/* 5 */
new HttpHeader(":path", "/index.html"),
/* 6 */
new HttpHeader(":scheme", "http"),
/* 7 */
new HttpHeader(":scheme", "https"),
/* 8 */
new HttpHeader(":status", "200"),
/* 9 */
new HttpHeader(":status", "204"),
/* 10 */
new HttpHeader(":status", "206"),
/* 11 */
new HttpHeader(":status", "304"),
/* 12 */
new HttpHeader(":status", "400"),
/* 13 */
new HttpHeader(":status", "404"),
/* 14 */
new HttpHeader(":status", "500"),
/* 15 */
new HttpHeader("accept-charset", string.Empty),
/* 16 */
new HttpHeader("accept-encoding", "gzip, deflate"),
/* 17 */
new HttpHeader("accept-language", string.Empty),
/* 18 */
new HttpHeader("accept-ranges", string.Empty),
/* 19 */
new HttpHeader("accept", string.Empty),
/* 20 */
new HttpHeader("access-control-allow-origin", string.Empty),
/* 21 */
new HttpHeader("age", string.Empty),
/* 22 */
new HttpHeader("allow", string.Empty),
/* 23 */
new HttpHeader("authorization", string.Empty),
/* 24 */
new HttpHeader("cache-control", string.Empty),
/* 25 */
new HttpHeader("content-disposition", string.Empty),
/* 26 */
new HttpHeader("content-encoding", string.Empty),
/* 27 */
new HttpHeader("content-language", string.Empty),
/* 28 */
new HttpHeader("content-length", string.Empty),
/* 29 */
new HttpHeader("content-location", string.Empty),
/* 30 */
new HttpHeader("content-range", string.Empty),
/* 31 */
new HttpHeader("content-type", string.Empty),
/* 32 */
new HttpHeader("cookie", string.Empty),
/* 33 */
new HttpHeader("date", string.Empty),
/* 34 */
new HttpHeader("etag", string.Empty),
/* 35 */
new HttpHeader("expect", string.Empty),
/* 36 */
new HttpHeader("expires", string.Empty),
/* 37 */
new HttpHeader("from", string.Empty),
/* 38 */
new HttpHeader("host", string.Empty),
/* 39 */
new HttpHeader("if-match", string.Empty),
/* 40 */
new HttpHeader("if-modified-since", string.Empty),
/* 41 */
new HttpHeader("if-none-match", string.Empty),
/* 42 */
new HttpHeader("if-range", string.Empty),
/* 43 */
new HttpHeader("if-unmodified-since", string.Empty),
/* 44 */
new HttpHeader("last-modified", string.Empty),
/* 45 */
new HttpHeader("link", string.Empty),
/* 46 */
new HttpHeader("location", string.Empty),
/* 47 */
new HttpHeader("max-forwards", string.Empty),
/* 48 */
new HttpHeader("proxy-authenticate", string.Empty),
/* 49 */
new HttpHeader("proxy-authorization", string.Empty),
/* 50 */
new HttpHeader("range", string.Empty),
/* 51 */
new HttpHeader("referer", string.Empty),
/* 52 */
new HttpHeader("refresh", string.Empty),
/* 53 */
new HttpHeader("retry-after", string.Empty),
/* 54 */
new HttpHeader("server", string.Empty),
/* 55 */
new HttpHeader("set-cookie", string.Empty),
/* 56 */
new HttpHeader("strict-transport-security", string.Empty),
/* 57 */
new HttpHeader("transfer-encoding", string.Empty),
/* 58 */
new HttpHeader("user-agent", string.Empty),
/* 59 */
new HttpHeader("vary", string.Empty),
/* 60 */
new HttpHeader("via", string.Empty),
/* 61 */
new HttpHeader("www-authenticate", string.Empty)
};
private static readonly Dictionary<string, int> staticIndexByName = CreateMap();
/// <summary>
/// The number of header fields in the static table.
/// </summary>
/// <value>The length.</value>
public static int Length => staticTable.Count;
/// <summary>
/// Return the http header field at the given index value.
/// </summary>
/// <returns>The header field.</returns>
/// <param name="index">Index.</param>
public static HttpHeader Get(int index)
{
return staticTable[index - 1];
}
/// <summary>
/// Returns the lowest index value for the given header field name in the static table.
/// Returns -1 if the header field name is not in the static table.
/// </summary>
/// <returns>The index.</returns>
/// <param name="name">Name.</param>
public static int GetIndex(string name)
{
if (!staticIndexByName.ContainsKey(name))
{
return -1;
}
return staticIndexByName[name];
}
/// <summary>
/// Returns the index value for the given header field in the static table.
/// Returns -1 if the header field is not in the static table.
/// </summary>
/// <returns>The index.</returns>
/// <param name="name">Name.</param>
/// <param name="value">Value.</param>
public static int GetIndex(string name, string value)
{
int index = GetIndex(name);
if (index == -1)
{
return -1;
}
// Note this assumes all entries for a given header field are sequential.
while (index <= Length)
{
var entry = Get(index);
if (!HpackUtil.Equals(name, entry.Name))
{
break;
}
if (HpackUtil.Equals(value, entry.Value))
{
return index;
}
index++;
}
return -1;
}
/// <summary>
/// create a map of header name to index value to allow quick lookup
/// </summary>
/// <returns>The map.</returns>
private static Dictionary<string, int> CreateMap()
{
int length = staticTable.Count;
var ret = new Dictionary<string, int>(length);
// Iterate through the static table in reverse order to
// save the smallest index for a given name in the map.
for (int index = length; index > 0; index--)
{
var entry = Get(index);
string name = entry.Name;
ret[name] = index;
}
return ret;
}
}
}
\ No newline at end of file
#if NETCOREAPP2_1
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http2.Hpack;
namespace Titanium.Web.Proxy.Http2
{
[Flags]
internal enum Http2FrameFlag
{
Ack = 0x01,
EndStream = 0x01,
EndHeaders = 0x04,
Padded = 0x08,
Priority = 0x20,
}
internal class Http2Helper
{
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers
/// as prefix
/// Usefull for websocket requests
/// Task-based Asynchronous Pattern
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="connectionId"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendHttp2(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource, Guid connectionId,
ExceptionHandler exceptionFunc)
{
// Now async relay all server=>client & client=>server data
var sendRelay =
CopyHttp2FrameAsync(clientStream, serverStream, onDataSend, bufferSize, connectionId,
true, cancellationTokenSource.Token);
var receiveRelay =
CopyHttp2FrameAsync(serverStream, clientStream, onDataReceive, bufferSize, connectionId,
false, cancellationTokenSource.Token);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
await Task.WhenAll(sendRelay, receiveRelay);
}
private static async Task CopyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize, Guid connectionId, bool isClient, CancellationToken cancellationToken)
{
var decoder = new Decoder(8192, 4096);
var headerBuffer = new byte[9];
var buffer = new byte[32768];
while (true)
{
int read = await ForceRead(input, headerBuffer, 0, 9, cancellationToken);
if (read != 9)
{
return;
}
int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2];
byte type = headerBuffer[3];
byte flags = headerBuffer[4];
int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) +
headerBuffer[8];
read = await ForceRead(input, buffer, 0, length, cancellationToken);
if (read != length)
{
return;
}
if (isClient)
{
if (type == 1 /*headers*/)
{
bool endHeaders = (flags & (int)Http2FrameFlag.EndHeaders) != 0;
bool padded = (flags & (int)Http2FrameFlag.Padded) != 0;
bool priority = (flags & (int)Http2FrameFlag.Priority) != 0;
System.Diagnostics.Debug.WriteLine("HEADER: " + streamId + " end: " + endHeaders);
int offset = 0;
if (padded)
{
offset = 1;
}
if (priority)
{
offset += 5;
}
int dataLength = length - offset;
if (padded)
{
dataLength -= buffer[0];
}
var headerListener = new MyHeaderListener();
try
{
decoder.Decode(new BinaryReader(new MemoryStream(buffer, offset, dataLength)),
headerListener);
decoder.EndHeaderBlock();
}
catch (Exception)
{
}
}
}
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken);
await output.WriteAsync(buffer, 0, length, cancellationToken);
/*using (var fs = new System.IO.FileStream($@"c:\11\{connectionId}.{streamId}.dat", FileMode.Append))
{
fs.Write(headerBuffer, 0, headerBuffer.Length);
fs.Write(buffer, 0, length);
}*/
}
}
private static async Task<int> ForceRead(Stream input, byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken)
{
int totalRead = 0;
while (bytesToRead > 0)
{
int read = await input.ReadAsync(buffer, offset, bytesToRead, cancellationToken);
if (read == -1)
{
break;
}
totalRead += read;
bytesToRead -= read;
offset += read;
}
return totalRead;
}
class MyHeaderListener : IHeaderListener
{
public void AddHeader(string name, string value, bool sensitive)
{
Console.WriteLine(name + ": " + value + " " + sensitive);
}
}
}
}
#endif
\ No newline at end of file
......@@ -9,6 +9,13 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class HttpHeader
{
/// <summary>
/// HPACK: Header Compression for HTTP/2
/// Section 4.1. Calculating Table Size
/// The additional 32 octets account for an estimated overhead associated with an entry.
/// </summary>
public const int HttpHeaderOverhead = 32;
internal static readonly Version VersionUnknown = new Version(0, 0);
internal static readonly Version Version10 = new Version(1, 0);
......@@ -45,6 +52,13 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public string Value { get; set; }
public int Size => Name.Length + Value.Length + HttpHeaderOverhead;
public static int SizeOf(string name, string value)
{
return name.Length + value.Length + HttpHeaderOverhead;
}
/// <summary>
/// Returns header as a valid header string.
/// </summary>
......
using System;
using System.IO;
using System.Net;
#if NETCOREAPP2_1
using System.Net.Security;
#endif
using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
......
using System;
using System.Net;
#if NETCOREAPP2_1
using System.Net.Security;
#endif
using System.Net.Sockets;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
......
......@@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
#if NETCOREAPP2_1
using System.Net.Security;
#endif
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
......@@ -472,7 +475,6 @@ namespace Titanium.Web.Proxy
/// Gets the connection cache key.
/// </summary>
/// <param name="args">The session event arguments.</param>
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="applicationProtocol"></param>
/// <returns></returns>
private async Task<string> getConnectionCacheKey(SessionEventArgsBase args,
......
......@@ -38,4 +38,27 @@
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\LittleEndian.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\Message.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Network\WinAuth\Security\State.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
<Compile Update="Properties\AssemblyInfo.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
<ExcludeFromStyleCop>True</ExcludeFromStyleCop>
</Compile>
</ItemGroup>
</Project>
\ No newline at end of file
......@@ -155,6 +155,35 @@
</tr>
</tbody>
</table>
<h3 id="fields">Fields
</h3>
<h4 id="Titanium_Web_Proxy_Models_HttpHeader_HttpHeaderOverhead" data-uid="Titanium.Web.Proxy.Models.HttpHeader.HttpHeaderOverhead">HttpHeaderOverhead</h4>
<div class="markdown level1 summary"><p>HPACK: Header Compression for HTTP/2
Section 4.1. Calculating Table Size
The additional 32 octets account for an estimated overhead associated with an entry. </p>
</div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public const int HttpHeaderOverhead = 32</code></pre>
</div>
<h5 class="fieldValue">Field Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.int32">Int32</a></td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="properties">Properties
</h3>
......@@ -185,6 +214,31 @@
</table>
<a id="Titanium_Web_Proxy_Models_HttpHeader_Size_" data-uid="Titanium.Web.Proxy.Models.HttpHeader.Size*"></a>
<h4 id="Titanium_Web_Proxy_Models_HttpHeader_Size" data-uid="Titanium.Web.Proxy.Models.HttpHeader.Size">Size</h4>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public int Size { get; }</code></pre>
</div>
<h5 class="propertyValue">Property Value</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.int32">Int32</a></td>
<td></td>
</tr>
</tbody>
</table>
<a id="Titanium_Web_Proxy_Models_HttpHeader_Value_" data-uid="Titanium.Web.Proxy.Models.HttpHeader.Value*"></a>
<h4 id="Titanium_Web_Proxy_Models_HttpHeader_Value" data-uid="Titanium.Web.Proxy.Models.HttpHeader.Value">Value</h4>
<div class="markdown level1 summary"><p>Header Value.</p>
......@@ -213,6 +267,53 @@
</h3>
<a id="Titanium_Web_Proxy_Models_HttpHeader_SizeOf_" data-uid="Titanium.Web.Proxy.Models.HttpHeader.SizeOf*"></a>
<h4 id="Titanium_Web_Proxy_Models_HttpHeader_SizeOf_System_String_System_String_" data-uid="Titanium.Web.Proxy.Models.HttpHeader.SizeOf(System.String,System.String)">SizeOf(String, String)</h4>
<div class="markdown level1 summary"></div>
<div class="markdown level1 conceptual"></div>
<h5 class="decalaration">Declaration</h5>
<div class="codewrapper">
<pre><code class="lang-csharp hljs">public static int SizeOf(string name, string value)</code></pre>
</div>
<h5 class="parameters">Parameters</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.string">String</a></td>
<td><span class="parametername">name</span></td>
<td></td>
</tr>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.string">String</a></td>
<td><span class="parametername">value</span></td>
<td></td>
</tr>
</tbody>
</table>
<h5 class="returns">Returns</h5>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a class="xref" href="https://docs.microsoft.com/dotnet/api/system.int32">Int32</a></td>
<td></td>
</tr>
</tbody>
</table>
<a id="Titanium_Web_Proxy_Models_HttpHeader_ToString_" data-uid="Titanium.Web.Proxy.Models.HttpHeader.ToString*"></a>
<h4 id="Titanium_Web_Proxy_Models_HttpHeader_ToString" data-uid="Titanium.Web.Proxy.Models.HttpHeader.ToString">ToString()</h4>
<div class="markdown level1 summary"><p>Returns header as a valid header string.</p>
......
......@@ -162,7 +162,7 @@
"api/Titanium.Web.Proxy.Models.HttpHeader.html": {
"href": "api/Titanium.Web.Proxy.Models.HttpHeader.html",
"title": "Class HttpHeader | Titanium Web Proxy",
"keywords": "Class HttpHeader Http Header object used by proxy Inheritance Object HttpHeader Inherited Members Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Models Assembly : Titanium.Web.Proxy.dll Syntax public class HttpHeader Constructors HttpHeader(String, String) Initialize a new instance. Declaration public HttpHeader(string name, string value) Parameters Type Name Description String name Header name. String value Header value. Properties Name Header Name. Declaration public string Name { get; set; } Property Value Type Description String Value Header Value. Declaration public string Value { get; set; } Property Value Type Description String Methods ToString() Returns header as a valid header string. Declaration public override string ToString() Returns Type Description String Overrides Object.ToString()"
"keywords": "Class HttpHeader Http Header object used by proxy Inheritance Object HttpHeader Inherited Members Object.Equals(Object) Object.Equals(Object, Object) Object.ReferenceEquals(Object, Object) Object.GetHashCode() Object.GetType() Object.MemberwiseClone() Namespace : Titanium.Web.Proxy.Models Assembly : Titanium.Web.Proxy.dll Syntax public class HttpHeader Constructors HttpHeader(String, String) Initialize a new instance. Declaration public HttpHeader(string name, string value) Parameters Type Name Description String name Header name. String value Header value. Fields HttpHeaderOverhead HPACK: Header Compression for HTTP/2 Section 4.1. Calculating Table Size The additional 32 octets account for an estimated overhead associated with an entry. Declaration public const int HttpHeaderOverhead = 32 Field Value Type Description Int32 Properties Name Header Name. Declaration public string Name { get; set; } Property Value Type Description String Size Declaration public int Size { get; } Property Value Type Description Int32 Value Header Value. Declaration public string Value { get; set; } Property Value Type Description String Methods SizeOf(String, String) Declaration public static int SizeOf(string name, string value) Parameters Type Name Description String name String value Returns Type Description Int32 ToString() Returns header as a valid header string. Declaration public override string ToString() Returns Type Description String Overrides Object.ToString()"
},
"api/Titanium.Web.Proxy.Models.ProxyEndPoint.html": {
"href": "api/Titanium.Web.Proxy.Models.ProxyEndPoint.html",
......
......@@ -2070,6 +2070,12 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.HttpHeader.HttpHeader
nameWithType: HttpHeader.HttpHeader
- uid: Titanium.Web.Proxy.Models.HttpHeader.HttpHeaderOverhead
name: HttpHeaderOverhead
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_HttpHeaderOverhead
commentId: F:Titanium.Web.Proxy.Models.HttpHeader.HttpHeaderOverhead
fullName: Titanium.Web.Proxy.Models.HttpHeader.HttpHeaderOverhead
nameWithType: HttpHeader.HttpHeaderOverhead
- uid: Titanium.Web.Proxy.Models.HttpHeader.Name
name: Name
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_Name
......@@ -2083,6 +2089,32 @@ references:
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.HttpHeader.Name
nameWithType: HttpHeader.Name
- uid: Titanium.Web.Proxy.Models.HttpHeader.Size
name: Size
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_Size
commentId: P:Titanium.Web.Proxy.Models.HttpHeader.Size
fullName: Titanium.Web.Proxy.Models.HttpHeader.Size
nameWithType: HttpHeader.Size
- uid: Titanium.Web.Proxy.Models.HttpHeader.Size*
name: Size
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_Size_
commentId: Overload:Titanium.Web.Proxy.Models.HttpHeader.Size
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.HttpHeader.Size
nameWithType: HttpHeader.Size
- uid: Titanium.Web.Proxy.Models.HttpHeader.SizeOf(System.String,System.String)
name: SizeOf(String, String)
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_SizeOf_System_String_System_String_
commentId: M:Titanium.Web.Proxy.Models.HttpHeader.SizeOf(System.String,System.String)
fullName: Titanium.Web.Proxy.Models.HttpHeader.SizeOf(System.String, System.String)
nameWithType: HttpHeader.SizeOf(String, String)
- uid: Titanium.Web.Proxy.Models.HttpHeader.SizeOf*
name: SizeOf
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_SizeOf_
commentId: Overload:Titanium.Web.Proxy.Models.HttpHeader.SizeOf
isSpec: "True"
fullName: Titanium.Web.Proxy.Models.HttpHeader.SizeOf
nameWithType: HttpHeader.SizeOf
- uid: Titanium.Web.Proxy.Models.HttpHeader.ToString
name: ToString()
href: api/Titanium.Web.Proxy.Models.HttpHeader.html#Titanium_Web_Proxy_Models_HttpHeader_ToString
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment