You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
7.7 KiB
199 lines
7.7 KiB
1 year ago
|
#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace BestHTTP.Connections.HTTP2
|
||
|
{
|
||
|
sealed class HeaderTable
|
||
|
{
|
||
|
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||
|
// Valid indexes starts with 1, so there's an empty entry.
|
||
|
static string[] StaticTableValues = new string[] { string.Empty, string.Empty, "GET", "POST", "/", "/index.html", "http", "https", "200", "204", "206", "304", "400", "404", "500", string.Empty, "gzip, deflate" };
|
||
|
|
||
|
// https://http2.github.io/http2-spec/compression.html#static.table.definition
|
||
|
// Valid indexes starts with 1, so there's an empty entry.
|
||
|
static string[] StaticTable = new string[62]
|
||
|
{
|
||
|
string.Empty,
|
||
|
":authority",
|
||
|
":method", // GET
|
||
|
":method", // POST
|
||
|
":path", // /
|
||
|
":path", // index.html
|
||
|
":scheme", // http
|
||
|
":scheme", // https
|
||
|
":status", // 200
|
||
|
":status", // 204
|
||
|
":status", // 206
|
||
|
":status", // 304
|
||
|
":status", // 400
|
||
|
":status", // 404
|
||
|
":status", // 500
|
||
|
"accept-charset",
|
||
|
"accept-encoding", // gzip, deflate
|
||
|
"accept-language",
|
||
|
"accept-ranges",
|
||
|
"accept",
|
||
|
"access-control-allow-origin",
|
||
|
"age",
|
||
|
"allow",
|
||
|
"authorization",
|
||
|
"cache-control",
|
||
|
"content-disposition",
|
||
|
"content-encoding",
|
||
|
"content-language",
|
||
|
"content-length",
|
||
|
"content-location",
|
||
|
"content-range",
|
||
|
"content-type",
|
||
|
"cookie",
|
||
|
"date",
|
||
|
"etag",
|
||
|
"expect",
|
||
|
"expires",
|
||
|
"from",
|
||
|
"host",
|
||
|
"if-match",
|
||
|
"if-modified-since",
|
||
|
"if-none-match",
|
||
|
"if-range",
|
||
|
"if-unmodified-since",
|
||
|
"last-modified",
|
||
|
"link",
|
||
|
"location",
|
||
|
"max-forwards",
|
||
|
"proxy-authenticate",
|
||
|
"proxy-authorization",
|
||
|
"range",
|
||
|
"referer",
|
||
|
"refresh",
|
||
|
"retry-after",
|
||
|
"server",
|
||
|
"set-cookie",
|
||
|
"strict-transport-security",
|
||
|
"transfer-encoding",
|
||
|
"user-agent",
|
||
|
"vary",
|
||
|
"via",
|
||
|
"www-authenticate",
|
||
|
};
|
||
|
|
||
|
public UInt32 DynamicTableSize { get; private set; }
|
||
|
public UInt32 MaxDynamicTableSize {
|
||
|
get { return this._maxDynamicTableSize; }
|
||
|
set
|
||
|
{
|
||
|
this._maxDynamicTableSize = value;
|
||
|
EvictEntries(0);
|
||
|
}
|
||
|
}
|
||
|
private UInt32 _maxDynamicTableSize;
|
||
|
|
||
|
private List<KeyValuePair<string, string>> DynamicTable = new List<KeyValuePair<string, string>>();
|
||
|
private HTTP2SettingsRegistry settingsRegistry;
|
||
|
|
||
|
public HeaderTable(HTTP2SettingsRegistry registry)
|
||
|
{
|
||
|
this.settingsRegistry = registry;
|
||
|
this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
|
||
|
}
|
||
|
|
||
|
public KeyValuePair<UInt32, UInt32> GetIndex(string key, string value)
|
||
|
{
|
||
|
for (int i = 0; i < DynamicTable.Count; ++i)
|
||
|
{
|
||
|
var kvp = DynamicTable[i];
|
||
|
|
||
|
// Exact match for both key and value
|
||
|
if (kvp.Key.Equals(key, StringComparison.OrdinalIgnoreCase) && kvp.Value.Equals(value, StringComparison.OrdinalIgnoreCase))
|
||
|
return new KeyValuePair<UInt32, UInt32>((UInt32)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
|
||
|
}
|
||
|
|
||
|
KeyValuePair<UInt32, UInt32> bestMatch = new KeyValuePair<UInt32, UInt32>(0, 0);
|
||
|
for (int i = 0; i < StaticTable.Length; ++i)
|
||
|
{
|
||
|
if (StaticTable[i].Equals(key, StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
if (i < StaticTableValues.Length && !string.IsNullOrEmpty(StaticTableValues[i]) && StaticTableValues[i].Equals(value, StringComparison.OrdinalIgnoreCase))
|
||
|
return new KeyValuePair<UInt32, UInt32>((UInt32)i, (UInt32)i);
|
||
|
else
|
||
|
bestMatch = new KeyValuePair<UInt32, UInt32>((UInt32)i, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bestMatch;
|
||
|
}
|
||
|
|
||
|
public string GetKey(UInt32 index)
|
||
|
{
|
||
|
if (index < StaticTable.Length)
|
||
|
return StaticTable[index];
|
||
|
|
||
|
return this.DynamicTable[(int)(index - StaticTable.Length)].Key;
|
||
|
}
|
||
|
|
||
|
public KeyValuePair<string, string> GetHeader(UInt32 index)
|
||
|
{
|
||
|
if (index < StaticTable.Length)
|
||
|
return new KeyValuePair<string, string>(StaticTable[index],
|
||
|
index < StaticTableValues.Length ? StaticTableValues[index] : null);
|
||
|
|
||
|
return this.DynamicTable[(int)(index - StaticTable.Length)];
|
||
|
}
|
||
|
|
||
|
public void Add(KeyValuePair<string, string> header)
|
||
|
{
|
||
|
// https://http2.github.io/http2-spec/compression.html#calculating.table.size
|
||
|
// The size of an entry is the sum of its name's length in octets (as defined in Section 5.2),
|
||
|
// its value's length in octets, and 32.
|
||
|
UInt32 newHeaderSize = CalculateEntrySize(header);
|
||
|
|
||
|
EvictEntries(newHeaderSize);
|
||
|
|
||
|
// If the size of the new entry is less than or equal to the maximum size, that entry is added to the table.
|
||
|
// It is not an error to attempt to add an entry that is larger than the maximum size;
|
||
|
// an attempt to add an entry larger than the maximum size causes the table to be
|
||
|
// emptied of all existing entries and results in an empty table.
|
||
|
if (this.DynamicTableSize + newHeaderSize <= this.MaxDynamicTableSize)
|
||
|
{
|
||
|
this.DynamicTable.Insert(0, header);
|
||
|
this.DynamicTableSize += (UInt32)newHeaderSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private UInt32 CalculateEntrySize(KeyValuePair<string, string> entry)
|
||
|
{
|
||
|
return 32 + (UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Key) +
|
||
|
(UInt32)System.Text.Encoding.UTF8.GetByteCount(entry.Value);
|
||
|
}
|
||
|
|
||
|
private void EvictEntries(uint newHeaderSize)
|
||
|
{
|
||
|
// https://http2.github.io/http2-spec/compression.html#entry.addition
|
||
|
// Before a new entry is added to the dynamic table, entries are evicted from the end of the dynamic
|
||
|
// table until the size of the dynamic table is less than or equal to (maximum size - new entry size) or until the table is empty.
|
||
|
while (this.DynamicTableSize + newHeaderSize > this.MaxDynamicTableSize && this.DynamicTable.Count > 0)
|
||
|
{
|
||
|
KeyValuePair<string, string> entry = this.DynamicTable[this.DynamicTable.Count - 1];
|
||
|
this.DynamicTable.RemoveAt(this.DynamicTable.Count - 1);
|
||
|
this.DynamicTableSize -= CalculateEntrySize(entry);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string ToString()
|
||
|
{
|
||
|
System.Text.StringBuilder sb = new System.Text.StringBuilder("[HeaderTable ");
|
||
|
sb.AppendFormat("DynamicTable count: {0}, DynamicTableSize: {1}, MaxDynamicTableSize: {2}, ", this.DynamicTable.Count, this.DynamicTableSize, this.MaxDynamicTableSize);
|
||
|
|
||
|
foreach(var kvp in this.DynamicTable)
|
||
|
sb.AppendFormat("\"{0}\": \"{1}\", ", kvp.Key, kvp.Value);
|
||
|
|
||
|
sb.Append("]");
|
||
|
return sb.ToString();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|