diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..d1ed9d154497fbc4871842035d692b74d02478ba
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+*.dll filter=lfs diff=lfs merge=lfs -text
+*.FBX filter=lfs diff=lfs merge=lfs -text
+*.cpp filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4a1d2f92448ced5d0dab1057a496d6c9a8daa161
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,74 @@
+# This .gitignore file should be placed at the root of your Unity project directory
+#
+# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
+#
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Uu]ser[Ss]ettings/
+/ServerData/
+# /[Aa]ssets/ThiredParty*
+
+# MemoryCaptures can get excessive in size.
+# They also could contain extremely sensitive data
+/[Mm]emoryCaptures/
+
+# Asset meta data should only be ignored when the corresponding asset is also ignored
+!/[Aa]ssets/**/*.meta
+
+# Uncomment this line if you wish to ignore the asset store tools plugin
+# /[Aa]ssets/AssetStoreTools*
+
+# Autogenerated Jetbrains Rider plugin
+/[Aa]ssets/Plugins/Editor/JetBrains*
+
+# Visual Studio cache directory
+.vs/
+
+# Gradle cache directory
+.gradle/
+
+# Autogenerated VS/MD/Consulo solution and project files
+ExportedObj/
+.consulo/
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+*.mdb
+*.opendb
+*.VC.db
+
+# Unity3D generated meta files
+*.pidb.meta
+*.pdb.meta
+*.mdb.meta
+
+# Unity3D generated file on crash reports
+sysinfo.txt
+
+# Builds
+*.apk
+*.aab
+*.unitypackage
+
+# Crashlytics generated file
+crashlytics-build.properties
+
+# Packed Addressables
+[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
+
+# Temporary auto-generated Android Assets
+/[Aa]ssets/[Ss]treamingAssets/aa.meta
+/[Aa]ssets/[Ss]treamingAssets/aa/*
+
diff --git a/.vsconfig b/.vsconfig
new file mode 100644
index 0000000000000000000000000000000000000000..d70cd98b77b4984d6943f1ee8d64b111f4847b24
--- /dev/null
+++ b/.vsconfig
@@ -0,0 +1,6 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.VisualStudio.Workload.ManagedGame"
+ ]
+}
diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5e2602b04fffbdf7881228baef3e4af4c8abc2f5
--- /dev/null
+++ b/Assets/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4d8fbb24c92a74c4982a9cc3ac7a8c1f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/Android.meta b/Assets/Plugins/Android.meta
new file mode 100644
index 0000000000000000000000000000000000000000..9e6aa03f40201d5df05266bebb67a553740c76de
--- /dev/null
+++ b/Assets/Plugins/Android.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 68c61ddc9e8fa044983ad30d11ab92c2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/Android/AndroidManifest.xml.DISABLED b/Assets/Plugins/Android/AndroidManifest.xml.DISABLED
new file mode 100644
index 0000000000000000000000000000000000000000..cb2c54a7fe1de63b576e5baa6c1311bb7214ae9d
--- /dev/null
+++ b/Assets/Plugins/Android/AndroidManifest.xml.DISABLED
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Assets/Plugins/Android/AndroidManifest.xml.DISABLED.meta b/Assets/Plugins/Android/AndroidManifest.xml.DISABLED.meta
new file mode 100644
index 0000000000000000000000000000000000000000..6d469b331b2e195bc010721836586d945c667488
--- /dev/null
+++ b/Assets/Plugins/Android/AndroidManifest.xml.DISABLED.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ed89d774b11ba834ba59880af9473927
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP.meta b/Assets/Plugins/BestHTTP.meta
new file mode 100644
index 0000000000000000000000000000000000000000..56dd34e9455ab18c49f37464a387776dc785db3c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2b3da68b2f1c33d44bd5d9b6a1adcc34
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/BestHTTP.asmdef b/Assets/Plugins/BestHTTP/BestHTTP.asmdef
new file mode 100644
index 0000000000000000000000000000000000000000..ee22308bdf37b104d62a2878adb9b823edcf14a9
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/BestHTTP.asmdef
@@ -0,0 +1,7 @@
+{
+ "name": "BestHTTP",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/BestHTTP.asmdef.meta b/Assets/Plugins/BestHTTP/BestHTTP.asmdef.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3e30a13c26561b8c0613fa5fa176958f9caf0625
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/BestHTTP.asmdef.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9069ac25d95ca17448a247f3bb1c769f
+timeCreated: 1571210040
+licenseType: Store
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Documentation.txt b/Assets/Plugins/BestHTTP/Documentation.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d0ee1edd0539e30941219c54f8fef641b5558366
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Documentation.txt
@@ -0,0 +1 @@
+The latest documentation can be read online here: https://benedicht.github.io/BestHTTP-Documentation/
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Documentation.txt.meta b/Assets/Plugins/BestHTTP/Documentation.txt.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5129723e9eeb85d5018688349250117d2adf9d05
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Documentation.txt.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1e156a48f38d6f04f9c643063bcaf535
+timeCreated: 1571210052
+licenseType: Store
+TextScriptImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Plugins.meta b/Assets/Plugins/BestHTTP/Plugins.meta
new file mode 100644
index 0000000000000000000000000000000000000000..261cd799ac482ddc0a3693b25783cc75e1af078e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f2acd3f924ae69c44b7aadb54e686d44
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Plugins/WebGL.meta b/Assets/Plugins/BestHTTP/Plugins/WebGL.meta
new file mode 100644
index 0000000000000000000000000000000000000000..aebd4cd6534cfc78a970443d813cbd56c2bd3a35
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins/WebGL.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b48528ef6a3e52f4ba5e4335dff83841
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib
new file mode 100644
index 0000000000000000000000000000000000000000..f3e91bd7a967fb70324728a59ff6a07f1fcb4dd2
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib
@@ -0,0 +1,155 @@
+var Lib_BEST_HTTP_WebGL_ES_Bridge =
+{
+ $es: {
+ eventSourceInstances: {},
+ nextInstanceId : 1,
+
+ Set : function(event) {
+ es.eventSourceInstances[es.nextInstanceId] = event;
+ return es.nextInstanceId++;
+ },
+
+ Get : function(id) {
+ return es.eventSourceInstances[id];
+ },
+
+ Remove: function(id) {
+ delete es.eventSourceInstances[id];
+ },
+
+ _callOnError: function(errCallback, id, reason)
+ {
+ if (reason)
+ {
+ var length = lengthBytesUTF8(reason) + 1;
+ var buffer = _malloc(length);
+
+ stringToUTF8Array(reason, HEAPU8, buffer, length);
+
+ Module['dynCall_vii'](errCallback, id, buffer);
+
+ _free(buffer);
+ }
+ else
+ Module['dynCall_vii'](errCallback, [id, 0]);
+ },
+
+ _GenericEventHandler: function(id, eventName, e, onMessage) {
+ function AllocString(str) {
+ if (str != undefined)
+ {
+ var length = lengthBytesUTF8(str) + 1;
+ var buff = _malloc(length);
+
+ stringToUTF8Array(str, HEAPU8, buff, length);
+
+ return buff;
+ }
+
+ return 0;
+ }
+
+ var eventBuffer = AllocString(eventName);
+ var dataBuffer = AllocString(e.data);
+ var idBuffer = AllocString(e.id);
+
+ Module['dynCall_viiiii'](onMessage, id, eventBuffer, dataBuffer, idBuffer, e.retry);
+
+ if (eventBuffer != 0)
+ _free(eventBuffer);
+
+ if (dataBuffer != 0)
+ _free(dataBuffer);
+
+ if (idBuffer != 0)
+ _free(idBuffer);
+ }
+ },
+
+ ES_IsSupported: function() {
+ return typeof(EventSource) !== "undefined";
+ },
+
+ ES_Create: function(urlPtr, withCredentials, onOpen, onMessage, onError)
+ {
+ var url = new URL(UTF8ToString(urlPtr)); ///*encodeURI*/(UTF8ToString(urlPtr)).replace(/\+/g, '%2B').replace(/%252[fF]/ig, '%2F');
+
+ var event = {
+ onError: onError
+ };
+
+ var id = es.nextInstanceId;
+
+ console.log(id + ' ES_Create(' + url + ', ' + withCredentials + ')');
+
+ event.eventImpl = new EventSource(url, { withCredentials: withCredentials != 0 ? true : false } );
+ event.onMessage = onMessage;
+
+ event.eventImpl.onopen = function() {
+ console.log(id + ' ES_Create - onOpen');
+
+ Module['dynCall_vi'](onOpen, id);
+ };
+
+ event.eventImpl.onmessage = function(e) {
+ console.log(id + ' on Generic Message');
+ es._GenericEventHandler(id, undefined, e, onMessage);
+ };
+
+ event.eventImpl.onerror = function(e) {
+ console.log(id + ' ES_Create - onError');
+
+ es._callOnError(onError, id, "Unknown Error!");
+
+ if (e.target.readyState === 0)
+ event.eventImpl.close();
+ };
+
+ return es.Set(event);
+ },
+
+ ES_AddEventHandler: function(id, eventNamePtr) {
+ var eventName = UTF8ToString(eventNamePtr);
+
+ console.log(id + ' ES_AddEventHandler(' + eventName + ')');
+
+ var event = es.Get(id);
+
+ try
+ {
+ event.eventImpl.addEventListener(eventName, function(e) {
+ console.log(id + ' onEvent('+ eventName + ')');
+
+ es._GenericEventHandler(id, eventName, e, event.onMessage);
+ });
+ }
+ catch(e) {
+ es._callOnError(event.eventImpl.onError, id, ' ' + e.name + ': ' + e.message);
+ }
+ },
+
+ ES_Close: function(id)
+ {
+ console.log(id + ' ES_Close');
+
+ var event = es.Get(id);
+
+ try
+ {
+ event.eventImpl.close();
+ }
+ catch(e) {
+ es._callOnError(event.eventImpl.onError, id, ' ' + e.name + ': ' + e.message);
+ }
+ },
+
+ ES_Release: function(id)
+ {
+ console.log(id + ' ES_Release');
+
+ es.Remove(id);
+ }
+};
+
+autoAddDeps(Lib_BEST_HTTP_WebGL_ES_Bridge, '$es');
+mergeInto(LibraryManager.library, Lib_BEST_HTTP_WebGL_ES_Bridge);
diff --git a/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib.meta b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3edf2fb4a1d8a171250167587bf30ac463f98296
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_EventSource.jslib.meta
@@ -0,0 +1,81 @@
+fileFormatVersion: 2
+guid: ac23b06f7735b8e478ce14b0d4ac2a68
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 1
+ Exclude Linux64: 1
+ Exclude OSXUniversal: 1
+ Exclude WebGL: 0
+ Exclude Win: 1
+ Exclude Win64: 1
+ - first:
+ : data
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ WebGL: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebRequest.jslib b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebRequest.jslib
new file mode 100644
index 0000000000000000000000000000000000000000..9146b1482c42e69918ee81ea7cd3a7a9a01249ba
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebRequest.jslib
@@ -0,0 +1,269 @@
+var Lib_BEST_HTTP_WebGL_HTTP_Bridge =
+{
+ /*LogLevels: {
+ All: 0,
+ Information: 1,
+ Warning: 2,
+ Error: 3,
+ Exception: 4,
+ None: 5
+ }*/
+
+ $wr: {
+ requestInstances: {},
+ nextRequestId: 1,
+ loglevel: 2
+ },
+
+ XHR_Create: function(method, url, user, passwd, withCredentials)
+ {
+ var _url = new URL(UTF8ToString(url)); ///*encodeURI*/(UTF8ToString(url)).replace(/\+/g, '%2B').replace(/%252[fF]/ig, '%2F');
+ var _method = UTF8ToString(method);
+
+ if (wr.loglevel <= 1) /*information*/
+ console.log(wr.nextRequestId + ' XHR_Create - withCredentials: ' + withCredentials + ' method: ' + _method + ' url: ' + _url.toString());
+
+ var http = new XMLHttpRequest();
+
+ if (user && passwd)
+ {
+ var u = UTF8ToString(user);
+ var p = UTF8ToString(passwd);
+
+ http.withCredentials = true;
+ http.open(_method, _url.toString(), /*async:*/ true , u, p);
+ }
+ else {
+ http.withCredentials = withCredentials;
+ http.open(_method, _url.toString(), /*async:*/ true);
+ }
+
+ http.responseType = 'arraybuffer';
+
+ wr.requestInstances[wr.nextRequestId] = http;
+ return wr.nextRequestId++;
+ },
+
+ XHR_SetTimeout: function (request, timeout)
+ {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetTimeout ' + timeout);
+
+ wr.requestInstances[request].timeout = timeout;
+ },
+
+ XHR_SetRequestHeader: function (request, header, value)
+ {
+ var _header = UTF8ToString(header);
+ var _value = UTF8ToString(value);
+
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetRequestHeader ' + _header + ' ' + _value);
+
+ if (_header != 'Cookie')
+ wr.requestInstances[request].setRequestHeader(_header, _value);
+ else {
+ var cookies = _value.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ document.cookie = cookies[i];
+ }
+ }
+ },
+
+ XHR_CopyResponseTo: function (request, array, size) {
+ var http = wr.requestInstances[request];
+
+ var response = 0;
+ if (!!http.response)
+ response = http.response;
+
+ var responseBytes = new Uint8Array(response);
+ var buffer = HEAPU8.subarray(array, array + size);
+ buffer.set(responseBytes)
+ },
+
+ XHR_SetResponseHandler: function (request, onresponse, onerror, ontimeout, onaborted)
+ {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetResponseHandler');
+
+ var http = wr.requestInstances[request];
+ // LOAD
+ http.onload = function http_onload(e) {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' - onload ' + http.status + ' ' + http.statusText);
+
+ if (onresponse)
+ {
+ var responseLength = 0;
+ if (!!http.response)
+ responseLength = http.response.byteLength;
+
+ Module['dynCall_viiiii'](onresponse, request, http.status, 0, responseLength, 0);
+ }
+ };
+
+ if (onerror)
+ {
+ http.onerror = function http_onerror(e) {
+ function HandleError(err)
+ {
+ var length = lengthBytesUTF8(err) + 1;
+ var buffer = _malloc(length);
+
+ stringToUTF8Array(err, HEAPU8, buffer, length);
+
+ Module['dynCall_vii'](onerror, request, buffer);
+
+ _free(buffer);
+ }
+
+ if (e.error)
+ HandleError(e.error);
+ else
+ HandleError("Unknown Error! Maybe a CORS porblem?");
+ };
+ }
+
+ if (ontimeout)
+ http.ontimeout = function http_onerror(e) {
+ Module['dynCall_vi'](ontimeout, request);
+ };
+
+ if (onaborted)
+ http.onabort = function http_onerror(e) {
+ Module['dynCall_vi'](onaborted, request);
+ };
+ },
+
+ XHR_SetProgressHandler: function (request, onprogress, onuploadprogress)
+ {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetProgressHandler');
+
+ var http = wr.requestInstances[request];
+ if (http)
+ {
+ if (onprogress)
+ http.onprogress = function http_onprogress(e) {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetProgressHandler - onProgress ' + e.loaded + ' ' + e.total);
+
+ if (e.lengthComputable)
+ Module['dynCall_viii'](onprogress, request, e.loaded, e.total);
+ };
+
+ if (onuploadprogress)
+ http.upload.addEventListener("progress", function http_onprogress(e) {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_SetProgressHandler - onUploadProgress ' + e.loaded + ' ' + e.total);
+
+ if (e.lengthComputable)
+ Module['dynCall_viii'](onuploadprogress, request, e.loaded, e.total);
+ }, true);
+ }
+ },
+
+ XHR_Send: function (request, ptr, length)
+ {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_Send ' + ptr + ' ' + length);
+
+ var http = wr.requestInstances[request];
+
+ try {
+ if (length > 0)
+ http.send(HEAPU8.subarray(ptr, ptr+length));
+ else
+ http.send();
+ }
+ catch(e) {
+ if (wr.loglevel <= 4) /*exception*/
+ console.error(request + ' ' + e.name + ": " + e.message);
+ }
+ },
+
+ XHR_GetResponseHeaders: function(request, callback)
+ {
+ if (wr.loglevel <= 1) /*information*/
+ console.log(request + ' XHR_GetResponseHeaders');
+
+ var headers = ''
+ var cookies = document.cookie.split(';');
+ for(var i = 0; i < cookies.length; ++i) {
+ const cookie = cookies[i].trim();
+
+ if (cookie.length > 0)
+ headers += "Set-Cookie:" + cookie + "\r\n";
+ }
+
+ var additionalHeaders = wr.requestInstances[request].getAllResponseHeaders().trim();
+ if (additionalHeaders.length > 0) {
+ headers += additionalHeaders;
+ headers += "\r\n";
+ }
+
+ headers += "\r\n";
+
+ if (wr.loglevel <= 1) /*information*/
+ console.log(' "' + headers + '"');
+
+ var byteArray = new Uint8Array(headers.length);
+ for(var i=0,j=headers.length;i 0)
+ // ws._callOnError(onError, id, e.reason);
+ // else
+ // {
+ // switch (e.code)
+ // {
+ // case 1001: ws._callOnError(onError, id, "Endpoint going away.");
+ // break;
+ // case 1002: ws._callOnError(onError, id, "Protocol error.");
+ // break;
+ // case 1003: ws._callOnError(onError, id, "Unsupported message.");
+ // break;
+ // case 1005: ws._callOnError(onError, id, "No status.");
+ // break;
+ // case 1006: ws._callOnError(onError, id, "Abnormal disconnection.");
+ // break;
+ // case 1009: ws._callOnError(onError, id, "Data frame too large.");
+ // break;
+ // default: ws._callOnError(onError, id, "Error " + e.code);
+ // }
+ // }
+ //}
+ //else
+ ws._callOnClose(onClose, id, e.code, e.reason);
+ };
+
+ return ws.Set(socket);
+ },
+
+ WS_GetState: function (id)
+ {
+ var socket = ws.Get(id);
+
+ if (typeof socket === 'undefined' ||
+ socket == null ||
+ typeof socket.socketImpl === 'undefined' ||
+ socket.socketImpl == null)
+ return 3; // closed
+
+ return socket.socketImpl.readyState;
+ },
+
+ WS_GetBufferedAmount: function (id)
+ {
+ var socket = ws.Get(id);
+ return socket.socketImpl.bufferedAmount;
+ },
+
+ WS_Send_String: function (id, str)
+ {
+ var socket = ws.Get(id);
+ var str = UTF8ToString(str);
+
+ try
+ {
+ socket.socketImpl.send(str);
+ }
+ catch(e) {
+ ws._callOnError(socket.onError, id, ' ' + e.name + ': ' + e.message);
+ }
+
+ return socket.socketImpl.bufferedAmount;
+ },
+
+ WS_Send_Binary: function(id, ptr, pos, length)
+ {
+ var socket = ws.Get(id);
+
+ try
+ {
+ var buff = HEAPU8.subarray(ptr + pos, ptr + pos + length);
+ socket.socketImpl.send(buff /*HEAPU8.buffer.slice(ptr + pos, ptr + pos + length)*/);
+ }
+ catch(e) {
+ ws._callOnError(socket.onError, id, ' ' + e.name + ': ' + e.message);
+ }
+
+ return socket.socketImpl.bufferedAmount;
+ },
+
+ WS_Close: function (id, code, reason)
+ {
+ var socket = ws.Get(id);
+ var reasonStr = UTF8ToString(reason);
+
+ console.log(id + ' WS_Close(' + code + ', ' + reasonStr + ')');
+
+ socket.socketImpl.close(/*ulong*/code, reasonStr);
+ },
+
+ WS_Release: function(id)
+ {
+ console.log(id + ' WS_Release');
+
+ ws.Remove(id);
+ }
+};
+
+autoAddDeps(Lib_BEST_HTTP_WebGL_WS_Bridge, '$ws');
+mergeInto(LibraryManager.library, Lib_BEST_HTTP_WebGL_WS_Bridge);
diff --git a/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebSocket.jslib.meta b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebSocket.jslib.meta
new file mode 100644
index 0000000000000000000000000000000000000000..2de51c054cfe9b8fcf9272916c365e5f03568104
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Plugins/WebGL/BestHTTP_WebSocket.jslib.meta
@@ -0,0 +1,76 @@
+fileFormatVersion: 2
+guid: 8efe6cedf2832a647a33465b85e4f311
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ validateReferences: 1
+ platformData:
+ - first:
+ : Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Android: 1
+ Exclude Editor: 1
+ Exclude Linux64: 1
+ Exclude OSXUniversal: 1
+ Exclude WebGL: 0
+ Exclude Win: 1
+ Exclude Win64: 1
+ - first:
+ Android: Android
+ second:
+ enabled: 0
+ settings:
+ CPU: ARMv7
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Standalone: Linux64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ Standalone: Win64
+ second:
+ enabled: 0
+ settings:
+ CPU: None
+ - first:
+ WebGL: WebGL
+ second:
+ enabled: 1
+ settings: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/ReleaseNotes.txt b/Assets/Plugins/BestHTTP/ReleaseNotes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1e03da6c870b00f446053f35f53d72cee505616c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/ReleaseNotes.txt
@@ -0,0 +1 @@
+Release notes can be read online here: https://benedicht.github.io/BestHTTP-Documentation/#changelog/
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/ReleaseNotes.txt.meta b/Assets/Plugins/BestHTTP/ReleaseNotes.txt.meta
new file mode 100644
index 0000000000000000000000000000000000000000..b364ce40a3e10a1d47975b9040b8ad7e61bdfad9
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/ReleaseNotes.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 378c871e816aa904cacfe5043f5354f4
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source.meta b/Assets/Plugins/BestHTTP/Source.meta
new file mode 100644
index 0000000000000000000000000000000000000000..958753b8daf44d8fbbc1480dda4974bd88e31b44
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: dc57aabb2dd648642bdfb021906367b1
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication.meta b/Assets/Plugins/BestHTTP/Source/Authentication.meta
new file mode 100644
index 0000000000000000000000000000000000000000..37d02f005475eedb42a26ac84bfd1f3a8400df7a
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e9dc42aeefc20544bb572bce27e3b7b1
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs b/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4d87cdee5ddd42dd3a7b9f875d02a45ac105b413
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs
@@ -0,0 +1,63 @@
+namespace BestHTTP.Authentication
+{
+ ///
+ /// Authentication types that supported by BestHTTP.
+ /// The authentication is defined by the server, so the Basic and Digest are not interchangeable. If you don't know what to use, the preferred way is to choose Unknow.
+ ///
+ public enum AuthenticationTypes
+ {
+ ///
+ /// If the authentication type is not known this will do a challenge turn to receive what methode should be choosen.
+ ///
+ Unknown,
+
+ ///
+ /// The most basic authentication type. It's easy to do, and easy to crack. ;)
+ ///
+ Basic,
+
+ ///
+ ///
+ ///
+ Digest
+ }
+
+ ///
+ /// Hold all information that required to authenticate to a remote server.
+ ///
+ public sealed class Credentials
+ {
+ ///
+ /// The type of the Authentication. If you don't know what to use, the preferred way is to choose Unknow.
+ ///
+ public AuthenticationTypes Type { get; private set; }
+
+ ///
+ /// The username to authenticate on the remote server.
+ ///
+ public string UserName { get; private set; }
+
+ ///
+ /// The password to use in the authentication process. The password will be stored only in this class.
+ ///
+ public string Password { get; private set; }
+
+ ///
+ /// Set up the authentication credentials with the username and password. The Type will be set to Unknown.
+ ///
+ public Credentials(string userName, string password)
+ :this(AuthenticationTypes.Unknown, userName, password)
+ {
+ }
+
+ ///
+ /// Set up the authentication credentials with the given authentication type, username and password.
+ ///
+ public Credentials(AuthenticationTypes type, string userName, string password)
+ {
+ this.Type = type;
+ this.UserName = userName;
+ this.Password = password;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs.meta b/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..06dea042be3089d92387b2ab3d4441c4d68623a7
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/Credentials.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: a6a6217993b733a4e8555355d5d62776
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs b/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0bd476a51fb2346836738ee7bdbde08e0bd3e243
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Authentication
+{
+ using BestHTTP.Extensions;
+ using BestHTTP.PlatformSupport.Memory;
+ using System.Text;
+
+ ///
+ /// Internal class that stores all information that received from a server in a WWW-Authenticate and need to construct a valid Authorization header. Based on rfc 2617 (http://tools.ietf.org/html/rfc2617).
+ /// Used only internally by the plugin.
+ ///
+ public sealed class Digest
+ {
+ #region Public Properties
+
+ ///
+ /// The Uri that this Digest is bound to.
+ ///
+ public Uri Uri { get; private set; }
+
+ public AuthenticationTypes Type { get; private set; }
+
+ ///
+ /// A string to be displayed to users so they know which username and password to use.
+ /// This string should contain at least the name of the host performing the authentication and might additionally indicate the collection of users who might have access.
+ ///
+ public string Realm { get; private set; }
+
+ ///
+ /// A flag, indicating that the previous request from the client was rejected because the nonce value was stale.
+ /// If stale is TRUE (case-insensitive), the client may wish to simply retry the request with a new encrypted response, without the user for a new username and password.
+ /// The server should only set stale to TRUE if it receives a request for which the nonce is invalid but with a valid digest for that nonce
+ /// (indicating that the client knows the correct username/password).
+ /// If stale is FALSE, or anything other than TRUE, or the stale directive is not present, the username and/or password are invalid, and new values must be obtained.
+ ///
+ public bool Stale { get; private set; }
+
+ #endregion
+
+ #region Private Properties
+
+ ///
+ /// A server-specified data string which should be uniquely generated each time a 401 response is made.
+ /// Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
+ ///
+ private string Nonce { get; set; }
+
+ ///
+ /// A string of data, specified by the server, which should be returned by the client unchanged in the Authorization header of subsequent requests with URIs in the same protection space.
+ /// It is recommended that this string be base64 or data.
+ ///
+ private string Opaque { get; set; }
+
+ ///
+ /// A string indicating a pair of algorithms used to produce the digest and a checksum. If this is not present it is assumed to be "MD5".
+ /// If the algorithm is not understood, the challenge should be ignored (and a different one used, if there is more than one).
+ ///
+ private string Algorithm { get; set; }
+
+ ///
+ /// List of URIs, as specified in RFC XURI, that define the protection space.
+ /// If a URI is an abs_path, it is relative to the canonical root URL (see section 1.2 above) of the server being accessed.
+ /// An absoluteURI in this list may refer to a different server than the one being accessed.
+ /// The client can use this list to determine the set of URIs for which the same authentication information may be sent:
+ /// any URI that has a URI in this list as a prefix (after both have been made absolute) may be assumed to be in the same protection space.
+ /// If this directive is omitted or its value is empty, the client should assume that the protection space consists of all URIs on the responding server.
+ ///
+ public List ProtectedUris { get; private set; }
+
+ ///
+ /// If present, it is a quoted string of one or more tokens indicating the "quality of protection" values supported by the server.
+ /// The value "auth" indicates authentication. The value "auth-int" indicates authentication with integrity protection.
+ ///
+ private string QualityOfProtections { get; set; }
+
+ ///
+ /// his MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field.
+ /// The nc-value is the hexadecimal count of the number of requests (including the current request) that the client has sent with the nonce value in this request.
+ ///
+ private int NonceCount { get; set; }
+
+ ///
+ /// Used to store the last HA1 that can be used in the next header generation when Algorithm is set to "md5-sess".
+ ///
+ private string HA1Sess { get; set; }
+
+ #endregion
+
+ internal Digest(Uri uri)
+ {
+ this.Uri = uri;
+ this.Algorithm = "md5";
+ }
+
+ ///
+ /// Parses a WWW-Authenticate header's value to retrive all information.
+ ///
+ public void ParseChallange(string header)
+ {
+ // Reset some values to its defaults.
+ this.Type = AuthenticationTypes.Unknown;
+ this.Stale = false;
+ this.Opaque = null;
+ this.HA1Sess = null;
+ this.NonceCount = 0;
+ this.QualityOfProtections = null;
+
+ if (this.ProtectedUris != null)
+ this.ProtectedUris.Clear();
+
+ // Parse the header
+ WWWAuthenticateHeaderParser qpl = new WWWAuthenticateHeaderParser(header);
+
+ // Then process
+ foreach (var qp in qpl.Values)
+ switch (qp.Key)
+ {
+ case "basic": this.Type = AuthenticationTypes.Basic; break;
+ case "digest": this.Type = AuthenticationTypes.Digest; break;
+ case "realm": this.Realm = qp.Value; break;
+ case "domain":
+ {
+ if (string.IsNullOrEmpty(qp.Value) || qp.Value.Length == 0)
+ break;
+
+ if (this.ProtectedUris == null)
+ this.ProtectedUris = new List();
+
+ int idx = 0;
+ string val = qp.Value.Read(ref idx, ' ');
+ do
+ {
+ this.ProtectedUris.Add(val);
+ val = qp.Value.Read(ref idx, ' ');
+ } while (idx < qp.Value.Length);
+
+ break;
+ }
+ case "nonce": this.Nonce = qp.Value; break;
+ case "qop": this.QualityOfProtections = qp.Value; break;
+ case "stale": this.Stale = bool.Parse(qp.Value); break;
+ case "opaque": this.Opaque = qp.Value; break;
+ case "algorithm": this.Algorithm = qp.Value; break;
+ }
+ }
+
+ ///
+ /// Generates a string that can be set to an Authorization header.
+ ///
+ public string GenerateResponseHeader(HTTPRequest request, Credentials credentials, bool isProxy = false)
+ {
+ try
+ {
+ switch (Type)
+ {
+ case AuthenticationTypes.Basic:
+ return string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", credentials.UserName, credentials.Password))));
+
+ case AuthenticationTypes.Digest:
+ {
+ NonceCount++;
+
+ string HA1 = string.Empty;
+
+ // The cnonce-value is an opaque quoted string value provided by the client and used by both client and server to avoid chosen plaintext attacks, to provide mutual
+ // authentication, and to provide some message integrity protection.
+ string cnonce = new System.Random(request.GetHashCode()).Next(int.MinValue, int.MaxValue).ToString("X8");
+
+ string ncvalue = NonceCount.ToString("X8");
+ switch (Algorithm.TrimAndLower())
+ {
+ case "md5":
+ HA1 = string.Format("{0}:{1}:{2}", credentials.UserName, Realm, credentials.Password).CalculateMD5Hash();
+ break;
+
+ case "md5-sess":
+ if (string.IsNullOrEmpty(this.HA1Sess))
+ this.HA1Sess = string.Format("{0}:{1}:{2}:{3}:{4}", credentials.UserName, Realm, credentials.Password, Nonce, ncvalue).CalculateMD5Hash();
+ HA1 = this.HA1Sess;
+ break;
+
+ default: //throw new NotSupportedException("Not supported hash algorithm found in Web Authentication: " + Algorithm);
+ return string.Empty;
+ }
+
+ // A string of 32 hex digits, which proves that the user knows a password. Set according to the qop value.
+ string response = string.Empty;
+
+ // The server sent QoP-value can be a list of supported methodes(if sent at all - in this case it's null).
+ // The rfc is not specify that this is a space or comma separeted list. So it can be "auth, auth-int" or "auth auth-int".
+ // We will first check the longer value("auth-int") then the short one ("auth"). If one matches we will reset the qop to the exact value.
+ string qop = this.QualityOfProtections != null ? this.QualityOfProtections.TrimAndLower() : null;
+
+ // When we authenticate with a proxy and we want to tunnel the request, we have to use the CONNECT method instead of the
+ // request's, as the proxy will not know about the request itself.
+ string method = isProxy ? "CONNECT" : request.MethodType.ToString().ToUpper();
+
+ // When we authenticate with a proxy and we want to tunnel the request, the uri must match what we are sending in the CONNECT request's
+ // Host header.
+ string uri = isProxy ? request.CurrentUri.Host + ":" + request.CurrentUri.Port : request.CurrentUri.GetRequestPathAndQueryURL();
+
+ if (qop == null)
+ {
+ string HA2 = string.Concat(request.MethodType.ToString().ToUpper(), ":", request.CurrentUri.GetRequestPathAndQueryURL()).CalculateMD5Hash();
+ response = string.Format("{0}:{1}:{2}", HA1, Nonce, HA2).CalculateMD5Hash();
+ }
+ else if (qop.Contains("auth-int"))
+ {
+ qop = "auth-int";
+
+ byte[] entityBody = request.GetEntityBody();
+
+ if (entityBody == null)
+ entityBody = BufferPool.NoData; //string.Empty.GetASCIIBytes();
+
+ string HA2 = string.Format("{0}:{1}:{2}", method, uri, entityBody.CalculateMD5Hash()).CalculateMD5Hash();
+
+ response = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, Nonce, ncvalue, cnonce, qop, HA2).CalculateMD5Hash();
+ }
+ else if (qop.Contains("auth"))
+ {
+ qop = "auth";
+ string HA2 = string.Concat(method, ":", uri).CalculateMD5Hash();
+
+ response = string.Format("{0}:{1}:{2}:{3}:{4}:{5}", HA1, Nonce, ncvalue, cnonce, qop, HA2).CalculateMD5Hash();
+ }
+ else //throw new NotSupportedException("Unrecognized Quality of Protection value found: " + this.QualityOfProtections);
+ return string.Empty;
+
+ string result = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", cnonce=\"{4}\", response=\"{5}\"",
+ credentials.UserName, Realm, Nonce, uri, cnonce, response);
+
+ if (qop != null)
+ result += String.Concat(", qop=\"", qop, "\", nc=", ncvalue);
+
+ if (!string.IsNullOrEmpty(Opaque))
+ result = String.Concat(result, ", opaque=\"", Opaque, "\"");
+
+ return result;
+ }// end of case "digest":
+
+ default:
+ break;
+ }
+ }
+ catch
+ {
+ }
+
+ return string.Empty;
+ }
+
+ public bool IsUriProtected(Uri uri)
+ {
+ // http://tools.ietf.org/html/rfc2617#section-3.2.1
+ // An absoluteURI in this list may refer to
+ // a different server than the one being accessed. The client can use
+ // this list to determine the set of URIs for which the same
+ // authentication information may be sent: any URI that has a URI in
+ // this list as a prefix (after both have been made absolute) may be
+ // assumed to be in the same protection space. If this directive is
+ // omitted or its value is empty, the client should assume that the
+ // protection space consists of all URIs on the responding server.
+
+ if (string.CompareOrdinal(uri.Host, this.Uri.Host) != 0)
+ return false;
+
+ string uriStr = uri.ToString();
+
+ if (ProtectedUris != null && ProtectedUris.Count > 0)
+ for (int i = 0; i < ProtectedUris.Count; ++i)
+ if (uriStr.Contains(ProtectedUris[i]))
+ return true;
+
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs.meta b/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..869c596494e3fa4869933902b8df685982cb7517
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/Digest.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 424d976119ee3fc438fe98095013859f
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs b/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9f215d8d2bd5d72727021b684873c0df82682e69
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Authentication
+{
+ ///
+ /// Stores and manages already received digest infos.
+ ///
+ public static class DigestStore
+ {
+ private static Dictionary Digests = new Dictionary();
+
+ private static System.Threading.ReaderWriterLockSlim rwLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
+
+ ///
+ /// Array of algorithms that the plugin supports. It's in the order of priority(first has the highest priority).
+ ///
+ private static string[] SupportedAlgorithms = new string[] { "digest", "basic" };
+
+ public static Digest Get(Uri uri)
+ {
+ rwLock.EnterReadLock();
+ try{
+ Digest digest = null;
+ if (Digests.TryGetValue(uri.Host, out digest))
+ if (!digest.IsUriProtected(uri))
+ return null;
+ return digest;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// It will retrieve or create a new Digest for the given Uri.
+ ///
+ ///
+ ///
+ public static Digest GetOrCreate(Uri uri)
+ {
+ rwLock.EnterUpgradeableReadLock();
+ try{
+ Digest digest = null;
+ if (!Digests.TryGetValue(uri.Host, out digest))
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ Digests.Add(uri.Host, digest = new Digest(uri));
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+ return digest;
+ }
+ finally
+ {
+ rwLock.ExitUpgradeableReadLock();
+ }
+ }
+
+ public static void Remove(Uri uri)
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ Digests.Remove(uri.Host);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+
+ public static string FindBest(List authHeaders)
+ {
+ if (authHeaders == null || authHeaders.Count == 0)
+ return string.Empty;
+
+ List headers = new List(authHeaders.Count);
+ for (int i = 0; i < authHeaders.Count; ++i)
+ headers.Add(authHeaders[i].ToLower());
+
+ for (int i = 0; i < SupportedAlgorithms.Length; ++i)
+ {
+ int idx = headers.FindIndex((header) => header.StartsWith(SupportedAlgorithms[i]));
+ if (idx != -1)
+ return authHeaders[idx];
+ }
+
+ return string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs.meta b/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..280472e7c5d0b12df2a4d9caa06672cf489d1c3f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Authentication/DigestStore.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 0f808b023553ee2419249f92eb8ac31f
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Caching.meta b/Assets/Plugins/BestHTTP/Source/Caching.meta
new file mode 100644
index 0000000000000000000000000000000000000000..319ec4f6a0ae074f5ffd501cf00b5b03fb9761ea
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 26e64cd0d1567634fb40b1386ba0c48d
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..79702ce9be4e0d788ac6ca162818e520bdd76887
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs
@@ -0,0 +1,502 @@
+#if !BESTHTTP_DISABLE_CACHING
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace BestHTTP.Caching
+{
+ using BestHTTP.Extensions;
+ using BestHTTP.PlatformSupport.FileSystem;
+
+ ///
+ /// Holds all metadata that need for efficient caching, so we don't need to touch the disk to load headers.
+ ///
+ public class HTTPCacheFileInfo : IComparable
+ {
+ #region Properties
+
+ ///
+ /// The uri that this HTTPCacheFileInfo belongs to.
+ ///
+ public Uri Uri { get; private set; }
+
+ ///
+ /// The last access time to this cache entity. The date is in UTC.
+ ///
+ public DateTime LastAccess { get; private set; }
+
+ ///
+ /// The length of the cache entity's body.
+ ///
+ public int BodyLength { get; internal set; }
+
+ ///
+ /// ETag of the entity.
+ ///
+ public string ETag { get; private set; }
+
+ ///
+ /// LastModified date of the entity.
+ ///
+ public string LastModified { get; private set; }
+
+ ///
+ /// When the cache will expire.
+ ///
+ public DateTime Expires { get; private set; }
+
+ ///
+ /// The age that came with the response
+ ///
+ public long Age { get; private set; }
+
+ ///
+ /// Maximum how long the entry should served from the cache without revalidation.
+ ///
+ public long MaxAge { get; private set; }
+
+ ///
+ /// The Date that came with the response.
+ ///
+ public DateTime Date { get; private set; }
+
+ ///
+ /// Indicates whether the entity must be revalidated with the server or can be serverd directly from the cache without touching the server when the content is considered stale.
+ ///
+ public bool MustRevalidate { get; private set; }
+
+ ///
+ /// If it's true, the client always have to revalidate the cached content when it's stale.
+ ///
+ public bool NoCache { get; private set; }
+
+ ///
+ /// It's a grace period to serve staled content without revalidation.
+ ///
+ public long StaleWhileRevalidate { get; private set; }
+
+ ///
+ /// Allows the client to serve stale content if the server responds with an 5xx error.
+ ///
+ public long StaleIfError { get; private set; }
+
+ ///
+ /// The date and time when the HTTPResponse received.
+ ///
+ public DateTime Received { get; private set; }
+
+ ///
+ /// Cached path.
+ ///
+ public string ConstructedPath { get; private set; }
+
+ ///
+ /// This is the index of the entity. Filenames are generated from this value.
+ ///
+ internal UInt64 MappedNameIDX { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ internal HTTPCacheFileInfo(Uri uri)
+ :this(uri, DateTime.UtcNow, -1)
+ {
+ }
+
+ internal HTTPCacheFileInfo(Uri uri, DateTime lastAcces, int bodyLength)
+ {
+ this.Uri = uri;
+ this.LastAccess = lastAcces;
+ this.BodyLength = bodyLength;
+ this.MaxAge = -1;
+
+ this.MappedNameIDX = HTTPCacheService.GetNameIdx();
+ }
+
+ internal HTTPCacheFileInfo(Uri uri, System.IO.BinaryReader reader, int version)
+ {
+ this.Uri = uri;
+ this.LastAccess = DateTime.FromBinary(reader.ReadInt64());
+ this.BodyLength = reader.ReadInt32();
+
+ switch(version)
+ {
+ case 3:
+ this.NoCache = reader.ReadBoolean();
+ this.StaleWhileRevalidate = reader.ReadInt64();
+ this.StaleIfError = reader.ReadInt64();
+ goto case 2;
+
+ case 2:
+ this.MappedNameIDX = reader.ReadUInt64();
+ goto case 1;
+
+ case 1:
+ {
+ this.ETag = reader.ReadString();
+ this.LastModified = reader.ReadString();
+ this.Expires = DateTime.FromBinary(reader.ReadInt64());
+ this.Age = reader.ReadInt64();
+ this.MaxAge = reader.ReadInt64();
+ this.Date = DateTime.FromBinary(reader.ReadInt64());
+ this.MustRevalidate = reader.ReadBoolean();
+ this.Received = DateTime.FromBinary(reader.ReadInt64());
+ break;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Helper Functions
+
+ internal void SaveTo(System.IO.BinaryWriter writer)
+ {
+ // base
+ writer.Write(this.LastAccess.ToBinary());
+ writer.Write(this.BodyLength);
+
+ // version 3
+ writer.Write(this.NoCache);
+ writer.Write(this.StaleWhileRevalidate);
+ writer.Write(this.StaleIfError);
+
+ // version 2
+ writer.Write(this.MappedNameIDX);
+
+ // version 1
+ writer.Write(this.ETag);
+ writer.Write(this.LastModified);
+ writer.Write(this.Expires.ToBinary());
+ writer.Write(this.Age);
+ writer.Write(this.MaxAge);
+ writer.Write(this.Date.ToBinary());
+ writer.Write(this.MustRevalidate);
+ writer.Write(this.Received.ToBinary());
+ }
+
+ public string GetPath()
+ {
+ if (ConstructedPath != null)
+ return ConstructedPath;
+
+ return ConstructedPath = System.IO.Path.Combine(HTTPCacheService.CacheFolder, MappedNameIDX.ToString("X"));
+ }
+
+ public bool IsExists()
+ {
+ if (!HTTPCacheService.IsSupported)
+ return false;
+
+ return HTTPManager.IOService.FileExists(GetPath());
+ }
+
+ internal void Delete()
+ {
+ if (!HTTPCacheService.IsSupported)
+ return;
+
+ string path = GetPath();
+ try
+ {
+ HTTPManager.IOService.FileDelete(path);
+ }
+ catch
+ { }
+ finally
+ {
+ Reset();
+ }
+ }
+
+ private void Reset()
+ {
+ // MappedNameIDX will remain the same. When we re-save an entity, it will not reset the MappedNameIDX.
+ this.BodyLength = -1;
+ this.ETag = string.Empty;
+ this.Expires = DateTime.FromBinary(0);
+ this.LastModified = string.Empty;
+ this.Age = 0;
+ this.MaxAge = -1;
+ this.Date = DateTime.FromBinary(0);
+ this.MustRevalidate = false;
+ this.Received = DateTime.FromBinary(0);
+ this.NoCache = false;
+ this.StaleWhileRevalidate = 0;
+ this.StaleIfError = 0;
+ }
+
+ #endregion
+
+ #region Caching
+
+ internal void SetUpCachingValues(HTTPResponse response)
+ {
+ response.CacheFileInfo = this;
+
+ this.ETag = response.GetFirstHeaderValue("ETag").ToStr(this.ETag ?? string.Empty);
+ this.Expires = response.GetFirstHeaderValue("Expires").ToDateTime(this.Expires);
+ this.LastModified = response.GetFirstHeaderValue("Last-Modified").ToStr(this.LastModified ?? string.Empty);
+
+ this.Age = response.GetFirstHeaderValue("Age").ToInt64(this.Age);
+
+ this.Date = response.GetFirstHeaderValue("Date").ToDateTime(this.Date);
+
+ List cacheControls = response.GetHeaderValues("cache-control");
+ if (cacheControls != null && cacheControls.Count > 0)
+ {
+ // Merge all Cache-Control header values into one
+ string cacheControl = cacheControls[0];
+ for (int i = 1; i < cacheControls.Count; ++i)
+ cacheControl += "," + cacheControls[i];
+
+ if (!string.IsNullOrEmpty(cacheControl))
+ {
+ HeaderParser parser = new HeaderParser(cacheControl);
+
+ if (parser.Values != null)
+ {
+ for (int i = 0; i < parser.Values.Count; ++i)
+ {
+ var kvp = parser.Values[i];
+
+ switch(kvp.Key.ToLowerInvariant())
+ {
+ case "max-age":
+ if (kvp.HasValue)
+ {
+ // Some cache proxies will return float values
+ double maxAge;
+ if (double.TryParse(kvp.Value, out maxAge))
+ this.MaxAge = (int)maxAge;
+ else
+ this.MaxAge = 0;
+ }
+ else
+ this.MaxAge = 0;
+ break;
+
+ case "stale-while-revalidate":
+ this.StaleWhileRevalidate = kvp.HasValue ? kvp.Value.ToInt64(0) : 0;
+ break;
+
+ case "stale-if-error":
+ this.StaleIfError = kvp.HasValue ? kvp.Value.ToInt64(0) : 0;
+ break;
+
+ case "must-revalidate":
+ this.MustRevalidate = true;
+ break;
+
+ case "no-cache":
+ this.NoCache = true;
+ break;
+ }
+ }
+ }
+
+ //string[] options = cacheControl.ToLowerInvariant().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ //
+ //string[] kvp = options.FindOption("max-age");
+ //if (kvp != null && kvp.Length > 1)
+ //{
+ // // Some cache proxies will return float values
+ // double maxAge;
+ // if (double.TryParse(kvp[1], out maxAge))
+ // this.MaxAge = (int)maxAge;
+ // else
+ // this.MaxAge = 0;
+ //}
+ //else
+ // this.MaxAge = 0;
+ //
+ //kvp = options.FindOption("stale-while-revalidate");
+ //if (kvp != null && kvp.Length == 2 && !string.IsNullOrEmpty(kvp[1]))
+ // this.StaleWhileRevalidate = kvp[1].ToInt64(0);
+ //
+ //kvp = options.FindOption("stale-if-error");
+ //if (kvp != null && kvp.Length == 2 && !string.IsNullOrEmpty(kvp[1]))
+ // this.StaleIfError = kvp[1].ToInt64(0);
+ //
+ //this.MustRevalidate = cacheControl.Contains("must-revalidate");
+ //this.NoCache = cacheControl.Contains("no-cache");
+ }
+ }
+
+ this.Received = DateTime.UtcNow;
+ }
+
+ ///
+ /// isInError should be true if downloading the content fails, and in that case, it might extend the content's freshness
+ ///
+ public bool WillExpireInTheFuture(bool isInError)
+ {
+ if (!IsExists())
+ return false;
+
+ // https://csswizardry.com/2019/03/cache-control-for-civilians/#no-cache
+ // no-cache will always hit the network as it has to revalidate with the server before it can release the browser’s cached copy (unless the server responds with a fresher response),
+ // but if the server responds favourably, the network transfer is only a file’s headers: the body can be grabbed from cache rather than redownloaded.
+ if (this.NoCache)
+ return false;
+
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4 :
+ // The max-age directive takes priority over Expires
+ if (MaxAge > 0)
+ {
+ // Age calculation:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3
+
+ long apparent_age = Math.Max(0, (long)(this.Received - this.Date).TotalSeconds);
+ long corrected_received_age = Math.Max(apparent_age, this.Age);
+ long resident_time = (long)(DateTime.UtcNow - this.Date).TotalSeconds;
+ long current_age = corrected_received_age + resident_time;
+
+ long maxAge = this.MaxAge + (this.NoCache ? 0 : this.StaleWhileRevalidate) + (isInError ? this.StaleIfError : 0);
+
+ return current_age < maxAge || this.Expires > DateTime.UtcNow;
+ }
+
+ return this.Expires > DateTime.UtcNow;
+ }
+
+ internal void SetUpRevalidationHeaders(HTTPRequest request)
+ {
+ if (!IsExists())
+ return;
+
+ // -If an entity tag has been provided by the origin server, MUST use that entity tag in any cache-conditional request (using If-Match or If-None-Match).
+ // -If only a Last-Modified value has been provided by the origin server, SHOULD use that value in non-subrange cache-conditional requests (using If-Modified-Since).
+ // -If both an entity tag and a Last-Modified value have been provided by the origin server, SHOULD use both validators in cache-conditional requests. This allows both HTTP/1.0 and HTTP/1.1 caches to respond appropriately.
+
+ if (!string.IsNullOrEmpty(ETag))
+ request.SetHeader("If-None-Match", ETag);
+
+ if (!string.IsNullOrEmpty(LastModified))
+ request.SetHeader("If-Modified-Since", LastModified);
+ }
+
+ public System.IO.Stream GetBodyStream(out int length)
+ {
+ if (!IsExists())
+ {
+ length = 0;
+ return null;
+ }
+
+ length = BodyLength;
+
+ LastAccess = DateTime.UtcNow;
+
+ Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.OpenRead);
+ stream.Seek(-length, System.IO.SeekOrigin.End);
+
+ return stream;
+ }
+
+ internal HTTPResponse ReadResponseTo(HTTPRequest request)
+ {
+ if (!IsExists())
+ return null;
+
+ LastAccess = DateTime.UtcNow;
+
+ using (Stream stream = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.OpenRead))
+ {
+ var response = new HTTPResponse(request, stream, request.UseStreaming, true);
+ response.CacheFileInfo = this;
+ response.Receive(BodyLength);
+ return response;
+ }
+ }
+
+ internal void Store(HTTPResponse response)
+ {
+ if (!HTTPCacheService.IsSupported)
+ return;
+
+ string path = GetPath();
+
+ // Path name too long, we don't want to get exceptions
+ if (path.Length > HTTPManager.MaxPathLength)
+ return;
+
+ if (HTTPManager.IOService.FileExists(path))
+ Delete();
+
+ using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create))
+ {
+ writer.WriteLine("HTTP/{0}.{1} {2} {3}", response.VersionMajor, response.VersionMinor, response.StatusCode, response.Message);
+ foreach (var kvp in response.Headers)
+ {
+ for (int i = 0; i < kvp.Value.Count; ++i)
+ writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
+ }
+
+ writer.WriteLine();
+
+ writer.Write(response.Data, 0, response.Data.Length);
+ }
+
+ BodyLength = response.Data.Length;
+
+ LastAccess = DateTime.UtcNow;
+
+ SetUpCachingValues(response);
+ }
+
+ internal System.IO.Stream GetSaveStream(HTTPResponse response)
+ {
+ if (!HTTPCacheService.IsSupported)
+ return null;
+
+ LastAccess = DateTime.UtcNow;
+
+ string path = GetPath();
+
+ if (HTTPManager.IOService.FileExists(path))
+ Delete();
+
+ // Path name too long, we don't want to get exceptions
+ if (path.Length > HTTPManager.MaxPathLength)
+ return null;
+
+ // First write out the headers
+ using (Stream writer = HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Create))
+ {
+ writer.WriteLine("HTTP/1.1 {0} {1}", response.StatusCode, response.Message);
+ foreach (var kvp in response.Headers)
+ {
+ for (int i = 0; i < kvp.Value.Count; ++i)
+ writer.WriteLine("{0}: {1}", kvp.Key, kvp.Value[i]);
+ }
+
+ writer.WriteLine();
+ }
+
+ // If caching is enabled and the response is from cache, and no content-length header set, then we set one to the response.
+ if (response.IsFromCache && !response.HasHeader("content-length"))
+ response.AddHeader("content-length", BodyLength.ToString());
+
+ SetUpCachingValues(response);
+
+ // then create the stream with Append FileMode
+ return HTTPManager.IOService.CreateFileStream(GetPath(), FileStreamModes.Append);
+ }
+
+ #endregion
+
+ #region IComparable
+
+ public int CompareTo(HTTPCacheFileInfo other)
+ {
+ return this.LastAccess.CompareTo(other.LastAccess);
+ }
+
+ #endregion
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs.meta b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7aa44aab98c2d5321e3c6933dcaca0864012ec68
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileInfo.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 1f50da5c22225384fbe271e0453fe545
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs
new file mode 100644
index 0000000000000000000000000000000000000000..21b0ed6963983775d9e570b8bce450d1683094e5
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs
@@ -0,0 +1,43 @@
+#if !BESTHTTP_DISABLE_CACHING
+
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Caching
+{
+ //static class HTTPCacheFileLock
+ //{
+ // private static Dictionary FileLocks = new Dictionary();
+ // //private static object SyncRoot = new object();
+ // private static System.Threading.ReaderWriterLockSlim rwLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
+ //
+ // internal static object Acquire(Uri uri)
+ // {
+ // rwLock.EnterUpgradeableReadLock();
+ // try
+ // {
+ // object fileLock;
+ // if (!FileLocks.TryGetValue(uri, out fileLock))
+ // {
+ // rwLock.EnterWriteLock();
+ // try
+ // {
+ // FileLocks.Add(uri, fileLock = new object());
+ // }
+ // finally
+ // {
+ // rwLock.ExitWriteLock();
+ // }
+ // }
+ //
+ // return fileLock;
+ // }
+ // finally
+ // {
+ // rwLock.ExitUpgradeableReadLock();
+ // }
+ // }
+ //}
+}
+
+#endif
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs.meta b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5ee7d6ed7fc478872667b9f6d9fac77d2dab3d11
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheFileLock.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 7b141dab023cd9d438e0585b93cf2826
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ffffd96e19f5d3a5dab258fec5fb08d1a3171654
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs
@@ -0,0 +1,27 @@
+#if !BESTHTTP_DISABLE_CACHING
+
+using System;
+
+namespace BestHTTP.Caching
+{
+ public sealed class HTTPCacheMaintananceParams
+ {
+ ///
+ /// Delete cache entries that accessed older then this value. If TimeSpan.FromSeconds(0) is used then all cache entries will be deleted. With TimeSpan.FromDays(2) entries that older then two days will be deleted.
+ ///
+ public TimeSpan DeleteOlder { get; private set; }
+
+ ///
+ /// If the cache is larger then the MaxCacheSize after the first maintanance step, then the maintanance job will forcedelete cache entries starting with the oldest last accessed one.
+ ///
+ public ulong MaxCacheSize { get; private set; }
+
+ public HTTPCacheMaintananceParams(TimeSpan deleteOlder, ulong maxCacheSize)
+ {
+ this.DeleteOlder = deleteOlder;
+ this.MaxCacheSize = maxCacheSize;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs.meta b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..0d3225b7019027f068edc3f8f28020be12c71d37
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheMaintananceParams.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 3ce5552e539e38647a8f37ab36951bf7
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aac31a154a1f935c8184b8ba1c9f37436284e214
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs
@@ -0,0 +1,919 @@
+#if !BESTHTTP_DISABLE_CACHING
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+//
+// Version 1: Initial release
+// Version 2: Filenames are generated from an index.
+//
+
+namespace BestHTTP.Caching
+{
+ using BestHTTP.Core;
+ using BestHTTP.Extensions;
+ using BestHTTP.PlatformSupport.FileSystem;
+
+ public sealed class UriComparer : IEqualityComparer
+ {
+ public bool Equals(Uri x, Uri y)
+ {
+ return Uri.Compare(x, y, UriComponents.HttpRequestUrl, UriFormat.SafeUnescaped, StringComparison.Ordinal) == 0;
+ }
+
+ public int GetHashCode(Uri uri)
+ {
+ return uri.ToString().GetHashCode();
+ }
+ }
+
+
+ public static class HTTPCacheService
+ {
+ #region Properties & Fields
+
+ ///
+ /// Library file-format versioning support
+ ///
+ private const int LibraryVersion = 3;
+
+ public static bool IsSupported
+ {
+ get
+ {
+ if (IsSupportCheckDone)
+ return isSupported;
+
+ try
+ {
+#if UNITY_WEBGL && !UNITY_EDITOR
+ // Explicitly disable cahing under WebGL
+ isSupported = false;
+#else
+ // If DirectoryExists throws an exception we will set IsSupprted to false
+ HTTPManager.IOService.DirectoryExists(HTTPManager.GetRootCacheFolder());
+ isSupported = true;
+#endif
+ }
+ catch
+ {
+ isSupported = false;
+
+ HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
+ }
+ finally
+ {
+ IsSupportCheckDone = true;
+ }
+
+ return isSupported;
+ }
+ }
+ private static bool isSupported;
+ private static bool IsSupportCheckDone;
+
+ private static Dictionary library;
+
+ private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
+
+ private static Dictionary UsedIndexes = new Dictionary();
+
+ internal static string CacheFolder { get; private set; }
+ private static string LibraryPath { get; set; }
+
+ private volatile static bool InClearThread;
+ private volatile static bool InMaintainenceThread;
+
+ ///
+ /// This property returns true while the service is in a Clear or Maintenance thread.
+ ///
+ public static bool IsDoingMaintainence { get { return InClearThread || InMaintainenceThread; } }
+
+ ///
+ /// Stores the index of the next stored entity. The entity's file name is generated from this index.
+ ///
+ private static UInt64 NextNameIDX;
+
+#endregion
+
+ static HTTPCacheService()
+ {
+ NextNameIDX = 0x0001;
+ }
+
+#region Common Functions
+
+ internal static void CheckSetup()
+ {
+ if (!HTTPCacheService.IsSupported)
+ return;
+
+ try
+ {
+ SetupCacheFolder();
+ LoadLibrary();
+ }
+ catch
+ { }
+ }
+
+ internal static void SetupCacheFolder()
+ {
+ if (!HTTPCacheService.IsSupported)
+ return;
+
+ try
+ {
+ if (string.IsNullOrEmpty(CacheFolder) || string.IsNullOrEmpty(LibraryPath))
+ {
+ CacheFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "HTTPCache");
+ if (!HTTPManager.IOService.DirectoryExists(CacheFolder))
+ HTTPManager.IOService.DirectoryCreate(CacheFolder);
+
+ LibraryPath = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Library");
+ }
+ }
+ catch
+ {
+ isSupported = false;
+
+ HTTPManager.Logger.Warning("HTTPCacheService", "Cache Service Disabled!");
+ }
+ }
+
+ internal static UInt64 GetNameIdx()
+ {
+ UInt64 result = NextNameIDX;
+
+ do
+ {
+ NextNameIDX = ++NextNameIDX % UInt64.MaxValue;
+ } while (UsedIndexes.ContainsKey(NextNameIDX));
+
+ return result;
+ }
+
+ public static bool HasEntity(Uri uri)
+ {
+ if (!IsSupported)
+ return false;
+
+ CheckSetup();
+
+ rwLock.EnterReadLock();
+ try
+ {
+ return library.ContainsKey(uri);
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+ public static bool DeleteEntity(Uri uri, bool removeFromLibrary = true)
+ {
+ if (!IsSupported)
+ return false;
+
+ // 2019.05.10: Removed all locking except the one on the library.
+
+ CheckSetup();
+
+ rwLock.EnterUpgradeableReadLock();
+ try
+ {
+ DeleteEntityImpl(uri, removeFromLibrary, true);
+
+ return true;
+ }
+ finally
+ {
+ rwLock.ExitUpgradeableReadLock();
+ }
+ }
+
+ private static void DeleteEntityImpl(Uri uri, bool removeFromLibrary = true, bool useLocking = false)
+ {
+ HTTPCacheFileInfo info;
+ bool inStats = library.TryGetValue(uri, out info);
+ if (inStats)
+ info.Delete();
+
+ if (inStats && removeFromLibrary)
+ {
+ if (useLocking)
+ rwLock.EnterWriteLock();
+ try
+ {
+ library.Remove(uri);
+ UsedIndexes.Remove(info.MappedNameIDX);
+ }
+ finally
+ {
+ if (useLocking)
+ rwLock.ExitWriteLock();
+ }
+ }
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
+ }
+
+ internal static bool IsCachedEntityExpiresInTheFuture(HTTPRequest request)
+ {
+ if (!IsSupported || request.DisableCache)
+ return false;
+
+ CheckSetup();
+
+ HTTPCacheFileInfo info = null;
+ rwLock.EnterReadLock();
+ try
+ {
+ if (!library.TryGetValue(request.CurrentUri, out info))
+ return false;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+
+ return info.WillExpireInTheFuture(request.State == HTTPRequestStates.ConnectionTimedOut ||
+ request.State == HTTPRequestStates.TimedOut ||
+ request.State == HTTPRequestStates.Error ||
+ (request.State == HTTPRequestStates.Finished && request.Response != null && request.Response.StatusCode >= 500));
+ }
+
+ ///
+ /// Utility function to set the cache control headers according to the spec.: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
+ ///
+ ///
+ internal static void SetHeaders(HTTPRequest request)
+ {
+ if (!IsSupported)
+ return;
+
+ CheckSetup();
+
+ request.RemoveHeader("If-None-Match");
+ request.RemoveHeader("If-Modified-Since");
+
+ HTTPCacheFileInfo info = null;
+ rwLock.EnterReadLock();
+ try
+ {
+ if (!library.TryGetValue(request.CurrentUri, out info))
+ return;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+
+ info.SetUpRevalidationHeaders(request);
+ }
+
+#endregion
+
+#region Get Functions
+
+ public static HTTPCacheFileInfo GetEntity(Uri uri)
+ {
+ if (!IsSupported)
+ return null;
+
+ CheckSetup();
+
+ HTTPCacheFileInfo info = null;
+
+ rwLock.EnterReadLock();
+ try
+ {
+ library.TryGetValue(uri, out info);
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+
+ return info;
+ }
+
+ internal static HTTPResponse GetFullResponse(HTTPRequest request)
+ {
+ if (!IsSupported)
+ return null;
+
+ CheckSetup();
+
+ HTTPCacheFileInfo info = null;
+ rwLock.EnterReadLock();
+ try
+ {
+ if (!library.TryGetValue(request.CurrentUri, out info))
+ return null;
+
+ return info.ReadResponseTo(request);
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+#endregion
+
+#region Storing
+
+ ///
+ /// Checks if the given response can be cached. http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4
+ ///
+ /// Returns true if cacheable, false otherwise.
+ internal static bool IsCacheble(Uri uri, HTTPMethods method, HTTPResponse response)
+ {
+ if (!IsSupported)
+ return false;
+
+ if (method != HTTPMethods.Get)
+ return false;
+
+ if (response == null)
+ return false;
+
+ // https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.12 - Cache Replacement
+ // It MAY insert it into cache storage and MAY, if it meets all other requirements, use it to respond to any future requests that would previously have caused the old response to be returned.
+ //if (response.StatusCode == 304)
+ // return false;
+
+ // Partial response
+ if (response.StatusCode == 206)
+ return false;
+
+ if (response.StatusCode < 200 || response.StatusCode >= 400)
+ return false;
+
+ //http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.2
+ bool hasValidMaxAge = false;
+ var cacheControls = response.GetHeaderValues("cache-control");
+ if (cacheControls != null)
+ {
+ if (cacheControls.Exists(headerValue =>
+ {
+ HeaderParser parser = new HeaderParser(headerValue);
+ if (parser.Values != null && parser.Values.Count > 0) {
+ for (int i = 0; i < parser.Values.Count; ++i)
+ {
+ var value = parser.Values[i];
+
+ // https://csswizardry.com/2019/03/cache-control-for-civilians/#no-store
+ if (value.Key == "no-store")
+ return true;
+
+ if (value.Key == "max-age" && value.HasValue)
+ {
+ double maxAge;
+ if (double.TryParse(value.Value, out maxAge))
+ {
+ // A negative max-age value is a no cache
+ if (maxAge <= 0)
+ return true;
+ hasValidMaxAge = true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }))
+ return false;
+ }
+
+ var pragmas = response.GetHeaderValues("pragma");
+ if (pragmas != null)
+ {
+ if (pragmas.Exists(headerValue =>
+ {
+ string value = headerValue.ToLower();
+ return value.Contains("no-store") || value.Contains("no-cache");
+ }))
+ return false;
+ }
+
+ // Responses with byte ranges not supported yet.
+ var byteRanges = response.GetHeaderValues("content-range");
+ if (byteRanges != null)
+ return false;
+
+ // Store only if at least one caching header with proper value present
+
+ var etag = response.GetFirstHeaderValue("ETag");
+ if (!string.IsNullOrEmpty(etag))
+ return true;
+
+ var expires = response.GetFirstHeaderValue("Expires").ToDateTime(DateTime.FromBinary(0));
+ if (expires >= DateTime.UtcNow)
+ return true;
+
+ if (response.GetFirstHeaderValue("Last-Modified") != null)
+ return true;
+
+ return hasValidMaxAge;
+ }
+
+ internal static HTTPCacheFileInfo Store(Uri uri, HTTPMethods method, HTTPResponse response)
+ {
+ if (response == null || response.Data == null || response.Data.Length == 0)
+ return null;
+
+ if (!IsSupported)
+ return null;
+
+ CheckSetup();
+
+ HTTPCacheFileInfo info = null;
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ if (!library.TryGetValue(uri, out info))
+ {
+ library.Add(uri, info = new HTTPCacheFileInfo(uri));
+ UsedIndexes.Add(info.MappedNameIDX, info);
+ }
+
+ try
+ {
+ info.Store(response);
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTPCacheService", string.Format("{0} - Saved to cache", uri.ToString()), response.baseRequest.Context);
+ }
+ catch
+ {
+ // If something happens while we write out the response, than we will delete it because it might be in an invalid state.
+ DeleteEntityImpl(uri);
+
+ throw;
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ return info;
+ }
+
+ internal static void SetUpCachingValues(Uri uri, HTTPResponse response)
+ {
+ if (!IsSupported)
+ return;
+
+ CheckSetup();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ HTTPCacheFileInfo info = null;
+ if (!library.TryGetValue(uri, out info))
+ {
+ library.Add(uri, info = new HTTPCacheFileInfo(uri));
+ UsedIndexes.Add(info.MappedNameIDX, info);
+ }
+
+ try
+ {
+ info.SetUpCachingValues(response);
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTPCacheService", string.Format("{0} - SetUpCachingValues done!", uri.ToString()), response.baseRequest.Context);
+ }
+ catch
+ {
+ // If something happens while we write out the response, than we will delete it because it might be in an invalid state.
+ DeleteEntityImpl(uri);
+
+ throw;
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+
+ internal static System.IO.Stream PrepareStreamed(Uri uri, HTTPResponse response)
+ {
+ if (!IsSupported)
+ return null;
+
+ CheckSetup();
+
+ HTTPCacheFileInfo info;
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ if (!library.TryGetValue(uri, out info))
+ {
+ library.Add(uri, info = new HTTPCacheFileInfo(uri));
+ UsedIndexes.Add(info.MappedNameIDX, info);
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ try
+ {
+ return info.GetSaveStream(response);
+ }
+ catch
+ {
+ // If something happens while we write out the response, than we will delete it because it might be in an invalid state.
+ DeleteEntityImpl(uri, true, true);
+
+ throw;
+ }
+ }
+
+#endregion
+
+#region Public Maintenance Functions
+
+ ///
+ /// Deletes all cache entity. Non blocking.
+ /// Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!
+ ///
+ public static void BeginClear()
+ {
+ if (!IsSupported)
+ return;
+
+ if (InClearThread)
+ return;
+ InClearThread = true;
+
+ SetupCacheFolder();
+
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(ClearImpl);
+ }
+
+ private static void ClearImpl()
+ {
+ if (!IsSupported)
+ return;
+
+ CheckSetup();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ // GetFiles will return a string array that contains the files in the folder with the full path
+ string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
+
+ if (cacheEntries != null)
+ for (int i = 0; i < cacheEntries.Length; ++i)
+ {
+ // We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
+ // So while there might be some problem with any file, we don't want to abort the whole for loop
+ try
+ {
+ HTTPManager.IOService.FileDelete(cacheEntries[i]);
+ }
+ catch
+ { }
+ }
+ }
+ finally
+ {
+ UsedIndexes.Clear();
+ library.Clear();
+ NextNameIDX = 0x0001;
+
+ InClearThread = false;
+
+ rwLock.ExitWriteLock();
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
+ }
+ }
+
+ ///
+ /// Deletes all expired cache entity.
+ /// Call it only if there no requests currently processed, because cache entries can be deleted while a server sends back a 304 result, so there will be no data to read from the cache!
+ ///
+ public static void BeginMaintainence(HTTPCacheMaintananceParams maintananceParam)
+ {
+ if (maintananceParam == null)
+ throw new ArgumentNullException("maintananceParams == null");
+
+ if (!HTTPCacheService.IsSupported)
+ return;
+
+ if (InMaintainenceThread)
+ return;
+
+ InMaintainenceThread = true;
+
+ SetupCacheFolder();
+
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(MaintananceImpl, maintananceParam);
+ }
+
+ private static void MaintananceImpl(HTTPCacheMaintananceParams maintananceParam)
+ {
+ CheckSetup();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ // Delete cache entries older than the given time.
+ DateTime deleteOlderAccessed = DateTime.UtcNow - maintananceParam.DeleteOlder;
+ List removedEntities = new List();
+ foreach (var kvp in library)
+ if (kvp.Value.LastAccess < deleteOlderAccessed)
+ {
+ DeleteEntityImpl(kvp.Key, false, false);
+ removedEntities.Add(kvp.Value);
+ }
+
+ for (int i = 0; i < removedEntities.Count; ++i)
+ {
+ library.Remove(removedEntities[i].Uri);
+ UsedIndexes.Remove(removedEntities[i].MappedNameIDX);
+ }
+ removedEntities.Clear();
+
+ ulong cacheSize = GetCacheSizeImpl();
+
+ // This step will delete all entries starting with the oldest LastAccess property while the cache size greater then the MaxCacheSize in the given param.
+ if (cacheSize > maintananceParam.MaxCacheSize)
+ {
+ List fileInfos = new List(library.Count);
+
+ foreach (var kvp in library)
+ fileInfos.Add(kvp.Value);
+
+ fileInfos.Sort();
+
+ int idx = 0;
+ while (cacheSize >= maintananceParam.MaxCacheSize && idx < fileInfos.Count)
+ {
+ try
+ {
+ var fi = fileInfos[idx];
+ ulong length = (ulong)fi.BodyLength;
+
+ DeleteEntityImpl(fi.Uri);
+
+ cacheSize -= length;
+ }
+ catch
+ { }
+ finally
+ {
+ ++idx;
+ }
+ }
+ }
+ }
+ finally
+ {
+ InMaintainenceThread = false;
+ rwLock.ExitWriteLock();
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
+ }
+ }
+
+ public static int GetCacheEntityCount()
+ {
+ if (!HTTPCacheService.IsSupported)
+ return 0;
+
+ CheckSetup();
+
+ rwLock.EnterReadLock();
+ try
+ {
+ return library.Count;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+ public static ulong GetCacheSize()
+ {
+ if (!IsSupported)
+ return 0;
+
+ CheckSetup();
+
+ rwLock.EnterReadLock();
+ try
+ {
+ return GetCacheSizeImpl();
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+ private static ulong GetCacheSizeImpl()
+ {
+ ulong size = 0;
+
+ foreach (var kvp in library)
+ if (kvp.Value.BodyLength > 0)
+ size += (ulong)kvp.Value.BodyLength;
+
+ return size;
+ }
+
+#endregion
+
+#region Cache Library Management
+
+ private static void LoadLibrary()
+ {
+ // Already loaded?
+ if (library != null)
+ return;
+
+ if (!IsSupported)
+ return;
+
+ int version = 1;
+
+ rwLock.EnterWriteLock();
+
+ library = new Dictionary(new UriComparer());
+ try
+ {
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
+ using (var br = new System.IO.BinaryReader(fs))
+ {
+ version = br.ReadInt32();
+
+ if (version > 1)
+ NextNameIDX = br.ReadUInt64();
+
+ int statCount = br.ReadInt32();
+
+ for (int i = 0; i < statCount; ++i)
+ {
+ Uri uri = new Uri(br.ReadString());
+
+ var entity = new HTTPCacheFileInfo(uri, br, version);
+ if (entity.IsExists())
+ {
+ library.Add(uri, entity);
+
+ if (version > 1)
+ UsedIndexes.Add(entity.MappedNameIDX, entity);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Exception("HTTPCacheService", "LoadLibrary", ex);
+ }
+
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ if (version == 1)
+ BeginClear();
+ else
+ DeleteUnusedFiles();
+ }
+
+ internal static void SaveLibrary()
+ {
+ if (library == null)
+ return;
+
+ if (!IsSupported)
+ return;
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
+ using (var bw = new System.IO.BinaryWriter(fs))
+ {
+ bw.Write(LibraryVersion);
+ bw.Write(NextNameIDX);
+
+ bw.Write(library.Count);
+ foreach (var kvp in library)
+ {
+ bw.Write(kvp.Key.ToString());
+
+ kvp.Value.SaveTo(bw);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Exception("HTTPCacheService", "SaveLibrary", ex);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+
+
+ internal static void SetBodyLength(Uri uri, int bodyLength)
+ {
+ if (!IsSupported)
+ return;
+
+ CheckSetup();
+
+ rwLock.EnterUpgradeableReadLock();
+ try
+ {
+ HTTPCacheFileInfo fileInfo;
+ if (library.TryGetValue(uri, out fileInfo))
+ fileInfo.BodyLength = bodyLength;
+ else
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ library.Add(uri, fileInfo = new HTTPCacheFileInfo(uri, DateTime.UtcNow, bodyLength));
+ UsedIndexes.Add(fileInfo.MappedNameIDX, fileInfo);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+ }
+ finally
+ {
+ rwLock.ExitUpgradeableReadLock();
+ }
+ }
+
+ ///
+ /// Deletes all files from the cache folder that isn't in the Library.
+ ///
+ private static void DeleteUnusedFiles()
+ {
+ if (!IsSupported)
+ return;
+
+ CheckSetup();
+
+ // GetFiles will return a string array that contains the files in the folder with the full path
+ string[] cacheEntries = HTTPManager.IOService.GetFiles(CacheFolder);
+
+ for (int i = 0; i < cacheEntries.Length; ++i)
+ {
+ // We need a try-catch block because between the Directory.GetFiles call and the File.Delete calls a maintenance job, or other file operations can delete any file from the cache folder.
+ // So while there might be some problem with any file, we don't want to abort the whole for loop
+ try
+ {
+ string filename = System.IO.Path.GetFileName(cacheEntries[i]);
+ UInt64 idx = 0;
+ bool deleteFile = false;
+ if (UInt64.TryParse(filename, System.Globalization.NumberStyles.AllowHexSpecifier, null, out idx))
+ {
+ rwLock.EnterReadLock();
+ try
+ {
+ deleteFile = !UsedIndexes.ContainsKey(idx);
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+ else
+ deleteFile = true;
+
+ if (deleteFile)
+ HTTPManager.IOService.FileDelete(cacheEntries[i]);
+ }
+ catch
+ { }
+ }
+ }
+
+#endregion
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs.meta b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..fa7c39fc43f1a1298c2a26ca2ccccc04f0c54d28
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Caching/HTTPCacheService.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: d29e8551599393f4193bc4daa6968607
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections.meta b/Assets/Plugins/BestHTTP/Source/Connections.meta
new file mode 100644
index 0000000000000000000000000000000000000000..edb913f20e098411c036af9a0950ec6748f4e6de
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 65fae491919869042be9bb2f969bf050
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fdb7de0f00ed0d033190e39050b5af7866a7ccab
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs
@@ -0,0 +1,124 @@
+using System;
+using BestHTTP.Logger;
+
+namespace BestHTTP.Connections
+{
+ public abstract class ConnectionBase : IDisposable
+ {
+ #region Public Properties
+
+ ///
+ /// The address of the server that this connection is bound to.
+ ///
+ public string ServerAddress { get; protected set; }
+
+ ///
+ /// The state of this connection.
+ ///
+ public HTTPConnectionStates State { get; internal set; }
+
+ ///
+ /// If the State is HTTPConnectionStates.Processing, then it holds a HTTPRequest instance. Otherwise it's null.
+ ///
+ public HTTPRequest CurrentRequest { get; internal set; }
+
+ ///
+ /// How much the connection kept alive after its last request processing.
+ ///
+ public virtual TimeSpan KeepAliveTime { get; protected set; }
+
+ public virtual bool CanProcessMultiple { get { return false; } }
+
+ ///
+ /// When we start to process the current request. It's set after the connection is established.
+ ///
+ public DateTime StartTime { get; protected set; }
+
+ public Uri LastProcessedUri { get; protected set; }
+
+ public DateTime LastProcessTime { get; protected set; }
+
+ internal LoggingContext Context;
+
+ #endregion
+
+ #region Privates
+
+ private bool IsThreaded;
+
+ #endregion
+
+ public ConnectionBase(string serverAddress)
+ :this(serverAddress, true)
+ {}
+
+ public ConnectionBase(string serverAddress, bool threaded)
+ {
+ this.ServerAddress = serverAddress;
+ this.State = HTTPConnectionStates.Initial;
+ this.LastProcessTime = DateTime.Now;
+ this.KeepAliveTime = HTTPManager.MaxConnectionIdleTime;
+ this.IsThreaded = threaded;
+
+ this.Context = new LoggingContext(this);
+ this.Context.Add("ServerAddress", serverAddress);
+ this.Context.Add("Threaded", threaded);
+ }
+
+ internal virtual void Process(HTTPRequest request)
+ {
+ if (State == HTTPConnectionStates.Processing)
+ throw new Exception("Connection already processing a request! " + this.ToString());
+
+ StartTime = DateTime.MaxValue;
+ State = HTTPConnectionStates.Processing;
+
+ CurrentRequest = request;
+ LastProcessedUri = CurrentRequest.CurrentUri;
+
+ if (IsThreaded)
+ PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ThreadFunc);
+ else
+ ThreadFunc();
+ }
+
+ protected virtual void ThreadFunc()
+ {
+
+ }
+
+ public ShutdownTypes ShutdownType { get; protected set; }
+
+ ///
+ /// Called when the plugin shuts down immediately.
+ ///
+ public virtual void Shutdown(ShutdownTypes type)
+ {
+ this.ShutdownType = type;
+ }
+
+ #region Dispose Pattern
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+
+ ~ConnectionBase()
+ {
+ Dispose(false);
+ }
+
+ #endregion
+
+ public override string ToString()
+ {
+ return string.Format("[{0}:{1}]", this.GetHashCode(), this.ServerAddress);
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..695ea4dc3a9f298d283aded772b6284586cb0ae3
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionBase.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: f59e4337e77f0be4d8f6649beaca8362
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..38fb3ef19dbebf922006c56d9e7a4cb2601bba55
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs
@@ -0,0 +1,368 @@
+using System;
+using System.Collections.Generic;
+using BestHTTP.Authentication;
+using BestHTTP.Core;
+using BestHTTP.Extensions;
+
+#if !BESTHTTP_DISABLE_CACHING
+using BestHTTP.Caching;
+#endif
+#if !BESTHTTP_DISABLE_COOKIES
+using BestHTTP.Cookies;
+#endif
+
+using BestHTTP.Logger;
+using BestHTTP.Timings;
+
+namespace BestHTTP.Connections
+{
+ ///
+ /// https://tools.ietf.org/html/draft-thomson-hybi-http-timeout-03
+ /// Test servers: http://tools.ietf.org/ http://nginx.org/
+ ///
+ public sealed class KeepAliveHeader
+ {
+ ///
+ /// A host sets the value of the "timeout" parameter to the time that the host will allow an idle connection to remain open before it is closed. A connection is idle if no data is sent or received by a host.
+ ///
+ public TimeSpan TimeOut { get; private set; }
+
+ ///
+ /// The "max" parameter has been used to indicate the maximum number of requests that would be made on the connection.This parameter is deprecated.Any limit on requests can be enforced by sending "Connection: close" and closing the connection.
+ ///
+ public int MaxRequests { get; private set; }
+
+ public void Parse(List headerValues)
+ {
+ HeaderParser parser = new HeaderParser(headerValues[0]);
+ HeaderValue value;
+ if (parser.TryGet("timeout", out value) && value.HasValue)
+ {
+ int intValue = 0;
+ if (int.TryParse(value.Value, out intValue) && intValue > 1)
+ this.TimeOut = TimeSpan.FromSeconds(intValue - 1);
+ else
+ this.TimeOut = TimeSpan.MaxValue;
+ }
+
+ if (parser.TryGet("max", out value) && value.HasValue)
+ {
+ int intValue = 0;
+ if (int.TryParse("max", out intValue))
+ this.MaxRequests = intValue;
+ else
+ this.MaxRequests = int.MaxValue;
+ }
+ }
+ }
+
+ public static class ConnectionHelper
+ {
+ public static void HandleResponse(string context, HTTPRequest request, out bool resendRequest, out HTTPConnectionStates proposedConnectionState, ref KeepAliveHeader keepAlive, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
+ {
+ resendRequest = false;
+ proposedConnectionState = HTTPConnectionStates.Processing;
+
+ if (request.Response != null)
+ {
+#if !BESTHTTP_DISABLE_COOKIES
+ // Try to store cookies before we do anything else, as we may remove the response deleting the cookies as well.
+ if (request.IsCookiesEnabled)
+ CookieJar.Set(request.Response);
+#endif
+
+ switch (request.Response.StatusCode)
+ {
+ // Not authorized
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
+ case 401:
+ {
+ string authHeader = DigestStore.FindBest(request.Response.GetHeaderValues("www-authenticate"));
+ if (!string.IsNullOrEmpty(authHeader))
+ {
+ var digest = DigestStore.GetOrCreate(request.CurrentUri);
+ digest.ParseChallange(authHeader);
+
+ if (request.Credentials != null && digest.IsUriProtected(request.CurrentUri) && (!request.HasHeader("Authorization") || digest.Stale))
+ resendRequest = true;
+ }
+
+ goto default;
+ }
+
+ case 407:
+ {
+ if (request.Proxy == null)
+ goto default;
+
+ resendRequest = request.Proxy.SetupRequest(request);
+
+ goto default;
+ }
+
+ // Redirected
+ case 301: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
+ case 302: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
+ case 307: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8
+ case 308: // http://tools.ietf.org/html/rfc7238
+ {
+ if (request.RedirectCount >= request.MaxRedirects)
+ goto default;
+ request.RedirectCount++;
+
+ string location = request.Response.GetFirstHeaderValue("location");
+ if (!string.IsNullOrEmpty(location))
+ {
+ Uri redirectUri = ConnectionHelper.GetRedirectUri(request, location);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Redirected to Location: '{1}' redirectUri: '{1}'", context, location, redirectUri), loggingContext1, loggingContext2, loggingContext3);
+
+ if (redirectUri == request.CurrentUri)
+ {
+ HTTPManager.Logger.Information("HTTPConnection", string.Format("[{0}] - Redirected to the same location!", context), loggingContext1, loggingContext2, loggingContext3);
+ goto default;
+ }
+
+ // Let the user to take some control over the redirection
+ if (!request.CallOnBeforeRedirection(redirectUri))
+ {
+ HTTPManager.Logger.Information("HTTPConnection", string.Format("[{0}] OnBeforeRedirection returned False", context), loggingContext1, loggingContext2, loggingContext3);
+ goto default;
+ }
+
+ // Remove the previously set Host header.
+ request.RemoveHeader("Host");
+
+ // Set the Referer header to the last Uri.
+ request.SetHeader("Referer", request.CurrentUri.ToString());
+
+ // Set the new Uri, the CurrentUri will return this while the IsRedirected property is true
+ request.RedirectUri = redirectUri;
+
+ request.IsRedirected = true;
+
+ resendRequest = true;
+ }
+ else
+ throw new Exception(string.Format("[{0}] Got redirect status({1}) without 'location' header!", context, request.Response.StatusCode.ToString()));
+
+ goto default;
+ }
+
+#if !BESTHTTP_DISABLE_CACHING
+ case 304:
+ if (request.DisableCache)
+ break;
+
+ if (ConnectionHelper.LoadFromCache(context, request, loggingContext1, loggingContext2, loggingContext3))
+ {
+ request.Timing.Add(TimingEventNames.Loading_From_Cache);
+ HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache successfully!", context), loggingContext1, loggingContext2, loggingContext3);
+
+ // Update any caching value
+ HTTPCacheService.SetUpCachingValues(request.CurrentUri, request.Response);
+ }
+ else
+ {
+ HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - HandleResponse - Loaded from cache failed!", context), loggingContext1, loggingContext2, loggingContext3);
+ resendRequest = true;
+ }
+
+ break;
+#endif
+
+ default:
+#if !BESTHTTP_DISABLE_CACHING
+ ConnectionHelper.TryStoreInCache(request);
+#endif
+ break;
+ }
+
+ // Closing the stream is done manually?
+ if (request.Response != null && !request.Response.IsClosedManually)
+ {
+ // If we have a response and the server telling us that it closed the connection after the message sent to us, then
+ // we will close the connection too.
+ bool closeByServer = request.Response.HasHeaderWithValue("connection", "close");
+ bool closeByClient = !request.IsKeepAlive;
+
+ if (closeByServer || closeByClient)
+ {
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ else if (request.Response != null)
+ {
+ var keepAliveheaderValues = request.Response.GetHeaderValues("keep-alive");
+ if (keepAliveheaderValues != null && keepAliveheaderValues.Count > 0)
+ {
+ if (keepAlive == null)
+ keepAlive = new KeepAliveHeader();
+ keepAlive.Parse(keepAliveheaderValues);
+ }
+ }
+ }
+
+ // Null out the response here instead of the redirected cases (301, 302, 307, 308)
+ // because response might have a Connection: Close header that we would miss to process.
+ // If Connection: Close is present, the server is closing the connection and we would
+ // reuse that closed connection.
+ if (resendRequest)
+ {
+ // Discard the redirect response, we don't need it any more
+ request.Response = null;
+
+ if (proposedConnectionState == HTTPConnectionStates.Closed)
+ proposedConnectionState = HTTPConnectionStates.ClosedResendRequest;
+ }
+ }
+ }
+
+#if !BESTHTTP_DISABLE_CACHING
+ public static bool LoadFromCache(string context, HTTPRequest request, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
+ {
+ if (request.IsRedirected)
+ {
+ if (LoadFromCache(context, request, request.RedirectUri, loggingContext1, loggingContext2, loggingContext3))
+ return true;
+ else
+ {
+ Caching.HTTPCacheService.DeleteEntity(request.RedirectUri);
+ }
+ }
+
+ bool loaded = LoadFromCache(context, request, request.Uri, loggingContext1, loggingContext2, loggingContext3);
+ if (!loaded)
+ Caching.HTTPCacheService.DeleteEntity(request.Uri);
+
+ return loaded;
+ }
+
+ private static bool LoadFromCache(string context, HTTPRequest request, Uri uri, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - LoadFromCache for Uri: {1}", context, uri.ToString()), loggingContext1, loggingContext2, loggingContext3);
+
+ var cacheEntity = HTTPCacheService.GetEntity(uri);
+ if (cacheEntity == null)
+ {
+ HTTPManager.Logger.Warning("HTTPConnection", string.Format("[{0}] - LoadFromCache for Uri: {1} - Cached entity not found!", context, uri.ToString()), loggingContext1, loggingContext2, loggingContext3);
+ return false;
+ }
+
+ request.Response.CacheFileInfo = cacheEntity;
+
+ try
+ {
+ int bodyLength;
+ using (var cacheStream = cacheEntity.GetBodyStream(out bodyLength))
+ {
+ if (cacheStream == null)
+ return false;
+
+ if (!request.Response.HasHeader("content-length"))
+ request.Response.AddHeader("content-length", bodyLength.ToString());
+ request.Response.IsFromCache = true;
+
+ if (!request.CacheOnly)
+ request.Response.ReadRaw(cacheStream, bodyLength);
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static bool TryLoadAllFromCache(string context, HTTPRequest request, LoggingContext loggingContext1 = null, LoggingContext loggingContext2 = null, LoggingContext loggingContext3 = null)
+ {
+ // We will try to read the response from the cache, but if something happens we will fallback to the normal way.
+ try
+ {
+ //Unless specifically constrained by a cache-control (section 14.9) directive, a caching system MAY always store a successful response (see section 13.8) as a cache entity,
+ // MAY return it without validation if it is fresh, and MAY return it after successful validation.
+ // MAY return it without validation if it is fresh!
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("ConnectionHelper", string.Format("[{0}] - TryLoadAllFromCache - whole response loading from cache", context), loggingContext1, loggingContext2, loggingContext3);
+
+ request.Response = HTTPCacheService.GetFullResponse(request);
+
+ if (request.Response != null)
+ return true;
+ }
+ catch
+ {
+ HTTPManager.Logger.Verbose("ConnectionHelper", string.Format("[{0}] - TryLoadAllFromCache - failed to load content!", context), loggingContext1, loggingContext2, loggingContext3);
+ HTTPCacheService.DeleteEntity(request.CurrentUri);
+ }
+
+ return false;
+ }
+
+ public static void TryStoreInCache(HTTPRequest request)
+ {
+ // if UseStreaming && !DisableCache then we already wrote the response to the cache
+ if (!request.UseStreaming &&
+ !request.DisableCache &&
+ request.Response != null &&
+ HTTPCacheService.IsSupported &&
+ HTTPCacheService.IsCacheble(request.CurrentUri, request.MethodType, request.Response))
+ {
+ if (request.IsRedirected)
+ HTTPCacheService.Store(request.Uri, request.MethodType, request.Response);
+ else
+ HTTPCacheService.Store(request.CurrentUri, request.MethodType, request.Response);
+ request.Timing.Add(TimingEventNames.Writing_To_Cache);
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCacheLibrary));
+ }
+ }
+#endif
+
+ public static Uri GetRedirectUri(HTTPRequest request, string location)
+ {
+ Uri result = null;
+ try
+ {
+ result = new Uri(location);
+
+ if (result.IsFile || result.AbsolutePath == location)
+ result = null;
+ }
+ catch
+ {
+ // Sometimes the server sends back only the path and query component of the new uri
+ result = null;
+ }
+
+ if (result == null)
+ {
+ var baseURL = request.CurrentUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
+
+ if (!location.StartsWith("/"))
+ {
+ var segments = request.CurrentUri.Segments;
+ segments[segments.Length - 1] = location;
+
+ location = String.Join(string.Empty, segments);
+ if (location.StartsWith("//"))
+ location = location.Substring(1);
+ }
+
+ bool endsWithSlash = baseURL[baseURL.Length - 1] == '/';
+ bool startsWithSlash = location[0] == '/';
+ if (endsWithSlash && startsWithSlash)
+ result = new Uri(baseURL + location.Substring(1));
+ else if (!endsWithSlash && !startsWithSlash)
+ result = new Uri(baseURL + '/' + location);
+ else
+ result = new Uri(baseURL + location);
+ }
+
+ return result;
+ }
+
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..8654040ed7ad7353055e646fc932bedb81d16e9b
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/ConnectionHelper.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 91126523a3b8dfe4cb0ca1f8cb75d0ae
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs b/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9ab52c71bcb1d2dd26290a6d7457d18871cd2272
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs
@@ -0,0 +1,71 @@
+#if !UNITY_WEBGL || UNITY_EDITOR
+
+using System;
+using BestHTTP.Core;
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.FileSystem;
+
+namespace BestHTTP.Connections
+{
+ internal sealed class FileConnection : ConnectionBase
+ {
+ public FileConnection(string serverAddress)
+ :base(serverAddress)
+ { }
+
+ protected override void ThreadFunc()
+ {
+ try
+ {
+ // Step 1 : create a stream with header information
+ // Step 2 : create a stream from the file
+ // Step 3 : create a StreamList
+ // Step 4 : create a HTTPResponse object
+ // Step 5 : call the Receive function of the response object
+
+ using (System.IO.Stream fs = HTTPManager.IOService.CreateFileStream(this.CurrentRequest.CurrentUri.LocalPath, FileStreamModes.OpenRead))
+ using (StreamList stream = new StreamList(new BufferPoolMemoryStream(), fs))
+ {
+ // This will write to the MemoryStream
+ stream.Write("HTTP/1.1 200 Ok\r\n");
+ stream.Write("Content-Type: application/octet-stream\r\n");
+ stream.Write("Content-Length: " + fs.Length.ToString() + "\r\n");
+ stream.Write("\r\n");
+
+ stream.Seek(0, System.IO.SeekOrigin.Begin);
+
+ base.CurrentRequest.Response = new HTTPResponse(base.CurrentRequest, stream, base.CurrentRequest.UseStreaming, false);
+
+ if (!CurrentRequest.Response.Receive())
+ CurrentRequest.Response = null;
+ }
+ }
+ catch(Exception e)
+ {
+ CurrentRequest.Response = null;
+
+ if (!CurrentRequest.IsCancellationRequested)
+ {
+ CurrentRequest.Exception = e;
+ CurrentRequest.State = HTTPRequestStates.Error;
+ }
+ }
+ finally
+ {
+ if (this.CurrentRequest.IsCancellationRequested)
+ {
+ this.CurrentRequest.Response = null;
+ this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+ }
+ else if (this.CurrentRequest.Response == null)
+ this.CurrentRequest.State = HTTPRequestStates.Error;
+ else
+ this.CurrentRequest.State = HTTPRequestStates.Finished;
+
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ }
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..8d899f8e12a01566db9818f16895c00f0f5c5d99
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/FileConnection.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 24106f78264881a419f83ef4b760ded7
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..92741ce51a1950c9494ab50fc9f533082fa39b26
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs
@@ -0,0 +1,267 @@
+#if !UNITY_WEBGL || UNITY_EDITOR
+using System;
+using BestHTTP.Core;
+using BestHTTP.Logger;
+
+#if !BESTHTTP_DISABLE_CACHING
+using BestHTTP.Caching;
+#endif
+
+using BestHTTP.Timings;
+
+namespace BestHTTP.Connections
+{
+ public sealed class HTTP1Handler : IHTTPRequestHandler
+ {
+ public bool HasCustomRequestProcessor { get { return false; } }
+ public KeepAliveHeader KeepAlive { get { return this._keepAlive; } }
+ private KeepAliveHeader _keepAlive;
+
+ public bool CanProcessMultiple { get { return false; } }
+
+ private readonly HTTPConnection conn;
+
+ public LoggingContext Context { get; private set; }
+
+ public HTTP1Handler(HTTPConnection conn)
+ {
+ this.Context = new LoggingContext(this);
+ this.conn = conn;
+ }
+
+ public void Process(HTTPRequest request)
+ {
+ }
+
+ public void RunHandler()
+ {
+ HTTPManager.Logger.Information("HTTP1Handler", string.Format("[{0}] started processing request '{1}'", this, this.conn.CurrentRequest.CurrentUri.ToString()), this.Context, this.conn.CurrentRequest.Context);
+
+ System.Threading.Thread.CurrentThread.Name = "BestHTTP.HTTP1 R&W";
+
+ HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing;
+
+ bool resendRequest = false;
+
+ try
+ {
+ if (this.conn.CurrentRequest.IsCancellationRequested)
+ return;
+
+#if !BESTHTTP_DISABLE_CACHING
+ // Setup cache control headers before we send out the request
+ if (!this.conn.CurrentRequest.DisableCache)
+ HTTPCacheService.SetHeaders(this.conn.CurrentRequest);
+#endif
+
+ // Write the request to the stream
+ this.conn.CurrentRequest.QueuedAt = DateTime.MinValue;
+ this.conn.CurrentRequest.ProcessingStarted = DateTime.UtcNow;
+ this.conn.CurrentRequest.SendOutTo(this.conn.connector.Stream);
+ this.conn.CurrentRequest.Timing.Add(TimingEventNames.Request_Sent);
+
+ if (this.conn.CurrentRequest.IsCancellationRequested)
+ return;
+
+ this.conn.CurrentRequest.OnCancellationRequested += OnCancellationRequested;
+
+ // Receive response from the server
+ bool received = Receive(this.conn.CurrentRequest);
+
+ this.conn.CurrentRequest.Timing.Add(TimingEventNames.Response_Received);
+
+ if (this.conn.CurrentRequest.IsCancellationRequested)
+ return;
+
+ if (!received && this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries)
+ {
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ this.conn.CurrentRequest.Retries++;
+ resendRequest = true;
+ return;
+ }
+
+ ConnectionHelper.HandleResponse(this.conn.ToString(), this.conn.CurrentRequest, out resendRequest, out proposedConnectionState, ref this._keepAlive, this.conn.Context, this.conn.CurrentRequest.Context);
+ }
+ catch (TimeoutException e)
+ {
+ this.conn.CurrentRequest.Response = null;
+
+ // Do nothing here if Abort() got called on the request, its State is already set.
+ if (!this.conn.CurrentRequest.IsTimedOut)
+ {
+ // We will try again only once
+ if (this.conn.CurrentRequest.Retries < this.conn.CurrentRequest.MaxRetries)
+ {
+ this.conn.CurrentRequest.Retries++;
+ resendRequest = true;
+ }
+ else
+ {
+ this.conn.CurrentRequest.Exception = e;
+ this.conn.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
+ }
+ }
+
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ catch (Exception e)
+ {
+ if (this.ShutdownType == ShutdownTypes.Immediate)
+ return;
+
+ string exceptionMessage = string.Empty;
+ if (e == null)
+ exceptionMessage = "null";
+ else
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+
+ Exception exception = e;
+ int counter = 1;
+ while (exception != null)
+ {
+ sb.AppendFormat("{0}: {1} {2}", counter++.ToString(), exception.Message, exception.StackTrace);
+
+ exception = exception.InnerException;
+
+ if (exception != null)
+ sb.AppendLine();
+ }
+
+ exceptionMessage = sb.ToString();
+ }
+ HTTPManager.Logger.Verbose("HTTP1Handler", exceptionMessage, this.Context, this.conn.CurrentRequest.Context);
+
+#if !BESTHTTP_DISABLE_CACHING
+ if (this.conn.CurrentRequest.UseStreaming)
+ HTTPCacheService.DeleteEntity(this.conn.CurrentRequest.CurrentUri);
+#endif
+
+ // Something gone bad, Response must be null!
+ this.conn.CurrentRequest.Response = null;
+
+ // Do nothing here if Abort() got called on the request, its State is already set.
+ if (!this.conn.CurrentRequest.IsCancellationRequested)
+ {
+ this.conn.CurrentRequest.Exception = e;
+ this.conn.CurrentRequest.State = HTTPRequestStates.Error;
+ }
+
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ finally
+ {
+ this.conn.CurrentRequest.OnCancellationRequested -= OnCancellationRequested;
+
+ // Exit ASAP
+ if (this.ShutdownType != ShutdownTypes.Immediate)
+ {
+ if (this.conn.CurrentRequest.IsCancellationRequested)
+ {
+ // we don't know what stage the request is canceled, we can't safely reuse the tcp channel.
+ proposedConnectionState = HTTPConnectionStates.Closed;
+
+ this.conn.CurrentRequest.Response = null;
+
+ // The request's State already set, or going to be set soon in RequestEvents.cs.
+ //this.conn.CurrentRequest.State = this.conn.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+ }
+ else if (resendRequest)
+ {
+ // Here introducing a ClosedResendRequest connection state, where we have to process the connection's state change to Closed
+ // than we have to resend the request.
+ // If we would send the Resend request here, than a few lines below the Closed connection state change,
+ // request events are processed before connection events (just switching the EnqueueRequestEvent and EnqueueConnectionEvent wouldn't work
+ // see order of ProcessQueues in HTTPManager.OnUpdate!) and it would pick this very same closing/closed connection!
+
+ if (proposedConnectionState == HTTPConnectionStates.Closed)
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, this.conn.CurrentRequest));
+ else
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.conn.CurrentRequest, RequestEvents.Resend));
+ }
+ else if (this.conn.CurrentRequest.Response != null && this.conn.CurrentRequest.Response.IsUpgraded)
+ {
+ proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown;
+ }
+ else if (this.conn.CurrentRequest.State == HTTPRequestStates.Processing)
+ {
+ if (this.conn.CurrentRequest.Response != null)
+ this.conn.CurrentRequest.State = HTTPRequestStates.Finished;
+ else
+ {
+ this.conn.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}",
+ this.ToString(),
+ this.conn.CurrentRequest.State.ToString(),
+ this.conn.State.ToString()));
+ this.conn.CurrentRequest.State = HTTPRequestStates.Error;
+
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ }
+
+ this.conn.CurrentRequest = null;
+
+ if (proposedConnectionState == HTTPConnectionStates.Processing)
+ proposedConnectionState = HTTPConnectionStates.Recycle;
+
+ if (proposedConnectionState != HTTPConnectionStates.ClosedResendRequest)
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, proposedConnectionState));
+ }
+ }
+ }
+
+ private void OnCancellationRequested(HTTPRequest obj)
+ {
+ if (this.conn != null && this.conn.connector != null)
+ this.conn.connector.Dispose();
+ }
+
+ private bool Receive(HTTPRequest request)
+ {
+ SupportedProtocols protocol = request.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri) : request.ProtocolHandler;
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTPConnection", string.Format("[{0}] - Receive - protocol: {1}", this.ToString(), protocol.ToString()), this.Context, request.Context);
+
+ request.Response = HTTPProtocolFactory.Get(protocol, request, this.conn.connector.Stream, request.UseStreaming, false);
+
+ if (!request.Response.Receive())
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Failed! Response will be null, returning with false.", this.ToString()), this.Context, request.Context);
+ request.Response = null;
+ return false;
+ }
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("HTTP1Handler", string.Format("[{0}] - Receive - Finished Successfully!", this.ToString()), this.Context, request.Context);
+
+ return true;
+ }
+
+ public ShutdownTypes ShutdownType { get; private set; }
+
+ public void Shutdown(ShutdownTypes type)
+ {
+ this.ShutdownType = type;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ }
+
+ ~HTTP1Handler()
+ {
+ Dispose(false);
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..0c788e1c5903a3bc203915db1c891b66e9884912
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP1Handler.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 12ead80cbad179b49a5026a8b6c04348
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3df6388220c60ca200d4dac1b32c67036c88cade
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2edfd4a45eb2f664da87da040d6fcb81
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f2a4bda00e825b82436a7b1e7aeb73cf1a25cf35
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs
@@ -0,0 +1,140 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using System;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ public static class BufferHelper
+ {
+ public static void SetUInt16(byte[] buffer, int offset, UInt16 value)
+ {
+ buffer[offset + 1] = (byte)(value);
+ buffer[offset + 0] = (byte)(value >> 8);
+ }
+
+ public static void SetUInt24(byte[] buffer, int offset, UInt32 value)
+ {
+ buffer[offset + 2] = (byte)(value);
+ buffer[offset + 1] = (byte)(value >> 8);
+ buffer[offset + 0] = (byte)(value >> 16);
+ }
+
+ public static void SetUInt31(byte[] buffer, int offset, UInt32 value)
+ {
+ buffer[offset + 3] = (byte)(value);
+ buffer[offset + 2] = (byte)(value >> 8);
+ buffer[offset + 1] = (byte)(value >> 16);
+ buffer[offset + 0] = (byte)((value >> 24) & 0x7F);
+ }
+
+ public static void SetUInt32(byte[] buffer, int offset, UInt32 value)
+ {
+ buffer[offset + 3] = (byte)(value);
+ buffer[offset + 2] = (byte)(value >> 8);
+ buffer[offset + 1] = (byte)(value >> 16);
+ buffer[offset + 0] = (byte)(value >> 24);
+ }
+
+ public static void SetLong(byte[] buffer, int offset, long value)
+ {
+ buffer[offset + 7] = (byte)(value);
+ buffer[offset + 6] = (byte)(value >> 8);
+ buffer[offset + 5] = (byte)(value >> 16);
+ buffer[offset + 4] = (byte)(value >> 24);
+ buffer[offset + 3] = (byte)(value >> 32);
+ buffer[offset + 2] = (byte)(value >> 40);
+ buffer[offset + 1] = (byte)(value >> 48);
+ buffer[offset + 0] = (byte)(value >> 56);
+ }
+
+ ///
+ /// bitIdx: 01234567
+ ///
+ public static byte SetBit(byte value, byte bitIdx, bool bitValue)
+ {
+ return SetBit(value, bitIdx, Convert.ToByte(bitValue));
+ }
+
+ ///
+ /// bitIdx: 01234567
+ ///
+ public static byte SetBit(byte value, byte bitIdx, byte bitValue)
+ {
+ //byte mask = (byte)(0x80 >> bitIdx);
+
+ return (byte)((value ^ (value & (0x80 >> bitIdx))) | bitValue << (7 - bitIdx));
+ }
+
+ ///
+ /// bitIdx: 01234567
+ ///
+ public static byte ReadBit(byte value, byte bitIdx)
+ {
+ byte mask = (byte)(0x80 >> bitIdx);
+
+ return (byte)((value & mask) >> (7 - bitIdx));
+ }
+
+ ///
+ /// bitIdx: 01234567
+ ///
+ public static byte ReadValue(byte value, byte fromBit, byte toBit)
+ {
+ byte result = 0;
+ short idx = toBit;
+
+ while (idx >= fromBit)
+ {
+ result += (byte)(ReadBit(value, (byte)idx) << (toBit - idx));
+ idx--;
+ }
+
+ return result;
+ }
+
+ public static UInt16 ReadUInt16(byte[] buffer, int offset)
+ {
+ return (UInt16)(buffer[offset + 1] | buffer[offset] << 8);
+ }
+
+ public static UInt32 ReadUInt24(byte[] buffer, int offset)
+ {
+ return (UInt32)(buffer[offset + 2] |
+ buffer[offset + 1] << 8 |
+ buffer[offset + 0] << 16
+ );
+ }
+
+ public static UInt32 ReadUInt31(byte[] buffer, int offset)
+ {
+ return (UInt32)(buffer[offset + 3] |
+ buffer[offset + 2] << 8 |
+ buffer[offset + 1] << 16 |
+ (buffer[offset] & 0x7F) << 24
+ );
+ }
+
+ public static UInt32 ReadUInt32(byte[] buffer, int offset)
+ {
+ return (UInt32)(buffer[offset + 3] |
+ buffer[offset + 2] << 8 |
+ buffer[offset + 1] << 16 |
+ buffer[offset + 0] << 24
+ );
+ }
+
+ public static long ReadLong(byte[] buffer, int offset)
+ {
+ return (long)buffer[offset + 7] |
+ (long)buffer[offset + 6] << 8 |
+ (long)buffer[offset + 5] << 16 |
+ (long)buffer[offset + 4] << 24 |
+ (long)buffer[offset + 3] << 32 |
+ (long)buffer[offset + 2] << 40 |
+ (long)buffer[offset + 1] << 48 |
+ (long)buffer[offset + 0] << 56;
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..8807b03eb7d11bf7444d2a89acacd292d68e4e0f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/BufferHelper.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 53d7db68937661d4eb9c0fe989b9f46c
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ff3de5811495df3f8e484118bae29532bc480758
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs
@@ -0,0 +1,233 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ interface IFrameDataView : IDisposable
+ {
+ long Length { get; }
+ long Position { get; }
+
+ void AddFrame(HTTP2FrameHeaderAndPayload frame);
+ int ReadByte();
+ int Read(byte[] buffer, int offset, int count);
+ }
+
+ abstract class CommonFrameView : IFrameDataView
+ {
+ public long Length { get; protected set; }
+ public long Position { get; protected set; }
+
+ protected List frames = new List();
+ protected int currentFrameIdx = -1;
+ protected byte[] data;
+ protected UInt32 dataOffset;
+ protected UInt32 maxOffset;
+
+ public abstract void AddFrame(HTTP2FrameHeaderAndPayload frame);
+ protected abstract long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame);
+
+ public virtual int Read(byte[] buffer, int offset, int count)
+ {
+ if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
+ return -1;
+
+ int readCount = 0;
+
+ while (count > 0)
+ {
+ long copyCount = Math.Min(count, this.maxOffset - this.dataOffset);
+
+ Array.Copy(this.data, this.dataOffset, buffer, offset + readCount, copyCount);
+
+ count -= (int)copyCount;
+ readCount += (int)copyCount;
+
+ this.dataOffset += (UInt32)copyCount;
+ this.Position += copyCount;
+
+ if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
+ break;
+ }
+
+ return readCount;
+ }
+
+ public virtual int ReadByte()
+ {
+ if (this.dataOffset >= this.maxOffset && !AdvanceFrame())
+ return -1;
+
+ byte data = this.data[this.dataOffset];
+ this.dataOffset++;
+ this.Position++;
+
+ return data;
+ }
+
+ protected abstract bool AdvanceFrame();
+
+ public virtual void Dispose()
+ {
+ for (int i = 0; i < this.frames.Count; ++i)
+ if (this.frames[i].Payload != null && !this.frames[i].DontUseMemPool)
+ BufferPool.Release(this.frames[i].Payload);
+ this.frames.Clear();
+ }
+
+ public override string ToString()
+ {
+ var sb = new System.Text.StringBuilder("[CommonFrameView ");
+
+ for (int i = 0; i < this.frames.Count; ++i) {
+ sb.AppendFormat("{0} Payload: {1}\n", this.frames[i], this.frames[i].PayloadAsHex());
+ }
+
+ sb.Append("]");
+
+ return sb.ToString();
+ }
+ }
+
+ sealed class HeaderFrameView : CommonFrameView
+ {
+ public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
+ {
+ if (frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
+ throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
+
+ this.frames.Add(frame);
+ this.Length += CalculateDataLengthForFrame(frame);
+
+ if (this.currentFrameIdx == -1)
+ AdvanceFrame();
+ }
+
+ protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
+ {
+ switch (frame.Type)
+ {
+ case HTTP2FrameTypes.HEADERS:
+ return HTTP2FrameHelper.ReadHeadersFrame(frame).HeaderBlockFragmentLength;
+
+ case HTTP2FrameTypes.CONTINUATION:
+ return frame.PayloadLength;
+ }
+
+ return 0;
+ }
+
+ protected override bool AdvanceFrame()
+ {
+ if (this.currentFrameIdx >= this.frames.Count - 1)
+ return false;
+
+ this.currentFrameIdx++;
+ HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
+
+ this.data = frame.Payload;
+
+ switch (frame.Type)
+ {
+ case HTTP2FrameTypes.HEADERS:
+ var header = HTTP2FrameHelper.ReadHeadersFrame(frame);
+ this.dataOffset = header.HeaderBlockFragmentIdx;
+ this.maxOffset = this.dataOffset + header.HeaderBlockFragmentLength;
+ break;
+
+ case HTTP2FrameTypes.CONTINUATION:
+ this.dataOffset = 0;
+ this.maxOffset = frame.PayloadLength;
+ break;
+ }
+
+ return true;
+ }
+ }
+
+ sealed class DataFrameView : CommonFrameView
+ {
+ public override void AddFrame(HTTP2FrameHeaderAndPayload frame)
+ {
+ if (frame.Type != HTTP2FrameTypes.DATA)
+ throw new ArgumentException("HeaderFrameView - Unexpected frame type: " + frame.Type);
+
+ this.frames.Add(frame);
+ this.Length += CalculateDataLengthForFrame(frame);
+ }
+
+ protected override long CalculateDataLengthForFrame(HTTP2FrameHeaderAndPayload frame)
+ {
+ return HTTP2FrameHelper.ReadDataFrame(frame).DataLength;
+ }
+
+ protected override bool AdvanceFrame()
+ {
+ if (this.currentFrameIdx >= this.frames.Count - 1)
+ return false;
+
+ this.currentFrameIdx++;
+ HTTP2FrameHeaderAndPayload frame = this.frames[this.currentFrameIdx];
+ HTTP2DataFrame dataFrame = HTTP2FrameHelper.ReadDataFrame(frame);
+
+ this.data = frame.Payload;
+ this.dataOffset = dataFrame.DataIdx;
+ this.maxOffset = dataFrame.DataIdx + dataFrame.DataLength;
+
+ return true;
+ }
+ }
+
+ sealed class FramesAsStreamView : Stream
+ {
+ public override bool CanRead { get { return true; } }
+ public override bool CanSeek { get { return false; } }
+ public override bool CanWrite { get { return false; } }
+ public override long Length { get { return this.view.Length; } }
+ public override long Position { get { return this.view.Position; } set { throw new NotSupportedException(); } }
+
+ private IFrameDataView view;
+
+ public FramesAsStreamView(IFrameDataView view)
+ {
+ this.view = view;
+ }
+
+ public void AddFrame(HTTP2FrameHeaderAndPayload frame)
+ {
+ this.view.AddFrame(frame);
+ }
+
+ public override int ReadByte()
+ {
+ return this.view.ReadByte();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return this.view.Read(buffer, offset, count);
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ this.view.Dispose();
+ }
+
+ public override void Flush() {}
+ public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
+ public override void SetLength(long value) { throw new NotImplementedException(); }
+ public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); }
+
+ public override string ToString()
+ {
+ return this.view.ToString();
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..9d87c8e73070429f5fb703cfc89ca7b84ad3dcf4
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/FramesAsStreamView.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 591ec412e5c6f2a42b591c9c02921d33
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..65e8dd7fb2d9cdd3879bef6d29b14be0a88456ee
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs
@@ -0,0 +1,794 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ public sealed class HPACKEncoder
+ {
+ private HTTP2SettingsManager settingsRegistry;
+
+ // https://http2.github.io/http2-spec/compression.html#encoding.context
+ // When used for bidirectional communication, such as in HTTP, the encoding and decoding dynamic tables
+ // maintained by an endpoint are completely independent, i.e., the request and response dynamic tables are separate.
+ private HeaderTable requestTable;
+ private HeaderTable responseTable;
+
+ private HTTP2Handler parent;
+
+ public HPACKEncoder(HTTP2Handler parentHandler, HTTP2SettingsManager registry)
+ {
+ this.parent = parentHandler;
+ this.settingsRegistry = registry;
+
+ // I'm unsure what settings (local or remote) we should use for these two tables!
+ this.requestTable = new HeaderTable(this.settingsRegistry.MySettings);
+ this.responseTable = new HeaderTable(this.settingsRegistry.RemoteSettings);
+ }
+
+ public void Encode(HTTP2Stream context, HTTPRequest request, Queue to, UInt32 streamId)
+ {
+ // Add usage of SETTINGS_MAX_HEADER_LIST_SIZE to be able to create a header and one or more continuation fragments
+ // (https://httpwg.org/specs/rfc7540.html#SettingValues)
+
+ using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream())
+ {
+ WriteHeader(bufferStream, ":method", HTTPRequest.MethodNames[(int)request.MethodType]);
+ // add path
+ WriteHeader(bufferStream, ":path", request.CurrentUri.PathAndQuery);
+ // add authority
+ WriteHeader(bufferStream, ":authority", request.CurrentUri.Authority);
+ // add scheme
+ WriteHeader(bufferStream, ":scheme", "https");
+
+ //bool hasBody = false;
+
+ // add other, regular headers
+ request.EnumerateHeaders((header, values) =>
+ {
+ if (header.Equals("connection", StringComparison.OrdinalIgnoreCase) ||
+ header.Equals("te", StringComparison.OrdinalIgnoreCase) ||
+ header.Equals("host", StringComparison.OrdinalIgnoreCase) ||
+ header.Equals("keep-alive", StringComparison.OrdinalIgnoreCase))
+ return;
+
+ //if (!hasBody)
+ // hasBody = header.Equals("content-length", StringComparison.OrdinalIgnoreCase) && int.Parse(values[0]) > 0;
+
+ // https://httpwg.org/specs/rfc7540.html#HttpSequence
+ // The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
+ if (header.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
+ {
+ // error!
+ return;
+ }
+
+ // https://httpwg.org/specs/rfc7540.html#HttpHeaders
+ // Just as in HTTP/1.x, header field names are strings of ASCII characters that are compared in a case-insensitive fashion.
+ // However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2.
+ // A request or response containing uppercase header field names MUST be treated as malformed
+ if (header.Any(Char.IsUpper))
+ header = header.ToLower();
+
+ for (int i = 0; i < values.Count; ++i)
+ {
+ WriteHeader(bufferStream, header, values[i]);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] - Encode - Header({1}/{2}): '{3}': '{4}'", context.Id, i + 1, values.Count, header, values[i]), this.parent.Context, context.Context, request.Context);
+ }
+ }, true);
+
+ var upStreamInfo = request.GetUpStream();
+ CreateHeaderFrames(to,
+ streamId,
+ bufferStream.ToArray(true),
+ (UInt32)bufferStream.Length,
+ upStreamInfo.Stream != null);
+ }
+ }
+
+ public void Decode(HTTP2Stream context, Stream stream, List> to)
+ {
+ int headerType = stream.ReadByte();
+ while (headerType != -1)
+ {
+ byte firstDataByte = (byte)headerType;
+
+ // https://http2.github.io/http2-spec/compression.html#indexed.header.representation
+ if (BufferHelper.ReadBit(firstDataByte, 0) == 1)
+ {
+ var header = ReadIndexedHeader(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - IndexedHeader: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ to.Add(header);
+ }
+ else if (BufferHelper.ReadValue(firstDataByte, 0, 1) == 1)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
+
+ if (BufferHelper.ReadValue(firstDataByte, 2, 7) == 0)
+ {
+ // Literal Header Field with Incremental Indexing — New Name
+ var header = ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ this.responseTable.Add(header);
+ to.Add(header);
+ }
+ else
+ {
+ // Literal Header Field with Incremental Indexing — Indexed Name
+ var header = ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldWithIncrementalIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ this.responseTable.Add(header);
+ to.Add(header);
+ }
+ } else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 0)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
+
+ if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
+ {
+ // Literal Header Field without Indexing — New Name
+ var header = ReadLiteralHeaderFieldwithoutIndexing_NewName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ to.Add(header);
+ }
+ else
+ {
+ // Literal Header Field without Indexing — Indexed Name
+ var header = ReadLiteralHeaderFieldwithoutIndexing_IndexedName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldwithoutIndexing_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ to.Add(header);
+ }
+ }
+ else if (BufferHelper.ReadValue(firstDataByte, 0, 3) == 1)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
+
+ if (BufferHelper.ReadValue(firstDataByte, 4, 7) == 0)
+ {
+ // Literal Header Field Never Indexed — New Name
+ var header = ReadLiteralHeaderFieldNeverIndexed_NewName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_NewName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ to.Add(header);
+ }
+ else
+ {
+ // Literal Header Field Never Indexed — Indexed Name
+ var header = ReadLiteralHeaderFieldNeverIndexed_IndexedName(firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - LiteralHeaderFieldNeverIndexed_IndexedName: {1}", context.Id, header.ToString()), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ to.Add(header);
+ }
+ }
+ else if (BufferHelper.ReadValue(firstDataByte, 0, 2) == 1)
+ {
+ // https://http2.github.io/http2-spec/compression.html#encoding.context.update
+
+ UInt32 newMaxSize = DecodeInteger(5, firstDataByte, stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HPACKEncoder", string.Format("[{0}] Decode - Dynamic Table Size Update: {1}", context.Id, newMaxSize), this.parent.Context, context.Context, context.AssignedRequest.Context);
+
+ //this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE] = (UInt16)newMaxSize;
+ this.responseTable.MaxDynamicTableSize = (UInt16)newMaxSize;
+ }
+ else
+ {
+ // ERROR
+ }
+
+ headerType = stream.ReadByte();
+ }
+ }
+
+ private KeyValuePair ReadIndexedHeader(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#indexed.header.representation
+
+ UInt32 index = DecodeInteger(7, firstByte, stream);
+ return this.responseTable.GetHeader(index);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldWithIncrementalIndexing_IndexedName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
+
+ UInt32 keyIndex = DecodeInteger(6, firstByte, stream);
+
+ string header = this.responseTable.GetKey(keyIndex);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldWithIncrementalIndexing_NewName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
+
+ string header = DecodeString(stream);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldwithoutIndexing_IndexedName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
+
+ UInt32 index = DecodeInteger(4, firstByte, stream);
+ string header = this.responseTable.GetKey(index);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldwithoutIndexing_NewName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
+
+ string header = DecodeString(stream);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldNeverIndexed_IndexedName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
+
+ UInt32 index = DecodeInteger(4, firstByte, stream);
+ string header = this.responseTable.GetKey(index);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private KeyValuePair ReadLiteralHeaderFieldNeverIndexed_NewName(byte firstByte, Stream stream)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
+
+ string header = DecodeString(stream);
+ string value = DecodeString(stream);
+
+ return new KeyValuePair(header, value);
+ }
+
+ private string DecodeString(Stream stream)
+ {
+ byte start = (byte)stream.ReadByte();
+ bool rawString = BufferHelper.ReadBit(start, 0) == 0;
+ UInt32 stringLength = DecodeInteger(7, start, stream);
+
+ if (rawString)
+ {
+ byte[] buffer = BufferPool.Get(stringLength, true);
+
+ stream.Read(buffer, 0, (int)stringLength);
+
+ BufferPool.Release(buffer);
+
+ return System.Text.Encoding.UTF8.GetString(buffer, 0, (int)stringLength);
+ }
+ else
+ {
+ var node = HuffmanEncoder.GetRoot();
+ byte currentByte = (byte)stream.ReadByte();
+ byte bitIdx = 0; // 0..7
+
+ using (BufferPoolMemoryStream bufferStream = new BufferPoolMemoryStream())
+ {
+ do
+ {
+ byte bitValue = BufferHelper.ReadBit(currentByte, bitIdx);
+
+ if (++bitIdx > 7)
+ {
+ stringLength--;
+
+ if (stringLength > 0)
+ {
+ bitIdx = 0;
+ currentByte = (byte)stream.ReadByte();
+ }
+ }
+
+ node = HuffmanEncoder.GetNext(node, bitValue);
+
+ if (node.Value != 0)
+ {
+ if (node.Value != HuffmanEncoder.EOS)
+ bufferStream.WriteByte((byte)node.Value);
+
+ node = HuffmanEncoder.GetRoot();
+ }
+ } while (stringLength > 0);
+
+ byte[] buffer = bufferStream.ToArray(true);
+
+ string result = System.Text.Encoding.UTF8.GetString(buffer, 0, (int)bufferStream.Length);
+
+ BufferPool.Release(buffer);
+
+ return result;
+ }
+ }
+ }
+
+ private void CreateHeaderFrames(Queue to, UInt32 streamId, byte[] dataToSend, UInt32 payloadLength, bool hasBody)
+ {
+ UInt32 maxFrameSize = this.settingsRegistry.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE];
+
+ // Only one headers frame
+ if (payloadLength <= maxFrameSize)
+ {
+ HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
+ frameHeader.Type = HTTP2FrameTypes.HEADERS;
+ frameHeader.StreamId = streamId;
+ frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_HEADERS);
+
+ if (!hasBody)
+ frameHeader.Flags |= (byte)(HTTP2HeadersFlags.END_STREAM);
+
+ frameHeader.PayloadLength = payloadLength;
+ frameHeader.Payload = dataToSend;
+
+ to.Enqueue(frameHeader);
+ }
+ else
+ {
+ HTTP2FrameHeaderAndPayload frameHeader = new HTTP2FrameHeaderAndPayload();
+ frameHeader.Type = HTTP2FrameTypes.HEADERS;
+ frameHeader.StreamId = streamId;
+ frameHeader.PayloadLength = maxFrameSize;
+ frameHeader.Payload = dataToSend;
+ frameHeader.DontUseMemPool = true;
+ frameHeader.PayloadOffset = 0;
+
+ if (!hasBody)
+ frameHeader.Flags = (byte)(HTTP2HeadersFlags.END_STREAM);
+
+ to.Enqueue(frameHeader);
+
+ UInt32 offset = maxFrameSize;
+ while (offset < payloadLength)
+ {
+ frameHeader = new HTTP2FrameHeaderAndPayload();
+ frameHeader.Type = HTTP2FrameTypes.CONTINUATION;
+ frameHeader.StreamId = streamId;
+ frameHeader.PayloadLength = maxFrameSize;
+ frameHeader.Payload = dataToSend;
+ frameHeader.PayloadOffset = offset;
+
+ offset += maxFrameSize;
+
+ if (offset >= payloadLength)
+ {
+ frameHeader.Flags = (byte)(HTTP2ContinuationFlags.END_HEADERS);
+ // last sent continuation fragment will release back the payload buffer
+ frameHeader.DontUseMemPool = false;
+ }
+ else
+ frameHeader.DontUseMemPool = true;
+
+ to.Enqueue(frameHeader);
+ }
+ }
+ }
+
+ private void WriteHeader(Stream stream, string header, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#header.representation
+
+ KeyValuePair index = this.requestTable.GetIndex(header, value);
+
+ if (index.Key == 0 && index.Value == 0)
+ {
+ WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(stream, header, value);
+ this.requestTable.Add(new KeyValuePair(header, value));
+ }
+ else if (index.Key != 0 && index.Value == 0)
+ {
+ WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(stream, index.Key, value);
+ this.requestTable.Add(new KeyValuePair(header, value));
+ }
+ else
+ {
+ WriteIndexedHeaderField(stream, index.Key);
+ }
+ }
+
+ private static void WriteIndexedHeaderField(Stream stream, UInt32 index)
+ {
+ byte requiredBytes = RequiredBytesToEncodeInteger(index, 7);
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[0] = 0x80;
+ EncodeInteger(index, 7, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldWithIncrementalIndexing_IndexedName(Stream stream, UInt32 index, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
+
+ UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 6) +
+ RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[0] = 0x40;
+ EncodeInteger(index, 6, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldWithIncrementalIndexing_NewName(Stream stream, string header, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.with.incremental.indexing
+
+ UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[offset++] = 0x40;
+ EncodeString(header, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldWithoutIndexing_IndexedName(Stream stream, UInt32 index, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
+
+ UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[0] = 0;
+ EncodeInteger(index, 4, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldWithoutIndexing_NewName(Stream stream, string header, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.without.indexing
+
+ UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[offset++] = 0;
+ EncodeString(header, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldNeverIndexed_IndexedName(Stream stream, UInt32 index, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
+
+ UInt32 requiredBytes = RequiredBytesToEncodeInteger(index, 4) + RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[0] = 0x10;
+ EncodeInteger(index, 4, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteLiteralHeaderFieldNeverIndexed_NewName(Stream stream, string header, string value)
+ {
+ // https://http2.github.io/http2-spec/compression.html#literal.header.never.indexed
+
+ UInt32 requiredBytes = 1 + RequiredBytesToEncodeString(header) + RequiredBytesToEncodeString(value);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[offset++] = 0x10;
+ EncodeString(header, buffer, ref offset);
+ EncodeString(value, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static void WriteDynamicTableSizeUpdate(Stream stream, UInt16 maxSize)
+ {
+ // https://http2.github.io/http2-spec/compression.html#encoding.context.update
+
+ UInt32 requiredBytes = RequiredBytesToEncodeInteger(maxSize, 5);
+
+ byte[] buffer = BufferPool.Get(requiredBytes, true);
+ UInt32 offset = 0;
+
+ buffer[offset] = 0x20;
+ EncodeInteger(maxSize, 5, buffer, ref offset);
+
+ stream.Write(buffer, 0, (int)offset);
+
+ BufferPool.Release(buffer);
+ }
+
+ private static UInt32 RequiredBytesToEncodeString(string str)
+ {
+ uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
+ uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
+ requiredBytesForHuffman += RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7);
+
+ return Math.Min(requiredBytesForRawStr, requiredBytesForHuffman);
+ }
+
+ private static void EncodeString(string str, byte[] buffer, ref UInt32 offset)
+ {
+ uint requiredBytesForRawStr = RequiredBytesToEncodeRawString(str);
+ uint requiredBytesForHuffman = RequiredBytesToEncodeStringWithHuffman(str);
+
+ // if using huffman encoding would produce the same length, we choose raw encoding instead as it requires
+ // less CPU cicles
+ if (requiredBytesForRawStr <= requiredBytesForHuffman + RequiredBytesToEncodeInteger(requiredBytesForHuffman, 7))
+ EncodeRawStringTo(str, buffer, ref offset);
+ else
+ EncodeStringWithHuffman(str, requiredBytesForHuffman, buffer, ref offset);
+ }
+
+ // This calculates only the length of the compressed string,
+ // additional header length must be calculated using the value returned by this function
+ private static UInt32 RequiredBytesToEncodeStringWithHuffman(string str)
+ {
+ int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
+ byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
+
+ System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
+
+ UInt32 requiredBits = 0;
+
+ for (int i = 0; i < requiredBytesForStr; ++i)
+ requiredBits += HuffmanEncoder.GetEntryForCodePoint(strBytes[i]).Bits;
+
+ BufferPool.Release(strBytes);
+
+ return (UInt32)((requiredBits / 8) + ((requiredBits % 8) == 0 ? 0 : 1));
+ }
+
+ private static void EncodeStringWithHuffman(string str, UInt32 encodedLength, byte[] buffer, ref UInt32 offset)
+ {
+ int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
+ byte[] strBytes = BufferPool.Get(requiredBytesForStr, true);
+
+ System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, strBytes, 0);
+
+ // 0. bit: huffman flag
+ buffer[offset] = 0x80;
+
+ // 1..7+ bit: length
+ EncodeInteger(encodedLength, 7, buffer, ref offset);
+
+ byte bufferBitIdx = 0;
+
+ for (int i = 0; i < requiredBytesForStr; ++i)
+ AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(strBytes[i]), buffer, ref offset, ref bufferBitIdx);
+
+ // https://http2.github.io/http2-spec/compression.html#string.literal.representation
+ // As the Huffman-encoded data doesn't always end at an octet boundary, some padding is inserted after it,
+ // up to the next octet boundary. To prevent this padding from being misinterpreted as part of the string literal,
+ // the most significant bits of the code corresponding to the EOS (end-of-string) symbol are used.
+ if (bufferBitIdx != 0)
+ AddCodePointToBuffer(HuffmanEncoder.GetEntryForCodePoint(256), buffer, ref offset, ref bufferBitIdx, true);
+
+ BufferPool.Release(strBytes);
+ }
+
+ private static void AddCodePointToBuffer(HuffmanEncoder.TableEntry code, byte[] buffer, ref UInt32 offset, ref byte bufferBitIdx, bool finishOnBoundary = false)
+ {
+ for (byte codeBitIdx = 1; codeBitIdx <= code.Bits; ++codeBitIdx)
+ {
+ byte bit = code.GetBitAtIdx(codeBitIdx);
+ buffer[offset] = BufferHelper.SetBit(buffer[offset], bufferBitIdx, bit);
+
+ // octet boundary reached, proceed to the next octet
+ if (++bufferBitIdx == 8)
+ {
+ if (++offset < buffer.Length)
+ buffer[offset] = 0;
+
+ if (finishOnBoundary)
+ return;
+
+ bufferBitIdx = 0;
+ }
+ }
+ }
+
+ private static UInt32 RequiredBytesToEncodeRawString(string str)
+ {
+ int requiredBytesForStr = System.Text.Encoding.UTF8.GetByteCount(str);
+ int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
+
+ return (UInt32)(requiredBytesForStr + requiredBytesForLengthPrefix);
+ }
+
+ // This method encodes a string without huffman encoding
+ private static void EncodeRawStringTo(string str, byte[] buffer, ref UInt32 offset)
+ {
+ uint requiredBytesForStr = (uint)System.Text.Encoding.UTF8.GetByteCount(str);
+ int requiredBytesForLengthPrefix = RequiredBytesToEncodeInteger((UInt32)requiredBytesForStr, 7);
+
+ UInt32 originalOffset = offset;
+ buffer[offset] = 0;
+ EncodeInteger(requiredBytesForStr, 7, buffer, ref offset);
+
+ // Zero out the huffman flag
+ buffer[originalOffset] = BufferHelper.SetBit(buffer[originalOffset], 0, false);
+
+ if (offset != originalOffset + requiredBytesForLengthPrefix)
+ throw new Exception(string.Format("offset({0}) != originalOffset({1}) + requiredBytesForLengthPrefix({1})", offset, originalOffset, requiredBytesForLengthPrefix));
+
+ System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, (int)offset);
+ offset += requiredBytesForStr;
+ }
+
+ private static byte RequiredBytesToEncodeInteger(UInt32 value, byte N)
+ {
+ UInt32 maxValue = (1u << N) - 1;
+ byte count = 0;
+
+ // If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
+ if (value < maxValue)
+ {
+ count++;
+ }
+ else
+ {
+ // Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
+ count++;
+ value -= maxValue;
+
+ while (value >= 0x80)
+ {
+ // The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
+ count++;
+ value = value / 0x80;
+ }
+
+ count++;
+ }
+
+ return count;
+ }
+
+ // https://http2.github.io/http2-spec/compression.html#integer.representation
+ private static void EncodeInteger(UInt32 value, byte N, byte[] buffer, ref UInt32 offset)
+ {
+ // 2^N - 1
+ UInt32 maxValue = (1u << N) - 1;
+
+ // If the integer value is small enough, i.e., strictly less than 2^N-1, it is encoded within the N-bit prefix.
+ if (value < maxValue)
+ {
+ buffer[offset++] |= (byte)value;
+ }
+ else
+ {
+ // Otherwise, all the bits of the prefix are set to 1, and the value, decreased by 2^N-1
+ buffer[offset++] |= (byte)(0xFF >> (8 - N));
+ value -= maxValue;
+
+ while (value >= 0x80)
+ {
+ // The most significant bit of each octet is used as a continuation flag: its value is set to 1 except for the last octet in the list.
+ buffer[offset++] = (byte)(0x80 | (0x7F & value));
+ value = value / 0x80;
+ }
+
+ buffer[offset++] = (byte)value;
+ }
+ }
+
+ // https://http2.github.io/http2-spec/compression.html#integer.representation
+ private static UInt32 DecodeInteger(byte N, byte[] buffer, ref UInt32 offset)
+ {
+ // The starting value is the value behind the mask of the N bits
+ UInt32 value = (UInt32)(buffer[offset++] & (byte)(0xFF >> (8 - N)));
+
+ // All N bits are 1s ? If so, we have at least one another byte to decode
+ if (value == (1u << N) - 1)
+ {
+ byte shift = 0;
+
+ do
+ {
+ // The most significant bit is a continuation flag, so we have to mask it out
+ value += (UInt32)((buffer[offset] & 0x7F) << shift);
+ shift += 7;
+ } while ((buffer[offset++] & 0x80) == 0x80);
+ }
+
+ return value;
+ }
+
+ // https://http2.github.io/http2-spec/compression.html#integer.representation
+ private static UInt32 DecodeInteger(byte N, byte data, Stream stream)
+ {
+ // The starting value is the value behind the mask of the N bits
+ UInt32 value = (UInt32)(data & (byte)(0xFF >> (8 - N)));
+
+ // All N bits are 1s ? If so, we have at least one another byte to decode
+ if (value == (1u << N) - 1)
+ {
+ byte shift = 0;
+
+ do
+ {
+ data = (byte)stream.ReadByte();
+
+ // The most significant bit is a continuation flag, so we have to mask it out
+ value += (UInt32)((data & 0x7F) << shift);
+ shift += 7;
+ } while ((data & 0x80) == 0x80);
+ }
+
+ return value;
+ }
+
+ public override string ToString()
+ {
+ return this.requestTable.ToString() + this.responseTable.ToString();
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..6f3591da0d2b881074b69041c458b8eaad442996
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HPACKEncoder.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 6b9891b070f01ec458dd74bd4e91cf75
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2578de7d700d53f88a47c6e48e73e9acc176769e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs
@@ -0,0 +1,385 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ // https://httpwg.org/specs/rfc7540.html#ErrorCodes
+ public enum HTTP2ErrorCodes
+ {
+ NO_ERROR = 0x00,
+ PROTOCOL_ERROR = 0x01,
+ INTERNAL_ERROR = 0x02,
+ FLOW_CONTROL_ERROR = 0x03,
+ SETTINGS_TIMEOUT = 0x04,
+ STREAM_CLOSED = 0x05,
+ FRAME_SIZE_ERROR = 0x06,
+ REFUSED_STREAM = 0x07,
+ CANCEL = 0x08,
+ COMPRESSION_ERROR = 0x09,
+ CONNECT_ERROR = 0x0A,
+ ENHANCE_YOUR_CALM = 0x0B,
+ INADEQUATE_SECURITY = 0x0C,
+ HTTP_1_1_REQUIRED = 0x0D
+ }
+
+ public static class HTTP2FrameHelper
+ {
+ public static HTTP2ContinuationFrame ReadContinuationFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#CONTINUATION
+
+ HTTP2ContinuationFrame frame = new HTTP2ContinuationFrame(header);
+
+ frame.HeaderBlockFragment = header.Payload;
+ header.Payload = null;
+
+ return frame;
+ }
+
+ public static HTTP2WindowUpdateFrame ReadWindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
+
+ HTTP2WindowUpdateFrame frame = new HTTP2WindowUpdateFrame(header);
+
+ frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0);
+ frame.WindowSizeIncrement = BufferHelper.ReadUInt31(header.Payload, 0);
+
+ return frame;
+ }
+
+ public static HTTP2GoAwayFrame ReadGoAwayFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#GOAWAY
+ // str id error
+ // | 0, 1, 2, 3 | 4, 5, 6, 7 | ...
+
+ HTTP2GoAwayFrame frame = new HTTP2GoAwayFrame(header);
+
+ frame.ReservedBit = BufferHelper.ReadBit(header.Payload[0], 0);
+ frame.LastStreamId = BufferHelper.ReadUInt31(header.Payload, 0);
+ frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 4);
+
+ frame.AdditionalDebugDataLength = header.PayloadLength - 8;
+ if (frame.AdditionalDebugDataLength > 0)
+ {
+ frame.AdditionalDebugData = BufferPool.Get(frame.AdditionalDebugDataLength, true);
+ Array.Copy(header.Payload, 8, frame.AdditionalDebugData, 0, frame.AdditionalDebugDataLength);
+ }
+
+ return frame;
+ }
+
+ public static HTTP2PingFrame ReadPingFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#PING
+
+ HTTP2PingFrame frame = new HTTP2PingFrame(header);
+
+ Array.Copy(header.Payload, 0, frame.OpaqueData, 0, frame.OpaqueDataLength);
+
+ return frame;
+ }
+
+ public static HTTP2PushPromiseFrame ReadPush_PromiseFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#PUSH_PROMISE
+
+ HTTP2PushPromiseFrame frame = new HTTP2PushPromiseFrame(header);
+ frame.HeaderBlockFragmentLength = header.PayloadLength - 4; // PromisedStreamId
+
+ bool isPadded = (frame.Flags & HTTP2PushPromiseFlags.PADDED) != 0;
+ if (isPadded)
+ {
+ frame.PadLength = header.Payload[0];
+ frame.HeaderBlockFragmentLength -= (uint)(1 + (frame.PadLength ?? 0));
+ }
+
+ frame.ReservedBit = BufferHelper.ReadBit(header.Payload[1], 0);
+ frame.PromisedStreamId = BufferHelper.ReadUInt31(header.Payload, 1);
+
+ frame.HeaderBlockFragmentIdx = (UInt32)(isPadded ? 5 : 4);
+ frame.HeaderBlockFragment = header.Payload;
+ header.Payload = null;
+
+ return frame;
+ }
+
+ public static HTTP2RSTStreamFrame ReadRST_StreamFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#RST_STREAM
+
+ HTTP2RSTStreamFrame frame = new HTTP2RSTStreamFrame(header);
+ frame.ErrorCode = BufferHelper.ReadUInt32(header.Payload, 0);
+
+ return frame;
+ }
+
+ public static HTTP2PriorityFrame ReadPriorityFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#PRIORITY
+
+ if (header.PayloadLength != 5)
+ {
+ //throw FRAME_SIZE_ERROR
+ }
+
+ HTTP2PriorityFrame frame = new HTTP2PriorityFrame(header);
+
+ frame.IsExclusive = BufferHelper.ReadBit(header.Payload[0], 0);
+ frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, 0);
+ frame.Weight = header.Payload[4];
+
+ return frame;
+ }
+
+ public static HTTP2HeadersFrame ReadHeadersFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#HEADERS
+
+ HTTP2HeadersFrame frame = new HTTP2HeadersFrame(header);
+ frame.HeaderBlockFragmentLength = header.PayloadLength;
+
+ bool isPadded = (frame.Flags & HTTP2HeadersFlags.PADDED) != 0;
+ bool isPriority = (frame.Flags & HTTP2HeadersFlags.PRIORITY) != 0;
+
+ int payloadIdx = 0;
+
+ if (isPadded)
+ {
+ frame.PadLength = header.Payload[payloadIdx++];
+
+ uint subLength = (uint)(1 + (frame.PadLength ?? 0));
+ if (subLength <= frame.HeaderBlockFragmentLength)
+ frame.HeaderBlockFragmentLength -= subLength;
+ //else
+ // throw PROTOCOL_ERROR;
+ }
+
+ if (isPriority)
+ {
+ frame.IsExclusive = BufferHelper.ReadBit(header.Payload[payloadIdx], 0);
+ frame.StreamDependency = BufferHelper.ReadUInt31(header.Payload, payloadIdx);
+ payloadIdx += 4;
+ frame.Weight = header.Payload[payloadIdx++];
+
+ uint subLength = 5;
+ if (subLength <= frame.HeaderBlockFragmentLength)
+ frame.HeaderBlockFragmentLength -= subLength;
+ //else
+ // throw PROTOCOL_ERROR;
+ }
+
+ frame.HeaderBlockFragmentIdx = (UInt32)payloadIdx;
+ frame.HeaderBlockFragment = header.Payload;
+
+ return frame;
+ }
+
+ public static HTTP2DataFrame ReadDataFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#DATA
+
+ HTTP2DataFrame frame = new HTTP2DataFrame(header);
+
+ frame.DataLength = header.PayloadLength;
+
+ bool isPadded = (frame.Flags & HTTP2DataFlags.PADDED) != 0;
+ if (isPadded)
+ {
+ frame.PadLength = header.Payload[0];
+
+ uint subLength = (uint)(1 + (frame.PadLength ?? 0));
+ if (subLength <= frame.DataLength)
+ frame.DataLength -= subLength;
+ //else
+ // throw PROTOCOL_ERROR;
+ }
+
+ frame.DataIdx = (UInt32)(isPadded ? 1 : 0);
+ frame.Data = header.Payload;
+ header.Payload = null;
+
+ return frame;
+ }
+
+ public static HTTP2AltSVCFrame ReadAltSvcFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ HTTP2AltSVCFrame frame = new HTTP2AltSVCFrame(header);
+
+ // Implement
+
+ return frame;
+ }
+
+ public static void StreamRead(Stream stream, byte[] buffer, int offset, uint count)
+ {
+ if (count == 0)
+ return;
+
+ uint sumRead = 0;
+ do
+ {
+ int readCount = (int)(count - sumRead);
+ int streamReadCount = stream.Read(buffer, (int)(offset + sumRead), readCount);
+ if (streamReadCount <= 0 && readCount > 0)
+ throw new Exception("TCP Stream closed!");
+ sumRead += (uint)streamReadCount;
+ } while (sumRead < count);
+ }
+
+ public static PooledBuffer HeaderAsBinary(HTTP2FrameHeaderAndPayload header)
+ {
+ // https://httpwg.org/specs/rfc7540.html#FrameHeader
+
+ var buffer = BufferPool.Get(9, true);
+
+ BufferHelper.SetUInt24(buffer, 0, header.PayloadLength);
+ buffer[3] = (byte)header.Type;
+ buffer[4] = header.Flags;
+ BufferHelper.SetUInt31(buffer, 5, header.StreamId);
+
+ return new PooledBuffer { Data = buffer, Length = 9 };
+ }
+
+ public static HTTP2FrameHeaderAndPayload ReadHeader(Stream stream)
+ {
+ byte[] buffer = BufferPool.Get(9, true);
+
+ StreamRead(stream, buffer, 0, 9);
+
+ HTTP2FrameHeaderAndPayload header = new HTTP2FrameHeaderAndPayload();
+
+ header.PayloadLength = BufferHelper.ReadUInt24(buffer, 0);
+ header.Type = (HTTP2FrameTypes)buffer[3];
+ header.Flags = buffer[4];
+ header.StreamId = BufferHelper.ReadUInt31(buffer, 5);
+
+ BufferPool.Release(buffer);
+
+ header.Payload = BufferPool.Get(header.PayloadLength, true);
+ StreamRead(stream, header.Payload, 0, header.PayloadLength);
+
+ return header;
+ }
+
+ public static HTTP2SettingsFrame ReadSettings(HTTP2FrameHeaderAndPayload header)
+ {
+ HTTP2SettingsFrame frame = new HTTP2SettingsFrame(header);
+
+ if (header.PayloadLength > 0)
+ {
+ int kvpCount = (int)(header.PayloadLength / 6);
+
+ frame.Settings = new List>(kvpCount);
+ for (int i = 0; i < kvpCount; ++i)
+ {
+ HTTP2Settings key = (HTTP2Settings)BufferHelper.ReadUInt16(header.Payload, i * 6);
+ UInt32 value = BufferHelper.ReadUInt32(header.Payload, (i * 6) + 2);
+
+ frame.Settings.Add(new KeyValuePair(key, value));
+ }
+ }
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreateACKSettingsFrame()
+ {
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.SETTINGS;
+ frame.Flags = (byte)HTTP2SettingsFlags.ACK;
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreateSettingsFrame(List> settings)
+ {
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.SETTINGS;
+ frame.Flags = 0;
+ frame.PayloadLength = (UInt32)settings.Count * 6;
+
+ frame.Payload = BufferPool.Get(frame.PayloadLength, true);
+
+ for (int i = 0; i < settings.Count; ++i)
+ {
+ BufferHelper.SetUInt16(frame.Payload, i * 6, (UInt16)settings[i].Key);
+ BufferHelper.SetUInt32(frame.Payload, (i * 6) + 2, settings[i].Value);
+ }
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreatePingFrame(HTTP2PingFlags flags = HTTP2PingFlags.None)
+ {
+ // https://httpwg.org/specs/rfc7540.html#PING
+
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.PING;
+ frame.Flags = (byte)flags;
+ frame.StreamId = 0;
+ frame.Payload = BufferPool.Get(8, true);
+ frame.PayloadLength = 8;
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreateWindowUpdateFrame(UInt32 streamId, UInt32 windowSizeIncrement)
+ {
+ // https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
+
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.WINDOW_UPDATE;
+ frame.Flags = 0;
+ frame.StreamId = streamId;
+ frame.Payload = BufferPool.Get(4, true);
+ frame.PayloadLength = 4;
+
+ BufferHelper.SetBit(0, 0, 0);
+ BufferHelper.SetUInt31(frame.Payload, 0, windowSizeIncrement);
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreateGoAwayFrame(UInt32 lastStreamId, HTTP2ErrorCodes error)
+ {
+ // https://httpwg.org/specs/rfc7540.html#GOAWAY
+
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.GOAWAY;
+ frame.Flags = 0;
+ frame.StreamId = 0;
+ frame.Payload = BufferPool.Get(8, true);
+ frame.PayloadLength = 8;
+
+ BufferHelper.SetUInt31(frame.Payload, 0, lastStreamId);
+ BufferHelper.SetUInt31(frame.Payload, 4, (UInt32)error);
+
+ return frame;
+ }
+
+ public static HTTP2FrameHeaderAndPayload CreateRSTFrame(UInt32 streamId, HTTP2ErrorCodes errorCode)
+ {
+ // https://httpwg.org/specs/rfc7540.html#RST_STREAM
+
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.RST_STREAM;
+ frame.Flags = 0;
+ frame.StreamId = streamId;
+ frame.Payload = BufferPool.Get(4, true);
+ frame.PayloadLength = 4;
+
+ BufferHelper.SetUInt32(frame.Payload, 0, (UInt32)errorCode);
+
+ return frame;
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..82d113a58425a1f638a011fcf4f3f10f9a2338ad
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2FrameHelper.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8a3c905b246be424b8afe85c7e0a8526
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d3d60506f3c7bdae8d999b80a3a9e31f5500668c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs
@@ -0,0 +1,402 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ // https://httpwg.org/specs/rfc7540.html#iana-frames
+ public enum HTTP2FrameTypes : byte
+ {
+ DATA = 0x00,
+ HEADERS = 0x01,
+ PRIORITY = 0x02,
+ RST_STREAM = 0x03,
+ SETTINGS = 0x04,
+ PUSH_PROMISE = 0x05,
+ PING = 0x06,
+ GOAWAY = 0x07,
+ WINDOW_UPDATE = 0x08,
+ CONTINUATION = 0x09,
+
+ // https://tools.ietf.org/html/rfc7838#section-4
+ ALT_SVC = 0x0A
+ }
+
+ [Flags]
+ public enum HTTP2DataFlags : byte
+ {
+ None = 0x00,
+ END_STREAM = 0x01,
+ PADDED = 0x08,
+ }
+
+ [Flags]
+ public enum HTTP2HeadersFlags : byte
+ {
+ None = 0x00,
+ END_STREAM = 0x01,
+ END_HEADERS = 0x04,
+ PADDED = 0x08,
+ PRIORITY = 0x20,
+ }
+
+ [Flags]
+ public enum HTTP2SettingsFlags : byte
+ {
+ None = 0x00,
+ ACK = 0x01,
+ }
+
+ [Flags]
+ public enum HTTP2PushPromiseFlags : byte
+ {
+ None = 0x00,
+ END_HEADERS = 0x04,
+ PADDED = 0x08,
+ }
+
+ [Flags]
+ public enum HTTP2PingFlags : byte
+ {
+ None = 0x00,
+ ACK = 0x01,
+ }
+
+ [Flags]
+ public enum HTTP2ContinuationFlags : byte
+ {
+ None = 0x00,
+ END_HEADERS = 0x04,
+ }
+
+ public struct HTTP2FrameHeaderAndPayload
+ {
+ public UInt32 PayloadLength;
+ public HTTP2FrameTypes Type;
+ public byte Flags;
+ public UInt32 StreamId;
+ public byte[] Payload;
+
+ public UInt32 PayloadOffset;
+ public bool DontUseMemPool;
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2FrameHeaderAndPayload Length: {0}, Type: {1}, Flags: {2}, StreamId: {3}, PayloadOffset: {4}, DontUseMemPool: {5}]",
+ this.PayloadLength, this.Type, this.Flags.ToBinaryStr(), this.StreamId, this.PayloadOffset, this.DontUseMemPool);
+ }
+
+ public string PayloadAsHex()
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder("[", (int)this.PayloadLength + 1);
+ if (this.Payload != null && this.PayloadLength > 0)
+ {
+ uint idx = this.PayloadOffset;
+ sb.Append(this.Payload[idx++]);
+ for (int i = 1; i < this.PayloadLength; i++)
+ sb.AppendFormat(", {0:X2}", this.Payload[idx++]);
+ }
+ sb.Append("]");
+
+ return sb.ToString();
+ }
+ }
+
+ public struct HTTP2SettingsFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2SettingsFlags Flags { get { return (HTTP2SettingsFlags)this.Header.Flags; } }
+ public List> Settings;
+
+ public HTTP2SettingsFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.Settings = null;
+ }
+
+ public override string ToString()
+ {
+ string settings = null;
+ if (this.Settings != null)
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder("[");
+ foreach (var kvp in this.Settings)
+ sb.AppendFormat("[{0}: {1}]", kvp.Key, kvp.Value);
+ sb.Append("]");
+
+ settings = sb.ToString();
+ }
+
+ return string.Format("[HTTP2SettingsFrame Header: {0}, Flags: {1}, Settings: {2}]", this.Header.ToString(), this.Flags, settings ?? "Empty");
+ }
+ }
+
+ public struct HTTP2DataFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2DataFlags Flags { get { return (HTTP2DataFlags)this.Header.Flags; } }
+
+ public byte? PadLength;
+ public UInt32 DataIdx;
+ public byte[] Data;
+ public uint DataLength;
+
+ public HTTP2DataFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.PadLength = null;
+ this.DataIdx = 0;
+ this.Data = null;
+ this.DataLength = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2DataFrame Header: {0}, Flags: {1}, PadLength: {2}, DataLength: {3}]",
+ this.Header.ToString(),
+ this.Flags,
+ this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
+ this.DataLength);
+ }
+ }
+
+ public struct HTTP2HeadersFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2HeadersFlags Flags { get { return (HTTP2HeadersFlags)this.Header.Flags; } }
+
+ public byte? PadLength;
+ public byte? IsExclusive;
+ public UInt32? StreamDependency;
+ public byte? Weight;
+ public UInt32 HeaderBlockFragmentIdx;
+ public byte[] HeaderBlockFragment;
+ public UInt32 HeaderBlockFragmentLength;
+
+ public HTTP2HeadersFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.PadLength = null;
+ this.IsExclusive = null;
+ this.StreamDependency = null;
+ this.Weight = null;
+ this.HeaderBlockFragmentIdx = 0;
+ this.HeaderBlockFragment = null;
+ this.HeaderBlockFragmentLength = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2HeadersFrame Header: {0}, Flags: {1}, PadLength: {2}, IsExclusive: {3}, StreamDependency: {4}, Weight: {5}, HeaderBlockFragmentLength: {6}]",
+ this.Header.ToString(),
+ this.Flags,
+ this.PadLength == null ? ":Empty" : this.PadLength.Value.ToString(),
+ this.IsExclusive == null ? "Empty" : this.IsExclusive.Value.ToString(),
+ this.StreamDependency == null ? "Empty" : this.StreamDependency.Value.ToString(),
+ this.Weight == null ? "Empty" : this.Weight.Value.ToString(),
+ this.HeaderBlockFragmentLength);
+ }
+ }
+
+ public struct HTTP2PriorityFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+
+ public byte IsExclusive;
+ public UInt32 StreamDependency;
+ public byte Weight;
+
+ public HTTP2PriorityFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.IsExclusive = 0;
+ this.StreamDependency = 0;
+ this.Weight = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2PriorityFrame Header: {0}, IsExclusive: {1}, StreamDependency: {2}, Weight: {3}]",
+ this.Header.ToString(), this.IsExclusive, this.StreamDependency, this.Weight);
+ }
+ }
+
+ public struct HTTP2RSTStreamFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+
+ public UInt32 ErrorCode;
+ public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
+
+ public HTTP2RSTStreamFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.ErrorCode = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2RST_StreamFrame Header: {0}, Error: {1}({2})]", this.Header.ToString(), this.Error, this.ErrorCode);
+ }
+ }
+
+ public struct HTTP2PushPromiseFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2PushPromiseFlags Flags { get { return (HTTP2PushPromiseFlags)this.Header.Flags; } }
+
+ public byte? PadLength;
+ public byte ReservedBit;
+ public UInt32 PromisedStreamId;
+ public UInt32 HeaderBlockFragmentIdx;
+ public byte[] HeaderBlockFragment;
+ public UInt32 HeaderBlockFragmentLength;
+
+ public HTTP2PushPromiseFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.PadLength = null;
+ this.ReservedBit = 0;
+ this.PromisedStreamId = 0;
+ this.HeaderBlockFragmentIdx = 0;
+ this.HeaderBlockFragment = null;
+ this.HeaderBlockFragmentLength = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2Push_PromiseFrame Header: {0}, Flags: {1}, PadLength: {2}, ReservedBit: {3}, PromisedStreamId: {4}, HeaderBlockFragmentLength: {5}]",
+ this.Header.ToString(),
+ this.Flags,
+ this.PadLength == null ? "Empty" : this.PadLength.Value.ToString(),
+ this.ReservedBit,
+ this.PromisedStreamId,
+ this.HeaderBlockFragmentLength);
+ }
+ }
+
+ public struct HTTP2PingFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2PingFlags Flags { get { return (HTTP2PingFlags)this.Header.Flags; } }
+
+ public readonly byte[] OpaqueData;
+ public readonly byte OpaqueDataLength;
+
+ public HTTP2PingFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.OpaqueData = BufferPool.Get(8, true);
+ this.OpaqueDataLength = 8;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2PingFrame Header: {0}, Flags: {1}, OpaqueData: {2}]",
+ this.Header.ToString(),
+ this.Flags,
+ SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.OpaqueData, 0, this.OpaqueDataLength));
+ }
+ }
+
+ public struct HTTP2GoAwayFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2ErrorCodes Error { get { return (HTTP2ErrorCodes)this.ErrorCode; } }
+
+ public byte ReservedBit;
+ public UInt32 LastStreamId;
+ public UInt32 ErrorCode;
+ public byte[] AdditionalDebugData;
+ public UInt32 AdditionalDebugDataLength;
+
+ public HTTP2GoAwayFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.ReservedBit = 0;
+ this.LastStreamId = 0;
+ this.ErrorCode = 0;
+ this.AdditionalDebugData = null;
+ this.AdditionalDebugDataLength = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2GoAwayFrame Header: {0}, ReservedBit: {1}, LastStreamId: {2}, Error: {3}({4}), AdditionalDebugData({5}): {6}]",
+ this.Header.ToString(),
+ this.ReservedBit,
+ this.LastStreamId,
+ this.Error,
+ this.ErrorCode,
+ this.AdditionalDebugDataLength,
+ this.AdditionalDebugData == null ? "Empty" : SecureProtocol.Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(this.AdditionalDebugData, 0, (int)this.AdditionalDebugDataLength));
+ }
+ }
+
+ public struct HTTP2WindowUpdateFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+
+ public byte ReservedBit;
+ public UInt32 WindowSizeIncrement;
+
+ public HTTP2WindowUpdateFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.ReservedBit = 0;
+ this.WindowSizeIncrement = 0;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2WindowUpdateFrame Header: {0}, ReservedBit: {1}, WindowSizeIncrement: {2}]",
+ this.Header.ToString(), this.ReservedBit, this.WindowSizeIncrement);
+ }
+ }
+
+ public struct HTTP2ContinuationFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+ public HTTP2ContinuationFlags Flags { get { return (HTTP2ContinuationFlags)this.Header.Flags; } }
+
+ public byte[] HeaderBlockFragment;
+ public UInt32 HeaderBlockFragmentLength { get { return this.Header.PayloadLength; } }
+
+ public HTTP2ContinuationFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.HeaderBlockFragment = null;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HTTP2ContinuationFrame Header: {0}, Flags: {1}, HeaderBlockFragmentLength: {2}]",
+ this.Header.ToString(),
+ this.Flags,
+ this.HeaderBlockFragmentLength);
+ }
+ }
+
+ ///
+ /// https://tools.ietf.org/html/rfc7838#section-4
+ ///
+ public struct HTTP2AltSVCFrame
+ {
+ public readonly HTTP2FrameHeaderAndPayload Header;
+
+ public string Origin;
+ public string AltSvcFieldValue;
+
+ public HTTP2AltSVCFrame(HTTP2FrameHeaderAndPayload header)
+ {
+ this.Header = header;
+ this.Origin = null;
+ this.AltSvcFieldValue = null;
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..dca9372ca556ae4281b1b77860d8c44439998b39
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Frames.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 06f965acc306c134093ce2a816832155
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..13d8cd05e2d2651ed6330cbc5fd39a0238c90484
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs
@@ -0,0 +1,620 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+using System.Collections.Concurrent;
+
+using BestHTTP.Extensions;
+using BestHTTP.Core;
+using BestHTTP.PlatformSupport.Memory;
+using BestHTTP.Logger;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ public sealed class HTTP2Handler : IHTTPRequestHandler
+ {
+ public bool HasCustomRequestProcessor { get { return true; } }
+
+ public KeepAliveHeader KeepAlive { get { return null; } }
+
+ public bool CanProcessMultiple { get { return this.goAwaySentAt == DateTime.MaxValue && this.isRunning; } }
+
+ // Connection preface starts with the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n).
+ private static readonly byte[] MAGIC = new byte[24] { 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a };
+ public const UInt32 MaxValueFor31Bits = 0xFFFFFFFF >> 1;
+
+ public double Latency { get; private set; }
+
+ public HTTP2SettingsManager settings;
+ public HPACKEncoder HPACKEncoder;
+
+ public LoggingContext Context { get; private set; }
+
+ private DateTime lastPingSent = DateTime.MinValue;
+ private TimeSpan pingFrequency = TimeSpan.FromMinutes(5);
+ public static int RTTBufferCapacity = 5;
+ private CircularBuffer rtts = new CircularBuffer(RTTBufferCapacity);
+
+ private volatile bool isRunning;
+
+ private AutoResetEvent newFrameSignal = new AutoResetEvent(false);
+
+ private ConcurrentQueue requestQueue = new ConcurrentQueue();
+
+ private List clientInitiatedStreams = new List();
+
+ private ConcurrentQueue newFrames = new ConcurrentQueue();
+
+ private List outgoingFrames = new List();
+
+ private UInt32 remoteWindow;
+ private DateTime lastInteraction;
+ private DateTime goAwaySentAt = DateTime.MaxValue;
+
+ private HTTPConnection conn;
+ private int threadExitCount;
+
+ private TimeSpan MaxGoAwayWaitTime { get { return this.goAwaySentAt == DateTime.MaxValue ? TimeSpan.MaxValue : TimeSpan.FromMilliseconds(Math.Max(this.Latency * 2.5, 1500)); } }
+
+ // https://httpwg.org/specs/rfc7540.html#StreamIdentifiers
+ // Streams initiated by a client MUST use odd-numbered stream identifiers
+ // With an initial value of -1, the first client initiated stream's id going to be 1.
+ private long LastStreamId = -1;
+
+ public HTTP2Handler(HTTPConnection conn)
+ {
+ this.Context = new LoggingContext(this);
+
+ this.conn = conn;
+ this.isRunning = true;
+
+ this.settings = new HTTP2SettingsManager(this);
+
+ Process(this.conn.CurrentRequest);
+ }
+
+ public void Process(HTTPRequest request)
+ {
+ HTTPManager.Logger.Information("HTTP2Handler", "Process request called", this.Context, request.Context);
+
+ request.QueuedAt = DateTime.MinValue;
+ request.ProcessingStarted = this.lastInteraction = DateTime.UtcNow;
+
+ this.requestQueue.Enqueue(request);
+
+ // Wee might added the request to a dead queue, signaling would be pointless.
+ // When the ConnectionEventHelper processes the Close state-change event
+ // requests in the queue going to be resent. (We should avoid resending the request just right now,
+ // as it might still select this connection/handler resulting in a infinite loop.)
+ if (Volatile.Read(ref this.threadExitCount) == 0)
+ this.newFrameSignal.Set();
+ }
+
+ public void SignalRunnerThread()
+ {
+ this.newFrameSignal.Set();
+ }
+
+ public void RunHandler()
+ {
+ HTTPManager.Logger.Information("HTTP2Handler", "Processing thread up and running!", this.Context);
+
+ Thread.CurrentThread.Name = "BestHTTP.HTTP2 Process";
+
+ PlatformSupport.Threading.ThreadedRunner.RunLongLiving(ReadThread);
+
+ try
+ {
+ bool atLeastOneStreamHasAFrameToSend = true;
+
+ this.HPACKEncoder = new HPACKEncoder(this, this.settings);
+
+ // https://httpwg.org/specs/rfc7540.html#InitialWindowSize
+ // The connection flow-control window is also 65,535 octets.
+ this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
+
+ // we want to pack as many data as we can in one tcp segment, but setting the buffer's size too high
+ // we might keep data too long and send them in bursts instead of in a steady stream.
+ // Keeping it too low might result in a full tcp segment and one with very low payload
+ // Is it possible that one full tcp segment sized buffer would be the best, or multiple of it.
+ // It would keep the network busy without any fragments. The ethernet layer has a maximum of 1500 bytes,
+ // but there's two layers of 20 byte headers each, so as a theoretical maximum it's 1500-20-20 bytes.
+ // On the other hand, if the buffer is small (1-2), that means that for larger data, we have to do a lot
+ // of system calls, in that case a larger buffer might be better. Still, if we are not cpu bound,
+ // a well saturated network might serve us better.
+ using (WriteOnlyBufferedStream bufferedStream = new WriteOnlyBufferedStream(this.conn.connector.Stream, 1024 * 1024 /*1500 - 20 - 20*/))
+ {
+ // The client connection preface starts with a sequence of 24 octets
+ bufferedStream.Write(MAGIC, 0, MAGIC.Length);
+
+ // This sequence MUST be followed by a SETTINGS frame (Section 6.5), which MAY be empty.
+ // The client sends the client connection preface immediately upon receipt of a
+ // 101 (Switching Protocols) response (indicating a successful upgrade)
+ // or as the first application data octets of a TLS connection
+
+ // Set streams' initial window size to its maximum.
+ this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] = HTTPManager.HTTP2Settings.InitialStreamWindowSize;
+ this.settings.InitiatedMySettings[HTTP2Settings.MAX_CONCURRENT_STREAMS] = HTTPManager.HTTP2Settings.MaxConcurrentStreams;
+ this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] = (uint)(HTTPManager.HTTP2Settings.EnableConnectProtocol ? 1 : 0);
+ this.settings.InitiatedMySettings[HTTP2Settings.ENABLE_PUSH] = 0;
+ this.settings.SendChanges(this.outgoingFrames);
+ this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
+
+ // The default window size for the whole connection is 65535 bytes,
+ // but we want to set it to the maximum possible value.
+ Int64 initialConnectionWindowSize = HTTPManager.HTTP2Settings.InitialConnectionWindowSize;
+
+ // yandex.ru returns with an FLOW_CONTROL_ERROR (3) error when the plugin tries to set the connection window to 2^31 - 1
+ // and works only with a maximum value of 2^31 - 10Mib (10 * 1024 * 1024).
+ if (initialConnectionWindowSize == HTTP2Handler.MaxValueFor31Bits)
+ initialConnectionWindowSize -= 10 * 1024 * 1024;
+
+ Int64 diff = initialConnectionWindowSize - 65535;
+ if (diff > 0)
+ this.outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(0, (UInt32)diff));
+
+ this.pingFrequency = HTTPManager.HTTP2Settings.PingFrequency;
+
+ while (this.isRunning)
+ {
+ DateTime now = DateTime.UtcNow;
+
+ if (!atLeastOneStreamHasAFrameToSend)
+ {
+ // buffered stream will call flush automatically if its internal buffer is full.
+ // But we have to make it sure that we flush remaining data before we go to sleep.
+ bufferedStream.Flush();
+
+ // Wait until we have to send the next ping, OR a new frame is received on the read thread.
+ // Sent Now Sent+frequency
+ //----|-----|---------------|--------|-------------------|
+ // lastInteraction lastInteraction + MaxIdleTime
+
+ var sendPingAt = this.lastPingSent + this.pingFrequency;
+ var disconnectByIdleAt = this.lastInteraction + HTTPManager.HTTP2Settings.MaxIdleTime;
+ var nextDueClientInteractionAt = sendPingAt < disconnectByIdleAt ? sendPingAt : disconnectByIdleAt;
+
+ int wait = (int)(nextDueClientInteractionAt - now).TotalMilliseconds;
+
+ wait = (int)Math.Min(wait, this.MaxGoAwayWaitTime.TotalMilliseconds);
+
+ if (wait >= 1)
+ {
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
+ HTTPManager.Logger.Information("HTTP2Handler", string.Format("Sleeping for {0:N0}ms", wait), this.Context);
+ this.newFrameSignal.WaitOne(wait);
+
+ now = DateTime.UtcNow;
+ }
+ }
+
+ if (now - this.lastPingSent >= this.pingFrequency)
+ {
+ this.lastPingSent = now;
+
+ var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.None);
+ BufferHelper.SetLong(frame.Payload, 0, now.Ticks);
+
+ this.outgoingFrames.Add(frame);
+ }
+
+ // Process received frames
+ HTTP2FrameHeaderAndPayload header;
+ while (this.newFrames.TryDequeue(out header))
+ {
+ if (header.StreamId > 0)
+ {
+ HTTP2Stream http2Stream = FindStreamById(header.StreamId);
+
+ // Add frame to the stream, so it can process it when its Process function is called
+ if (http2Stream != null)
+ {
+ http2Stream.AddFrame(header, this.outgoingFrames);
+ }
+ else
+ {
+ // Error? It's possible that we closed and removed the stream while the server was in the middle of sending frames
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Warning("HTTP2Handler", string.Format("No stream found for id: {0}! Can't deliver frame: {1}", header.StreamId, header), this.Context, http2Stream.Context);
+ }
+ }
+ else
+ {
+ switch (header.Type)
+ {
+ case HTTP2FrameTypes.SETTINGS:
+ this.settings.Process(header, this.outgoingFrames);
+
+ PluginEventHelper.EnqueuePluginEvent(
+ new PluginEventInfo(PluginEvents.HTTP2ConnectProtocol,
+ new HTTP2ConnectProtocolInfo(this.conn.LastProcessedUri.Host,
+ this.settings.MySettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1 && this.settings.RemoteSettings[HTTP2Settings.ENABLE_CONNECT_PROTOCOL] == 1)));
+ break;
+
+ case HTTP2FrameTypes.PING:
+ var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
+
+ // if it wasn't an ack for our ping, we have to send one
+ if ((pingFrame.Flags & HTTP2PingFlags.ACK) == 0)
+ {
+ var frame = HTTP2FrameHelper.CreatePingFrame(HTTP2PingFlags.ACK);
+ Array.Copy(pingFrame.OpaqueData, 0, frame.Payload, 0, pingFrame.OpaqueDataLength);
+
+ this.outgoingFrames.Add(frame);
+ }
+ break;
+
+ case HTTP2FrameTypes.WINDOW_UPDATE:
+ var windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(header);
+ this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
+ break;
+
+ case HTTP2FrameTypes.GOAWAY:
+ // parse the frame, so we can print out detailed information
+ HTTP2GoAwayFrame goAwayFrame = HTTP2FrameHelper.ReadGoAwayFrame(header);
+
+ HTTPManager.Logger.Information("HTTP2Handler", "Received GOAWAY frame: " + goAwayFrame.ToString(), this.Context);
+
+ string msg = string.Format("Server closing the connection! Error code: {0} ({1})", goAwayFrame.Error, goAwayFrame.ErrorCode);
+ for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
+ this.clientInitiatedStreams[i].Abort(msg);
+ this.clientInitiatedStreams.Clear();
+
+ // set the running flag to false, so the thread can exit
+ this.isRunning = false;
+
+ this.conn.State = HTTPConnectionStates.Closed;
+ break;
+
+ case HTTP2FrameTypes.ALT_SVC:
+ //HTTP2AltSVCFrame altSvcFrame = HTTP2FrameHelper.ReadAltSvcFrame(header);
+
+ // Implement
+ //HTTPManager.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.AltSvcHeader, new AltSvcEventInfo(altSvcFrame.Origin, ))
+ break;
+ }
+
+ if (header.Payload != null)
+ BufferPool.Release(header.Payload);
+ }
+ }
+
+ UInt32 maxConcurrentStreams = Math.Min(HTTPManager.HTTP2Settings.MaxConcurrentStreams, this.settings.RemoteSettings[HTTP2Settings.MAX_CONCURRENT_STREAMS]);
+
+ // pre-test stream count to lock only when truly needed.
+ if (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.isRunning)
+ {
+ // grab requests from queue
+ HTTPRequest request;
+ while (this.clientInitiatedStreams.Count < maxConcurrentStreams && this.requestQueue.TryDequeue(out request))
+ {
+ // create a new stream
+ var newStream = new HTTP2Stream((UInt32)Interlocked.Add(ref LastStreamId, 2), this, this.settings, this.HPACKEncoder);
+
+ // process the request
+ newStream.Assign(request);
+
+ this.clientInitiatedStreams.Add(newStream);
+ }
+ }
+
+ // send any settings changes
+ this.settings.SendChanges(this.outgoingFrames);
+
+ atLeastOneStreamHasAFrameToSend = false;
+
+ // process other streams
+ // Room for improvement Streams should be processed by their priority!
+ for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
+ {
+ var stream = this.clientInitiatedStreams[i];
+ stream.Process(this.outgoingFrames);
+
+ // remove closed, empty streams (not enough to check the closed flag, a closed stream still can contain frames to send)
+ if (stream.State == HTTP2StreamStates.Closed && !stream.HasFrameToSend)
+ {
+ this.clientInitiatedStreams.RemoveAt(i--);
+ stream.Removed();
+ }
+
+ atLeastOneStreamHasAFrameToSend |= stream.HasFrameToSend;
+
+ this.lastInteraction = DateTime.UtcNow;
+ }
+
+ // If we encounter a data frame that too large for the current remote window, we have to stop
+ // sending all data frames as we could send smaller data frames before the large ones.
+ // Room for improvement: An improvement would be here to stop data frame sending per-stream.
+ bool haltDataSending = false;
+
+ if (this.ShutdownType == ShutdownTypes.Running && now - this.lastInteraction >= HTTPManager.HTTP2Settings.MaxIdleTime)
+ {
+ this.lastInteraction = DateTime.UtcNow;
+ HTTPManager.Logger.Information("HTTP2Handler", "Reached idle time, sending GoAway frame!", this.Context);
+ this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
+ this.goAwaySentAt = DateTime.UtcNow;
+ }
+
+ // https://httpwg.org/specs/rfc7540.html#GOAWAY
+ // Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote peer can know whether a stream has been partially processed or not.
+ if (this.ShutdownType == ShutdownTypes.Gentle)
+ {
+ HTTPManager.Logger.Information("HTTP2Handler", "Connection abort requested, sending GoAway frame!", this.Context);
+
+ this.outgoingFrames.Clear();
+ this.outgoingFrames.Add(HTTP2FrameHelper.CreateGoAwayFrame(0, HTTP2ErrorCodes.NO_ERROR));
+ this.goAwaySentAt = DateTime.UtcNow;
+ }
+
+ if (this.isRunning && now - goAwaySentAt >= this.MaxGoAwayWaitTime)
+ {
+ HTTPManager.Logger.Information("HTTP2Handler", "No GoAway frame received back. Really quitting now!", this.Context);
+ this.isRunning = false;
+ conn.State = HTTPConnectionStates.Closed;
+ }
+
+ uint streamWindowUpdates = 0;
+
+ // Go through all the collected frames and send them.
+ for (int i = 0; i < this.outgoingFrames.Count; ++i)
+ {
+ var frame = this.outgoingFrames[i];
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.All && frame.Type != HTTP2FrameTypes.DATA /*&& frame.Type != HTTP2FrameTypes.PING*/)
+ HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
+
+ // post process frames
+ switch (frame.Type)
+ {
+ case HTTP2FrameTypes.DATA:
+ if (haltDataSending)
+ continue;
+
+ // if the tracked remoteWindow is smaller than the frame's payload, we stop sending
+ // data frames until we receive window-update frames
+ if (frame.PayloadLength > this.remoteWindow)
+ {
+ haltDataSending = true;
+ HTTPManager.Logger.Warning("HTTP2Handler", string.Format("Data sending halted for this round. Remote Window: {0:N0}, frame: {1}", this.remoteWindow, frame.ToString()), this.Context);
+ continue;
+ }
+
+ break;
+
+ case HTTP2FrameTypes.WINDOW_UPDATE:
+ if (frame.StreamId > 0)
+ streamWindowUpdates += BufferHelper.ReadUInt31(frame.Payload, 0);
+ break;
+ }
+
+ this.outgoingFrames.RemoveAt(i--);
+
+ using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
+ bufferedStream.Write(buffer.Data, 0, buffer.Length);
+
+ if (frame.PayloadLength > 0)
+ {
+ bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
+
+ if (!frame.DontUseMemPool)
+ BufferPool.Release(frame.Payload);
+ }
+
+ if (frame.Type == HTTP2FrameTypes.DATA)
+ this.remoteWindow -= frame.PayloadLength;
+ }
+
+ if (streamWindowUpdates > 0)
+ {
+ var frame = HTTP2FrameHelper.CreateWindowUpdateFrame(0, streamWindowUpdates);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
+ HTTPManager.Logger.Information("HTTP2Handler", "Sending frame: " + frame.ToString(), this.Context);
+
+ using (var buffer = HTTP2FrameHelper.HeaderAsBinary(frame))
+ bufferedStream.Write(buffer.Data, 0, buffer.Length);
+
+ bufferedStream.Write(frame.Payload, (int)frame.PayloadOffset, (int)frame.PayloadLength);
+ }
+
+ } // while (this.isRunning)
+
+ bufferedStream.Flush();
+ }
+ }
+ catch (Exception ex)
+ {
+ // Log out the exception if it's a non-expected one.
+ if (this.ShutdownType == ShutdownTypes.Running && this.goAwaySentAt == DateTime.MaxValue && HTTPManager.IsQuitting)
+ HTTPManager.Logger.Exception("HTTP2Handler", "Sender thread", ex, this.Context);
+ }
+ finally
+ {
+ TryToCleanup();
+
+ HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing - cleaning up remaining request...", this.Context);
+
+ for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
+ this.clientInitiatedStreams[i].Abort("Connection closed unexpectedly");
+ this.clientInitiatedStreams.Clear();
+
+ HTTPManager.Logger.Information("HTTP2Handler", "Sender thread closing", this.Context);
+ }
+
+ try
+ {
+ if (this.conn != null && this.conn.connector != null)
+ {
+ // Works in the new runtime
+ if (this.conn.connector.TopmostStream != null)
+ using (this.conn.connector.TopmostStream) { }
+
+ // Works in the old runtime
+ if (this.conn.connector.Stream != null)
+ using (this.conn.connector.Stream) { }
+ }
+ }
+ catch
+ { }
+ }
+
+ private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
+ {
+ switch(setting)
+ {
+ case HTTP2Settings.INITIAL_WINDOW_SIZE:
+ this.remoteWindow = newValue - (oldValue - this.remoteWindow);
+ break;
+ }
+ }
+
+ private void ReadThread()
+ {
+ try
+ {
+ Thread.CurrentThread.Name = "BestHTTP.HTTP2 Read";
+ HTTPManager.Logger.Information("HTTP2Handler", "Reader thread up and running!", this.Context);
+
+ while (this.isRunning)
+ {
+ // TODO:
+ // 1. Set the local window to a reasonable size
+ // 2. stop reading when the local window is about to be 0.
+ // 3.
+ HTTP2FrameHeaderAndPayload header = HTTP2FrameHelper.ReadHeader(this.conn.connector.Stream);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information && header.Type != HTTP2FrameTypes.DATA /*&& header.Type != HTTP2FrameTypes.PING*/)
+ HTTPManager.Logger.Information("HTTP2Handler", "New frame received: " + header.ToString(), this.Context);
+
+ // Add the new frame to the queue. Processing it on the write thread gives us the advantage that
+ // we don't have to deal with too much locking.
+ this.newFrames.Enqueue(header);
+
+ // ping write thread to process the new frame
+ this.newFrameSignal.Set();
+
+ switch (header.Type)
+ {
+ // Handle pongs on the read thread, so no additional latency is added to the rtt calculation.
+ case HTTP2FrameTypes.PING:
+ var pingFrame = HTTP2FrameHelper.ReadPingFrame(header);
+
+ if ((pingFrame.Flags & HTTP2PingFlags.ACK) != 0)
+ {
+ // it was an ack, payload must contain what we sent
+
+ var ticks = BufferHelper.ReadLong(pingFrame.OpaqueData, 0);
+
+ // the difference between the current time and the time when the ping message is sent
+ TimeSpan diff = TimeSpan.FromTicks(DateTime.UtcNow.Ticks - ticks);
+
+ // add it to the buffer
+ this.rtts.Add(diff.TotalMilliseconds);
+
+ // and calculate the new latency
+ this.Latency = CalculateLatency();
+
+ HTTPManager.Logger.Verbose("HTTP2Handler", string.Format("Latency: {0:F2}ms, RTT buffer: {1}", this.Latency, this.rtts.ToString()), this.Context);
+ }
+ break;
+
+ case HTTP2FrameTypes.GOAWAY:
+ // Just exit from this thread. The processing thread will handle the frame too.
+ return;
+ }
+ }
+ }
+ catch //(Exception ex)
+ {
+ //HTTPManager.Logger.Exception("HTTP2Handler", "", ex, this.Context);
+
+ //this.isRunning = false;
+ }
+ finally
+ {
+ TryToCleanup();
+ HTTPManager.Logger.Information("HTTP2Handler", "Reader thread closing", this.Context);
+ }
+ }
+
+ private void TryToCleanup()
+ {
+ this.isRunning = false;
+
+ // First thread closing notifies the ConnectionEventHelper
+ int counter = Interlocked.Increment(ref this.threadExitCount);
+ if (counter == 1)
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this.conn, HTTPConnectionStates.Closed));
+
+ // Last thread closes the AutoResetEvent
+ if (counter == 2)
+ {
+ if (this.newFrameSignal != null)
+ this.newFrameSignal.Close();
+ this.newFrameSignal = null;
+ }
+ }
+
+ private double CalculateLatency()
+ {
+ if (this.rtts.Count == 0)
+ return 0;
+
+ double sumLatency = 0;
+ for (int i = 0; i < this.rtts.Count; ++i)
+ sumLatency += this.rtts[i];
+
+ return sumLatency / this.rtts.Count;
+ }
+
+ HTTP2Stream FindStreamById(UInt32 streamId)
+ {
+ for (int i = 0; i < this.clientInitiatedStreams.Count; ++i)
+ {
+ var stream = this.clientInitiatedStreams[i];
+ if (stream.Id == streamId)
+ return stream;
+ }
+
+ return null;
+ }
+
+ public ShutdownTypes ShutdownType { get; private set; }
+
+ public void Shutdown(ShutdownTypes type)
+ {
+ this.ShutdownType = type;
+
+ switch(this.ShutdownType)
+ {
+ case ShutdownTypes.Gentle:
+ this.newFrameSignal.Set();
+ break;
+
+ case ShutdownTypes.Immediate:
+ this.conn.connector.Stream.Dispose();
+ break;
+ }
+ }
+
+ public void Dispose()
+ {
+ HTTPRequest request = null;
+ while (this.requestQueue.TryDequeue(out request))
+ {
+ HTTPManager.Logger.Information("HTTP2Handler", string.Format("Dispose - Request '{0}' IsCancellationRequested: {1}", request.CurrentUri.ToString(), request.IsCancellationRequested.ToString()), this.Context);
+ if (request.IsCancellationRequested)
+ {
+ request.Response = null;
+ request.State = HTTPRequestStates.Aborted;
+ }
+ else
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
+ }
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..dbd37f83d8c2a0a611915f5a436b193d6e944c9e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Handler.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 21ffa29fa36f0aa48bcf898a72ab5ecf
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d1d82deb638d40517c2d52c80d1d022d077a13ea
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs
@@ -0,0 +1,69 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+using System;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ public sealed class WebSocketOverHTTP2Settings
+ {
+ ///
+ /// Set it to false to disable Websocket Over HTTP/2 (RFC 8441). It's true by default.
+ ///
+ public bool EnableWebSocketOverHTTP2 { get; set; } = true;
+
+ ///
+ /// Set it to disable fallback logic from the Websocket Over HTTP/2 implementation to the 'old' HTTP/1 implementation when it fails to connect.
+ ///
+ public bool EnableImplementationFallback { get; set; } = true;
+ }
+
+ public sealed class HTTP2PluginSettings
+ {
+ ///
+ /// Maximum size of the HPACK header table.
+ ///
+ public UInt32 HeaderTableSize = 4096; // Spec default: 4096
+
+ ///
+ /// Maximum concurrent http2 stream on http2 connection will allow. Its default value is 128;
+ ///
+ public UInt32 MaxConcurrentStreams = 128; // Spec default: not defined
+
+ ///
+ /// Initial window size of a http2 stream. Its default value is 10 Mb (10 * 1024 * 1024).
+ ///
+ public UInt32 InitialStreamWindowSize = 10 * 1024 * 1024; // Spec default: 65535
+
+ ///
+ /// Global window size of a http/2 connection. Its default value is the maximum possible value on 31 bits.
+ ///
+ public UInt32 InitialConnectionWindowSize = HTTP2Handler.MaxValueFor31Bits; // Spec default: 65535
+
+ ///
+ /// Maximum size of a http2 frame.
+ ///
+ public UInt32 MaxFrameSize = 16384; // 16384 spec def.
+
+ ///
+ /// Not used.
+ ///
+ public UInt32 MaxHeaderListSize = UInt32.MaxValue; // Spec default: infinite
+
+ ///
+ /// With HTTP/2 only one connection will be open so we can keep it open longer as we hope it will be reused more.
+ ///
+ public TimeSpan MaxIdleTime = TimeSpan.FromSeconds(120);
+
+ ///
+ /// Time between two ping messages.
+ ///
+ public TimeSpan PingFrequency = TimeSpan.FromSeconds(30);
+
+ ///
+ /// Set to true to enable RFC 8441 "Bootstrapping WebSockets with HTTP/2" (https://tools.ietf.org/html/rfc8441).
+ ///
+ public bool EnableConnectProtocol = false;
+
+ public WebSocketOverHTTP2Settings WebSocketOverHTTP2Settings = new WebSocketOverHTTP2Settings();
+ }
+}
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..b296e47bff40d57b20cd7ddf9cf5cb1d5137cd55
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2PluginSettings.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 218daa3af21407f40ac4304f80a79b59
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8a23b0eae9d1ca833e3160660141d6cce0cadc9a
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs
@@ -0,0 +1,133 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using BestHTTP.Core;
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ public sealed class HTTP2Response : HTTPResponse
+ {
+ // For progress report
+ public long ExpectedContentLength { get; private set; }
+ public bool IsCompressed { get; private set; }
+
+ public HTTP2Response(HTTPRequest request, bool isFromCache)
+ : base(request, isFromCache)
+ {
+ this.VersionMajor = 2;
+ this.VersionMinor = 0;
+ }
+
+ internal void AddHeaders(List> headers)
+ {
+ this.ExpectedContentLength = -1;
+ for (int i = 0; i < headers.Count; ++i)
+ {
+ KeyValuePair header = headers[i];
+
+ if (header.Key.Equals(":status", StringComparison.Ordinal))
+ {
+ base.StatusCode = int.Parse(header.Value);
+ base.Message = string.Empty;
+ }
+ else
+ {
+ if (!this.IsCompressed && header.Key.Equals("content-encoding", StringComparison.OrdinalIgnoreCase))
+ {
+ this.IsCompressed = true;
+ }
+ else if (base.baseRequest.OnDownloadProgress != null && header.Key.Equals("content-length", StringComparison.OrdinalIgnoreCase))
+ {
+ long contentLength;
+ if (long.TryParse(header.Value, out contentLength))
+ this.ExpectedContentLength = contentLength;
+ else
+ HTTPManager.Logger.Information("HTTP2Response", string.Format("AddHeaders - Can't parse Content-Length as an int: '{0}'", header.Value), this.baseRequest.Context, this.Context);
+ }
+
+ base.AddHeader(header.Key, header.Value);
+ }
+ }
+
+ if (this.ExpectedContentLength == -1 && base.baseRequest.OnDownloadProgress != null)
+ HTTPManager.Logger.Information("HTTP2Response", "AddHeaders - No Content-Length header found!", this.baseRequest.Context, this.Context);
+
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.Headers));
+ }
+
+ internal void AddData(Stream stream)
+ {
+ if (this.IsCompressed)
+ {
+ using (var decoderStream = new Decompression.Zlib.GZipStream(stream, Decompression.Zlib.CompressionMode.Decompress))
+ {
+ using (var ms = new BufferPoolMemoryStream((int)stream.Length))
+ {
+ var buf = BufferPool.Get(8 * 1024, true);
+ int byteCount = 0;
+
+ while ((byteCount = decoderStream.Read(buf, 0, buf.Length)) > 0)
+ ms.Write(buf, 0, byteCount);
+
+ BufferPool.Release(buf);
+
+ base.Data = ms.ToArray();
+ }
+ }
+ }
+ else
+ {
+ base.Data = BufferPool.Get(stream.Length, false);
+ stream.Read(base.Data, 0, (int)stream.Length);
+ }
+ }
+
+ bool isPrepared;
+ private Decompression.GZipDecompressor decompressor;
+
+ internal void ProcessData(byte[] payload, int payloadLength)
+ {
+ if (!this.isPrepared)
+ {
+ this.isPrepared = true;
+ base.BeginReceiveStreamFragments();
+ }
+
+ if (this.IsCompressed)
+ {
+ if (this.decompressor == null)
+ this.decompressor = new Decompression.GZipDecompressor(0);
+ var result = this.decompressor.Decompress(payload, 0, payloadLength, true, true);
+
+ base.FeedStreamFragment(result.Data, 0, result.Length);
+ }
+ else
+ base.FeedStreamFragment(payload, 0, payloadLength);
+ }
+
+ internal void FinishProcessData()
+ {
+ base.FlushRemainingFragmentBuffer();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ if (this.decompressor != null)
+ {
+ this.decompressor.Dispose();
+ this.decompressor = null;
+ }
+ }
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..8c2a4bc090d75b350748fa8ce072b826001c58e9
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Response.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: b8916e7811100a643b66249f17136fcc
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c9cbdbc205c963169f52818ca2b946f6caeff5ff
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs
@@ -0,0 +1,277 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ // https://httpwg.org/specs/rfc7540.html#iana-settings
+ public enum HTTP2Settings : ushort
+ {
+ ///
+ /// Allows the sender to inform the remote endpoint of the maximum size of the
+ /// header compression table used to decode header blocks, in octets.
+ /// The encoder can select any size equal to or less than this value
+ /// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
+ /// The initial value is 4,096 octets.
+ ///
+ HEADER_TABLE_SIZE = 0x01,
+
+ ///
+ /// This setting can be used to disable server push (Section 8.2).
+ /// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
+ /// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
+ /// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
+ ///
+ /// The initial value is 1, which indicates that server push is permitted.
+ /// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
+ ///
+ ENABLE_PUSH = 0x02,
+
+ ///
+ /// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
+ /// it applies to the number of streams that the sender permits the receiver to create.
+ /// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
+ /// so as to not unnecessarily limit parallelism.
+ ///
+ /// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
+ /// A zero value does prevent the creation of new streams;
+ /// however, this can also happen for any limit that is exhausted with active streams.
+ /// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
+ /// closing the connection is more appropriate.
+ ///
+ MAX_CONCURRENT_STREAMS = 0x03,
+
+ ///
+ /// Indicates the sender's initial window size (in octets) for stream-level flow control.
+ /// The initial value is 2^16-1 (65,535) octets.
+ ///
+ /// This setting affects the window size of all streams (see Section 6.9.2).
+ ///
+ /// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
+ /// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
+ ///
+ INITIAL_WINDOW_SIZE = 0x04,
+
+ ///
+ /// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
+ ///
+ /// The initial value is 2^14 (16,384) octets.
+ /// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
+ /// (2^24-1 or 16,777,215 octets), inclusive.
+ /// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
+ ///
+ MAX_FRAME_SIZE = 0x05,
+
+ ///
+ /// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
+ /// The value is based on the uncompressed size of header fields,
+ /// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
+ ///
+ /// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
+ ///
+ MAX_HEADER_LIST_SIZE = 0x06,
+
+ RESERVED = 0x07,
+
+ ///
+ /// https://tools.ietf.org/html/rfc8441
+ /// Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1, a client MAY use the Extended CONNECT as defined in this document when creating new streams.
+ /// Receipt of this parameter by a server does not have any impact.
+ ///
+ /// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
+ ///
+ ENABLE_CONNECT_PROTOCOL = 0x08
+ }
+
+ public sealed class HTTP2SettingsRegistry
+ {
+ public bool IsReadOnly { get; private set; }
+ public Action OnSettingChangedEvent;
+
+ private UInt32[] values;
+ private bool[] changeFlags;
+
+ public UInt32 this[HTTP2Settings setting]
+ {
+ get { return this.values[(ushort)setting]; }
+
+ set
+ {
+ if (this.IsReadOnly)
+ throw new NotSupportedException("It's a read-only one!");
+
+ ushort idx = (ushort)setting;
+
+ // https://httpwg.org/specs/rfc7540.html#SettingValues
+ // An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
+ if (idx == 0 || idx >= this.values.Length)
+ return;
+
+ UInt32 oldValue = this.values[idx];
+ if (oldValue != value)
+ {
+ this.values[idx] = value;
+ this.changeFlags[idx] = true;
+ IsChanged = true;
+
+ if (this.OnSettingChangedEvent != null)
+ this.OnSettingChangedEvent(this, setting, oldValue, value);
+ }
+ }
+ }
+
+ public bool IsChanged { get; private set; }
+
+ private HTTP2SettingsManager _parent;
+
+ public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
+ {
+ this._parent = parent;
+
+ this.values = new UInt32[HTTP2SettingsManager.SettingsCount];
+
+ this.IsReadOnly = readOnly;
+ if (!this.IsReadOnly)
+ this.changeFlags = new bool[HTTP2SettingsManager.SettingsCount];
+
+ // Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
+ this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
+ this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
+ this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
+ this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
+ this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
+ this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
+
+ if (this.IsChanged = treatItAsAlreadyChanged)
+ {
+ this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
+ }
+ }
+
+ public void Merge(List> settings)
+ {
+ if (settings == null)
+ return;
+
+ for (int i = 0; i < settings.Count; ++i)
+ {
+ HTTP2Settings setting = settings[i].Key;
+ UInt16 key = (UInt16)setting;
+ UInt32 value = settings[i].Value;
+
+ if (key > 0 && key <= HTTP2SettingsManager.SettingsCount)
+ {
+ UInt32 oldValue = this.values[key];
+ this.values[key] = value;
+
+ if (oldValue != value && this.OnSettingChangedEvent != null)
+ this.OnSettingChangedEvent(this, setting, oldValue, value);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
+ HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Parent.Context);
+ }
+ }
+ }
+
+ public void Merge(HTTP2SettingsRegistry from)
+ {
+ if (this.values != null)
+ this.values = new uint[from.values.Length];
+
+ for (int i = 0; i < this.values.Length; ++i)
+ this.values[i] = from.values[i];
+ }
+
+ internal HTTP2FrameHeaderAndPayload CreateFrame()
+ {
+ List> keyValuePairs = new List>(HTTP2SettingsManager.SettingsCount);
+
+ for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++i)
+ if (this.changeFlags[i])
+ {
+ keyValuePairs.Add(new KeyValuePair((HTTP2Settings)i, this[(HTTP2Settings)i]));
+ this.changeFlags[i] = false;
+ }
+
+ this.IsChanged = false;
+
+ return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs);
+ }
+ }
+
+ public sealed class HTTP2SettingsManager
+ {
+ public static readonly int SettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
+
+ ///
+ /// This is the ACKd or default settings that we sent to the server.
+ ///
+ public HTTP2SettingsRegistry MySettings { get; private set; }
+
+ ///
+ /// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
+ /// to MySettings.
+ ///
+ public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
+
+ ///
+ /// Settings of the remote peer
+ ///
+ public HTTP2SettingsRegistry RemoteSettings { get; private set; }
+
+ public DateTime SettingsChangesSentAt { get; private set; }
+
+ public HTTP2Handler Parent { get; private set; }
+
+ public HTTP2SettingsManager(HTTP2Handler parentHandler)
+ {
+ this.Parent = parentHandler;
+
+ this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
+ this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
+ this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
+ this.SettingsChangesSentAt = DateTime.MinValue;
+ }
+
+ internal void Process(HTTP2FrameHeaderAndPayload frame, List outgoingFrames)
+ {
+ if (frame.Type != HTTP2FrameTypes.SETTINGS)
+ return;
+
+ HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.Context);
+
+ if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
+ {
+ this.MySettings.Merge(this.InitiatedMySettings);
+ this.SettingsChangesSentAt = DateTime.MinValue;
+ }
+ else
+ {
+ this.RemoteSettings.Merge(settingsFrame.Settings);
+ outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
+ }
+ }
+
+ internal void SendChanges(List outgoingFrames)
+ {
+ if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= TimeSpan.FromSeconds(10))
+ {
+ HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Parent.Context);
+ this.SettingsChangesSentAt = DateTime.MinValue;
+ }
+
+ // Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
+ if (!this.InitiatedMySettings.IsChanged)
+ return;
+
+ outgoingFrames.Add(this.InitiatedMySettings.CreateFrame());
+ this.SettingsChangesSentAt = DateTime.UtcNow;
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..f47cbbf549f5e111f90d09b13b06b3efe462d77b
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2SettingsRegistry.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 38a4e3fed00ab4c4181c1a7ff3dcb858
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..62ede5b0c4d465ed261203517a29b2ff56ce3df1
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs
@@ -0,0 +1,635 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using System;
+using System.Collections.Generic;
+using BestHTTP.Core;
+using BestHTTP.PlatformSupport.Memory;
+using BestHTTP.Logger;
+
+#if !BESTHTTP_DISABLE_CACHING
+using BestHTTP.Caching;
+#endif
+
+using BestHTTP.Timings;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ // https://httpwg.org/specs/rfc7540.html#StreamStates
+ //
+ // Idle
+ // |
+ // V
+ // Open
+ // Receive END_STREAM / | \ Send END_STREAM
+ // v |R V
+ // Half Closed Remote |S Half Closed Locale
+ // \ |T /
+ // Send END_STREAM | RST_STREAM \ | / Receive END_STREAM | RST_STREAM
+ // Receive RST_STREAM \ | / Send RST_STREAM
+ // V
+ // Closed
+ //
+ // IDLE -> send headers -> OPEN -> send data -> HALF CLOSED - LOCAL -> receive headers -> receive Data -> CLOSED
+ // | ^ | ^
+ // +-------------------------------------+ +-----------------------------+
+ // END_STREAM flag present? END_STREAM flag present?
+ //
+
+ public enum HTTP2StreamStates
+ {
+ Idle,
+ //ReservedLocale,
+ //ReservedRemote,
+ Open,
+ HalfClosedLocal,
+ HalfClosedRemote,
+ Closed
+ }
+
+ public sealed class HTTP2Stream
+ {
+ public UInt32 Id { get; private set; }
+
+ public HTTP2StreamStates State {
+ get { return this._state; }
+
+ private set {
+ var oldState = this._state;
+
+ this._state = value;
+
+ if (oldState != this._state)
+ {
+ this.lastStateChangedAt = DateTime.Now;
+
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] State changed from {1} to {2}", this.Id, oldState, this._state), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ }
+ }
+ }
+ private HTTP2StreamStates _state;
+
+ private DateTime lastStateChangedAt;
+ //private TimeSpan TimeSpentInCurrentState { get { return DateTime.Now - this.lastStateChangedAt; } }
+
+ ///
+ /// This flag is checked by the connection to decide whether to do a new processing-frame sending round before sleeping until new data arrives
+ ///
+ public bool HasFrameToSend
+ {
+ get
+ {
+ // Don't let the connection sleep until
+ return this.outgoing.Count > 0 || // we already booked at least one frame in advance
+ (this.State == HTTP2StreamStates.Open && this.remoteWindow > 0 && this.lastReadCount > 0); // we are in the middle of sending request data
+ }
+ }
+
+ public HTTPRequest AssignedRequest { get; private set; }
+
+ public LoggingContext Context { get; private set; }
+
+ private bool isStreamedDownload;
+ private uint downloaded;
+
+ private HTTPRequest.UploadStreamInfo uploadStreamInfo;
+
+ private HTTP2SettingsManager settings;
+ private HPACKEncoder encoder;
+
+ // Outgoing frames. The stream will send one frame per Process call, but because one step might be able to
+ // generate more than one frames, we use a list.
+ private Queue outgoing = new Queue();
+
+ private Queue incomingFrames = new Queue();
+
+ private FramesAsStreamView headerView;
+ private FramesAsStreamView dataView;
+
+ private UInt32 localWindow;
+ private Int64 remoteWindow;
+
+ private uint windowUpdateThreshold;
+
+ private UInt32 sentData;
+
+ private bool isRSTFrameSent;
+ private bool isEndSTRReceived;
+
+ private HTTP2Response response;
+
+ private HTTP2Handler parent;
+ private int lastReadCount;
+
+ ///
+ /// Constructor to create a client stream.
+ ///
+ public HTTP2Stream(UInt32 id, HTTP2Handler parentHandler, HTTP2SettingsManager registry, HPACKEncoder hpackEncoder)
+ {
+ this.Id = id;
+ this.parent = parentHandler;
+ this.settings = registry;
+ this.encoder = hpackEncoder;
+
+ this.remoteWindow = this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
+ this.settings.RemoteSettings.OnSettingChangedEvent += OnRemoteSettingChanged;
+
+ // Room for improvement: If INITIAL_WINDOW_SIZE is small (what we can consider a 'small' value?), threshold must be higher
+ this.windowUpdateThreshold = (uint)(this.remoteWindow / 2);
+
+ this.Context = new LoggingContext(this);
+ this.Context.Add("id", id);
+ }
+
+ public void Assign(HTTPRequest request)
+ {
+ if (request.IsRedirected)
+ request.Timing.Add(TimingEventNames.Queued_For_Redirection);
+ else
+ request.Timing.Add(TimingEventNames.Queued);
+
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Request assigned to stream. Remote Window: {1:N0}. Uri: {2}", this.Id, this.remoteWindow, request.CurrentUri.ToString()), this.Context, request.Context, this.parent.Context);
+ this.AssignedRequest = request;
+ this.isStreamedDownload = request.UseStreaming && request.OnStreamingData != null;
+ this.downloaded = 0;
+ }
+
+ public void Process(List outgoingFrames)
+ {
+ if (this.AssignedRequest.IsCancellationRequested && !this.isRSTFrameSent)
+ {
+ // These two are already set in HTTPRequest's Abort().
+ //this.AssignedRequest.Response = null;
+ //this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+
+ this.outgoing.Clear();
+ if (this.State != HTTP2StreamStates.Idle)
+ this.outgoing.Enqueue(HTTP2FrameHelper.CreateRSTFrame(this.Id, HTTP2ErrorCodes.CANCEL));
+
+ // We can close the stream if already received headers, or not even sent one
+ if (this.State == HTTP2StreamStates.HalfClosedRemote || this.State == HTTP2StreamStates.Idle)
+ this.State = HTTP2StreamStates.Closed;
+
+ this.isRSTFrameSent = true;
+ }
+
+ // 1.) Go through incoming frames
+ ProcessIncomingFrames(outgoingFrames);
+
+ // 2.) Create outgoing frames based on the stream's state and the request processing state.
+ ProcessState(outgoingFrames);
+
+ // 3.) Send one frame per Process call
+ if (this.outgoing.Count > 0)
+ {
+ HTTP2FrameHeaderAndPayload frame = this.outgoing.Dequeue();
+
+ outgoingFrames.Add(frame);
+
+ // If END_Stream in header or data frame is present => half closed local
+ if ((frame.Type == HTTP2FrameTypes.HEADERS && (frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0) ||
+ (frame.Type == HTTP2FrameTypes.DATA && (frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0))
+ {
+ this.State = HTTP2StreamStates.HalfClosedLocal;
+ }
+ }
+ }
+
+ public void AddFrame(HTTP2FrameHeaderAndPayload frame, List outgoingFrames)
+ {
+ // Room for improvement: error check for forbidden frames (like settings) and stream state
+
+ this.incomingFrames.Enqueue(frame);
+
+ ProcessIncomingFrames(outgoingFrames);
+ }
+
+ public void Abort(string msg)
+ {
+ if (this.AssignedRequest.State != HTTPRequestStates.Processing)
+ {
+ // do nothing, its state is already set.
+ }
+ else if (this.AssignedRequest.IsCancellationRequested)
+ {
+ // These two are already set in HTTPRequest's Abort().
+ //this.AssignedRequest.Response = null;
+ //this.AssignedRequest.State = this.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+
+ this.State = HTTP2StreamStates.Closed;
+ }
+ else if (this.AssignedRequest.Retries >= this.AssignedRequest.MaxRetries)
+ {
+ this.AssignedRequest.Response = null;
+ this.AssignedRequest.Exception = new Exception(msg);
+ this.AssignedRequest.State = HTTPRequestStates.Error;
+
+ this.State = HTTP2StreamStates.Closed;
+ }
+ else
+ {
+ this.AssignedRequest.Retries++;
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.Resend));
+ }
+
+ this.Removed();
+ }
+
+ private void ProcessIncomingFrames(List outgoingFrames)
+ {
+ UInt32 windowUpdate = 0;
+
+ while (this.incomingFrames.Count > 0)
+ {
+ HTTP2FrameHeaderAndPayload frame = this.incomingFrames.Dequeue();
+
+ if ((this.isRSTFrameSent || this.AssignedRequest.IsCancellationRequested) && frame.Type != HTTP2FrameTypes.HEADERS && frame.Type != HTTP2FrameTypes.CONTINUATION)
+ {
+ BufferPool.Release(frame.Payload);
+ continue;
+ }
+
+ if (/*HTTPManager.Logger.Level == Logger.Loglevels.All && */frame.Type != HTTP2FrameTypes.DATA && frame.Type != HTTP2FrameTypes.WINDOW_UPDATE)
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Process - processing frame: {1}", this.Id, frame.ToString()), this.Context, this.AssignedRequest.Context, this.parent.Context);
+
+ switch (frame.Type)
+ {
+ case HTTP2FrameTypes.HEADERS:
+ case HTTP2FrameTypes.CONTINUATION:
+ if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open && this.State != HTTP2StreamStates.Idle)
+ {
+ // ERROR!
+ continue;
+ }
+
+ // payload will be released by the view
+ frame.DontUseMemPool = true;
+
+ if (this.headerView == null)
+ {
+ this.AssignedRequest.Timing.Add(TimingEventNames.Waiting_TTFB);
+ this.headerView = new FramesAsStreamView(new HeaderFrameView());
+ }
+
+ this.headerView.AddFrame(frame);
+
+ // END_STREAM may arrive sooner than an END_HEADERS, so we have to store that we already received it
+ if ((frame.Flags & (byte)HTTP2HeadersFlags.END_STREAM) != 0)
+ this.isEndSTRReceived = true;
+
+ if ((frame.Flags & (byte)HTTP2HeadersFlags.END_HEADERS) != 0)
+ {
+ List> headers = new List>();
+
+ try
+ {
+ this.encoder.Decode(this, this.headerView, headers);
+ }
+ catch(Exception ex)
+ {
+ HTTPManager.Logger.Exception("HTTP2Stream", string.Format("[{0}] ProcessIncomingFrames - Header Frames: {1}, Encoder: {2}", this.Id, this.headerView.ToString(), this.encoder.ToString()), ex, this.Context, this.AssignedRequest.Context, this.parent.Context);
+ }
+
+ this.AssignedRequest.Timing.Add(TimingEventNames.Headers);
+
+ if (this.isRSTFrameSent)
+ {
+ this.State = HTTP2StreamStates.Closed;
+ break;
+ }
+
+ if (this.response == null)
+ this.AssignedRequest.Response = this.response = new HTTP2Response(this.AssignedRequest, false);
+
+ this.response.AddHeaders(headers);
+
+ this.headerView.Close();
+ this.headerView = null;
+
+ if (this.isEndSTRReceived)
+ {
+ // If there's any trailing header, no data frame has an END_STREAM flag
+ if (this.isStreamedDownload)
+ this.response.FinishProcessData();
+
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(FinishRequest, this, this.dataView);
+
+ this.dataView = null;
+
+ if (this.State == HTTP2StreamStates.HalfClosedLocal)
+ this.State = HTTP2StreamStates.Closed;
+ else
+ this.State = HTTP2StreamStates.HalfClosedRemote;
+ }
+ }
+ break;
+
+ case HTTP2FrameTypes.DATA:
+ if (this.State != HTTP2StreamStates.HalfClosedLocal && this.State != HTTP2StreamStates.Open)
+ {
+ // ERROR!
+ continue;
+ }
+
+ this.downloaded += frame.PayloadLength;
+
+ if (this.isStreamedDownload && frame.Payload != null && frame.PayloadLength > 0)
+ this.response.ProcessData(frame.Payload, (int)frame.PayloadLength);
+
+ // frame's buffer will be released by the frames view
+ frame.DontUseMemPool = !this.isStreamedDownload;
+
+ if (this.dataView == null && !this.isStreamedDownload)
+ this.dataView = new FramesAsStreamView(new DataFrameView());
+
+ if (!this.isStreamedDownload)
+ this.dataView.AddFrame(frame);
+
+ // Track received data, and if necessary(local window getting too low), send a window update frame
+ if (this.localWindow < frame.PayloadLength)
+ {
+ HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] Frame's PayloadLength ({1:N0}) is larger then local window ({2:N0}). Frame: {3}", this.Id, frame.PayloadLength, this.localWindow, frame), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ }
+ else
+ this.localWindow -= frame.PayloadLength;
+
+ if ((frame.Flags & (byte)HTTP2DataFlags.END_STREAM) != 0)
+ this.isEndSTRReceived = true;
+
+ // Window update logic.
+ // 1.) We could use a logic to only send window update(s) after a threshold is reached.
+ // When the initial window size is high enough to contain the whole or most of the result,
+ // sending back two window updates (connection and stream) after every data frame is pointless.
+ // 2.) On the other hand, window updates are cheap and works even when initial window size is low.
+ // (
+ if (this.isEndSTRReceived || this.localWindow <= this.windowUpdateThreshold)
+ windowUpdate += this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE] - this.localWindow - windowUpdate;
+
+ if (this.isEndSTRReceived)
+ {
+ if (this.isStreamedDownload)
+ this.response.FinishProcessData();
+
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] All data arrived, data length: {1:N0}", this.Id, this.downloaded), this.Context, this.AssignedRequest.Context, this.parent.Context);
+
+ // create a short living thread to process the downloaded data:
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(FinishRequest, this, this.dataView);
+
+ this.dataView = null;
+
+ if (this.State == HTTP2StreamStates.HalfClosedLocal)
+ this.State = HTTP2StreamStates.Closed;
+ else
+ this.State = HTTP2StreamStates.HalfClosedRemote;
+ }
+ else if (this.AssignedRequest.OnDownloadProgress != null)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest,
+ RequestEvents.DownloadProgress,
+ downloaded,
+ this.response.ExpectedContentLength));
+
+ break;
+
+ case HTTP2FrameTypes.WINDOW_UPDATE:
+ HTTP2WindowUpdateFrame windowUpdateFrame = HTTP2FrameHelper.ReadWindowUpdateFrame(frame);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Received Window Update: {1:N0}, new remoteWindow: {2:N0}, initial remote window: {3:N0}, total data sent: {4:N0}", this.Id, windowUpdateFrame.WindowSizeIncrement, this.remoteWindow + windowUpdateFrame.WindowSizeIncrement, this.settings.RemoteSettings[HTTP2Settings.INITIAL_WINDOW_SIZE], this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
+
+ this.remoteWindow += windowUpdateFrame.WindowSizeIncrement;
+ break;
+
+ case HTTP2FrameTypes.RST_STREAM:
+ // https://httpwg.org/specs/rfc7540.html#RST_STREAM
+
+ // It's possible to receive an RST_STREAM on a closed stream. In this case, we have to ignore it.
+ if (this.State == HTTP2StreamStates.Closed)
+ break;
+
+ var rstStreamFrame = HTTP2FrameHelper.ReadRST_StreamFrame(frame);
+
+ //HTTPManager.Logger.Error("HTTP2Stream", string.Format("[{0}] RST Stream frame ({1}) received in state {2}!", this.Id, rstStreamFrame, this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
+
+ Abort(string.Format("RST_STREAM frame received! Error code: {0}({1})", rstStreamFrame.Error.ToString(), rstStreamFrame.ErrorCode));
+ break;
+
+ default:
+ HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Unexpected frame ({1}, Payload: {2}) in state {3}!", this.Id, frame, frame.PayloadAsHex(), this.State), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ break;
+ }
+
+ if (!frame.DontUseMemPool)
+ BufferPool.Release(frame.Payload);
+ }
+
+ if (windowUpdate > 0)
+ {
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Sending window update: {1:N0}, current window: {2:N0}, initial window size: {3:N0}", this.Id, windowUpdate, this.localWindow, this.settings.MySettings[HTTP2Settings.INITIAL_WINDOW_SIZE]), this.Context, this.AssignedRequest.Context, this.parent.Context);
+
+ this.localWindow += windowUpdate;
+
+ outgoingFrames.Add(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, windowUpdate));
+ }
+ }
+
+ private void ProcessState(List outgoingFrames)
+ {
+ switch (this.State)
+ {
+ case HTTP2StreamStates.Idle:
+
+ UInt32 initiatedInitialWindowSize = this.settings.InitiatedMySettings[HTTP2Settings.INITIAL_WINDOW_SIZE];
+ this.localWindow = initiatedInitialWindowSize;
+ // window update with a zero increment would be an error (https://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE)
+ //if (HTTP2Connection.MaxValueFor31Bits > initiatedInitialWindowSize)
+ // this.outgoing.Enqueue(HTTP2FrameHelper.CreateWindowUpdateFrame(this.Id, HTTP2Connection.MaxValueFor31Bits - initiatedInitialWindowSize));
+ //this.localWindow = HTTP2Connection.MaxValueFor31Bits;
+
+#if !BESTHTTP_DISABLE_CACHING
+ // Setup cache control headers before we send out the request
+ if (!this.AssignedRequest.DisableCache)
+ HTTPCacheService.SetHeaders(this.AssignedRequest);
+#endif
+
+ // hpack encode the request's headers
+ this.encoder.Encode(this, this.AssignedRequest, this.outgoing, this.Id);
+
+ // HTTP/2 uses DATA frames to carry message payloads.
+ // The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
+ this.uploadStreamInfo = this.AssignedRequest.GetUpStream();
+
+ //this.State = HTTP2StreamStates.Open;
+
+ if (this.uploadStreamInfo.Stream == null)
+ {
+ this.State = HTTP2StreamStates.HalfClosedLocal;
+ this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
+ }
+ else
+ {
+ this.State = HTTP2StreamStates.Open;
+ this.lastReadCount = 1;
+ }
+ break;
+
+ case HTTP2StreamStates.Open:
+ // remote Window can be negative! See https://httpwg.org/specs/rfc7540.html#InitialWindowSize
+ if (this.remoteWindow <= 0)
+ {
+ HTTPManager.Logger.Warning("HTTP2Stream", string.Format("[{0}] Skipping data sending as remote Window is {1}!", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ return;
+ }
+
+ // This step will send one frame per OpenState call.
+
+ Int64 maxFrameSize = Math.Min(this.remoteWindow, this.settings.RemoteSettings[HTTP2Settings.MAX_FRAME_SIZE]);
+
+ HTTP2FrameHeaderAndPayload frame = new HTTP2FrameHeaderAndPayload();
+ frame.Type = HTTP2FrameTypes.DATA;
+ frame.StreamId = this.Id;
+
+ frame.Payload = BufferPool.Get(maxFrameSize, true);
+
+ // Expect a readCount of zero if it's end of the stream. But, to enable non-blocking scenario to wait for data, going to treat a negative value as no data.
+ this.lastReadCount = this.uploadStreamInfo.Stream.Read(frame.Payload, 0, (int)Math.Min(maxFrameSize, int.MaxValue));
+ if (this.lastReadCount <= 0)
+ {
+ BufferPool.Release(frame.Payload);
+ frame.Payload = null;
+ frame.PayloadLength = 0;
+
+ if (this.lastReadCount < 0)
+ break;
+ }
+ else
+ frame.PayloadLength = (UInt32)this.lastReadCount;
+
+ frame.PayloadOffset = 0;
+ frame.DontUseMemPool = false;
+
+ if (this.lastReadCount <= 0)
+ {
+ this.uploadStreamInfo.Stream.Dispose();
+ this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
+
+ frame.Flags = (byte)(HTTP2DataFlags.END_STREAM);
+
+ this.State = HTTP2StreamStates.HalfClosedLocal;
+
+ this.AssignedRequest.Timing.Add(TimingEventNames.Request_Sent);
+ }
+
+ this.outgoing.Enqueue(frame);
+
+ this.remoteWindow -= frame.PayloadLength;
+
+ this.sentData += frame.PayloadLength;
+
+ if (this.AssignedRequest.OnUploadProgress != null)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.AssignedRequest, RequestEvents.UploadProgress, this.sentData, this.uploadStreamInfo.Length));
+
+ //HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] New DATA frame created! remoteWindow: {1:N0}", this.Id, this.remoteWindow), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ break;
+
+ case HTTP2StreamStates.HalfClosedLocal:
+ break;
+
+ case HTTP2StreamStates.HalfClosedRemote:
+ break;
+
+ case HTTP2StreamStates.Closed:
+ break;
+ }
+ }
+
+ private void OnRemoteSettingChanged(HTTP2SettingsRegistry registry, HTTP2Settings setting, uint oldValue, uint newValue)
+ {
+ switch (setting)
+ {
+ case HTTP2Settings.INITIAL_WINDOW_SIZE:
+ // https://httpwg.org/specs/rfc7540.html#InitialWindowSize
+ // "Prior to receiving a SETTINGS frame that sets a value for SETTINGS_INITIAL_WINDOW_SIZE,
+ // an endpoint can only use the default initial window size when sending flow-controlled frames."
+ // "In addition to changing the flow-control window for streams that are not yet active,
+ // a SETTINGS frame can alter the initial flow-control window size for streams with active flow-control windows
+ // (that is, streams in the "open" or "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE changes,
+ // a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value."
+
+ // So, if we created a stream before the remote peer's initial settings frame is received, we
+ // will adjust the window size. For example: initial window size by default is 65535, if we later
+ // receive a change to 1048576 (1 MB) we will increase the current remoteWindow by (1 048 576 - 65 535 =) 983 041
+
+ // But because initial window size in a setting frame can be smaller then the default 65535 bytes,
+ // the difference can be negative:
+ // "A change to SETTINGS_INITIAL_WINDOW_SIZE can cause the available space in a flow-control window to become negative.
+ // A sender MUST track the negative flow-control window and MUST NOT send new flow-controlled frames
+ // until it receives WINDOW_UPDATE frames that cause the flow-control window to become positive.
+
+ // For example, if the client sends 60 KB immediately on connection establishment
+ // and the server sets the initial window size to be 16 KB, the client will recalculate
+ // the available flow - control window to be - 44 KB on receipt of the SETTINGS frame.
+ // The client retains a negative flow-control window until WINDOW_UPDATE frames restore the
+ // window to being positive, after which the client can resume sending."
+
+ this.remoteWindow += newValue - oldValue;
+
+ HTTPManager.Logger.Information("HTTP2Stream", string.Format("[{0}] Remote Setting's Initial Window Updated from {1:N0} to {2:N0}, diff: {3:N0}, new remoteWindow: {4:N0}, total data sent: {5:N0}", this.Id, oldValue, newValue, newValue - oldValue, this.remoteWindow, this.sentData), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ break;
+ }
+ }
+
+ private static void FinishRequest(HTTP2Stream stream, FramesAsStreamView dataStream)
+ {
+ if (dataStream != null)
+ {
+ try
+ {
+ stream.response.AddData(dataStream);
+ }
+ finally
+ {
+ dataStream.Close();
+ }
+ }
+
+ stream.AssignedRequest.Timing.Add(TimingEventNames.Response_Received);
+
+ bool resendRequest;
+ HTTPConnectionStates proposedConnectionStates; // ignored
+ KeepAliveHeader keepAliveHeader = null; // ignored
+
+ ConnectionHelper.HandleResponse("HTTP2Stream", stream.AssignedRequest, out resendRequest, out proposedConnectionStates, ref keepAliveHeader, stream.Context, stream.AssignedRequest.Context);
+
+ if (resendRequest && !stream.AssignedRequest.IsCancellationRequested)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(stream.AssignedRequest, RequestEvents.Resend));
+ else if (stream.AssignedRequest.State == HTTPRequestStates.Processing && !stream.AssignedRequest.IsCancellationRequested)
+ stream.AssignedRequest.State = HTTPRequestStates.Finished;
+ else
+ {
+ // Already set in HTTPRequest's Abort().
+ //if (stream.AssignedRequest.State == HTTPRequestStates.Processing && stream.AssignedRequest.IsCancellationRequested)
+ // stream.AssignedRequest.State = stream.AssignedRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+ }
+ }
+
+ public void Removed()
+ {
+ if (this.uploadStreamInfo.Stream != null)
+ {
+ this.uploadStreamInfo.Stream.Dispose();
+ this.uploadStreamInfo = new HTTPRequest.UploadStreamInfo();
+ }
+
+ // After receiving a RST_STREAM on a stream, the receiver MUST NOT send additional frames for that stream, with the exception of PRIORITY.
+ this.outgoing.Clear();
+
+ // https://github.com/Benedicht/BestHTTP-Issues/issues/77
+ // Unsubscribe from OnSettingChangedEvent to remove reference to this instance.
+ this.settings.RemoteSettings.OnSettingChangedEvent -= OnRemoteSettingChanged;
+
+ HTTPManager.Logger.Information("HTTP2Stream", "Stream removed: " + this.Id.ToString(), this.Context, this.AssignedRequest.Context, this.parent.Context);
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d0ac4622823f028e8cefabdfc28ec630026f98be
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HTTP2Stream.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: cc308fd8e0bd56844b6a3511b489602c
+timeCreated: 1571210042
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9efa32ab8a6fa8e6ab705418e0c7f68ec6b0127e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs
@@ -0,0 +1,198 @@
+#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> DynamicTable = new List>();
+ private HTTP2SettingsRegistry settingsRegistry;
+
+ public HeaderTable(HTTP2SettingsRegistry registry)
+ {
+ this.settingsRegistry = registry;
+ this.MaxDynamicTableSize = this.settingsRegistry[HTTP2Settings.HEADER_TABLE_SIZE];
+ }
+
+ public KeyValuePair 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)(StaticTable.Length + i), (UInt32)(StaticTable.Length + i));
+ }
+
+ KeyValuePair bestMatch = new KeyValuePair(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)i, (UInt32)i);
+ else
+ bestMatch = new KeyValuePair((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 GetHeader(UInt32 index)
+ {
+ if (index < StaticTable.Length)
+ return new KeyValuePair(StaticTable[index],
+ index < StaticTableValues.Length ? StaticTableValues[index] : null);
+
+ return this.DynamicTable[(int)(index - StaticTable.Length)];
+ }
+
+ public void Add(KeyValuePair 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 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 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
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..eda16dedd47a5fba9074f2b989c9bfc6b6d36bc3
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HeaderTable.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 5b73749356303be43b6d9debd22d7937
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8e875ae5436414ed3aec9f980027ac15732af5cb
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs
@@ -0,0 +1,917 @@
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Connections.HTTP2
+{
+ static class HuffmanEncoder
+ {
+ public const UInt16 EOS = 256;
+
+ public struct TableEntry
+ {
+ public UInt32 Code;
+ public byte Bits;
+
+ ///
+ /// It must return 0 or 1 at bit index. Indexing will be relative to the Bits representing the current code. Idx grows from left to right. Idx must be between [1..Bits].
+ ///
+ public byte GetBitAtIdx(byte idx)
+ {
+ return (byte)((this.Code >> (this.Bits - idx)) & 1);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[TableEntry Code: 0x{0:X}, Bits: {1}]", this.Code, this.Bits);
+ }
+ }
+
+ public struct TreeNode
+ {
+ public UInt16 NextZeroIdx;
+ public UInt16 NextOneIdx;
+
+ public UInt16 Value;
+
+ public override string ToString()
+ {
+ return string.Format("[TreeNode Value: {0}, NextZeroIdx: {1}, NextOneIdx: {2}]",
+ this.Value, this.NextZeroIdx, this.NextOneIdx);
+ }
+ }
+
+ static TableEntry[] StaticTable = new TableEntry[257]
+ {
+ new TableEntry{ Code = 0x1ff8 , Bits = 13 },
+ new TableEntry{ Code = 0x7fffd8 , Bits = 23 },
+ new TableEntry{ Code = 0xfffffe2, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe3, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe4, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe5, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe6, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe7, Bits = 28 },
+ new TableEntry{ Code = 0xfffffe8, Bits = 28 },
+ new TableEntry{ Code = 0xffffea, Bits = 24 },
+ new TableEntry{ Code = 0x3ffffffc , Bits = 30 },
+ new TableEntry{ Code = 0xfffffe9 , Bits = 28 },
+ new TableEntry{ Code = 0xfffffea , Bits = 28 },
+ new TableEntry{ Code = 0x3ffffffd , Bits = 30 },
+ new TableEntry{ Code = 0xfffffeb , Bits = 28 },
+ new TableEntry{ Code = 0xfffffec , Bits = 28 },
+ new TableEntry{ Code = 0xfffffed , Bits = 28 },
+ new TableEntry{ Code = 0xfffffee , Bits = 28 },
+ new TableEntry{ Code = 0xfffffef , Bits = 28 },
+ new TableEntry{ Code = 0xffffff0 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff1 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff2 , Bits = 28 },
+ new TableEntry{ Code = 0x3ffffffe, Bits = 30 },
+ new TableEntry{ Code = 0xffffff3 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff4 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff5 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff6 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff7 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff8 , Bits = 28 },
+ new TableEntry{ Code = 0xffffff9 , Bits = 28 },
+ new TableEntry{ Code = 0xffffffa , Bits = 28 },
+ new TableEntry{ Code = 0xffffffb , Bits = 28 },
+ new TableEntry{ Code = 0x14 , Bits = 6 },
+ new TableEntry{ Code = 0x3f8, Bits = 10 },
+ new TableEntry{ Code = 0x3f9, Bits = 10 },
+ new TableEntry{ Code = 0xffa, Bits = 12 },
+ new TableEntry{ Code = 0x1ff9 , Bits = 13 },
+ new TableEntry{ Code = 0x15 , Bits = 6 },
+ new TableEntry{ Code = 0xf8 , Bits = 8 },
+ new TableEntry{ Code = 0x7fa, Bits = 11 },
+ new TableEntry{ Code = 0x3fa, Bits = 10 },
+ new TableEntry{ Code = 0x3fb, Bits = 10 },
+ new TableEntry{ Code = 0xf9 , Bits = 8 },
+ new TableEntry{ Code = 0x7fb, Bits = 11 },
+ new TableEntry{ Code = 0xfa , Bits = 8 },
+ new TableEntry{ Code = 0x16 , Bits = 6 },
+ new TableEntry{ Code = 0x17 , Bits = 6 },
+ new TableEntry{ Code = 0x18 , Bits = 6 },
+ new TableEntry{ Code = 0x0 , Bits = 5 },
+ new TableEntry{ Code = 0x1 , Bits = 5 },
+ new TableEntry{ Code = 0x2 , Bits = 5 },
+ new TableEntry{ Code = 0x19 , Bits = 6 },
+ new TableEntry{ Code = 0x1a , Bits = 6 },
+ new TableEntry{ Code = 0x1b , Bits = 6 },
+ new TableEntry{ Code = 0x1c , Bits = 6 },
+ new TableEntry{ Code = 0x1d , Bits = 6 },
+ new TableEntry{ Code = 0x1e , Bits = 6 },
+ new TableEntry{ Code = 0x1f , Bits = 6 },
+ new TableEntry{ Code = 0x5c , Bits = 7 },
+ new TableEntry{ Code = 0xfb , Bits = 8 },
+ new TableEntry{ Code = 0x7ffc , Bits = 15 },
+ new TableEntry{ Code = 0x20 , Bits = 6 },
+ new TableEntry{ Code = 0xffb, Bits = 12 },
+ new TableEntry{ Code = 0x3fc, Bits = 10 },
+ new TableEntry{ Code = 0x1ffa , Bits = 13 },
+ new TableEntry{ Code = 0x21, Bits = 6 },
+ new TableEntry{ Code = 0x5d, Bits = 7 },
+ new TableEntry{ Code = 0x5e, Bits = 7 },
+ new TableEntry{ Code = 0x5f, Bits = 7 },
+ new TableEntry{ Code = 0x60, Bits = 7 },
+ new TableEntry{ Code = 0x61, Bits = 7 },
+ new TableEntry{ Code = 0x62, Bits = 7 },
+ new TableEntry{ Code = 0x63, Bits = 7 },
+ new TableEntry{ Code = 0x64, Bits = 7 },
+ new TableEntry{ Code = 0x65, Bits = 7 },
+ new TableEntry{ Code = 0x66, Bits = 7 },
+ new TableEntry{ Code = 0x67, Bits = 7 },
+ new TableEntry{ Code = 0x68, Bits = 7 },
+ new TableEntry{ Code = 0x69, Bits = 7 },
+ new TableEntry{ Code = 0x6a, Bits = 7 },
+ new TableEntry{ Code = 0x6b, Bits = 7 },
+ new TableEntry{ Code = 0x6c, Bits = 7 },
+ new TableEntry{ Code = 0x6d, Bits = 7 },
+ new TableEntry{ Code = 0x6e, Bits = 7 },
+ new TableEntry{ Code = 0x6f, Bits = 7 },
+ new TableEntry{ Code = 0x70, Bits = 7 },
+ new TableEntry{ Code = 0x71, Bits = 7 },
+ new TableEntry{ Code = 0x72, Bits = 7 },
+ new TableEntry{ Code = 0xfc, Bits = 8 },
+ new TableEntry{ Code = 0x73, Bits = 7 },
+ new TableEntry{ Code = 0xfd, Bits = 8 },
+ new TableEntry{ Code = 0x1ffb, Bits = 13 },
+ new TableEntry{ Code = 0x7fff0, Bits = 19 },
+ new TableEntry{ Code = 0x1ffc, Bits = 13 },
+ new TableEntry{ Code = 0x3ffc, Bits = 14 },
+ new TableEntry{ Code = 0x22, Bits = 6 },
+ new TableEntry{ Code = 0x7ffd, Bits = 15 },
+ new TableEntry{ Code = 0x3, Bits = 5 },
+ new TableEntry{ Code = 0x23, Bits = 6 },
+ new TableEntry{ Code = 0x4, Bits = 5 },
+ new TableEntry{ Code = 0x24, Bits = 6 },
+ new TableEntry{ Code = 0x5, Bits = 5 },
+ new TableEntry{ Code = 0x25, Bits = 6 },
+ new TableEntry{ Code = 0x26, Bits = 6 },
+ new TableEntry{ Code = 0x27, Bits = 6 },
+ new TableEntry{ Code = 0x6 , Bits = 5 },
+ new TableEntry{ Code = 0x74, Bits = 7 },
+ new TableEntry{ Code = 0x75, Bits = 7 },
+ new TableEntry{ Code = 0x28, Bits = 6 },
+ new TableEntry{ Code = 0x29, Bits = 6 },
+ new TableEntry{ Code = 0x2a, Bits = 6 },
+ new TableEntry{ Code = 0x7 , Bits = 5 },
+ new TableEntry{ Code = 0x2b, Bits = 6 },
+ new TableEntry{ Code = 0x76, Bits = 7 },
+ new TableEntry{ Code = 0x2c, Bits = 6 },
+ new TableEntry{ Code = 0x8 , Bits = 5 },
+ new TableEntry{ Code = 0x9 , Bits = 5 },
+ new TableEntry{ Code = 0x2d, Bits = 6 },
+ new TableEntry{ Code = 0x77, Bits = 7 },
+ new TableEntry{ Code = 0x78, Bits = 7 },
+ new TableEntry{ Code = 0x79, Bits = 7 },
+ new TableEntry{ Code = 0x7a, Bits = 7 },
+ new TableEntry{ Code = 0x7b, Bits = 7 },
+ new TableEntry{ Code = 0x7ffe, Bits = 15 },
+ new TableEntry{ Code = 0x7fc, Bits = 11 },
+ new TableEntry{ Code = 0x3ffd, Bits = 14 },
+ new TableEntry{ Code = 0x1ffd, Bits = 13 },
+ new TableEntry{ Code = 0xffffffc, Bits = 28 },
+ new TableEntry{ Code = 0xfffe6 , Bits = 20 },
+ new TableEntry{ Code = 0x3fffd2, Bits = 22 },
+ new TableEntry{ Code = 0xfffe7 , Bits = 20 },
+ new TableEntry{ Code = 0xfffe8 , Bits = 20 },
+ new TableEntry{ Code = 0x3fffd3, Bits = 22 },
+ new TableEntry{ Code = 0x3fffd4, Bits = 22 },
+ new TableEntry{ Code = 0x3fffd5, Bits = 22 },
+ new TableEntry{ Code = 0x7fffd9, Bits = 23 },
+ new TableEntry{ Code = 0x3fffd6, Bits = 22 },
+ new TableEntry{ Code = 0x7fffda, Bits = 23 },
+ new TableEntry{ Code = 0x7fffdb, Bits = 23 },
+ new TableEntry{ Code = 0x7fffdc, Bits = 23 },
+ new TableEntry{ Code = 0x7fffdd, Bits = 23 },
+ new TableEntry{ Code = 0x7fffde, Bits = 23 },
+ new TableEntry{ Code = 0xffffeb, Bits = 24 },
+ new TableEntry{ Code = 0x7fffdf, Bits = 23 },
+ new TableEntry{ Code = 0xffffec, Bits = 24 },
+ new TableEntry{ Code = 0xffffed, Bits = 24 },
+ new TableEntry{ Code = 0x3fffd7, Bits = 22 },
+ new TableEntry{ Code = 0x7fffe0, Bits = 23 },
+ new TableEntry{ Code = 0xffffee, Bits = 24 },
+ new TableEntry{ Code = 0x7fffe1, Bits = 23 },
+ new TableEntry{ Code = 0x7fffe2, Bits = 23 },
+ new TableEntry{ Code = 0x7fffe3, Bits = 23 },
+ new TableEntry{ Code = 0x7fffe4, Bits = 23 },
+ new TableEntry{ Code = 0x1fffdc, Bits = 21 },
+ new TableEntry{ Code = 0x3fffd8, Bits = 22 },
+ new TableEntry{ Code = 0x7fffe5, Bits = 23 },
+ new TableEntry{ Code = 0x3fffd9, Bits = 22 },
+ new TableEntry{ Code = 0x7fffe6, Bits = 23 },
+ new TableEntry{ Code = 0x7fffe7, Bits = 23 },
+ new TableEntry{ Code = 0xffffef, Bits = 24 },
+ new TableEntry{ Code = 0x3fffda, Bits = 22 },
+ new TableEntry{ Code = 0x1fffdd, Bits = 21 },
+ new TableEntry{ Code = 0xfffe9 , Bits = 20 },
+ new TableEntry{ Code = 0x3fffdb, Bits = 22 },
+ new TableEntry{ Code = 0x3fffdc, Bits = 22 },
+ new TableEntry{ Code = 0x7fffe8, Bits = 23 },
+ new TableEntry{ Code = 0x7fffe9, Bits = 23 },
+ new TableEntry{ Code = 0x1fffde, Bits = 21 },
+ new TableEntry{ Code = 0x7fffea, Bits = 23 },
+ new TableEntry{ Code = 0x3fffdd, Bits = 22 },
+ new TableEntry{ Code = 0x3fffde, Bits = 22 },
+ new TableEntry{ Code = 0xfffff0, Bits = 24 },
+ new TableEntry{ Code = 0x1fffdf, Bits = 21 },
+ new TableEntry{ Code = 0x3fffdf, Bits = 22 },
+ new TableEntry{ Code = 0x7fffeb, Bits = 23 },
+ new TableEntry{ Code = 0x7fffec, Bits = 23 },
+ new TableEntry{ Code = 0x1fffe0, Bits = 21 },
+ new TableEntry{ Code = 0x1fffe1, Bits = 21 },
+ new TableEntry{ Code = 0x3fffe0, Bits = 22 },
+ new TableEntry{ Code = 0x1fffe2, Bits = 21 },
+ new TableEntry{ Code = 0x7fffed, Bits = 23 },
+ new TableEntry{ Code = 0x3fffe1, Bits = 22 },
+ new TableEntry{ Code = 0x7fffee, Bits = 23 },
+ new TableEntry{ Code = 0x7fffef, Bits = 23 },
+ new TableEntry{ Code = 0xfffea , Bits = 20 },
+ new TableEntry{ Code = 0x3fffe2, Bits = 22 },
+ new TableEntry{ Code = 0x3fffe3, Bits = 22 },
+ new TableEntry{ Code = 0x3fffe4, Bits = 22 },
+ new TableEntry{ Code = 0x7ffff0, Bits = 23 },
+ new TableEntry{ Code = 0x3fffe5, Bits = 22 },
+ new TableEntry{ Code = 0x3fffe6, Bits = 22 },
+ new TableEntry{ Code = 0x7ffff1, Bits = 23 },
+ new TableEntry{ Code = 0x3ffffe0, Bits = 26 },
+ new TableEntry{ Code = 0x3ffffe1, Bits = 26 },
+ new TableEntry{ Code = 0xfffeb , Bits = 20 },
+ new TableEntry{ Code = 0x7fff1 , Bits = 19 },
+ new TableEntry{ Code = 0x3fffe7, Bits = 22 },
+ new TableEntry{ Code = 0x7ffff2, Bits = 23 },
+ new TableEntry{ Code = 0x3fffe8, Bits = 22 },
+ new TableEntry{ Code = 0x1ffffec, Bits = 25 },
+ new TableEntry{ Code = 0x3ffffe2, Bits = 26 },
+ new TableEntry{ Code = 0x3ffffe3, Bits = 26 },
+ new TableEntry{ Code = 0x3ffffe4, Bits = 26 },
+ new TableEntry{ Code = 0x7ffffde, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffdf, Bits = 27 },
+ new TableEntry{ Code = 0x3ffffe5, Bits = 26 },
+ new TableEntry{ Code = 0xfffff1 , Bits = 24 },
+ new TableEntry{ Code = 0x1ffffed, Bits = 25 },
+ new TableEntry{ Code = 0x7fff2 , Bits = 19 },
+ new TableEntry{ Code = 0x1fffe3 , Bits = 21 },
+ new TableEntry{ Code = 0x3ffffe6, Bits = 26 },
+ new TableEntry{ Code = 0x7ffffe0, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffe1, Bits = 27 },
+ new TableEntry{ Code = 0x3ffffe7, Bits = 26 },
+ new TableEntry{ Code = 0x7ffffe2, Bits = 27 },
+ new TableEntry{ Code = 0xfffff2 , Bits = 24 },
+ new TableEntry{ Code = 0x1fffe4 , Bits = 21 },
+ new TableEntry{ Code = 0x1fffe5 , Bits = 21 },
+ new TableEntry{ Code = 0x3ffffe8, Bits = 26 },
+ new TableEntry{ Code = 0x3ffffe9, Bits = 26 },
+ new TableEntry{ Code = 0xffffffd, Bits = 28 },
+ new TableEntry{ Code = 0x7ffffe3, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffe4, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffe5, Bits = 27 },
+ new TableEntry{ Code = 0xfffec , Bits = 20 },
+ new TableEntry{ Code = 0xfffff3, Bits = 24 },
+ new TableEntry{ Code = 0xfffed , Bits = 20 },
+ new TableEntry{ Code = 0x1fffe6, Bits = 21 },
+ new TableEntry{ Code = 0x3fffe9, Bits = 22 },
+ new TableEntry{ Code = 0x1fffe7, Bits = 21 },
+ new TableEntry{ Code = 0x1fffe8, Bits = 21 },
+ new TableEntry{ Code = 0x7ffff3, Bits = 23 },
+ new TableEntry{ Code = 0x3fffea, Bits = 22 },
+ new TableEntry{ Code = 0x3fffeb, Bits = 22 },
+ new TableEntry{ Code = 0x1ffffee, Bits = 25 },
+ new TableEntry{ Code = 0x1ffffef, Bits = 25 },
+ new TableEntry{ Code = 0xfffff4 , Bits = 24 },
+ new TableEntry{ Code = 0xfffff5 , Bits = 24 },
+ new TableEntry{ Code = 0x3ffffea, Bits = 26 },
+ new TableEntry{ Code = 0x7ffff4 , Bits = 23 },
+ new TableEntry{ Code = 0x3ffffeb, Bits = 26 },
+ new TableEntry{ Code = 0x7ffffe6, Bits = 27 },
+ new TableEntry{ Code = 0x3ffffec, Bits = 26 },
+ new TableEntry{ Code = 0x3ffffed, Bits = 26 },
+ new TableEntry{ Code = 0x7ffffe7, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffe8, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffe9, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffea, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffeb, Bits = 27 },
+ new TableEntry{ Code = 0xffffffe, Bits = 28 },
+ new TableEntry{ Code = 0x7ffffec, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffed, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffee, Bits = 27 },
+ new TableEntry{ Code = 0x7ffffef, Bits = 27 },
+ new TableEntry{ Code = 0x7fffff0, Bits = 27 },
+ new TableEntry{ Code = 0x3ffffee, Bits = 26 },
+ new TableEntry{ Code = 0x3fffffff, Bits = 30 }
+ };
+ //static List entries = new List();
+
+ static TreeNode[] HuffmanTree = new TreeNode[]
+ {
+ new TreeNode { Value = 0, NextZeroIdx = 98, NextOneIdx = 1 },
+ new TreeNode { Value = 0, NextZeroIdx = 151, NextOneIdx = 2 },
+ new TreeNode { Value = 0, NextZeroIdx = 173, NextOneIdx = 3 },
+ new TreeNode { Value = 0, NextZeroIdx = 204, NextOneIdx = 4 },
+ new TreeNode { Value = 0, NextZeroIdx = 263, NextOneIdx = 5 },
+ new TreeNode { Value = 0, NextZeroIdx = 113, NextOneIdx = 6 },
+ new TreeNode { Value = 0, NextZeroIdx = 211, NextOneIdx = 7 },
+ new TreeNode { Value = 0, NextZeroIdx = 104, NextOneIdx = 8 },
+ new TreeNode { Value = 0, NextZeroIdx = 116, NextOneIdx = 9 },
+ new TreeNode { Value = 0, NextZeroIdx = 108, NextOneIdx = 10 },
+ new TreeNode { Value = 0, NextZeroIdx = 11, NextOneIdx = 14 },
+ new TreeNode { Value = 0, NextZeroIdx = 12, NextOneIdx = 166 },
+ new TreeNode { Value = 0, NextZeroIdx = 13, NextOneIdx = 111 },
+ new TreeNode { Value = 0, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 220, NextOneIdx = 15 },
+ new TreeNode { Value = 0, NextZeroIdx = 222, NextOneIdx = 16 },
+ new TreeNode { Value = 0, NextZeroIdx = 158, NextOneIdx = 17 },
+ new TreeNode { Value = 0, NextZeroIdx = 270, NextOneIdx = 18 },
+ new TreeNode { Value = 0, NextZeroIdx = 216, NextOneIdx = 19 },
+ new TreeNode { Value = 0, NextZeroIdx = 279, NextOneIdx = 20 },
+ new TreeNode { Value = 0, NextZeroIdx = 21, NextOneIdx = 27 },
+ new TreeNode { Value = 0, NextZeroIdx = 377, NextOneIdx = 22 },
+ new TreeNode { Value = 0, NextZeroIdx = 414, NextOneIdx = 23 },
+ new TreeNode { Value = 0, NextZeroIdx = 24, NextOneIdx = 301 },
+ new TreeNode { Value = 0, NextZeroIdx = 25, NextOneIdx = 298 },
+ new TreeNode { Value = 0, NextZeroIdx = 26, NextOneIdx = 295 },
+ new TreeNode { Value = 1, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 314, NextOneIdx = 28 },
+ new TreeNode { Value = 0, NextZeroIdx = 50, NextOneIdx = 29 },
+ new TreeNode { Value = 0, NextZeroIdx = 362, NextOneIdx = 30 },
+ new TreeNode { Value = 0, NextZeroIdx = 403, NextOneIdx = 31 },
+ new TreeNode { Value = 0, NextZeroIdx = 440, NextOneIdx = 32 },
+ new TreeNode { Value = 0, NextZeroIdx = 33, NextOneIdx = 55 },
+ new TreeNode { Value = 0, NextZeroIdx = 34, NextOneIdx = 46 },
+ new TreeNode { Value = 0, NextZeroIdx = 35, NextOneIdx = 39 },
+ new TreeNode { Value = 0, NextZeroIdx = 510, NextOneIdx = 36 },
+ new TreeNode { Value = 0, NextZeroIdx = 37, NextOneIdx = 38 },
+ new TreeNode { Value = 2, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 3, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 40, NextOneIdx = 43 },
+ new TreeNode { Value = 0, NextZeroIdx = 41, NextOneIdx = 42 },
+ new TreeNode { Value = 4, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 5, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 44, NextOneIdx = 45 },
+ new TreeNode { Value = 6, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 7, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 47, NextOneIdx = 67 },
+ new TreeNode { Value = 0, NextZeroIdx = 48, NextOneIdx = 63 },
+ new TreeNode { Value = 0, NextZeroIdx = 49, NextOneIdx = 62 },
+ new TreeNode { Value = 8, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 396, NextOneIdx = 51 },
+ new TreeNode { Value = 0, NextZeroIdx = 52, NextOneIdx = 309 },
+ new TreeNode { Value = 0, NextZeroIdx = 486, NextOneIdx = 53 },
+ new TreeNode { Value = 0, NextZeroIdx = 54, NextOneIdx = 307 },
+ new TreeNode { Value = 9, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 74, NextOneIdx = 56 },
+ new TreeNode { Value = 0, NextZeroIdx = 91, NextOneIdx = 57 },
+ new TreeNode { Value = 0, NextZeroIdx = 274, NextOneIdx = 58 },
+ new TreeNode { Value = 0, NextZeroIdx = 502, NextOneIdx = 59 },
+ new TreeNode { Value = 0, NextZeroIdx = 60, NextOneIdx = 81 },
+ new TreeNode { Value = 0, NextZeroIdx = 61, NextOneIdx = 65 },
+ new TreeNode { Value = 10, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 11, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 64, NextOneIdx = 66 },
+ new TreeNode { Value = 12, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 13, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 14, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 68, NextOneIdx = 71 },
+ new TreeNode { Value = 0, NextZeroIdx = 69, NextOneIdx = 70 },
+ new TreeNode { Value = 15, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 16, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 72, NextOneIdx = 73 },
+ new TreeNode { Value = 17, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 18, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 75, NextOneIdx = 84 },
+ new TreeNode { Value = 0, NextZeroIdx = 76, NextOneIdx = 79 },
+ new TreeNode { Value = 0, NextZeroIdx = 77, NextOneIdx = 78 },
+ new TreeNode { Value = 19, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 20, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 80, NextOneIdx = 83 },
+ new TreeNode { Value = 21, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 82, NextOneIdx = 512 },
+ new TreeNode { Value = 22, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 23, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 85, NextOneIdx = 88 },
+ new TreeNode { Value = 0, NextZeroIdx = 86, NextOneIdx = 87 },
+ new TreeNode { Value = 24, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 25, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 89, NextOneIdx = 90 },
+ new TreeNode { Value = 26, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 27, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 92, NextOneIdx = 95 },
+ new TreeNode { Value = 0, NextZeroIdx = 93, NextOneIdx = 94 },
+ new TreeNode { Value = 28, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 29, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 96, NextOneIdx = 97 },
+ new TreeNode { Value = 30, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 31, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 133, NextOneIdx = 99 },
+ new TreeNode { Value = 0, NextZeroIdx = 100, NextOneIdx = 129 },
+ new TreeNode { Value = 0, NextZeroIdx = 258, NextOneIdx = 101 },
+ new TreeNode { Value = 0, NextZeroIdx = 102, NextOneIdx = 126 },
+ new TreeNode { Value = 0, NextZeroIdx = 103, NextOneIdx = 112 },
+ new TreeNode { Value = 32, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 105, NextOneIdx = 119 },
+ new TreeNode { Value = 0, NextZeroIdx = 106, NextOneIdx = 107 },
+ new TreeNode { Value = 33, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 34, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 271, NextOneIdx = 109 },
+ new TreeNode { Value = 0, NextZeroIdx = 110, NextOneIdx = 164 },
+ new TreeNode { Value = 35, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 36, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 37, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 114, NextOneIdx = 124 },
+ new TreeNode { Value = 0, NextZeroIdx = 115, NextOneIdx = 122 },
+ new TreeNode { Value = 38, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 165, NextOneIdx = 117 },
+ new TreeNode { Value = 0, NextZeroIdx = 118, NextOneIdx = 123 },
+ new TreeNode { Value = 39, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 120, NextOneIdx = 121 },
+ new TreeNode { Value = 40, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 41, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 42, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 43, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 125, NextOneIdx = 157 },
+ new TreeNode { Value = 44, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 127, NextOneIdx = 128 },
+ new TreeNode { Value = 45, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 46, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 130, NextOneIdx = 144 },
+ new TreeNode { Value = 0, NextZeroIdx = 131, NextOneIdx = 141 },
+ new TreeNode { Value = 0, NextZeroIdx = 132, NextOneIdx = 140 },
+ new TreeNode { Value = 47, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 134, NextOneIdx = 229 },
+ new TreeNode { Value = 0, NextZeroIdx = 135, NextOneIdx = 138 },
+ new TreeNode { Value = 0, NextZeroIdx = 136, NextOneIdx = 137 },
+ new TreeNode { Value = 48, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 49, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 139, NextOneIdx = 227 },
+ new TreeNode { Value = 50, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 51, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 142, NextOneIdx = 143 },
+ new TreeNode { Value = 52, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 53, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 145, NextOneIdx = 148 },
+ new TreeNode { Value = 0, NextZeroIdx = 146, NextOneIdx = 147 },
+ new TreeNode { Value = 54, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 55, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 149, NextOneIdx = 150 },
+ new TreeNode { Value = 56, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 57, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 160, NextOneIdx = 152 },
+ new TreeNode { Value = 0, NextZeroIdx = 246, NextOneIdx = 153 },
+ new TreeNode { Value = 0, NextZeroIdx = 256, NextOneIdx = 154 },
+ new TreeNode { Value = 0, NextZeroIdx = 155, NextOneIdx = 170 },
+ new TreeNode { Value = 0, NextZeroIdx = 156, NextOneIdx = 169 },
+ new TreeNode { Value = 58, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 59, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 159, NextOneIdx = 226 },
+ new TreeNode { Value = 60, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 161, NextOneIdx = 232 },
+ new TreeNode { Value = 0, NextZeroIdx = 162, NextOneIdx = 224 },
+ new TreeNode { Value = 0, NextZeroIdx = 163, NextOneIdx = 168 },
+ new TreeNode { Value = 61, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 62, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 63, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 167, NextOneIdx = 215 },
+ new TreeNode { Value = 64, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 65, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 66, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 171, NextOneIdx = 172 },
+ new TreeNode { Value = 67, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 68, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 174, NextOneIdx = 189 },
+ new TreeNode { Value = 0, NextZeroIdx = 175, NextOneIdx = 182 },
+ new TreeNode { Value = 0, NextZeroIdx = 176, NextOneIdx = 179 },
+ new TreeNode { Value = 0, NextZeroIdx = 177, NextOneIdx = 178 },
+ new TreeNode { Value = 69, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 70, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 180, NextOneIdx = 181 },
+ new TreeNode { Value = 71, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 72, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 183, NextOneIdx = 186 },
+ new TreeNode { Value = 0, NextZeroIdx = 184, NextOneIdx = 185 },
+ new TreeNode { Value = 73, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 74, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 187, NextOneIdx = 188 },
+ new TreeNode { Value = 75, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 76, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 190, NextOneIdx = 197 },
+ new TreeNode { Value = 0, NextZeroIdx = 191, NextOneIdx = 194 },
+ new TreeNode { Value = 0, NextZeroIdx = 192, NextOneIdx = 193 },
+ new TreeNode { Value = 77, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 78, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 195, NextOneIdx = 196 },
+ new TreeNode { Value = 79, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 80, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 198, NextOneIdx = 201 },
+ new TreeNode { Value = 0, NextZeroIdx = 199, NextOneIdx = 200 },
+ new TreeNode { Value = 81, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 82, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 202, NextOneIdx = 203 },
+ new TreeNode { Value = 83, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 84, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 205, NextOneIdx = 242 },
+ new TreeNode { Value = 0, NextZeroIdx = 206, NextOneIdx = 209 },
+ new TreeNode { Value = 0, NextZeroIdx = 207, NextOneIdx = 208 },
+ new TreeNode { Value = 85, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 86, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 210, NextOneIdx = 213 },
+ new TreeNode { Value = 87, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 212, NextOneIdx = 214 },
+ new TreeNode { Value = 88, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 89, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 90, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 91, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 217, NextOneIdx = 286 },
+ new TreeNode { Value = 0, NextZeroIdx = 218, NextOneIdx = 276 },
+ new TreeNode { Value = 0, NextZeroIdx = 219, NextOneIdx = 410 },
+ new TreeNode { Value = 92, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 221, NextOneIdx = 273 },
+ new TreeNode { Value = 93, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 223, NextOneIdx = 272 },
+ new TreeNode { Value = 94, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 225, NextOneIdx = 228 },
+ new TreeNode { Value = 95, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 96, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 97, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 98, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 230, NextOneIdx = 240 },
+ new TreeNode { Value = 0, NextZeroIdx = 231, NextOneIdx = 235 },
+ new TreeNode { Value = 99, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 233, NextOneIdx = 237 },
+ new TreeNode { Value = 0, NextZeroIdx = 234, NextOneIdx = 236 },
+ new TreeNode { Value = 100, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 101, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 102, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 238, NextOneIdx = 239 },
+ new TreeNode { Value = 103, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 104, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 241, NextOneIdx = 252 },
+ new TreeNode { Value = 105, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 243, NextOneIdx = 254 },
+ new TreeNode { Value = 0, NextZeroIdx = 244, NextOneIdx = 245 },
+ new TreeNode { Value = 106, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 107, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 247, NextOneIdx = 250 },
+ new TreeNode { Value = 0, NextZeroIdx = 248, NextOneIdx = 249 },
+ new TreeNode { Value = 108, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 109, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 251, NextOneIdx = 253 },
+ new TreeNode { Value = 110, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 111, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 112, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 255, NextOneIdx = 262 },
+ new TreeNode { Value = 113, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 257, NextOneIdx = 261 },
+ new TreeNode { Value = 114, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 259, NextOneIdx = 260 },
+ new TreeNode { Value = 115, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 116, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 117, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 118, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 264, NextOneIdx = 267 },
+ new TreeNode { Value = 0, NextZeroIdx = 265, NextOneIdx = 266 },
+ new TreeNode { Value = 119, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 120, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 268, NextOneIdx = 269 },
+ new TreeNode { Value = 121, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 122, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 123, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 124, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 125, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 126, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 275, NextOneIdx = 459 },
+ new TreeNode { Value = 127, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 436, NextOneIdx = 277 },
+ new TreeNode { Value = 0, NextZeroIdx = 278, NextOneIdx = 285 },
+ new TreeNode { Value = 128, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 372, NextOneIdx = 280 },
+ new TreeNode { Value = 0, NextZeroIdx = 281, NextOneIdx = 332 },
+ new TreeNode { Value = 0, NextZeroIdx = 282, NextOneIdx = 291 },
+ new TreeNode { Value = 0, NextZeroIdx = 473, NextOneIdx = 283 },
+ new TreeNode { Value = 0, NextZeroIdx = 284, NextOneIdx = 290 },
+ new TreeNode { Value = 129, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 130, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 287, NextOneIdx = 328 },
+ new TreeNode { Value = 0, NextZeroIdx = 288, NextOneIdx = 388 },
+ new TreeNode { Value = 0, NextZeroIdx = 289, NextOneIdx = 345 },
+ new TreeNode { Value = 131, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 132, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 292, NextOneIdx = 296 },
+ new TreeNode { Value = 0, NextZeroIdx = 293, NextOneIdx = 294 },
+ new TreeNode { Value = 133, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 134, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 135, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 297, NextOneIdx = 313 },
+ new TreeNode { Value = 136, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 299, NextOneIdx = 300 },
+ new TreeNode { Value = 137, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 138, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 302, NextOneIdx = 305 },
+ new TreeNode { Value = 0, NextZeroIdx = 303, NextOneIdx = 304 },
+ new TreeNode { Value = 139, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 140, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 306, NextOneIdx = 308 },
+ new TreeNode { Value = 141, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 142, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 143, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 310, NextOneIdx = 319 },
+ new TreeNode { Value = 0, NextZeroIdx = 311, NextOneIdx = 312 },
+ new TreeNode { Value = 144, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 145, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 146, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 315, NextOneIdx = 350 },
+ new TreeNode { Value = 0, NextZeroIdx = 316, NextOneIdx = 325 },
+ new TreeNode { Value = 0, NextZeroIdx = 317, NextOneIdx = 322 },
+ new TreeNode { Value = 0, NextZeroIdx = 318, NextOneIdx = 321 },
+ new TreeNode { Value = 147, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 320, NextOneIdx = 341 },
+ new TreeNode { Value = 148, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 149, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 323, NextOneIdx = 324 },
+ new TreeNode { Value = 150, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 151, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 326, NextOneIdx = 338 },
+ new TreeNode { Value = 0, NextZeroIdx = 327, NextOneIdx = 336 },
+ new TreeNode { Value = 152, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 465, NextOneIdx = 329 },
+ new TreeNode { Value = 0, NextZeroIdx = 330, NextOneIdx = 355 },
+ new TreeNode { Value = 0, NextZeroIdx = 331, NextOneIdx = 344 },
+ new TreeNode { Value = 153, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 333, NextOneIdx = 347 },
+ new TreeNode { Value = 0, NextZeroIdx = 334, NextOneIdx = 342 },
+ new TreeNode { Value = 0, NextZeroIdx = 335, NextOneIdx = 337 },
+ new TreeNode { Value = 154, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 155, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 156, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 339, NextOneIdx = 340 },
+ new TreeNode { Value = 157, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 158, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 159, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 343, NextOneIdx = 346 },
+ new TreeNode { Value = 160, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 161, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 162, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 163, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 348, NextOneIdx = 360 },
+ new TreeNode { Value = 0, NextZeroIdx = 349, NextOneIdx = 359 },
+ new TreeNode { Value = 164, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 351, NextOneIdx = 369 },
+ new TreeNode { Value = 0, NextZeroIdx = 352, NextOneIdx = 357 },
+ new TreeNode { Value = 0, NextZeroIdx = 353, NextOneIdx = 354 },
+ new TreeNode { Value = 165, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 166, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 356, NextOneIdx = 366 },
+ new TreeNode { Value = 167, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 358, NextOneIdx = 368 },
+ new TreeNode { Value = 168, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 169, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 361, NextOneIdx = 367 },
+ new TreeNode { Value = 170, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 363, NextOneIdx = 417 },
+ new TreeNode { Value = 0, NextZeroIdx = 364, NextOneIdx = 449 },
+ new TreeNode { Value = 0, NextZeroIdx = 365, NextOneIdx = 434 },
+ new TreeNode { Value = 171, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 172, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 173, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 174, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 370, NextOneIdx = 385 },
+ new TreeNode { Value = 0, NextZeroIdx = 371, NextOneIdx = 383 },
+ new TreeNode { Value = 175, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 373, NextOneIdx = 451 },
+ new TreeNode { Value = 0, NextZeroIdx = 374, NextOneIdx = 381 },
+ new TreeNode { Value = 0, NextZeroIdx = 375, NextOneIdx = 376 },
+ new TreeNode { Value = 176, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 177, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 378, NextOneIdx = 393 },
+ new TreeNode { Value = 0, NextZeroIdx = 379, NextOneIdx = 390 },
+ new TreeNode { Value = 0, NextZeroIdx = 380, NextOneIdx = 384 },
+ new TreeNode { Value = 178, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 382, NextOneIdx = 437 },
+ new TreeNode { Value = 179, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 180, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 181, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 386, NextOneIdx = 387 },
+ new TreeNode { Value = 182, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 183, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 389, NextOneIdx = 409 },
+ new TreeNode { Value = 184, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 391, NextOneIdx = 392 },
+ new TreeNode { Value = 185, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 186, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 394, NextOneIdx = 400 },
+ new TreeNode { Value = 0, NextZeroIdx = 395, NextOneIdx = 399 },
+ new TreeNode { Value = 187, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 397, NextOneIdx = 412 },
+ new TreeNode { Value = 0, NextZeroIdx = 398, NextOneIdx = 402 },
+ new TreeNode { Value = 188, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 189, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 401, NextOneIdx = 411 },
+ new TreeNode { Value = 190, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 191, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 404, NextOneIdx = 427 },
+ new TreeNode { Value = 0, NextZeroIdx = 405, NextOneIdx = 424 },
+ new TreeNode { Value = 0, NextZeroIdx = 406, NextOneIdx = 421 },
+ new TreeNode { Value = 0, NextZeroIdx = 407, NextOneIdx = 408 },
+ new TreeNode { Value = 192, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 193, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 194, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 195, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 196, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 413, NextOneIdx = 474 },
+ new TreeNode { Value = 197, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 415, NextOneIdx = 475 },
+ new TreeNode { Value = 0, NextZeroIdx = 416, NextOneIdx = 471 },
+ new TreeNode { Value = 198, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 481, NextOneIdx = 418 },
+ new TreeNode { Value = 0, NextZeroIdx = 419, NextOneIdx = 478 },
+ new TreeNode { Value = 0, NextZeroIdx = 420, NextOneIdx = 435 },
+ new TreeNode { Value = 199, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 422, NextOneIdx = 423 },
+ new TreeNode { Value = 200, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 201, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 425, NextOneIdx = 438 },
+ new TreeNode { Value = 0, NextZeroIdx = 426, NextOneIdx = 433 },
+ new TreeNode { Value = 202, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 455, NextOneIdx = 428 },
+ new TreeNode { Value = 0, NextZeroIdx = 490, NextOneIdx = 429 },
+ new TreeNode { Value = 0, NextZeroIdx = 511, NextOneIdx = 430 },
+ new TreeNode { Value = 0, NextZeroIdx = 431, NextOneIdx = 432 },
+ new TreeNode { Value = 203, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 204, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 205, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 206, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 207, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 208, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 209, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 439, NextOneIdx = 446 },
+ new TreeNode { Value = 210, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 441, NextOneIdx = 494 },
+ new TreeNode { Value = 0, NextZeroIdx = 442, NextOneIdx = 461 },
+ new TreeNode { Value = 0, NextZeroIdx = 443, NextOneIdx = 447 },
+ new TreeNode { Value = 0, NextZeroIdx = 444, NextOneIdx = 445 },
+ new TreeNode { Value = 211, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 212, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 213, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 448, NextOneIdx = 460 },
+ new TreeNode { Value = 214, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 450, NextOneIdx = 467 },
+ new TreeNode { Value = 215, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 452, NextOneIdx = 469 },
+ new TreeNode { Value = 0, NextZeroIdx = 453, NextOneIdx = 454 },
+ new TreeNode { Value = 216, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 217, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 456, NextOneIdx = 484 },
+ new TreeNode { Value = 0, NextZeroIdx = 457, NextOneIdx = 458 },
+ new TreeNode { Value = 218, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 219, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 220, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 221, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 462, NextOneIdx = 488 },
+ new TreeNode { Value = 0, NextZeroIdx = 463, NextOneIdx = 464 },
+ new TreeNode { Value = 222, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 223, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 466, NextOneIdx = 468 },
+ new TreeNode { Value = 224, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 225, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 226, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 470, NextOneIdx = 472 },
+ new TreeNode { Value = 227, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 228, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 229, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 230, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 231, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 476, NextOneIdx = 477 },
+ new TreeNode { Value = 232, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 233, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 479, NextOneIdx = 480 },
+ new TreeNode { Value = 234, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 235, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 482, NextOneIdx = 483 },
+ new TreeNode { Value = 236, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 237, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 485, NextOneIdx = 487 },
+ new TreeNode { Value = 238, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 239, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 240, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 489, NextOneIdx = 493 },
+ new TreeNode { Value = 241, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 491, NextOneIdx = 492 },
+ new TreeNode { Value = 242, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 243, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 244, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 495, NextOneIdx = 503 },
+ new TreeNode { Value = 0, NextZeroIdx = 496, NextOneIdx = 499 },
+ new TreeNode { Value = 0, NextZeroIdx = 497, NextOneIdx = 498 },
+ new TreeNode { Value = 245, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 246, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 500, NextOneIdx = 501 },
+ new TreeNode { Value = 247, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 248, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 249, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 504, NextOneIdx = 507 },
+ new TreeNode { Value = 0, NextZeroIdx = 505, NextOneIdx = 506 },
+ new TreeNode { Value = 250, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 251, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 0, NextZeroIdx = 508, NextOneIdx = 509 },
+ new TreeNode { Value = 252, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 253, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 254, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 255, NextZeroIdx = 0, NextOneIdx = 0 },
+ new TreeNode { Value = 256, NextZeroIdx = 0, NextOneIdx = 0 }
+ };
+
+ //static HuffmanEncoder()
+ //{
+ // BuildTree();
+ //}
+ //
+ //private static void BuildTree()
+ //{
+ // // Add root
+ // entries.Add(new TreeNode());
+ //
+ // for (int i = 0; i < StaticTable.Length; ++i)
+ // {
+ // var tableEntry = StaticTable[i];
+ // var currentNode = entries[0];
+ // int currentNodeIdx = 0;
+ //
+ // for (byte bitIdx = 1; bitIdx <= tableEntry.Bits; bitIdx++)
+ // {
+ // byte bit = tableEntry.GetBitAtIdx(bitIdx);
+ //
+ // switch(bit)
+ // {
+ // case 0:
+ // if (currentNode.NextZeroIdx == 0)
+ // {
+ // currentNode.NextZeroIdx = (UInt16)entries.Count;
+ // entries[currentNodeIdx] = currentNode;
+ // entries.Add(new TreeNode());
+ // }
+ //
+ // currentNodeIdx = currentNode.NextZeroIdx;
+ // currentNode = entries[currentNodeIdx];
+ // break;
+ //
+ // case 1:
+ // if (currentNode.NextOneIdx == 0)
+ // {
+ // currentNode.NextOneIdx = (UInt16)entries.Count;
+ // entries[currentNodeIdx] = currentNode;
+ // entries.Add(new TreeNode());
+ // }
+ //
+ // currentNodeIdx = currentNode.NextOneIdx;
+ // currentNode = entries[currentNodeIdx];
+ // break;
+ //
+ // default:
+ // HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - GetBitAtIdx returned with an unsupported value: " + bit);
+ // break;
+ // }
+ // }
+ //
+ // entries[currentNodeIdx] = new TreeNode { Value = (UInt16)i };
+ //
+ // //HTTPManager.Logger.Information("HuffmanEncoder", string.Format("BuildTree - {0} - Entry({1}) added to idx: {2}", i, entries[currentNodeIdx], currentNodeIdx));
+ // }
+ //
+ // //HTTPManager.Logger.Information("HuffmanEncoder", "BuildTree - entries: " + entries.Count);
+ // //for (int i = 0; i < entries.Count; ++i)
+ // // HTTPManager.Logger.Information("HuffmanEncoder", string.Format("{0} - Entry : {1}", i, entries[i]));
+ // System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ // for (int i = 0; i < entries.Count; ++i)
+ // {
+ // sb.AppendFormat("new TreeNode {{ Value = {0}, NextZeroIdx = {1}, NextOneIdx = {2} }},\n", entries[i].Value, entries[i].NextZeroIdx, entries[i].NextOneIdx);
+ // }
+ // UnityEngine.Debug.Log(sb.ToString());
+ //}
+
+ public static TreeNode GetRoot()
+ {
+ return HuffmanTree[0];
+ }
+
+ public static TreeNode GetNext(TreeNode current, byte bit)
+ {
+ switch(bit)
+ {
+ case 0:
+ return HuffmanTree[current.NextZeroIdx];
+ case 1:
+ return HuffmanTree[current.NextOneIdx];
+ }
+
+ throw new Exception("HuffmanEncoder - GetNext - unsupported bit: " + bit);
+ }
+
+ public static TableEntry GetEntryForCodePoint(UInt16 codePoint)
+ {
+ return StaticTable[codePoint];
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3eed4fbf84f915e9b788cf87b1431902d6b3748b
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTP2/HuffmanEncoder.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2b07c0aa55ea00940b8c8bae8d5d9c34
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9a251b3c2c496d3cbead61795bce5aa28c5418dc
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs
@@ -0,0 +1,211 @@
+#if !UNITY_WEBGL || UNITY_EDITOR
+
+using System;
+
+using BestHTTP.Core;
+using BestHTTP.Timings;
+
+namespace BestHTTP.Connections
+{
+ ///
+ /// Represents and manages a connection to a server.
+ ///
+ public sealed class HTTPConnection : ConnectionBase
+ {
+ public TCPConnector connector;
+ public IHTTPRequestHandler requestHandler;
+
+ public override TimeSpan KeepAliveTime {
+ get {
+ if (this.requestHandler != null && this.requestHandler.KeepAlive != null)
+ {
+ if (this.requestHandler.KeepAlive.MaxRequests > 0)
+ {
+ if (base.KeepAliveTime < this.requestHandler.KeepAlive.TimeOut)
+ return base.KeepAliveTime;
+ else
+ return this.requestHandler.KeepAlive.TimeOut;
+ }
+ else
+ return TimeSpan.Zero;
+ }
+
+ return base.KeepAliveTime;
+ }
+
+ protected set
+ {
+ base.KeepAliveTime = value;
+ }
+ }
+
+ public override bool CanProcessMultiple
+ {
+ get
+ {
+ if (this.requestHandler != null)
+ return this.requestHandler.CanProcessMultiple;
+ return base.CanProcessMultiple;
+ }
+ }
+
+ internal HTTPConnection(string serverAddress)
+ :base(serverAddress)
+ {}
+
+ internal override void Process(HTTPRequest request)
+ {
+ this.LastProcessedUri = request.CurrentUri;
+
+ if (this.requestHandler == null || !this.requestHandler.HasCustomRequestProcessor)
+ base.Process(request);
+ else
+ {
+ this.requestHandler.Process(request);
+ LastProcessTime = DateTime.Now;
+ }
+ }
+
+ protected override void ThreadFunc()
+ {
+ if (this.CurrentRequest.IsRedirected)
+ this.CurrentRequest.Timing.Add(TimingEventNames.Queued_For_Redirection);
+ else
+ this.CurrentRequest.Timing.Add(TimingEventNames.Queued);
+
+ if (this.connector != null && !this.connector.IsConnected)
+ {
+ // this will send the request back to the queue
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ return;
+ }
+
+ if (this.connector == null)
+ {
+ this.connector = new Connections.TCPConnector();
+
+ try
+ {
+ this.connector.Connect(this.CurrentRequest);
+ }
+ catch(Exception ex)
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Exception("HTTPConnection", "Connector.Connect", ex, this.Context, this.CurrentRequest.Context);
+
+
+ if (ex is TimeoutException)
+ this.CurrentRequest.State = HTTPRequestStates.ConnectionTimedOut;
+ else if (!this.CurrentRequest.IsTimedOut) // Do nothing here if Abort() got called on the request, its State is already set.
+ {
+ this.CurrentRequest.Exception = ex;
+ this.CurrentRequest.State = HTTPRequestStates.Error;
+ }
+
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+
+ return;
+ }
+
+#if !NETFX_CORE
+ // data sending is buffered for all protocols, so when we put data into the socket we want to send them asap
+ this.connector.Client.NoDelay = true;
+#endif
+ StartTime = DateTime.UtcNow;
+
+ HTTPManager.Logger.Information("HTTPConnection", "Negotiated protocol through ALPN: '" + this.connector.NegotiatedProtocol + "'", this.Context, this.CurrentRequest.Context);
+
+ switch (this.connector.NegotiatedProtocol)
+ {
+ case HTTPProtocolFactory.W3C_HTTP1:
+ this.requestHandler = new Connections.HTTP1Handler(this);
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP1));
+ break;
+
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+ case HTTPProtocolFactory.W3C_HTTP2:
+ this.requestHandler = new Connections.HTTP2.HTTP2Handler(this);
+ this.CurrentRequest = null;
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HostProtocolSupport.HTTP2));
+ break;
+#endif
+
+ default:
+ HTTPManager.Logger.Error("HTTPConnection", "Unknown negotiated protocol: " + this.connector.NegotiatedProtocol, this.Context, this.CurrentRequest.Context);
+
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(CurrentRequest, RequestEvents.Resend));
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ return;
+ }
+ }
+
+ this.requestHandler.Context.Add("Connection", this.GetHashCode());
+ this.Context.Add("RequestHandler", this.requestHandler.GetHashCode());
+
+ this.requestHandler.RunHandler();
+ LastProcessTime = DateTime.Now;
+ }
+
+ public override void Shutdown(ShutdownTypes type)
+ {
+ base.Shutdown(type);
+
+ if (this.requestHandler != null)
+ this.requestHandler.Shutdown(type);
+
+ switch(this.ShutdownType)
+ {
+ case ShutdownTypes.Immediate:
+ this.connector.Dispose();
+ break;
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ LastProcessedUri = null;
+ if (this.State != HTTPConnectionStates.WaitForProtocolShutdown)
+ {
+ if (this.connector != null)
+ {
+ try
+ {
+ this.connector.Close();
+ }
+ catch
+ { }
+ this.connector = null;
+ }
+
+ if (this.requestHandler != null)
+ {
+ try
+ {
+ this.requestHandler.Dispose();
+ }
+ catch
+ { }
+ this.requestHandler = null;
+ }
+ }
+ else
+ {
+ // We have to connector to do not close its stream at any cost while disposing.
+ // All references to this connection will be removed, so this and the connector may be finalized after some time.
+ // But, finalizing (and disposing) the connector while the protocol is still active would be fatal,
+ // so we have to make sure that it will not happen. This also means that the protocol has the responsibility (as always had)
+ // to close the stream and TCP connection properly.
+ if (this.connector != null)
+ this.connector.LeaveOpen = true;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..997691daa54bfe4f30e045e9b84eea3fe9ad56c0
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnection.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: c6e10f65ea45b8e4682ebe6a0c885e06
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e9fb36fb8d79c498b8aa03c8dace1b93763d0c2
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs
@@ -0,0 +1,44 @@
+namespace BestHTTP.Connections
+{
+ ///
+ /// Possible states of a Http Connection.
+ /// The ideal lifecycle of a connection that has KeepAlive is the following: Initial => [Processing => WaitForRecycle => Free] => Closed.
+ ///
+ public enum HTTPConnectionStates
+ {
+ ///
+ /// This Connection instance is just created.
+ ///
+ Initial,
+
+ ///
+ /// This Connection is processing a request
+ ///
+ Processing,
+
+ ///
+ /// Wait for the upgraded protocol to shut down.
+ ///
+ WaitForProtocolShutdown,
+
+ ///
+ /// The Connection is finished processing the request, it's waiting now to deliver it's result.
+ ///
+ Recycle,
+
+ ///
+ /// The request result's delivered, it's now up to processing again.
+ ///
+ Free,
+
+ ///
+ /// If it's not a KeepAlive connection, or something happened, then we close this connection and remove from the pool.
+ ///
+ Closed,
+
+ ///
+ /// Same as the Closed state, but processing this request requires resending the last processed request too.
+ ///
+ ClosedResendRequest
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3ead1f3b131e8b452fa22881f742237976b2a004
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPConnectionStates.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: ae2c4bf49da6b1b4097d46250f2ff3bf
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs b/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b244a5dc4da8640da846ef8d9661efc7d8d23853
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs
@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+
+namespace BestHTTP.Connections
+{
+ public enum SupportedProtocols
+ {
+ Unknown,
+ HTTP,
+
+#if !BESTHTTP_DISABLE_WEBSOCKET
+ WebSocket,
+#endif
+
+#if !BESTHTTP_DISABLE_SERVERSENT_EVENTS
+ ServerSentEvents
+#endif
+ }
+
+ public static class HTTPProtocolFactory
+ {
+ public const string W3C_HTTP1 = "http/1.1";
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+ public const string W3C_HTTP2 = "h2";
+#endif
+
+ public static HTTPResponse Get(SupportedProtocols protocol, HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache)
+ {
+ switch (protocol)
+ {
+#if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
+ case SupportedProtocols.WebSocket: return new WebSocket.WebSocketResponse(request, stream, isStreamed, isFromCache);
+#endif
+ default: return new HTTPResponse(request, stream, isStreamed, isFromCache);
+ }
+ }
+
+ public static SupportedProtocols GetProtocolFromUri(Uri uri)
+ {
+ if (uri == null || uri.Scheme == null)
+ throw new Exception("Malformed URI in GetProtocolFromUri");
+
+ string scheme = uri.Scheme.ToLowerInvariant();
+ switch (scheme)
+ {
+#if !BESTHTTP_DISABLE_WEBSOCKET
+ case "ws":
+ case "wss":
+ return SupportedProtocols.WebSocket;
+#endif
+
+ default:
+ return SupportedProtocols.HTTP;
+ }
+ }
+
+ public static bool IsSecureProtocol(Uri uri)
+ {
+ if (uri == null || uri.Scheme == null)
+ throw new Exception("Malformed URI in IsSecureProtocol");
+
+ string scheme = uri.Scheme.ToLowerInvariant();
+ switch (scheme)
+ {
+ // http
+ case "https":
+
+#if !BESTHTTP_DISABLE_WEBSOCKET
+ // WebSocket
+ case "wss":
+#endif
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..96dfe0b35915a8934a24f8d92bdfac3d0e56342d
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/HTTPProtocolFactory.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 42fc2aaed5d783a48b2d61c1d7db8f23
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs b/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f74ab34d24acf47a8bd1442a0782ce06b48aa0e1
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs
@@ -0,0 +1,642 @@
+#if !UNITY_WEBGL || UNITY_EDITOR
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using BestHTTP.Extensions;
+
+#if !NETFX_CORE || UNITY_EDITOR
+using System.Net.Security;
+#endif
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls;
+using BestHTTP.PlatformSupport.Memory;
+using System.Runtime.InteropServices;
+using System.Net.Sockets;
+#endif
+
+#if NETFX_CORE
+ using System.Threading.Tasks;
+ using Windows.Networking.Sockets;
+
+ using TcpClient = BestHTTP.PlatformSupport.TcpClient.WinRT.TcpClient;
+
+ //Disable CD4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
+#pragma warning disable 4014
+#else
+ using TcpClient = BestHTTP.PlatformSupport.TcpClient.General.TcpClient;
+#endif
+
+using BestHTTP.Timings;
+
+namespace BestHTTP.Connections
+{
+ // Non-used experimental stream. Reading from the inner stream is done parallel and Read is blocked if no data is buffered.
+ // Additionally BC reads 5 bytes for the TLS header, than the payload. Buffering data from the network could save at least one context switch per TLS message.
+ // In theory it, could help as reading from the network could be done parallel with TLS decryption.
+ // However, if decrypting data is done faster than data is coming on the network, waiting for data longer and letting SpinWait to go deep-sleep it's going to
+ // resume the thread milliseconds after new data is available. Those little afters are adding up and actually slowing down the download.
+ // Not using locking just calling TryDequeue until there's data would solve the slow-down, but with the price of using 100% CPU of a core.
+ // The whole struggle might worth it if Unity would implement SocketAsyncEventArgs properly.
+ //sealed class BufferedReadNetworkStream : Stream
+ //{
+ // public override bool CanRead => throw new NotImplementedException();
+ //
+ // public override bool CanSeek => throw new NotImplementedException();
+ //
+ // public override bool CanWrite => throw new NotImplementedException();
+ //
+ // public override long Length => throw new NotImplementedException();
+ //
+ // public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ //
+ // byte[] buf;
+ // int available = 0;
+ // int pos = 0;
+ //
+ // private System.Net.Sockets.Socket client;
+ // int readBufferSize;
+ // int bufferSize;
+ // private System.Threading.SpinWait spinWait = new System.Threading.SpinWait();
+ //
+ // System.Collections.Concurrent.ConcurrentQueue downloadedData = new System.Collections.Concurrent.ConcurrentQueue();
+ // private int downloadedBytes;
+ // private System.Threading.SpinWait downWait = new System.Threading.SpinWait();
+ // private int closed = 0;
+ //
+ // //System.Net.Sockets.SocketAsyncEventArgs socketAsyncEventArgs = new System.Net.Sockets.SocketAsyncEventArgs();
+ //
+ // //DateTime started;
+ //
+ // public BufferedReadNetworkStream(System.Net.Sockets.Socket socket, int readBufferSize, int bufferSize)
+ // {
+ // this.client = socket;
+ // this.readBufferSize = readBufferSize;
+ // this.bufferSize = bufferSize;
+ //
+ // //this.socketAsyncEventArgs.AcceptSocket = this.client;
+ // //
+ // //var buffer = BufferPool.Get(this.readBufferSize, true);
+ // //this.socketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);
+ // //
+ // ////var bufferList = new List>();
+ // ////for (int i = 0; i < 1; i++)
+ // ////{
+ // //// var buffer = BufferPool.Get(this.readBufferSize, true);
+ // //// bufferList.Add(new ArraySegment(buffer));
+ // ////}
+ // ////this.socketAsyncEventArgs.BufferList = bufferList;
+ // //
+ // //this.socketAsyncEventArgs.Completed += SocketAsyncEventArgs_Completed;
+ // //
+ // //this.started = DateTime.Now;
+ // //if (!this.client.ReceiveAsync(this.socketAsyncEventArgs))
+ // // SocketAsyncEventArgs_Completed(null, this.socketAsyncEventArgs);
+ //
+ // BestHTTP.PlatformSupport.Threading.ThreadedRunner.RunShortLiving(() =>
+ // {
+ // DateTime started = DateTime.Now;
+ // try
+ // {
+ // while (closed == 0)
+ // {
+ // var buffer = BufferPool.Get(this.readBufferSize, true);
+ //
+ // int count = this.client.Receive(buffer, 0, buffer.Length, System.Net.Sockets.SocketFlags.None);
+ // //int count = 0;
+ // //unsafe {
+ // // fixed (byte* pBuffer = buffer)
+ // // {
+ // // int zero = 0;
+ // // count = recvfrom(this.client.Handle, pBuffer, buffer.Length, SocketFlags.None, null, ref zero);
+ // // }
+ // //}
+ //
+ // this.downloadedData.Enqueue(new BufferSegment(buffer, 0, count));
+ // System.Threading.Interlocked.Add(ref downloadedBytes, count);
+ //
+ // if (HTTPManager.Logger.Level <= Logger.Loglevels.Warning)
+ // HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"read count: {count:N0} downloadedBytes: {downloadedBytes:N0} / {this.bufferSize:N0}");
+ //
+ // if (count <= 0)
+ // {
+ // System.Threading.Interlocked.Exchange(ref closed, 1);
+ // return;
+ // }
+ //
+ // while (downloadedBytes >= this.bufferSize)
+ // {
+ // downWait.SpinOnce();
+ // }
+ // }
+ // }
+ // catch (Exception ex)
+ // {
+ // UnityEngine.Debug.LogException(ex);
+ // }
+ // finally
+ // {
+ // UnityEngine.Debug.Log($"Reading finished in {(DateTime.Now - started)}");
+ // }
+ // });
+ // }
+ //
+ // //private void SocketAsyncEventArgs_Completed(object sender, System.Net.Sockets.SocketAsyncEventArgs e)
+ // //{
+ // // this.downloadedData.Enqueue(new BufferSegment(e.Buffer, 0, e.BytesTransferred));
+ // //
+ // // if (e.BytesTransferred == 0)
+ // // {
+ // // UnityEngine.Debug.Log($"Reading finished in {(DateTime.Now - started)}");
+ // // return;
+ // // }
+ // //
+ // // int down = System.Threading.Interlocked.Add(ref downloadedBytes, e.BytesTransferred);
+ // //
+ // // if (HTTPManager.Logger.Level <= Logger.Loglevels.Warning)
+ // // HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"SocketAsyncEventArgs_Completed - read count: {e.BytesTransferred:N0} downloadedBytes: {down:N0} / {this.bufferSize:N0}");
+ // //
+ // // var buffer = BufferPool.Get(this.readBufferSize, true);
+ // // this.socketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);
+ // //
+ // // if (!this.client.ReceiveAsync(this.socketAsyncEventArgs))
+ // // SocketAsyncEventArgs_Completed(null, this.socketAsyncEventArgs);
+ // //}
+ //
+ // private void SwitchBuffers(bool waitForData)
+ // {
+ // //HTTPManager.Logger.Error("Read", $"{this.downloadedData.Count}");
+ // BufferSegment segment;
+ // while (!this.downloadedData.TryDequeue(out segment))
+ // {
+ // if (waitForData && closed == 0)
+ // {
+ // if (HTTPManager.Logger.Level <= Logger.Loglevels.Error)
+ // HTTPManager.Logger.Error(nameof(BufferedReadNetworkStream), $"SpinOnce");
+ // this.spinWait.SpinOnce();
+ // }
+ // else
+ // return;
+ // }
+ //
+ // //if (segment.Count <= 0)
+ // // throw new Exception("Connection closed!");
+ //
+ // if (buf != null)
+ // BufferPool.Release(buf);
+ //
+ // System.Threading.Interlocked.Add(ref downloadedBytes, -segment.Count);
+ //
+ // buf = segment.Data;
+ // available = segment.Count;
+ // pos = 0;
+ // }
+ //
+ // public override int Read(byte[] buffer, int offset, int size)
+ // {
+ // if (this.buf == null)
+ // {
+ // SwitchBuffers(true);
+ // }
+ //
+ // if (size <= available)
+ // {
+ // Array.Copy(buf, pos, buffer, offset, size);
+ // available -= size;
+ // pos += size;
+ //
+ // if (available == 0)
+ // {
+ // SwitchBuffers(false);
+ // }
+ //
+ // return size;
+ // }
+ // else
+ // {
+ // int readcount = 0;
+ // if (available > 0)
+ // {
+ // Array.Copy(buf, pos, buffer, offset, available);
+ // offset += available;
+ // readcount += available;
+ // available = 0;
+ // pos = 0;
+ // }
+ //
+ // while (true)
+ // {
+ // try
+ // {
+ // SwitchBuffers(true);
+ // }
+ // catch (Exception ex)
+ // {
+ // if (readcount > 0)
+ // {
+ // return readcount;
+ // }
+ //
+ // throw (ex);
+ // }
+ //
+ // if (available < 1)
+ // {
+ // if (readcount > 0)
+ // {
+ // return readcount;
+ // }
+ //
+ // return available;
+ // }
+ // else
+ // {
+ // int toread = size - readcount;
+ // if (toread <= available)
+ // {
+ // Array.Copy(buf, pos, buffer, offset, toread);
+ // available -= toread;
+ // pos += toread;
+ // readcount += toread;
+ // return readcount;
+ // }
+ // else
+ // {
+ // Array.Copy(buf, pos, buffer, offset, available);
+ // offset += available;
+ // readcount += available;
+ // pos = 0;
+ // available = 0;
+ // }
+ // }
+ // }
+ // }
+ // }
+ //
+ // public override long Seek(long offset, SeekOrigin origin)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //
+ // public override void SetLength(long value)
+ // {
+ // throw new NotImplementedException();
+ // }
+ //
+ // public override void Write(byte[] buffer, int offset, int count)
+ // {
+ // this.client.Send(buffer, offset, count, System.Net.Sockets.SocketFlags.None);
+ //
+ // HTTPManager.Logger.Warning(nameof(BufferedReadNetworkStream), $"Wrote: {count}");
+ // }
+ //
+ // public override void Close()
+ // {
+ // base.Close();
+ //
+ // //socketAsyncEventArgs.Dispose();
+ // //socketAsyncEventArgs = null;
+ // }
+ //
+ // public override void Flush()
+ // {
+ // }
+ //}
+
+ sealed class BufferedReadNetworkStream : Stream
+ {
+ public override bool CanRead { get { throw new NotImplementedException(); } }
+
+ public override bool CanSeek { get { throw new NotImplementedException(); } }
+
+ public override bool CanWrite { get { throw new NotImplementedException(); } }
+
+ public override long Length { get { throw new NotImplementedException(); } }
+
+ public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
+
+ private ReadOnlyBufferedStream readStream;
+ private Stream innerStream;
+
+ public BufferedReadNetworkStream(Stream stream, int bufferSize)
+ {
+ this.innerStream = stream;
+ this.readStream = new ReadOnlyBufferedStream(stream, bufferSize);
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return this.readStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ this.innerStream.Write(buffer, offset, count);
+ }
+
+ public override void Close()
+ {
+ base.Close();
+
+ this.innerStream.Close();
+ }
+ }
+
+ public sealed class TCPConnector : IDisposable
+ {
+ public bool IsConnected { get { return this.Client != null && this.Client.Connected; } }
+
+ public string NegotiatedProtocol { get; private set; }
+
+ public TcpClient Client { get; private set; }
+
+ public Stream TopmostStream { get; private set; }
+
+ public Stream Stream { get; private set; }
+
+ public bool LeaveOpen { get; set; }
+
+ public void Connect(HTTPRequest request)
+ {
+ string negotiatedProtocol = HTTPProtocolFactory.W3C_HTTP1;
+
+ Uri uri =
+#if !BESTHTTP_DISABLE_PROXY
+ request.HasProxy ? request.Proxy.Address :
+#endif
+ request.CurrentUri;
+
+ #region TCP Connection
+
+ if (Client == null)
+ Client = new TcpClient();
+
+ if (!Client.Connected)
+ {
+ Client.ConnectTimeout = request.ConnectTimeout;
+
+#if NETFX_CORE
+ Client.UseHTTPSProtocol =
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ !Request.UseAlternateSSL &&
+#endif
+ HTTPProtocolFactory.IsSecureProtocol(uri);
+#endif
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Connecting to {1}:{2}", request.CurrentUri.ToString(), uri.Host, uri.Port.ToString()), request.Context);
+
+#if !NETFX_CORE && (!UNITY_WEBGL || UNITY_EDITOR)
+ bool changed = false;
+ int? sendBufferSize = null, receiveBufferSize = null;
+
+ if (HTTPManager.SendBufferSize.HasValue)
+ {
+ sendBufferSize = Client.SendBufferSize;
+ Client.SendBufferSize = HTTPManager.SendBufferSize.Value;
+ changed = true;
+ }
+
+ if (HTTPManager.ReceiveBufferSize.HasValue)
+ {
+ receiveBufferSize = Client.ReceiveBufferSize;
+ Client.ReceiveBufferSize = HTTPManager.ReceiveBufferSize.Value;
+ changed = true;
+ }
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ {
+ if (changed)
+ HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes changed - Send from: {1} to: {2}, Receive from: {3} to: {4}, Blocking: {5}",
+ request.CurrentUri.ToString(),
+ sendBufferSize,
+ Client.SendBufferSize,
+ receiveBufferSize,
+ Client.ReceiveBufferSize,
+ Client.Client.Blocking),
+ request.Context);
+ else
+ HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - Buffer sizes - Send: {1} Receive: {2} Blocking: {3}", request.CurrentUri.ToString(), Client.SendBufferSize, Client.ReceiveBufferSize, Client.Client.Blocking), request.Context);
+ }
+#endif
+
+#if NETFX_CORE && !UNITY_EDITOR && !ENABLE_IL2CPP
+ try
+ {
+ Client.Connect(uri.Host, uri.Port);
+ }
+ finally
+ {
+ request.Timing.Add(TimingEventNames.TCP_Connection);
+ }
+#else
+ System.Net.IPAddress[] addresses = null;
+ try
+ {
+ if (Client.ConnectTimeout > TimeSpan.Zero)
+ {
+ // https://forum.unity3d.com/threads/best-http-released.200006/page-37#post-3150972
+ using (System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false))
+ {
+ IAsyncResult result = System.Net.Dns.BeginGetHostAddresses(uri.Host, (res) => { try { mre.Set(); } catch { } }, null);
+ bool success = mre.WaitOne(Client.ConnectTimeout);
+ if (success)
+ {
+ addresses = System.Net.Dns.EndGetHostAddresses(result);
+ }
+ else
+ {
+ throw new TimeoutException("DNS resolve timed out!");
+ }
+ }
+ }
+ else
+ {
+ addresses = System.Net.Dns.GetHostAddresses(uri.Host);
+ }
+ }
+ finally
+ {
+ request.Timing.Add(TimingEventNames.DNS_Lookup);
+ }
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ HTTPManager.Logger.Verbose("TCPConnector", string.Format("'{0}' - DNS Query returned with addresses: {1}", request.CurrentUri.ToString(), addresses != null ? addresses.Length : -1), request.Context);
+
+ if (request.IsCancellationRequested)
+ throw new Exception("IsCancellationRequested");
+
+ try
+ {
+ Client.Connect(addresses, uri.Port, request);
+ }
+ finally
+ {
+ request.Timing.Add(TimingEventNames.TCP_Connection);
+ }
+
+ if (request.IsCancellationRequested)
+ throw new Exception("IsCancellationRequested");
+#endif
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("TCPConnector", "Connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context);
+ }
+ else if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("TCPConnector", "Already connected to " + uri.Host + ":" + uri.Port.ToString(), request.Context);
+
+#endregion
+
+ if (Stream == null)
+ {
+ bool isSecure = HTTPProtocolFactory.IsSecureProtocol(request.CurrentUri);
+
+ // set the stream to Client.GetStream() so the proxy, if there's any can use it directly.
+ this.Stream = this.TopmostStream = Client.GetStream();
+
+ /*if (Stream.CanTimeout)
+ Stream.ReadTimeout = Stream.WriteTimeout = (int)Request.Timeout.TotalMilliseconds;*/
+
+#if !BESTHTTP_DISABLE_PROXY
+ if (request.HasProxy)
+ {
+ try
+ {
+ request.Proxy.Connect(this.Stream, request);
+ }
+ finally
+ {
+ request.Timing.Add(TimingEventNames.Proxy_Negotiation);
+ }
+ }
+
+ if (request.IsCancellationRequested)
+ throw new Exception("IsCancellationRequested");
+#endif
+
+ // proxy connect is done, we can set the stream to a buffered one. HTTPProxy requires the raw NetworkStream for HTTPResponse's ReadUnknownSize!
+ this.Stream = this.TopmostStream = new BufferedReadNetworkStream(Client.GetStream(), Math.Max(8 * 1024, HTTPManager.ReceiveBufferSize ?? Client.ReceiveBufferSize));
+
+ // We have to use Request.CurrentUri here, because uri can be a proxy uri with a different protocol
+ if (isSecure)
+ {
+ DateTime tlsNegotiationStartedAt = DateTime.Now;
+#region SSL Upgrade
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ if (request.UseAlternateSSL)
+ {
+ var handler = new TlsClientProtocol(this.Stream, new BestHTTP.SecureProtocol.Org.BouncyCastle.Security.SecureRandom());
+
+ List protocols = new List();
+#if !BESTHTTP_DISABLE_HTTP2
+ SupportedProtocols protocol = request.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(request.CurrentUri) : request.ProtocolHandler;
+ if (protocol == SupportedProtocols.HTTP)
+ {
+ // http/2 over tls (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids)
+ protocols.Add(HTTPProtocolFactory.W3C_HTTP2);
+ }
+#endif
+
+ protocols.Add(HTTPProtocolFactory.W3C_HTTP1);
+
+ AbstractTlsClient tlsClient = null;
+ if (HTTPManager.TlsClientFactory == null)
+ {
+ tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols);
+ }
+ else
+ {
+ try
+ {
+ tlsClient = HTTPManager.TlsClientFactory(request, protocols);
+ }
+ catch
+ { }
+
+ if (tlsClient == null)
+ tlsClient = HTTPManager.DefaultTlsClientFactory(request, protocols);
+ }
+
+ tlsClient.LoggingContext = request.Context;
+ handler.Connect(tlsClient);
+
+ if (!string.IsNullOrEmpty(tlsClient.ServerSupportedProtocol))
+ negotiatedProtocol = tlsClient.ServerSupportedProtocol;
+
+ Stream = handler.Stream;
+ }
+ else
+#endif
+ {
+#if !NETFX_CORE
+ SslStream sslStream = new SslStream(Client.GetStream(), false, (sender, cert, chain, errors) =>
+ {
+ return request.CallCustomCertificationValidator(cert, chain);
+ });
+
+ if (!sslStream.IsAuthenticated)
+ sslStream.AuthenticateAsClient(request.CurrentUri.Host);
+ Stream = sslStream;
+#else
+ Stream = Client.GetStream();
+#endif
+ }
+#endregion
+
+ request.Timing.Add(TimingEventNames.TLS_Negotiation, DateTime.Now - tlsNegotiationStartedAt);
+ }
+ }
+
+ this.NegotiatedProtocol = negotiatedProtocol;
+ }
+
+ public void Close()
+ {
+ if (Client != null && !this.LeaveOpen)
+ {
+ try
+ {
+ if (Stream != null)
+ Stream.Close();
+ }
+ catch { }
+ finally
+ {
+ Stream = null;
+ }
+
+ try
+ {
+ Client.Close();
+ }
+ catch { }
+ finally
+ {
+ Client = null;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Close();
+ }
+ }
+}
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..bdd37077b45b3cd6fb224d655c18c2ccf2389632
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/TCPConnector.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: d12096d6e11917c408186442aade2b09
+timeCreated: 1571210042
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs b/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8ddb6f20295275ce238ee57a110dfb436a3b4562
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs
@@ -0,0 +1,434 @@
+#if UNITY_WEBGL && !UNITY_EDITOR
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+#if !BESTHTTP_DISABLE_CACHING
+using BestHTTP.Caching;
+#endif
+using BestHTTP.Core;
+using BestHTTP.Extensions;
+using BestHTTP.Connections;
+using BestHTTP.PlatformSupport.Memory;
+
+namespace BestHTTP.Connections
+{
+ delegate void OnWebGLRequestHandlerDelegate(int nativeId, int httpStatus, IntPtr pBuffer, int length, int zero);
+ delegate void OnWebGLBufferDelegate(int nativeId, IntPtr pBuffer, int length);
+ delegate void OnWebGLProgressDelegate(int nativeId, int downloaded, int total);
+ delegate void OnWebGLErrorDelegate(int nativeId, string error);
+ delegate void OnWebGLTimeoutDelegate(int nativeId);
+ delegate void OnWebGLAbortedDelegate(int nativeId);
+
+ internal sealed class WebGLConnection : ConnectionBase
+ {
+ static Dictionary Connections = new Dictionary(4);
+
+ int NativeId;
+ BufferSegmentStream Stream;
+
+ public WebGLConnection(string serverAddress)
+ : base(serverAddress, false)
+ {
+ XHR_SetLoglevel((byte)HTTPManager.Logger.Level);
+ }
+
+ public override void Shutdown(ShutdownTypes type)
+ {
+ base.Shutdown(type);
+
+ XHR_Abort(this.NativeId);
+ }
+
+ protected override void ThreadFunc()
+ {
+ // XmlHttpRequest setup
+
+ this.NativeId = XHR_Create(HTTPRequest.MethodNames[(byte)CurrentRequest.MethodType],
+ CurrentRequest.CurrentUri.OriginalString,
+ CurrentRequest.Credentials != null ? CurrentRequest.Credentials.UserName : null,
+ CurrentRequest.Credentials != null ? CurrentRequest.Credentials.Password : null,
+ CurrentRequest.WithCredentials ? 1 : 0);
+ Connections.Add(NativeId, this);
+
+ CurrentRequest.EnumerateHeaders((header, values) =>
+ {
+ if (!header.Equals("Content-Length"))
+ for (int i = 0; i < values.Count; ++i)
+ XHR_SetRequestHeader(NativeId, header, values[i]);
+ }, /*callBeforeSendCallback:*/ true);
+
+ XHR_SetResponseHandler(NativeId, WebGLConnection.OnResponse, WebGLConnection.OnError, WebGLConnection.OnTimeout, WebGLConnection.OnAborted);
+ // Setting OnUploadProgress result in an addEventListener("progress", ...) call making the request non-simple.
+ // https://forum.unity.com/threads/best-http-released.200006/page-49#post-3696220
+ XHR_SetProgressHandler(NativeId,
+ CurrentRequest.OnDownloadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnDownloadProgress,
+ CurrentRequest.OnUploadProgress == null ? (OnWebGLProgressDelegate)null : WebGLConnection.OnUploadProgress);
+
+ XHR_SetTimeout(NativeId, (uint)(CurrentRequest.ConnectTimeout.TotalMilliseconds + CurrentRequest.Timeout.TotalMilliseconds));
+
+ byte[] body = CurrentRequest.GetEntityBody();
+ int length = 0;
+ bool releaseBodyBuffer = false;
+
+ if (body == null)
+ {
+ var upStreamInfo = CurrentRequest.GetUpStream();
+ if (upStreamInfo.Stream != null)
+ {
+ var internalBuffer = BufferPool.Get(upStreamInfo.Length > 0 ? upStreamInfo.Length : HTTPRequest.UploadChunkSize, true);
+ using (BufferPoolMemoryStream ms = new BufferPoolMemoryStream(internalBuffer, 0, internalBuffer.Length, true, true, false, true))
+ {
+ var buffer = BufferPool.Get(HTTPRequest.UploadChunkSize, true);
+ int readCount = -1;
+ while ((readCount = upStreamInfo.Stream.Read(buffer, 0, buffer.Length)) > 0)
+ ms.Write(buffer, 0, readCount);
+
+ BufferPool.Release(buffer);
+
+ length = (int)ms.Position;
+ body = ms.GetBuffer();
+
+ releaseBodyBuffer = true;
+ }
+ }
+ }
+ else
+ {
+ length = body.Length;
+ }
+
+ XHR_Send(NativeId, body, length);
+
+ if (releaseBodyBuffer)
+ BufferPool.Release(body);
+ }
+
+#region Callback Implementations
+
+ void OnResponse(int httpStatus, BufferSegment payload)
+ {
+ HTTPConnectionStates proposedConnectionState = HTTPConnectionStates.Processing;
+ bool resendRequest = false;
+
+ try
+ {
+ if (this.CurrentRequest.IsCancellationRequested)
+ return;
+
+ using (var ms = new BufferSegmentStream())
+ {
+ Stream = ms;
+
+ XHR_GetStatusLine(NativeId, OnBufferCallback);
+ XHR_GetResponseHeaders(NativeId, OnBufferCallback);
+
+ if (payload != BufferSegment.Empty)
+ ms.Write(payload);
+
+ SupportedProtocols protocol = CurrentRequest.ProtocolHandler == SupportedProtocols.Unknown ? HTTPProtocolFactory.GetProtocolFromUri(CurrentRequest.CurrentUri) : CurrentRequest.ProtocolHandler;
+ CurrentRequest.Response = HTTPProtocolFactory.Get(protocol, CurrentRequest, ms, CurrentRequest.UseStreaming, false);
+
+ CurrentRequest.Response.Receive(payload != BufferSegment.Empty && payload.Count > 0 ? (int)payload.Count : -1, true);
+
+ KeepAliveHeader keepAlive = null;
+ ConnectionHelper.HandleResponse(this.ToString(), this.CurrentRequest, out resendRequest, out proposedConnectionState, ref keepAlive);
+ }
+ }
+ catch (Exception e)
+ {
+ HTTPManager.Logger.Exception(this.NativeId + " WebGLConnection", "OnResponse", e, this.Context);
+
+ if (this.ShutdownType == ShutdownTypes.Immediate)
+ return;
+
+#if !BESTHTTP_DISABLE_CACHING
+ if (this.CurrentRequest.UseStreaming)
+ HTTPCacheService.DeleteEntity(this.CurrentRequest.CurrentUri);
+#endif
+
+ // Something gone bad, Response must be null!
+ this.CurrentRequest.Response = null;
+
+ if (!this.CurrentRequest.IsCancellationRequested)
+ {
+ this.CurrentRequest.Exception = e;
+ this.CurrentRequest.State = HTTPRequestStates.Error;
+ }
+
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ finally
+ {
+ // Exit ASAP
+ if (this.ShutdownType != ShutdownTypes.Immediate)
+ {
+ if (this.CurrentRequest.IsCancellationRequested)
+ {
+ // we don't know what stage the request is cancelled, we can't safely reuse the tcp channel.
+ proposedConnectionState = HTTPConnectionStates.Closed;
+
+ this.CurrentRequest.Response = null;
+
+ this.CurrentRequest.State = this.CurrentRequest.IsTimedOut ? HTTPRequestStates.TimedOut : HTTPRequestStates.Aborted;
+ }
+ else if (resendRequest)
+ {
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.Resend));
+ }
+ else if (this.CurrentRequest.Response != null && this.CurrentRequest.Response.IsUpgraded)
+ {
+ proposedConnectionState = HTTPConnectionStates.WaitForProtocolShutdown;
+ }
+ else if (this.CurrentRequest.State == HTTPRequestStates.Processing)
+ {
+ if (this.CurrentRequest.Response != null)
+ this.CurrentRequest.State = HTTPRequestStates.Finished;
+ else
+ {
+ this.CurrentRequest.Exception = new Exception(string.Format("[{0}] Remote server closed the connection before sending response header! Previous request state: {1}. Connection state: {2}",
+ this.ToString(),
+ this.CurrentRequest.State.ToString(),
+ this.State.ToString()));
+ this.CurrentRequest.State = HTTPRequestStates.Error;
+
+ proposedConnectionState = HTTPConnectionStates.Closed;
+ }
+ }
+
+ this.CurrentRequest = null;
+
+ if (proposedConnectionState == HTTPConnectionStates.Processing)
+ proposedConnectionState = HTTPConnectionStates.Closed;
+
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, proposedConnectionState));
+ }
+ }
+ }
+
+ void OnBuffer(BufferSegment buffer)
+ {
+ if (Stream != null)
+ {
+ Stream.Write(buffer);
+ //Stream.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
+ }
+ }
+
+ void OnDownloadProgress(int down, int total)
+ {
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.DownloadProgress, down, total));
+ }
+
+ void OnUploadProgress(int up, int total)
+ {
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.CurrentRequest, RequestEvents.UploadProgress, up, total));
+ }
+
+ void OnError(string error)
+ {
+ HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnError", error, this.Context);
+
+ LastProcessTime = DateTime.UtcNow;
+
+ CurrentRequest.Response = null;
+ CurrentRequest.Exception = new Exception(error);
+ CurrentRequest.State = HTTPRequestStates.Error;
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ }
+
+ void OnTimeout()
+ {
+ HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnResponse", string.Empty, this.Context);
+
+ CurrentRequest.Response = null;
+ CurrentRequest.State = HTTPRequestStates.TimedOut;
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ }
+
+ void OnAborted()
+ {
+ HTTPManager.Logger.Information(this.NativeId + " WebGLConnection - OnAborted", string.Empty, this.Context);
+
+ CurrentRequest.Response = null;
+ CurrentRequest.State = HTTPRequestStates.Aborted;
+ ConnectionEventHelper.EnqueueConnectionEvent(new ConnectionEventInfo(this, HTTPConnectionStates.Closed));
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ Connections.Remove(NativeId);
+ XHR_Release(NativeId);
+
+ Stream = null;
+ }
+ }
+#endregion
+
+#region WebGL Static Callbacks
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLRequestHandlerDelegate))]
+ static void OnResponse(int nativeId, int httpStatus, IntPtr pBuffer, int length, int err)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnResponse", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ HTTPManager.Logger.Information("WebGLConnection - OnResponse", string.Format("{0} {1} {2} {3}", nativeId, httpStatus, length, err), conn.Context);
+
+ BufferSegment payload = BufferSegment.Empty;
+ if (length > 0)
+ {
+ var buffer = BufferPool.Get(length, true);
+
+ XHR_CopyResponseTo(nativeId, buffer, length);
+
+ payload = new BufferSegment(buffer, 0, length);
+ }
+
+ conn.OnResponse(httpStatus, payload);
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLBufferDelegate))]
+ static void OnBufferCallback(int nativeId, IntPtr pBuffer, int length)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnBufferCallback", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ byte[] buffer = BufferPool.Get(length, true);
+
+ // Copy data from the 'unmanaged' memory to managed memory. Buffer will be reclaimed by the GC.
+ Marshal.Copy(pBuffer, buffer, 0, length);
+
+ conn.OnBuffer(new BufferSegment(buffer, 0, length));
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
+ static void OnDownloadProgress(int nativeId, int downloaded, int total)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnDownloadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ HTTPManager.Logger.Information(nativeId + " OnDownloadProgress", downloaded.ToString() + " / " + total.ToString(), conn.Context);
+
+ conn.OnDownloadProgress(downloaded, total);
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLProgressDelegate))]
+ static void OnUploadProgress(int nativeId, int uploaded, int total)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnUploadProgress", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ HTTPManager.Logger.Information(nativeId + " OnUploadProgress", uploaded.ToString() + " / " + total.ToString(), conn.Context);
+
+ conn.OnUploadProgress(uploaded, total);
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLErrorDelegate))]
+ static void OnError(int nativeId, string error)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnError", "No WebGL connection found for nativeId: " + nativeId.ToString() + " Error: " + error);
+ return;
+ }
+
+ conn.OnError(error);
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLTimeoutDelegate))]
+ static void OnTimeout(int nativeId)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnTimeout", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ conn.OnTimeout();
+ }
+
+ [AOT.MonoPInvokeCallback(typeof(OnWebGLAbortedDelegate))]
+ static void OnAborted(int nativeId)
+ {
+ WebGLConnection conn = null;
+ if (!Connections.TryGetValue(nativeId, out conn))
+ {
+ HTTPManager.Logger.Error("WebGLConnection - OnAborted", "No WebGL connection found for nativeId: " + nativeId.ToString());
+ return;
+ }
+
+ conn.OnAborted();
+ }
+
+#endregion
+
+#region WebGL Interface
+
+ [DllImport("__Internal")]
+ private static extern int XHR_Create(string method, string url, string userName, string passwd, int withCredentials);
+
+ ///
+ /// Is an unsigned long representing the number of milliseconds a request can take before automatically being terminated. A value of 0 (which is the default) means there is no timeout.
+ ///
+ [DllImport("__Internal")]
+ private static extern void XHR_SetTimeout(int nativeId, uint timeout);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_SetRequestHeader(int nativeId, string header, string value);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_SetResponseHandler(int nativeId, OnWebGLRequestHandlerDelegate onresponse, OnWebGLErrorDelegate onerror, OnWebGLTimeoutDelegate ontimeout, OnWebGLAbortedDelegate onabort);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_SetProgressHandler(int nativeId, OnWebGLProgressDelegate onDownloadProgress, OnWebGLProgressDelegate onUploadProgress);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_CopyResponseTo(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] response, int length);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_Send(int nativeId, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] body, int length);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_GetResponseHeaders(int nativeId, OnWebGLBufferDelegate callback);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_GetStatusLine(int nativeId, OnWebGLBufferDelegate callback);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_Abort(int nativeId);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_Release(int nativeId);
+
+ [DllImport("__Internal")]
+ private static extern void XHR_SetLoglevel(int logLevel);
+
+#endregion
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs.meta b/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..a8744f4260573d33c5b914255909922010fa7611
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Connections/WebGLConnection.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: cca5a5d266418134dbb9c0b87c68e62a
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Cookies.meta b/Assets/Plugins/BestHTTP/Source/Cookies.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3767b0fb727aa6473abe5b672ed7c6ef77163a9a
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Cookies.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e138652289c643d4ca6bff547a5bd4f0
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs b/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bdf425dfbd40ca7c92c00846edf1241ac206f3db
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs
@@ -0,0 +1,377 @@
+#if !BESTHTTP_DISABLE_COOKIES
+
+using System;
+using System.Collections.Generic;
+using BestHTTP.Extensions;
+using System.IO;
+
+namespace BestHTTP.Cookies
+{
+ ///
+ /// The Cookie implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
+ ///
+ public sealed class Cookie : IComparable, IEquatable
+ {
+ private const int Version = 1;
+
+ #region Public Properties
+
+ ///
+ /// The name of the cookie.
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// The value of the cookie.
+ ///
+ public string Value { get; private set; }
+
+ ///
+ /// The Date when the Cookie is registered.
+ ///
+ public DateTime Date { get; internal set; }
+
+ ///
+ /// When this Cookie last used in a request.
+ ///
+ public DateTime LastAccess { get; set; }
+
+ ///
+ /// The Expires attribute indicates the maximum lifetime of the cookie, represented as the date and time at which the cookie expires.
+ /// The user agent is not required to retain the cookie until the specified date has passed.
+ /// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
+ ///
+ public DateTime Expires { get; private set; }
+
+ ///
+ /// The Max-Age attribute indicates the maximum lifetime of the cookie, represented as the number of seconds until the cookie expires.
+ /// The user agent is not required to retain the cookie for the specified duration.
+ /// In fact, user agents often evict cookies due to memory pressure or privacy concerns.
+ ///
+ public long MaxAge { get; private set; }
+
+ ///
+ /// If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie until "the current session is over".
+ ///
+ public bool IsSession { get; private set; }
+
+ ///
+ /// The Domain attribute specifies those hosts to which the cookie will be sent.
+ /// For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie
+ /// in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com.
+ /// If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.
+ ///
+ public string Domain { get; private set; }
+
+ ///
+ /// The scope of each cookie is limited to a set of paths, controlled by the Path attribute.
+ /// If the server omits the Path attribute, the user agent will use the "directory" of the request-uri's path component as the default value.
+ ///
+ public string Path { get; private set; }
+
+ ///
+ /// The Secure attribute limits the scope of the cookie to "secure" channels (where "secure" is defined by the user agent).
+ /// When a cookie has the Secure attribute, the user agent will include the cookie in an HTTP request only if the request is
+ /// transmitted over a secure channel (typically HTTP over Transport Layer Security (TLS)).
+ ///
+ public bool IsSecure { get; private set; }
+
+ ///
+ /// The HttpOnly attribute limits the scope of the cookie to HTTP requests.
+ /// In particular, the attribute instructs the user agent to omit the cookie when providing access to
+ /// cookies via "non-HTTP" APIs (such as a web browser API that exposes cookies to scripts).
+ ///
+ public bool IsHttpOnly { get; private set; }
+
+ ///
+ /// SameSite prevents the browser from sending this cookie along with cross-site requests.
+ /// The main goal is mitigate the risk of cross-origin information leakage.
+ /// It also provides some protection against cross-site request forgery attacks. Possible values for the flag are lax or strict.
+ ///
+ ///
+ public string SameSite { get; private set; }
+
+ #endregion
+
+ #region Public Constructors
+
+ public Cookie(string name, string value)
+ :this(name, value, "/", string.Empty)
+ {}
+
+ public Cookie(string name, string value, string path)
+ : this(name, value, path, string.Empty)
+ {}
+
+ public Cookie(string name, string value, string path, string domain)
+ :this() // call the parameter-less constructor to set default values
+ {
+ this.Name = name;
+ this.Value = value;
+ this.Path = path;
+ this.Domain = domain;
+ }
+
+ public Cookie(Uri uri, string name, string value, DateTime expires, bool isSession = true)
+ :this(name, value, uri.AbsolutePath, uri.Host)
+ {
+ this.Expires = expires;
+ this.IsSession = isSession;
+ this.Date = DateTime.UtcNow;
+ }
+
+ public Cookie(Uri uri, string name, string value, long maxAge = -1, bool isSession = true)
+ :this(name, value, uri.AbsolutePath, uri.Host)
+ {
+ this.MaxAge = maxAge;
+ this.IsSession = isSession;
+ this.Date = DateTime.UtcNow;
+ this.SameSite = "none";
+ }
+
+ #endregion
+
+ internal Cookie()
+ {
+ // If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie
+ // until "the current session is over" (as defined by the user agent).
+ IsSession = true;
+ MaxAge = -1;
+ LastAccess = DateTime.UtcNow;
+ }
+
+ public bool WillExpireInTheFuture()
+ {
+ // No Expires or Max-Age value sent from the server, we will fake the return value so we will not delete the newly came Cookie
+ if (IsSession)
+ return true;
+
+ // If a cookie has both the Max-Age and the Expires attribute, the Max-Age attribute has precedence and controls the expiration date of the cookie.
+ return MaxAge != -1 ?
+ Math.Max(0, (long)(DateTime.UtcNow - Date).TotalSeconds) < MaxAge :
+ Expires > DateTime.UtcNow;
+ }
+
+ ///
+ /// Guess the storage size of the cookie.
+ ///
+ ///
+ public uint GuessSize()
+ {
+ return (uint)((this.Name != null ? this.Name.Length * sizeof(char) : 0) +
+ (this.Value != null ? this.Value.Length * sizeof(char) : 0) +
+ (this.Domain != null ? this.Domain.Length * sizeof(char) : 0) +
+ (this.Path != null ? this.Path.Length * sizeof(char) : 0) +
+ (this.SameSite != null ? this.SameSite.Length * sizeof(char) : 0) +
+ (sizeof(long) * 4) +
+ (sizeof(bool) * 3));
+ }
+
+ public static Cookie Parse(string header, Uri defaultDomain, Logger.LoggingContext context)
+ {
+ Cookie cookie = new Cookie();
+ try
+ {
+ var kvps = ParseCookieHeader(header);
+
+ foreach (var kvp in kvps)
+ {
+ switch (kvp.Key.ToLowerInvariant())
+ {
+ case "path":
+ // If the attribute-value is empty or if the first character of the attribute-value is not %x2F ("/"):
+ // Let cookie-path be the default-path.
+ cookie.Path = string.IsNullOrEmpty(kvp.Value) || !kvp.Value.StartsWith("/") ? "/" : cookie.Path = kvp.Value;
+ break;
+
+ case "domain":
+ // If the attribute-value is empty, the behavior is undefined. However, the user agent SHOULD ignore the cookie-av entirely.
+ if (string.IsNullOrEmpty(kvp.Value))
+ return null;
+
+ // If the first character of the attribute-value string is %x2E ("."):
+ // Let cookie-domain be the attribute-value without the leading %x2E (".") character.
+ cookie.Domain = kvp.Value.StartsWith(".") ? kvp.Value.Substring(1) : kvp.Value;
+ break;
+
+ case "expires":
+ cookie.Expires = kvp.Value.ToDateTime(DateTime.FromBinary(0));
+ cookie.IsSession = false;
+ break;
+
+ case "max-age":
+ cookie.MaxAge = kvp.Value.ToInt64(-1);
+ cookie.IsSession = false;
+ break;
+
+ case "secure":
+ cookie.IsSecure = true;
+ break;
+
+ case "httponly":
+ cookie.IsHttpOnly = true;
+ break;
+
+ case "samesite":
+ cookie.SameSite = kvp.Value;
+ break;
+
+ default:
+ // check whether name is already set to avoid overwriting it with a non-listed setting
+ if (string.IsNullOrEmpty(cookie.Name))
+ {
+ cookie.Name = kvp.Key;
+ cookie.Value = kvp.Value;
+ }
+ break;
+ }
+ }
+
+ // Some user agents provide users the option of preventing persistent storage of cookies across sessions.
+ // When configured thusly, user agents MUST treat all received cookies as if the persistent-flag were set to false.
+ if (HTTPManager.EnablePrivateBrowsing)
+ cookie.IsSession = true;
+
+ // http://tools.ietf.org/html/rfc6265#section-4.1.2.3
+ // WARNING: Some existing user agents treat an absent Domain attribute as if the Domain attribute were present and contained the current host name.
+ // For example, if example.com returns a Set-Cookie header without a Domain attribute, these user agents will erroneously send the cookie to www.example.com as well.
+ if (string.IsNullOrEmpty(cookie.Domain))
+ cookie.Domain = defaultDomain.Host;
+
+ // http://tools.ietf.org/html/rfc6265#section-5.3 section 7:
+ // If the cookie-attribute-list contains an attribute with an attribute-name of "Path",
+ // set the cookie's path to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Path".
+ // __Otherwise, set the cookie's path to the default-path of the request-uri.__
+ if (string.IsNullOrEmpty(cookie.Path))
+ cookie.Path = defaultDomain.AbsolutePath;
+
+ cookie.Date = cookie.LastAccess = DateTime.UtcNow;
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Warning("Cookie", "Parse - Couldn't parse header: " + header + " exception: " + ex.ToString() + " " + ex.StackTrace, context);
+ }
+ return cookie;
+ }
+
+ #region Save & Load
+
+ internal void SaveTo(BinaryWriter stream)
+ {
+ stream.Write(Version);
+ stream.Write(Name ?? string.Empty);
+ stream.Write(Value ?? string.Empty);
+ stream.Write(Date.ToBinary());
+ stream.Write(LastAccess.ToBinary());
+ stream.Write(Expires.ToBinary());
+ stream.Write(MaxAge);
+ stream.Write(IsSession);
+ stream.Write(Domain ?? string.Empty);
+ stream.Write(Path ?? string.Empty);
+ stream.Write(IsSecure);
+ stream.Write(IsHttpOnly);
+ }
+
+ internal void LoadFrom(BinaryReader stream)
+ {
+ /*int version = */stream.ReadInt32();
+ this.Name = stream.ReadString();
+ this.Value = stream.ReadString();
+ this.Date = DateTime.FromBinary(stream.ReadInt64());
+ this.LastAccess = DateTime.FromBinary(stream.ReadInt64());
+ this.Expires = DateTime.FromBinary(stream.ReadInt64());
+ this.MaxAge = stream.ReadInt64();
+ this.IsSession = stream.ReadBoolean();
+ this.Domain = stream.ReadString();
+ this.Path = stream.ReadString();
+ this.IsSecure = stream.ReadBoolean();
+ this.IsHttpOnly = stream.ReadBoolean();
+ }
+
+ #endregion
+
+ #region Overrides and new Equals function
+
+ public override string ToString()
+ {
+ return string.Concat(this.Name, "=", this.Value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return false;
+
+ return this.Equals(obj as Cookie);
+ }
+
+ public bool Equals(Cookie cookie)
+ {
+ if (cookie == null)
+ return false;
+
+ if (Object.ReferenceEquals(this, cookie))
+ return true;
+
+ return this.Name.Equals(cookie.Name, StringComparison.Ordinal) &&
+ ((this.Domain == null && cookie.Domain == null) || this.Domain.Equals(cookie.Domain, StringComparison.Ordinal)) &&
+ ((this.Path == null && cookie.Path == null) || this.Path.Equals(cookie.Path, StringComparison.Ordinal));
+ }
+
+ public override int GetHashCode()
+ {
+ return this.ToString().GetHashCode();
+ }
+
+ #endregion
+
+ #region Private Helper Functions
+
+ private static string ReadValue(string str, ref int pos)
+ {
+ string result = string.Empty;
+ if (str == null)
+ return result;
+
+ return str.Read(ref pos, ';');
+ }
+
+ private static List ParseCookieHeader(string str)
+ {
+ List result = new List();
+
+ if (str == null)
+ return result;
+
+ int idx = 0;
+
+ // process the rest of the text
+ while (idx < str.Length)
+ {
+ // Read key
+ string key = str.Read(ref idx, (ch) => ch != '=' && ch != ';').Trim();
+ HeaderValue qp = new HeaderValue(key);
+
+ if (idx < str.Length && str[idx - 1] == '=')
+ qp.Value = ReadValue(str, ref idx);
+
+ result.Add(qp);
+ }
+
+ return result;
+ }
+
+ #endregion
+
+ #region IComparable implementation
+
+ public int CompareTo(Cookie other)
+ {
+ return this.LastAccess.CompareTo(other.LastAccess);
+ }
+
+ #endregion
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs.meta b/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..cee7bf166d0dad9fbdf302aeb8b4d6007b4554db
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Cookies/Cookie.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 3c89f8c2ca77c56448d03216927e0546
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs b/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3e651b82c5a1fd1c82e9f5b56fa072ee9cae54d6
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs
@@ -0,0 +1,538 @@
+#if !BESTHTTP_DISABLE_COOKIES
+
+using BestHTTP.Core;
+using BestHTTP.PlatformSupport.FileSystem;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace BestHTTP.Cookies
+{
+ ///
+ /// The Cookie Jar implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
+ ///
+ public static class CookieJar
+ {
+ // Version of the cookie store. It may be used in a future version for maintaining compatibility.
+ private const int Version = 1;
+
+ ///
+ /// Returns true if File apis are supported.
+ ///
+ public static bool IsSavingSupported
+ {
+ get
+ {
+#if !BESTHTTP_DISABLE_COOKIE_SAVE
+ if (IsSupportCheckDone)
+ return _isSavingSupported;
+
+ try
+ {
+#if UNITY_WEBGL && !UNITY_EDITOR
+ _isSavingSupported = false;
+#else
+ HTTPManager.IOService.DirectoryExists(HTTPManager.GetRootCacheFolder());
+ _isSavingSupported = true;
+#endif
+ }
+ catch
+ {
+ _isSavingSupported = false;
+
+ HTTPManager.Logger.Warning("CookieJar", "Cookie saving and loading disabled!");
+ }
+ finally
+ {
+ IsSupportCheckDone = true;
+ }
+
+ return _isSavingSupported;
+#else
+ return false;
+#endif
+ }
+ }
+
+ ///
+ /// The plugin will delete cookies that are accessed this threshold ago. Its default value is 7 days.
+ ///
+ public static TimeSpan AccessThreshold = TimeSpan.FromDays(7);
+
+#region Privates
+
+ ///
+ /// List of the Cookies
+ ///
+ private static List Cookies = new List();
+ private static string CookieFolder { get; set; }
+ private static string LibraryPath { get; set; }
+
+ ///
+ /// Synchronization object for thread safety.
+ ///
+ private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
+
+#if !BESTHTTP_DISABLE_COOKIE_SAVE
+ private static bool _isSavingSupported;
+ private static bool IsSupportCheckDone;
+#endif
+
+ private static bool Loaded;
+#endregion
+
+#region Internal Functions
+
+ internal static void SetupFolder()
+ {
+#if !BESTHTTP_DISABLE_COOKIE_SAVE
+ if (!CookieJar.IsSavingSupported)
+ return;
+
+ try
+ {
+ if (string.IsNullOrEmpty(CookieFolder) || string.IsNullOrEmpty(LibraryPath))
+ {
+ CookieFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Cookies");
+ LibraryPath = System.IO.Path.Combine(CookieFolder, "Library");
+ }
+ }
+ catch
+ { }
+#endif
+ }
+
+ ///
+ /// Will set or update all cookies from the response object.
+ ///
+ internal static bool Set(HTTPResponse response)
+ {
+ if (response == null)
+ return false;
+
+ List newCookies = new List();
+ var setCookieHeaders = response.GetHeaderValues("set-cookie");
+
+ // No cookies. :'(
+ if (setCookieHeaders == null)
+ return false;
+
+ foreach (var cookieHeader in setCookieHeaders)
+ {
+ Cookie cookie = Cookie.Parse(cookieHeader, response.baseRequest.CurrentUri, response.baseRequest.Context);
+ if (cookie != null)
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ int idx;
+ var old = Find(cookie, out idx);
+
+ // if no value for the cookie or already expired then the server asked us to delete the cookie
+ bool expired = string.IsNullOrEmpty(cookie.Value) || !cookie.WillExpireInTheFuture();
+
+ if (!expired)
+ {
+ // no old cookie, add it straight to the list
+ if (old == null)
+ {
+ Cookies.Add(cookie);
+
+ newCookies.Add(cookie);
+ }
+ else
+ {
+ // Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
+ cookie.Date = old.Date;
+ Cookies[idx] = cookie;
+
+ newCookies.Add(cookie);
+ }
+ }
+ else if (idx != -1) // delete the cookie
+ Cookies.RemoveAt(idx);
+ }
+ catch
+ {
+ // Ignore cookie on error
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+ }
+
+ response.Cookies = newCookies;
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
+
+ return true;
+ }
+
+ ///
+ /// Deletes all expired or 'old' cookies, and will keep the sum size of cookies under the given size.
+ ///
+ internal static void Maintain(bool sendEvent)
+ {
+ // It's not the same as in the rfc:
+ // http://tools.ietf.org/html/rfc6265#section-5.3
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ uint size = 0;
+
+ for (int i = 0; i < Cookies.Count; )
+ {
+ var cookie = Cookies[i];
+
+ // Remove expired or not used cookies
+ if (!cookie.WillExpireInTheFuture() || (cookie.LastAccess + AccessThreshold) < DateTime.UtcNow)
+ {
+ Cookies.RemoveAt(i);
+ }
+ else
+ {
+ if (!cookie.IsSession)
+ size += cookie.GuessSize();
+ i++;
+ }
+ }
+
+ if (size > HTTPManager.CookieJarSize)
+ {
+ Cookies.Sort();
+
+ while (size > HTTPManager.CookieJarSize && Cookies.Count > 0)
+ {
+ var cookie = Cookies[0];
+ Cookies.RemoveAt(0);
+
+ size -= cookie.GuessSize();
+ }
+ }
+ }
+ catch
+ { }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ if (sendEvent)
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
+ }
+
+ ///
+ /// Saves the Cookie Jar to a file.
+ ///
+ /// Not implemented under Unity WebPlayer
+ internal static void Persist()
+ {
+#if !BESTHTTP_DISABLE_COOKIE_SAVE
+ if (!IsSavingSupported)
+ return;
+
+ if (!Loaded)
+ return;
+
+ // Delete any expired cookie
+ Maintain(false);
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
+ HTTPManager.IOService.DirectoryCreate(CookieFolder);
+
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
+ using (var bw = new System.IO.BinaryWriter(fs))
+ {
+ bw.Write(Version);
+
+ // Count how many non-session cookies we have
+ int count = 0;
+ foreach (var cookie in Cookies)
+ if (!cookie.IsSession)
+ count++;
+
+ bw.Write(count);
+
+ // Save only the persistable cookies
+ foreach (var cookie in Cookies)
+ if (!cookie.IsSession)
+ cookie.SaveTo(bw);
+ }
+ }
+ catch
+ { }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+#endif
+ }
+
+ ///
+ /// Load previously persisted cookie library from the file.
+ ///
+ internal static void Load()
+ {
+#if !BESTHTTP_DISABLE_COOKIE_SAVE
+ if (!IsSavingSupported)
+ return;
+
+ if (Loaded)
+ return;
+
+ SetupFolder();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ Cookies.Clear();
+
+ if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
+ HTTPManager.IOService.DirectoryCreate(CookieFolder);
+
+ if (!HTTPManager.IOService.FileExists(LibraryPath))
+ return;
+
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
+ using (var br = new System.IO.BinaryReader(fs))
+ {
+ /*int version = */br.ReadInt32();
+ int cookieCount = br.ReadInt32();
+
+ for (int i = 0; i < cookieCount; ++i)
+ {
+ Cookie cookie = new Cookie();
+ cookie.LoadFrom(br);
+
+ if (cookie.WillExpireInTheFuture())
+ Cookies.Add(cookie);
+ }
+ }
+ }
+ catch
+ {
+ Cookies.Clear();
+ }
+ finally
+ {
+ Loaded = true;
+ rwLock.ExitWriteLock();
+ }
+#endif
+ }
+
+#endregion
+
+#region Public Functions
+
+ ///
+ /// Returns all Cookies that corresponds to the given Uri.
+ ///
+ public static List Get(Uri uri)
+ {
+ Load();
+
+ rwLock.EnterReadLock();
+ try
+ {
+ List result = null;
+
+ for (int i = 0; i < Cookies.Count; ++i)
+ {
+ Cookie cookie = Cookies[i];
+ if (cookie.WillExpireInTheFuture() && (uri.Host.IndexOf(cookie.Domain) != -1 || string.Format("{0}:{1}", uri.Host, uri.Port).IndexOf(cookie.Domain) != -1) && uri.AbsolutePath.StartsWith(cookie.Path))
+ {
+ if (result == null)
+ result = new List();
+
+ result.Add(cookie);
+ }
+ }
+
+ return result;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// Will add a new, or overwrite an old cookie if already exists.
+ ///
+ public static void Set(Uri uri, Cookie cookie)
+ {
+ Set(cookie);
+ }
+
+ ///
+ /// Will add a new, or overwrite an old cookie if already exists.
+ ///
+ public static void Set(Cookie cookie)
+ {
+ Load();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ int idx;
+ Find(cookie, out idx);
+
+ if (idx >= 0)
+ Cookies[idx] = cookie;
+ else
+ Cookies.Add(cookie);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
+ }
+
+ public static List GetAll()
+ {
+ Load();
+
+ return Cookies;
+ }
+
+ ///
+ /// Deletes all cookies from the Jar.
+ ///
+ public static void Clear()
+ {
+ Load();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ Cookies.Clear();
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ Persist();
+ }
+
+ ///
+ /// Removes cookies that older than the given parameter.
+ ///
+ public static void Clear(TimeSpan olderThan)
+ {
+ Load();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ for (int i = 0; i < Cookies.Count; )
+ {
+ var cookie = Cookies[i];
+
+ // Remove expired or not used cookies
+ if (!cookie.WillExpireInTheFuture() || (cookie.Date + olderThan) < DateTime.UtcNow)
+ Cookies.RemoveAt(i);
+ else
+ i++;
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ Persist();
+ }
+
+ ///
+ /// Removes cookies that matches to the given domain.
+ ///
+ public static void Clear(string domain)
+ {
+ Load();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ for (int i = 0; i < Cookies.Count; )
+ {
+ var cookie = Cookies[i];
+
+ // Remove expired or not used cookies
+ if (!cookie.WillExpireInTheFuture() || cookie.Domain.IndexOf(domain) != -1)
+ Cookies.RemoveAt(i);
+ else
+ i++;
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ Persist();
+ }
+
+ public static void Remove(Uri uri, string name)
+ {
+ Load();
+
+ rwLock.EnterWriteLock();
+ try
+ {
+ for (int i = 0; i < Cookies.Count; )
+ {
+ var cookie = Cookies[i];
+
+ if (cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && uri.Host.IndexOf(cookie.Domain) != -1)
+ Cookies.RemoveAt(i);
+ else
+ i++;
+ }
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+
+ PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
+ }
+
+#endregion
+
+#region Private Helper Functions
+
+ ///
+ /// Find and return a Cookie and his index in the list.
+ ///
+ private static Cookie Find(Cookie cookie, out int idx)
+ {
+ for (int i = 0; i < Cookies.Count; ++i)
+ {
+ Cookie c = Cookies[i];
+
+ if (c.Equals(cookie))
+ {
+ idx = i;
+ return c;
+ }
+ }
+
+ idx = -1;
+ return null;
+ }
+
+#endregion
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs.meta b/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..83631dca9c7f01c8d8ac3c2bbb51c34aac12b288
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Cookies/CookieJar.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: b6b22cd983735ee46908eac3f37eb798
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core.meta b/Assets/Plugins/BestHTTP/Source/Core.meta
new file mode 100644
index 0000000000000000000000000000000000000000..46d59a57a42200c71003910fca053d845c5e7d8e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1df2191ed468b024296665b1ad27ca11
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs b/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9c607d4386bc83b6b3cb9c2aef61c6995bc74e1d
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Concurrent;
+
+using BestHTTP.Connections;
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+
+namespace BestHTTP.Core
+{
+ public enum ConnectionEvents
+ {
+ StateChange,
+ ProtocolSupport
+ }
+
+ public
+#if CSHARP_7_OR_LATER
+ readonly
+#endif
+ struct ConnectionEventInfo
+ {
+ public readonly ConnectionBase Source;
+
+ public readonly ConnectionEvents Event;
+
+ public readonly HTTPConnectionStates State;
+
+ public readonly HostProtocolSupport ProtocolSupport;
+
+ public readonly HTTPRequest Request;
+
+ public ConnectionEventInfo(ConnectionBase sourceConn, ConnectionEvents @event)
+ {
+ this.Source = sourceConn;
+ this.Event = @event;
+
+ this.State = HTTPConnectionStates.Initial;
+
+ this.ProtocolSupport = HostProtocolSupport.Unknown;
+
+ this.Request = null;
+ }
+
+ public ConnectionEventInfo(ConnectionBase sourceConn, HTTPConnectionStates newState)
+ {
+ this.Source = sourceConn;
+
+ this.Event = ConnectionEvents.StateChange;
+
+ this.State = newState;
+
+ this.ProtocolSupport = HostProtocolSupport.Unknown;
+
+ this.Request = null;
+ }
+
+ public ConnectionEventInfo(ConnectionBase sourceConn, HostProtocolSupport protocolSupport)
+ {
+ this.Source = sourceConn;
+ this.Event = ConnectionEvents.ProtocolSupport;
+
+ this.State = HTTPConnectionStates.Initial;
+
+ this.ProtocolSupport = protocolSupport;
+
+ this.Request = null;
+ }
+
+ public ConnectionEventInfo(ConnectionBase sourceConn, HTTPRequest request)
+ {
+ this.Source = sourceConn;
+
+ this.Event = ConnectionEvents.StateChange;
+
+ this.State = HTTPConnectionStates.ClosedResendRequest;
+
+ this.ProtocolSupport = HostProtocolSupport.Unknown;
+
+ this.Request = request;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[ConnectionEventInfo SourceConnection: {0}, Event: {1}, State: {2}, ProtocolSupport: {3}]",
+ this.Source.ToString(), this.Event, this.State, this.ProtocolSupport);
+ }
+ }
+
+ public static class ConnectionEventHelper
+ {
+ private static ConcurrentQueue connectionEventQueue = new ConcurrentQueue();
+
+#pragma warning disable 0649
+ public static Action OnEvent;
+#pragma warning restore
+
+ public static void EnqueueConnectionEvent(ConnectionEventInfo @event)
+ {
+ connectionEventQueue.Enqueue(@event);
+ }
+
+ internal static void Clear()
+ {
+ connectionEventQueue.Clear();
+ }
+
+ internal static void ProcessQueue()
+ {
+ ConnectionEventInfo connectionEvent;
+ while (connectionEventQueue.TryDequeue(out connectionEvent))
+ {
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Information("ConnectionEventHelper", "Processing connection event: " + connectionEvent.ToString(), connectionEvent.Source.Context);
+
+ if (OnEvent != null)
+ {
+ try
+ {
+ OnEvent(connectionEvent);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("ConnectionEventHelper", "ProcessQueue", ex, connectionEvent.Source.Context);
+ }
+ }
+
+ switch (connectionEvent.Event)
+ {
+ case ConnectionEvents.StateChange:
+ HandleConnectionStateChange(connectionEvent);
+ break;
+
+ case ConnectionEvents.ProtocolSupport:
+ HostManager.GetHost(connectionEvent.Source.LastProcessedUri.Host)
+ .GetHostDefinition(connectionEvent.Source.ServerAddress)
+ .AddProtocol(connectionEvent.ProtocolSupport);
+ break;
+ }
+ }
+ }
+
+ private static void HandleConnectionStateChange(ConnectionEventInfo @event)
+ {
+ var connection = @event.Source;
+
+ switch (@event.State)
+ {
+ case HTTPConnectionStates.Recycle:
+ HostManager.GetHost(connection.LastProcessedUri.Host)
+ .GetHostDefinition(connection.ServerAddress)
+ .RecycleConnection(connection)
+ .TryToSendQueuedRequests();
+
+ break;
+
+ case HTTPConnectionStates.WaitForProtocolShutdown:
+ HostManager.GetHost(connection.LastProcessedUri.Host)
+ .GetHostDefinition(connection.ServerAddress)
+ .RemoveConnection(connection, @event.State);
+ break;
+
+ case HTTPConnectionStates.Closed:
+ case HTTPConnectionStates.ClosedResendRequest:
+ // in case of ClosedResendRequest
+ if (@event.Request != null)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(@event.Request, RequestEvents.Resend));
+
+ HostManager.GetHost(connection.LastProcessedUri.Host)
+ .GetHostDefinition(connection.ServerAddress)
+ .RemoveConnection(connection, @event.State)
+ .TryToSendQueuedRequests();
+ break;
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5c8cd9eecfad789e7e0c910b122aec5d9b2da0d8
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/ConnectionEvents.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 4c8c0b8f6a117514f8c6dfcc26c949e0
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs b/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..71130ed64ed35fd7d6a927c01ef63d333acd11de
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+
+using BestHTTP.Connections;
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+
+namespace BestHTTP.Core
+{
+ public enum HostProtocolSupport : byte
+ {
+ Unknown = 0x00,
+ HTTP1 = 0x01,
+ HTTP2 = 0x02
+ }
+
+ ///
+ /// A HostConnection object manages the connections to a host and the request queue.
+ ///
+ public sealed class HostConnection
+ {
+ public HostDefinition Host { get; private set; }
+
+ public string VariantId { get; private set; }
+
+ public HostProtocolSupport ProtocolSupport { get; private set; }
+ public DateTime LastProtocolSupportUpdate { get; private set; }
+
+ public int QueuedRequests { get { return this.Queue.Count; } }
+
+ public LoggingContext Context { get; private set; }
+
+ private List Connections = new List();
+ private List Queue = new List();
+
+ public HostConnection(HostDefinition host, string variantId)
+ {
+ this.Host = host;
+ this.VariantId = variantId;
+
+ this.Context = new LoggingContext(this);
+ this.Context.Add("Host", this.Host.Host);
+ this.Context.Add("VariantId", this.VariantId);
+ }
+
+ internal void AddProtocol(HostProtocolSupport protocolSupport)
+ {
+ this.LastProtocolSupportUpdate = DateTime.UtcNow;
+
+ var oldProtocol = this.ProtocolSupport;
+
+ if (oldProtocol != protocolSupport)
+ {
+ this.ProtocolSupport = protocolSupport;
+
+ HTTPManager.Logger.Information(typeof(HostConnection).Name, string.Format("AddProtocol({0}) - changing from {1} to {2}", this.VariantId, oldProtocol, protocolSupport), this.Context);
+
+ HostManager.Save();
+
+ TryToSendQueuedRequests();
+ }
+ }
+
+ internal HostConnection Send(HTTPRequest request)
+ {
+ var conn = GetNextAvailable(request);
+
+ if (conn != null)
+ {
+ request.State = HTTPRequestStates.Processing;
+
+ request.Prepare();
+
+ // then start process the request
+ conn.Process(request);
+ }
+ else
+ {
+ // If no free connection found and creation prohibited, we will put back to the queue
+ this.Queue.Add(request);
+ }
+
+ return this;
+ }
+
+ internal ConnectionBase GetNextAvailable(HTTPRequest request)
+ {
+ int activeConnections = 0;
+ ConnectionBase conn = null;
+ // Check the last created connection first. This way, if a higher level protocol is present that can handle more requests (== HTTP/2) that protocol will be chosen
+ // and others will be closed when their inactivity time is reached.
+ for (int i = Connections.Count - 1; i >= 0; --i)
+ {
+ conn = Connections[i];
+
+ if (conn.State == HTTPConnectionStates.Initial || conn.State == HTTPConnectionStates.Free || conn.CanProcessMultiple)
+ {
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - returning with connection. state: {0}, CanProcessMultiple: {1}", conn.State, conn.CanProcessMultiple), this.Context, request.Context, conn.Context);
+ return conn;
+ }
+
+ activeConnections++;
+ }
+
+ if (activeConnections >= HTTPManager.MaxConnectionPerServer)
+ {
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - activeConnections({0}) >= HTTPManager.MaxConnectionPerServer({1})", activeConnections, HTTPManager.MaxConnectionPerServer), this.Context, request.Context);
+ return null;
+ }
+
+ string key = HostDefinition.GetKeyForRequest(request);
+
+ conn = null;
+
+#if UNITY_WEBGL && !UNITY_EDITOR
+ conn = new WebGLConnection(key);
+#else
+ if (request.CurrentUri.IsFile)
+ conn = new FileConnection(key);
+ else
+ {
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL
+ // Hold back the creation of a new connection until we know more about the remote host's features.
+ // If we send out multiple requests at once it will execute the first and delay the others.
+ // While it will decrease performance initially, it will prevent the creation of TCP connections
+ // that will be unused after their first request processing if the server supports HTTP/2.
+ if (activeConnections >= 1 && (this.ProtocolSupport == HostProtocolSupport.Unknown || this.ProtocolSupport == HostProtocolSupport.HTTP2))
+ {
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - waiting for protocol support message. activeConnections: {0}, ProtocolSupport: {1} ", activeConnections, this.ProtocolSupport), this.Context, request.Context);
+ return null;
+ }
+#endif
+
+ conn = new HTTPConnection(key);
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("GetNextAvailable - creating new connection, key: {0} ", key), this.Context, request.Context, conn.Context);
+ }
+#endif
+
+ Connections.Add(conn);
+
+ return conn;
+ }
+
+ internal HostConnection RecycleConnection(ConnectionBase conn)
+ {
+ conn.State = HTTPConnectionStates.Free;
+
+ BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), conn, CloseConnectionAfterInactivity));
+
+ return this;
+ }
+
+ internal HostConnection RemoveConnection(ConnectionBase conn, HTTPConnectionStates setState)
+ {
+ conn.State = setState;
+ conn.Dispose();
+
+ bool found = this.Connections.Remove(conn);
+
+ if (!found)
+ HTTPManager.Logger.Warning(typeof(HostConnection).Name, string.Format("RemoveConnection - Couldn't find connection! key: {0}", conn.ServerAddress), this.Context, conn.Context);
+
+ return this;
+ }
+
+ internal HostConnection TryToSendQueuedRequests()
+ {
+ while (this.Queue.Count > 0 && GetNextAvailable(this.Queue[0]) != null)
+ {
+ Send(this.Queue[0]);
+ this.Queue.RemoveAt(0);
+ }
+
+ return this;
+ }
+
+ public ConnectionBase Find(Predicate match)
+ {
+ return this.Connections.Find(match);
+ }
+
+ private bool CloseConnectionAfterInactivity(DateTime now, object context)
+ {
+ var conn = context as ConnectionBase;
+
+ bool closeConnection = conn.State == HTTPConnectionStates.Free && now - conn.LastProcessTime >= conn.KeepAliveTime;
+ if (closeConnection)
+ {
+ HTTPManager.Logger.Information(typeof(HostConnection).Name, string.Format("CloseConnectionAfterInactivity - [{0}] Closing! State: {1}, Now: {2}, LastProcessTime: {3}, KeepAliveTime: {4}",
+ conn.ToString(), conn.State, now.ToString(System.Globalization.CultureInfo.InvariantCulture), conn.LastProcessTime.ToString(System.Globalization.CultureInfo.InvariantCulture), conn.KeepAliveTime), this.Context, conn.Context);
+
+ RemoveConnection(conn, HTTPConnectionStates.Closed);
+ return false;
+ }
+
+ // repeat until the connection's state is free
+ return conn.State == HTTPConnectionStates.Free;
+ }
+
+ internal void Shutdown()
+ {
+ this.Queue.Clear();
+
+ foreach (var conn in this.Connections)
+ {
+ // Swallow any exceptions, we are quitting anyway.
+ try
+ {
+ conn.Shutdown(ShutdownTypes.Immediate);
+ }
+ catch { }
+ }
+ //this.Connections.Clear();
+ }
+
+ internal void SaveTo(System.IO.BinaryWriter bw)
+ {
+ bw.Write(this.LastProtocolSupportUpdate.ToBinary());
+ bw.Write((byte)this.ProtocolSupport);
+ }
+
+ internal void LoadFrom(int version, System.IO.BinaryReader br)
+ {
+ this.LastProtocolSupportUpdate = DateTime.FromBinary(br.ReadInt64());
+ this.ProtocolSupport = (HostProtocolSupport)br.ReadByte();
+
+ if (DateTime.UtcNow - this.LastProtocolSupportUpdate >= TimeSpan.FromDays(1))
+ {
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("LoadFrom - Too Old! LastProtocolSupportUpdate: {0}, ProtocolSupport: {1}", this.LastProtocolSupportUpdate.ToString(System.Globalization.CultureInfo.InvariantCulture), this.ProtocolSupport), this.Context);
+ this.ProtocolSupport = HostProtocolSupport.Unknown;
+ }
+ else
+ HTTPManager.Logger.Verbose("HostConnection", string.Format("LoadFrom - LastProtocolSupportUpdate: {0}, ProtocolSupport: {1}", this.LastProtocolSupportUpdate.ToString(System.Globalization.CultureInfo.InvariantCulture), this.ProtocolSupport), this.Context);
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..459cd0699e7de14621357085339dc0fd2630e897
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostConnection.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: c4731d9a6e853854d845586222af3c67
+timeCreated: 1571210042
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs b/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6b882dfe37eb158680d7a5c39941a466eee8a5bc
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace BestHTTP.Core
+{
+ public sealed class HostDefinition
+ {
+ public string Host { get; private set; }
+
+ // alt-svc support:
+ // 1. When a request receives an alt-svc header send a plugin msg to the manager with all the details to route to the proper hostDefinition.
+ // 2. HostDefinition parses the header value
+ // 3. If there's at least one supported protocol found, start open a connection to that said alternate
+ // 4. If the new connection is open, route new requests to that connection
+ public List Alternates;
+
+ ///
+ /// Requests to the same host can require different connections: http, https, http + proxy, https + proxy, http2, http2 + proxy
+ ///
+ public Dictionary hostConnectionVariant = new Dictionary();
+
+ public HostDefinition(string host)
+ {
+ this.Host = host;
+ }
+
+ public HostConnection HasBetterAlternate(HTTPRequest request)
+ {
+ return null;
+ }
+
+ public HostConnection GetHostDefinition(HTTPRequest request)
+ {
+ string key = GetKeyForRequest(request);
+
+ return GetHostDefinition(key);
+ }
+
+ public HostConnection GetHostDefinition(string key)
+ {
+ HostConnection host = null;
+
+ if (!this.hostConnectionVariant.TryGetValue(key, out host))
+ this.hostConnectionVariant.Add(key, host = new HostConnection(this, key));
+
+ return host;
+ }
+
+ public void Send(HTTPRequest request)
+ {
+ GetHostDefinition(request)
+ .Send(request);
+ }
+
+ public void TryToSendQueuedRequests()
+ {
+ foreach (var kvp in hostConnectionVariant)
+ kvp.Value.TryToSendQueuedRequests();
+ }
+
+ public void HandleAltSvcHeader(HTTPResponse response)
+ {
+ var headerValues = response.GetHeaderValues("alt-svc");
+ if (headerValues == null)
+ HTTPManager.Logger.Warning(typeof(HostDefinition).Name, "Received HandleAltSvcHeader message, but no Alt-Svc header found!", response.Context);
+ }
+
+ public void HandleConnectProtocol(HTTP2ConnectProtocolInfo info)
+ {
+ HTTPManager.Logger.Information(typeof(HostDefinition).Name, string.Format("Received HandleConnectProtocol message. Connect protocol for host {0}. Enabled: {1}", info.Host, info.Enabled));
+ }
+
+ internal void Shutdown()
+ {
+ foreach (var kvp in this.hostConnectionVariant)
+ {
+ kvp.Value.Shutdown();
+ }
+ }
+
+ internal void SaveTo(System.IO.BinaryWriter bw)
+ {
+ bw.Write(this.hostConnectionVariant.Count);
+
+ foreach (var kvp in this.hostConnectionVariant)
+ {
+ bw.Write(kvp.Key.ToString());
+
+ kvp.Value.SaveTo(bw);
+ }
+ }
+
+ internal void LoadFrom(int version, System.IO.BinaryReader br)
+ {
+ int count = br.ReadInt32();
+ for (int i = 0; i < count; ++i)
+ {
+ GetHostDefinition(br.ReadString())
+ .LoadFrom(version, br);
+ }
+ }
+
+ private static System.Text.StringBuilder keyBuilder = new System.Text.StringBuilder(11);
+
+ // While a ReaderWriterLockSlim would be best with read and write locking and we use only WriteLock, it's still a lightweight locking mechanism instead of the lock statement.
+ private static System.Threading.ReaderWriterLockSlim keyBuilderLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.NoRecursion);
+
+ public static string GetKeyForRequest(HTTPRequest request)
+ {
+ return GetKeyFor(request.CurrentUri
+#if !BESTHTTP_DISABLE_PROXY
+ , request.Proxy
+#endif
+ );
+ }
+
+ public static string GetKeyFor(Uri uri
+#if !BESTHTTP_DISABLE_PROXY
+ , Proxy proxy
+#endif
+ )
+ {
+ if (uri.IsFile)
+ return uri.ToString();
+
+ keyBuilderLock.EnterWriteLock();
+
+ try
+ {
+ keyBuilder.Length = 0;
+
+#if !BESTHTTP_DISABLE_PROXY
+ if (proxy != null && proxy.UseProxyForAddress(uri))
+ {
+ keyBuilder.Append(proxy.Address.Scheme);
+ keyBuilder.Append("://");
+ keyBuilder.Append(proxy.Address.Host);
+ keyBuilder.Append(":");
+ keyBuilder.Append(proxy.Address.Port);
+ keyBuilder.Append(" @ ");
+ }
+#endif
+
+ keyBuilder.Append(uri.Scheme);
+ keyBuilder.Append("://");
+ keyBuilder.Append(uri.Host);
+ keyBuilder.Append(":");
+ keyBuilder.Append(uri.Port);
+
+ return keyBuilder.ToString();
+ }
+ finally
+ {
+ keyBuilderLock.ExitWriteLock();
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..fa642b6dee832bac4ba5557d72dfb63f1a255592
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostDefinition.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: bea9e2ccce9dba14194244739c9a37ac
+timeCreated: 1571210042
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs b/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ea4e442b8616e466bb47f11a2980a44706bdbb4f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs
@@ -0,0 +1,128 @@
+using BestHTTP.PlatformSupport.FileSystem;
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Core
+{
+ internal static class HostManager
+ {
+ private const int Version = 1;
+ private static string LibraryPath = string.Empty;
+ private static bool IsSaveAndLoadSupported = false;
+ private static bool IsLoaded = false;
+
+ private static Dictionary hosts = new Dictionary();
+
+ public static HostDefinition GetHost(string hostStr)
+ {
+ HostDefinition host;
+ if (!hosts.TryGetValue(hostStr, out host))
+ hosts.Add(hostStr, host = new HostDefinition(hostStr));
+
+ return host;
+ }
+
+ public static void TryToSendQueuedRequests()
+ {
+ foreach (var kvp in hosts)
+ kvp.Value.TryToSendQueuedRequests();
+ }
+
+ public static void Shutdown()
+ {
+ HTTPManager.Logger.Information("HostManager", "Shutdown initiated!");
+ foreach (var kvp in hosts)
+ kvp.Value.Shutdown();
+ }
+
+ public static void Clear()
+ {
+ HTTPManager.Logger.Information("HostManager", "Clearing hosts!");
+ hosts.Clear();
+ }
+
+ private static void SetupFolder()
+ {
+ if (string.IsNullOrEmpty(LibraryPath))
+ {
+ try
+ {
+ LibraryPath = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Hosts");
+ HTTPManager.IOService.FileExists(LibraryPath);
+ IsSaveAndLoadSupported = true;
+ }
+ catch
+ {
+ IsSaveAndLoadSupported = false;
+ HTTPManager.Logger.Warning("HostManager", "Save and load Disabled!");
+ }
+ }
+ }
+
+ public static void Save()
+ {
+ if (!IsSaveAndLoadSupported || string.IsNullOrEmpty(LibraryPath))
+ return;
+
+ try
+ {
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
+ using (var bw = new System.IO.BinaryWriter(fs))
+ {
+ bw.Write(Version);
+
+ bw.Write(hosts.Count);
+ foreach (var kvp in hosts)
+ {
+ bw.Write(kvp.Key.ToString());
+
+ kvp.Value.SaveTo(bw);
+ }
+ }
+ HTTPManager.Logger.Information("HostManager", hosts.Count + " hosts saved!");
+ }
+ catch
+ { }
+ }
+
+ public static void Load()
+ {
+ if (IsLoaded)
+ return;
+ IsLoaded = true;
+
+ SetupFolder();
+
+ if (!IsSaveAndLoadSupported || string.IsNullOrEmpty(LibraryPath) || !HTTPManager.IOService.FileExists(LibraryPath))
+ return;
+
+ try
+ {
+ using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
+ using (var br = new System.IO.BinaryReader(fs))
+ {
+ int version = br.ReadInt32();
+
+ int hostCount = br.ReadInt32();
+
+ for (int i = 0; i < hostCount; ++i)
+ {
+ GetHost(br.ReadString())
+ .LoadFrom(version, br);
+ }
+
+ HTTPManager.Logger.Information("HostManager", hostCount.ToString() + " HostDefinitions loaded!");
+ }
+ }
+ catch
+ {
+ try
+ {
+ HTTPManager.IOService.FileDelete(LibraryPath);
+ }
+ catch
+ { }
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..72a7a9420b43e1f24257da42b24990d995a337b1
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/HostManager.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 954a40ed8659ae247a8d51be385cd86c
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs b/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c0a00ed9e72133b8c99f315247d7fb3c1b2cf77e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs
@@ -0,0 +1,31 @@
+#if !UNITY_WEBGL || UNITY_EDITOR
+using System;
+using BestHTTP.Connections;
+using BestHTTP.Logger;
+
+namespace BestHTTP.Core
+{
+ public interface IHTTPRequestHandler : IDisposable
+ {
+ bool HasCustomRequestProcessor { get; }
+
+ KeepAliveHeader KeepAlive { get; }
+
+ bool CanProcessMultiple { get; }
+
+ ShutdownTypes ShutdownType { get; }
+
+ LoggingContext Context { get; }
+
+ void Process(HTTPRequest request);
+
+ void RunHandler();
+
+ ///
+ /// An immediate shutdown request that called only on application closure.
+ ///
+ void Shutdown(ShutdownTypes type);
+ }
+}
+
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..283c118f1ec9cd5f66a41c72af409a25a69b9b75
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/IHTTPRequestHandler.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2c82a52761c9d93439428e52e18e53d5
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs b/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs
new file mode 100644
index 0000000000000000000000000000000000000000..26cdf909faaa28b14691dc1885e98fc97301d4de
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace BestHTTP.Core
+{
+ public struct HostConnectionKey
+ {
+ public readonly string Host;
+ public readonly string Connection;
+
+ public HostConnectionKey(string host, string connection)
+ {
+ this.Host = host;
+ this.Connection = connection;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[HostConnectionKey Host: '{0}', Connection: '{1}']", this.Host, this.Connection);
+ }
+ }
+
+ public interface IProtocol : IDisposable
+ {
+ HostConnectionKey ConnectionKey { get; }
+
+ bool IsClosed { get; }
+
+ void HandleEvents();
+
+ void CancellationRequested();
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d05f98a0509dfb4c71f0aecfce206eb1d8f1a596
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/IProtocol.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 990ae7e763c243241babd6da91b2bb40
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs b/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs
new file mode 100644
index 0000000000000000000000000000000000000000..29f7aecebadab876583ba8149b077e572594f682
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs
@@ -0,0 +1,157 @@
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+using System;
+using System.Collections.Concurrent;
+
+namespace BestHTTP.Core
+{
+ public enum PluginEvents
+ {
+#if !BESTHTTP_DISABLE_COOKIES
+ SaveCookieLibrary,
+#endif
+
+ SaveCacheLibrary,
+
+ AltSvcHeader,
+
+ HTTP2ConnectProtocol
+ }
+
+ public
+#if CSHARP_7_OR_LATER
+ readonly
+#endif
+ struct PluginEventInfo
+ {
+ public readonly PluginEvents Event;
+ public readonly object Payload;
+
+ public PluginEventInfo(PluginEvents @event)
+ {
+ this.Event = @event;
+ this.Payload = null;
+ }
+
+ public PluginEventInfo(PluginEvents @event, object payload)
+ {
+ this.Event = @event;
+ this.Payload = payload;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[PluginEventInfo Event: {0}]", this.Event);
+ }
+ }
+
+ public static class PluginEventHelper
+ {
+ private static ConcurrentQueue pluginEvents = new ConcurrentQueue();
+
+#pragma warning disable 0649
+ public static Action OnEvent;
+#pragma warning restore
+
+ public static void EnqueuePluginEvent(PluginEventInfo @event)
+ {
+ pluginEvents.Enqueue(@event);
+ }
+
+ internal static void Clear()
+ {
+ pluginEvents.Clear();
+ }
+
+ internal static void ProcessQueue()
+ {
+#if !BESTHTTP_DISABLE_COOKIES
+ bool saveCookieLibrary = false;
+#endif
+
+#if !BESTHTTP_DISABLE_CACHING
+ bool saveCacheLibrary = false;
+#endif
+
+ PluginEventInfo pluginEvent;
+ while (pluginEvents.TryDequeue(out pluginEvent))
+ {
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Information("PluginEventHelper", "Processing plugin event: " + pluginEvent.ToString());
+
+ if (OnEvent != null)
+ {
+ try
+ {
+ OnEvent(pluginEvent);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("PluginEventHelper", "ProcessQueue", ex);
+ }
+ }
+
+ switch (pluginEvent.Event)
+ {
+#if !BESTHTTP_DISABLE_COOKIES
+ case PluginEvents.SaveCookieLibrary:
+ saveCookieLibrary = true;
+ break;
+#endif
+
+#if !BESTHTTP_DISABLE_CACHING
+ case PluginEvents.SaveCacheLibrary:
+ saveCacheLibrary = true;
+ break;
+#endif
+
+ case PluginEvents.AltSvcHeader:
+ AltSvcEventInfo altSvcEventInfo = pluginEvent.Payload as AltSvcEventInfo;
+ HostManager.GetHost(altSvcEventInfo.Host)
+ .HandleAltSvcHeader(altSvcEventInfo.Response);
+ break;
+
+ case PluginEvents.HTTP2ConnectProtocol:
+ HTTP2ConnectProtocolInfo info = pluginEvent.Payload as HTTP2ConnectProtocolInfo;
+ HostManager.GetHost(info.Host)
+ .HandleConnectProtocol(info);
+ break;
+ }
+ }
+
+#if !BESTHTTP_DISABLE_COOKIES
+ if (saveCookieLibrary)
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(Cookies.CookieJar.Persist);
+#endif
+
+#if !BESTHTTP_DISABLE_CACHING
+ if (saveCacheLibrary)
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving(Caching.HTTPCacheService.SaveLibrary);
+#endif
+ }
+ }
+
+ public sealed class AltSvcEventInfo
+ {
+ public readonly string Host;
+ public readonly HTTPResponse Response;
+
+ public AltSvcEventInfo(string host, HTTPResponse resp)
+ {
+ this.Host = host;
+ this.Response = resp;
+ }
+ }
+
+ public sealed class HTTP2ConnectProtocolInfo
+ {
+ public readonly string Host;
+ public readonly bool Enabled;
+
+ public HTTP2ConnectProtocolInfo(string host, bool enabled)
+ {
+ this.Host = host;
+ this.Enabled = enabled;
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..25696e3d1b8ee1ffefb3661ae1df3b06efcef91f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/PluginEvents.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 0f4588a7329bbf94da4c4413524a6543
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs b/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2a18c532d1c2e5722b530ed1aa5cbf57c71fd347
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs
@@ -0,0 +1,99 @@
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace BestHTTP.Core
+{
+ public
+#if CSHARP_7_OR_LATER
+ readonly
+#endif
+ struct ProtocolEventInfo
+ {
+ public readonly IProtocol Source;
+
+ public ProtocolEventInfo(IProtocol source)
+ {
+ this.Source = source;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[ProtocolEventInfo Source: {0}]", Source);
+ }
+ }
+
+ public static class ProtocolEventHelper
+ {
+ private static ConcurrentQueue protocolEvents = new ConcurrentQueue();
+ private static List ActiveProtocols = new List(2);
+
+#pragma warning disable 0649
+ public static Action OnEvent;
+#pragma warning restore
+
+ public static void EnqueueProtocolEvent(ProtocolEventInfo @event)
+ {
+ protocolEvents.Enqueue(@event);
+ }
+
+ internal static void Clear()
+ {
+ protocolEvents.Clear();
+ }
+
+ internal static void ProcessQueue()
+ {
+ ProtocolEventInfo protocolEvent;
+ while (protocolEvents.TryDequeue(out protocolEvent))
+ {
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Information("ProtocolEventHelper", "Processing protocol event: " + protocolEvent.ToString());
+
+ if (OnEvent != null)
+ {
+ try
+ {
+ OnEvent(protocolEvent);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("ProtocolEventHelper", "ProcessQueue", ex);
+ }
+ }
+
+ IProtocol protocol = protocolEvent.Source;
+
+ protocol.HandleEvents();
+
+ if (protocol.IsClosed)
+ {
+ ActiveProtocols.Remove(protocol);
+
+ HostManager.GetHost(protocol.ConnectionKey.Host)
+ .GetHostDefinition(protocol.ConnectionKey.Connection)
+ .TryToSendQueuedRequests();
+
+ protocol.Dispose();
+ }
+ }
+ }
+
+ internal static void AddProtocol(IProtocol protocol)
+ {
+ ActiveProtocols.Add(protocol);
+ }
+
+ internal static void CancelActiveProtocols()
+ {
+ for (int i = 0; i < ActiveProtocols.Count; ++i)
+ {
+ var protocol = ActiveProtocols[i];
+
+ protocol.CancellationRequested();
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7de219516fc8629038078b961cabaff57e6e88ae
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/ProtocolEvents.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 6f79c9054305c3942b7948fe994769bb
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs b/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c049cbb7452fb0ff18c0c937f50fe407590f6b6c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs
@@ -0,0 +1,455 @@
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+using BestHTTP.PlatformSupport.Memory;
+using BestHTTP.Timings;
+
+using System;
+using System.Collections.Concurrent;
+
+namespace BestHTTP.Core
+{
+ public enum RequestEvents
+ {
+ Upgraded,
+ DownloadProgress,
+ UploadProgress,
+ StreamingData,
+ StateChange,
+ Resend,
+ Headers,
+ TimingData
+ }
+
+ public
+#if CSHARP_7_OR_LATER
+ readonly
+#endif
+ struct RequestEventInfo
+ {
+ public readonly HTTPRequest SourceRequest;
+ public readonly RequestEvents Event;
+
+ public readonly HTTPRequestStates State;
+
+ public readonly long Progress;
+ public readonly long ProgressLength;
+
+ public readonly byte[] Data;
+ public readonly int DataLength;
+
+ // Timing Data
+ public readonly string Name;
+ public readonly DateTime Time;
+ public readonly TimeSpan Duration;
+
+ public RequestEventInfo(HTTPRequest request, RequestEvents @event)
+ {
+ this.SourceRequest = request;
+ this.Event = @event;
+
+ this.State = HTTPRequestStates.Initial;
+
+ this.Progress = this.ProgressLength = 0;
+
+ this.Data = null;
+ this.DataLength = 0;
+
+ // TimingData
+ this.Name = null;
+ this.Time = DateTime.MinValue;
+ this.Duration = TimeSpan.Zero;
+ }
+
+ public RequestEventInfo(HTTPRequest request, HTTPRequestStates newState)
+ {
+ this.SourceRequest = request;
+ this.Event = RequestEvents.StateChange;
+ this.State = newState;
+
+ this.Progress = this.ProgressLength = 0;
+ this.Data = null;
+ this.DataLength = 0;
+
+ // TimingData
+ this.Name = null;
+ this.Time = DateTime.MinValue;
+ this.Duration = TimeSpan.Zero;
+ }
+
+ public RequestEventInfo(HTTPRequest request, RequestEvents @event, long progress, long progressLength)
+ {
+ this.SourceRequest = request;
+ this.Event = @event;
+ this.State = HTTPRequestStates.Initial;
+
+ this.Progress = progress;
+ this.ProgressLength = progressLength;
+ this.Data = null;
+ this.DataLength = 0;
+
+ // TimingData
+ this.Name = null;
+ this.Time = DateTime.MinValue;
+ this.Duration = TimeSpan.Zero;
+ }
+
+ public RequestEventInfo(HTTPRequest request, byte[] data, int dataLength)
+ {
+ this.SourceRequest = request;
+ this.Event = RequestEvents.StreamingData;
+ this.State = HTTPRequestStates.Initial;
+
+ this.Progress = this.ProgressLength = 0;
+ this.Data = data;
+ this.DataLength = dataLength;
+
+ // TimingData
+ this.Name = null;
+ this.Time = DateTime.MinValue;
+ this.Duration = TimeSpan.Zero;
+ }
+
+ public RequestEventInfo(HTTPRequest request, string name, DateTime time)
+ {
+ this.SourceRequest = request;
+ this.Event = RequestEvents.TimingData;
+ this.State = HTTPRequestStates.Initial;
+
+ this.Progress = this.ProgressLength = 0;
+ this.Data = null;
+ this.DataLength = 0;
+
+ // TimingData
+ this.Name = name;
+ this.Time = time;
+ this.Duration = TimeSpan.Zero;
+ }
+
+ public RequestEventInfo(HTTPRequest request, string name, TimeSpan duration)
+ {
+ this.SourceRequest = request;
+ this.Event = RequestEvents.TimingData;
+ this.State = HTTPRequestStates.Initial;
+
+ this.Progress = this.ProgressLength = 0;
+ this.Data = null;
+ this.DataLength = 0;
+
+ // TimingData
+ this.Name = name;
+ this.Time = DateTime.Now;
+ this.Duration = duration;
+ }
+
+ public override string ToString()
+ {
+ switch (this.Event)
+ {
+ case RequestEvents.Upgraded:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Upgraded]", this.SourceRequest.CurrentUri);
+ case RequestEvents.DownloadProgress:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: DownloadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
+ case RequestEvents.UploadProgress:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: UploadProgress, Progress: {1}, ProgressLength: {2}]", this.SourceRequest.CurrentUri, this.Progress, this.ProgressLength);
+ case RequestEvents.StreamingData:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StreamingData, DataLength: {1}]", this.SourceRequest.CurrentUri, this.DataLength);
+ case RequestEvents.StateChange:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: StateChange, State: {1}]", this.SourceRequest.CurrentUri, this.State);
+ case RequestEvents.Resend:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Resend]", this.SourceRequest.CurrentUri);
+ case RequestEvents.Headers:
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: Headers]", this.SourceRequest.CurrentUri);
+ case RequestEvents.TimingData:
+ if (this.Duration == TimeSpan.Zero)
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}]", this.SourceRequest.CurrentUri, this.Name, this.Time);
+ else
+ return string.Format("[RequestEventInfo SourceRequest: {0}, Event: TimingData, Name: {1}, Time: {2}, Duration: {3}]", this.SourceRequest.CurrentUri, this.Name, this.Time, this.Duration);
+ default:
+ throw new NotImplementedException(this.Event.ToString());
+ }
+ }
+ }
+
+ public static class RequestEventHelper
+ {
+ private static ConcurrentQueue requestEventQueue = new ConcurrentQueue();
+
+#pragma warning disable 0649
+ public static Action OnEvent;
+#pragma warning restore
+
+ public static void EnqueueRequestEvent(RequestEventInfo @event)
+ {
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Information("RequestEventHelper", "Enqueue request event: " + @event.ToString(), @event.SourceRequest.Context);
+
+ requestEventQueue.Enqueue(@event);
+ }
+
+ internal static void Clear()
+ {
+ requestEventQueue.Clear();
+ }
+
+ internal static void ProcessQueue()
+ {
+ RequestEventInfo requestEvent;
+ while (requestEventQueue.TryDequeue(out requestEvent))
+ {
+ HTTPRequest source = requestEvent.SourceRequest;
+
+ if (HTTPManager.Logger.Level == Loglevels.All)
+ HTTPManager.Logger.Information("RequestEventHelper", "Processing request event: " + requestEvent.ToString(), source.Context);
+
+ if (OnEvent != null)
+ {
+ try
+ {
+ OnEvent(requestEvent);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "ProcessQueue", ex, source.Context);
+ }
+ }
+
+ switch (requestEvent.Event)
+ {
+ case RequestEvents.StreamingData:
+ {
+ var response = source.Response;
+ if (response != null)
+ System.Threading.Interlocked.Decrement(ref response.UnprocessedFragments);
+
+ bool reuseBuffer = true;
+ try
+ {
+ if (source.OnStreamingData != null)
+ reuseBuffer = source.OnStreamingData(source, response, requestEvent.Data, requestEvent.DataLength);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.StreamingData", ex, source.Context);
+ }
+
+ if (reuseBuffer)
+ BufferPool.Release(requestEvent.Data);
+ break;
+ }
+
+ case RequestEvents.DownloadProgress:
+ try
+ {
+ if (source.OnDownloadProgress != null)
+ source.OnDownloadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.DownloadProgress", ex, source.Context);
+ }
+ break;
+
+ case RequestEvents.UploadProgress:
+ try
+ {
+ if (source.OnUploadProgress != null)
+ source.OnUploadProgress(source, requestEvent.Progress, requestEvent.ProgressLength);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.UploadProgress", ex, source.Context);
+ }
+ break;
+
+ case RequestEvents.Upgraded:
+ try
+ {
+ if (source.OnUpgraded != null)
+ source.OnUpgraded(source, source.Response);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Upgraded", ex, source.Context);
+ }
+
+ IProtocol protocol = source.Response as IProtocol;
+ if (protocol != null)
+ ProtocolEventHelper.AddProtocol(protocol);
+ break;
+
+ case RequestEvents.Resend:
+ source.State = HTTPRequestStates.Initial;
+
+ var host = HostManager.GetHost(source.CurrentUri.Host);
+
+ host.Send(source);
+
+ break;
+
+ case RequestEvents.Headers:
+ {
+ try
+ {
+ var response = source.Response;
+ if (source.OnHeadersReceived != null && response != null)
+ source.OnHeadersReceived(source, response);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "Process RequestEventQueue - RequestEvents.Headers", ex, source.Context);
+ }
+ break;
+ }
+
+ case RequestEvents.StateChange:
+ try
+ {
+ RequestEventHelper.HandleRequestStateChange(requestEvent);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange", ex, source.Context);
+ }
+ break;
+
+ case RequestEvents.TimingData:
+ source.Timing.AddEvent(requestEvent.Name, requestEvent.Time, requestEvent.Duration);
+ break;
+ }
+ }
+ }
+
+ private static bool AbortRequestWhenTimedOut(DateTime now, object context)
+ {
+ HTTPRequest request = context as HTTPRequest;
+
+ if (request.State >= HTTPRequestStates.Finished)
+ return false; // don't repeat
+
+ // Protocols will shut down themselves
+ if (request.Response is IProtocol)
+ return false;
+
+ if (request.IsTimedOut)
+ {
+ HTTPManager.Logger.Information("RequestEventHelper", "AbortRequestWhenTimedOut - Request timed out. CurrentUri: " + request.CurrentUri.ToString(), request.Context);
+ request.Abort();
+
+ return false; // don't repeat
+ }
+
+ return true; // repeat
+ }
+
+ internal static void HandleRequestStateChange(RequestEventInfo @event)
+ {
+ HTTPRequest source = @event.SourceRequest;
+
+ // Because there's a race condition between setting the request's State in its Abort() function running on Unity's main thread
+ // and the HTTP1/HTTP2 handlers running on an another one.
+ // Because of these race conditions cases violating expectations can be:
+ // 1.) State is finished but the response null
+ // 2.) State is (Connection)TimedOut and the response non-null
+ // We have to make sure that no callbacks are called twice and in the request must be in a consistent state!
+
+ // State | Request
+ // --------- +---------
+ // 1 Null
+ // Finished | Skip
+ // Timeout/Abort | Deliver
+ //
+ // 2 Non-Null
+ // Finished | Deliver
+ // Timeout/Abort | Skip
+
+ switch (@event.State)
+ {
+ case HTTPRequestStates.Queued:
+ source.QueuedAt = DateTime.UtcNow;
+ if ((!source.UseStreaming && source.UploadStream == null) || source.EnableTimoutForStreaming)
+ BestHTTP.Extensions.Timer.Add(new TimerData(TimeSpan.FromSeconds(1), @event.SourceRequest, AbortRequestWhenTimedOut));
+ break;
+
+ case HTTPRequestStates.ConnectionTimedOut:
+ case HTTPRequestStates.TimedOut:
+ case HTTPRequestStates.Error:
+ case HTTPRequestStates.Aborted:
+ source.Response = null;
+ goto case HTTPRequestStates.Finished;
+
+ case HTTPRequestStates.Finished:
+
+#if !BESTHTTP_DISABLE_CACHING
+ // Here we will try to load content for a failed load. Failed load is a request with ConnectionTimedOut, TimedOut or Error state.
+ // A request with Finished state but response with status code >= 500 also something that we will try to load from the cache.
+ // We have to set what we going to try to load here too (other place is inside IsCachedEntityExpiresInTheFuture) as we don't want to load a cached content for
+ // a request that just finished without any problem!
+
+ try
+ {
+ bool tryLoad = !source.DisableCache && source.State != HTTPRequestStates.Aborted && (source.State != HTTPRequestStates.Finished || source.Response == null || source.Response.StatusCode >= 500);
+ if (tryLoad && Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(source))
+ {
+ HTTPManager.Logger.Information("RequestEventHelper", "IsCachedEntityExpiresInTheFuture check returned true! CurrentUri: " + source.CurrentUri.ToString(), source.Context);
+
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving((req) =>
+ {
+ // Disable any other cache activity.
+ req.DisableCache = true;
+
+ var originalState = req.State;
+ if (Connections.ConnectionHelper.TryLoadAllFromCache("RequestEventHelper", req, req.Context))
+ {
+ if (req.State != HTTPRequestStates.Finished)
+ req.State = HTTPRequestStates.Finished;
+ else
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, HTTPRequestStates.Finished));
+ }
+ else
+ {
+ HTTPManager.Logger.Information("RequestEventHelper", "TryLoadAllFromCache failed to load! CurrentUri: " + req.CurrentUri.ToString(), source.Context);
+
+ // If for some reason it couldn't load we place back the request to the queue.
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(req, originalState));
+ }
+ }, source);
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", string.Format("HandleRequestStateChange - Cache probe - CurrentUri: \"{0}\" State: {1} StatusCode: {2}", source.CurrentUri, source.State, source.Response != null ? source.Response.StatusCode : 0), ex, source.Context);
+ }
+#endif
+
+ source.Timing.AddEvent(TimingEventNames.Queued_For_Disptach, DateTime.Now, TimeSpan.Zero);
+ source.Timing.AddEvent(TimingEventNames.Finished, DateTime.Now, DateTime.Now - source.Timing.Start);
+
+ if (source.Callback != null)
+ {
+ try
+ {
+ source.Callback(source, source.Response);
+
+ source.Timing.AddEvent(TimingEventNames.Callback, DateTime.Now, TimeSpan.Zero);
+
+ if (HTTPManager.Logger.Level <= Loglevels.Information)
+ HTTPManager.Logger.Information("RequestEventHelper", "Finishing request. Timings: " + source.Timing.ToString(), source.Context);
+
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("RequestEventHelper", "HandleRequestStateChange " + @event.State, ex, source.Context);
+ }
+ }
+
+ source.Dispose();
+
+ HostManager.GetHost(source.CurrentUri.Host)
+ .GetHostDefinition(HostDefinition.GetKeyForRequest(source))
+ .TryToSendQueuedRequests();
+ break;
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs.meta b/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3879a7408d6d0b680b98426998c932e10050b570
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Core/RequestEvents.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 69a63e18840e18947be1b714e8abb476
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression.meta b/Assets/Plugins/BestHTTP/Source/Decompression.meta
new file mode 100644
index 0000000000000000000000000000000000000000..14dbc3bf55f109d26cc9bad862276c33251a2360
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4d4075eb3d5bdee4c9f373d710808d60
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs b/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a7065d7e7329ee5218ec157516a93115b4c9d898
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs
@@ -0,0 +1,473 @@
+// CRC32.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2011 Dino Chiesa.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// Last Saved: <2011-August-02 18:25:54>
+//
+// ------------------------------------------------------------------
+//
+// This module defines the CRC32 class, which can do the CRC32 algorithm, using
+// arbitrary starting polynomials, and bit reversal. The bit reversal is what
+// distinguishes this CRC-32 used in BZip2 from the CRC-32 that is used in PKZIP
+// files, or GZIP files. This class does both.
+//
+// ------------------------------------------------------------------
+
+
+using System;
+using Interop = System.Runtime.InteropServices;
+
+namespace BestHTTP.Decompression.Crc
+{
+ ///
+ /// Computes a CRC-32. The CRC-32 algorithm is parameterized - you
+ /// can set the polynomial and enable or disable bit
+ /// reversal. This can be used for GZIP, BZip2, or ZIP.
+ ///
+ ///
+ /// This type is used internally by DotNetZip; it is generally not used
+ /// directly by applications wishing to create, read, or manipulate zip
+ /// archive files.
+ ///
+
+ internal class CRC32
+ {
+ ///
+ /// Indicates the total number of bytes applied to the CRC.
+ ///
+ public Int64 TotalBytesRead
+ {
+ get
+ {
+ return _TotalBytesRead;
+ }
+ }
+
+ ///
+ /// Indicates the current CRC for all blocks slurped in.
+ ///
+ public Int32 Crc32Result
+ {
+ get
+ {
+ return unchecked((Int32)(~_register));
+ }
+ }
+
+ ///
+ /// Returns the CRC32 for the specified stream.
+ ///
+ /// The stream over which to calculate the CRC32
+ /// the CRC32 calculation
+ public Int32 GetCrc32(System.IO.Stream input)
+ {
+ return GetCrc32AndCopy(input, null);
+ }
+
+ ///
+ /// Returns the CRC32 for the specified stream, and writes the input into the
+ /// output stream.
+ ///
+ /// The stream over which to calculate the CRC32
+ /// The stream into which to deflate the input
+ /// the CRC32 calculation
+ public Int32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output)
+ {
+ if (input == null)
+ throw new Exception("The input stream must not be null.");
+
+ unchecked
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int readSize = BUFFER_SIZE;
+
+ _TotalBytesRead = 0;
+ int count = input.Read(buffer, 0, readSize);
+ if (output != null) output.Write(buffer, 0, count);
+ _TotalBytesRead += count;
+ while (count > 0)
+ {
+ SlurpBlock(buffer, 0, count);
+ count = input.Read(buffer, 0, readSize);
+ if (output != null) output.Write(buffer, 0, count);
+ _TotalBytesRead += count;
+ }
+
+ return (Int32)(~_register);
+ }
+ }
+
+
+ ///
+ /// Get the CRC32 for the given (word,byte) combo. This is a
+ /// computation defined by PKzip for PKZIP 2.0 (weak) encryption.
+ ///
+ /// The word to start with.
+ /// The byte to combine it with.
+ /// The CRC-ized result.
+ public Int32 ComputeCrc32(Int32 W, byte B)
+ {
+ return _InternalComputeCrc32((UInt32)W, B);
+ }
+
+ internal Int32 _InternalComputeCrc32(UInt32 W, byte B)
+ {
+ return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8));
+ }
+
+
+ ///
+ /// Update the value for the running CRC32 using the given block of bytes.
+ /// This is useful when using the CRC32() class in a Stream.
+ ///
+ /// block of bytes to slurp
+ /// starting point in the block
+ /// how many bytes within the block to slurp
+ public void SlurpBlock(byte[] block, int offset, int count)
+ {
+ if (block == null)
+ throw new Exception("The data buffer must not be null.");
+
+ // bzip algorithm
+ for (int i = 0; i < count; i++)
+ {
+ int x = offset + i;
+ byte b = block[x];
+ if (this.reverseBits)
+ {
+ UInt32 temp = (_register >> 24) ^ b;
+ _register = (_register << 8) ^ crc32Table[temp];
+ }
+ else
+ {
+ UInt32 temp = (_register & 0x000000FF) ^ b;
+ _register = (_register >> 8) ^ crc32Table[temp];
+ }
+ }
+ _TotalBytesRead += count;
+ }
+
+
+ ///
+ /// Process one byte in the CRC.
+ ///
+ /// the byte to include into the CRC .
+ public void UpdateCRC(byte b)
+ {
+ if (this.reverseBits)
+ {
+ UInt32 temp = (_register >> 24) ^ b;
+ _register = (_register << 8) ^ crc32Table[temp];
+ }
+ else
+ {
+ UInt32 temp = (_register & 0x000000FF) ^ b;
+ _register = (_register >> 8) ^ crc32Table[temp];
+ }
+ }
+
+ ///
+ /// Process a run of N identical bytes into the CRC.
+ ///
+ ///
+ ///
+ /// This method serves as an optimization for updating the CRC when a
+ /// run of identical bytes is found. Rather than passing in a buffer of
+ /// length n, containing all identical bytes b, this method accepts the
+ /// byte value and the length of the (virtual) buffer - the length of
+ /// the run.
+ ///
+ ///
+ /// the byte to include into the CRC.
+ /// the number of times that byte should be repeated.
+ public void UpdateCRC(byte b, int n)
+ {
+ while (n-- > 0)
+ {
+ if (this.reverseBits)
+ {
+ uint temp = (_register >> 24) ^ b;
+ _register = (_register << 8) ^ crc32Table[(temp >= 0)
+ ? temp
+ : (temp + 256)];
+ }
+ else
+ {
+ UInt32 temp = (_register & 0x000000FF) ^ b;
+ _register = (_register >> 8) ^ crc32Table[(temp >= 0)
+ ? temp
+ : (temp + 256)];
+
+ }
+ }
+ }
+
+
+
+ private static uint ReverseBits(uint data)
+ {
+ unchecked
+ {
+ uint ret = data;
+ ret = (ret & 0x55555555) << 1 | (ret >> 1) & 0x55555555;
+ ret = (ret & 0x33333333) << 2 | (ret >> 2) & 0x33333333;
+ ret = (ret & 0x0F0F0F0F) << 4 | (ret >> 4) & 0x0F0F0F0F;
+ ret = (ret << 24) | ((ret & 0xFF00) << 8) | ((ret >> 8) & 0xFF00) | (ret >> 24);
+ return ret;
+ }
+ }
+
+ private static byte ReverseBits(byte data)
+ {
+ unchecked
+ {
+ uint u = (uint)data * 0x00020202;
+ uint m = 0x01044010;
+ uint s = u & m;
+ uint t = (u << 2) & (m << 1);
+ return (byte)((0x01001001 * (s + t)) >> 24);
+ }
+ }
+
+
+
+ private void GenerateLookupTable()
+ {
+ crc32Table = new UInt32[256];
+ unchecked
+ {
+ UInt32 dwCrc;
+ byte i = 0;
+ do
+ {
+ dwCrc = i;
+ for (byte j = 8; j > 0; j--)
+ {
+ if ((dwCrc & 1) == 1)
+ {
+ dwCrc = (dwCrc >> 1) ^ dwPolynomial;
+ }
+ else
+ {
+ dwCrc >>= 1;
+ }
+ }
+ if (reverseBits)
+ {
+ crc32Table[ReverseBits(i)] = ReverseBits(dwCrc);
+ }
+ else
+ {
+ crc32Table[i] = dwCrc;
+ }
+ i++;
+ } while (i!=0);
+ }
+
+#if VERBOSE
+ Console.WriteLine();
+ Console.WriteLine("private static readonly UInt32[] crc32Table = {");
+ for (int i = 0; i < crc32Table.Length; i+=4)
+ {
+ Console.Write(" ");
+ for (int j=0; j < 4; j++)
+ {
+ Console.Write(" 0x{0:X8}U,", crc32Table[i+j]);
+ }
+ Console.WriteLine();
+ }
+ Console.WriteLine("};");
+ Console.WriteLine();
+#endif
+ }
+
+
+ private uint gf2_matrix_times(uint[] matrix, uint vec)
+ {
+ uint sum = 0;
+ int i=0;
+ while (vec != 0)
+ {
+ if ((vec & 0x01)== 0x01)
+ sum ^= matrix[i];
+ vec >>= 1;
+ i++;
+ }
+ return sum;
+ }
+
+ private void gf2_matrix_square(uint[] square, uint[] mat)
+ {
+ for (int i = 0; i < 32; i++)
+ square[i] = gf2_matrix_times(mat, mat[i]);
+ }
+
+
+
+ ///
+ /// Combines the given CRC32 value with the current running total.
+ ///
+ ///
+ /// This is useful when using a divide-and-conquer approach to
+ /// calculating a CRC. Multiple threads can each calculate a
+ /// CRC32 on a segment of the data, and then combine the
+ /// individual CRC32 values at the end.
+ ///
+ /// the crc value to be combined with this one
+ /// the length of data the CRC value was calculated on
+ public void Combine(int crc, int length)
+ {
+ uint[] even = new uint[32]; // even-power-of-two zeros operator
+ uint[] odd = new uint[32]; // odd-power-of-two zeros operator
+
+ if (length == 0)
+ return;
+
+ uint crc1= ~_register;
+ uint crc2= (uint) crc;
+
+ // put operator for one zero bit in odd
+ odd[0] = this.dwPolynomial; // the CRC-32 polynomial
+ uint row = 1;
+ for (int i = 1; i < 32; i++)
+ {
+ odd[i] = row;
+ row <<= 1;
+ }
+
+ // put operator for two zero bits in even
+ gf2_matrix_square(even, odd);
+
+ // put operator for four zero bits in odd
+ gf2_matrix_square(odd, even);
+
+ uint len2 = (uint) length;
+
+ // apply len2 zeros to crc1 (first square will put the operator for one
+ // zero byte, eight zero bits, in even)
+ do {
+ // apply zeros operator for this bit of len2
+ gf2_matrix_square(even, odd);
+
+ if ((len2 & 1)== 1)
+ crc1 = gf2_matrix_times(even, crc1);
+ len2 >>= 1;
+
+ if (len2 == 0)
+ break;
+
+ // another iteration of the loop with odd and even swapped
+ gf2_matrix_square(odd, even);
+ if ((len2 & 1)==1)
+ crc1 = gf2_matrix_times(odd, crc1);
+ len2 >>= 1;
+
+
+ } while (len2 != 0);
+
+ crc1 ^= crc2;
+
+ _register= ~crc1;
+
+ //return (int) crc1;
+ return;
+ }
+
+
+ ///
+ /// Create an instance of the CRC32 class using the default settings: no
+ /// bit reversal, and a polynomial of 0xEDB88320.
+ ///
+ public CRC32() : this(false)
+ {
+ }
+
+ ///
+ /// Create an instance of the CRC32 class, specifying whether to reverse
+ /// data bits or not.
+ ///
+ ///
+ /// specify true if the instance should reverse data bits.
+ ///
+ ///
+ ///
+ /// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
+ /// want a CRC32 with compatibility with BZip2, you should pass true
+ /// here. In the CRC-32 used by GZIP and PKZIP, the bits are not
+ /// reversed; Therefore if you want a CRC32 with compatibility with
+ /// those, you should pass false.
+ ///
+ ///
+ public CRC32(bool reverseBits) :
+ this( unchecked((int)0xEDB88320), reverseBits)
+ {
+ }
+
+
+ ///
+ /// Create an instance of the CRC32 class, specifying the polynomial and
+ /// whether to reverse data bits or not.
+ ///
+ ///
+ /// The polynomial to use for the CRC, expressed in the reversed (LSB)
+ /// format: the highest ordered bit in the polynomial value is the
+ /// coefficient of the 0th power; the second-highest order bit is the
+ /// coefficient of the 1 power, and so on. Expressed this way, the
+ /// polynomial for the CRC-32C used in IEEE 802.3, is 0xEDB88320.
+ ///
+ ///
+ /// specify true if the instance should reverse data bits.
+ ///
+ ///
+ ///
+ ///
+ /// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
+ /// want a CRC32 with compatibility with BZip2, you should pass true
+ /// here for the reverseBits parameter. In the CRC-32 used by
+ /// GZIP and PKZIP, the bits are not reversed; Therefore if you want a
+ /// CRC32 with compatibility with those, you should pass false for the
+ /// reverseBits parameter.
+ ///
+ ///
+ public CRC32(int polynomial, bool reverseBits)
+ {
+ this.reverseBits = reverseBits;
+ this.dwPolynomial = (uint) polynomial;
+ this.GenerateLookupTable();
+ }
+
+ ///
+ /// Reset the CRC-32 class - clear the CRC "remainder register."
+ ///
+ ///
+ ///
+ /// Use this when employing a single instance of this class to compute
+ /// multiple, distinct CRCs on multiple, distinct data blocks.
+ ///
+ ///
+ public void Reset()
+ {
+ _register = 0xFFFFFFFFU;
+ }
+
+ // private member vars
+ private UInt32 dwPolynomial;
+ private Int64 _TotalBytesRead;
+ private bool reverseBits;
+ private UInt32[] crc32Table;
+ private const int BUFFER_SIZE = 8192;
+ private UInt32 _register = 0xFFFFFFFFU;
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..31284208699dbca7abb3fb58139ee9e0bdb6c34d
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/CRC32.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 16fc99dd9503b7046bed4bfdbbfec3c0
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs b/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a196dc82aa9455d18d6a8e7843905787a0f8a49c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs
@@ -0,0 +1,1884 @@
+// Deflate.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2011-August-03 19:52:15>
+//
+// ------------------------------------------------------------------
+//
+// This module defines logic for handling the Deflate or compression.
+//
+// This code is based on multiple sources:
+// - the original zlib v1.2.3 source, which is Copyright (C) 1995-2005 Jean-loup Gailly.
+// - the original jzlib, which is Copyright (c) 2000-2003 ymnk, JCraft,Inc.
+//
+// However, this code is significantly different from both.
+// The object model is not the same, and many of the behaviors are different.
+//
+// In keeping with the license for these other works, the copyrights for
+// jzlib and zlib are here.
+//
+// -----------------------------------------------------------------------
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+using BestHTTP.PlatformSupport.Memory;
+using System;
+
+#pragma warning disable 0675
+
+namespace BestHTTP.Decompression.Zlib
+{
+
+ internal enum BlockState
+ {
+ NeedMore = 0, // block not completed, need more input or more output
+ BlockDone, // block flush performed
+ FinishStarted, // finish started, need only more output at next deflate
+ FinishDone // finish done, accept no more input or output
+ }
+
+ internal enum DeflateFlavor
+ {
+ Store,
+ Fast,
+ Slow
+ }
+
+ internal sealed class DeflateManager
+ {
+ private static readonly int MEM_LEVEL_MAX = 9;
+ private static readonly int MEM_LEVEL_DEFAULT = 8;
+
+ internal delegate BlockState CompressFunc(FlushType flush);
+
+ internal class Config
+ {
+ // Use a faster search when the previous match is longer than this
+ internal int GoodLength; // reduce lazy search above this match length
+
+ // Attempt to find a better match only when the current match is
+ // strictly smaller than this value. This mechanism is used only for
+ // compression levels >= 4. For levels 1,2,3: MaxLazy is actually
+ // MaxInsertLength. (See DeflateFast)
+
+ internal int MaxLazy; // do not perform lazy search above this match length
+
+ internal int NiceLength; // quit search above this match length
+
+ // To speed up deflation, hash chains are never searched beyond this
+ // length. A higher limit improves compression ratio but degrades the speed.
+
+ internal int MaxChainLength;
+
+ internal DeflateFlavor Flavor;
+
+ private Config(int goodLength, int maxLazy, int niceLength, int maxChainLength, DeflateFlavor flavor)
+ {
+ this.GoodLength = goodLength;
+ this.MaxLazy = maxLazy;
+ this.NiceLength = niceLength;
+ this.MaxChainLength = maxChainLength;
+ this.Flavor = flavor;
+ }
+
+ public static Config Lookup(CompressionLevel level)
+ {
+ return Table[(int)level];
+ }
+
+
+ static Config()
+ {
+ Table = new Config[] {
+ new Config(0, 0, 0, 0, DeflateFlavor.Store),
+ new Config(4, 4, 8, 4, DeflateFlavor.Fast),
+ new Config(4, 5, 16, 8, DeflateFlavor.Fast),
+ new Config(4, 6, 32, 32, DeflateFlavor.Fast),
+
+ new Config(4, 4, 16, 16, DeflateFlavor.Slow),
+ new Config(8, 16, 32, 32, DeflateFlavor.Slow),
+ new Config(8, 16, 128, 128, DeflateFlavor.Slow),
+ new Config(8, 32, 128, 256, DeflateFlavor.Slow),
+ new Config(32, 128, 258, 1024, DeflateFlavor.Slow),
+ new Config(32, 258, 258, 4096, DeflateFlavor.Slow),
+ };
+ }
+
+ private static readonly Config[] Table;
+ }
+
+
+ private CompressFunc DeflateFunction;
+
+ private static readonly System.String[] _ErrorMessage = new System.String[]
+ {
+ "need dictionary",
+ "stream end",
+ "",
+ "file error",
+ "stream error",
+ "data error",
+ "insufficient memory",
+ "buffer error",
+ "incompatible version",
+ ""
+ };
+
+ // preset dictionary flag in zlib header
+ private static readonly int PRESET_DICT = 0x20;
+
+ private static readonly int INIT_STATE = 42;
+ private static readonly int BUSY_STATE = 113;
+ private static readonly int FINISH_STATE = 666;
+
+ // The deflate compression method
+ private static readonly int Z_DEFLATED = 8;
+
+ private static readonly int STORED_BLOCK = 0;
+ private static readonly int STATIC_TREES = 1;
+ private static readonly int DYN_TREES = 2;
+
+ // The three kinds of block type
+ private static readonly int Z_BINARY = 0;
+ private static readonly int Z_ASCII = 1;
+ private static readonly int Z_UNKNOWN = 2;
+
+ private static readonly int Buf_size = 8 * 2;
+
+ private static readonly int MIN_MATCH = 3;
+ private static readonly int MAX_MATCH = 258;
+
+ private static readonly int MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
+
+ private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1);
+
+ private static readonly int END_BLOCK = 256;
+
+ internal ZlibCodec _codec; // the zlib encoder/decoder
+ internal int status; // as the name implies
+ internal byte[] pending; // output still pending - waiting to be compressed
+ internal int nextPending; // index of next pending byte to output to the stream
+ internal int pendingCount; // number of bytes in the pending buffer
+
+ internal sbyte data_type; // UNKNOWN, BINARY or ASCII
+ internal int last_flush; // value of flush param for previous deflate call
+
+ internal int w_size; // LZ77 window size (32K by default)
+ internal int w_bits; // log2(w_size) (8..16)
+ internal int w_mask; // w_size - 1
+
+ //internal byte[] dictionary;
+ internal byte[] window;
+
+ // Sliding window. Input bytes are read into the second half of the window,
+ // and move to the first half later to keep a dictionary of at least wSize
+ // bytes. With this organization, matches are limited to a distance of
+ // wSize-MAX_MATCH bytes, but this ensures that IO is always
+ // performed with a length multiple of the block size.
+ //
+ // To do: use the user input buffer as sliding window.
+
+ internal int window_size;
+ // Actual size of window: 2*wSize, except when the user input buffer
+ // is directly used as sliding window.
+
+ internal short[] prev;
+ // Link to older string with same hash index. To limit the size of this
+ // array to 64K, this link is maintained only for the last 32K strings.
+ // An index in this array is thus a window index modulo 32K.
+
+ internal short[] head; // Heads of the hash chains or NIL.
+
+ internal int ins_h; // hash index of string to be inserted
+ internal int hash_size; // number of elements in hash table
+ internal int hash_bits; // log2(hash_size)
+ internal int hash_mask; // hash_size-1
+
+ // Number of bits by which ins_h must be shifted at each input
+ // step. It must be such that after MIN_MATCH steps, the oldest
+ // byte no longer takes part in the hash key, that is:
+ // hash_shift * MIN_MATCH >= hash_bits
+ internal int hash_shift;
+
+ // Window position at the beginning of the current output block. Gets
+ // negative when the window is moved backwards.
+
+ internal int block_start;
+
+ Config config;
+ internal int match_length; // length of best match
+ internal int prev_match; // previous match
+ internal int match_available; // set if previous match exists
+ internal int strstart; // start of string to insert into.....????
+ internal int match_start; // start of matching string
+ internal int lookahead; // number of valid bytes ahead in window
+
+ // Length of the best match at previous step. Matches not greater than this
+ // are discarded. This is used in the lazy match evaluation.
+ internal int prev_length;
+
+ // Insert new strings in the hash table only if the match length is not
+ // greater than this length. This saves time but degrades compression.
+ // max_insert_length is used only for compression levels <= 3.
+
+ internal CompressionLevel compressionLevel; // compression level (1..9)
+ internal CompressionStrategy compressionStrategy; // favor or force Huffman coding
+
+
+ internal short[] dyn_ltree; // literal and length tree
+ internal short[] dyn_dtree; // distance tree
+ internal short[] bl_tree; // Huffman tree for bit lengths
+
+ internal ZTree treeLiterals = new ZTree(); // desc for literal tree
+ internal ZTree treeDistances = new ZTree(); // desc for distance tree
+ internal ZTree treeBitLengths = new ZTree(); // desc for bit length tree
+
+ // number of codes at each bit length for an optimal tree
+ internal short[] bl_count = new short[InternalConstants.MAX_BITS + 1];
+
+ // heap used to build the Huffman trees
+ internal int[] heap = new int[2 * InternalConstants.L_CODES + 1];
+
+ internal int heap_len; // number of elements in the heap
+ internal int heap_max; // element of largest frequency
+
+ // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ // The same heap array is used to build all trees.
+
+ // Depth of each subtree used as tie breaker for trees of equal frequency
+ internal sbyte[] depth = new sbyte[2 * InternalConstants.L_CODES + 1];
+
+ internal int _lengthOffset; // index for literals or lengths
+
+
+ // Size of match buffer for literals/lengths. There are 4 reasons for
+ // limiting lit_bufsize to 64K:
+ // - frequencies can be kept in 16 bit counters
+ // - if compression is not successful for the first block, all input
+ // data is still in the window so we can still emit a stored block even
+ // when input comes from standard input. (This can also be done for
+ // all blocks if lit_bufsize is not greater than 32K.)
+ // - if compression is not successful for a file smaller than 64K, we can
+ // even emit a stored file instead of a stored block (saving 5 bytes).
+ // This is applicable only for zip (not gzip or zlib).
+ // - creating new Huffman trees less frequently may not provide fast
+ // adaptation to changes in the input data statistics. (Take for
+ // example a binary file with poorly compressible code followed by
+ // a highly compressible string table.) Smaller buffer sizes give
+ // fast adaptation but have of course the overhead of transmitting
+ // trees more frequently.
+
+ internal int lit_bufsize;
+
+ internal int last_lit; // running index in l_buf
+
+ // Buffer for distances. To simplify the code, d_buf and l_buf have
+ // the same number of elements. To use different lengths, an extra flag
+ // array would be necessary.
+
+ internal int _distanceOffset; // index into pending; points to distance data??
+
+ internal int opt_len; // bit length of current block with optimal trees
+ internal int static_len; // bit length of current block with static trees
+ internal int matches; // number of string matches in current block
+ internal int last_eob_len; // bit length of EOB code for last block
+
+ // Output buffer. bits are inserted starting at the bottom (least
+ // significant bits).
+ internal short bi_buf;
+
+ // Number of valid bits in bi_buf. All bits above the last valid bit
+ // are always zero.
+ internal int bi_valid;
+
+
+ internal DeflateManager()
+ {
+ dyn_ltree = new short[HEAP_SIZE * 2];
+ dyn_dtree = new short[(2 * InternalConstants.D_CODES + 1) * 2]; // distance tree
+ bl_tree = new short[(2 * InternalConstants.BL_CODES + 1) * 2]; // Huffman tree for bit lengths
+ }
+
+
+ // lm_init
+ private void _InitializeLazyMatch()
+ {
+ window_size = 2 * w_size;
+
+ // clear the hash - workitem 9063
+ Array.Clear(head, 0, hash_size);
+ //for (int i = 0; i < hash_size; i++) head[i] = 0;
+
+ config = Config.Lookup(compressionLevel);
+ SetDeflater();
+
+ strstart = 0;
+ block_start = 0;
+ lookahead = 0;
+ match_length = prev_length = MIN_MATCH - 1;
+ match_available = 0;
+ ins_h = 0;
+ }
+
+ // Initialize the tree data structures for a new zlib stream.
+ private void _InitializeTreeData()
+ {
+ treeLiterals.dyn_tree = dyn_ltree;
+ treeLiterals.staticTree = StaticTree.Literals;
+
+ treeDistances.dyn_tree = dyn_dtree;
+ treeDistances.staticTree = StaticTree.Distances;
+
+ treeBitLengths.dyn_tree = bl_tree;
+ treeBitLengths.staticTree = StaticTree.BitLengths;
+
+ bi_buf = 0;
+ bi_valid = 0;
+ last_eob_len = 8; // enough lookahead for inflate
+
+ // Initialize the first block of the first file:
+ _InitializeBlocks();
+ }
+
+ internal void _InitializeBlocks()
+ {
+ // Initialize the trees.
+ for (int i = 0; i < InternalConstants.L_CODES; i++)
+ dyn_ltree[i * 2] = 0;
+ for (int i = 0; i < InternalConstants.D_CODES; i++)
+ dyn_dtree[i * 2] = 0;
+ for (int i = 0; i < InternalConstants.BL_CODES; i++)
+ bl_tree[i * 2] = 0;
+
+ dyn_ltree[END_BLOCK * 2] = 1;
+ opt_len = static_len = 0;
+ last_lit = matches = 0;
+ }
+
+ // Restore the heap property by moving down the tree starting at node k,
+ // exchanging a node with the smallest of its two sons if necessary, stopping
+ // when the heap property is re-established (each father smaller than its
+ // two sons).
+ internal void pqdownheap(short[] tree, int k)
+ {
+ int v = heap[k];
+ int j = k << 1; // left son of k
+ while (j <= heap_len)
+ {
+ // Set j to the smallest of the two sons:
+ if (j < heap_len && _IsSmaller(tree, heap[j + 1], heap[j], depth))
+ {
+ j++;
+ }
+ // Exit if v is smaller than both sons
+ if (_IsSmaller(tree, v, heap[j], depth))
+ break;
+
+ // Exchange v with the smallest son
+ heap[k] = heap[j]; k = j;
+ // And continue down the tree, setting j to the left son of k
+ j <<= 1;
+ }
+ heap[k] = v;
+ }
+
+ internal static bool _IsSmaller(short[] tree, int n, int m, sbyte[] depth)
+ {
+ short tn2 = tree[n * 2];
+ short tm2 = tree[m * 2];
+ return (tn2 < tm2 || (tn2 == tm2 && depth[n] <= depth[m]));
+ }
+
+
+ // Scan a literal or distance tree to determine the frequencies of the codes
+ // in the bit length tree.
+ internal void scan_tree(short[] tree, int max_code)
+ {
+ int n; // iterates over all tree elements
+ int prevlen = -1; // last emitted length
+ int curlen; // length of current code
+ int nextlen = (int)tree[0 * 2 + 1]; // length of next code
+ int count = 0; // repeat count of the current code
+ int max_count = 7; // max repeat count
+ int min_count = 4; // min repeat count
+
+ if (nextlen == 0)
+ {
+ max_count = 138; min_count = 3;
+ }
+ tree[(max_code + 1) * 2 + 1] = (short)0x7fff; // guard //??
+
+ for (n = 0; n <= max_code; n++)
+ {
+ curlen = nextlen; nextlen = (int)tree[(n + 1) * 2 + 1];
+ if (++count < max_count && curlen == nextlen)
+ {
+ continue;
+ }
+ else if (count < min_count)
+ {
+ bl_tree[curlen * 2] = (short)(bl_tree[curlen * 2] + count);
+ }
+ else if (curlen != 0)
+ {
+ if (curlen != prevlen)
+ bl_tree[curlen * 2]++;
+ bl_tree[InternalConstants.REP_3_6 * 2]++;
+ }
+ else if (count <= 10)
+ {
+ bl_tree[InternalConstants.REPZ_3_10 * 2]++;
+ }
+ else
+ {
+ bl_tree[InternalConstants.REPZ_11_138 * 2]++;
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0)
+ {
+ max_count = 138; min_count = 3;
+ }
+ else if (curlen == nextlen)
+ {
+ max_count = 6; min_count = 3;
+ }
+ else
+ {
+ max_count = 7; min_count = 4;
+ }
+ }
+ }
+
+ // Construct the Huffman tree for the bit lengths and return the index in
+ // bl_order of the last bit length code to send.
+ internal int build_bl_tree()
+ {
+ int max_blindex; // index of last bit length code of non zero freq
+
+ // Determine the bit length frequencies for literal and distance trees
+ scan_tree(dyn_ltree, treeLiterals.max_code);
+ scan_tree(dyn_dtree, treeDistances.max_code);
+
+ // Build the bit length tree:
+ treeBitLengths.build_tree(this);
+ // opt_len now includes the length of the tree representations, except
+ // the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+
+ // Determine the number of bit length codes to send. The pkzip format
+ // requires that at least 4 bit length codes be sent. (appnote.txt says
+ // 3 but the actual value used is 4.)
+ for (max_blindex = InternalConstants.BL_CODES - 1; max_blindex >= 3; max_blindex--)
+ {
+ if (bl_tree[ZTree.bl_order[max_blindex] * 2 + 1] != 0)
+ break;
+ }
+ // Update opt_len to include the bit length tree and counts
+ opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+
+ return max_blindex;
+ }
+
+
+ // Send the header for a block using dynamic Huffman trees: the counts, the
+ // lengths of the bit length codes, the literal tree and the distance tree.
+ // IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ internal void send_all_trees(int lcodes, int dcodes, int blcodes)
+ {
+ int rank; // index in bl_order
+
+ send_bits(lcodes - 257, 5); // not +255 as stated in appnote.txt
+ send_bits(dcodes - 1, 5);
+ send_bits(blcodes - 4, 4); // not -3 as stated in appnote.txt
+ for (rank = 0; rank < blcodes; rank++)
+ {
+ send_bits(bl_tree[ZTree.bl_order[rank] * 2 + 1], 3);
+ }
+ send_tree(dyn_ltree, lcodes - 1); // literal tree
+ send_tree(dyn_dtree, dcodes - 1); // distance tree
+ }
+
+ // Send a literal or distance tree in compressed form, using the codes in
+ // bl_tree.
+ internal void send_tree(short[] tree, int max_code)
+ {
+ int n; // iterates over all tree elements
+ int prevlen = -1; // last emitted length
+ int curlen; // length of current code
+ int nextlen = tree[0 * 2 + 1]; // length of next code
+ int count = 0; // repeat count of the current code
+ int max_count = 7; // max repeat count
+ int min_count = 4; // min repeat count
+
+ if (nextlen == 0)
+ {
+ max_count = 138; min_count = 3;
+ }
+
+ for (n = 0; n <= max_code; n++)
+ {
+ curlen = nextlen; nextlen = tree[(n + 1) * 2 + 1];
+ if (++count < max_count && curlen == nextlen)
+ {
+ continue;
+ }
+ else if (count < min_count)
+ {
+ do
+ {
+ send_code(curlen, bl_tree);
+ }
+ while (--count != 0);
+ }
+ else if (curlen != 0)
+ {
+ if (curlen != prevlen)
+ {
+ send_code(curlen, bl_tree); count--;
+ }
+ send_code(InternalConstants.REP_3_6, bl_tree);
+ send_bits(count - 3, 2);
+ }
+ else if (count <= 10)
+ {
+ send_code(InternalConstants.REPZ_3_10, bl_tree);
+ send_bits(count - 3, 3);
+ }
+ else
+ {
+ send_code(InternalConstants.REPZ_11_138, bl_tree);
+ send_bits(count - 11, 7);
+ }
+ count = 0; prevlen = curlen;
+ if (nextlen == 0)
+ {
+ max_count = 138; min_count = 3;
+ }
+ else if (curlen == nextlen)
+ {
+ max_count = 6; min_count = 3;
+ }
+ else
+ {
+ max_count = 7; min_count = 4;
+ }
+ }
+ }
+
+ // Output a block of bytes on the stream.
+ // IN assertion: there is enough room in pending_buf.
+ private void put_bytes(byte[] p, int start, int len)
+ {
+ Array.Copy(p, start, pending, pendingCount, len);
+ pendingCount += len;
+ }
+
+#if NOTNEEDED
+ private void put_byte(byte c)
+ {
+ pending[pendingCount++] = c;
+ }
+ internal void put_short(int b)
+ {
+ unchecked
+ {
+ pending[pendingCount++] = (byte)b;
+ pending[pendingCount++] = (byte)(b >> 8);
+ }
+ }
+ internal void putShortMSB(int b)
+ {
+ unchecked
+ {
+ pending[pendingCount++] = (byte)(b >> 8);
+ pending[pendingCount++] = (byte)b;
+ }
+ }
+#endif
+
+ internal void send_code(int c, short[] tree)
+ {
+ int c2 = c * 2;
+ send_bits((tree[c2] & 0xffff), (tree[c2 + 1] & 0xffff));
+ }
+
+ internal void send_bits(int value, int length)
+ {
+ int len = length;
+ unchecked
+ {
+ if (bi_valid > (int)Buf_size - len)
+ {
+ //int val = value;
+ // bi_buf |= (val << bi_valid);
+
+ bi_buf |= (short)((value << bi_valid) & 0xffff);
+ //put_short(bi_buf);
+ pending[pendingCount++] = (byte)bi_buf;
+ pending[pendingCount++] = (byte)(bi_buf >> 8);
+
+
+ bi_buf = (short)((uint)value >> (Buf_size - bi_valid));
+ bi_valid += len - Buf_size;
+ }
+ else
+ {
+ // bi_buf |= (value) << bi_valid;
+ bi_buf |= (short)((value << bi_valid) & 0xffff);
+ bi_valid += len;
+ }
+ }
+ }
+
+ // Send one empty static block to give enough lookahead for inflate.
+ // This takes 10 bits, of which 7 may remain in the bit buffer.
+ // The current inflate code requires 9 bits of lookahead. If the
+ // last two codes for the previous block (real code plus EOB) were coded
+ // on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode
+ // the last real code. In this case we send two empty static blocks instead
+ // of one. (There are no problems if the previous block is stored or fixed.)
+ // To simplify the code, we assume the worst case of last real code encoded
+ // on one bit only.
+ internal void _tr_align()
+ {
+ send_bits(STATIC_TREES << 1, 3);
+ send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes);
+
+ bi_flush();
+
+ // Of the 10 bits for the empty block, we have already sent
+ // (10 - bi_valid) bits. The lookahead for the last real code (before
+ // the EOB of the previous block) was thus at least one plus the length
+ // of the EOB plus what we have just sent of the empty static block.
+ if (1 + last_eob_len + 10 - bi_valid < 9)
+ {
+ send_bits(STATIC_TREES << 1, 3);
+ send_code(END_BLOCK, StaticTree.lengthAndLiteralsTreeCodes);
+ bi_flush();
+ }
+ last_eob_len = 7;
+ }
+
+
+ // Save the match info and tally the frequency counts. Return true if
+ // the current block must be flushed.
+ internal bool _tr_tally(int dist, int lc)
+ {
+ pending[_distanceOffset + last_lit * 2] = unchecked((byte) ( (uint)dist >> 8 ) );
+ pending[_distanceOffset + last_lit * 2 + 1] = unchecked((byte)dist);
+ pending[_lengthOffset + last_lit] = unchecked((byte)lc);
+ last_lit++;
+
+ if (dist == 0)
+ {
+ // lc is the unmatched char
+ dyn_ltree[lc * 2]++;
+ }
+ else
+ {
+ matches++;
+ // Here, lc is the match length - MIN_MATCH
+ dist--; // dist = match distance - 1
+ dyn_ltree[(ZTree.LengthCode[lc] + InternalConstants.LITERALS + 1) * 2]++;
+ dyn_dtree[ZTree.DistanceCode(dist) * 2]++;
+ }
+
+ if ((last_lit & 0x1fff) == 0 && (int)compressionLevel > 2)
+ {
+ // Compute an upper bound for the compressed length
+ int out_length = last_lit << 3;
+ int in_length = strstart - block_start;
+ int dcode;
+ for (dcode = 0; dcode < InternalConstants.D_CODES; dcode++)
+ {
+ out_length = (int)(out_length + (int)dyn_dtree[dcode * 2] * (5L + ZTree.ExtraDistanceBits[dcode]));
+ }
+ out_length >>= 3;
+ if ((matches < (last_lit / 2)) && out_length < in_length / 2)
+ return true;
+ }
+
+ return (last_lit == lit_bufsize - 1) || (last_lit == lit_bufsize);
+ // dinoch - wraparound?
+ // We avoid equality with lit_bufsize because of wraparound at 64K
+ // on 16 bit machines and because stored blocks are restricted to
+ // 64K-1 bytes.
+ }
+
+
+
+ // Send the block data compressed using the given Huffman trees
+ internal void send_compressed_block(short[] ltree, short[] dtree)
+ {
+ int distance; // distance of matched string
+ int lc; // match length or unmatched char (if dist == 0)
+ int lx = 0; // running index in l_buf
+ int code; // the code to send
+ int extra; // number of extra bits to send
+
+ if (last_lit != 0)
+ {
+ do
+ {
+ int ix = _distanceOffset + lx * 2;
+ distance = ((pending[ix] << 8) & 0xff00) |
+ (pending[ix + 1] & 0xff);
+ lc = (pending[_lengthOffset + lx]) & 0xff;
+ lx++;
+
+ if (distance == 0)
+ {
+ send_code(lc, ltree); // send a literal byte
+ }
+ else
+ {
+ // literal or match pair
+ // Here, lc is the match length - MIN_MATCH
+ code = ZTree.LengthCode[lc];
+
+ // send the length code
+ send_code(code + InternalConstants.LITERALS + 1, ltree);
+ extra = ZTree.ExtraLengthBits[code];
+ if (extra != 0)
+ {
+ // send the extra length bits
+ lc -= ZTree.LengthBase[code];
+ send_bits(lc, extra);
+ }
+ distance--; // dist is now the match distance - 1
+ code = ZTree.DistanceCode(distance);
+
+ // send the distance code
+ send_code(code, dtree);
+
+ extra = ZTree.ExtraDistanceBits[code];
+ if (extra != 0)
+ {
+ // send the extra distance bits
+ distance -= ZTree.DistanceBase[code];
+ send_bits(distance, extra);
+ }
+ }
+
+ // Check that the overlay between pending and d_buf+l_buf is ok:
+ }
+ while (lx < last_lit);
+ }
+
+ send_code(END_BLOCK, ltree);
+ last_eob_len = ltree[END_BLOCK * 2 + 1];
+ }
+
+
+
+ // Set the data type to ASCII or BINARY, using a crude approximation:
+ // binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise.
+ // IN assertion: the fields freq of dyn_ltree are set and the total of all
+ // frequencies does not exceed 64K (to fit in an int on 16 bit machines).
+ internal void set_data_type()
+ {
+ int n = 0;
+ int ascii_freq = 0;
+ int bin_freq = 0;
+ while (n < 7)
+ {
+ bin_freq += dyn_ltree[n * 2]; n++;
+ }
+ while (n < 128)
+ {
+ ascii_freq += dyn_ltree[n * 2]; n++;
+ }
+ while (n < InternalConstants.LITERALS)
+ {
+ bin_freq += dyn_ltree[n * 2]; n++;
+ }
+ data_type = (sbyte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII);
+ }
+
+
+
+ // Flush the bit buffer, keeping at most 7 bits in it.
+ internal void bi_flush()
+ {
+ if (bi_valid == 16)
+ {
+ pending[pendingCount++] = (byte)bi_buf;
+ pending[pendingCount++] = (byte)(bi_buf >> 8);
+ bi_buf = 0;
+ bi_valid = 0;
+ }
+ else if (bi_valid >= 8)
+ {
+ //put_byte((byte)bi_buf);
+ pending[pendingCount++] = (byte)bi_buf;
+ bi_buf >>= 8;
+ bi_valid -= 8;
+ }
+ }
+
+ // Flush the bit buffer and align the output on a byte boundary
+ internal void bi_windup()
+ {
+ if (bi_valid > 8)
+ {
+ pending[pendingCount++] = (byte)bi_buf;
+ pending[pendingCount++] = (byte)(bi_buf >> 8);
+ }
+ else if (bi_valid > 0)
+ {
+ //put_byte((byte)bi_buf);
+ pending[pendingCount++] = (byte)bi_buf;
+ }
+ bi_buf = 0;
+ bi_valid = 0;
+ }
+
+ // Copy a stored block, storing first the length and its
+ // one's complement if requested.
+ internal void copy_block(int buf, int len, bool header)
+ {
+ bi_windup(); // align on byte boundary
+ last_eob_len = 8; // enough lookahead for inflate
+
+ if (header)
+ unchecked
+ {
+ //put_short((short)len);
+ pending[pendingCount++] = (byte)len;
+ pending[pendingCount++] = (byte)(len >> 8);
+ //put_short((short)~len);
+ pending[pendingCount++] = (byte)~len;
+ pending[pendingCount++] = (byte)(~len >> 8);
+ }
+
+ put_bytes(window, buf, len);
+ }
+
+ internal void flush_block_only(bool eof)
+ {
+ _tr_flush_block(block_start >= 0 ? block_start : -1, strstart - block_start, eof);
+ block_start = strstart;
+ _codec.flush_pending();
+ }
+
+ // Copy without compression as much as possible from the input stream, return
+ // the current block state.
+ // This function does not insert new strings in the dictionary since
+ // uncompressible data is probably not useful. This function is used
+ // only for the level=0 compression option.
+ // NOTE: this function should be optimized to avoid extra copying from
+ // window to pending_buf.
+ internal BlockState DeflateNone(FlushType flush)
+ {
+ // Stored blocks are limited to 0xffff bytes, pending is limited
+ // to pending_buf_size, and each stored block has a 5 byte header:
+
+ int max_block_size = 0xffff;
+ int max_start;
+
+ if (max_block_size > pending.Length - 5)
+ {
+ max_block_size = pending.Length - 5;
+ }
+
+ // Copy as much as possible from input to output:
+ while (true)
+ {
+ // Fill the window as much as possible:
+ if (lookahead <= 1)
+ {
+ _fillWindow();
+ if (lookahead == 0 && flush == FlushType.None)
+ return BlockState.NeedMore;
+ if (lookahead == 0)
+ break; // flush the current block
+ }
+
+ strstart += lookahead;
+ lookahead = 0;
+
+ // Emit a stored block if pending will be full:
+ max_start = block_start + max_block_size;
+ if (strstart == 0 || strstart >= max_start)
+ {
+ // strstart == 0 is possible when wraparound on 16-bit machine
+ lookahead = (int)(strstart - max_start);
+ strstart = (int)max_start;
+
+ flush_block_only(false);
+ if (_codec.AvailableBytesOut == 0)
+ return BlockState.NeedMore;
+ }
+
+ // Flush if we may have to slide, otherwise block_start may become
+ // negative and the data will be gone:
+ if (strstart - block_start >= w_size - MIN_LOOKAHEAD)
+ {
+ flush_block_only(false);
+ if (_codec.AvailableBytesOut == 0)
+ return BlockState.NeedMore;
+ }
+ }
+
+ flush_block_only(flush == FlushType.Finish);
+ if (_codec.AvailableBytesOut == 0)
+ return (flush == FlushType.Finish) ? BlockState.FinishStarted : BlockState.NeedMore;
+
+ return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone;
+ }
+
+
+ // Send a stored block
+ internal void _tr_stored_block(int buf, int stored_len, bool eof)
+ {
+ send_bits((STORED_BLOCK << 1) + (eof ? 1 : 0), 3); // send block type
+ copy_block(buf, stored_len, true); // with header
+ }
+
+ // Determine the best encoding for the current block: dynamic trees, static
+ // trees or store, and output the encoded block to the zip file.
+ internal void _tr_flush_block(int buf, int stored_len, bool eof)
+ {
+ int opt_lenb, static_lenb; // opt_len and static_len in bytes
+ int max_blindex = 0; // index of last bit length code of non zero freq
+
+ // Build the Huffman trees unless a stored block is forced
+ if (compressionLevel > 0)
+ {
+ // Check if the file is ascii or binary
+ if (data_type == Z_UNKNOWN)
+ set_data_type();
+
+ // Construct the literal and distance trees
+ treeLiterals.build_tree(this);
+
+ treeDistances.build_tree(this);
+
+ // At this point, opt_len and static_len are the total bit lengths of
+ // the compressed block data, excluding the tree representations.
+
+ // Build the bit length tree for the above two trees, and get the index
+ // in bl_order of the last bit length code to send.
+ max_blindex = build_bl_tree();
+
+ // Determine the best encoding. Compute first the block length in bytes
+ opt_lenb = (opt_len + 3 + 7) >> 3;
+ static_lenb = (static_len + 3 + 7) >> 3;
+
+ if (static_lenb <= opt_lenb)
+ opt_lenb = static_lenb;
+ }
+ else
+ {
+ opt_lenb = static_lenb = stored_len + 5; // force a stored block
+ }
+
+ if (stored_len + 4 <= opt_lenb && buf != -1)
+ {
+ // 4: two words for the lengths
+ // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ // Otherwise we can't have processed more than WSIZE input bytes since
+ // the last block flush, because compression would have been
+ // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ // transform a block into a stored block.
+ _tr_stored_block(buf, stored_len, eof);
+ }
+ else if (static_lenb == opt_lenb)
+ {
+ send_bits((STATIC_TREES << 1) + (eof ? 1 : 0), 3);
+ send_compressed_block(StaticTree.lengthAndLiteralsTreeCodes, StaticTree.distTreeCodes);
+ }
+ else
+ {
+ send_bits((DYN_TREES << 1) + (eof ? 1 : 0), 3);
+ send_all_trees(treeLiterals.max_code + 1, treeDistances.max_code + 1, max_blindex + 1);
+ send_compressed_block(dyn_ltree, dyn_dtree);
+ }
+
+ // The above check is made mod 2^32, for files larger than 512 MB
+ // and uLong implemented on 32 bits.
+
+ _InitializeBlocks();
+
+ if (eof)
+ {
+ bi_windup();
+ }
+ }
+
+ // Fill the window when the lookahead becomes insufficient.
+ // Updates strstart and lookahead.
+ //
+ // IN assertion: lookahead < MIN_LOOKAHEAD
+ // OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ // At least one byte has been read, or avail_in == 0; reads are
+ // performed for at least two bytes (required for the zip translate_eol
+ // option -- not supported here).
+ private void _fillWindow()
+ {
+ int n, m;
+ int p;
+ int more; // Amount of free space at the end of the window.
+
+ do
+ {
+ more = (window_size - lookahead - strstart);
+
+ // Deal with !@#$% 64K limit:
+ if (more == 0 && strstart == 0 && lookahead == 0)
+ {
+ more = w_size;
+ }
+ else if (more == -1)
+ {
+ // Very unlikely, but possible on 16 bit machine if strstart == 0
+ // and lookahead == 1 (input done one byte at time)
+ more--;
+
+ // If the window is almost full and there is insufficient lookahead,
+ // move the upper half to the lower one to make room in the upper half.
+ }
+ else if (strstart >= w_size + w_size - MIN_LOOKAHEAD)
+ {
+ Array.Copy(window, w_size, window, 0, w_size);
+ match_start -= w_size;
+ strstart -= w_size; // we now have strstart >= MAX_DIST
+ block_start -= w_size;
+
+ // Slide the hash table (could be avoided with 32 bit values
+ // at the expense of memory usage). We slide even when level == 0
+ // to keep the hash table consistent if we switch back to level > 0
+ // later. (Using level 0 permanently is not an optimal usage of
+ // zlib, so we don't care about this pathological case.)
+
+ n = hash_size;
+ p = n;
+ do
+ {
+ m = (head[--p] & 0xffff);
+ head[p] = (short)((m >= w_size) ? (m - w_size) : 0);
+ }
+ while (--n != 0);
+
+ n = w_size;
+ p = n;
+ do
+ {
+ m = (prev[--p] & 0xffff);
+ prev[p] = (short)((m >= w_size) ? (m - w_size) : 0);
+ // If n is not on any hash chain, prev[n] is garbage but
+ // its value will never be used.
+ }
+ while (--n != 0);
+ more += w_size;
+ }
+
+ if (_codec.AvailableBytesIn == 0)
+ return;
+
+ // If there was no sliding:
+ // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ // more == window_size - lookahead - strstart
+ // => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ // => more >= window_size - 2*WSIZE + 2
+ // In the BIG_MEM or MMAP case (not yet supported),
+ // window_size == input_size + MIN_LOOKAHEAD &&
+ // strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ // Otherwise, window_size == 2*WSIZE so more >= 2.
+ // If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+
+ n = _codec.read_buf(window, strstart + lookahead, more);
+ lookahead += n;
+
+ // Initialize the hash value now that we have some input:
+ if (lookahead >= MIN_MATCH)
+ {
+ ins_h = window[strstart] & 0xff;
+ ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
+ }
+ // If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+ // but this is not important since only literal bytes will be emitted.
+ }
+ while (lookahead < MIN_LOOKAHEAD && _codec.AvailableBytesIn != 0);
+ }
+
+ // Compress as much as possible from the input stream, return the current
+ // block state.
+ // This function does not perform lazy evaluation of matches and inserts
+ // new strings in the dictionary only for unmatched strings or for short
+ // matches. It is used only for the fast compression options.
+ internal BlockState DeflateFast(FlushType flush)
+ {
+ // short hash_head = 0; // head of the hash chain
+ int hash_head = 0; // head of the hash chain
+ bool bflush; // set if current block must be flushed
+
+ while (true)
+ {
+ // Make sure that we always have enough lookahead, except
+ // at the end of the input file. We need MAX_MATCH bytes
+ // for the next match, plus MIN_MATCH bytes to insert the
+ // string following the next match.
+ if (lookahead < MIN_LOOKAHEAD)
+ {
+ _fillWindow();
+ if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None)
+ {
+ return BlockState.NeedMore;
+ }
+ if (lookahead == 0)
+ break; // flush the current block
+ }
+
+ // Insert the string window[strstart .. strstart+2] in the
+ // dictionary, and set hash_head to the head of the hash chain:
+ if (lookahead >= MIN_MATCH)
+ {
+ ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+
+ // prev[strstart&w_mask]=hash_head=head[ins_h];
+ hash_head = (head[ins_h] & 0xffff);
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = unchecked((short)strstart);
+ }
+
+ // Find the longest match, discarding those <= prev_length.
+ // At this point we have always match_length < MIN_MATCH
+
+ if (hash_head != 0L && ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD)
+ {
+ // To simplify the code, we prevent matches with the string
+ // of window index 0 (in particular we have to avoid a match
+ // of the string with itself at the start of the input file).
+ if (compressionStrategy != CompressionStrategy.HuffmanOnly)
+ {
+ match_length = longest_match(hash_head);
+ }
+ // longest_match() sets match_start
+ }
+ if (match_length >= MIN_MATCH)
+ {
+ // check_match(strstart, match_start, match_length);
+
+ bflush = _tr_tally(strstart - match_start, match_length - MIN_MATCH);
+
+ lookahead -= match_length;
+
+ // Insert new strings in the hash table only if the match length
+ // is not too large. This saves time but degrades compression.
+ if (match_length <= config.MaxLazy && lookahead >= MIN_MATCH)
+ {
+ match_length--; // string at strstart already in hash table
+ do
+ {
+ strstart++;
+
+ ins_h = ((ins_h << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+ // prev[strstart&w_mask]=hash_head=head[ins_h];
+ hash_head = (head[ins_h] & 0xffff);
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = unchecked((short)strstart);
+
+ // strstart never exceeds WSIZE-MAX_MATCH, so there are
+ // always MIN_MATCH bytes ahead.
+ }
+ while (--match_length != 0);
+ strstart++;
+ }
+ else
+ {
+ strstart += match_length;
+ match_length = 0;
+ ins_h = window[strstart] & 0xff;
+
+ ins_h = (((ins_h) << hash_shift) ^ (window[strstart + 1] & 0xff)) & hash_mask;
+ // If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+ // matter since it will be recomputed at next deflate call.
+ }
+ }
+ else
+ {
+ // No match, output a literal byte
+
+ bflush = _tr_tally(0, window[strstart] & 0xff);
+ lookahead--;
+ strstart++;
+ }
+ if (bflush)
+ {
+ flush_block_only(false);
+ if (_codec.AvailableBytesOut == 0)
+ return BlockState.NeedMore;
+ }
+ }
+
+ flush_block_only(flush == FlushType.Finish);
+ if (_codec.AvailableBytesOut == 0)
+ {
+ if (flush == FlushType.Finish)
+ return BlockState.FinishStarted;
+ else
+ return BlockState.NeedMore;
+ }
+ return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone;
+ }
+
+ // Same as above, but achieves better compression. We use a lazy
+ // evaluation for matches: a match is finally adopted only if there is
+ // no better match at the next window position.
+ internal BlockState DeflateSlow(FlushType flush)
+ {
+ // short hash_head = 0; // head of hash chain
+ int hash_head = 0; // head of hash chain
+ bool bflush; // set if current block must be flushed
+
+ // Process the input block.
+ while (true)
+ {
+ // Make sure that we always have enough lookahead, except
+ // at the end of the input file. We need MAX_MATCH bytes
+ // for the next match, plus MIN_MATCH bytes to insert the
+ // string following the next match.
+
+ if (lookahead < MIN_LOOKAHEAD)
+ {
+ _fillWindow();
+ if (lookahead < MIN_LOOKAHEAD && flush == FlushType.None)
+ return BlockState.NeedMore;
+
+ if (lookahead == 0)
+ break; // flush the current block
+ }
+
+ // Insert the string window[strstart .. strstart+2] in the
+ // dictionary, and set hash_head to the head of the hash chain:
+
+ if (lookahead >= MIN_MATCH)
+ {
+ ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+ // prev[strstart&w_mask]=hash_head=head[ins_h];
+ hash_head = (head[ins_h] & 0xffff);
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = unchecked((short)strstart);
+ }
+
+ // Find the longest match, discarding those <= prev_length.
+ prev_length = match_length;
+ prev_match = match_start;
+ match_length = MIN_MATCH - 1;
+
+ if (hash_head != 0 && prev_length < config.MaxLazy &&
+ ((strstart - hash_head) & 0xffff) <= w_size - MIN_LOOKAHEAD)
+ {
+ // To simplify the code, we prevent matches with the string
+ // of window index 0 (in particular we have to avoid a match
+ // of the string with itself at the start of the input file).
+
+ if (compressionStrategy != CompressionStrategy.HuffmanOnly)
+ {
+ match_length = longest_match(hash_head);
+ }
+ // longest_match() sets match_start
+
+ if (match_length <= 5 && (compressionStrategy == CompressionStrategy.Filtered ||
+ (match_length == MIN_MATCH && strstart - match_start > 4096)))
+ {
+
+ // If prev_match is also MIN_MATCH, match_start is garbage
+ // but we will ignore the current match anyway.
+ match_length = MIN_MATCH - 1;
+ }
+ }
+
+ // If there was a match at the previous step and the current
+ // match is not better, output the previous match:
+ if (prev_length >= MIN_MATCH && match_length <= prev_length)
+ {
+ int max_insert = strstart + lookahead - MIN_MATCH;
+ // Do not insert strings in hash table beyond this.
+
+ // check_match(strstart-1, prev_match, prev_length);
+
+ bflush = _tr_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH);
+
+ // Insert in hash table all strings up to the end of the match.
+ // strstart-1 and strstart are already inserted. If there is not
+ // enough lookahead, the last two strings are not inserted in
+ // the hash table.
+ lookahead -= (prev_length - 1);
+ prev_length -= 2;
+ do
+ {
+ if (++strstart <= max_insert)
+ {
+ ins_h = (((ins_h) << hash_shift) ^ (window[(strstart) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+ //prev[strstart&w_mask]=hash_head=head[ins_h];
+ hash_head = (head[ins_h] & 0xffff);
+ prev[strstart & w_mask] = head[ins_h];
+ head[ins_h] = unchecked((short)strstart);
+ }
+ }
+ while (--prev_length != 0);
+ match_available = 0;
+ match_length = MIN_MATCH - 1;
+ strstart++;
+
+ if (bflush)
+ {
+ flush_block_only(false);
+ if (_codec.AvailableBytesOut == 0)
+ return BlockState.NeedMore;
+ }
+ }
+ else if (match_available != 0)
+ {
+
+ // If there was no match at the previous position, output a
+ // single literal. If there was a match but the current match
+ // is longer, truncate the previous match to a single literal.
+
+ bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+
+ if (bflush)
+ {
+ flush_block_only(false);
+ }
+ strstart++;
+ lookahead--;
+ if (_codec.AvailableBytesOut == 0)
+ return BlockState.NeedMore;
+ }
+ else
+ {
+ // There is no previous match to compare with, wait for
+ // the next step to decide.
+
+ match_available = 1;
+ strstart++;
+ lookahead--;
+ }
+ }
+
+ if (match_available != 0)
+ {
+ bflush = _tr_tally(0, window[strstart - 1] & 0xff);
+ match_available = 0;
+ }
+ flush_block_only(flush == FlushType.Finish);
+
+ if (_codec.AvailableBytesOut == 0)
+ {
+ if (flush == FlushType.Finish)
+ return BlockState.FinishStarted;
+ else
+ return BlockState.NeedMore;
+ }
+
+ return flush == FlushType.Finish ? BlockState.FinishDone : BlockState.BlockDone;
+ }
+
+
+ internal int longest_match(int cur_match)
+ {
+ int chain_length = config.MaxChainLength; // max hash chain length
+ int scan = strstart; // current string
+ int match; // matched string
+ int len; // length of current match
+ int best_len = prev_length; // best match length so far
+ int limit = strstart > (w_size - MIN_LOOKAHEAD) ? strstart - (w_size - MIN_LOOKAHEAD) : 0;
+
+ int niceLength = config.NiceLength;
+
+ // Stop when cur_match becomes <= limit. To simplify the code,
+ // we prevent matches with the string of window index 0.
+
+ int wmask = w_mask;
+
+ int strend = strstart + MAX_MATCH;
+ byte scan_end1 = window[scan + best_len - 1];
+ byte scan_end = window[scan + best_len];
+
+ // The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ // It is easy to get rid of this optimization if necessary.
+
+ // Do not waste too much time if we already have a good match:
+ if (prev_length >= config.GoodLength)
+ {
+ chain_length >>= 2;
+ }
+
+ // Do not look for matches beyond the end of the input. This is necessary
+ // to make deflate deterministic.
+ if (niceLength > lookahead)
+ niceLength = lookahead;
+
+ do
+ {
+ match = cur_match;
+
+ // Skip to next match if the match length cannot increase
+ // or if the match length is less than 2:
+ if (window[match + best_len] != scan_end ||
+ window[match + best_len - 1] != scan_end1 ||
+ window[match] != window[scan] ||
+ window[++match] != window[scan + 1])
+ continue;
+
+ // The check at best_len-1 can be removed because it will be made
+ // again later. (This heuristic is not always a win.)
+ // It is not necessary to compare scan[2] and match[2] since they
+ // are always equal when the other bytes match, given that
+ // the hash keys are equal and that HASH_BITS >= 8.
+ scan += 2; match++;
+
+ // We check for insufficient lookahead only every 8th comparison;
+ // the 256th check will be made at strstart+258.
+ do
+ {
+ }
+ while (window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] &&
+ window[++scan] == window[++match] && scan < strend);
+
+ len = MAX_MATCH - (int)(strend - scan);
+ scan = strend - MAX_MATCH;
+
+ if (len > best_len)
+ {
+ match_start = cur_match;
+ best_len = len;
+ if (len >= niceLength)
+ break;
+ scan_end1 = window[scan + best_len - 1];
+ scan_end = window[scan + best_len];
+ }
+ }
+ while ((cur_match = (prev[cur_match & wmask] & 0xffff)) > limit && --chain_length != 0);
+
+ if (best_len <= lookahead)
+ return best_len;
+ return lookahead;
+ }
+
+
+ private bool Rfc1950BytesEmitted = false;
+ private bool _WantRfc1950HeaderBytes = true;
+ internal bool WantRfc1950HeaderBytes
+ {
+ get { return _WantRfc1950HeaderBytes; }
+ set { _WantRfc1950HeaderBytes = value; }
+ }
+
+
+ internal int Initialize(ZlibCodec codec, CompressionLevel level)
+ {
+ return Initialize(codec, level, ZlibConstants.WindowBitsMax);
+ }
+
+ internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits)
+ {
+ return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, CompressionStrategy.Default);
+ }
+
+ internal int Initialize(ZlibCodec codec, CompressionLevel level, int bits, CompressionStrategy compressionStrategy)
+ {
+ return Initialize(codec, level, bits, MEM_LEVEL_DEFAULT, compressionStrategy);
+ }
+
+ internal int Initialize(ZlibCodec codec, CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
+ {
+ _codec = codec;
+ _codec.Message = null;
+
+ // validation
+ if (windowBits < 9 || windowBits > 15)
+ throw new ZlibException("windowBits must be in the range 9..15.");
+
+ if (memLevel < 1 || memLevel > MEM_LEVEL_MAX)
+ throw new ZlibException(String.Format("memLevel must be in the range 1.. {0}", MEM_LEVEL_MAX));
+
+ _codec.dstate = this;
+
+ w_bits = windowBits;
+ w_size = 1 << w_bits;
+ w_mask = w_size - 1;
+
+ hash_bits = memLevel + 7;
+ hash_size = 1 << hash_bits;
+ hash_mask = hash_size - 1;
+ hash_shift = ((hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+
+ window = BufferPool.Get(w_size * 2, false);
+ prev = new short[w_size];
+ head = new short[hash_size];
+
+ // for memLevel==8, this will be 16384, 16k
+ lit_bufsize = 1 << (memLevel + 6);
+
+ // Use a single array as the buffer for data pending compression,
+ // the output distance codes, and the output length codes (aka tree).
+ // orig comment: This works just fine since the average
+ // output size for (length,distance) codes is <= 24 bits.
+ pending = BufferPool.Get(lit_bufsize * 4, false);
+ _distanceOffset = lit_bufsize;
+ _lengthOffset = (1 + 2) * lit_bufsize;
+
+ // So, for memLevel 8, the length of the pending buffer is 65536. 64k.
+ // The first 16k are pending bytes.
+ // The middle slice, of 32k, is used for distance codes.
+ // The final 16k are length codes.
+
+ this.compressionLevel = level;
+ this.compressionStrategy = strategy;
+
+ Reset();
+ return ZlibConstants.Z_OK;
+ }
+
+
+ internal void Reset()
+ {
+ _codec.TotalBytesIn = _codec.TotalBytesOut = 0;
+ _codec.Message = null;
+ //strm.data_type = Z_UNKNOWN;
+
+ pendingCount = 0;
+ nextPending = 0;
+
+ Rfc1950BytesEmitted = false;
+
+ status = (WantRfc1950HeaderBytes) ? INIT_STATE : BUSY_STATE;
+ _codec._Adler32 = Adler.Adler32(0, null, 0, 0);
+
+ last_flush = (int)FlushType.None;
+
+ _InitializeTreeData();
+ _InitializeLazyMatch();
+ }
+
+
+ internal int End()
+ {
+ if (status != INIT_STATE && status != BUSY_STATE && status != FINISH_STATE)
+ {
+ return ZlibConstants.Z_STREAM_ERROR;
+ }
+ // Deallocate in reverse order of allocations:
+ BufferPool.Release(pending);
+ pending = null;
+ head = null;
+ prev = null;
+ BufferPool.Release(window);
+ window = null;
+ // free
+ // dstate=null;
+ return status == BUSY_STATE ? ZlibConstants.Z_DATA_ERROR : ZlibConstants.Z_OK;
+ }
+
+
+ private void SetDeflater()
+ {
+ switch (config.Flavor)
+ {
+ case DeflateFlavor.Store:
+ DeflateFunction = DeflateNone;
+ break;
+ case DeflateFlavor.Fast:
+ DeflateFunction = DeflateFast;
+ break;
+ case DeflateFlavor.Slow:
+ DeflateFunction = DeflateSlow;
+ break;
+ }
+ }
+
+
+ internal int SetParams(CompressionLevel level, CompressionStrategy strategy)
+ {
+ int result = ZlibConstants.Z_OK;
+
+ if (compressionLevel != level)
+ {
+ Config newConfig = Config.Lookup(level);
+
+ // change in the deflate flavor (Fast vs slow vs none)?
+ if (newConfig.Flavor != config.Flavor && _codec.TotalBytesIn != 0)
+ {
+ // Flush the last buffer:
+ result = _codec.Deflate(FlushType.Partial);
+ }
+
+ compressionLevel = level;
+ config = newConfig;
+ SetDeflater();
+ }
+
+ // no need to flush with change in strategy? Really?
+ compressionStrategy = strategy;
+
+ return result;
+ }
+
+
+ internal int SetDictionary(byte[] dictionary)
+ {
+ int length = dictionary.Length;
+ int index = 0;
+
+ if (dictionary == null || status != INIT_STATE)
+ throw new ZlibException("Stream error.");
+
+ _codec._Adler32 = Adler.Adler32(_codec._Adler32, dictionary, 0, dictionary.Length);
+
+ if (length < MIN_MATCH)
+ return ZlibConstants.Z_OK;
+ if (length > w_size - MIN_LOOKAHEAD)
+ {
+ length = w_size - MIN_LOOKAHEAD;
+ index = dictionary.Length - length; // use the tail of the dictionary
+ }
+ Array.Copy(dictionary, index, window, 0, length);
+ strstart = length;
+ block_start = length;
+
+ // Insert all strings in the hash table (except for the last two bytes).
+ // s->lookahead stays null, so s->ins_h will be recomputed at the next
+ // call of fill_window.
+
+ ins_h = window[0] & 0xff;
+ ins_h = (((ins_h) << hash_shift) ^ (window[1] & 0xff)) & hash_mask;
+
+ for (int n = 0; n <= length - MIN_MATCH; n++)
+ {
+ ins_h = (((ins_h) << hash_shift) ^ (window[(n) + (MIN_MATCH - 1)] & 0xff)) & hash_mask;
+ prev[n & w_mask] = head[ins_h];
+ head[ins_h] = (short)n;
+ }
+ return ZlibConstants.Z_OK;
+ }
+
+
+
+ internal int Deflate(FlushType flush)
+ {
+ int old_flush;
+
+ if (_codec.OutputBuffer == null ||
+ (_codec.InputBuffer == null && _codec.AvailableBytesIn != 0) ||
+ (status == FINISH_STATE && flush != FlushType.Finish))
+ {
+ _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_STREAM_ERROR)];
+ throw new ZlibException(String.Format("Something is fishy. [{0}]", _codec.Message));
+ }
+ if (_codec.AvailableBytesOut == 0)
+ {
+ _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)];
+ throw new ZlibException("OutputBuffer is full (AvailableBytesOut == 0)");
+ }
+
+ old_flush = last_flush;
+ last_flush = (int)flush;
+
+ // Write the zlib (rfc1950) header bytes
+ if (status == INIT_STATE)
+ {
+ int header = (Z_DEFLATED + ((w_bits - 8) << 4)) << 8;
+ int level_flags = (((int)compressionLevel - 1) & 0xff) >> 1;
+
+ if (level_flags > 3)
+ level_flags = 3;
+ header |= (level_flags << 6);
+ if (strstart != 0)
+ header |= PRESET_DICT;
+ header += 31 - (header % 31);
+
+ status = BUSY_STATE;
+ //putShortMSB(header);
+ unchecked
+ {
+ pending[pendingCount++] = (byte)(header >> 8);
+ pending[pendingCount++] = (byte)header;
+ }
+ // Save the adler32 of the preset dictionary:
+ if (strstart != 0)
+ {
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0xFF000000) >> 24);
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0x00FF0000) >> 16);
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0x0000FF00) >> 8);
+ pending[pendingCount++] = (byte)(_codec._Adler32 & 0x000000FF);
+ }
+ _codec._Adler32 = Adler.Adler32(0, null, 0, 0);
+ }
+
+ // Flush as much pending output as possible
+ if (pendingCount != 0)
+ {
+ _codec.flush_pending();
+ if (_codec.AvailableBytesOut == 0)
+ {
+ //System.out.println(" avail_out==0");
+ // Since avail_out is 0, deflate will be called again with
+ // more output space, but possibly with both pending and
+ // avail_in equal to zero. There won't be anything to do,
+ // but this is not an error situation so make sure we
+ // return OK instead of BUF_ERROR at next call of deflate:
+ last_flush = -1;
+ return ZlibConstants.Z_OK;
+ }
+
+ // Make sure there is something to do and avoid duplicate consecutive
+ // flushes. For repeated and useless calls with Z_FINISH, we keep
+ // returning Z_STREAM_END instead of Z_BUFF_ERROR.
+ }
+ else if (_codec.AvailableBytesIn == 0 &&
+ (int)flush <= old_flush &&
+ flush != FlushType.Finish)
+ {
+ // workitem 8557
+ //
+ // Not sure why this needs to be an error. pendingCount == 0, which
+ // means there's nothing to deflate. And the caller has not asked
+ // for a FlushType.Finish, but... that seems very non-fatal. We
+ // can just say "OK" and do nothing.
+
+ // _codec.Message = z_errmsg[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)];
+ // throw new ZlibException("AvailableBytesIn == 0 && flush<=old_flush && flush != FlushType.Finish");
+
+ return ZlibConstants.Z_OK;
+ }
+
+ // User must not provide more input after the first FINISH:
+ if (status == FINISH_STATE && _codec.AvailableBytesIn != 0)
+ {
+ _codec.Message = _ErrorMessage[ZlibConstants.Z_NEED_DICT - (ZlibConstants.Z_BUF_ERROR)];
+ throw new ZlibException("status == FINISH_STATE && _codec.AvailableBytesIn != 0");
+ }
+
+ // Start a new block or continue the current one.
+ if (_codec.AvailableBytesIn != 0 || lookahead != 0 || (flush != FlushType.None && status != FINISH_STATE))
+ {
+ BlockState bstate = DeflateFunction(flush);
+
+ if (bstate == BlockState.FinishStarted || bstate == BlockState.FinishDone)
+ {
+ status = FINISH_STATE;
+ }
+ if (bstate == BlockState.NeedMore || bstate == BlockState.FinishStarted)
+ {
+ if (_codec.AvailableBytesOut == 0)
+ {
+ last_flush = -1; // avoid BUF_ERROR next call, see above
+ }
+ return ZlibConstants.Z_OK;
+ // If flush != Z_NO_FLUSH && avail_out == 0, the next call
+ // of deflate should use the same flush parameter to make sure
+ // that the flush is complete. So we don't have to output an
+ // empty block here, this will be done at next call. This also
+ // ensures that for a very small output buffer, we emit at most
+ // one empty block.
+ }
+
+ if (bstate == BlockState.BlockDone)
+ {
+ if (flush == FlushType.Partial)
+ {
+ _tr_align();
+ }
+ else
+ {
+ // FlushType.Full or FlushType.Sync
+ _tr_stored_block(0, 0, false);
+ // For a full flush, this empty block will be recognized
+ // as a special marker by inflate_sync().
+ if (flush == FlushType.Full)
+ {
+ // clear hash (forget the history)
+ for (int i = 0; i < hash_size; i++)
+ head[i] = 0;
+ }
+ }
+ _codec.flush_pending();
+ if (_codec.AvailableBytesOut == 0)
+ {
+ last_flush = -1; // avoid BUF_ERROR at next call, see above
+ return ZlibConstants.Z_OK;
+ }
+ }
+ }
+
+ if (flush != FlushType.Finish)
+ return ZlibConstants.Z_OK;
+
+ if (!WantRfc1950HeaderBytes || Rfc1950BytesEmitted)
+ return ZlibConstants.Z_STREAM_END;
+
+ // Write the zlib trailer (adler32)
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0xFF000000) >> 24);
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0x00FF0000) >> 16);
+ pending[pendingCount++] = (byte)((_codec._Adler32 & 0x0000FF00) >> 8);
+ pending[pendingCount++] = (byte)(_codec._Adler32 & 0x000000FF);
+ //putShortMSB((int)(SharedUtils.URShift(_codec._Adler32, 16)));
+ //putShortMSB((int)(_codec._Adler32 & 0xffff));
+
+ _codec.flush_pending();
+
+ // If avail_out is zero, the application will call deflate again
+ // to flush the rest.
+
+ Rfc1950BytesEmitted = true; // write the trailer only once!
+
+ return pendingCount != 0 ? ZlibConstants.Z_OK : ZlibConstants.Z_STREAM_END;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..dc3401957e45c9d6ab63bdf598e7a3dca23400b8
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/Deflate.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: ad45ad0b3a07a2f4181d9f4243b82395
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs b/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9aaa9c2fac1fa0885e395124731b5d209fb588fc
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs
@@ -0,0 +1,692 @@
+// DeflateStream.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009-2010 Dino Chiesa.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2011-July-31 14:48:11>
+//
+// ------------------------------------------------------------------
+//
+// This module defines the DeflateStream class, which can be used as a replacement for
+// the System.IO.Compression.DeflateStream class in the .NET BCL.
+//
+// ------------------------------------------------------------------
+
+
+using System;
+
+namespace BestHTTP.Decompression.Zlib
+{
+ ///
+ /// A class for compressing and decompressing streams using the Deflate algorithm.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The DeflateStream is a Decorator on a . It adds DEFLATE compression or decompression to any
+ /// stream.
+ ///
+ ///
+ ///
+ /// Using this stream, applications can compress or decompress data via stream
+ /// Read and Write operations. Either compresssion or decompression
+ /// can occur through either reading or writing. The compression format used is
+ /// DEFLATE, which is documented in IETF RFC 1951, "DEFLATE
+ /// Compressed Data Format Specification version 1.3.".
+ ///
+ ///
+ ///
+ ///
+ ///
+ public class DeflateStream : System.IO.Stream
+ {
+ internal ZlibBaseStream _baseStream;
+ internal System.IO.Stream _innerStream;
+ bool _disposed;
+
+ ///
+ /// Create a DeflateStream using the specified CompressionMode.
+ ///
+ ///
+ ///
+ /// When mode is CompressionMode.Compress, the DeflateStream will use
+ /// the default compression level. The "captive" stream will be closed when
+ /// the DeflateStream is closed.
+ ///
+ ///
+ ///
+ /// This example uses a DeflateStream to compress data from a file, and writes
+ /// the compressed data to another file.
+ ///
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (var raw = System.IO.File.Create(fileToCompress + ".deflated"))
+ /// {
+ /// using (Stream compressor = new DeflateStream(raw, CompressionMode.Compress))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n;
+ /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
+ /// {
+ /// compressor.Write(buffer, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using raw As FileStream = File.Create(fileToCompress & ".deflated")
+ /// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// End Using
+ ///
+ ///
+ /// The stream which will be read or written.
+ /// Indicates whether the DeflateStream will compress or decompress.
+ public DeflateStream(System.IO.Stream stream, CompressionMode mode)
+ : this(stream, mode, CompressionLevel.Default, false)
+ {
+ }
+
+ ///
+ /// Create a DeflateStream using the specified CompressionMode and the specified CompressionLevel.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// When mode is CompressionMode.Decompress, the level parameter is
+ /// ignored. The "captive" stream will be closed when the DeflateStream is
+ /// closed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example uses a DeflateStream to compress data from a file, and writes
+ /// the compressed data to another file.
+ ///
+ ///
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (var raw = System.IO.File.Create(fileToCompress + ".deflated"))
+ /// {
+ /// using (Stream compressor = new DeflateStream(raw,
+ /// CompressionMode.Compress,
+ /// CompressionLevel.BestCompression))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n= -1;
+ /// while (n != 0)
+ /// {
+ /// if (n > 0)
+ /// compressor.Write(buffer, 0, n);
+ /// n= input.Read(buffer, 0, buffer.Length);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using raw As FileStream = File.Create(fileToCompress & ".deflated")
+ /// Using compressor As Stream = New DeflateStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// End Using
+ ///
+ ///
+ /// The stream to be read or written while deflating or inflating.
+ /// Indicates whether the DeflateStream will compress or decompress.
+ /// A tuning knob to trade speed for effectiveness.
+ public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level)
+ : this(stream, mode, level, false)
+ {
+ }
+
+ ///
+ /// Create a DeflateStream using the specified
+ /// CompressionMode, and explicitly specify whether the
+ /// stream should be left open after Deflation or Inflation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This constructor allows the application to request that the captive stream
+ /// remain open after the deflation or inflation occurs. By default, after
+ /// Close() is called on the stream, the captive stream is also
+ /// closed. In some cases this is not desired, for example if the stream is a
+ /// memory stream that will be re-read after compression. Specify true for
+ /// the parameter to leave the stream open.
+ ///
+ ///
+ ///
+ /// The DeflateStream will use the default compression level.
+ ///
+ ///
+ ///
+ /// See the other overloads of this constructor for example code.
+ ///
+ ///
+ ///
+ ///
+ /// The stream which will be read or written. This is called the
+ /// "captive" stream in other places in this documentation.
+ ///
+ ///
+ ///
+ /// Indicates whether the DeflateStream will compress or decompress.
+ ///
+ ///
+ /// true if the application would like the stream to
+ /// remain open after inflation/deflation.
+ public DeflateStream(System.IO.Stream stream, CompressionMode mode, bool leaveOpen)
+ : this(stream, mode, CompressionLevel.Default, leaveOpen)
+ {
+ }
+
+ ///
+ /// Create a DeflateStream using the specified CompressionMode
+ /// and the specified CompressionLevel, and explicitly specify whether
+ /// the stream should be left open after Deflation or Inflation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// When mode is CompressionMode.Decompress, the level parameter is ignored.
+ ///
+ ///
+ ///
+ /// This constructor allows the application to request that the captive stream
+ /// remain open after the deflation or inflation occurs. By default, after
+ /// Close() is called on the stream, the captive stream is also
+ /// closed. In some cases this is not desired, for example if the stream is a
+ /// that will be re-read after
+ /// compression. Specify true for the parameter
+ /// to leave the stream open.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a DeflateStream to compress data from
+ /// a file, and store the compressed data into another file.
+ ///
+ ///
+ /// using (var output = System.IO.File.Create(fileToCompress + ".deflated"))
+ /// {
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (Stream compressor = new DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n= -1;
+ /// while (n != 0)
+ /// {
+ /// if (n > 0)
+ /// compressor.Write(buffer, 0, n);
+ /// n= input.Read(buffer, 0, buffer.Length);
+ /// }
+ /// }
+ /// }
+ /// // can write additional data to the output stream here
+ /// }
+ ///
+ ///
+ ///
+ /// Using output As FileStream = File.Create(fileToCompress & ".deflated")
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using compressor As Stream = New DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// ' can write additional data to the output stream here.
+ /// End Using
+ ///
+ ///
+ /// The stream which will be read or written.
+ /// Indicates whether the DeflateStream will compress or decompress.
+ /// true if the application would like the stream to remain open after inflation/deflation.
+ /// A tuning knob to trade speed for effectiveness.
+ public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen)
+ {
+ _innerStream = stream;
+ _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, leaveOpen);
+ }
+
+ ///
+ /// Create a DeflateStream using the specified CompressionMode
+ /// and the specified CompressionLevel, and explicitly specify whether
+ /// the stream should be left open after Deflation or Inflation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// When mode is CompressionMode.Decompress, the level parameter is ignored.
+ ///
+ ///
+ ///
+ /// This constructor allows the application to request that the captive stream
+ /// remain open after the deflation or inflation occurs. By default, after
+ /// Close() is called on the stream, the captive stream is also
+ /// closed. In some cases this is not desired, for example if the stream is a
+ /// that will be re-read after
+ /// compression. Specify true for the parameter
+ /// to leave the stream open.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a DeflateStream to compress data from
+ /// a file, and store the compressed data into another file.
+ ///
+ ///
+ /// using (var output = System.IO.File.Create(fileToCompress + ".deflated"))
+ /// {
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (Stream compressor = new DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n= -1;
+ /// while (n != 0)
+ /// {
+ /// if (n > 0)
+ /// compressor.Write(buffer, 0, n);
+ /// n= input.Read(buffer, 0, buffer.Length);
+ /// }
+ /// }
+ /// }
+ /// // can write additional data to the output stream here
+ /// }
+ ///
+ ///
+ ///
+ /// Using output As FileStream = File.Create(fileToCompress & ".deflated")
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using compressor As Stream = New DeflateStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// ' can write additional data to the output stream here.
+ /// End Using
+ ///
+ ///
+ /// The stream which will be read or written.
+ /// Indicates whether the DeflateStream will compress or decompress.
+ /// true if the application would like the stream to remain open after inflation/deflation.
+ /// A tuning knob to trade speed for effectiveness.
+ /// Desired window bits.
+ public DeflateStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen, int windowBits)
+ {
+ _innerStream = stream;
+ _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.DEFLATE, leaveOpen, windowBits);
+ }
+
+ #region Zlib properties
+
+ ///
+ /// This property sets the flush behavior on the stream.
+ ///
+ /// See the ZLIB documentation for the meaning of the flush behavior.
+ ///
+ virtual public FlushType FlushMode
+ {
+ get { return (this._baseStream._flushMode); }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ this._baseStream._flushMode = value;
+ }
+ }
+
+ ///
+ /// The size of the working buffer for the compression codec.
+ ///
+ ///
+ ///
+ ///
+ /// The working buffer is used for all stream operations. The default size is
+ /// 1024 bytes. The minimum size is 128 bytes. You may get better performance
+ /// with a larger buffer. Then again, you might not. You would have to test
+ /// it.
+ ///
+ ///
+ ///
+ /// Set this before the first call to Read() or Write() on the
+ /// stream. If you try to set it afterwards, it will throw.
+ ///
+ ///
+ public int BufferSize
+ {
+ get
+ {
+ return this._baseStream._bufferSize;
+ }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ if (this._baseStream._workingBuffer != null)
+ throw new ZlibException("The working buffer is already set.");
+ if (value < ZlibConstants.WorkingBufferSizeMin)
+ throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin));
+ this._baseStream._bufferSize = value;
+ }
+ }
+
+ ///
+ /// The ZLIB strategy to be used during compression.
+ ///
+ ///
+ ///
+ /// By tweaking this parameter, you may be able to optimize the compression for
+ /// data with particular characteristics.
+ ///
+ public CompressionStrategy Strategy
+ {
+ get
+ {
+ return this._baseStream.Strategy;
+ }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ this._baseStream.Strategy = value;
+ }
+ }
+
+ /// Returns the total number of bytes input so far.
+ virtual public long TotalIn
+ {
+ get
+ {
+ return this._baseStream._z.TotalBytesIn;
+ }
+ }
+
+ /// Returns the total number of bytes output so far.
+ virtual public long TotalOut
+ {
+ get
+ {
+ return this._baseStream._z.TotalBytesOut;
+ }
+ }
+
+ #endregion
+
+ #region System.IO.Stream methods
+ ///
+ /// Dispose the stream.
+ ///
+ ///
+ ///
+ /// This may or may not result in a Close() call on the captive
+ /// stream. See the constructors that have a leaveOpen parameter
+ /// for more information.
+ ///
+ ///
+ /// Application code won't call this code directly. This method may be
+ /// invoked in two distinct scenarios. If disposing == true, the method
+ /// has been called directly or indirectly by a user's code, for example
+ /// via the public Dispose() method. In this case, both managed and
+ /// unmanaged resources can be referenced and disposed. If disposing ==
+ /// false, the method has been called by the runtime from inside the
+ /// object finalizer and this method should not reference other objects;
+ /// in that case only unmanaged resources must be referenced or
+ /// disposed.
+ ///
+ ///
+ ///
+ /// true if the Dispose method was invoked by user code.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (!_disposed)
+ {
+ if (disposing && (this._baseStream != null))
+ this._baseStream.Close();
+ _disposed = true;
+ }
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+
+
+
+ ///
+ /// Indicates whether the stream can be read.
+ ///
+ ///
+ /// The return value depends on whether the captive stream supports reading.
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ return _baseStream._stream.CanRead;
+ }
+ }
+
+ ///
+ /// Indicates whether the stream supports Seek operations.
+ ///
+ ///
+ /// Always returns false.
+ ///
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+
+ ///
+ /// Indicates whether the stream can be written.
+ ///
+ ///
+ /// The return value depends on whether the captive stream supports writing.
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ return _baseStream._stream.CanWrite;
+ }
+ }
+
+ ///
+ /// Flush the stream.
+ ///
+ public override void Flush()
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ _baseStream.Flush();
+ }
+
+ ///
+ /// Reading this property always throws a .
+ ///
+ public override long Length
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ ///
+ /// The position of the stream pointer.
+ ///
+ ///
+ ///
+ /// Setting this property always throws a . Reading will return the total bytes
+ /// written out, if used in writing, or the total bytes read in, if used in
+ /// reading. The count may refer to compressed bytes or uncompressed bytes,
+ /// depending on how you've used the stream.
+ ///
+ public override long Position
+ {
+ get
+ {
+ if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Writer)
+ return this._baseStream._z.TotalBytesOut;
+ if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Reader)
+ return this._baseStream._z.TotalBytesIn;
+ return 0;
+ }
+ set { throw new NotImplementedException(); }
+ }
+
+ ///
+ /// Read data from the stream.
+ ///
+ ///
+ ///
+ ///
+ /// If you wish to use the DeflateStream to compress data while
+ /// reading, you can create a DeflateStream with
+ /// CompressionMode.Compress, providing an uncompressed data stream.
+ /// Then call Read() on that DeflateStream, and the data read will be
+ /// compressed as you read. If you wish to use the DeflateStream to
+ /// decompress data while reading, you can create a DeflateStream with
+ /// CompressionMode.Decompress, providing a readable compressed data
+ /// stream. Then call Read() on that DeflateStream, and the data read
+ /// will be decompressed as you read.
+ ///
+ ///
+ ///
+ /// A DeflateStream can be used for Read() or Write(), but not both.
+ ///
+ ///
+ ///
+ /// The buffer into which the read data should be placed.
+ /// the offset within that data array to put the first byte read.
+ /// the number of bytes to read.
+ /// the number of bytes actually read
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ return _baseStream.Read(buffer, offset, count);
+ }
+
+
+ ///
+ /// Calling this method always throws a .
+ ///
+ /// this is irrelevant, since it will always throw!
+ /// this is irrelevant, since it will always throw!
+ /// irrelevant!
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Will call the base stream's SetLength method.
+ ///
+ public override void SetLength(long value)
+ {
+ _baseStream.SetLength(value);
+ }
+
+ ///
+ /// Write data to the stream.
+ ///
+ ///
+ ///
+ ///
+ /// If you wish to use the DeflateStream to compress data while
+ /// writing, you can create a DeflateStream with
+ /// CompressionMode.Compress, and a writable output stream. Then call
+ /// Write() on that DeflateStream, providing uncompressed data
+ /// as input. The data sent to the output stream will be the compressed form
+ /// of the data written. If you wish to use the DeflateStream to
+ /// decompress data while writing, you can create a DeflateStream with
+ /// CompressionMode.Decompress, and a writable output stream. Then
+ /// call Write() on that stream, providing previously compressed
+ /// data. The data sent to the output stream will be the decompressed form of
+ /// the data written.
+ ///
+ ///
+ ///
+ /// A DeflateStream can be used for Read() or Write(),
+ /// but not both.
+ ///
+ ///
+ ///
+ ///
+ /// The buffer holding data to write to the stream.
+ /// the offset within that data array to find the first byte to write.
+ /// the number of bytes to write.
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (_disposed) throw new ObjectDisposedException("DeflateStream");
+ _baseStream.Write(buffer, offset, count);
+ }
+ #endregion
+ }
+
+}
+
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..e4d5ac675cf43382fd4b49cd230653a0d63be755
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/DeflateStream.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 8b7a3b80f9aa82941a62c619c263ff5e
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs b/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e113ae089496501c6ee0a52873cc029413a17f84
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs
@@ -0,0 +1,105 @@
+using BestHTTP.Extensions;
+using BestHTTP.PlatformSupport.Memory;
+using System;
+
+namespace BestHTTP.Decompression
+{
+ public struct DecompressedData
+ {
+ public readonly byte[] Data;
+ public readonly int Length;
+
+ internal DecompressedData(byte[] data, int length)
+ {
+ this.Data = data;
+ this.Length = length;
+ }
+ }
+
+ public sealed class GZipDecompressor : IDisposable
+ {
+ private BufferPoolMemoryStream decompressorInputStream;
+ private BufferPoolMemoryStream decompressorOutputStream;
+ private Zlib.GZipStream decompressorGZipStream;
+
+ private int MinLengthToDecompress = 256;
+
+ public GZipDecompressor(int minLengthToDecompress)
+ {
+ this.MinLengthToDecompress = minLengthToDecompress;
+ }
+
+ private void CloseDecompressors()
+ {
+ if (decompressorGZipStream != null)
+ decompressorGZipStream.Dispose();
+ decompressorGZipStream = null;
+
+ if (decompressorInputStream != null)
+ decompressorInputStream.Dispose();
+ decompressorInputStream = null;
+
+ if (decompressorOutputStream != null)
+ decompressorOutputStream.Dispose();
+ decompressorOutputStream = null;
+ }
+
+ public DecompressedData Decompress(byte[] data, int offset, int count, bool forceDecompress = false, bool dataCanBeLarger = false)
+ {
+ if (decompressorInputStream == null)
+ decompressorInputStream = new BufferPoolMemoryStream(count);
+
+ if (data != null)
+ decompressorInputStream.Write(data, offset, count);
+
+ if (!forceDecompress && decompressorInputStream.Length < MinLengthToDecompress)
+ return new DecompressedData(null, 0);
+
+ decompressorInputStream.Position = 0;
+
+ if (decompressorGZipStream == null)
+ {
+ decompressorGZipStream = new Zlib.GZipStream(decompressorInputStream,
+ Zlib.CompressionMode.Decompress,
+ Zlib.CompressionLevel.Default,
+ true);
+ decompressorGZipStream.FlushMode = Zlib.FlushType.Sync;
+ }
+
+ if (decompressorOutputStream == null)
+ decompressorOutputStream = new BufferPoolMemoryStream();
+ decompressorOutputStream.SetLength(0);
+
+ byte[] copyBuffer = BufferPool.Get(1024, true);
+
+ int readCount;
+ int sumReadCount = 0;
+ while ((readCount = decompressorGZipStream.Read(copyBuffer, 0, copyBuffer.Length)) != 0)
+ {
+ decompressorOutputStream.Write(copyBuffer, 0, readCount);
+ sumReadCount += readCount;
+ }
+
+ BufferPool.Release(copyBuffer);
+
+ // If no read is done (returned with any data) don't zero out the input stream, as it would delete any not yet used data.
+ if (sumReadCount > 0)
+ decompressorGZipStream.SetLength(0);
+
+ byte[] result = decompressorOutputStream.ToArray(dataCanBeLarger);
+
+ return new DecompressedData(result, dataCanBeLarger ? (int)decompressorOutputStream.Length : result.Length);
+ }
+
+ ~GZipDecompressor()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ CloseDecompressors();
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..32c157545b541ff2acd8954706185dfd79d176c6
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/GZipDecompressor.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 1472669cee77e2d4294077c4c1ac3663
+timeCreated: 1571210040
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs b/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c7d90072153cb700c350f78dc69991313e66ad73
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs
@@ -0,0 +1,920 @@
+// GZipStream.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2011-July-11 21:42:34>
+//
+// ------------------------------------------------------------------
+//
+// This module defines the GZipStream class, which can be used as a replacement for
+// the System.IO.Compression.GZipStream class in the .NET BCL. NB: The design is not
+// completely OO clean: there is some intelligence in the ZlibBaseStream that reads the
+// GZip header.
+//
+// ------------------------------------------------------------------
+
+
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.IO;
+
+namespace BestHTTP.Decompression.Zlib
+{
+ ///
+ /// A class for compressing and decompressing GZIP streams.
+ ///
+ ///
+ ///
+ ///
+ /// The GZipStream is a Decorator on a
+ /// . It adds GZIP compression or decompression to any
+ /// stream.
+ ///
+ ///
+ ///
+ /// Like the System.IO.Compression.GZipStream in the .NET Base Class Library, the
+ /// Ionic.Zlib.GZipStream can compress while writing, or decompress while
+ /// reading, but not vice versa. The compression method used is GZIP, which is
+ /// documented in IETF RFC
+ /// 1952, "GZIP file format specification version 4.3".
+ ///
+ ///
+ /// A GZipStream can be used to decompress data (through Read()) or
+ /// to compress data (through Write()), but not both.
+ ///
+ ///
+ ///
+ /// If you wish to use the GZipStream to compress data, you must wrap it
+ /// around a write-able stream. As you call Write() on the GZipStream, the
+ /// data will be compressed into the GZIP format. If you want to decompress data,
+ /// you must wrap the GZipStream around a readable stream that contains an
+ /// IETF RFC 1952-compliant stream. The data will be decompressed as you call
+ /// Read() on the GZipStream.
+ ///
+ ///
+ ///
+ /// Though the GZIP format allows data from multiple files to be concatenated
+ /// together, this stream handles only a single segment of GZIP format, typically
+ /// representing a single file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public class GZipStream : System.IO.Stream
+ {
+ // GZip format
+ // source: http://tools.ietf.org/html/rfc1952
+ //
+ // header id: 2 bytes 1F 8B
+ // compress method 1 byte 8= DEFLATE (none other supported)
+ // flag 1 byte bitfield (See below)
+ // mtime 4 bytes time_t (seconds since jan 1, 1970 UTC of the file.
+ // xflg 1 byte 2 = max compress used , 4 = max speed (can be ignored)
+ // OS 1 byte OS for originating archive. set to 0xFF in compression.
+ // extra field length 2 bytes optional - only if FEXTRA is set.
+ // extra field varies
+ // filename varies optional - if FNAME is set. zero terminated. ISO-8859-1.
+ // file comment varies optional - if FCOMMENT is set. zero terminated. ISO-8859-1.
+ // crc16 1 byte optional - present only if FHCRC bit is set
+ // compressed data varies
+ // CRC32 4 bytes
+ // isize 4 bytes data size modulo 2^32
+ //
+ // FLG (FLaGs)
+ // bit 0 FTEXT - indicates file is ASCII text (can be safely ignored)
+ // bit 1 FHCRC - there is a CRC16 for the header immediately following the header
+ // bit 2 FEXTRA - extra fields are present
+ // bit 3 FNAME - the zero-terminated filename is present. encoding; ISO-8859-1.
+ // bit 4 FCOMMENT - a zero-terminated file comment is present. encoding: ISO-8859-1
+ // bit 5 reserved
+ // bit 6 reserved
+ // bit 7 reserved
+ //
+ // On consumption:
+ // Extra field is a bunch of nonsense and can be safely ignored.
+ // Header CRC and OS, likewise.
+ //
+ // on generation:
+ // all optional fields get 0, except for the OS, which gets 255.
+ //
+
+
+
+ ///
+ /// The comment on the GZIP stream.
+ ///
+ ///
+ ///
+ ///
+ /// The GZIP format allows for each file to optionally have an associated
+ /// comment stored with the file. The comment is encoded with the ISO-8859-1
+ /// code page. To include a comment in a GZIP stream you create, set this
+ /// property before calling Write() for the first time on the
+ /// GZipStream.
+ ///
+ ///
+ ///
+ /// When using GZipStream to decompress, you can retrieve this property
+ /// after the first call to Read(). If no comment has been set in the
+ /// GZIP bytestream, the Comment property will return null
+ /// (Nothing in VB).
+ ///
+ ///
+ public String Comment
+ {
+ get
+ {
+ return _Comment;
+ }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ _Comment = value;
+ }
+ }
+
+ ///
+ /// The FileName for the GZIP stream.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The GZIP format optionally allows each file to have an associated
+ /// filename. When compressing data (through Write()), set this
+ /// FileName before calling Write() the first time on the GZipStream.
+ /// The actual filename is encoded into the GZIP bytestream with the
+ /// ISO-8859-1 code page, according to RFC 1952. It is the application's
+ /// responsibility to insure that the FileName can be encoded and decoded
+ /// correctly with this code page.
+ ///
+ ///
+ ///
+ /// When decompressing (through Read()), you can retrieve this value
+ /// any time after the first Read(). In the case where there was no filename
+ /// encoded into the GZIP bytestream, the property will return null (Nothing
+ /// in VB).
+ ///
+ ///
+ public String FileName
+ {
+ get { return _FileName; }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ _FileName = value;
+ if (_FileName == null) return;
+ if (_FileName.IndexOf("/") != -1)
+ {
+ _FileName = _FileName.Replace("/", "\\");
+ }
+ if (_FileName.EndsWith("\\"))
+ throw new Exception("Illegal filename");
+ if (_FileName.IndexOf("\\") != -1)
+ {
+ // trim any leading path
+ _FileName = Path.GetFileName(_FileName);
+ }
+ }
+ }
+
+ ///
+ /// The last modified time for the GZIP stream.
+ ///
+ ///
+ ///
+ /// GZIP allows the storage of a last modified time with each GZIP entity.
+ /// When compressing data, you can set this before the first call to
+ /// Write(). When decompressing, you can retrieve this value any time
+ /// after the first call to Read().
+ ///
+ public DateTime? LastModified;
+
+ ///
+ /// The CRC on the GZIP stream.
+ ///
+ ///
+ /// This is used for internal error checking. You probably don't need to look at this property.
+ ///
+ public int Crc32 { get { return _Crc32; } }
+
+ private int _headerByteCount;
+ internal ZlibBaseStream _baseStream;
+ bool _disposed;
+ bool _firstReadDone;
+ string _FileName;
+ string _Comment;
+ int _Crc32;
+
+
+ ///
+ /// Create a GZipStream using the specified CompressionMode.
+ ///
+ ///
+ ///
+ ///
+ /// When mode is CompressionMode.Compress, the GZipStream will use the
+ /// default compression level.
+ ///
+ ///
+ ///
+ /// As noted in the class documentation, the CompressionMode (Compress
+ /// or Decompress) also establishes the "direction" of the stream. A
+ /// GZipStream with CompressionMode.Compress works only through
+ /// Write(). A GZipStream with
+ /// CompressionMode.Decompress works only through Read().
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a GZipStream to compress data.
+ ///
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (var raw = System.IO.File.Create(outputFile))
+ /// {
+ /// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n;
+ /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
+ /// {
+ /// compressor.Write(buffer, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ /// Dim outputFile As String = (fileToCompress & ".compressed")
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using raw As FileStream = File.Create(outputFile)
+ /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// End Using
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a GZipStream to uncompress a file.
+ ///
+ /// private void GunZipFile(string filename)
+ /// {
+ /// if (!filename.EndsWith(".gz))
+ /// throw new ArgumentException("filename");
+ /// var DecompressedFile = filename.Substring(0,filename.Length-3);
+ /// byte[] working = new byte[WORKING_BUFFER_SIZE];
+ /// int n= 1;
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(filename))
+ /// {
+ /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
+ /// {
+ /// using (var output = System.IO.File.Create(DecompressedFile))
+ /// {
+ /// while (n !=0)
+ /// {
+ /// n= decompressor.Read(working, 0, working.Length);
+ /// if (n > 0)
+ /// {
+ /// output.Write(working, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// Private Sub GunZipFile(ByVal filename as String)
+ /// If Not (filename.EndsWith(".gz)) Then
+ /// Throw New ArgumentException("filename")
+ /// End If
+ /// Dim DecompressedFile as String = filename.Substring(0,filename.Length-3)
+ /// Dim working(WORKING_BUFFER_SIZE) as Byte
+ /// Dim n As Integer = 1
+ /// Using input As Stream = File.OpenRead(filename)
+ /// Using decompressor As Stream = new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, True)
+ /// Using output As Stream = File.Create(UncompressedFile)
+ /// Do
+ /// n= decompressor.Read(working, 0, working.Length)
+ /// If n > 0 Then
+ /// output.Write(working, 0, n)
+ /// End IF
+ /// Loop While (n > 0)
+ /// End Using
+ /// End Using
+ /// End Using
+ /// End Sub
+ ///
+ ///
+ ///
+ /// The stream which will be read or written.
+ /// Indicates whether the GZipStream will compress or decompress.
+ public GZipStream(Stream stream, CompressionMode mode)
+ : this(stream, mode, CompressionLevel.Default, false)
+ {
+ }
+
+ ///
+ /// Create a GZipStream using the specified CompressionMode and
+ /// the specified CompressionLevel.
+ ///
+ ///
+ ///
+ ///
+ /// The CompressionMode (Compress or Decompress) also establishes the
+ /// "direction" of the stream. A GZipStream with
+ /// CompressionMode.Compress works only through Write(). A
+ /// GZipStream with CompressionMode.Decompress works only
+ /// through Read().
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a GZipStream to compress a file into a .gz file.
+ ///
+ ///
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (var raw = System.IO.File.Create(fileToCompress + ".gz"))
+ /// {
+ /// using (Stream compressor = new GZipStream(raw,
+ /// CompressionMode.Compress,
+ /// CompressionLevel.BestCompression))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n;
+ /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
+ /// {
+ /// compressor.Write(buffer, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ ///
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using raw As FileStream = File.Create(fileToCompress & ".gz")
+ /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// End Using
+ ///
+ ///
+ /// The stream to be read or written while deflating or inflating.
+ /// Indicates whether the GZipStream will compress or decompress.
+ /// A tuning knob to trade speed for effectiveness.
+ public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level)
+ : this(stream, mode, level, false)
+ {
+ }
+
+ ///
+ /// Create a GZipStream using the specified CompressionMode, and
+ /// explicitly specify whether the stream should be left open after Deflation
+ /// or Inflation.
+ ///
+ ///
+ ///
+ ///
+ /// This constructor allows the application to request that the captive stream
+ /// remain open after the deflation or inflation occurs. By default, after
+ /// Close() is called on the stream, the captive stream is also
+ /// closed. In some cases this is not desired, for example if the stream is a
+ /// memory stream that will be re-read after compressed data has been written
+ /// to it. Specify true for the parameter to leave
+ /// the stream open.
+ ///
+ ///
+ ///
+ /// The (Compress or Decompress) also
+ /// establishes the "direction" of the stream. A GZipStream with
+ /// CompressionMode.Compress works only through Write(). A GZipStream
+ /// with CompressionMode.Decompress works only through Read().
+ ///
+ ///
+ ///
+ /// The GZipStream will use the default compression level. If you want
+ /// to specify the compression level, see .
+ ///
+ ///
+ ///
+ /// See the other overloads of this constructor for example code.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The stream which will be read or written. This is called the "captive"
+ /// stream in other places in this documentation.
+ ///
+ ///
+ /// Indicates whether the GZipStream will compress or decompress.
+ ///
+ ///
+ ///
+ /// true if the application would like the base stream to remain open after
+ /// inflation/deflation.
+ ///
+ public GZipStream(Stream stream, CompressionMode mode, bool leaveOpen)
+ : this(stream, mode, CompressionLevel.Default, leaveOpen)
+ {
+ }
+
+ ///
+ /// Create a GZipStream using the specified CompressionMode and the
+ /// specified CompressionLevel, and explicitly specify whether the
+ /// stream should be left open after Deflation or Inflation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This constructor allows the application to request that the captive stream
+ /// remain open after the deflation or inflation occurs. By default, after
+ /// Close() is called on the stream, the captive stream is also
+ /// closed. In some cases this is not desired, for example if the stream is a
+ /// memory stream that will be re-read after compressed data has been written
+ /// to it. Specify true for the parameter to
+ /// leave the stream open.
+ ///
+ ///
+ ///
+ /// As noted in the class documentation, the CompressionMode (Compress
+ /// or Decompress) also establishes the "direction" of the stream. A
+ /// GZipStream with CompressionMode.Compress works only through
+ /// Write(). A GZipStream with CompressionMode.Decompress works only
+ /// through Read().
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// This example shows how to use a GZipStream to compress data.
+ ///
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
+ /// {
+ /// using (var raw = System.IO.File.Create(outputFile))
+ /// {
+ /// using (Stream compressor = new GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, true))
+ /// {
+ /// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
+ /// int n;
+ /// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
+ /// {
+ /// compressor.Write(buffer, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ /// Dim outputFile As String = (fileToCompress & ".compressed")
+ /// Using input As Stream = File.OpenRead(fileToCompress)
+ /// Using raw As FileStream = File.Create(outputFile)
+ /// Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, True)
+ /// Dim buffer As Byte() = New Byte(4096) {}
+ /// Dim n As Integer = -1
+ /// Do While (n <> 0)
+ /// If (n > 0) Then
+ /// compressor.Write(buffer, 0, n)
+ /// End If
+ /// n = input.Read(buffer, 0, buffer.Length)
+ /// Loop
+ /// End Using
+ /// End Using
+ /// End Using
+ ///
+ ///
+ /// The stream which will be read or written.
+ /// Indicates whether the GZipStream will compress or decompress.
+ /// true if the application would like the stream to remain open after inflation/deflation.
+ /// A tuning knob to trade speed for effectiveness.
+ public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen)
+ {
+ _baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, leaveOpen);
+ }
+
+ #region Zlib properties
+
+ ///
+ /// This property sets the flush behavior on the stream.
+ ///
+ virtual public FlushType FlushMode
+ {
+ get { return (this._baseStream._flushMode); }
+ set {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ this._baseStream._flushMode = value;
+ }
+ }
+
+ ///
+ /// The size of the working buffer for the compression codec.
+ ///
+ ///
+ ///
+ ///
+ /// The working buffer is used for all stream operations. The default size is
+ /// 1024 bytes. The minimum size is 128 bytes. You may get better performance
+ /// with a larger buffer. Then again, you might not. You would have to test
+ /// it.
+ ///
+ ///
+ ///
+ /// Set this before the first call to Read() or Write() on the
+ /// stream. If you try to set it afterwards, it will throw.
+ ///
+ ///
+ public int BufferSize
+ {
+ get
+ {
+ return this._baseStream._bufferSize;
+ }
+ set
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ if (this._baseStream._workingBuffer != null)
+ throw new ZlibException("The working buffer is already set.");
+ if (value < ZlibConstants.WorkingBufferSizeMin)
+ throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin));
+ this._baseStream._bufferSize = value;
+ }
+ }
+
+
+ /// Returns the total number of bytes input so far.
+ virtual public long TotalIn
+ {
+ get
+ {
+ return this._baseStream._z.TotalBytesIn;
+ }
+ }
+
+ /// Returns the total number of bytes output so far.
+ virtual public long TotalOut
+ {
+ get
+ {
+ return this._baseStream._z.TotalBytesOut;
+ }
+ }
+
+ #endregion
+
+ #region Stream methods
+
+ ///
+ /// Dispose the stream.
+ ///
+ ///
+ ///
+ /// This may or may not result in a Close() call on the captive
+ /// stream. See the constructors that have a leaveOpen parameter
+ /// for more information.
+ ///
+ ///
+ /// This method may be invoked in two distinct scenarios. If disposing
+ /// == true, the method has been called directly or indirectly by a
+ /// user's code, for example via the public Dispose() method. In this
+ /// case, both managed and unmanaged resources can be referenced and
+ /// disposed. If disposing == false, the method has been called by the
+ /// runtime from inside the object finalizer and this method should not
+ /// reference other objects; in that case only unmanaged resources must
+ /// be referenced or disposed.
+ ///
+ ///
+ ///
+ /// indicates whether the Dispose method was invoked by user code.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ if (!_disposed)
+ {
+ if (disposing && (this._baseStream != null))
+ {
+ this._baseStream.Close();
+ this._Crc32 = _baseStream.Crc32;
+ }
+ _disposed = true;
+ }
+ }
+ finally
+ {
+ base.Dispose(disposing);
+ }
+ }
+
+
+ ///
+ /// Indicates whether the stream can be read.
+ ///
+ ///
+ /// The return value depends on whether the captive stream supports reading.
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ return _baseStream._stream.CanRead;
+ }
+ }
+
+ ///
+ /// Indicates whether the stream supports Seek operations.
+ ///
+ ///
+ /// Always returns false.
+ ///
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+
+ ///
+ /// Indicates whether the stream can be written.
+ ///
+ ///
+ /// The return value depends on whether the captive stream supports writing.
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ return _baseStream._stream.CanWrite;
+ }
+ }
+
+ ///
+ /// Flush the stream.
+ ///
+ public override void Flush()
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ _baseStream.Flush();
+ }
+
+ ///
+ /// Reading this property always throws a .
+ ///
+ public override long Length
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ ///
+ /// The position of the stream pointer.
+ ///
+ ///
+ ///
+ /// Setting this property always throws a . Reading will return the total bytes
+ /// written out, if used in writing, or the total bytes read in, if used in
+ /// reading. The count may refer to compressed bytes or uncompressed bytes,
+ /// depending on how you've used the stream.
+ ///
+ public override long Position
+ {
+ get
+ {
+ if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Writer)
+ return this._baseStream._z.TotalBytesOut + _headerByteCount;
+ if (this._baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Reader)
+ return this._baseStream._z.TotalBytesIn + this._baseStream._gzipHeaderByteCount;
+ return 0;
+ }
+
+ set { throw new NotImplementedException(); }
+ }
+
+ ///
+ /// Read and decompress data from the source stream.
+ ///
+ ///
+ ///
+ /// With a GZipStream, decompression is done through reading.
+ ///
+ ///
+ ///
+ ///
+ /// byte[] working = new byte[WORKING_BUFFER_SIZE];
+ /// using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile))
+ /// {
+ /// using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true))
+ /// {
+ /// using (var output = System.IO.File.Create(_DecompressedFile))
+ /// {
+ /// int n;
+ /// while ((n= decompressor.Read(working, 0, working.Length)) !=0)
+ /// {
+ /// output.Write(working, 0, n);
+ /// }
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ /// The buffer into which the decompressed data should be placed.
+ /// the offset within that data array to put the first byte read.
+ /// the number of bytes to read.
+ /// the number of bytes actually read
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ int n = _baseStream.Read(buffer, offset, count);
+
+ // Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n);
+ // Console.WriteLine( Util.FormatByteArray(buffer, offset, n) );
+
+ if (!_firstReadDone)
+ {
+ _firstReadDone = true;
+ FileName = _baseStream._GzipFileName;
+ Comment = _baseStream._GzipComment;
+ }
+ return n;
+ }
+
+
+
+ ///
+ /// Calling this method always throws a .
+ ///
+ /// irrelevant; it will always throw!
+ /// irrelevant; it will always throw!
+ /// irrelevant!
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Calling this method always throws a .
+ ///
+ /// irrelevant; this method will always throw!
+ public override void SetLength(long value)
+ {
+ //throw new NotImplementedException();
+ _baseStream.SetLength(value);
+ }
+
+ ///
+ /// Write data to the stream.
+ ///
+ ///
+ ///
+ ///
+ /// If you wish to use the GZipStream to compress data while writing,
+ /// you can create a GZipStream with CompressionMode.Compress, and a
+ /// writable output stream. Then call Write() on that GZipStream,
+ /// providing uncompressed data as input. The data sent to the output stream
+ /// will be the compressed form of the data written.
+ ///
+ ///
+ ///
+ /// A GZipStream can be used for Read() or Write(), but not
+ /// both. Writing implies compression. Reading implies decompression.
+ ///
+ ///
+ ///
+ /// The buffer holding data to write to the stream.
+ /// the offset within that data array to find the first byte to write.
+ /// the number of bytes to write.
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (_disposed) throw new ObjectDisposedException("GZipStream");
+ if (_baseStream._streamMode == BestHTTP.Decompression.Zlib.ZlibBaseStream.StreamMode.Undefined)
+ {
+ //Console.WriteLine("GZipStream: First write");
+ if (_baseStream._wantCompress)
+ {
+ // first write in compression, therefore, emit the GZIP header
+ _headerByteCount = EmitHeader();
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ _baseStream.Write(buffer, offset, count);
+ }
+ #endregion
+
+
+ internal static readonly System.DateTime _unixEpoch = new System.DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+ internal static readonly System.Text.Encoding iso8859dash1 = System.Text.Encoding.GetEncoding("iso-8859-1");
+
+
+ private int EmitHeader()
+ {
+ byte[] commentBytes = (Comment == null) ? null : iso8859dash1.GetBytes(Comment);
+ byte[] filenameBytes = (FileName == null) ? null : iso8859dash1.GetBytes(FileName);
+
+ int cbLength = (Comment == null) ? 0 : commentBytes.Length + 1;
+ int fnLength = (FileName == null) ? 0 : filenameBytes.Length + 1;
+
+ int bufferLength = 10 + cbLength + fnLength;
+ byte[] header = BufferPool.Get(bufferLength, false);
+ int i = 0;
+ // ID
+ header[i++] = 0x1F;
+ header[i++] = 0x8B;
+
+ // compression method
+ header[i++] = 8;
+ byte flag = 0;
+ if (Comment != null)
+ flag ^= 0x10;
+ if (FileName != null)
+ flag ^= 0x8;
+
+ // flag
+ header[i++] = flag;
+
+ // mtime
+ if (!LastModified.HasValue) LastModified = DateTime.Now;
+ System.TimeSpan delta = LastModified.Value - _unixEpoch;
+ Int32 timet = (Int32)delta.TotalSeconds;
+ Array.Copy(BitConverter.GetBytes(timet), 0, header, i, 4);
+ i += 4;
+
+ // xflg
+ header[i++] = 0; // this field is totally useless
+ // OS
+ header[i++] = 0xFF; // 0xFF == unspecified
+
+ // extra field length - only if FEXTRA is set, which it is not.
+ //header[i++]= 0;
+ //header[i++]= 0;
+
+ // filename
+ if (fnLength != 0)
+ {
+ Array.Copy(filenameBytes, 0, header, i, fnLength - 1);
+ i += fnLength - 1;
+ header[i++] = 0; // terminate
+ }
+
+ // comment
+ if (cbLength != 0)
+ {
+ Array.Copy(commentBytes, 0, header, i, cbLength - 1);
+ i += cbLength - 1;
+ header[i++] = 0; // terminate
+ }
+
+ _baseStream._stream.Write(header, 0, header.Length);
+ int headerLength = header.Length;
+
+ BufferPool.Release(header);
+
+ return headerLength; // bytes written
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7d859b36a59fd1059dec0f5b2a8bd9f8adb26a00
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/GZipStream.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: be79ee53ca164d04cbbb654f7de657f5
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/InfTree.cs b/Assets/Plugins/BestHTTP/Source/Decompression/InfTree.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3fdb425307e6bedbeb28cae52c41c4b159e1b2e6
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/InfTree.cs
@@ -0,0 +1,436 @@
+// Inftree.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2009-October-28 12:43:54>
+//
+// ------------------------------------------------------------------
+//
+// This module defines classes used in decompression. This code is derived
+// from the jzlib implementation of zlib. In keeping with the license for jzlib,
+// the copyright to that code is below.
+//
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+
+using System;
+namespace BestHTTP.Decompression.Zlib
+{
+
+ sealed class InfTree
+ {
+
+ private const int MANY = 1440;
+
+ private const int Z_OK = 0;
+ private const int Z_STREAM_END = 1;
+ private const int Z_NEED_DICT = 2;
+ private const int Z_ERRNO = - 1;
+ private const int Z_STREAM_ERROR = - 2;
+ private const int Z_DATA_ERROR = - 3;
+ private const int Z_MEM_ERROR = - 4;
+ private const int Z_BUF_ERROR = - 5;
+ private const int Z_VERSION_ERROR = - 6;
+
+ internal const int fixed_bl = 9;
+ internal const int fixed_bd = 5;
+
+ //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] fixed_tl = new int[]{96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186,
+ 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8,
+ 14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255};
+ //UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] fixed_td = new int[]{80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577};
+
+ // Tables for deflate from PKZIP's appnote.txt.
+ //UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] cplens = new int[]{3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+
+ // see note #13 above about 258
+ //UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] cplext = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112};
+
+ //UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] cpdist = new int[]{1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+
+ //UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
+ internal static readonly int[] cpdext = new int[]{0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+
+ // If BMAX needs to be larger than 16, then h and x[] should be uLong.
+ internal const int BMAX = 15; // maximum bit length of any code
+
+ internal int[] hn = null; // hufts used in space
+ internal int[] v = null; // work area for huft_build
+ internal int[] c = null; // bit length count table
+ internal int[] r = null; // table entity for structure assignment
+ internal int[] u = null; // table stack
+ internal int[] x = null; // bit offsets, then code stack
+
+ private int huft_build(int[] b, int bindex, int n, int s, int[] d, int[] e, int[] t, int[] m, int[] hp, int[] hn, int[] v)
+ {
+ // Given a list of code lengths and a maximum table size, make a set of
+ // tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
+ // if the given code set is incomplete (the tables are still built in this
+ // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
+ // lengths), or Z_MEM_ERROR if not enough memory.
+
+ int a; // counter for codes of length k
+ int f; // i repeats in table every f entries
+ int g; // maximum code length
+ int h; // table level
+ int i; // counter, current code
+ int j; // counter
+ int k; // number of bits in current code
+ int l; // bits per table (returned in m)
+ int mask; // (1 << w) - 1, to avoid cc -O bug on HP
+ int p; // pointer into c[], b[], or v[]
+ int q; // points to current table
+ int w; // bits before this table == (l * h)
+ int xp; // pointer into x
+ int y; // number of dummy codes added
+ int z; // number of entries in current table
+
+ // Generate counts for each bit length
+
+ p = 0; i = n;
+ do
+ {
+ c[b[bindex + p]]++; p++; i--; // assume all entries <= BMAX
+ }
+ while (i != 0);
+
+ if (c[0] == n)
+ {
+ // null input--all zero length codes
+ t[0] = - 1;
+ m[0] = 0;
+ return Z_OK;
+ }
+
+ // Find minimum and maximum length, bound *m by those
+ l = m[0];
+ for (j = 1; j <= BMAX; j++)
+ if (c[j] != 0)
+ break;
+ k = j; // minimum code length
+ if (l < j)
+ {
+ l = j;
+ }
+ for (i = BMAX; i != 0; i--)
+ {
+ if (c[i] != 0)
+ break;
+ }
+ g = i; // maximum code length
+ if (l > i)
+ {
+ l = i;
+ }
+ m[0] = l;
+
+ // Adjust last length count to fill out codes, if needed
+ for (y = 1 << j; j < i; j++, y <<= 1)
+ {
+ if ((y -= c[j]) < 0)
+ {
+ return Z_DATA_ERROR;
+ }
+ }
+ if ((y -= c[i]) < 0)
+ {
+ return Z_DATA_ERROR;
+ }
+ c[i] += y;
+
+ // Generate starting offsets into the value table for each length
+ x[1] = j = 0;
+ p = 1; xp = 2;
+ while (--i != 0)
+ {
+ // note that i == g from above
+ x[xp] = (j += c[p]);
+ xp++;
+ p++;
+ }
+
+ // Make a table of values in order of bit lengths
+ i = 0; p = 0;
+ do
+ {
+ if ((j = b[bindex + p]) != 0)
+ {
+ v[x[j]++] = i;
+ }
+ p++;
+ }
+ while (++i < n);
+ n = x[g]; // set n to length of v
+
+ // Generate the Huffman codes and for each, make the table entries
+ x[0] = i = 0; // first Huffman code is zero
+ p = 0; // grab values in bit order
+ h = - 1; // no tables yet--level -1
+ w = - l; // bits decoded == (l * h)
+ u[0] = 0; // just to keep compilers happy
+ q = 0; // ditto
+ z = 0; // ditto
+
+ // go through the bit lengths (k already is bits in shortest code)
+ for (; k <= g; k++)
+ {
+ a = c[k];
+ while (a-- != 0)
+ {
+ // here i is the Huffman code of length k bits for value *p
+ // make tables up to required level
+ while (k > w + l)
+ {
+ h++;
+ w += l; // previous table always l bits
+ // compute minimum size table less than or equal to l bits
+ z = g - w;
+ z = (z > l)?l:z; // table size upper limit
+ if ((f = 1 << (j = k - w)) > a + 1)
+ {
+ // try a k-w bit table
+ // too few codes for k-w bit table
+ f -= (a + 1); // deduct codes from patterns left
+ xp = k;
+ if (j < z)
+ {
+ while (++j < z)
+ {
+ // try smaller tables up to z bits
+ if ((f <<= 1) <= c[++xp])
+ break; // enough codes to use up j bits
+ f -= c[xp]; // else deduct codes from patterns
+ }
+ }
+ }
+ z = 1 << j; // table entries for j-bit table
+
+ // allocate new table
+ if (hn[0] + z > MANY)
+ {
+ // (note: doesn't matter for fixed)
+ return Z_DATA_ERROR; // overflow of MANY
+ }
+ u[h] = q = hn[0]; // DEBUG
+ hn[0] += z;
+
+ // connect to last table, if there is one
+ if (h != 0)
+ {
+ x[h] = i; // save pattern for backing up
+ r[0] = (sbyte) j; // bits in this table
+ r[1] = (sbyte) l; // bits to dump before this table
+ j = SharedUtils.URShift(i, (w - l));
+ r[2] = (int) (q - u[h - 1] - j); // offset to this table
+ Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table
+ }
+ else
+ {
+ t[0] = q; // first table is returned result
+ }
+ }
+
+ // set up table entity in r
+ r[1] = (sbyte) (k - w);
+ if (p >= n)
+ {
+ r[0] = 128 + 64; // out of values--invalid code
+ }
+ else if (v[p] < s)
+ {
+ r[0] = (sbyte) (v[p] < 256?0:32 + 64); // 256 is end-of-block
+ r[2] = v[p++]; // simple code is just the value
+ }
+ else
+ {
+ r[0] = (sbyte) (e[v[p] - s] + 16 + 64); // non-simple--look up in lists
+ r[2] = d[v[p++] - s];
+ }
+
+ // fill code-like entries with r
+ f = 1 << (k - w);
+ for (j = SharedUtils.URShift(i, w); j < z; j += f)
+ {
+ Array.Copy(r, 0, hp, (q + j) * 3, 3);
+ }
+
+ // backwards increment the k-bit code i
+ for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1))
+ {
+ i ^= j;
+ }
+ i ^= j;
+
+ // backup over finished tables
+ mask = (1 << w) - 1; // needed on HP, cc -O bug
+ while ((i & mask) != x[h])
+ {
+ h--; // don't need to update q
+ w -= l;
+ mask = (1 << w) - 1;
+ }
+ }
+ }
+ // Return Z_BUF_ERROR if we were given an incomplete table
+ return y != 0 && g != 1?Z_BUF_ERROR:Z_OK;
+ }
+
+ internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z)
+ {
+ int result;
+ initWorkArea(19);
+ hn[0] = 0;
+ result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v);
+
+ if (result == Z_DATA_ERROR)
+ {
+ z.Message = "oversubscribed dynamic bit lengths tree";
+ }
+ else if (result == Z_BUF_ERROR || bb[0] == 0)
+ {
+ z.Message = "incomplete dynamic bit lengths tree";
+ result = Z_DATA_ERROR;
+ }
+ return result;
+ }
+
+ internal int inflate_trees_dynamic(int nl, int nd, int[] c, int[] bl, int[] bd, int[] tl, int[] td, int[] hp, ZlibCodec z)
+ {
+ int result;
+
+ // build literal/length tree
+ initWorkArea(288);
+ hn[0] = 0;
+ result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v);
+ if (result != Z_OK || bl[0] == 0)
+ {
+ if (result == Z_DATA_ERROR)
+ {
+ z.Message = "oversubscribed literal/length tree";
+ }
+ else if (result != Z_MEM_ERROR)
+ {
+ z.Message = "incomplete literal/length tree";
+ result = Z_DATA_ERROR;
+ }
+ return result;
+ }
+
+ // build distance tree
+ initWorkArea(288);
+ result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v);
+
+ if (result != Z_OK || (bd[0] == 0 && nl > 257))
+ {
+ if (result == Z_DATA_ERROR)
+ {
+ z.Message = "oversubscribed distance tree";
+ }
+ else if (result == Z_BUF_ERROR)
+ {
+ z.Message = "incomplete distance tree";
+ result = Z_DATA_ERROR;
+ }
+ else if (result != Z_MEM_ERROR)
+ {
+ z.Message = "empty distance tree with lengths";
+ result = Z_DATA_ERROR;
+ }
+ return result;
+ }
+
+ return Z_OK;
+ }
+
+ internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z)
+ {
+ bl[0] = fixed_bl;
+ bd[0] = fixed_bd;
+ tl[0] = fixed_tl;
+ td[0] = fixed_td;
+ return Z_OK;
+ }
+
+ private void initWorkArea(int vsize)
+ {
+ if (hn == null)
+ {
+ hn = new int[1];
+ v = new int[vsize];
+ c = new int[BMAX + 1];
+ r = new int[3];
+ u = new int[BMAX];
+ x = new int[BMAX + 1];
+ }
+ else
+ {
+ if (v.Length < vsize)
+ {
+ v = new int[vsize];
+ }
+ Array.Clear(v,0,vsize);
+ Array.Clear(c,0,BMAX+1);
+ r[0]=0; r[1]=0; r[2]=0;
+ // for(int i=0; i
+//
+// ------------------------------------------------------------------
+//
+// This module defines classes for decompression. This code is derived
+// from the jzlib implementation of zlib, but significantly modified.
+// The object model is not the same, and many of the behaviors are
+// different. Nonetheless, in keeping with the license for jzlib, I am
+// reproducing the copyright to that code here.
+//
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+using BestHTTP.PlatformSupport.Memory;
+using System;
+namespace BestHTTP.Decompression.Zlib
+{
+ sealed class InflateBlocks
+ {
+ private const int MANY = 1440;
+
+ // Table for deflate from PKZIP's appnote.txt.
+ internal static readonly int[] border = new int[]
+ { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ private enum InflateBlockMode
+ {
+ TYPE = 0, // get type bits (3, including end bit)
+ LENS = 1, // get lengths for stored
+ STORED = 2, // processing stored block
+ TABLE = 3, // get table lengths
+ BTREE = 4, // get bit lengths tree for a dynamic block
+ DTREE = 5, // get length, distance trees for a dynamic block
+ CODES = 6, // processing fixed or dynamic block
+ DRY = 7, // output remaining window bytes
+ DONE = 8, // finished last block, done
+ BAD = 9, // ot a data error--stuck here
+ }
+
+ private InflateBlockMode mode; // current inflate_block mode
+
+ internal int left; // if STORED, bytes left to copy
+
+ internal int table; // table lengths (14 bits)
+ internal int index; // index into blens (or border)
+ internal int[] blens; // bit lengths of codes
+ internal int[] bb = new int[1]; // bit length tree depth
+ internal int[] tb = new int[1]; // bit length decoding tree
+
+ internal InflateCodes codes = new InflateCodes(); // if CODES, current state
+
+ internal int last; // true if this block is the last block
+
+ internal ZlibCodec _codec; // pointer back to this zlib stream
+
+ // mode independent information
+ internal int bitk; // bits in bit buffer
+ internal int bitb; // bit buffer
+ internal int[] hufts; // single malloc for tree space
+ internal byte[] window; // sliding window
+ internal int end; // one byte after sliding window
+ internal int readAt; // window read pointer
+ internal int writeAt; // window write pointer
+ internal System.Object checkfn; // check function
+ internal uint check; // check on output
+
+ internal InfTree inftree = new InfTree();
+
+ internal InflateBlocks(ZlibCodec codec, System.Object checkfn, int w)
+ {
+ _codec = codec;
+ hufts = new int[MANY * 3];
+ window = BufferPool.Get(w, true);
+ end = w;
+ this.checkfn = checkfn;
+ mode = InflateBlockMode.TYPE;
+ Reset();
+ }
+
+ internal uint Reset()
+ {
+ uint oldCheck = check;
+ mode = InflateBlockMode.TYPE;
+ bitk = 0;
+ bitb = 0;
+ readAt = writeAt = 0;
+
+ if (checkfn != null)
+ _codec._Adler32 = check = Adler.Adler32(0, null, 0, 0);
+ return oldCheck;
+ }
+
+
+ internal int Process(int r)
+ {
+ int t; // temporary storage
+ int b; // bit buffer
+ int k; // bits in bit buffer
+ int p; // input data pointer
+ int n; // bytes available there
+ int q; // output window write pointer
+ int m; // bytes to end of window or read pointer
+
+ // copy input/output information to locals (UPDATE macro restores)
+
+ p = _codec.NextIn;
+ n = _codec.AvailableBytesIn;
+ b = bitb;
+ k = bitk;
+
+ q = writeAt;
+ m = (int)(q < readAt ? readAt - q - 1 : end - q);
+
+
+ // process input based on current state
+ while (true)
+ {
+ switch (mode)
+ {
+ case InflateBlockMode.TYPE:
+
+ while (k < (3))
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+ t = (int)(b & 7);
+ last = t & 1;
+
+ switch ((uint)t >> 1)
+ {
+ case 0: // stored
+ b >>= 3; k -= (3);
+ t = k & 7; // go to byte boundary
+ b >>= t; k -= t;
+ mode = InflateBlockMode.LENS; // get length of stored block
+ break;
+
+ case 1: // fixed
+ int[] bl = new int[1];
+ int[] bd = new int[1];
+ int[][] tl = new int[1][];
+ int[][] td = new int[1][];
+ InfTree.inflate_trees_fixed(bl, bd, tl, td, _codec);
+ codes.Init(bl[0], bd[0], tl[0], 0, td[0], 0);
+ b >>= 3; k -= 3;
+ mode = InflateBlockMode.CODES;
+ break;
+
+ case 2: // dynamic
+ b >>= 3; k -= 3;
+ mode = InflateBlockMode.TABLE;
+ break;
+
+ case 3: // illegal
+ b >>= 3; k -= 3;
+ mode = InflateBlockMode.BAD;
+ _codec.Message = "invalid block type";
+ r = ZlibConstants.Z_DATA_ERROR;
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ break;
+
+ case InflateBlockMode.LENS:
+
+ while (k < (32))
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ ;
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ if ( ( ((~b)>>16) & 0xffff) != (b & 0xffff))
+ {
+ mode = InflateBlockMode.BAD;
+ _codec.Message = "invalid stored block lengths";
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ left = (b & 0xffff);
+ b = k = 0; // dump bits
+ mode = left != 0 ? InflateBlockMode.STORED : (last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE);
+ break;
+
+ case InflateBlockMode.STORED:
+ if (n == 0)
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ if (m == 0)
+ {
+ if (q == end && readAt != 0)
+ {
+ q = 0; m = (int)(q < readAt ? readAt - q - 1 : end - q);
+ }
+ if (m == 0)
+ {
+ writeAt = q;
+ r = Flush(r);
+ q = writeAt; m = (int)(q < readAt ? readAt - q - 1 : end - q);
+ if (q == end && readAt != 0)
+ {
+ q = 0; m = (int)(q < readAt ? readAt - q - 1 : end - q);
+ }
+ if (m == 0)
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ }
+ }
+ r = ZlibConstants.Z_OK;
+
+ t = left;
+ if (t > n)
+ t = n;
+ if (t > m)
+ t = m;
+ Array.Copy(_codec.InputBuffer, p, window, q, t);
+ p += t; n -= t;
+ q += t; m -= t;
+ if ((left -= t) != 0)
+ break;
+ mode = last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE;
+ break;
+
+ case InflateBlockMode.TABLE:
+
+ while (k < (14))
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ table = t = (b & 0x3fff);
+ if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
+ {
+ mode = InflateBlockMode.BAD;
+ _codec.Message = "too many length or distance symbols";
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
+ if (blens == null || blens.Length < t)
+ {
+ blens = new int[t];
+ }
+ else
+ {
+ Array.Clear(blens, 0, t);
+ // for (int i = 0; i < t; i++)
+ // {
+ // blens[i] = 0;
+ // }
+ }
+
+ b >>= 14;
+ k -= 14;
+
+
+ index = 0;
+ mode = InflateBlockMode.BTREE;
+ goto case InflateBlockMode.BTREE;
+
+ case InflateBlockMode.BTREE:
+ while (index < 4 + (table >> 10))
+ {
+ while (k < (3))
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ blens[border[index++]] = b & 7;
+
+ b >>= 3; k -= 3;
+ }
+
+ while (index < 19)
+ {
+ blens[border[index++]] = 0;
+ }
+
+ bb[0] = 7;
+ t = inftree.inflate_trees_bits(blens, bb, tb, hufts, _codec);
+ if (t != ZlibConstants.Z_OK)
+ {
+ r = t;
+ if (r == ZlibConstants.Z_DATA_ERROR)
+ {
+ blens = null;
+ mode = InflateBlockMode.BAD;
+ }
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ index = 0;
+ mode = InflateBlockMode.DTREE;
+ goto case InflateBlockMode.DTREE;
+
+ case InflateBlockMode.DTREE:
+ while (true)
+ {
+ t = table;
+ if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)))
+ {
+ break;
+ }
+
+ int i, j, c;
+
+ t = bb[0];
+
+ while (k < t)
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ t = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 1];
+ c = hufts[(tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3 + 2];
+
+ if (c < 16)
+ {
+ b >>= t; k -= t;
+ blens[index++] = c;
+ }
+ else
+ {
+ // c == 16..18
+ i = c == 18 ? 7 : c - 14;
+ j = c == 18 ? 11 : 3;
+
+ while (k < (t + i))
+ {
+ if (n != 0)
+ {
+ r = ZlibConstants.Z_OK;
+ }
+ else
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ n--;
+ b |= (_codec.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ b >>= t; k -= t;
+
+ j += (b & InternalInflateConstants.InflateMask[i]);
+
+ b >>= i; k -= i;
+
+ i = index;
+ t = table;
+ if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1))
+ {
+ blens = null;
+ mode = InflateBlockMode.BAD;
+ _codec.Message = "invalid bit length repeat";
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+
+ c = (c == 16) ? blens[i-1] : 0;
+ do
+ {
+ blens[i++] = c;
+ }
+ while (--j != 0);
+ index = i;
+ }
+ }
+
+ tb[0] = -1;
+ {
+ int[] bl = new int[] { 9 }; // must be <= 9 for lookahead assumptions
+ int[] bd = new int[] { 6 }; // must be <= 9 for lookahead assumptions
+ int[] tl = new int[1];
+ int[] td = new int[1];
+
+ t = table;
+ t = inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), blens, bl, bd, tl, td, hufts, _codec);
+
+ if (t != ZlibConstants.Z_OK)
+ {
+ if (t == ZlibConstants.Z_DATA_ERROR)
+ {
+ blens = null;
+ mode = InflateBlockMode.BAD;
+ }
+ r = t;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ codes.Init(bl[0], bd[0], hufts, tl[0], hufts, td[0]);
+ }
+ mode = InflateBlockMode.CODES;
+ goto case InflateBlockMode.CODES;
+
+ case InflateBlockMode.CODES:
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+
+ r = codes.Process(this, r);
+ if (r != ZlibConstants.Z_STREAM_END)
+ {
+ return Flush(r);
+ }
+
+ r = ZlibConstants.Z_OK;
+ p = _codec.NextIn;
+ n = _codec.AvailableBytesIn;
+ b = bitb;
+ k = bitk;
+ q = writeAt;
+ m = (int)(q < readAt ? readAt - q - 1 : end - q);
+
+ if (last == 0)
+ {
+ mode = InflateBlockMode.TYPE;
+ break;
+ }
+ mode = InflateBlockMode.DRY;
+ goto case InflateBlockMode.DRY;
+
+ case InflateBlockMode.DRY:
+ writeAt = q;
+ r = Flush(r);
+ q = writeAt; m = (int)(q < readAt ? readAt - q - 1 : end - q);
+ if (readAt != writeAt)
+ {
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ mode = InflateBlockMode.DONE;
+ goto case InflateBlockMode.DONE;
+
+ case InflateBlockMode.DONE:
+ r = ZlibConstants.Z_STREAM_END;
+ bitb = b;
+ bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+
+ case InflateBlockMode.BAD:
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+
+
+ default:
+ r = ZlibConstants.Z_STREAM_ERROR;
+
+ bitb = b; bitk = k;
+ _codec.AvailableBytesIn = n;
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ writeAt = q;
+ return Flush(r);
+ }
+ }
+ }
+
+
+ internal void Free()
+ {
+ Reset();
+ BufferPool.Release(window);
+ window = null;
+ hufts = null;
+ }
+
+ internal void SetDictionary(byte[] d, int start, int n)
+ {
+ Array.Copy(d, start, window, 0, n);
+ readAt = writeAt = n;
+ }
+
+ // Returns true if inflate is currently at the end of a block generated
+ // by Z_SYNC_FLUSH or Z_FULL_FLUSH.
+ internal int SyncPoint()
+ {
+ return mode == InflateBlockMode.LENS ? 1 : 0;
+ }
+
+ // copy as much as possible from the sliding window to the output area
+ internal int Flush(int r)
+ {
+ int nBytes;
+
+ for (int pass=0; pass < 2; pass++)
+ {
+ if (pass==0)
+ {
+ // compute number of bytes to copy as far as end of window
+ nBytes = (int)((readAt <= writeAt ? writeAt : end) - readAt);
+ }
+ else
+ {
+ // compute bytes to copy
+ nBytes = writeAt - readAt;
+ }
+
+ // workitem 8870
+ if (nBytes == 0)
+ {
+ if (r == ZlibConstants.Z_BUF_ERROR)
+ r = ZlibConstants.Z_OK;
+ return r;
+ }
+
+ if (nBytes > _codec.AvailableBytesOut)
+ nBytes = _codec.AvailableBytesOut;
+
+ if (nBytes != 0 && r == ZlibConstants.Z_BUF_ERROR)
+ r = ZlibConstants.Z_OK;
+
+ // update counters
+ _codec.AvailableBytesOut -= nBytes;
+ _codec.TotalBytesOut += nBytes;
+
+ // update check information
+ if (checkfn != null)
+ _codec._Adler32 = check = Adler.Adler32(check, window, readAt, nBytes);
+
+ // copy as far as end of window
+ Array.Copy(window, readAt, _codec.OutputBuffer, _codec.NextOut, nBytes);
+ _codec.NextOut += nBytes;
+ readAt += nBytes;
+
+ // see if more to copy at beginning of window
+ if (readAt == end && pass == 0)
+ {
+ // wrap pointers
+ readAt = 0;
+ if (writeAt == end)
+ writeAt = 0;
+ }
+ else pass++;
+ }
+
+ // done
+ return r;
+ }
+ }
+
+
+ internal static class InternalInflateConstants
+ {
+ // And'ing with mask[n] masks the lower n bits
+ internal static readonly int[] InflateMask = new int[] {
+ 0x00000000, 0x00000001, 0x00000003, 0x00000007,
+ 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f,
+ 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
+ 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff };
+ }
+
+
+ sealed class InflateCodes
+ {
+ // waiting for "i:"=input,
+ // "o:"=output,
+ // "x:"=nothing
+ private const int START = 0; // x: set up for LEN
+ private const int LEN = 1; // i: get length/literal/eob next
+ private const int LENEXT = 2; // i: getting length extra (have base)
+ private const int DIST = 3; // i: get distance next
+ private const int DISTEXT = 4; // i: getting distance extra
+ private const int COPY = 5; // o: copying bytes in window, waiting for space
+ private const int LIT = 6; // o: got literal, waiting for output space
+ private const int WASH = 7; // o: got eob, possibly still output waiting
+ private const int END = 8; // x: got eob and all data flushed
+ private const int BADCODE = 9; // x: got error
+
+ internal int mode; // current inflate_codes mode
+
+ // mode dependent information
+ internal int len;
+
+ internal int[] tree; // pointer into tree
+ internal int tree_index = 0;
+ internal int need; // bits needed
+
+ internal int lit;
+
+ // if EXT or COPY, where and how much
+ internal int bitsToGet; // bits to get for extra
+ internal int dist; // distance back to copy from
+
+ internal byte lbits; // ltree bits decoded per branch
+ internal byte dbits; // dtree bits decoder per branch
+ internal int[] ltree; // literal/length/eob tree
+ internal int ltree_index; // literal/length/eob tree
+ internal int[] dtree; // distance tree
+ internal int dtree_index; // distance tree
+
+ internal InflateCodes()
+ {
+ }
+
+ internal void Init(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index)
+ {
+ mode = START;
+ lbits = (byte)bl;
+ dbits = (byte)bd;
+ ltree = tl;
+ ltree_index = tl_index;
+ dtree = td;
+ dtree_index = td_index;
+ tree = null;
+ }
+
+ internal int Process(InflateBlocks blocks, int r)
+ {
+ int j; // temporary storage
+ int tindex; // temporary pointer
+ int e; // extra bits or operation
+ int b = 0; // bit buffer
+ int k = 0; // bits in bit buffer
+ int p = 0; // input data pointer
+ int n; // bytes available there
+ int q; // output window write pointer
+ int m; // bytes to end of window or read pointer
+ int f; // pointer to copy strings from
+
+ ZlibCodec z = blocks._codec;
+
+ // copy input/output information to locals (UPDATE macro restores)
+ p = z.NextIn;
+ n = z.AvailableBytesIn;
+ b = blocks.bitb;
+ k = blocks.bitk;
+ q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+
+ // process input and output based on current state
+ while (true)
+ {
+ switch (mode)
+ {
+ // waiting for "i:"=input, "o:"=output, "x:"=nothing
+ case START: // x: set up for LEN
+ if (m >= 258 && n >= 10)
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n;
+ z.TotalBytesIn += p - z.NextIn;
+ z.NextIn = p;
+ blocks.writeAt = q;
+ r = InflateFast(lbits, dbits, ltree, ltree_index, dtree, dtree_index, blocks, z);
+
+ p = z.NextIn;
+ n = z.AvailableBytesIn;
+ b = blocks.bitb;
+ k = blocks.bitk;
+ q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+
+ if (r != ZlibConstants.Z_OK)
+ {
+ mode = (r == ZlibConstants.Z_STREAM_END) ? WASH : BADCODE;
+ break;
+ }
+ }
+ need = lbits;
+ tree = ltree;
+ tree_index = ltree_index;
+
+ mode = LEN;
+ goto case LEN;
+
+ case LEN: // i: get length/literal/eob next
+ j = need;
+
+ while (k < j)
+ {
+ if (n != 0)
+ r = ZlibConstants.Z_OK;
+ else
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n;
+ z.TotalBytesIn += p - z.NextIn;
+ z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ n--;
+ b |= (z.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3;
+
+ b >>= (tree[tindex + 1]);
+ k -= (tree[tindex + 1]);
+
+ e = tree[tindex];
+
+ if (e == 0)
+ {
+ // literal
+ lit = tree[tindex + 2];
+ mode = LIT;
+ break;
+ }
+ if ((e & 16) != 0)
+ {
+ // length
+ bitsToGet = e & 15;
+ len = tree[tindex + 2];
+ mode = LENEXT;
+ break;
+ }
+ if ((e & 64) == 0)
+ {
+ // next table
+ need = e;
+ tree_index = tindex / 3 + tree[tindex + 2];
+ break;
+ }
+ if ((e & 32) != 0)
+ {
+ // end of block
+ mode = WASH;
+ break;
+ }
+ mode = BADCODE; // invalid code
+ z.Message = "invalid literal/length code";
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n;
+ z.TotalBytesIn += p - z.NextIn;
+ z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+
+
+ case LENEXT: // i: getting length extra (have base)
+ j = bitsToGet;
+
+ while (k < j)
+ {
+ if (n != 0)
+ r = ZlibConstants.Z_OK;
+ else
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ n--; b |= (z.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ len += (b & InternalInflateConstants.InflateMask[j]);
+
+ b >>= j;
+ k -= j;
+
+ need = dbits;
+ tree = dtree;
+ tree_index = dtree_index;
+ mode = DIST;
+ goto case DIST;
+
+ case DIST: // i: get distance next
+ j = need;
+
+ while (k < j)
+ {
+ if (n != 0)
+ r = ZlibConstants.Z_OK;
+ else
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ n--; b |= (z.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3;
+
+ b >>= tree[tindex + 1];
+ k -= tree[tindex + 1];
+
+ e = (tree[tindex]);
+ if ((e & 0x10) != 0)
+ {
+ // distance
+ bitsToGet = e & 15;
+ dist = tree[tindex + 2];
+ mode = DISTEXT;
+ break;
+ }
+ if ((e & 64) == 0)
+ {
+ // next table
+ need = e;
+ tree_index = tindex / 3 + tree[tindex + 2];
+ break;
+ }
+ mode = BADCODE; // invalid code
+ z.Message = "invalid distance code";
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+
+
+ case DISTEXT: // i: getting distance extra
+ j = bitsToGet;
+
+ while (k < j)
+ {
+ if (n != 0)
+ r = ZlibConstants.Z_OK;
+ else
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ n--; b |= (z.InputBuffer[p++] & 0xff) << k;
+ k += 8;
+ }
+
+ dist += (b & InternalInflateConstants.InflateMask[j]);
+
+ b >>= j;
+ k -= j;
+
+ mode = COPY;
+ goto case COPY;
+
+ case COPY: // o: copying bytes in window, waiting for space
+ f = q - dist;
+ while (f < 0)
+ {
+ // modulo window size-"while" instead
+ f += blocks.end; // of "if" handles invalid distances
+ }
+ while (len != 0)
+ {
+ if (m == 0)
+ {
+ if (q == blocks.end && blocks.readAt != 0)
+ {
+ q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+ }
+ if (m == 0)
+ {
+ blocks.writeAt = q; r = blocks.Flush(r);
+ q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+
+ if (q == blocks.end && blocks.readAt != 0)
+ {
+ q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+ }
+
+ if (m == 0)
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n;
+ z.TotalBytesIn += p - z.NextIn;
+ z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ }
+ }
+
+ blocks.window[q++] = blocks.window[f++]; m--;
+
+ if (f == blocks.end)
+ f = 0;
+ len--;
+ }
+ mode = START;
+ break;
+
+ case LIT: // o: got literal, waiting for output space
+ if (m == 0)
+ {
+ if (q == blocks.end && blocks.readAt != 0)
+ {
+ q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+ }
+ if (m == 0)
+ {
+ blocks.writeAt = q; r = blocks.Flush(r);
+ q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+
+ if (q == blocks.end && blocks.readAt != 0)
+ {
+ q = 0; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+ }
+ if (m == 0)
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ }
+ }
+ r = ZlibConstants.Z_OK;
+
+ blocks.window[q++] = (byte)lit; m--;
+
+ mode = START;
+ break;
+
+ case WASH: // o: got eob, possibly more output
+ if (k > 7)
+ {
+ // return unused byte, if any
+ k -= 8;
+ n++;
+ p--; // can always return one
+ }
+
+ blocks.writeAt = q; r = blocks.Flush(r);
+ q = blocks.writeAt; m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
+
+ if (blocks.readAt != blocks.writeAt)
+ {
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ mode = END;
+ goto case END;
+
+ case END:
+ r = ZlibConstants.Z_STREAM_END;
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+
+ case BADCODE: // x: got error
+
+ r = ZlibConstants.Z_DATA_ERROR;
+
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+
+ default:
+ r = ZlibConstants.Z_STREAM_ERROR;
+
+ blocks.bitb = b; blocks.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ blocks.writeAt = q;
+ return blocks.Flush(r);
+ }
+ }
+ }
+
+
+ // Called with number of bytes left to write in window at least 258
+ // (the maximum string length) and number of input bytes available
+ // at least ten. The ten bytes are six bytes for the longest length/
+ // distance pair plus four bytes for overloading the bit buffer.
+
+ internal int InflateFast(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index, InflateBlocks s, ZlibCodec z)
+ {
+ int t; // temporary pointer
+ int[] tp; // temporary pointer
+ int tp_index; // temporary pointer
+ int e; // extra bits or operation
+ int b; // bit buffer
+ int k; // bits in bit buffer
+ int p; // input data pointer
+ int n; // bytes available there
+ int q; // output window write pointer
+ int m; // bytes to end of window or read pointer
+ int ml; // mask for literal/length tree
+ int md; // mask for distance tree
+ int c; // bytes to copy
+ int d; // distance back to copy from
+ int r; // copy source pointer
+
+ int tp_index_t_3; // (tp_index+t)*3
+
+ // load input, output, bit values
+ p = z.NextIn; n = z.AvailableBytesIn; b = s.bitb; k = s.bitk;
+ q = s.writeAt; m = q < s.readAt ? s.readAt - q - 1 : s.end - q;
+
+ // initialize masks
+ ml = InternalInflateConstants.InflateMask[bl];
+ md = InternalInflateConstants.InflateMask[bd];
+
+ // do until not enough input or output space for fast loop
+ do
+ {
+ // assume called with m >= 258 && n >= 10
+ // get literal/length code
+ while (k < (20))
+ {
+ // max bits for literal/length code
+ n--;
+ b |= (z.InputBuffer[p++] & 0xff) << k; k += 8;
+ }
+
+ t = b & ml;
+ tp = tl;
+ tp_index = tl_index;
+ tp_index_t_3 = (tp_index + t) * 3;
+ if ((e = tp[tp_index_t_3]) == 0)
+ {
+ b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]);
+
+ s.window[q++] = (byte)tp[tp_index_t_3 + 2];
+ m--;
+ continue;
+ }
+ do
+ {
+
+ b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]);
+
+ if ((e & 16) != 0)
+ {
+ e &= 15;
+ c = tp[tp_index_t_3 + 2] + ((int)b & InternalInflateConstants.InflateMask[e]);
+
+ b >>= e; k -= e;
+
+ // decode distance base of block to copy
+ while (k < 15)
+ {
+ // max bits for distance code
+ n--;
+ b |= (z.InputBuffer[p++] & 0xff) << k; k += 8;
+ }
+
+ t = b & md;
+ tp = td;
+ tp_index = td_index;
+ tp_index_t_3 = (tp_index + t) * 3;
+ e = tp[tp_index_t_3];
+
+ do
+ {
+
+ b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]);
+
+ if ((e & 16) != 0)
+ {
+ // get extra bits to add to distance base
+ e &= 15;
+ while (k < e)
+ {
+ // get extra bits (up to 13)
+ n--;
+ b |= (z.InputBuffer[p++] & 0xff) << k; k += 8;
+ }
+
+ d = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]);
+
+ b >>= e; k -= e;
+
+ // do the copy
+ m -= c;
+ if (q >= d)
+ {
+ // offset before dest
+ // just copy
+ r = q - d;
+ if (q - r > 0 && 2 > (q - r))
+ {
+ s.window[q++] = s.window[r++]; // minimum count is three,
+ s.window[q++] = s.window[r++]; // so unroll loop a little
+ c -= 2;
+ }
+ else
+ {
+ Array.Copy(s.window, r, s.window, q, 2);
+ q += 2; r += 2; c -= 2;
+ }
+ }
+ else
+ {
+ // else offset after destination
+ r = q - d;
+ do
+ {
+ r += s.end; // force pointer in window
+ }
+ while (r < 0); // covers invalid distances
+ e = s.end - r;
+ if (c > e)
+ {
+ // if source crosses,
+ c -= e; // wrapped copy
+ if (q - r > 0 && e > (q - r))
+ {
+ do
+ {
+ s.window[q++] = s.window[r++];
+ }
+ while (--e != 0);
+ }
+ else
+ {
+ Array.Copy(s.window, r, s.window, q, e);
+ q += e; r += e; e = 0;
+ }
+ r = 0; // copy rest from start of window
+ }
+ }
+
+ // copy all or what's left
+ if (q - r > 0 && c > (q - r))
+ {
+ do
+ {
+ s.window[q++] = s.window[r++];
+ }
+ while (--c != 0);
+ }
+ else
+ {
+ Array.Copy(s.window, r, s.window, q, c);
+ q += c; r += c; c = 0;
+ }
+ break;
+ }
+ else if ((e & 64) == 0)
+ {
+ t += tp[tp_index_t_3 + 2];
+ t += (b & InternalInflateConstants.InflateMask[e]);
+ tp_index_t_3 = (tp_index + t) * 3;
+ e = tp[tp_index_t_3];
+ }
+ else
+ {
+ z.Message = "invalid distance code";
+
+ c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3);
+
+ s.bitb = b; s.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ s.writeAt = q;
+
+ return ZlibConstants.Z_DATA_ERROR;
+ }
+ }
+ while (true);
+ break;
+ }
+
+ if ((e & 64) == 0)
+ {
+ t += tp[tp_index_t_3 + 2];
+ t += (b & InternalInflateConstants.InflateMask[e]);
+ tp_index_t_3 = (tp_index + t) * 3;
+ if ((e = tp[tp_index_t_3]) == 0)
+ {
+ b >>= (tp[tp_index_t_3 + 1]); k -= (tp[tp_index_t_3 + 1]);
+ s.window[q++] = (byte)tp[tp_index_t_3 + 2];
+ m--;
+ break;
+ }
+ }
+ else if ((e & 32) != 0)
+ {
+ c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3);
+
+ s.bitb = b; s.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ s.writeAt = q;
+
+ return ZlibConstants.Z_STREAM_END;
+ }
+ else
+ {
+ z.Message = "invalid literal/length code";
+
+ c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3);
+
+ s.bitb = b; s.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ s.writeAt = q;
+
+ return ZlibConstants.Z_DATA_ERROR;
+ }
+ }
+ while (true);
+ }
+ while (m >= 258 && n >= 10);
+
+ // not enough input or output--restore pointers and return
+ c = z.AvailableBytesIn - n; c = (k >> 3) < c ? k >> 3 : c; n += c; p -= c; k -= (c << 3);
+
+ s.bitb = b; s.bitk = k;
+ z.AvailableBytesIn = n; z.TotalBytesIn += p - z.NextIn; z.NextIn = p;
+ s.writeAt = q;
+
+ return ZlibConstants.Z_OK;
+ }
+ }
+
+
+ internal sealed class InflateManager
+ {
+ // preset dictionary flag in zlib header
+ private const int PRESET_DICT = 0x20;
+
+ private const int Z_DEFLATED = 8;
+
+ private enum InflateManagerMode
+ {
+ METHOD = 0, // waiting for method byte
+ FLAG = 1, // waiting for flag byte
+ DICT4 = 2, // four dictionary check bytes to go
+ DICT3 = 3, // three dictionary check bytes to go
+ DICT2 = 4, // two dictionary check bytes to go
+ DICT1 = 5, // one dictionary check byte to go
+ DICT0 = 6, // waiting for inflateSetDictionary
+ BLOCKS = 7, // decompressing blocks
+ CHECK4 = 8, // four check bytes to go
+ CHECK3 = 9, // three check bytes to go
+ CHECK2 = 10, // two check bytes to go
+ CHECK1 = 11, // one check byte to go
+ DONE = 12, // finished check, done
+ BAD = 13, // got an error--stay here
+ }
+
+ private InflateManagerMode mode; // current inflate mode
+ internal ZlibCodec _codec; // pointer back to this zlib stream
+
+ // mode dependent information
+ internal int method; // if FLAGS, method byte
+
+ // if CHECK, check values to compare
+ internal uint computedCheck; // computed check value
+ internal uint expectedCheck; // stream check value
+
+ // if BAD, inflateSync's marker bytes count
+ internal int marker;
+
+ // mode independent information
+ //internal int nowrap; // flag for no wrapper
+ private bool _handleRfc1950HeaderBytes = true;
+ internal bool HandleRfc1950HeaderBytes
+ {
+ get { return _handleRfc1950HeaderBytes; }
+ set { _handleRfc1950HeaderBytes = value; }
+ }
+ internal int wbits; // log2(window size) (8..15, defaults to 15)
+
+ internal InflateBlocks blocks; // current inflate_blocks state
+
+ public InflateManager() { }
+
+ public InflateManager(bool expectRfc1950HeaderBytes)
+ {
+ _handleRfc1950HeaderBytes = expectRfc1950HeaderBytes;
+ }
+
+ internal int Reset()
+ {
+ _codec.TotalBytesIn = _codec.TotalBytesOut = 0;
+ _codec.Message = null;
+ mode = HandleRfc1950HeaderBytes ? InflateManagerMode.METHOD : InflateManagerMode.BLOCKS;
+ blocks.Reset();
+ return ZlibConstants.Z_OK;
+ }
+
+ internal int End()
+ {
+ if (blocks != null)
+ blocks.Free();
+ blocks = null;
+ return ZlibConstants.Z_OK;
+ }
+
+ internal int Initialize(ZlibCodec codec, int w)
+ {
+ _codec = codec;
+ _codec.Message = null;
+ blocks = null;
+
+ // handle undocumented nowrap option (no zlib header or check)
+ //nowrap = 0;
+ //if (w < 0)
+ //{
+ // w = - w;
+ // nowrap = 1;
+ //}
+
+ // set window size
+ if (w < 8 || w > 15)
+ {
+ End();
+ throw new ZlibException("Bad window size.");
+
+ //return ZlibConstants.Z_STREAM_ERROR;
+ }
+ wbits = w;
+
+ blocks = new InflateBlocks(codec,
+ HandleRfc1950HeaderBytes ? this : null,
+ 1 << w);
+
+ // reset state
+ Reset();
+ return ZlibConstants.Z_OK;
+ }
+
+
+ internal int Inflate(FlushType flush)
+ {
+ int b;
+
+ if (_codec.InputBuffer == null)
+ throw new ZlibException("InputBuffer is null. ");
+
+// int f = (flush == FlushType.Finish)
+// ? ZlibConstants.Z_BUF_ERROR
+// : ZlibConstants.Z_OK;
+
+ // workitem 8870
+ int f = ZlibConstants.Z_OK;
+ int r = ZlibConstants.Z_BUF_ERROR;
+
+ while (true)
+ {
+ switch (mode)
+ {
+ case InflateManagerMode.METHOD:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ if (((method = _codec.InputBuffer[_codec.NextIn++]) & 0xf) != Z_DEFLATED)
+ {
+ mode = InflateManagerMode.BAD;
+ _codec.Message = String.Format("unknown compression method (0x{0:X2})", method);
+ marker = 5; // can't try inflateSync
+ break;
+ }
+ if ((method >> 4) + 8 > wbits)
+ {
+ mode = InflateManagerMode.BAD;
+ _codec.Message = String.Format("invalid window size ({0})", (method >> 4) + 8);
+ marker = 5; // can't try inflateSync
+ break;
+ }
+ mode = InflateManagerMode.FLAG;
+ break;
+
+
+ case InflateManagerMode.FLAG:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ b = (_codec.InputBuffer[_codec.NextIn++]) & 0xff;
+
+ if ((((method << 8) + b) % 31) != 0)
+ {
+ mode = InflateManagerMode.BAD;
+ _codec.Message = "incorrect header check";
+ marker = 5; // can't try inflateSync
+ break;
+ }
+
+ mode = ((b & PRESET_DICT) == 0)
+ ? InflateManagerMode.BLOCKS
+ : InflateManagerMode.DICT4;
+ break;
+
+ case InflateManagerMode.DICT4:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000);
+ mode = InflateManagerMode.DICT3;
+ break;
+
+ case InflateManagerMode.DICT3:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000);
+ mode = InflateManagerMode.DICT2;
+ break;
+
+ case InflateManagerMode.DICT2:
+
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00);
+ mode = InflateManagerMode.DICT1;
+ break;
+
+
+ case InflateManagerMode.DICT1:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--; _codec.TotalBytesIn++;
+ expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff);
+ _codec._Adler32 = expectedCheck;
+ mode = InflateManagerMode.DICT0;
+ return ZlibConstants.Z_NEED_DICT;
+
+
+ case InflateManagerMode.DICT0:
+ mode = InflateManagerMode.BAD;
+ _codec.Message = "need dictionary";
+ marker = 0; // can try inflateSync
+ return ZlibConstants.Z_STREAM_ERROR;
+
+
+ case InflateManagerMode.BLOCKS:
+ r = blocks.Process(r);
+ if (r == ZlibConstants.Z_DATA_ERROR)
+ {
+ mode = InflateManagerMode.BAD;
+ marker = 0; // can try inflateSync
+ break;
+ }
+
+ if (r == ZlibConstants.Z_OK) r = f;
+
+ if (r != ZlibConstants.Z_STREAM_END)
+ return r;
+
+ r = f;
+ computedCheck = blocks.Reset();
+ if (!HandleRfc1950HeaderBytes)
+ {
+ mode = InflateManagerMode.DONE;
+ return ZlibConstants.Z_STREAM_END;
+ }
+ mode = InflateManagerMode.CHECK4;
+ break;
+
+ case InflateManagerMode.CHECK4:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ expectedCheck = (uint)((_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000);
+ mode = InflateManagerMode.CHECK3;
+ break;
+
+ case InflateManagerMode.CHECK3:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--; _codec.TotalBytesIn++;
+ expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000);
+ mode = InflateManagerMode.CHECK2;
+ break;
+
+ case InflateManagerMode.CHECK2:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--;
+ _codec.TotalBytesIn++;
+ expectedCheck += (uint)((_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00);
+ mode = InflateManagerMode.CHECK1;
+ break;
+
+ case InflateManagerMode.CHECK1:
+ if (_codec.AvailableBytesIn == 0) return r;
+ r = f;
+ _codec.AvailableBytesIn--; _codec.TotalBytesIn++;
+ expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff);
+ if (computedCheck != expectedCheck)
+ {
+ mode = InflateManagerMode.BAD;
+ _codec.Message = "incorrect data check";
+ marker = 5; // can't try inflateSync
+ break;
+ }
+ mode = InflateManagerMode.DONE;
+ return ZlibConstants.Z_STREAM_END;
+
+ case InflateManagerMode.DONE:
+ return ZlibConstants.Z_STREAM_END;
+
+ case InflateManagerMode.BAD:
+ throw new ZlibException(String.Format("Bad state ({0})", _codec.Message));
+
+ default:
+ throw new ZlibException("Stream error.");
+
+ }
+ }
+ }
+
+
+
+ internal int SetDictionary(byte[] dictionary)
+ {
+ int index = 0;
+ int length = dictionary.Length;
+ if (mode != InflateManagerMode.DICT0)
+ throw new ZlibException("Stream error.");
+
+ if (Adler.Adler32(1, dictionary, 0, dictionary.Length) != _codec._Adler32)
+ {
+ return ZlibConstants.Z_DATA_ERROR;
+ }
+
+ _codec._Adler32 = Adler.Adler32(0, null, 0, 0);
+
+ if (length >= (1 << wbits))
+ {
+ length = (1 << wbits) - 1;
+ index = dictionary.Length - length;
+ }
+ blocks.SetDictionary(dictionary, index, length);
+ mode = InflateManagerMode.BLOCKS;
+ return ZlibConstants.Z_OK;
+ }
+
+
+ private static readonly byte[] mark = new byte[] { 0, 0, 0xff, 0xff };
+
+ internal int Sync()
+ {
+ int n; // number of bytes to look at
+ int p; // pointer to bytes
+ int m; // number of marker bytes found in a row
+ long r, w; // temporaries to save total_in and total_out
+
+ // set up
+ if (mode != InflateManagerMode.BAD)
+ {
+ mode = InflateManagerMode.BAD;
+ marker = 0;
+ }
+ if ((n = _codec.AvailableBytesIn) == 0)
+ return ZlibConstants.Z_BUF_ERROR;
+ p = _codec.NextIn;
+ m = marker;
+
+ // search
+ while (n != 0 && m < 4)
+ {
+ if (_codec.InputBuffer[p] == mark[m])
+ {
+ m++;
+ }
+ else if (_codec.InputBuffer[p] != 0)
+ {
+ m = 0;
+ }
+ else
+ {
+ m = 4 - m;
+ }
+ p++; n--;
+ }
+
+ // restore
+ _codec.TotalBytesIn += p - _codec.NextIn;
+ _codec.NextIn = p;
+ _codec.AvailableBytesIn = n;
+ marker = m;
+
+ // return no joy or set up to restart on a new block
+ if (m != 4)
+ {
+ return ZlibConstants.Z_DATA_ERROR;
+ }
+ r = _codec.TotalBytesIn;
+ w = _codec.TotalBytesOut;
+ Reset();
+ _codec.TotalBytesIn = r;
+ _codec.TotalBytesOut = w;
+ mode = InflateManagerMode.BLOCKS;
+ return ZlibConstants.Z_OK;
+ }
+
+
+ // Returns true if inflate is currently at the end of a block generated
+ // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+ // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
+ // but removes the length bytes of the resulting empty stored block. When
+ // decompressing, PPP checks that at the end of input packet, inflate is
+ // waiting for these length bytes.
+ internal int SyncPoint(ZlibCodec z)
+ {
+ return blocks.SyncPoint();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/Inflate.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/Inflate.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7eb68f07082d0dd4db9f02cf12bc6901f5e01904
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/Inflate.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 01ece23e3663e424ba8672d6d3d50f19
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs b/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs
new file mode 100644
index 0000000000000000000000000000000000000000..04437d50ea1305d428b1eb9a960eb839beb2cd10
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs
@@ -0,0 +1,423 @@
+// Tree.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2009-October-28 13:29:50>
+//
+// ------------------------------------------------------------------
+//
+// This module defines classes for zlib compression and
+// decompression. This code is derived from the jzlib implementation of
+// zlib. In keeping with the license for jzlib, the copyright to that
+// code is below.
+//
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+using System;
+
+namespace BestHTTP.Decompression.Zlib
+{
+ sealed class ZTree
+ {
+ private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1);
+
+ // extra bits for each length code
+ internal static readonly int[] ExtraLengthBits = new int[]
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+ 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
+ };
+
+ // extra bits for each distance code
+ internal static readonly int[] ExtraDistanceBits = new int[]
+ {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+ 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
+ };
+
+ // extra bits for each bit length code
+ internal static readonly int[] extra_blbits = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7};
+
+ internal static readonly sbyte[] bl_order = new sbyte[]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+
+ // The lengths of the bit length codes are sent in order of decreasing
+ // probability, to avoid transmitting the lengths for unused bit
+ // length codes.
+
+ internal const int Buf_size = 8 * 2;
+
+ // see definition of array dist_code below
+ //internal const int DIST_CODE_LEN = 512;
+
+ private static readonly sbyte[] _dist_code = new sbyte[]
+ {
+ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21,
+ 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+ };
+
+ internal static readonly sbyte[] LengthCode = new sbyte[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17,
+ 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
+ };
+
+
+ internal static readonly int[] LengthBase = new int[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28,
+ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0
+ };
+
+
+ internal static readonly int[] DistanceBase = new int[]
+ {
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192,
+ 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
+ };
+
+
+ ///
+ /// Map from a distance to a distance code.
+ ///
+ ///
+ /// No side effects. _dist_code[256] and _dist_code[257] are never used.
+ ///
+ internal static int DistanceCode(int dist)
+ {
+ return (dist < 256)
+ ? _dist_code[dist]
+ : _dist_code[256 + SharedUtils.URShift(dist, 7)];
+ }
+
+ internal short[] dyn_tree; // the dynamic tree
+ internal int max_code; // largest code with non zero frequency
+ internal StaticTree staticTree; // the corresponding static tree
+
+ // Compute the optimal bit lengths for a tree and update the total bit length
+ // for the current block.
+ // IN assertion: the fields freq and dad are set, heap[heap_max] and
+ // above are the tree nodes sorted by increasing frequency.
+ // OUT assertions: the field len is set to the optimal bit length, the
+ // array bl_count contains the frequencies for each bit length.
+ // The length opt_len is updated; static_len is also updated if stree is
+ // not null.
+ internal void gen_bitlen(DeflateManager s)
+ {
+ short[] tree = dyn_tree;
+ short[] stree = staticTree.treeCodes;
+ int[] extra = staticTree.extraBits;
+ int base_Renamed = staticTree.extraBase;
+ int max_length = staticTree.maxLength;
+ int h; // heap index
+ int n, m; // iterate over the tree elements
+ int bits; // bit length
+ int xbits; // extra bits
+ short f; // frequency
+ int overflow = 0; // number of elements with bit length too large
+
+ for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++)
+ s.bl_count[bits] = 0;
+
+ // In a first pass, compute the optimal bit lengths (which may
+ // overflow in the case of the bit length tree).
+ tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
+
+ for (h = s.heap_max + 1; h < HEAP_SIZE; h++)
+ {
+ n = s.heap[h];
+ bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
+ if (bits > max_length)
+ {
+ bits = max_length; overflow++;
+ }
+ tree[n * 2 + 1] = (short) bits;
+ // We overwrite tree[n*2+1] which is no longer needed
+
+ if (n > max_code)
+ continue; // not a leaf node
+
+ s.bl_count[bits]++;
+ xbits = 0;
+ if (n >= base_Renamed)
+ xbits = extra[n - base_Renamed];
+ f = tree[n * 2];
+ s.opt_len += f * (bits + xbits);
+ if (stree != null)
+ s.static_len += f * (stree[n * 2 + 1] + xbits);
+ }
+ if (overflow == 0)
+ return ;
+
+ // This happens for example on obj2 and pic of the Calgary corpus
+ // Find the first bit length which could increase:
+ do
+ {
+ bits = max_length - 1;
+ while (s.bl_count[bits] == 0)
+ bits--;
+ s.bl_count[bits]--; // move one leaf down the tree
+ s.bl_count[bits + 1] = (short) (s.bl_count[bits + 1] + 2); // move one overflow item as its brother
+ s.bl_count[max_length]--;
+ // The brother of the overflow item also moves one step up,
+ // but this does not affect bl_count[max_length]
+ overflow -= 2;
+ }
+ while (overflow > 0);
+
+ for (bits = max_length; bits != 0; bits--)
+ {
+ n = s.bl_count[bits];
+ while (n != 0)
+ {
+ m = s.heap[--h];
+ if (m > max_code)
+ continue;
+ if (tree[m * 2 + 1] != bits)
+ {
+ s.opt_len = (int) (s.opt_len + ((long) bits - (long) tree[m * 2 + 1]) * (long) tree[m * 2]);
+ tree[m * 2 + 1] = (short) bits;
+ }
+ n--;
+ }
+ }
+ }
+
+ // Construct one Huffman tree and assigns the code bit strings and lengths.
+ // Update the total bit length for the current block.
+ // IN assertion: the field freq is set for all tree elements.
+ // OUT assertions: the fields len and code are set to the optimal bit length
+ // and corresponding code. The length opt_len is updated; static_len is
+ // also updated if stree is not null. The field max_code is set.
+ internal void build_tree(DeflateManager s)
+ {
+ short[] tree = dyn_tree;
+ short[] stree = staticTree.treeCodes;
+ int elems = staticTree.elems;
+ int n, m; // iterate over heap elements
+ int max_code = -1; // largest code with non zero frequency
+ int node; // new node being created
+
+ // Construct the initial heap, with least frequent element in
+ // heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ // heap[0] is not used.
+ s.heap_len = 0;
+ s.heap_max = HEAP_SIZE;
+
+ for (n = 0; n < elems; n++)
+ {
+ if (tree[n * 2] != 0)
+ {
+ s.heap[++s.heap_len] = max_code = n;
+ s.depth[n] = 0;
+ }
+ else
+ {
+ tree[n * 2 + 1] = 0;
+ }
+ }
+
+ // The pkzip format requires that at least one distance code exists,
+ // and that at least one bit should be sent even if there is only one
+ // possible code. So to avoid special checks later on we force at least
+ // two codes of non zero frequency.
+ while (s.heap_len < 2)
+ {
+ node = s.heap[++s.heap_len] = (max_code < 2?++max_code:0);
+ tree[node * 2] = 1;
+ s.depth[node] = 0;
+ s.opt_len--;
+ if (stree != null)
+ s.static_len -= stree[node * 2 + 1];
+ // node is 0 or 1 so it does not have extra bits
+ }
+ this.max_code = max_code;
+
+ // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ // establish sub-heaps of increasing lengths:
+
+ for (n = s.heap_len / 2; n >= 1; n--)
+ s.pqdownheap(tree, n);
+
+ // Construct the Huffman tree by repeatedly combining the least two
+ // frequent nodes.
+
+ node = elems; // next internal node of the tree
+ do
+ {
+ // n = node of least frequency
+ n = s.heap[1];
+ s.heap[1] = s.heap[s.heap_len--];
+ s.pqdownheap(tree, 1);
+ m = s.heap[1]; // m = node of next least frequency
+
+ s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
+ s.heap[--s.heap_max] = m;
+
+ // Create a new node father of n and m
+ tree[node * 2] = unchecked((short) (tree[n * 2] + tree[m * 2]));
+ s.depth[node] = (sbyte) (System.Math.Max((byte) s.depth[n], (byte) s.depth[m]) + 1);
+ tree[n * 2 + 1] = tree[m * 2 + 1] = (short) node;
+
+ // and insert the new node in the heap
+ s.heap[1] = node++;
+ s.pqdownheap(tree, 1);
+ }
+ while (s.heap_len >= 2);
+
+ s.heap[--s.heap_max] = s.heap[1];
+
+ // At this point, the fields freq and dad are set. We can now
+ // generate the bit lengths.
+
+ gen_bitlen(s);
+
+ // The field len is now set, we can generate the bit codes
+ gen_codes(tree, max_code, s.bl_count);
+ }
+
+ // Generate the codes for a given tree and bit counts (which need not be
+ // optimal).
+ // IN assertion: the array bl_count contains the bit length statistics for
+ // the given tree and the field len is set for all tree elements.
+ // OUT assertion: the field code is set for all tree elements of non
+ // zero code length.
+ internal static void gen_codes(short[] tree, int max_code, short[] bl_count)
+ {
+ short[] next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length
+ short code = 0; // running code value
+ int bits; // bit index
+ int n; // code index
+
+ // The distribution counts are first used to generate the code values
+ // without bit reversal.
+ for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++)
+ unchecked {
+ next_code[bits] = code = (short) ((code + bl_count[bits - 1]) << 1);
+ }
+
+ // Check that the bit counts in bl_count are consistent. The last code
+ // must be all ones.
+ //Assert (code + bl_count[MAX_BITS]-1 == (1<>= 1; //SharedUtils.URShift(code, 1);
+ res <<= 1;
+ }
+ while (--len > 0);
+ return res >> 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..47691c86cda0cca2ec2aae84448d8d2a4111870d
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZTree.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 567080ac494bf3b47acca3e05bf46e84
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs b/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs
new file mode 100644
index 0000000000000000000000000000000000000000..082232d73cb6afe8192f8314042cf1bcf8e30f3f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs
@@ -0,0 +1,546 @@
+// Zlib.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009-2011 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// Last Saved: <2011-August-03 19:52:28>
+//
+// ------------------------------------------------------------------
+//
+// This module defines classes for ZLIB compression and
+// decompression. This code is derived from the jzlib implementation of
+// zlib, but significantly modified. The object model is not the same,
+// and many of the behaviors are new or different. Nonetheless, in
+// keeping with the license for jzlib, the copyright to that code is
+// included below.
+//
+// ------------------------------------------------------------------
+//
+// The following notice applies to jzlib:
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// jzlib is based on zlib-1.1.3.
+//
+// The following notice applies to zlib:
+//
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler
+//
+// The ZLIB software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+//
+// Jean-loup Gailly jloup@gzip.org
+// Mark Adler madler@alumni.caltech.edu
+//
+// -----------------------------------------------------------------------
+
+
+
+using System;
+using Interop=System.Runtime.InteropServices;
+
+namespace BestHTTP.Decompression.Zlib
+{
+
+ ///
+ /// Describes how to flush the current deflate operation.
+ ///
+ ///
+ /// The different FlushType values are useful when using a Deflate in a streaming application.
+ ///
+ public enum FlushType
+ {
+ /// No flush at all.
+ None = 0,
+
+ /// Closes the current block, but doesn't flush it to
+ /// the output. Used internally only in hypothetical
+ /// scenarios. This was supposed to be removed by Zlib, but it is
+ /// still in use in some edge cases.
+ ///
+ Partial,
+
+ ///
+ /// Use this during compression to specify that all pending output should be
+ /// flushed to the output buffer and the output should be aligned on a byte
+ /// boundary. You might use this in a streaming communication scenario, so that
+ /// the decompressor can get all input data available so far. When using this
+ /// with a ZlibCodec, AvailableBytesIn will be zero after the call if
+ /// enough output space has been provided before the call. Flushing will
+ /// degrade compression and so it should be used only when necessary.
+ ///
+ Sync,
+
+ ///
+ /// Use this during compression to specify that all output should be flushed, as
+ /// with FlushType.Sync, but also, the compression state should be reset
+ /// so that decompression can restart from this point if previous compressed
+ /// data has been damaged or if random access is desired. Using
+ /// FlushType.Full too often can significantly degrade the compression.
+ ///
+ Full,
+
+ /// Signals the end of the compression/decompression stream.
+ Finish,
+ }
+
+
+ ///
+ /// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress.
+ ///
+ public enum CompressionLevel
+ {
+ ///
+ /// None means that the data will be simply stored, with no change at all.
+ /// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None
+ /// cannot be opened with the default zip reader. Use a different CompressionLevel.
+ ///
+ None= 0,
+ ///
+ /// Same as None.
+ ///
+ Level0 = 0,
+
+ ///
+ /// The fastest but least effective compression.
+ ///
+ BestSpeed = 1,
+
+ ///
+ /// A synonym for BestSpeed.
+ ///
+ Level1 = 1,
+
+ ///
+ /// A little slower, but better, than level 1.
+ ///
+ Level2 = 2,
+
+ ///
+ /// A little slower, but better, than level 2.
+ ///
+ Level3 = 3,
+
+ ///
+ /// A little slower, but better, than level 3.
+ ///
+ Level4 = 4,
+
+ ///
+ /// A little slower than level 4, but with better compression.
+ ///
+ Level5 = 5,
+
+ ///
+ /// The default compression level, with a good balance of speed and compression efficiency.
+ ///
+ Default = 6,
+ ///
+ /// A synonym for Default.
+ ///
+ Level6 = 6,
+
+ ///
+ /// Pretty good compression!
+ ///
+ Level7 = 7,
+
+ ///
+ /// Better compression than Level7!
+ ///
+ Level8 = 8,
+
+ ///
+ /// The "best" compression, where best means greatest reduction in size of the input data stream.
+ /// This is also the slowest compression.
+ ///
+ BestCompression = 9,
+
+ ///
+ /// A synonym for BestCompression.
+ ///
+ Level9 = 9,
+ }
+
+ ///
+ /// Describes options for how the compression algorithm is executed. Different strategies
+ /// work better on different sorts of data. The strategy parameter can affect the compression
+ /// ratio and the speed of compression but not the correctness of the compresssion.
+ ///
+ public enum CompressionStrategy
+ {
+ ///
+ /// The default strategy is probably the best for normal data.
+ ///
+ Default = 0,
+
+ ///
+ /// The Filtered strategy is intended to be used most effectively with data produced by a
+ /// filter or predictor. By this definition, filtered data consists mostly of small
+ /// values with a somewhat random distribution. In this case, the compression algorithm
+ /// is tuned to compress them better. The effect of Filtered is to force more Huffman
+ /// coding and less string matching; it is a half-step between Default and HuffmanOnly.
+ ///
+ Filtered = 1,
+
+ ///
+ /// Using HuffmanOnly will force the compressor to do Huffman encoding only, with no
+ /// string matching.
+ ///
+ HuffmanOnly = 2,
+ }
+
+
+ ///
+ /// An enum to specify the direction of transcoding - whether to compress or decompress.
+ ///
+ public enum CompressionMode
+ {
+ ///
+ /// Used to specify that the stream should compress the data.
+ ///
+ Compress= 0,
+ ///
+ /// Used to specify that the stream should decompress the data.
+ ///
+ Decompress = 1,
+ }
+
+
+ ///
+ /// A general purpose exception class for exceptions in the Zlib library.
+ ///
+ [Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000E")]
+ internal class ZlibException : System.Exception
+ {
+ ///
+ /// The ZlibException class captures exception information generated
+ /// by the Zlib library.
+ ///
+ public ZlibException()
+ : base()
+ {
+ }
+
+ ///
+ /// This ctor collects a message attached to the exception.
+ ///
+ /// the message for the exception.
+ public ZlibException(System.String s)
+ : base(s)
+ {
+ }
+ }
+
+
+ internal class SharedUtils
+ {
+ ///
+ /// Performs an unsigned bitwise right shift with the specified number
+ ///
+ /// Number to operate on
+ /// Ammount of bits to shift
+ /// The resulting number from the shift operation
+ public static int URShift(int number, int bits)
+ {
+ return (int)((uint)number >> bits);
+ }
+
+#if NOT
+ ///
+ /// Performs an unsigned bitwise right shift with the specified number
+ ///
+ /// Number to operate on
+ /// Ammount of bits to shift
+ /// The resulting number from the shift operation
+ public static long URShift(long number, int bits)
+ {
+ return (long) ((UInt64)number >> bits);
+ }
+#endif
+
+ ///
+ /// Reads a number of characters from the current source TextReader and writes
+ /// the data to the target array at the specified index.
+ ///
+ ///
+ /// The source TextReader to read from
+ /// Contains the array of characteres read from the source TextReader.
+ /// The starting index of the target array.
+ /// The maximum number of characters to read from the source TextReader.
+ ///
+ ///
+ /// The number of characters read. The number will be less than or equal to
+ /// count depending on the data available in the source TextReader. Returns -1
+ /// if the end of the stream is reached.
+ ///
+ public static System.Int32 ReadInput(System.IO.TextReader sourceTextReader, byte[] target, int start, int count)
+ {
+ // Returns 0 bytes if not enough space in target
+ if (target.Length == 0) return 0;
+
+ char[] charArray = new char[target.Length];
+ int bytesRead = sourceTextReader.Read(charArray, start, count);
+
+ // Returns -1 if EOF
+ if (bytesRead == 0) return -1;
+
+ for (int index = start; index < start + bytesRead; index++)
+ target[index] = (byte)charArray[index];
+
+ return bytesRead;
+ }
+
+
+ internal static byte[] ToByteArray(System.String sourceString)
+ {
+ return System.Text.UTF8Encoding.UTF8.GetBytes(sourceString);
+ }
+
+
+ internal static char[] ToCharArray(byte[] byteArray)
+ {
+ return System.Text.UTF8Encoding.UTF8.GetChars(byteArray);
+ }
+ }
+
+ internal static class InternalConstants
+ {
+ internal static readonly int MAX_BITS = 15;
+ internal static readonly int BL_CODES = 19;
+ internal static readonly int D_CODES = 30;
+ internal static readonly int LITERALS = 256;
+ internal static readonly int LENGTH_CODES = 29;
+ internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES);
+
+ // Bit length codes must not exceed MAX_BL_BITS bits
+ internal static readonly int MAX_BL_BITS = 7;
+
+ // repeat previous bit length 3-6 times (2 bits of repeat count)
+ internal static readonly int REP_3_6 = 16;
+
+ // repeat a zero length 3-10 times (3 bits of repeat count)
+ internal static readonly int REPZ_3_10 = 17;
+
+ // repeat a zero length 11-138 times (7 bits of repeat count)
+ internal static readonly int REPZ_11_138 = 18;
+
+ }
+
+ internal sealed class StaticTree
+ {
+ internal static readonly short[] lengthAndLiteralsTreeCodes = new short[] {
+ 12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8,
+ 28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8,
+ 2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8,
+ 18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8,
+ 10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, 8, 106, 8, 234, 8,
+ 26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8,
+ 6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8,
+ 22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8,
+ 14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8,
+ 30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, 8, 126, 8, 254, 8,
+ 1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8,
+ 17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, 241, 8,
+ 9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8,
+ 25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8,
+ 5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8,
+ 21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8,
+ 13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, 237, 8,
+ 29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8,
+ 19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9,
+ 51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9,
+ 11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9,
+ 43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, 491, 9,
+ 27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9,
+ 59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, 251, 9, 507, 9,
+ 7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9,
+ 39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9,
+ 23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9,
+ 55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9,
+ 15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, 207, 9, 463, 9,
+ 47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9,
+ 31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, 223, 9, 479, 9,
+ 63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9,
+ 0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7,
+ 8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7,
+ 4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7,
+ 3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, 99, 8, 227, 8
+ };
+
+ internal static readonly short[] distTreeCodes = new short[] {
+ 0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5,
+ 2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5,
+ 1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5,
+ 3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 };
+
+ internal static readonly StaticTree Literals;
+ internal static readonly StaticTree Distances;
+ internal static readonly StaticTree BitLengths;
+
+ internal short[] treeCodes; // static tree or null
+ internal int[] extraBits; // extra bits for each code or null
+ internal int extraBase; // base index for extra_bits
+ internal int elems; // max number of elements in the tree
+ internal int maxLength; // max bit length for the codes
+
+ private StaticTree(short[] treeCodes, int[] extraBits, int extraBase, int elems, int maxLength)
+ {
+ this.treeCodes = treeCodes;
+ this.extraBits = extraBits;
+ this.extraBase = extraBase;
+ this.elems = elems;
+ this.maxLength = maxLength;
+ }
+ static StaticTree()
+ {
+ Literals = new StaticTree(lengthAndLiteralsTreeCodes, ZTree.ExtraLengthBits, InternalConstants.LITERALS + 1, InternalConstants.L_CODES, InternalConstants.MAX_BITS);
+ Distances = new StaticTree(distTreeCodes, ZTree.ExtraDistanceBits, 0, InternalConstants.D_CODES, InternalConstants.MAX_BITS);
+ BitLengths = new StaticTree(null, ZTree.extra_blbits, 0, InternalConstants.BL_CODES, InternalConstants.MAX_BL_BITS);
+ }
+ }
+
+
+
+ ///
+ /// Computes an Adler-32 checksum.
+ ///
+ ///
+ /// The Adler checksum is similar to a CRC checksum, but faster to compute, though less
+ /// reliable. It is used in producing RFC1950 compressed streams. The Adler checksum
+ /// is a required part of the "ZLIB" standard. Applications will almost never need to
+ /// use this class directly.
+ ///
+ ///
+ ///
+ public sealed class Adler
+ {
+ // largest prime smaller than 65536
+ private static readonly uint BASE = 65521;
+ // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
+ private static readonly int NMAX = 5552;
+
+
+#pragma warning disable 3001
+#pragma warning disable 3002
+
+ ///
+ /// Calculates the Adler32 checksum.
+ ///
+ ///
+ ///
+ /// This is used within ZLIB. You probably don't need to use this directly.
+ ///
+ ///
+ ///
+ /// To compute an Adler32 checksum on a byte array:
+ ///
+ /// var adler = Adler.Adler32(0, null, 0, 0);
+ /// adler = Adler.Adler32(adler, buffer, index, length);
+ ///
+ ///
+ public static uint Adler32(uint adler, byte[] buf, int index, int len)
+ {
+ if (buf == null)
+ return 1;
+
+ uint s1 = (uint) (adler & 0xffff);
+ uint s2 = (uint) ((adler >> 16) & 0xffff);
+
+ while (len > 0)
+ {
+ int k = len < NMAX ? len : NMAX;
+ len -= k;
+ while (k >= 16)
+ {
+ //s1 += (buf[index++] & 0xff); s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ s1 += buf[index++]; s2 += s1;
+ k -= 16;
+ }
+ if (k != 0)
+ {
+ do
+ {
+ s1 += buf[index++];
+ s2 += s1;
+ }
+ while (--k != 0);
+ }
+ s1 %= BASE;
+ s2 %= BASE;
+ }
+ return (uint)((s2 << 16) | s1);
+ }
+#pragma warning restore 3001
+#pragma warning restore 3002
+
+ }
+
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..a81b918da599c2c4a7d386eb419828cb6fc0e12c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/Zlib.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 7d946fbf5f38d4049a7b50fe52bd8b0e
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a6c5f98848595c59e16624c827c5047763b6e8f1
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs
@@ -0,0 +1,607 @@
+// ZlibBaseStream.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2011-August-06 21:22:38>
+//
+// ------------------------------------------------------------------
+//
+// This module defines the ZlibBaseStream class, which is an intnernal
+// base class for DeflateStream, ZlibStream and GZipStream.
+//
+// ------------------------------------------------------------------
+
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.IO;
+
+namespace BestHTTP.Decompression.Zlib
+{
+
+ internal enum ZlibStreamFlavor { ZLIB = 1950, DEFLATE = 1951, GZIP = 1952 }
+
+ internal class ZlibBaseStream : System.IO.Stream
+ {
+ protected internal ZlibCodec _z = null; // deferred init... new ZlibCodec();
+
+ protected internal StreamMode _streamMode = StreamMode.Undefined;
+ protected internal FlushType _flushMode;
+ protected internal ZlibStreamFlavor _flavor;
+ protected internal CompressionMode _compressionMode;
+ protected internal CompressionLevel _level;
+ protected internal bool _leaveOpen;
+ protected internal byte[] _workingBuffer;
+ protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault;
+ protected internal int windowBitsMax;
+ protected internal byte[] _buf1 = new byte[1];
+
+ protected internal System.IO.Stream _stream;
+ protected internal CompressionStrategy Strategy = CompressionStrategy.Default;
+
+ // workitem 7159
+ BestHTTP.Decompression.Crc.CRC32 crc;
+ protected internal string _GzipFileName;
+ protected internal string _GzipComment;
+ protected internal DateTime _GzipMtime;
+ protected internal int _gzipHeaderByteCount;
+
+ internal int Crc32 { get { if (crc == null) return 0; return crc.Crc32Result; } }
+
+ public ZlibBaseStream(System.IO.Stream stream,
+ CompressionMode compressionMode,
+ CompressionLevel level,
+ ZlibStreamFlavor flavor,
+ bool leaveOpen)
+ :this(stream, compressionMode, level, flavor,leaveOpen, ZlibConstants.WindowBitsDefault)
+ { }
+
+ public ZlibBaseStream(System.IO.Stream stream,
+ CompressionMode compressionMode,
+ CompressionLevel level,
+ ZlibStreamFlavor flavor,
+ bool leaveOpen,
+ int windowBits)
+ : base()
+ {
+ this._flushMode = FlushType.None;
+ //this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT];
+ this._stream = stream;
+ this._leaveOpen = leaveOpen;
+ this._compressionMode = compressionMode;
+ this._flavor = flavor;
+ this._level = level;
+ this.windowBitsMax = windowBits;
+ // workitem 7159
+ if (flavor == ZlibStreamFlavor.GZIP)
+ {
+ this.crc = new BestHTTP.Decompression.Crc.CRC32();
+ }
+ }
+
+
+ protected internal bool _wantCompress
+ {
+ get
+ {
+ return (this._compressionMode == CompressionMode.Compress);
+ }
+ }
+
+ private ZlibCodec z
+ {
+ get
+ {
+ if (_z == null)
+ {
+ bool wantRfc1950Header = (this._flavor == ZlibStreamFlavor.ZLIB);
+ _z = new ZlibCodec();
+ if (this._compressionMode == CompressionMode.Decompress)
+ {
+ _z.InitializeInflate(this.windowBitsMax, wantRfc1950Header);
+ }
+ else
+ {
+ _z.Strategy = Strategy;
+ _z.InitializeDeflate(this._level, this.windowBitsMax, wantRfc1950Header);
+ }
+ }
+ return _z;
+ }
+ }
+
+
+
+ private byte[] workingBuffer
+ {
+ get
+ {
+ if (_workingBuffer == null)
+ _workingBuffer = BufferPool.Get(_bufferSize, true);
+ return _workingBuffer;
+ }
+ }
+
+
+
+ public override void Write(System.Byte[] buffer, int offset, int count)
+ {
+ // workitem 7159
+ // calculate the CRC on the unccompressed data (before writing)
+ if (crc != null)
+ crc.SlurpBlock(buffer, offset, count);
+
+ if (_streamMode == StreamMode.Undefined)
+ _streamMode = StreamMode.Writer;
+ else if (_streamMode != StreamMode.Writer)
+ throw new ZlibException("Cannot Write after Reading.");
+
+ if (count == 0)
+ return;
+
+ // first reference of z property will initialize the private var _z
+ z.InputBuffer = buffer;
+ _z.NextIn = offset;
+ _z.AvailableBytesIn = count;
+ bool done = false;
+ do
+ {
+ _z.OutputBuffer = workingBuffer;
+ _z.NextOut = 0;
+ _z.AvailableBytesOut = _workingBuffer.Length;
+ int rc = (_wantCompress)
+ ? _z.Deflate(_flushMode)
+ : _z.Inflate(_flushMode);
+ if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
+ throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message);
+
+ //if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
+ _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
+
+ done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
+
+ // If GZIP and de-compress, we're done when 8 bytes remain.
+ if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
+ done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
+
+ }
+ while (!done);
+ }
+
+
+
+ private void finish()
+ {
+ if (_z == null) return;
+
+ if (_streamMode == StreamMode.Writer)
+ {
+ bool done = false;
+ do
+ {
+ _z.OutputBuffer = workingBuffer;
+ _z.NextOut = 0;
+ _z.AvailableBytesOut = _workingBuffer.Length;
+ int rc = (_wantCompress)
+ ? _z.Deflate(FlushType.Finish)
+ : _z.Inflate(FlushType.Finish);
+
+ if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
+ {
+ string verb = (_wantCompress ? "de" : "in") + "flating";
+ if (_z.Message == null)
+ throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc));
+ else
+ throw new ZlibException(verb + ": " + _z.Message);
+ }
+
+ if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
+ {
+ _stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
+ }
+
+ done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
+ // If GZIP and de-compress, we're done when 8 bytes remain.
+ if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
+ done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
+
+ }
+ while (!done);
+
+ Flush();
+
+ // workitem 7159
+ if (_flavor == ZlibStreamFlavor.GZIP)
+ {
+ if (_wantCompress)
+ {
+ // Emit the GZIP trailer: CRC32 and size mod 2^32
+ int c1 = crc.Crc32Result;
+ _stream.Write(BitConverter.GetBytes(c1), 0, 4);
+ int c2 = (Int32)(crc.TotalBytesRead & 0x00000000FFFFFFFF);
+ _stream.Write(BitConverter.GetBytes(c2), 0, 4);
+ }
+ else
+ {
+ throw new ZlibException("Writing with decompression is not supported.");
+ }
+ }
+ }
+ // workitem 7159
+ else if (_streamMode == StreamMode.Reader)
+ {
+ if (_flavor == ZlibStreamFlavor.GZIP)
+ {
+ if (!_wantCompress)
+ {
+ // workitem 8501: handle edge case (decompress empty stream)
+ if (_z.TotalBytesOut == 0L)
+ return;
+
+ // Read and potentially verify the GZIP trailer:
+ // CRC32 and size mod 2^32
+ byte[] trailer = BufferPool.Get(8, true);
+
+ // workitems 8679 & 12554
+ if (_z.AvailableBytesIn < 8)
+ {
+ // Make sure we have read to the end of the stream
+ Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, _z.AvailableBytesIn);
+ int bytesNeeded = 8 - _z.AvailableBytesIn;
+ int bytesRead = _stream.Read(trailer,
+ _z.AvailableBytesIn,
+ bytesNeeded);
+ if (bytesNeeded != bytesRead)
+ {
+ BufferPool.Release(trailer);
+ throw new ZlibException(String.Format("Missing or incomplete GZIP trailer. Expected 8 bytes, got {0}.",
+ _z.AvailableBytesIn + bytesRead));
+ }
+ }
+ else
+ {
+ Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, 8);
+ }
+
+ Int32 crc32_expected = BitConverter.ToInt32(trailer, 0);
+ Int32 crc32_actual = crc.Crc32Result;
+ Int32 isize_expected = BitConverter.ToInt32(trailer, 4);
+ Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF);
+
+ if (crc32_actual != crc32_expected)
+ {
+ BufferPool.Release(trailer);
+ throw new ZlibException(String.Format("Bad CRC32 in GZIP trailer. (actual({0:X8})!=expected({1:X8}))", crc32_actual, crc32_expected));
+ }
+
+ if (isize_actual != isize_expected)
+ {
+ BufferPool.Release(trailer);
+ throw new ZlibException(String.Format("Bad size in GZIP trailer. (actual({0})!=expected({1}))", isize_actual, isize_expected));
+ }
+ BufferPool.Release(trailer);
+ }
+ else
+ {
+ throw new ZlibException("Reading with compression is not supported.");
+ }
+ }
+ }
+ }
+
+
+ private void end()
+ {
+ if (z == null)
+ return;
+ if (_wantCompress)
+ {
+ _z.EndDeflate();
+ }
+ else
+ {
+ _z.EndInflate();
+ }
+ _z = null;
+ BufferPool.Release(_workingBuffer);
+ _workingBuffer = null;
+ }
+
+
+ public
+#if !NETFX_CORE
+ override
+#endif
+ void Close()
+ {
+ if (_stream == null) return;
+ try
+ {
+ finish();
+ }
+ finally
+ {
+ end();
+ if (!_leaveOpen) _stream.Dispose();
+ _stream = null;
+ }
+ }
+
+ public override void Flush()
+ {
+ _stream.Flush();
+ }
+
+ public override System.Int64 Seek(System.Int64 offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ //_outStream.Seek(offset, origin);
+ }
+ public override void SetLength(System.Int64 value)
+ {
+ _stream.SetLength(value);
+ nomoreinput = false;
+ }
+
+
+#if NOT
+ public int Read()
+ {
+ if (Read(_buf1, 0, 1) == 0)
+ return 0;
+ // calculate CRC after reading
+ if (crc!=null)
+ crc.SlurpBlock(_buf1,0,1);
+ return (_buf1[0] & 0xFF);
+ }
+#endif
+
+ private bool nomoreinput = false;
+
+
+
+ private string ReadZeroTerminatedString()
+ {
+ var list = new System.Collections.Generic.List();
+ bool done = false;
+ do
+ {
+ // workitem 7740
+ int n = _stream.Read(_buf1, 0, 1);
+ if (n != 1)
+ throw new ZlibException("Unexpected EOF reading GZIP header.");
+ else
+ {
+ if (_buf1[0] == 0)
+ done = true;
+ else
+ list.Add(_buf1[0]);
+ }
+ } while (!done);
+ byte[] a = list.ToArray();
+ return GZipStream.iso8859dash1.GetString(a, 0, a.Length);
+ }
+
+
+ private int _ReadAndValidateGzipHeader()
+ {
+ int totalBytesRead = 0;
+ // read the header on the first read
+ byte[] header = BufferPool.Get(10, true);
+ int n = _stream.Read(header, 0, 10);
+
+ // workitem 8501: handle edge case (decompress empty stream)
+ if (n == 0)
+ {
+ BufferPool.Release(header);
+ return 0;
+ }
+
+ if (n != 10)
+ {
+ BufferPool.Release(header);
+ throw new ZlibException("Not a valid GZIP stream.");
+ }
+
+ if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
+ {
+ BufferPool.Release(header);
+ throw new ZlibException("Bad GZIP header.");
+ }
+
+ Int32 timet = BitConverter.ToInt32(header, 4);
+ _GzipMtime = GZipStream._unixEpoch.AddSeconds(timet);
+ totalBytesRead += n;
+ if ((header[3] & 0x04) == 0x04)
+ {
+ // read and discard extra field
+ n = _stream.Read(header, 0, 2); // 2-byte length field
+ totalBytesRead += n;
+
+ Int16 extraLength = (Int16)(header[0] + header[1] * 256);
+ byte[] extra = BufferPool.Get(extraLength, true);
+ n = _stream.Read(extra, 0, extraLength);
+ if (n != extraLength)
+ {
+ BufferPool.Release(extra);
+ BufferPool.Release(header);
+ throw new ZlibException("Unexpected end-of-file reading GZIP header.");
+ }
+ totalBytesRead += n;
+ }
+ if ((header[3] & 0x08) == 0x08)
+ _GzipFileName = ReadZeroTerminatedString();
+ if ((header[3] & 0x10) == 0x010)
+ _GzipComment = ReadZeroTerminatedString();
+ if ((header[3] & 0x02) == 0x02)
+ Read(_buf1, 0, 1); // CRC16, ignore
+
+ BufferPool.Release(header);
+ return totalBytesRead;
+ }
+
+
+
+ public override System.Int32 Read(System.Byte[] buffer, System.Int32 offset, System.Int32 count)
+ {
+ // According to MS documentation, any implementation of the IO.Stream.Read function must:
+ // (a) throw an exception if offset & count reference an invalid part of the buffer,
+ // or if count < 0, or if buffer is null
+ // (b) return 0 only upon EOF, or if count = 0
+ // (c) if not EOF, then return at least 1 byte, up to bytes
+
+ if (_streamMode == StreamMode.Undefined)
+ {
+ if (!this._stream.CanRead) throw new ZlibException("The stream is not readable.");
+ // for the first read, set up some controls.
+ _streamMode = StreamMode.Reader;
+ // (The first reference to _z goes through the private accessor which
+ // may initialize it.)
+ z.AvailableBytesIn = 0;
+ if (_flavor == ZlibStreamFlavor.GZIP)
+ {
+ _gzipHeaderByteCount = _ReadAndValidateGzipHeader();
+ // workitem 8501: handle edge case (decompress empty stream)
+ if (_gzipHeaderByteCount == 0)
+ return 0;
+ }
+ }
+
+ if (_streamMode != StreamMode.Reader)
+ throw new ZlibException("Cannot Read after Writing.");
+
+ if (count == 0) return 0;
+ if (nomoreinput && _wantCompress) return 0; // workitem 8557
+ if (buffer == null) throw new ArgumentNullException("buffer");
+ if (count < 0) throw new ArgumentOutOfRangeException("count");
+ if (offset < buffer.GetLowerBound(0)) throw new ArgumentOutOfRangeException("offset");
+ if ((offset + count) > buffer.GetLength(0)) throw new ArgumentOutOfRangeException("count");
+
+ int rc = 0;
+
+ // set up the output of the deflate/inflate codec:
+ _z.OutputBuffer = buffer;
+ _z.NextOut = offset;
+ _z.AvailableBytesOut = count;
+
+ // This is necessary in case _workingBuffer has been resized. (new byte[])
+ // (The first reference to _workingBuffer goes through the private accessor which
+ // may initialize it.)
+ _z.InputBuffer = workingBuffer;
+
+ do
+ {
+ // need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any.
+ if ((_z.AvailableBytesIn == 0) && (!nomoreinput))
+ {
+ // No data available, so try to Read data from the captive stream.
+ _z.NextIn = 0;
+ _z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length);
+ if (_z.AvailableBytesIn == 0)
+ nomoreinput = true;
+
+ }
+ // we have data in InputBuffer; now compress or decompress as appropriate
+ rc = (_wantCompress)
+ ? _z.Deflate(_flushMode)
+ : _z.Inflate(_flushMode);
+
+ if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR))
+ return 0;
+
+ if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
+ throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"), rc, _z.Message));
+
+ if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count))
+ break; // nothing more to read
+ }
+ //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK);
+ while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK);
+
+
+ // workitem 8557
+ // is there more room in output?
+ if (_z.AvailableBytesOut > 0)
+ {
+ if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0)
+ {
+ // deferred
+ }
+
+ // are we completely done reading?
+ if (nomoreinput)
+ {
+ // and in compression?
+ if (_wantCompress)
+ {
+ // no more input data available; therefore we flush to
+ // try to complete the read
+ rc = _z.Deflate(FlushType.Finish);
+
+ if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
+ throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message));
+ }
+ }
+ }
+
+
+ rc = (count - _z.AvailableBytesOut);
+
+ // calculate CRC after reading
+ if (crc != null)
+ crc.SlurpBlock(buffer, offset, rc);
+
+ return rc;
+ }
+
+
+
+ public override System.Boolean CanRead
+ {
+ get { return this._stream.CanRead; }
+ }
+
+ public override System.Boolean CanSeek
+ {
+ get { return this._stream.CanSeek; }
+ }
+
+ public override System.Boolean CanWrite
+ {
+ get { return this._stream.CanWrite; }
+ }
+
+ public override System.Int64 Length
+ {
+ get { return _stream.Length; }
+ }
+
+ public override long Position
+ {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ internal enum StreamMode
+ {
+ Writer,
+ Reader,
+ Undefined,
+ }
+
+ }
+
+
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7824525c6462bad993568cccbc3e7040fcb7af02
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibBaseStream.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: b34dd239e486b474aad68fb4080797c8
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b1545440d3c59cc5e6657e987e6d2adaa882bced
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs
@@ -0,0 +1,712 @@
+// ZlibCodec.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2009-November-03 15:40:51>
+//
+// ------------------------------------------------------------------
+//
+// This module defines a Codec for ZLIB compression and
+// decompression. This code extends code that was based the jzlib
+// implementation of zlib, but this code is completely novel. The codec
+// class is new, and encapsulates some behaviors that are new, and some
+// that were present in other classes in the jzlib code base. In
+// keeping with the license for jzlib, the copyright to the jzlib code
+// is included below.
+//
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+using System;
+using Interop=System.Runtime.InteropServices;
+
+namespace BestHTTP.Decompression.Zlib
+{
+ ///
+ /// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951).
+ ///
+ ///
+ ///
+ /// This class compresses and decompresses data according to the Deflate algorithm
+ /// and optionally, the ZLIB format, as documented in RFC 1950 - ZLIB and RFC 1951 - DEFLATE.
+ ///
+ sealed internal class ZlibCodec
+ {
+ ///
+ /// The buffer from which data is taken.
+ ///
+ public byte[] InputBuffer;
+
+ ///
+ /// An index into the InputBuffer array, indicating where to start reading.
+ ///
+ public int NextIn;
+
+ ///
+ /// The number of bytes available in the InputBuffer, starting at NextIn.
+ ///
+ ///
+ /// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call.
+ /// The class will update this number as calls to Inflate/Deflate are made.
+ ///
+ public int AvailableBytesIn;
+
+ ///
+ /// Total number of bytes read so far, through all calls to Inflate()/Deflate().
+ ///
+ public long TotalBytesIn;
+
+ ///
+ /// Buffer to store output data.
+ ///
+ public byte[] OutputBuffer;
+
+ ///
+ /// An index into the OutputBuffer array, indicating where to start writing.
+ ///
+ public int NextOut;
+
+ ///
+ /// The number of bytes available in the OutputBuffer, starting at NextOut.
+ ///
+ ///
+ /// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call.
+ /// The class will update this number as calls to Inflate/Deflate are made.
+ ///
+ public int AvailableBytesOut;
+
+ ///
+ /// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate().
+ ///
+ public long TotalBytesOut;
+
+ ///
+ /// used for diagnostics, when something goes wrong!
+ ///
+ public System.String Message;
+
+ internal DeflateManager dstate;
+ internal InflateManager istate;
+
+ internal uint _Adler32;
+
+ ///
+ /// The compression level to use in this codec. Useful only in compression mode.
+ ///
+ public CompressionLevel CompressLevel = CompressionLevel.Default;
+
+ ///
+ /// The number of Window Bits to use.
+ ///
+ ///
+ /// This gauges the size of the sliding window, and hence the
+ /// compression effectiveness as well as memory consumption. It's best to just leave this
+ /// setting alone if you don't know what it is. The maximum value is 15 bits, which implies
+ /// a 32k window.
+ ///
+ public int WindowBits = ZlibConstants.WindowBitsDefault;
+
+ ///
+ /// The compression strategy to use.
+ ///
+ ///
+ /// This is only effective in compression. The theory offered by ZLIB is that different
+ /// strategies could potentially produce significant differences in compression behavior
+ /// for different data sets. Unfortunately I don't have any good recommendations for how
+ /// to set it differently. When I tested changing the strategy I got minimally different
+ /// compression performance. It's best to leave this property alone if you don't have a
+ /// good feel for it. Or, you may want to produce a test harness that runs through the
+ /// different strategy options and evaluates them on different file types. If you do that,
+ /// let me know your results.
+ ///
+ public CompressionStrategy Strategy = CompressionStrategy.Default;
+
+
+ ///
+ /// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this.
+ ///
+ public int Adler32 { get { return (int)_Adler32; } }
+
+
+ ///
+ /// Create a ZlibCodec.
+ ///
+ ///
+ /// If you use this default constructor, you will later have to explicitly call
+ /// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress
+ /// or decompress.
+ ///
+ public ZlibCodec() { }
+
+ ///
+ /// Create a ZlibCodec that either compresses or decompresses.
+ ///
+ ///
+ /// Indicates whether the codec should compress (deflate) or decompress (inflate).
+ ///
+ public ZlibCodec(CompressionMode mode)
+ {
+ if (mode == CompressionMode.Compress)
+ {
+ int rc = InitializeDeflate();
+ if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for deflate.");
+ }
+ else if (mode == CompressionMode.Decompress)
+ {
+ int rc = InitializeInflate();
+ if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for inflate.");
+ }
+ else throw new ZlibException("Invalid ZlibStreamFlavor.");
+ }
+
+ ///
+ /// Initialize the inflation state.
+ ///
+ ///
+ /// It is not necessary to call this before using the ZlibCodec to inflate data;
+ /// It is implicitly called when you call the constructor.
+ ///
+ /// Z_OK if everything goes well.
+ public int InitializeInflate()
+ {
+ return InitializeInflate(this.WindowBits);
+ }
+
+ ///
+ /// Initialize the inflation state with an explicit flag to
+ /// govern the handling of RFC1950 header bytes.
+ ///
+ ///
+ ///
+ /// By default, the ZLIB header defined in RFC 1950 is expected. If
+ /// you want to read a zlib stream you should specify true for
+ /// expectRfc1950Header. If you have a deflate stream, you will want to specify
+ /// false. It is only necessary to invoke this initializer explicitly if you
+ /// want to specify false.
+ ///
+ ///
+ /// whether to expect an RFC1950 header byte
+ /// pair when reading the stream of data to be inflated.
+ ///
+ /// Z_OK if everything goes well.
+ public int InitializeInflate(bool expectRfc1950Header)
+ {
+ return InitializeInflate(this.WindowBits, expectRfc1950Header);
+ }
+
+ ///
+ /// Initialize the ZlibCodec for inflation, with the specified number of window bits.
+ ///
+ /// The number of window bits to use. If you need to ask what that is,
+ /// then you shouldn't be calling this initializer.
+ /// Z_OK if all goes well.
+ public int InitializeInflate(int windowBits)
+ {
+ this.WindowBits = windowBits;
+ return InitializeInflate(windowBits, true);
+ }
+
+ ///
+ /// Initialize the inflation state with an explicit flag to govern the handling of
+ /// RFC1950 header bytes.
+ ///
+ ///
+ ///
+ /// If you want to read a zlib stream you should specify true for
+ /// expectRfc1950Header. In this case, the library will expect to find a ZLIB
+ /// header, as defined in RFC
+ /// 1950, in the compressed stream. If you will be reading a DEFLATE or
+ /// GZIP stream, which does not have such a header, you will want to specify
+ /// false.
+ ///
+ ///
+ /// whether to expect an RFC1950 header byte pair when reading
+ /// the stream of data to be inflated.
+ /// The number of window bits to use. If you need to ask what that is,
+ /// then you shouldn't be calling this initializer.
+ /// Z_OK if everything goes well.
+ public int InitializeInflate(int windowBits, bool expectRfc1950Header)
+ {
+ this.WindowBits = windowBits;
+ if (dstate != null) throw new ZlibException("You may not call InitializeInflate() after calling InitializeDeflate().");
+ istate = new InflateManager(expectRfc1950Header);
+ return istate.Initialize(this, windowBits);
+ }
+
+ ///
+ /// Inflate the data in the InputBuffer, placing the result in the OutputBuffer.
+ ///
+ ///
+ /// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and
+ /// AvailableBytesOut before calling this method.
+ ///
+ ///
+ ///
+ /// private void InflateBuffer()
+ /// {
+ /// int bufferSize = 1024;
+ /// byte[] buffer = new byte[bufferSize];
+ /// ZlibCodec decompressor = new ZlibCodec();
+ ///
+ /// Console.WriteLine("\n============================================");
+ /// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length);
+ /// MemoryStream ms = new MemoryStream(DecompressedBytes);
+ ///
+ /// int rc = decompressor.InitializeInflate();
+ ///
+ /// decompressor.InputBuffer = CompressedBytes;
+ /// decompressor.NextIn = 0;
+ /// decompressor.AvailableBytesIn = CompressedBytes.Length;
+ ///
+ /// decompressor.OutputBuffer = buffer;
+ ///
+ /// // pass 1: inflate
+ /// do
+ /// {
+ /// decompressor.NextOut = 0;
+ /// decompressor.AvailableBytesOut = buffer.Length;
+ /// rc = decompressor.Inflate(FlushType.None);
+ ///
+ /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
+ /// throw new Exception("inflating: " + decompressor.Message);
+ ///
+ /// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut);
+ /// }
+ /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0);
+ ///
+ /// // pass 2: finish and flush
+ /// do
+ /// {
+ /// decompressor.NextOut = 0;
+ /// decompressor.AvailableBytesOut = buffer.Length;
+ /// rc = decompressor.Inflate(FlushType.Finish);
+ ///
+ /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
+ /// throw new Exception("inflating: " + decompressor.Message);
+ ///
+ /// if (buffer.Length - decompressor.AvailableBytesOut > 0)
+ /// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut);
+ /// }
+ /// while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0);
+ ///
+ /// decompressor.EndInflate();
+ /// }
+ ///
+ ///
+ ///
+ /// The flush to use when inflating.
+ /// Z_OK if everything goes well.
+ public int Inflate(FlushType flush)
+ {
+ if (istate == null)
+ throw new ZlibException("No Inflate State!");
+ return istate.Inflate(flush);
+ }
+
+
+ ///
+ /// Ends an inflation session.
+ ///
+ ///
+ /// Call this after successively calling Inflate(). This will cause all buffers to be flushed.
+ /// After calling this you cannot call Inflate() without a intervening call to one of the
+ /// InitializeInflate() overloads.
+ ///
+ /// Z_OK if everything goes well.
+ public int EndInflate()
+ {
+ if (istate == null)
+ throw new ZlibException("No Inflate State!");
+ int ret = istate.End();
+ istate = null;
+ return ret;
+ }
+
+ ///
+ /// I don't know what this does!
+ ///
+ /// Z_OK if everything goes well.
+ public int SyncInflate()
+ {
+ if (istate == null)
+ throw new ZlibException("No Inflate State!");
+ return istate.Sync();
+ }
+
+ ///
+ /// Initialize the ZlibCodec for deflation operation.
+ ///
+ ///
+ /// The codec will use the MAX window bits and the default level of compression.
+ ///
+ ///
+ ///
+ /// int bufferSize = 40000;
+ /// byte[] CompressedBytes = new byte[bufferSize];
+ /// byte[] DecompressedBytes = new byte[bufferSize];
+ ///
+ /// ZlibCodec compressor = new ZlibCodec();
+ ///
+ /// compressor.InitializeDeflate(CompressionLevel.Default);
+ ///
+ /// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress);
+ /// compressor.NextIn = 0;
+ /// compressor.AvailableBytesIn = compressor.InputBuffer.Length;
+ ///
+ /// compressor.OutputBuffer = CompressedBytes;
+ /// compressor.NextOut = 0;
+ /// compressor.AvailableBytesOut = CompressedBytes.Length;
+ ///
+ /// while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize)
+ /// {
+ /// compressor.Deflate(FlushType.None);
+ /// }
+ ///
+ /// while (true)
+ /// {
+ /// int rc= compressor.Deflate(FlushType.Finish);
+ /// if (rc == ZlibConstants.Z_STREAM_END) break;
+ /// }
+ ///
+ /// compressor.EndDeflate();
+ ///
+ ///
+ ///
+ /// Z_OK if all goes well. You generally don't need to check the return code.
+ public int InitializeDeflate()
+ {
+ return _InternalInitializeDeflate(true);
+ }
+
+ ///
+ /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel.
+ ///
+ ///
+ /// The codec will use the maximum window bits (15) and the specified
+ /// CompressionLevel. It will emit a ZLIB stream as it compresses.
+ ///
+ /// The compression level for the codec.
+ /// Z_OK if all goes well.
+ public int InitializeDeflate(CompressionLevel level)
+ {
+ this.CompressLevel = level;
+ return _InternalInitializeDeflate(true);
+ }
+
+
+ ///
+ /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
+ /// and the explicit flag governing whether to emit an RFC1950 header byte pair.
+ ///
+ ///
+ /// The codec will use the maximum window bits (15) and the specified CompressionLevel.
+ /// If you want to generate a zlib stream, you should specify true for
+ /// wantRfc1950Header. In this case, the library will emit a ZLIB
+ /// header, as defined in RFC
+ /// 1950, in the compressed stream.
+ ///
+ /// The compression level for the codec.
+ /// whether to emit an initial RFC1950 byte pair in the compressed stream.
+ /// Z_OK if all goes well.
+ public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header)
+ {
+ this.CompressLevel = level;
+ return _InternalInitializeDeflate(wantRfc1950Header);
+ }
+
+
+ ///
+ /// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
+ /// and the specified number of window bits.
+ ///
+ ///
+ /// The codec will use the specified number of window bits and the specified CompressionLevel.
+ ///
+ /// The compression level for the codec.
+ /// the number of window bits to use. If you don't know what this means, don't use this method.
+ /// Z_OK if all goes well.
+ public int InitializeDeflate(CompressionLevel level, int bits)
+ {
+ this.CompressLevel = level;
+ this.WindowBits = bits;
+ return _InternalInitializeDeflate(true);
+ }
+
+ ///
+ /// Initialize the ZlibCodec for deflation operation, using the specified
+ /// CompressionLevel, the specified number of window bits, and the explicit flag
+ /// governing whether to emit an RFC1950 header byte pair.
+ ///
+ ///
+ /// The compression level for the codec.
+ /// whether to emit an initial RFC1950 byte pair in the compressed stream.
+ /// the number of window bits to use. If you don't know what this means, don't use this method.
+ /// Z_OK if all goes well.
+ public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header)
+ {
+ this.CompressLevel = level;
+ this.WindowBits = bits;
+ return _InternalInitializeDeflate(wantRfc1950Header);
+ }
+
+ private int _InternalInitializeDeflate(bool wantRfc1950Header)
+ {
+ if (istate != null) throw new ZlibException("You may not call InitializeDeflate() after calling InitializeInflate().");
+ dstate = new DeflateManager();
+ dstate.WantRfc1950HeaderBytes = wantRfc1950Header;
+
+ return dstate.Initialize(this, this.CompressLevel, this.WindowBits, this.Strategy);
+ }
+
+ ///
+ /// Deflate one batch of data.
+ ///
+ ///
+ /// You must have set InputBuffer and OutputBuffer before calling this method.
+ ///
+ ///
+ ///
+ /// private void DeflateBuffer(CompressionLevel level)
+ /// {
+ /// int bufferSize = 1024;
+ /// byte[] buffer = new byte[bufferSize];
+ /// ZlibCodec compressor = new ZlibCodec();
+ ///
+ /// Console.WriteLine("\n============================================");
+ /// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length);
+ /// MemoryStream ms = new MemoryStream();
+ ///
+ /// int rc = compressor.InitializeDeflate(level);
+ ///
+ /// compressor.InputBuffer = UncompressedBytes;
+ /// compressor.NextIn = 0;
+ /// compressor.AvailableBytesIn = UncompressedBytes.Length;
+ ///
+ /// compressor.OutputBuffer = buffer;
+ ///
+ /// // pass 1: deflate
+ /// do
+ /// {
+ /// compressor.NextOut = 0;
+ /// compressor.AvailableBytesOut = buffer.Length;
+ /// rc = compressor.Deflate(FlushType.None);
+ ///
+ /// if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
+ /// throw new Exception("deflating: " + compressor.Message);
+ ///
+ /// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut);
+ /// }
+ /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0);
+ ///
+ /// // pass 2: finish and flush
+ /// do
+ /// {
+ /// compressor.NextOut = 0;
+ /// compressor.AvailableBytesOut = buffer.Length;
+ /// rc = compressor.Deflate(FlushType.Finish);
+ ///
+ /// if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
+ /// throw new Exception("deflating: " + compressor.Message);
+ ///
+ /// if (buffer.Length - compressor.AvailableBytesOut > 0)
+ /// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut);
+ /// }
+ /// while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0);
+ ///
+ /// compressor.EndDeflate();
+ ///
+ /// ms.Seek(0, SeekOrigin.Begin);
+ /// CompressedBytes = new byte[compressor.TotalBytesOut];
+ /// ms.Read(CompressedBytes, 0, CompressedBytes.Length);
+ /// }
+ ///
+ ///
+ /// whether to flush all data as you deflate. Generally you will want to
+ /// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to
+ /// flush everything.
+ ///
+ /// Z_OK if all goes well.
+ public int Deflate(FlushType flush)
+ {
+ if (dstate == null)
+ throw new ZlibException("No Deflate State!");
+ return dstate.Deflate(flush);
+ }
+
+ ///
+ /// End a deflation session.
+ ///
+ ///
+ /// Call this after making a series of one or more calls to Deflate(). All buffers are flushed.
+ ///
+ /// Z_OK if all goes well.
+ public int EndDeflate()
+ {
+ if (dstate == null)
+ throw new ZlibException("No Deflate State!");
+ // Room for improvement: dinoch Tue, 03 Nov 2009 15:39 (test this)
+ //int ret = dstate.End();
+ dstate = null;
+ return ZlibConstants.Z_OK; //ret;
+ }
+
+ ///
+ /// Reset a codec for another deflation session.
+ ///
+ ///
+ /// Call this to reset the deflation state. For example if a thread is deflating
+ /// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first
+ /// block and before the next Deflate(None) of the second block.
+ ///
+ /// Z_OK if all goes well.
+ public void ResetDeflate()
+ {
+ if (dstate == null)
+ throw new ZlibException("No Deflate State!");
+ dstate.Reset();
+ }
+
+
+ ///
+ /// Set the CompressionStrategy and CompressionLevel for a deflation session.
+ ///
+ /// the level of compression to use.
+ /// the strategy to use for compression.
+ /// Z_OK if all goes well.
+ public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy)
+ {
+ if (dstate == null)
+ throw new ZlibException("No Deflate State!");
+ return dstate.SetParams(level, strategy);
+ }
+
+
+ ///
+ /// Set the dictionary to be used for either Inflation or Deflation.
+ ///
+ /// The dictionary bytes to use.
+ /// Z_OK if all goes well.
+ public int SetDictionary(byte[] dictionary)
+ {
+ if (istate != null)
+ return istate.SetDictionary(dictionary);
+
+ if (dstate != null)
+ return dstate.SetDictionary(dictionary);
+
+ throw new ZlibException("No Inflate or Deflate state!");
+ }
+
+ // Flush as much pending output as possible. All deflate() output goes
+ // through this function so some applications may wish to modify it
+ // to avoid allocating a large strm->next_out buffer and copying into it.
+ // (See also read_buf()).
+ internal void flush_pending()
+ {
+ int len = dstate.pendingCount;
+
+ if (len > AvailableBytesOut)
+ len = AvailableBytesOut;
+ if (len == 0)
+ return;
+
+ if (dstate.pending.Length <= dstate.nextPending ||
+ OutputBuffer.Length <= NextOut ||
+ dstate.pending.Length < (dstate.nextPending + len) ||
+ OutputBuffer.Length < (NextOut + len))
+ {
+ throw new ZlibException(String.Format("Invalid State. (pending.Length={0}, pendingCount={1})",
+ dstate.pending.Length, dstate.pendingCount));
+ }
+
+ Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len);
+
+ NextOut += len;
+ dstate.nextPending += len;
+ TotalBytesOut += len;
+ AvailableBytesOut -= len;
+ dstate.pendingCount -= len;
+ if (dstate.pendingCount == 0)
+ {
+ dstate.nextPending = 0;
+ }
+ }
+
+ // Read a new buffer from the current input stream, update the adler32
+ // and total number of bytes read. All deflate() input goes through
+ // this function so some applications may wish to modify it to avoid
+ // allocating a large strm->next_in buffer and copying from it.
+ // (See also flush_pending()).
+ internal int read_buf(byte[] buf, int start, int size)
+ {
+ int len = AvailableBytesIn;
+
+ if (len > size)
+ len = size;
+ if (len == 0)
+ return 0;
+
+ AvailableBytesIn -= len;
+
+ if (dstate.WantRfc1950HeaderBytes)
+ {
+ _Adler32 = Adler.Adler32(_Adler32, InputBuffer, NextIn, len);
+ }
+ Array.Copy(InputBuffer, NextIn, buf, start, len);
+ NextIn += len;
+ TotalBytesIn += len;
+ return len;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..a017f14f5db858628982c2b59314b3ce856634ec
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibCodec.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: bc104cfa5b2ba31408766b86b9977226
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b530c39b139edea1b6bac78af291760f7b7d81bd
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs
@@ -0,0 +1,128 @@
+// ZlibConstants.cs
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
+// All rights reserved.
+//
+// This code module is part of DotNetZip, a zipfile class library.
+//
+// ------------------------------------------------------------------
+//
+// This code is licensed under the Microsoft Public License.
+// See the file License.txt for the license details.
+// More info on: http://dotnetzip.codeplex.com
+//
+// ------------------------------------------------------------------
+//
+// last saved (in emacs):
+// Time-stamp: <2009-November-03 18:50:19>
+//
+// ------------------------------------------------------------------
+//
+// This module defines constants used by the zlib class library. This
+// code is derived from the jzlib implementation of zlib, but
+// significantly modified. In keeping with the license for jzlib, the
+// copyright to that code is included here.
+//
+// ------------------------------------------------------------------
+//
+// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the distribution.
+//
+// 3. The names of the authors may not be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// -----------------------------------------------------------------------
+//
+// This program is based on zlib-1.1.3; credit to authors
+// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
+// and contributors of zlib.
+//
+// -----------------------------------------------------------------------
+
+
+using System;
+
+namespace BestHTTP.Decompression.Zlib
+{
+ ///
+ /// A bunch of constants used in the Zlib interface.
+ ///
+ public static class ZlibConstants
+ {
+ ///
+ /// The maximum number of window bits for the Deflate algorithm.
+ ///
+ public const int WindowBitsMax = 15; // 32K LZ77 window
+
+ ///
+ /// The default number of window bits for the Deflate algorithm.
+ ///
+ public const int WindowBitsDefault = WindowBitsMax;
+
+ ///
+ /// indicates everything is A-OK
+ ///
+ public const int Z_OK = 0;
+
+ ///
+ /// Indicates that the last operation reached the end of the stream.
+ ///
+ public const int Z_STREAM_END = 1;
+
+ ///
+ /// The operation ended in need of a dictionary.
+ ///
+ public const int Z_NEED_DICT = 2;
+
+ ///
+ /// There was an error with the stream - not enough data, not open and readable, etc.
+ ///
+ public const int Z_STREAM_ERROR = -2;
+
+ ///
+ /// There was an error with the data - not enough data, bad data, etc.
+ ///
+ public const int Z_DATA_ERROR = -3;
+
+ ///
+ /// There was an error with the working buffer.
+ ///
+ public const int Z_BUF_ERROR = -5;
+
+ ///
+ /// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes.
+ ///
+#if NETCF
+ public const int WorkingBufferSizeDefault = 8192;
+#else
+ public const int WorkingBufferSizeDefault = 16384;
+#endif
+ ///
+ /// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes.
+ ///
+ public const int WorkingBufferSizeMin = 1024;
+ }
+
+}
+
diff --git a/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs.meta b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..a1659c4da56871a26b5b0277de5e5674b66cc5a4
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Decompression/ZlibConstants.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 4cd9445fbeecd0642955b4ea35e211c8
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions.meta b/Assets/Plugins/BestHTTP/Source/Extensions.meta
new file mode 100644
index 0000000000000000000000000000000000000000..ee824e777e51817920fb04a84239bfbe778e540f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e3ba637693c35a745aeb4052cfb287cb
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs b/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1d3f36308896fcfdf2642040c208d1a1ae93aa3e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs
@@ -0,0 +1,475 @@
+//
+// System.IO.MemoryStream.cs
+//
+// Authors: Marcin Szczepanski (marcins@zipworld.com.au)
+// Patrik Torstensson
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// (c) 2001,2002 Marcin Szczepanski, Patrik Torstensson
+// (c) 2003 Ximian, Inc. (http://www.ximian.com)
+// Copyright (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+using BestHTTP.PlatformSupport.Memory;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// This is a modified MemoryStream class to use VariableSizedBufferPool
+ ///
+ public sealed class BufferPoolMemoryStream : System.IO.Stream
+ {
+ bool canWrite;
+ bool allowGetBuffer;
+ int capacity;
+ int length;
+ byte[] internalBuffer;
+ int initialIndex;
+ bool expandable;
+ bool streamClosed;
+ int position;
+ int dirty_bytes;
+ bool releaseInternalBuffer;
+
+ public BufferPoolMemoryStream() : this(0)
+ {
+ }
+
+ public BufferPoolMemoryStream(int capacity)
+ {
+ if (capacity < 0)
+ throw new ArgumentOutOfRangeException("capacity");
+
+ canWrite = true;
+
+ //internalBuffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
+ //this.capacity = internalBuffer.Length;
+ //
+ //expandable = true;
+ //allowGetBuffer = true;
+ var buffer = capacity > 0 ? BufferPool.Get(capacity, true) : BufferPool.NoData;
+ InternalConstructor(buffer, 0, buffer.Length, true, true, true, true);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ InternalConstructor(buffer, 0, buffer.Length, true, false, true, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, bool writable)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ InternalConstructor(buffer, 0, buffer.Length, writable, false, true, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, int index, int count)
+ {
+ InternalConstructor(buffer, index, count, true, false, true, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable)
+ {
+ InternalConstructor(buffer, index, count, writable, false, true, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible)
+ {
+ InternalConstructor(buffer, index, count, writable, publiclyVisible, true, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer)
+ {
+ InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, false);
+ }
+
+ public BufferPoolMemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible, bool releaseBuffer, bool canExpand)
+ {
+ InternalConstructor(buffer, index, count, writable, publiclyVisible, releaseBuffer, canExpand);
+ }
+
+ void InternalConstructor(byte[] buffer, int index, int count, bool writable, bool publicallyVisible, bool releaseBuffer, bool canExpand)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ if (index < 0 || count < 0)
+ throw new ArgumentOutOfRangeException("index or count is less than 0.");
+
+ if (buffer.Length - index < count)
+ throw new ArgumentException("index+count",
+ "The size of the buffer is less than index + count.");
+
+ canWrite = writable;
+
+ internalBuffer = buffer;
+ capacity = count + index;
+ //length = capacity;
+ length = 0;
+ position = index;
+ initialIndex = index;
+
+ allowGetBuffer = publicallyVisible;
+ releaseInternalBuffer = releaseBuffer;
+ expandable = canExpand;
+ }
+
+ void CheckIfClosedThrowDisposed()
+ {
+ if (streamClosed)
+ throw new ObjectDisposedException("MemoryStream");
+ }
+
+ public override bool CanRead
+ {
+ get { return !streamClosed; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return !streamClosed; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return (!streamClosed && canWrite); }
+ }
+
+ public int Capacity
+ {
+ get
+ {
+ CheckIfClosedThrowDisposed();
+ return capacity - initialIndex;
+ }
+
+ set
+ {
+ CheckIfClosedThrowDisposed();
+ if (value == capacity)
+ return; // LAMENESS: see MemoryStreamTest.ConstructorFive
+
+ if (!expandable)
+ throw new NotSupportedException("Cannot expand this MemoryStream");
+
+ if (value < 0 || value < length)
+ throw new ArgumentOutOfRangeException("value",
+ "New capacity cannot be negative or less than the current capacity " + value + " " + capacity);
+
+ byte[] newBuffer = null;
+ if (value != 0)
+ {
+ newBuffer = BufferPool.Get(value, true);
+ Buffer.BlockCopy(internalBuffer, 0, newBuffer, 0, length);
+ }
+
+ dirty_bytes = 0; // discard any dirty area beyond previous length
+ BufferPool.Release(internalBuffer);
+ internalBuffer = newBuffer; // It's null when capacity is set to 0
+ capacity = internalBuffer != null ? internalBuffer.Length : 0;
+ }
+ }
+
+ public override long Length
+ {
+ get
+ {
+ // LAMESPEC: The spec says to throw an IOException if the
+ // stream is closed and an ObjectDisposedException if
+ // "methods were called after the stream was closed". What
+ // is the difference?
+
+ CheckIfClosedThrowDisposed();
+
+ // This is ok for MemoryStreamTest.ConstructorFive
+ return length - initialIndex;
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ CheckIfClosedThrowDisposed();
+ return position - initialIndex;
+ }
+
+ set
+ {
+ CheckIfClosedThrowDisposed();
+ if (value < 0)
+ throw new ArgumentOutOfRangeException("value",
+ "Position cannot be negative");
+
+ if (value > Int32.MaxValue)
+ throw new ArgumentOutOfRangeException("value",
+ "Position must be non-negative and less than 2^31 - 1 - origin");
+
+ position = initialIndex + (int)value;
+ }
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ streamClosed = true;
+ expandable = false;
+ if (disposing && internalBuffer != null && this.releaseInternalBuffer)
+ BufferPool.Release(internalBuffer);
+ internalBuffer = null;
+ }
+
+ public override void Flush()
+ {
+ // Do nothing
+ }
+
+ public byte[] GetBuffer()
+ {
+ if (!allowGetBuffer)
+ throw new UnauthorizedAccessException();
+
+ return internalBuffer;
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ CheckIfClosedThrowDisposed();
+
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ if (offset < 0 || count < 0)
+ throw new ArgumentOutOfRangeException("offset or count less than zero.");
+
+ if (buffer.Length - offset < count)
+ throw new ArgumentException("offset+count",
+ "The size of the buffer is less than offset + count.");
+
+ if (position >= length || count == 0)
+ return 0;
+
+ if (position > length - count)
+ count = length - position;
+
+ Buffer.BlockCopy(internalBuffer, position, buffer, offset, count);
+ position += count;
+ return count;
+ }
+
+ public override int ReadByte()
+ {
+ CheckIfClosedThrowDisposed();
+ if (position >= length)
+ return -1;
+
+ return internalBuffer[position++];
+ }
+
+ public override long Seek(long offset, SeekOrigin loc)
+ {
+ CheckIfClosedThrowDisposed();
+
+ // It's funny that they don't throw this exception for < Int32.MinValue
+ if (offset > (long)Int32.MaxValue)
+ throw new ArgumentOutOfRangeException("Offset out of range. " + offset);
+
+ int refPoint;
+ switch (loc)
+ {
+ case SeekOrigin.Begin:
+ if (offset < 0)
+ throw new IOException("Attempted to seek before start of MemoryStream.");
+ refPoint = initialIndex;
+ break;
+ case SeekOrigin.Current:
+ refPoint = position;
+ break;
+ case SeekOrigin.End:
+ refPoint = length;
+ break;
+ default:
+ throw new ArgumentException("loc", "Invalid SeekOrigin");
+ }
+
+ // LAMESPEC: My goodness, how may LAMESPECs are there in this
+ // class! :) In the spec for the Position property it's stated
+ // "The position must not be more than one byte beyond the end of the stream."
+ // In the spec for seek it says "Seeking to any location beyond the length of the
+ // stream is supported." That's a contradiction i'd say.
+ // I guess seek can go anywhere but if you use position it may get moved back.
+
+ refPoint += (int)offset;
+ if (refPoint < initialIndex)
+ throw new IOException("Attempted to seek before start of MemoryStream.");
+
+ position = refPoint;
+ return position;
+ }
+
+ int CalculateNewCapacity(int minimum)
+ {
+ if (minimum < 256)
+ minimum = 256; // See GetBufferTwo test
+
+ if (minimum < capacity * 2)
+ minimum = capacity * 2;
+
+ if (!UnityEngine.Mathf.IsPowerOfTwo(minimum))
+ minimum = UnityEngine.Mathf.NextPowerOfTwo(minimum);
+
+ return minimum;
+ }
+
+ void Expand(int newSize)
+ {
+ // We don't need to take into account the dirty bytes when incrementing the
+ // Capacity, as changing it will only preserve the valid clear region.
+ if (newSize > capacity)
+ Capacity = CalculateNewCapacity(newSize);
+ else if (dirty_bytes > 0)
+ {
+ Array.Clear(internalBuffer, length, dirty_bytes);
+ dirty_bytes = 0;
+ }
+ }
+
+ public override void SetLength(long value)
+ {
+ if (!expandable && value > capacity)
+ throw new NotSupportedException("Expanding this MemoryStream is not supported");
+
+ CheckIfClosedThrowDisposed();
+
+ if (!canWrite)
+ {
+ throw new NotSupportedException("Cannot write to this MemoryStream");
+ }
+
+ // LAMESPEC: AGAIN! It says to throw this exception if value is
+ // greater than "the maximum length of the MemoryStream". I haven't
+ // seen anywhere mention what the maximum length of a MemoryStream is and
+ // since we're this far this memory stream is expandable.
+ if (value < 0 || (value + initialIndex) > (long)Int32.MaxValue)
+ throw new ArgumentOutOfRangeException();
+
+ int newSize = (int)value + initialIndex;
+
+ if (newSize > length)
+ Expand(newSize);
+ else if (newSize < length) // Postpone the call to Array.Clear till expand time
+ dirty_bytes += length - newSize;
+
+ length = newSize;
+ if (position > length)
+ position = length;
+ }
+
+ public byte[] ToArray()
+ {
+ return ToArray(false);
+ }
+
+ public byte[] ToArray(bool canBeLarger)
+ {
+ int l = length - initialIndex;
+ byte[] outBuffer = l > 0 ? BufferPool.Get(l, canBeLarger) : BufferPool.NoData;
+
+ if (internalBuffer != null)
+ Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
+ return outBuffer;
+ }
+
+ public BufferSegment ToBufferSegment()
+ {
+ int l = length - initialIndex;
+ byte[] outBuffer = l > 0 ? BufferPool.Get(l, true) : BufferPool.NoData;
+
+ if (internalBuffer != null)
+ Buffer.BlockCopy(internalBuffer, initialIndex, outBuffer, 0, l);
+
+ return new BufferSegment(outBuffer, 0, l);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ CheckIfClosedThrowDisposed();
+
+ if (!canWrite)
+ throw new NotSupportedException("Cannot write to this stream.");
+
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+
+ if (offset < 0 || count < 0)
+ throw new ArgumentOutOfRangeException();
+
+ if (buffer.Length - offset < count)
+ throw new ArgumentException("offset+count",
+ "The size of the buffer is less than offset + count.");
+
+ // reordered to avoid possible integer overflow
+ if (position > length - count)
+ Expand(position + count);
+
+ Buffer.BlockCopy(buffer, offset, internalBuffer, position, count);
+ position += count;
+ if (position >= length)
+ length = position;
+ }
+
+ public override void WriteByte(byte value)
+ {
+ CheckIfClosedThrowDisposed();
+ if (!canWrite)
+ throw new NotSupportedException("Cannot write to this stream.");
+
+ if (position >= length)
+ {
+ Expand(position + 1);
+ length = position + 1;
+ }
+
+ internalBuffer[position++] = value;
+ }
+
+ public void WriteTo(Stream stream)
+ {
+ CheckIfClosedThrowDisposed();
+
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+
+ stream.Write(internalBuffer, initialIndex, length - initialIndex);
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..1cc7e2f61882f04d408d99da92d65cc1b723518f
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/BufferPoolMemoryStream.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ce34f1f3b5c30794b9e26c0922185261
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs b/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a9f4e2cfc3d47906895d8e5985211e38f955bea4
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using BestHTTP.PlatformSupport.Memory;
+
+namespace BestHTTP.Extensions
+{
+ public class BufferSegmentStream : Stream
+ {
+ public override bool CanRead { get { return false; } }
+
+ public override bool CanSeek { get { return false; } }
+
+ public override bool CanWrite { get { return false; } }
+
+ public override long Length { get { return this._length; } }
+ protected long _length;
+
+ public override long Position { get { return 0; } set { } }
+
+ protected List bufferList = new List();
+
+ private byte[] _tempByteArray = new byte[1];
+
+ public override int ReadByte()
+ {
+ if (Read(this._tempByteArray, 0, 1) == 0)
+ return -1;
+
+ return this._tempByteArray[0];
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ int sumReadCount = 0;
+
+ while (count > 0 && bufferList.Count > 0)
+ {
+ BufferSegment buff = this.bufferList[0];
+
+ int readCount = Math.Min(count, buff.Count);
+
+ Array.Copy(buff.Data, buff.Offset, buffer, offset, readCount);
+
+ sumReadCount += readCount;
+ offset += readCount;
+ count -= readCount;
+
+ if (readCount >= buff.Count)
+ {
+ this.bufferList.RemoveAt(0);
+ BufferPool.Release(buff.Data);
+ }
+ else
+ this.bufferList[0] = new BufferSegment(buff.Data, buff.Offset + readCount, buff.Count - readCount);
+ }
+
+ this._length -= sumReadCount;
+
+ return sumReadCount;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ Write(new BufferSegment(buffer, offset, count));
+ }
+
+ public virtual void Write(BufferSegment bufferSegment)
+ {
+ this.bufferList.Add(bufferSegment);
+ this._length += bufferSegment.Count;
+ }
+
+ public virtual void Reset()
+ {
+ for (int i = 0; i < this.bufferList.Count; ++i)
+ BufferPool.Release(this.bufferList[i]);
+ this.bufferList.Clear();
+ this._length = 0;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ this._length = 0;
+ }
+
+ public override void Flush() { }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..ea22a21814afbbc884c0c0f6e1e47fae52bb0087
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/BufferSegmentStream.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 163c4b09e7c509c4aaffd21e9961824c
+timeCreated: 1574255448
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs b/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5059f5b598424960995468f9e6c6435cb6c6a906
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs
@@ -0,0 +1,75 @@
+using System;
+
+namespace BestHTTP.Extensions
+{
+ public sealed class CircularBuffer
+ {
+ public int Capacity { get; private set; }
+ public int Count { get; private set; }
+ public int StartIdx { get { return this.startIdx; } }
+ public int EndIdx { get { return this.endIdx; } }
+
+ public T this[int idx]
+ {
+ get
+ {
+ int realIdx = (this.startIdx + idx) % this.Capacity;
+
+ return this.buffer[realIdx];
+ }
+
+ set
+ {
+ int realIdx = (this.startIdx + idx) % this.Capacity;
+
+ this.buffer[realIdx] = value;
+ }
+ }
+
+ private T[] buffer;
+ private int startIdx;
+ private int endIdx;
+
+ public CircularBuffer(int capacity)
+ {
+ this.Capacity = capacity;
+ }
+
+ public void Add(T element)
+ {
+ if (this.buffer == null)
+ this.buffer = new T[this.Capacity];
+
+ this.buffer[this.endIdx] = element;
+
+ this.endIdx = (this.endIdx + 1) % this.Capacity;
+ if (this.endIdx == this.startIdx)
+ this.startIdx = (this.startIdx + 1) % this.Capacity;
+
+ this.Count = Math.Min(this.Count + 1, this.Capacity);
+ }
+
+ public void Clear()
+ {
+ this.Count = this.startIdx = this.endIdx = 0;
+ }
+
+ public override string ToString()
+ {
+ var sb = new System.Text.StringBuilder("[");
+
+ int idx = this.startIdx;
+ while (idx != this.endIdx)
+ {
+ sb.Append(this.buffer[idx].ToString());
+
+ idx = (idx + 1) % this.Capacity;
+ if (idx != this.endIdx)
+ sb.Append("; ");
+ }
+ sb.Append("]");
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..78d460ce426049c15429c0229da2ad6f0c8799e9
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/CircularBuffer.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: d3e42ed97af74b54fba27235416b1191
+timeCreated: 1508423928
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs b/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f5719b6fdbc6bdaf6cd4d03af91ae501d3680ceb
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs
@@ -0,0 +1,498 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+#if NETFX_CORE
+ using Windows.Security.Cryptography;
+ using Windows.Security.Cryptography.Core;
+ using Windows.Storage.Streams;
+#else
+ using Cryptography = System.Security.Cryptography;
+ using FileStream = System.IO.FileStream;
+#endif
+
+using BestHTTP.PlatformSupport.Memory;
+
+namespace BestHTTP.Extensions
+{
+ public static class Extensions
+ {
+ #region ASCII Encoding (These are required because Windows Phone doesn't supports the Encoding.ASCII class.)
+
+ ///
+ /// On WP8 platform there are no ASCII encoding.
+ ///
+ public static string AsciiToString(this byte[] bytes)
+ {
+ StringBuilder sb = new StringBuilder(bytes.Length);
+ foreach (byte b in bytes)
+ sb.Append(b <= 0x7f ? (char)b : '?');
+ return sb.ToString();
+ }
+
+ ///
+ /// On WP8 platform there are no ASCII encoding.
+ ///
+ public static byte[] GetASCIIBytes(this string str)
+ {
+ byte[] result = BufferPool.Get(str.Length, false);
+ for (int i = 0; i < str.Length; ++i)
+ {
+ char ch = str[i];
+ result[i] = (byte)((ch < (char)0x80) ? ch : '?');
+ }
+
+ return result;
+ }
+
+ public static void SendAsASCII(this BinaryWriter stream, string str)
+ {
+ for (int i = 0; i < str.Length; ++i)
+ {
+ char ch = str[i];
+
+ stream.Write((byte)((ch < (char)0x80) ? ch : '?'));
+ }
+ }
+
+ #endregion
+
+ #region FileSystem WriteLine function support
+
+ public static void WriteLine(this Stream fs)
+ {
+ fs.Write(HTTPRequest.EOL, 0, 2);
+ }
+
+ public static void WriteLine(this Stream fs, string line)
+ {
+ var buff = line.GetASCIIBytes();
+ fs.Write(buff, 0, buff.Length);
+ fs.WriteLine();
+ BufferPool.Release(buff);
+ }
+
+ public static void WriteLine(this Stream fs, string format, params object[] values)
+ {
+ var buff = string.Format(format, values).GetASCIIBytes();
+ fs.Write(buff, 0, buff.Length);
+ fs.WriteLine();
+ BufferPool.Release(buff);
+ }
+
+ #endregion
+
+ #region Other Extensions
+
+ public static string GetRequestPathAndQueryURL(this Uri uri)
+ {
+ string requestPathAndQuery = uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);
+
+ // http://forum.unity3d.com/threads/best-http-released.200006/page-26#post-2723250
+ if (string.IsNullOrEmpty(requestPathAndQuery))
+ requestPathAndQuery = "/";
+
+ return requestPathAndQuery;
+ }
+
+ public static string[] FindOption(this string str, string option)
+ {
+ //s-maxage=2678400, must-revalidate, max-age=0
+ string[] options = str.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ option = option.ToLower();
+
+ for (int i = 0; i < options.Length; ++i)
+ if (options[i].Contains(option))
+ return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
+
+ return null;
+ }
+
+ public static string[] FindOption(this string[] options, string option)
+ {
+ for (int i = 0; i < options.Length; ++i)
+ if (options[i].Contains(option))
+ return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
+
+ return null;
+ }
+
+ public static void WriteArray(this Stream stream, byte[] array)
+ {
+ stream.Write(array, 0, array.Length);
+ }
+
+ ///
+ /// Returns true if the Uri's host is a valid IPv4 or IPv6 address.
+ ///
+ public static bool IsHostIsAnIPAddress(this Uri uri)
+ {
+ if (uri == null)
+ return false;
+
+ return IsIpV4AddressValid(uri.Host) || IsIpV6AddressValid(uri.Host);
+ }
+
+ // Original idea from: https://www.code4copy.com/csharp/c-validate-ip-address-string/
+ // Working regex: https://www.regular-expressions.info/ip.html
+ private static readonly System.Text.RegularExpressions.Regex validIpV4AddressRegex = new System.Text.RegularExpressions.Regex("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
+
+ ///
+ /// Validates an IPv4 address.
+ ///
+ public static bool IsIpV4AddressValid(string address)
+ {
+ if (!string.IsNullOrEmpty(address))
+ return validIpV4AddressRegex.IsMatch(address.Trim());
+
+ return false;
+ }
+
+ ///
+ /// Validates an IPv6 address.
+ ///
+ public static bool IsIpV6AddressValid(string address)
+ {
+#if !NETFX_CORE
+ if (!string.IsNullOrEmpty(address))
+ {
+ System.Net.IPAddress ip;
+ if (System.Net.IPAddress.TryParse(address, out ip))
+ return ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6;
+ }
+#endif
+ return false;
+ }
+
+ #endregion
+
+ #region String Conversions
+
+ public static int ToInt32(this string str, int defaultValue = default(int))
+ {
+ if (str == null)
+ return defaultValue;
+
+ try
+ {
+ return int.Parse(str);
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ public static long ToInt64(this string str, long defaultValue = default(long))
+ {
+ if (str == null)
+ return defaultValue;
+
+ try
+ {
+ return long.Parse(str);
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ public static DateTime ToDateTime(this string str, DateTime defaultValue = default(DateTime))
+ {
+ if (str == null)
+ return defaultValue;
+
+ try
+ {
+ DateTime.TryParse(str, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out defaultValue);
+ return defaultValue.ToUniversalTime();
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ public static string ToStrOrEmpty(this string str)
+ {
+ if (str == null)
+ return String.Empty;
+
+ return str;
+ }
+
+ public static string ToStr(this string str, string defaultVale)
+ {
+ if (str == null)
+ return defaultVale;
+
+ return str;
+ }
+
+ public static string ToBinaryStr(this byte value)
+ {
+ return Convert.ToString(value, 2).PadLeft(8, '0');
+ }
+
+ #endregion
+
+ #region MD5 Hashing
+
+ public static string CalculateMD5Hash(this string input)
+ {
+ byte[] ascii = input.GetASCIIBytes();
+ var hash = ascii.CalculateMD5Hash();
+ BufferPool.Release(ascii);
+ return hash;
+ }
+
+ public static string CalculateMD5Hash(this byte[] input)
+ {
+#if NETFX_CORE
+ var alg = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
+ IBuffer buff = CryptographicBuffer.CreateFromByteArray(input);
+ var hashed = alg.HashData(buff);
+ var res = CryptographicBuffer.EncodeToHexString(hashed);
+ return res;
+#else
+ using (var md5 = Cryptography.MD5.Create()) {
+ var hash = md5.ComputeHash(input);
+ var sb = new StringBuilder(hash.Length);
+ for (int i = 0; i < hash.Length; ++i)
+ sb.Append(hash[i].ToString("x2"));
+ BufferPool.Release(hash);
+ return sb.ToString();
+ }
+#endif
+ }
+
+ #endregion
+
+ #region Efficient String Parsing Helpers
+
+ internal static string Read(this string str, ref int pos, char block, bool needResult = true)
+ {
+ return str.Read(ref pos, (ch) => ch != block, needResult);
+ }
+
+ internal static string Read(this string str, ref int pos, Func block, bool needResult = true)
+ {
+ if (pos >= str.Length)
+ return string.Empty;
+
+ str.SkipWhiteSpace(ref pos);
+
+ int startPos = pos;
+
+ while (pos < str.Length && block(str[pos]))
+ pos++;
+
+ string result = needResult ? str.Substring(startPos, pos - startPos) : null;
+
+ // set position to the next char
+ pos++;
+
+ return result;
+ }
+
+ internal static string ReadPossibleQuotedText(this string str, ref int pos)
+ {
+ string result = string.Empty;
+ if (str == null)
+ return result;
+
+ // It's a quoted text?
+ if (str[pos] == '\"')
+ {
+ // Skip the starting quote
+ str.Read(ref pos, '\"', false);
+
+ // Read the text until the ending quote
+ result = str.Read(ref pos, '\"');
+
+ // Next option
+ str.Read(ref pos, (ch) => ch != ',' && ch != ';', false);
+ }
+ else
+ // It's not a quoted text, so we will read until the next option
+ result = str.Read(ref pos, (ch) => ch != ',' && ch != ';');
+
+ return result;
+ }
+
+ internal static void SkipWhiteSpace(this string str, ref int pos)
+ {
+ if (pos >= str.Length)
+ return;
+
+ while (pos < str.Length && char.IsWhiteSpace(str[pos]))
+ pos++;
+ }
+
+ internal static string TrimAndLower(this string str)
+ {
+ if (str == null)
+ return null;
+
+ char[] buffer = new char[str.Length];
+ int length = 0;
+
+ for (int i = 0; i < str.Length; ++i)
+ {
+ char ch = str[i];
+ if (!char.IsWhiteSpace(ch) && !char.IsControl(ch))
+ buffer[length++] = char.ToLowerInvariant(ch);
+ }
+
+ return new string(buffer, 0, length);
+ }
+
+ internal static char? Peek(this string str, int pos)
+ {
+ if (pos < 0 || pos >= str.Length)
+ return null;
+
+ return str[pos];
+ }
+
+ #endregion
+
+ #region Specialized String Parsers
+
+ //public, max-age=2592000
+ internal static List ParseOptionalHeader(this string str)
+ {
+ List result = new List();
+
+ if (str == null)
+ return result;
+
+ int idx = 0;
+
+ // process the rest of the text
+ while (idx < str.Length)
+ {
+ // Read key
+ string key = str.Read(ref idx, (ch) => ch != '=' && ch != ',').TrimAndLower();
+ HeaderValue qp = new HeaderValue(key);
+
+ if (str[idx - 1] == '=')
+ qp.Value = str.ReadPossibleQuotedText(ref idx);
+
+ result.Add(qp);
+ }
+
+ return result;
+ }
+
+ //deflate, gzip, x-gzip, identity, *;q=0
+ internal static List ParseQualityParams(this string str)
+ {
+ List result = new List();
+
+ if (str == null)
+ return result;
+
+ int idx = 0;
+ while (idx < str.Length)
+ {
+ string key = str.Read(ref idx, (ch) => ch != ',' && ch != ';').TrimAndLower();
+
+ HeaderValue qp = new HeaderValue(key);
+
+ if (str[idx - 1] == ';')
+ {
+ str.Read(ref idx, '=', false);
+ qp.Value = str.Read(ref idx, ',');
+ }
+
+ result.Add(qp);
+ }
+
+ return result;
+ }
+
+ #endregion
+
+ #region Buffer Filling
+
+ ///
+ /// Will fill the entire buffer from the stream. Will throw an exception when the underlying stream is closed.
+ ///
+ public static void ReadBuffer(this Stream stream, byte[] buffer)
+ {
+ int count = 0;
+
+ do
+ {
+ int read = stream.Read(buffer, count, buffer.Length - count);
+
+ if (read <= 0)
+ throw ExceptionHelper.ServerClosedTCPStream();
+
+ count += read;
+ } while (count < buffer.Length);
+ }
+
+ public static void ReadBuffer(this Stream stream, byte[] buffer, int length)
+ {
+ int count = 0;
+
+ do
+ {
+ int read = stream.Read(buffer, count, length - count);
+
+ if (read <= 0)
+ throw ExceptionHelper.ServerClosedTCPStream();
+
+ count += read;
+ } while (count < length);
+ }
+
+ #endregion
+
+ #region BufferPoolMemoryStream
+
+ public static void WriteString(this BufferPoolMemoryStream ms, string str)
+ {
+ var byteCount = Encoding.UTF8.GetByteCount(str);
+ byte[] buffer = BufferPool.Get(byteCount, true);
+ Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0);
+ ms.Write(buffer, 0, byteCount);
+ BufferPool.Release(buffer);
+ }
+
+ public static void WriteLine(this BufferPoolMemoryStream ms)
+ {
+ ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
+ }
+
+ public static void WriteLine(this BufferPoolMemoryStream ms, string str)
+ {
+ ms.WriteString(str);
+ ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
+ }
+
+ #endregion
+
+#if NET_STANDARD_2_0 || NETFX_CORE || NET_4_6
+ public static void Clear(this System.Collections.Concurrent.ConcurrentQueue queue)
+ {
+ T result;
+ while (queue.TryDequeue(out result))
+ ;
+ }
+#endif
+ }
+
+ public static class ExceptionHelper
+ {
+ public static Exception ServerClosedTCPStream()
+ {
+ return new Exception("TCP Stream closed unexpectedly by the remote server");
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d37e2a4c0cc8317149074334665f33ba284c41bb
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Extensions.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 3ed17defed57cb64697daf74b5bb5873
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs b/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b7209f2126066e39f770b48596857b51901f38c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs
@@ -0,0 +1,409 @@
+// Based on https://github.com/nickgravelyn/UnityToolbag/blob/master/Future/Future.cs
+/*
+ * The MIT License (MIT)
+
+Copyright (c) 2017, Nick Gravelyn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+ * */
+
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Futures
+{
+ ///
+ /// Describes the state of a future.
+ ///
+ public enum FutureState
+ {
+ ///
+ /// The future hasn't begun to resolve a value.
+ ///
+ Pending,
+
+ ///
+ /// The future is working on resolving a value.
+ ///
+ Processing,
+
+ ///
+ /// The future has a value ready.
+ ///
+ Success,
+
+ ///
+ /// The future failed to resolve a value.
+ ///
+ Error
+ }
+
+ ///
+ /// Defines the interface of an object that can be used to track a future value.
+ ///
+ /// The type of object being retrieved.
+ public interface IFuture
+ {
+ ///
+ /// Gets the state of the future.
+ ///
+ FutureState state { get; }
+
+ ///
+ /// Gets the value if the State is Success.
+ ///
+ T value { get; }
+
+ ///
+ /// Gets the failure exception if the State is Error.
+ ///
+ Exception error { get; }
+
+ ///
+ /// Adds a new callback to invoke when an intermediate result is known.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ IFuture OnItem(FutureValueCallback callback);
+
+ ///
+ /// Adds a new callback to invoke if the future value is retrieved successfully.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ IFuture OnSuccess(FutureValueCallback callback);
+
+ ///
+ /// Adds a new callback to invoke if the future has an error.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ IFuture OnError(FutureErrorCallback callback);
+
+ ///
+ /// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ IFuture OnComplete(FutureCallback callback);
+ }
+
+ ///
+ /// Defines the signature for callbacks used by the future.
+ ///
+ /// The future.
+ public delegate void FutureCallback(IFuture future);
+
+ public delegate void FutureValueCallback(T value);
+ public delegate void FutureErrorCallback(Exception error);
+
+ ///
+ /// An implementation of that can be used internally by methods that return futures.
+ ///
+ ///
+ /// Methods should always return the interface when calling code requests a future.
+ /// This class is intended to be constructed internally in the method to provide a simple implementation of
+ /// the interface. By returning the interface instead of the class it ensures the implementation can change
+ /// later on if requirements change, without affecting the calling code.
+ ///
+ /// The type of object being retrieved.
+ public class Future : IFuture
+ {
+ private volatile FutureState _state;
+ private T _value;
+ private Exception _error;
+ private Func _processFunc;
+
+ private readonly List> _itemCallbacks = new List>();
+ private readonly List> _successCallbacks = new List>();
+ private readonly List _errorCallbacks = new List();
+ private readonly List> _complationCallbacks = new List>();
+
+ ///
+ /// Gets the state of the future.
+ ///
+ public FutureState state { get { return _state; } }
+
+ ///
+ /// Gets the value if the State is Success.
+ ///
+ public T value
+ {
+ get
+ {
+ if (_state != FutureState.Success && _state != FutureState.Processing)
+ {
+ throw new InvalidOperationException("value is not available unless state is Success or Processing.");
+ }
+
+ return _value;
+ }
+ }
+
+ ///
+ /// Gets the failure exception if the State is Error.
+ ///
+ public Exception error
+ {
+ get
+ {
+ if (_state != FutureState.Error)
+ {
+ throw new InvalidOperationException("error is not available unless state is Error.");
+ }
+
+ return _error;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Future()
+ {
+ _state = FutureState.Pending;
+ }
+
+ public IFuture OnItem(FutureValueCallback callback)
+ {
+ if (_state < FutureState.Success && !_itemCallbacks.Contains(callback))
+ _itemCallbacks.Add(callback);
+
+ return this;
+ }
+
+ ///
+ /// Adds a new callback to invoke if the future value is retrieved successfully.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ public IFuture OnSuccess(FutureValueCallback callback)
+ {
+ if (_state == FutureState.Success)
+ {
+ callback(this.value);
+ }
+ else if (_state != FutureState.Error && !_successCallbacks.Contains(callback))
+ {
+ _successCallbacks.Add(callback);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Adds a new callback to invoke if the future has an error.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ public IFuture OnError(FutureErrorCallback callback)
+ {
+ if (_state == FutureState.Error)
+ {
+ callback(this.error);
+ }
+ else if (_state != FutureState.Success && !_errorCallbacks.Contains(callback))
+ {
+ _errorCallbacks.Add(callback);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Adds a new callback to invoke if the future value is retrieved successfully or has an error.
+ ///
+ /// The callback to invoke.
+ /// The future so additional calls can be chained together.
+ public IFuture OnComplete(FutureCallback callback)
+ {
+ if (_state == FutureState.Success || _state == FutureState.Error)
+ {
+ callback(this);
+ }
+ else
+ {
+ if (!_complationCallbacks.Contains(callback))
+ _complationCallbacks.Add(callback);
+ }
+
+ return this;
+ }
+
+#pragma warning disable 1998
+
+ ///
+ /// Begins running a given function on a background thread to resolve the future's value, as long
+ /// as it is still in the Pending state.
+ ///
+ /// The function that will retrieve the desired value.
+ public IFuture Process(Func func)
+ {
+ if (_state != FutureState.Pending)
+ {
+ throw new InvalidOperationException("Cannot process a future that isn't in the Pending state.");
+ }
+
+ BeginProcess();
+ _processFunc = func;
+
+#if NETFX_CORE
+#pragma warning disable 4014
+ Windows.System.Threading.ThreadPool.RunAsync(ThreadFunc);
+#pragma warning restore 4014
+#else
+ System.Threading.ThreadPool.QueueUserWorkItem(ThreadFunc);
+#endif
+
+ return this;
+ }
+
+ private
+#if NETFX_CORE
+ async
+#endif
+ void ThreadFunc(object param)
+ {
+ try
+ {
+ // Directly call the Impl version to avoid the state validation of the public method
+ AssignImpl(_processFunc());
+ }
+ catch (Exception e)
+ {
+ // Directly call the Impl version to avoid the state validation of the public method
+ FailImpl(e);
+ }
+ finally
+ {
+ _processFunc = null;
+ }
+ }
+
+#pragma warning restore 1998
+
+ ///
+ /// Allows manually assigning a value to a future, as long as it is still in the pending state.
+ ///
+ ///
+ /// There are times where you may not need to do background processing for a value. For example,
+ /// you may have a cache of values and can just hand one out. In those cases you still want to
+ /// return a future for the method signature, but can just call this method to fill in the future.
+ ///
+ /// The value to assign the future.
+ public void Assign(T value)
+ {
+ if (_state != FutureState.Pending && _state != FutureState.Processing)
+ {
+ throw new InvalidOperationException("Cannot assign a value to a future that isn't in the Pending or Processing state.");
+ }
+
+ AssignImpl(value);
+ }
+
+ public void BeginProcess(T initialItem = default(T))
+ {
+ _state = FutureState.Processing;
+ _value = initialItem;
+ }
+
+ public void AssignItem(T value)
+ {
+ _value = value;
+ _error = null;
+
+ foreach (var callback in _itemCallbacks)
+ callback(this.value);
+ }
+
+ public void Finish()
+ {
+ _state = FutureState.Success;
+
+ FlushSuccessCallbacks();
+ }
+
+ ///
+ /// Allows manually failing a future, as long as it is still in the pending state.
+ ///
+ ///
+ /// As with the Assign method, there are times where you may know a future value is a failure without
+ /// doing any background work. In those cases you can simply fail the future manually and return it.
+ ///
+ /// The exception to use to fail the future.
+ public void Fail(Exception error)
+ {
+ if (_state != FutureState.Pending && _state != FutureState.Processing)
+ {
+ throw new InvalidOperationException("Cannot fail future that isn't in the Pending or Processing state.");
+ }
+
+ FailImpl(error);
+ }
+
+ private void AssignImpl(T value)
+ {
+ _value = value;
+ _error = null;
+ _state = FutureState.Success;
+
+ FlushSuccessCallbacks();
+ }
+
+ private void FailImpl(Exception error)
+ {
+ _value = default(T);
+ _error = error;
+ _state = FutureState.Error;
+
+ FlushErrorCallbacks();
+ }
+
+ private void FlushSuccessCallbacks()
+ {
+ foreach (var callback in _successCallbacks)
+ callback(this.value);
+
+ FlushComplationCallbacks();
+ }
+
+ private void FlushErrorCallbacks()
+ {
+ foreach (var callback in _errorCallbacks)
+ callback(this.error);
+
+ FlushComplationCallbacks();
+ }
+
+ private void FlushComplationCallbacks()
+ {
+ foreach (var callback in _complationCallbacks)
+ callback(this);
+ ClearCallbacks();
+ }
+
+ private void ClearCallbacks()
+ {
+ _itemCallbacks.Clear();
+ _successCallbacks.Clear();
+ _errorCallbacks.Clear();
+ _complationCallbacks.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d9ecc4634563890aaf4ac963953573f264f29792
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Future.cs.meta
@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: a9009e6f5e86bd5459d443846e42be9e
+timeCreated: 1515175777
+licenseType: Store
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs b/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1b0dd3849f6d7fa8db3dd51306ef45ddf06065f0
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs
@@ -0,0 +1,267 @@
+#if CSHARP_7_OR_LATER
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using BestHTTP.Logger;
+using UnityEngine;
+
+namespace BestHTTP
+{
+ public sealed class AsyncHTTPException : Exception
+ {
+ ///
+ /// Status code of the server's response.
+ ///
+ public int StatusCode;
+
+ ///
+ /// Content sent by the server.
+ ///
+ public string Content;
+
+ public AsyncHTTPException(string message)
+ : base(message)
+ {
+ }
+
+ public AsyncHTTPException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public AsyncHTTPException(int statusCode, string message, string content)
+ :base(message)
+ {
+ this.StatusCode = statusCode;
+ this.Content = content;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("StatusCode: {0}, Message: {1}, Content: {2}, StackTrace: {3}", this.StatusCode, this.Message, this.Content, this.StackTrace);
+ }
+ }
+
+ public static class HTTPRequestAsyncExtensions
+ {
+ public static Task GetHTTPResponseAsync(this HTTPRequest request, CancellationToken token = default)
+ {
+ return CreateTask(request, token, (req, resp, tcs) =>
+ {
+ switch (req.State)
+ {
+ // The request finished without any problem.
+ case HTTPRequestStates.Finished:
+ tcs.TrySetResult(resp);
+ break;
+
+ // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
+ case HTTPRequestStates.Error:
+ VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
+
+ tcs.TrySetException(CreateException("No Exception", null, req.Exception));
+ break;
+
+ // The request aborted, initiated by the user.
+ case HTTPRequestStates.Aborted:
+ VerboseLogging(request, "Request Aborted!");
+
+ tcs.TrySetCanceled();
+ break;
+
+ // Connecting to the server is timed out.
+ case HTTPRequestStates.ConnectionTimedOut:
+ VerboseLogging(request, "Connection Timed Out!");
+
+ tcs.TrySetException(CreateException("Connection Timed Out!"));
+ break;
+
+ // The request didn't finished in the given time.
+ case HTTPRequestStates.TimedOut:
+ VerboseLogging(request, "Processing the request Timed Out!");
+
+ tcs.TrySetException(CreateException("Processing the request Timed Out!"));
+ break;
+ }
+ });
+ }
+
+ public static Task GetAsStringAsync(this HTTPRequest request, CancellationToken token = default)
+ {
+ return CreateTask(request, token, (req, resp, tcs) =>
+ {
+ switch (req.State)
+ {
+ // The request finished without any problem.
+ case HTTPRequestStates.Finished:
+ if (resp.IsSuccess)
+ tcs.TrySetResult(resp.DataAsText);
+ else
+ tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
+ break;
+
+ // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
+ case HTTPRequestStates.Error:
+ VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
+
+ tcs.TrySetException(CreateException("No Exception", null, req.Exception));
+ break;
+
+ // The request aborted, initiated by the user.
+ case HTTPRequestStates.Aborted:
+ VerboseLogging(request, "Request Aborted!");
+
+ tcs.TrySetCanceled();
+ break;
+
+ // Connecting to the server is timed out.
+ case HTTPRequestStates.ConnectionTimedOut:
+ VerboseLogging(request, "Connection Timed Out!");
+
+ tcs.TrySetException(CreateException("Connection Timed Out!"));
+ break;
+
+ // The request didn't finished in the given time.
+ case HTTPRequestStates.TimedOut:
+ VerboseLogging(request, "Processing the request Timed Out!");
+
+ tcs.TrySetException(CreateException("Processing the request Timed Out!"));
+ break;
+ }
+ });
+ }
+
+ public static Task GetAsTexture2DAsync(this HTTPRequest request, CancellationToken token = default)
+ {
+ return CreateTask(request, token, (req, resp, tcs) =>
+ {
+ switch (req.State)
+ {
+ // The request finished without any problem.
+ case HTTPRequestStates.Finished:
+ if (resp.IsSuccess)
+ tcs.TrySetResult(resp.DataAsTexture2D);
+ else
+ tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
+ break;
+
+ // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
+ case HTTPRequestStates.Error:
+ VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
+
+ tcs.TrySetException(CreateException("No Exception", null, req.Exception));
+ break;
+
+ // The request aborted, initiated by the user.
+ case HTTPRequestStates.Aborted:
+ VerboseLogging(request, "Request Aborted!");
+
+ tcs.TrySetCanceled();
+ break;
+
+ // Connecting to the server is timed out.
+ case HTTPRequestStates.ConnectionTimedOut:
+ VerboseLogging(request, "Connection Timed Out!");
+
+ tcs.TrySetException(CreateException("Connection Timed Out!"));
+ break;
+
+ // The request didn't finished in the given time.
+ case HTTPRequestStates.TimedOut:
+ VerboseLogging(request, "Processing the request Timed Out!");
+
+ tcs.TrySetException(CreateException("Processing the request Timed Out!"));
+ break;
+ }
+ });
+ }
+
+ public static Task GetRawDataAsync(this HTTPRequest request, CancellationToken token = default)
+ {
+ return CreateTask(request, token, (req, resp, tcs) =>
+ {
+ switch (req.State)
+ {
+ // The request finished without any problem.
+ case HTTPRequestStates.Finished:
+ if (resp.IsSuccess)
+ tcs.TrySetResult(resp.Data);
+ else
+ tcs.TrySetException(CreateException("Request finished Successfully, but the server sent an error.", resp));
+ break;
+
+ // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
+ case HTTPRequestStates.Error:
+ VerboseLogging(request, "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception"));
+
+ tcs.TrySetException(CreateException("No Exception", null, req.Exception));
+ break;
+
+ // The request aborted, initiated by the user.
+ case HTTPRequestStates.Aborted:
+ VerboseLogging(request, "Request Aborted!");
+
+ tcs.TrySetCanceled();
+ break;
+
+ // Connecting to the server is timed out.
+ case HTTPRequestStates.ConnectionTimedOut:
+ VerboseLogging(request, "Connection Timed Out!");
+
+ tcs.TrySetException(CreateException("Connection Timed Out!"));
+ break;
+
+ // The request didn't finished in the given time.
+ case HTTPRequestStates.TimedOut:
+ VerboseLogging(request, "Processing the request Timed Out!");
+
+ tcs.TrySetException(CreateException("Processing the request Timed Out!"));
+ break;
+ }
+ });
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static Task CreateTask(HTTPRequest request, CancellationToken token, Action> callback)
+ {
+ HTTPManager.Setup();
+
+ var tcs = new TaskCompletionSource();
+
+ request.Callback = (req, resp) =>
+ {
+ if (token.IsCancellationRequested)
+ tcs.SetCanceled();
+ else
+ callback(req, resp, tcs);
+ };
+
+ if (token.CanBeCanceled)
+ token.Register((state) => (state as HTTPRequest)?.Abort(), request);
+
+ if (request.State == HTTPRequestStates.Initial)
+ request.Send();
+
+ return tcs.Task;
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static void VerboseLogging(HTTPRequest request, string str)
+ {
+ HTTPManager.Logger.Verbose("HTTPRequestAsyncExtensions", str, request.Context);
+ }
+
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+ public static Exception CreateException(string errorMessage, HTTPResponse resp = null, Exception ex = null)
+ {
+ if (resp != null)
+ return new AsyncHTTPException(resp.StatusCode, resp.Message, resp.DataAsText);
+ else if (ex != null)
+ return new AsyncHTTPException(ex.Message, ex);
+ else
+ return new AsyncHTTPException(errorMessage);
+ }
+ }
+}
+#endif
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..2f02d6ba5988e2039199a51357e1bf0672a24fdf
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HTTPRequestAsyncExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 875eda53b9b14784484c59463d205081
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a2a63ad9a44794d9f5a518f957785acb4e34da2b
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// Will parse a comma-separeted header value
+ ///
+ public sealed class HeaderParser : KeyValuePairList
+ {
+ public HeaderParser(string headerStr)
+ {
+ base.Values = Parse(headerStr);
+ }
+
+ private List Parse(string headerStr)
+ {
+ List result = new List();
+
+ int pos = 0;
+
+ try
+ {
+ while (pos < headerStr.Length)
+ {
+ HeaderValue current = new HeaderValue();
+
+ current.Parse(headerStr, ref pos);
+
+ result.Add(current);
+ }
+ }
+ catch(System.Exception ex)
+ {
+ HTTPManager.Logger.Exception("HeaderParser - Parse", headerStr, ex);
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..3fffbecad0f5079a22502a8c2b7e332d27563e59
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderParser.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 40ea771765c2ae549abc4c264e500b76
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d04a6427f4568697f9e8d8ea2a71a6151b7b00c0
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// Used in string parsers. Its Value is optional.
+ ///
+ public sealed class HeaderValue
+ {
+ #region Public Properties
+
+ public string Key { get; set; }
+ public string Value { get; set; }
+ public List Options { get; set; }
+
+ public bool HasValue { get { return !string.IsNullOrEmpty(this.Value); } }
+
+ #endregion
+
+ #region Constructors
+
+ public HeaderValue()
+ { }
+
+ public HeaderValue(string key)
+ {
+ this.Key = key;
+ }
+
+ #endregion
+
+ #region Public Helper Functions
+
+ public void Parse(string headerStr, ref int pos)
+ {
+ ParseImplementation(headerStr, ref pos, true);
+ }
+
+ public bool TryGetOption(string key, out HeaderValue option)
+ {
+ option = null;
+
+ if (Options == null || Options.Count == 0)
+ return false;
+
+ for (int i = 0; i < Options.Count; ++i)
+ if (String.Equals(Options[i].Key, key, StringComparison.OrdinalIgnoreCase))
+ {
+ option = Options[i];
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Private Helper Functions
+
+ private void ParseImplementation(string headerStr, ref int pos, bool isOptionIsAnOption)
+ {
+ // According to https://tools.ietf.org/html/rfc7234#section-5.2.1.1
+ // Max-Age has a form "max-age=5", but some (imgur.com specifically) sends it as "max-age:5"
+ string key = headerStr.Read(ref pos, (ch) => ch != ';' && ch != '=' && ch != ':' && ch != ',', true);
+ this.Key = key;
+
+ char? skippedChar = headerStr.Peek(pos - 1);
+ bool isValue = skippedChar == '=' || skippedChar == ':';
+ bool isOption = isOptionIsAnOption && skippedChar == ';';
+
+ while ((skippedChar != null && isValue || isOption) && pos < headerStr.Length)
+ {
+
+ if (isValue)
+ {
+ string value = headerStr.ReadPossibleQuotedText(ref pos);
+ this.Value = value;
+ }
+ else if (isOption)
+ {
+ HeaderValue option = new HeaderValue();
+ option.ParseImplementation(headerStr, ref pos, false);
+
+ if (this.Options == null)
+ this.Options = new List();
+
+ this.Options.Add(option);
+ }
+
+ if (!isOptionIsAnOption)
+ return;
+
+ skippedChar = headerStr.Peek(pos - 1);
+ isValue = skippedChar == '=';
+ isOption = isOptionIsAnOption && skippedChar == ';';
+ }
+ }
+
+ #endregion
+
+ #region Overrides
+
+ public override string ToString()
+ {
+ if (this.Options != null && this.Options.Count > 0)
+ {
+ StringBuilder sb = new StringBuilder(4);
+ sb.Append(Key);
+ sb.Append("=");
+ sb.Append(Value);
+
+ foreach(var option in Options)
+ {
+ sb.Append(";");
+ sb.Append(option.ToString());
+ }
+
+ return sb.ToString();
+ }
+ else if (!string.IsNullOrEmpty(Value))
+ return Key + '=' + Value;
+ else
+ return Key;
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..b2c250c0a2ce6c389dc3b7a80b8fdf3d1bfb4aba
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeaderValue.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 1465d4183951bf64dad0047b7c6e7243
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs b/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3446d1b6effb07a4fa067430ba25f1250a302759
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace BestHTTP.Extensions
+{
+ public interface IHeartbeat
+ {
+ void OnHeartbeatUpdate(TimeSpan dif);
+ }
+
+ ///
+ /// A manager class that can handle subscribing and unsubscribeing in the same update.
+ ///
+ public sealed class HeartbeatManager
+ {
+ private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
+
+ private List Heartbeats = new List();
+ private IHeartbeat[] UpdateArray;
+ private DateTime LastUpdate = DateTime.MinValue;
+
+ public void Subscribe(IHeartbeat heartbeat)
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ if (!Heartbeats.Contains(heartbeat))
+ Heartbeats.Add(heartbeat);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+
+ public void Unsubscribe(IHeartbeat heartbeat)
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ Heartbeats.Remove(heartbeat);
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+
+ public void Update()
+ {
+ if (LastUpdate == DateTime.MinValue)
+ LastUpdate = DateTime.UtcNow;
+ else
+ {
+ TimeSpan dif = DateTime.UtcNow - LastUpdate;
+ LastUpdate = DateTime.UtcNow;
+
+ int count = 0;
+
+ rwLock.EnterReadLock();
+ try
+ {
+ if (UpdateArray == null || UpdateArray.Length < Heartbeats.Count)
+ Array.Resize(ref UpdateArray, Heartbeats.Count);
+
+ Heartbeats.CopyTo(0, UpdateArray, 0, Heartbeats.Count);
+
+ count = Heartbeats.Count;
+ }
+ finally
+ {
+ rwLock.ExitReadLock();
+ }
+
+ for (int i = 0; i < count; ++i)
+ {
+ try
+ {
+ UpdateArray[i].OnHeartbeatUpdate(dif);
+ }
+ catch
+ { }
+ }
+ }
+ }
+
+ public void Clear()
+ {
+ rwLock.EnterWriteLock();
+ try
+ {
+ Heartbeats.Clear();
+ }
+ finally
+ {
+ rwLock.ExitWriteLock();
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..c9d056e730e789bf8bda2495b949362a4c192d2e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/HeartbeatManager.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 6c610eeb580ec8e4c90789edb631cc76
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs b/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c18c18b0d9620a68fd4bd1a0ea3cb0d6c8f5f910
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// Base class for specialized parsers
+ ///
+ public class KeyValuePairList
+ {
+ public List Values { get; protected set; }
+
+ public bool TryGet(string valueKeyName, out HeaderValue @param)
+ {
+ @param = null;
+ for (int i = 0; i < Values.Count; ++i)
+ if (string.CompareOrdinal(Values[i].Key, valueKeyName) == 0)
+ {
+ @param = Values[i];
+ return true;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5c6d9fc3f564c09d7cba76ffd98105882fd17572
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/KeyValuePairList.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 834cc34ef3418894589ef2e8389ff8c0
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs b/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d9576c3c98396aaf33455d4efe1941488151ac0d
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs
@@ -0,0 +1,32 @@
+namespace BestHTTP.Extensions
+{
+ public sealed class PeekableIncomingSegmentStream : BufferSegmentStream
+ {
+ private int peek_listIdx;
+ private int peek_pos;
+
+ public void BeginPeek()
+ {
+ peek_listIdx = 0;
+ peek_pos = base.bufferList.Count > 0 ? base.bufferList[0].Offset : 0;
+ }
+
+ public int PeekByte()
+ {
+ if (base.bufferList.Count == 0)
+ return -1;
+
+ var segment = base.bufferList[this.peek_listIdx];
+ if (peek_pos >= segment.Offset + segment.Count)
+ {
+ if (base.bufferList.Count <= this.peek_listIdx + 1)
+ return -1;
+
+ segment = base.bufferList[++this.peek_listIdx];
+ this.peek_pos = segment.Offset;
+ }
+
+ return segment.Data[this.peek_pos++];
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..828d1fbdd29ce2135f83cbb23174c2ab03608b2c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/PeekableIncomingSegmentStream.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e917d28b8c5afd64fa0373d88a939bfe
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs b/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..47c88e6d15351b51ceb50bbcc4cd8a6a03305bbc
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs
@@ -0,0 +1,183 @@
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.IO;
+
+namespace BestHTTP.Extensions
+{
+ public sealed class ReadOnlyBufferedStream : Stream
+ {
+ Stream stream;
+ public const int READBUFFER = 8192;
+ byte[] buf;
+ int available = 0;
+ int pos = 0;
+
+ public ReadOnlyBufferedStream(Stream nstream)
+ :this(nstream, READBUFFER)
+ {
+ }
+
+ public ReadOnlyBufferedStream(Stream nstream, int bufferSize)
+ {
+ stream = nstream;
+ buf = BufferPool.Get(bufferSize, true);
+ }
+
+ public override int Read(byte[] buffer, int offset, int size)
+ {
+ if (size <= available)
+ {
+ Array.Copy(buf, pos, buffer, offset, size);
+ available -= size;
+ pos += size;
+ return size;
+ }
+ else
+ {
+ int readcount = 0;
+
+ if (available > 0)
+ {
+ Array.Copy(buf, pos, buffer, offset, available);
+ offset += available;
+ readcount += available;
+ available = 0;
+ pos = 0;
+ }
+
+ try
+ {
+ available = stream.Read(buf, 0, buf.Length);
+ pos = 0;
+ }
+ catch (Exception ex)
+ {
+ if (readcount > 0)
+ {
+ return readcount;
+ }
+
+ throw (ex);
+ }
+
+ if (available < 1)
+ {
+ if (readcount > 0)
+ {
+ return readcount;
+ }
+
+ return available;
+ }
+ else
+ {
+ int toread = size - readcount;
+ if (toread <= available)
+ {
+ Array.Copy(buf, pos, buffer, offset, toread);
+ available -= toread;
+ pos += toread;
+ readcount += toread;
+ return readcount;
+ }
+ else
+ {
+ Array.Copy(buf, pos, buffer, offset, available);
+ offset += available;
+ readcount += available;
+ pos = 0;
+ available = 0;
+ }
+ }
+
+ return readcount;
+ }
+ }
+
+ public override int ReadByte()
+ {
+ if (available > 0)
+ {
+ available -= 1;
+ pos += 1;
+ return buf[pos - 1];
+ }
+ else
+ {
+ try
+ {
+ available = stream.Read(buf, 0, buf.Length);
+ pos = 0;
+ }
+ catch
+ {
+ return -1;
+ }
+ if (available < 1)
+ {
+ return -1;
+ }
+ else
+ {
+ available -= 1;
+ pos += 1;
+ return buf[pos - 1];
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && buf != null)
+ BufferPool.Release(buf);
+
+ buf = null;
+ }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override bool CanWrite
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override long Length
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public override long Position
+ {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..1749a3f6e11059601371475dff3032fe9eec4626
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/ReadOnlyBufferedStream.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: e6b9a4ff6813b4f4b9f2ffa453c3fccc
+timeCreated: 1543513046
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs b/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e445d41c3a55b146e50e1726c5f1a75dbbe59e13
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs
@@ -0,0 +1,153 @@
+using BestHTTP.PlatformSupport.Memory;
+using System;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// Wrapper of multiple streams. Writes and reads are both supported. Read goes trough all the streams.
+ ///
+ public sealed class StreamList : System.IO.Stream
+ {
+ private System.IO.Stream[] Streams;
+ private int CurrentIdx;
+
+ public StreamList(params System.IO.Stream[] streams)
+ {
+ this.Streams = streams;
+ this.CurrentIdx = 0;
+ }
+
+ public override bool CanRead
+ {
+ get
+ {
+ if (CurrentIdx >= Streams.Length)
+ return false;
+ return Streams[CurrentIdx].CanRead;
+ }
+ }
+
+ public override bool CanSeek { get { return false; } }
+
+ public override bool CanWrite
+ {
+ get
+ {
+ if (CurrentIdx >= Streams.Length)
+ return false;
+ return Streams[CurrentIdx].CanWrite;
+ }
+ }
+
+ public override void Flush()
+ {
+ if (CurrentIdx >= Streams.Length)
+ return;
+
+ // We have to call the flush to all previous streams, as we may advanced the CurrentIdx
+ for (int i = 0; i <= CurrentIdx; ++i)
+ Streams[i].Flush();
+ }
+
+ public override long Length
+ {
+ get
+ {
+ if (CurrentIdx >= Streams.Length)
+ return 0;
+
+ long length = 0;
+ for (int i = 0; i < Streams.Length; ++i)
+ length += Streams[i].Length;
+
+ return length;
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (CurrentIdx >= Streams.Length)
+ return -1;
+
+ int readCount = Streams[CurrentIdx].Read(buffer, offset, count);
+
+ while (readCount < count && ++CurrentIdx < Streams.Length)
+ {
+ // Dispose previous stream
+ try
+ {
+ Streams[CurrentIdx - 1].Dispose();
+ Streams[CurrentIdx - 1] = null;
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("StreamList", "Dispose", ex);
+ }
+
+ readCount += Streams[CurrentIdx].Read(buffer, offset + readCount, count - readCount);
+ }
+
+ return readCount;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (CurrentIdx >= Streams.Length)
+ return;
+
+ Streams[CurrentIdx].Write(buffer, offset, count);
+ }
+
+ public void Write(string str)
+ {
+ byte[] bytes = str.GetASCIIBytes();
+
+ this.Write(bytes, 0, bytes.Length);
+ BufferPool.Release(bytes);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ for (int i = 0; i < Streams.Length; ++i)
+ if (Streams[i] != null)
+ {
+ try
+ {
+ Streams[i].Dispose();
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("StreamList", "Dispose", ex);
+ }
+ }
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotImplementedException("Position get");
+ }
+ set
+ {
+ throw new NotImplementedException("Position set");
+ }
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ if (CurrentIdx >= Streams.Length)
+ return 0;
+
+ return Streams[CurrentIdx].Seek(offset, origin);
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException("SetLength");
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..fb371b7dee6565004152fa2b4b8f6a995c0c8816
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/StreamList.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d9a35feccd354604bb0037b7e66a3e22
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs b/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f625c163fe3b21285e7987886233c91131ca2509
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+
+namespace BestHTTP.Extensions
+{
+ public
+#if CSHARP_7_OR_LATER
+ readonly
+#endif
+ struct TimerData
+ {
+ public readonly DateTime Created;
+ public readonly TimeSpan Interval;
+ public readonly object Context;
+
+ public readonly Func OnTimer;
+
+ public bool IsOnTime(DateTime now)
+ {
+ return now >= this.Created + this.Interval;
+ }
+
+ public TimerData(TimeSpan interval, object context, Func onTimer)
+ {
+ this.Created = DateTime.Now;
+ this.Interval = interval;
+ this.Context = context;
+ this.OnTimer = onTimer;
+ }
+
+ ///
+ /// Create a new TimerData but the Created field will be set to the current time.
+ ///
+ public TimerData CreateNew()
+ {
+ return new TimerData(this.Interval, this.Context, this.OnTimer);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("[TimerData Created: {0}, Interval: {1}, IsOnTime: {2}]", this.Created.ToString(System.Globalization.CultureInfo.InvariantCulture), this.Interval, this.IsOnTime(DateTime.Now));
+ }
+ }
+
+ public static class Timer
+ {
+ private static List Timers = new List();
+
+ public static void Add(TimerData timer)
+ {
+ Timers.Add(timer);
+ }
+
+ internal static void Process()
+ {
+ if (Timers.Count == 0)
+ return;
+
+ DateTime now = DateTime.Now;
+
+ for (int i = 0; i < Timers.Count; ++i)
+ {
+ TimerData timer = Timers[i];
+
+ if (timer.IsOnTime(now))
+ {
+ bool repeat = timer.OnTimer(now, timer.Context);
+
+ if (repeat)
+ Timers[i] = timer.CreateNew();
+ else
+ Timers.RemoveAt(i--);
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..7534a5f597d66f5130d372eb4fc8f813bf09c85e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/Timer.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 6b79070540cbf6f42b89c74c2fb978f5
+timeCreated: 1571210041
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs b/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3af73ee79a3c0fd150428717d6b7cee848ffadd4
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// Used for parsing WWW-Authenticate headers:
+ /// Digest realm="my realm", nonce="4664b327a2963503ba58bbe13ad672c0", qop=auth, opaque="f7e38bdc1c66fce214f9019ffe43117c"
+ ///
+ public sealed class WWWAuthenticateHeaderParser : KeyValuePairList
+ {
+ public WWWAuthenticateHeaderParser(string headerValue)
+ {
+ Values = ParseQuotedHeader(headerValue);
+ }
+
+ private List ParseQuotedHeader(string str)
+ {
+ List result = new List();
+
+ if (str != null)
+ {
+
+ int idx = 0;
+
+ // Read Type (Basic|Digest)
+ string type = str.Read(ref idx, (ch) => !char.IsWhiteSpace(ch) && !char.IsControl(ch)).TrimAndLower();
+ result.Add(new HeaderValue(type));
+
+ // process the rest of the text
+ while (idx < str.Length)
+ {
+ // Read key
+ string key = str.Read(ref idx, '=').TrimAndLower();
+ HeaderValue qp = new HeaderValue(key);
+
+ // Skip any white space
+ str.SkipWhiteSpace(ref idx);
+
+ qp.Value = str.ReadPossibleQuotedText(ref idx);
+
+ result.Add(qp);
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d26dda632aab30e148e60d71d35c13124357d83c
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/WWWAuthenticateHeaderParser.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 4cba91161fe643c419eedafefdf1f959
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs b/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a6db7a2290236dede736cbee4f9f8b4b75008572
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs
@@ -0,0 +1,82 @@
+using BestHTTP.PlatformSupport.Memory;
+using System;
+using System.IO;
+
+namespace BestHTTP.Extensions
+{
+ ///
+ /// A custom buffer stream implementation that will not close the underlying stream.
+ ///
+ public sealed class WriteOnlyBufferedStream : Stream
+ {
+ public override bool CanRead { get { return false; } }
+ public override bool CanSeek { get { return false; } }
+ public override bool CanWrite { get { return true; } }
+ public override long Length { get { return this.buffer.Length; } }
+
+ public override long Position { get { return this._position; } set { throw new NotImplementedException("Position set"); } }
+ private int _position;
+
+ private byte[] buffer;
+ private Stream stream;
+
+ public WriteOnlyBufferedStream(Stream stream, int bufferSize)
+ {
+ this.stream = stream;
+
+ this.buffer = BufferPool.Get(bufferSize, true);
+ this._position = 0;
+ }
+
+ public override void Flush()
+ {
+ if (this._position > 0)
+ {
+ this.stream.Write(this.buffer, 0, this._position);
+ this.stream.Flush();
+
+ //if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ // HTTPManager.Logger.Information("WriteOnlyBufferedStream", string.Format("Flushed {0:N0} bytes", this._position));
+
+ this._position = 0;
+ }
+ }
+
+ public override void Write(byte[] bufferFrom, int offset, int count)
+ {
+ while (count > 0)
+ {
+ int writeCount = Math.Min(count, this.buffer.Length - this._position);
+ Array.Copy(bufferFrom, offset, this.buffer, this._position, writeCount);
+
+ this._position += writeCount;
+ offset += writeCount;
+ count -= writeCount;
+
+ if (this._position == this.buffer.Length)
+ this.Flush();
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return 0;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return 0;
+ }
+
+ public override void SetLength(long value) { }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing && this.buffer != null)
+ BufferPool.Release(this.buffer);
+ this.buffer = null;
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs.meta b/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..1c4cc6ea8b9418938d08d581a1f34d4f865f7684
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Extensions/WriteOnlyBufferedStream.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2df120a767773ee478acf872b86ae326
+timeCreated: 1530027551
+licenseType: Store
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms.meta b/Assets/Plugins/BestHTTP/Source/Forms.meta
new file mode 100644
index 0000000000000000000000000000000000000000..dbba7c22dcd76e351bff816b8409adfb45b6f501
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 867abf3a1ab01c84681b6c85becf6b6d
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5ff5e7959952af7c84c27e2bbd00b87833d237f4
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Forms
+{
+ ///
+ /// This class represents a HTTP Form's field.
+ ///
+ public class HTTPFieldData
+ {
+ ///
+ /// The form's field.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Filename of the field. Optional.
+ ///
+ public string FileName { get; set; }
+
+ ///
+ /// Mime-type of the field. Optional
+ ///
+ public string MimeType { get; set; }
+
+ ///
+ /// Encoding of the data. Optional
+ ///
+ public Encoding Encoding { get; set; }
+
+ ///
+ /// The field's textual data.
+ ///
+ public string Text { get; set; }
+
+ ///
+ /// The field's binary data.
+ ///
+ public byte[] Binary { get; set; }
+
+ ///
+ /// Will return with the binary data, or if it's not present the textual data will be decoded to binary.
+ ///
+ public byte[] Payload
+ {
+ get
+ {
+ if (Binary != null)
+ return Binary;
+
+ if (Encoding == null)
+ Encoding = Encoding.UTF8;
+
+ return Binary = Encoding.GetBytes(Text);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs.meta b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..27f683ca86db3a1923b5d71d9636907f3df292df
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFieldData.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: bdf9d0d59653fa8418a86f22360ff91e
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..949dfd744b0db26569d613efcceaab3d6ad54b76
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Forms
+{
+ ///
+ /// Base class of a concrete implementation. Don't use it to create a form, use instead one of the already wrote implementation(HTTPMultiPartForm, HTTPUrlEncodedForm), or create a new one by inheriting from this base class.
+ ///
+ public class HTTPFormBase
+ {
+ const int LongLength = 256;
+
+ #region Properties
+
+ ///
+ /// A list that holds the form's fields.
+ ///
+ public List Fields { get; set; }
+
+ ///
+ /// Returns true if the Fields has no element.
+ ///
+ public bool IsEmpty { get { return Fields == null || Fields.Count == 0; } }
+
+ ///
+ /// True if new fields has been added to our list.
+ ///
+ public bool IsChanged { get; protected set; }
+
+ ///
+ /// True if there are at least one form-field with binary data.
+ ///
+ public bool HasBinary { get; protected set; }
+
+ ///
+ /// True if there are at least one form-field with a long textual data.
+ ///
+ public bool HasLongValue { get; protected set; }
+
+ #endregion
+
+ #region Field Management
+
+ public void AddBinaryData(string fieldName, byte[] content)
+ {
+ AddBinaryData(fieldName, content, null, null);
+ }
+
+ public void AddBinaryData(string fieldName, byte[] content, string fileName)
+ {
+ AddBinaryData(fieldName, content, fileName, null);
+ }
+
+ public void AddBinaryData(string fieldName, byte[] content, string fileName, string mimeType)
+ {
+ if (Fields == null)
+ Fields = new List();
+
+ HTTPFieldData field = new HTTPFieldData();
+ field.Name = fieldName;
+
+ if (fileName == null)
+ field.FileName = fieldName + ".dat";
+ else
+ field.FileName = fileName;
+
+ if (mimeType == null)
+ field.MimeType = "application/octet-stream";
+ else
+ field.MimeType = mimeType;
+
+ field.Binary = content;
+
+ Fields.Add(field);
+
+ HasBinary = IsChanged = true;
+ }
+
+ public void AddField(string fieldName, string value)
+ {
+ AddField(fieldName, value, System.Text.Encoding.UTF8);
+ }
+
+ public void AddField(string fieldName, string value, System.Text.Encoding e)
+ {
+ if (Fields == null)
+ Fields = new List();
+
+ HTTPFieldData field = new HTTPFieldData();
+ field.Name = fieldName;
+ field.FileName = null;
+ if (e != null)
+ field.MimeType = "text/plain; charset=" + e.WebName;
+ field.Text = value;
+ field.Encoding = e;
+
+ Fields.Add(field);
+
+ IsChanged = true;
+
+ HasLongValue |= value.Length > LongLength;
+ }
+
+ #endregion
+
+ #region Virtual Functions
+
+ ///
+ /// It should 'clone' all the data from the given HTTPFormBase object.
+ /// Called after the form-implementation created.
+ ///
+ public virtual void CopyFrom(HTTPFormBase fields)
+ {
+ this.Fields = new List(fields.Fields);
+ this.IsChanged = true;
+
+ this.HasBinary = fields.HasBinary;
+ this.HasLongValue = fields.HasLongValue;
+ }
+
+ ///
+ /// Prepares the request to sending a form. It should set only the headers.
+ ///
+ public virtual void PrepareRequest(HTTPRequest request)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Prepares and returns with the form's raw data.
+ ///
+ public virtual byte[] GetData()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs.meta b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..88b2c9a40c74091d2fdd05f2957d4ad91e6e4287
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormBase.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 7d275ec92441f18449bffc98e08d44e8
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4f65f698d467504b4f1f670b824ac9303820cf2
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Forms
+{
+ public enum HTTPFormUsage
+ {
+ ///
+ /// The plugin will try to choose the best form sending method.
+ ///
+ Automatic,
+
+ ///
+ /// The plugin will use the Url-Encoded form sending.
+ ///
+ UrlEncoded,
+
+ ///
+ /// The plugin will use the Multipart form sending.
+ ///
+ Multipart
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs.meta b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..f23208ab57f87addbab9cb814aa7de7f813bbc62
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/HTTPFormUsage.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: fc8643409dcf53d4db0bf0f654ad8500
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/Implementations.meta b/Assets/Plugins/BestHTTP/Source/Forms/Implementations.meta
new file mode 100644
index 0000000000000000000000000000000000000000..f74232590091a261c8be37b5ef8c038208453f0a
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/Implementations.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: acc326f47252d77498d6489dce7ff997
+folderAsset: yes
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bc8071c5fe24109664076f5447210ff00f5c0415
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs
@@ -0,0 +1,78 @@
+using BestHTTP.Extensions;
+
+namespace BestHTTP.Forms
+{
+ ///
+ /// A HTTP Form implementation to send textual and binary values.
+ ///
+ public sealed class HTTPMultiPartForm : HTTPFormBase
+ {
+ #region Private Fields
+
+ ///
+ /// A random boundary generated in the constructor.
+ ///
+ private string Boundary;
+
+ ///
+ ///
+ ///
+ private byte[] CachedData;
+
+ #endregion
+
+ public HTTPMultiPartForm()
+ {
+ this.Boundary = "BestHTTP_HTTPMultiPartForm_" + this.GetHashCode().ToString("X");
+ }
+
+ #region IHTTPForm Implementation
+
+ public override void PrepareRequest(HTTPRequest request)
+ {
+ // Set up Content-Type header for the request
+ request.SetHeader("Content-Type", "multipart/form-data; boundary=" + Boundary);
+ }
+
+ public override byte[] GetData()
+ {
+ if (CachedData != null)
+ return CachedData;
+
+ using (var ms = new BufferPoolMemoryStream())
+ {
+ for (int i = 0; i < Fields.Count; ++i)
+ {
+ HTTPFieldData field = Fields[i];
+
+ // Set the boundary
+ ms.WriteLine("--" + Boundary);
+
+ // Set up Content-Disposition header to our form with the name
+ ms.WriteLine("Content-Disposition: form-data; name=\"" + field.Name + "\"" + (!string.IsNullOrEmpty(field.FileName) ? "; filename=\"" + field.FileName + "\"" : string.Empty));
+
+ // Set up Content-Type head for the form.
+ if (!string.IsNullOrEmpty(field.MimeType))
+ ms.WriteLine("Content-Type: " + field.MimeType);
+
+ ms.WriteLine();
+
+ // Write the actual data to the MemoryStream
+ ms.Write(field.Payload, 0, field.Payload.Length);
+
+ ms.Write(HTTPRequest.EOL, 0, HTTPRequest.EOL.Length);
+ }
+
+ // Write out the trailing boundary
+ ms.WriteLine("--" + Boundary + "--");
+
+ IsChanged = false;
+
+ // Set the RawData of our request
+ return CachedData = ms.ToArray();
+ }
+ }
+
+ #endregion
+ };
+}
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs.meta b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d223b1ffcd6d5d73048f244936862b9af195510b
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPMultiPartForm.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 5f9390c7b6f72b845805363b01dfbba9
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs
new file mode 100644
index 0000000000000000000000000000000000000000..82595b1b75012727dfbc4e9091d9f45ab58fc7b8
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace BestHTTP.Forms
+{
+ ///
+ /// A HTTP Form implementation to send textual values.
+ ///
+ public sealed class HTTPUrlEncodedForm : HTTPFormBase
+ {
+ private const int EscapeTreshold = 256;
+
+ private byte[] CachedData;
+
+ public override void PrepareRequest(HTTPRequest request)
+ {
+ request.SetHeader("Content-Type", "application/x-www-form-urlencoded");
+ }
+
+ public override byte[] GetData()
+ {
+ if (CachedData != null && !IsChanged)
+ return CachedData;
+
+ StringBuilder sb = new StringBuilder();
+
+ // Create a "field1=value1&field2=value2" formatted string
+ for (int i = 0; i < Fields.Count; ++i)
+ {
+ var field = Fields[i];
+
+ if (i > 0)
+ sb.Append("&");
+
+ sb.Append(EscapeString(field.Name));
+ sb.Append("=");
+
+ if (!string.IsNullOrEmpty(field.Text) || field.Binary == null)
+ sb.Append(EscapeString(field.Text));
+ else
+ // If forced to this form type with binary data, we will create a base64 encoded string from it.
+ sb.Append(Convert.ToBase64String(field.Binary, 0, field.Binary.Length));
+ }
+
+ IsChanged = false;
+ return CachedData = Encoding.UTF8.GetBytes(sb.ToString());
+ }
+
+ public static string EscapeString(string originalString)
+ {
+ if (originalString.Length < EscapeTreshold)
+ return Uri.EscapeDataString(originalString);
+ else
+ {
+ int loops = originalString.Length / EscapeTreshold;
+ StringBuilder sb = new StringBuilder(loops);
+
+ for (int i = 0; i <= loops; i++)
+ sb.Append(i < loops ?
+ Uri.EscapeDataString(originalString.Substring(EscapeTreshold * i, EscapeTreshold)) :
+ Uri.EscapeDataString(originalString.Substring(EscapeTreshold * i)));
+ return sb.ToString();
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs.meta b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..74123e87ea86ec53758f2d9affadafc6ad1dd92e
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/Forms/Implementations/HTTPUrlEncodedForm.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 023a00e6d3a980b4babe8c70a7d00088
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPManager.cs b/Assets/Plugins/BestHTTP/Source/HTTPManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..34b6b9a11ed02ba2a18a401e2784398c4fc57c25
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPManager.cs
@@ -0,0 +1,471 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+#if !BESTHTTP_DISABLE_CACHING
+using BestHTTP.Caching;
+#endif
+
+using BestHTTP.Core;
+using BestHTTP.Extensions;
+using BestHTTP.Logger;
+using BestHTTP.PlatformSupport.Memory;
+
+#if !BESTHTTP_DISABLE_COOKIES
+using BestHTTP.Cookies;
+#endif
+
+namespace BestHTTP
+{
+ public enum ShutdownTypes
+ {
+ Running,
+ Gentle,
+ Immediate
+ }
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ public delegate SecureProtocol.Org.BouncyCastle.Crypto.Tls.AbstractTlsClient TlsClientFactoryDelegate(HTTPRequest request, List protocols);
+#endif
+
+ ///
+ ///
+ ///
+ [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
+ public static partial class HTTPManager
+ {
+ // Static constructor. Setup default values
+ static HTTPManager()
+ {
+ MaxConnectionPerServer = 6;
+ KeepAliveDefaultValue = true;
+ MaxPathLength = 255;
+ MaxConnectionIdleTime = TimeSpan.FromSeconds(20);
+
+#if !BESTHTTP_DISABLE_COOKIES
+#if UNITY_WEBGL && !UNITY_EDITOR
+ // Under webgl when IsCookiesEnabled is true, it will set the withCredentials flag for the XmlHTTPRequest
+ // and that's different from the default behavior.
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
+ IsCookiesEnabled = false;
+#else
+ IsCookiesEnabled = true;
+#endif
+#endif
+
+ CookieJarSize = 10 * 1024 * 1024;
+ EnablePrivateBrowsing = false;
+ ConnectTimeout = TimeSpan.FromSeconds(20);
+ RequestTimeout = TimeSpan.FromSeconds(60);
+
+ // Set the default logger mechanism
+ logger = new BestHTTP.Logger.ThreadedLogger();
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ DefaultCertificateVerifyer = null;
+ UseAlternateSSLDefaultValue = true;
+#endif
+
+#if NETFX_CORE
+ IOService = new PlatformSupport.FileSystem.NETFXCOREIOService();
+//#elif UNITY_WEBGL && !UNITY_EDITOR
+// IOService = new PlatformSupport.FileSystem.WebGLIOService();
+#else
+ IOService = new PlatformSupport.FileSystem.DefaultIOService();
+#endif
+ }
+
+#if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
+ ///
+ /// HTTP/2 settings
+ ///
+ public static Connections.HTTP2.HTTP2PluginSettings HTTP2Settings = new Connections.HTTP2.HTTP2PluginSettings();
+#endif
+
+#region Global Options
+
+ ///
+ /// The maximum active TCP connections that the client will maintain to a server. Default value is 6. Minimum value is 1.
+ ///
+ public static byte MaxConnectionPerServer
+ {
+ get{ return maxConnectionPerServer; }
+ set
+ {
+ if (value <= 0)
+ throw new ArgumentOutOfRangeException("MaxConnectionPerServer must be greater than 0!");
+
+ bool isGrowing = value > maxConnectionPerServer;
+ maxConnectionPerServer = value;
+
+ // If the allowed connections per server is growing, go through all hosts and try to send out queueud requests.
+ if (isGrowing)
+ HostManager.TryToSendQueuedRequests();
+ }
+ }
+ private static byte maxConnectionPerServer;
+
+ ///
+ /// Default value of a HTTP request's IsKeepAlive value. Default value is true. If you make rare request to the server it should be changed to false.
+ ///
+ public static bool KeepAliveDefaultValue { get; set; }
+
+#if !BESTHTTP_DISABLE_CACHING
+ ///
+ /// Set to true, if caching is prohibited.
+ ///
+ public static bool IsCachingDisabled { get; set; }
+#endif
+
+ ///
+ /// How many time must be passed to destroy that connection after a connection finished its last request. Its default value is 20 seconds.
+ ///
+ public static TimeSpan MaxConnectionIdleTime { get; set; }
+
+#if !BESTHTTP_DISABLE_COOKIES
+ ///
+ /// Set to false to disable all Cookie. It's default value is true.
+ ///
+ public static bool IsCookiesEnabled { get; set; }
+#endif
+
+ ///
+ /// Size of the Cookie Jar in bytes. It's default value is 10485760 (10 MB).
+ ///
+ public static uint CookieJarSize { get; set; }
+
+ ///
+ /// If this property is set to true, then new cookies treated as session cookies and these cookies are not saved to disk. Its default value is false;
+ ///
+ public static bool EnablePrivateBrowsing { get; set; }
+
+ ///
+ /// Global, default value of the HTTPRequest's ConnectTimeout property. If set to TimeSpan.Zero or lower, no connect timeout logic is executed. Default value is 20 seconds.
+ ///
+ public static TimeSpan ConnectTimeout { get; set; }
+
+ ///
+ /// Global, default value of the HTTPRequest's Timeout property. Default value is 60 seconds.
+ ///
+ public static TimeSpan RequestTimeout { get; set; }
+
+ ///
+ /// By default the plugin will save all cache and cookie data under the path returned by Application.persistentDataPath.
+ /// You can assign a function to this delegate to return a custom root path to define a new path.
+ /// This delegate will be called on a non Unity thread!
+ ///
+ public static System.Func RootCacheFolderProvider { get; set; }
+
+#if !BESTHTTP_DISABLE_PROXY
+ ///
+ /// The global, default proxy for all HTTPRequests. The HTTPRequest's Proxy still can be changed per-request. Default value is null.
+ ///
+ public static Proxy Proxy { get; set; }
+#endif
+
+ ///
+ /// Heartbeat manager to use less threads in the plugin. The heartbeat updates are called from the OnUpdate function.
+ ///
+ public static HeartbeatManager Heartbeats
+ {
+ get
+ {
+ if (heartbeats == null)
+ heartbeats = new HeartbeatManager();
+ return heartbeats;
+ }
+ }
+ private static HeartbeatManager heartbeats;
+
+ ///
+ /// A basic BestHTTP.Logger.ILogger implementation to be able to log intelligently additional informations about the plugin's internal mechanism.
+ ///
+ public static BestHTTP.Logger.ILogger Logger
+ {
+ get
+ {
+ // Make sure that it has a valid logger instance.
+ if (logger == null)
+ {
+ logger = new ThreadedLogger();
+ logger.Level = Loglevels.None;
+ }
+
+ return logger;
+ }
+
+ set { logger = value; }
+ }
+ private static BestHTTP.Logger.ILogger logger;
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+
+ public static TlsClientFactoryDelegate TlsClientFactory;
+
+ public static SecureProtocol.Org.BouncyCastle.Crypto.Tls.AbstractTlsClient DefaultTlsClientFactory(HTTPRequest request, List protocols)
+ {
+ // http://tools.ietf.org/html/rfc3546#section-3.1
+ // -It is RECOMMENDED that clients include an extension of type "server_name" in the client hello whenever they locate a server by a supported name type.
+ // -Literal IPv4 and IPv6 addresses are not permitted in "HostName".
+
+ // User-defined list has a higher priority
+ List hostNames = request.CustomTLSServerNameList;
+
+ // If there's no user defined one and the host isn't an IP address, add the default one
+ if ((hostNames == null || hostNames.Count == 0) && !request.CurrentUri.IsHostIsAnIPAddress())
+ {
+ hostNames = new List(1);
+ hostNames.Add(request.CurrentUri.Host);
+ }
+
+ return new SecureProtocol.Org.BouncyCastle.Crypto.Tls.LegacyTlsClient(request.CurrentUri,
+ request.CustomCertificateVerifyer == null ? new SecureProtocol.Org.BouncyCastle.Crypto.Tls.AlwaysValidVerifyer() : request.CustomCertificateVerifyer,
+ request.CustomClientCredentialsProvider,
+ hostNames,
+ protocols);
+ }
+
+ ///
+ /// The default ICertificateVerifyer implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
+ ///
+ public static SecureProtocol.Org.BouncyCastle.Crypto.Tls.ICertificateVerifyer DefaultCertificateVerifyer { get; set; }
+
+ ///
+ /// The default IClientCredentialsProvider implementation that the plugin will use when the request's UseAlternateSSL property is set to true.
+ ///
+ public static SecureProtocol.Org.BouncyCastle.Crypto.Tls.IClientCredentialsProvider DefaultClientCredentialsProvider { get; set; }
+
+ ///
+ /// The default value for the HTTPRequest's UseAlternateSSL property.
+ ///
+ public static bool UseAlternateSSLDefaultValue { get; set; }
+#endif
+
+#if !NETFX_CORE
+ public static Func DefaultCertificationValidator { get; set; }
+#endif
+
+ ///
+ /// TCP Client's send buffer size.
+ ///
+ public static int? SendBufferSize;
+
+ ///
+ /// TCP Client's receive buffer size.
+ ///
+ public static int? ReceiveBufferSize;
+
+ ///
+ /// An IIOService implementation to handle filesystem operations.
+ ///
+ public static PlatformSupport.FileSystem.IIOService IOService;
+
+ ///
+ /// On most systems the maximum length of a path is around 255 character. If a cache entity's path is longer than this value it doesn't get cached. There no platform independent API to query the exact value on the current system, but it's
+ /// exposed here and can be overridden. It's default value is 255.
+ ///
+ internal static int MaxPathLength { get; set; }
+
+ ///
+ /// User-agent string that will be sent with each requests.
+ ///
+ public static string UserAgent = "BestHTTP/2 v2.5.4";
+
+ ///
+ /// It's true if the application is quitting and the plugin is shutting down itself.
+ ///
+ public static bool IsQuitting { get { return _isQuitting; } private set { _isQuitting = value; } }
+ private static volatile bool _isQuitting;
+#endregion
+
+#region Manager variables
+
+ private static bool IsSetupCalled;
+
+#endregion
+
+#region Public Interface
+
+ public static void Setup()
+ {
+ if (IsSetupCalled)
+ return;
+ IsSetupCalled = true;
+ IsQuitting = false;
+
+ HTTPManager.Logger.Information("HTTPManager", "Setup called! UserAgent: " + UserAgent);
+
+ HTTPUpdateDelegator.CheckInstance();
+
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPCacheService.CheckSetup();
+#endif
+
+#if !BESTHTTP_DISABLE_COOKIES
+ Cookies.CookieJar.SetupFolder();
+ Cookies.CookieJar.Load();
+#endif
+
+ HostManager.Load();
+ }
+
+ public static HTTPRequest SendRequest(string url, OnRequestFinishedDelegate callback)
+ {
+ return SendRequest(new HTTPRequest(new Uri(url), HTTPMethods.Get, callback));
+ }
+
+ public static HTTPRequest SendRequest(string url, HTTPMethods methodType, OnRequestFinishedDelegate callback)
+ {
+ return SendRequest(new HTTPRequest(new Uri(url), methodType, callback));
+ }
+
+ public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
+ {
+ return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, callback));
+ }
+
+ public static HTTPRequest SendRequest(string url, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
+ {
+ return SendRequest(new HTTPRequest(new Uri(url), methodType, isKeepAlive, disableCache, callback));
+ }
+
+ public static HTTPRequest SendRequest(HTTPRequest request)
+ {
+ if (!IsSetupCalled)
+ Setup();
+
+ if (request.IsCancellationRequested || IsQuitting)
+ return request;
+
+#if !BESTHTTP_DISABLE_CACHING
+ // If possible load the full response from cache.
+ if (Caching.HTTPCacheService.IsCachedEntityExpiresInTheFuture(request))
+ {
+ DateTime started = DateTime.Now;
+ PlatformSupport.Threading.ThreadedRunner.RunShortLiving((req) =>
+ {
+ if (Connections.ConnectionHelper.TryLoadAllFromCache("HTTPManager", req, req.Context))
+ {
+ req.Timing.Add("Full Cache Load", DateTime.Now - started);
+ req.State = HTTPRequestStates.Finished;
+ }
+ else
+ {
+ // If for some reason it couldn't load we place back the request to the queue.
+
+ request.State = HTTPRequestStates.Queued;
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
+ }
+ }, request);
+ }
+ else
+#endif
+ {
+ request.State = HTTPRequestStates.Queued;
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(request, RequestEvents.Resend));
+ }
+
+ return request;
+ }
+
+#endregion
+
+#region Internal Helper Functions
+
+ ///
+ /// Will return where the various caches should be saved.
+ ///
+ public static string GetRootCacheFolder()
+ {
+ try
+ {
+ if (RootCacheFolderProvider != null)
+ return RootCacheFolderProvider();
+ }
+ catch(Exception ex)
+ {
+ HTTPManager.Logger.Exception("HTTPManager", "GetRootCacheFolder", ex);
+ }
+
+#if NETFX_CORE
+ return Windows.Storage.ApplicationData.Current.LocalFolder.Path;
+#else
+ return UnityEngine.Application.persistentDataPath;
+#endif
+ }
+
+#if UNITY_EDITOR
+#if UNITY_2019_3_OR_NEWER
+ [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
+#endif
+ public static void ResetSetup()
+ {
+ IsSetupCalled = false;
+ HTTPManager.Logger.Information("HTTPManager", "Reset called!");
+ }
+#endif
+
+#endregion
+
+#region MonoBehaviour Events (Called from HTTPUpdateDelegator)
+
+ ///
+ /// Update function that should be called regularly from a Unity event(Update, LateUpdate). Callbacks are dispatched from this function.
+ ///
+ public static void OnUpdate()
+ {
+ RequestEventHelper.ProcessQueue();
+ ConnectionEventHelper.ProcessQueue();
+ ProtocolEventHelper.ProcessQueue();
+ PluginEventHelper.ProcessQueue();
+
+ BestHTTP.Extensions.Timer.Process();
+
+ if (heartbeats != null)
+ heartbeats.Update();
+
+ BufferPool.Maintain();
+ }
+
+ public static void OnQuit()
+ {
+ HTTPManager.Logger.Information("HTTPManager", "OnQuit called!");
+
+ IsQuitting = true;
+
+ AbortAll();
+
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPCacheService.SaveLibrary();
+#endif
+
+#if !BESTHTTP_DISABLE_COOKIES
+ CookieJar.Persist();
+#endif
+
+ OnUpdate();
+
+ HostManager.Clear();
+
+ Heartbeats.Clear();
+ }
+
+ public static void AbortAll()
+ {
+ HTTPManager.Logger.Information("HTTPManager", "AbortAll called!");
+
+ // This is an immediate shutdown request!
+
+ RequestEventHelper.Clear();
+ ConnectionEventHelper.Clear();
+ PluginEventHelper.Clear();
+ ProtocolEventHelper.Clear();
+
+ HostManager.Shutdown();
+
+ ProtocolEventHelper.CancelActiveProtocols();
+ }
+
+#endregion
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPManager.cs.meta b/Assets/Plugins/BestHTTP/Source/HTTPManager.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..22e9749d263c28a4f4bdd74128e19205e403d0d6
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPManager.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 8598bf6546f50e64197d043a215a5897
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs b/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs
new file mode 100644
index 0000000000000000000000000000000000000000..466c61fb4a4ed72bde0b7983954b885b26aa9a5a
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs
@@ -0,0 +1,81 @@
+namespace BestHTTP
+{
+ ///
+ /// Some supported methods described in the rfc: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9
+ ///
+ public enum HTTPMethods : byte
+ {
+ ///
+ /// The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.
+ /// If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the
+ /// entity in the response and not the source text of the process, unless that text happens to be the output of the process.
+ ///
+ Get,
+
+ ///
+ /// The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.
+ /// The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request.
+ /// This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself.
+ /// This method is often used for testing hypertext links for validity, accessibility, and recent modification.
+ ///
+ Head,
+
+ ///
+ /// The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line.
+ /// POST is designed to allow a uniform method to cover the following functions:
+ ///
+ /// - Annotation of existing resources;
+ /// - Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
+ /// - Providing a block of data, such as the result of submitting a form, to a data-handling process;
+ /// - Extending a database through an append operation.
+ ///
+ /// The actual function performed by the POST method is determined by the server and is usually dependent on the Request-URI.
+ /// The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory containing it,
+ /// a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate to a database.
+ /// The action performed by the POST method might not result in a resource that can be identified by a URI. In this case,
+ /// either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.
+ ///
+ Post,
+
+ ///
+ /// The PUT method requests that the enclosed entity be stored under the supplied Request-URI.
+ /// If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.
+ /// If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent,
+ /// the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response.
+ /// If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.
+ /// If the resource could not be created or modified with the Request-URI, an appropriate error response SHOULD be given that reflects the nature of the problem.
+ /// The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return a 501 (Not Implemented) response in such cases.
+ ///
+ Put,
+
+ ///
+ /// The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server.
+ /// The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully.
+ /// However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location.
+ /// A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content)
+ /// if the action has been enacted but the response does not include an entity.
+ ///
+ Delete,
+
+ ///
+ /// http://tools.ietf.org/html/rfc5789
+ /// The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI.
+ /// The set of changes is represented in a format called a "patchdocument" identified by a media type. If the Request-URI does not point to an existing resource,
+ /// the server MAY create a new resource, depending on the patch document type (whether it can logically modify a null resource) and permissions, etc.
+ ///
+ Patch,
+
+ ///
+ /// The HTTP methods PATCH can be used to update partial resources. For instance, when you only need to update one field of the resource, PUTting a complete resource representation might be cumbersome and utilizes more bandwidth.
+ ///
+ ///
+ Merge,
+
+ Options,
+
+ ///
+ /// https://tools.ietf.org/html/rfc8441
+ ///
+ Connect
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs.meta b/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..d60a3c2adadc78bdaa753c4e1dc574d2fce928d7
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPMethods.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: faf468f5056a1614da3acf7f571cc6bb
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPRange.cs b/Assets/Plugins/BestHTTP/Source/HTTPRange.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4be4ba86b20695030a61b4da0436106d0a021911
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPRange.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace BestHTTP
+{
+ ///
+ ///
+ ///
+ public sealed class HTTPRange
+ {
+ ///
+ /// The first byte's position that the server sent.
+ ///
+ public long FirstBytePos { get; private set; }
+
+ ///
+ /// The last byte's position that the server sent.
+ ///
+ public long LastBytePos { get; private set; }
+
+ ///
+ /// Indicates the total length of the full entity-body on the server, -1 if this length is unknown or difficult to determine.
+ ///
+ public long ContentLength { get; private set; }
+
+ ///
+ ///
+ ///
+ public bool IsValid { get; private set; }
+
+ internal HTTPRange()
+ {
+ this.ContentLength = -1;
+ this.IsValid = false;
+ }
+
+ internal HTTPRange(int contentLength)
+ {
+ this.ContentLength = contentLength;
+ this.IsValid = false;
+ }
+
+ internal HTTPRange(long firstBytePosition, long lastBytePosition, long contentLength)
+ {
+ this.FirstBytePos = firstBytePosition;
+ this.LastBytePos = lastBytePosition;
+ this.ContentLength = contentLength;
+
+ // A byte-content-range-spec with a byte-range-resp-spec whose last-byte-pos value is less than its first-byte-pos value, or whose instance-length value is less than or equal to its last-byte-pos value, is invalid.
+ this.IsValid = this.FirstBytePos <= this.LastBytePos && this.ContentLength > this.LastBytePos;
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0}-{1}/{2} (valid: {3})", FirstBytePos, LastBytePos, ContentLength, IsValid);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPRange.cs.meta b/Assets/Plugins/BestHTTP/Source/HTTPRange.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..b9bf1e9484cdad184dff7d6c89af2203d093c7c9
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPRange.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 874904f9618b97045b510c161f498030
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs b/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0ed756fb302eede4551267359bc82a7f781b9b96
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs
@@ -0,0 +1,1552 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace BestHTTP
+{
+ using BestHTTP.Authentication;
+ using BestHTTP.Extensions;
+ using BestHTTP.Forms;
+
+#if !BESTHTTP_DISABLE_COOKIES
+ using BestHTTP.Cookies;
+#endif
+
+ using BestHTTP.Core;
+ using BestHTTP.PlatformSupport.Memory;
+ using BestHTTP.Connections;
+ using BestHTTP.Logger;
+ using BestHTTP.Timings;
+
+ ///
+ /// Possible logical states of a HTTTPRequest object.
+ ///
+ public enum HTTPRequestStates
+ {
+ ///
+ /// Initial status of a request. No callback will be called with this status.
+ ///
+ Initial,
+
+ ///
+ /// The request queued for processing.
+ ///
+ Queued,
+
+ ///
+ /// Processing of the request started. In this state the client will send the request, and parse the response. No callback will be called with this status.
+ ///
+ Processing,
+
+ ///
+ /// The request finished without problem. Parsing the response done, the result can be used. The user defined callback will be called with a valid response object. The request’s Exception property will be null.
+ ///
+ Finished,
+
+ ///
+ /// The request finished with an unexpected error. The user defined callback will be called with a null response object. The request's Exception property may contain more info about the error, but it can be null.
+ ///
+ Error,
+
+ ///
+ /// The request aborted by the client(HTTPRequest’s Abort() function). The user defined callback will be called with a null response. The request’s Exception property will be null.
+ ///
+ Aborted,
+
+ ///
+ /// Connecting to the server timed out. The user defined callback will be called with a null response. The request’s Exception property will be null.
+ ///
+ ConnectionTimedOut,
+
+ ///
+ /// The request didn't finished in the given time. The user defined callback will be called with a null response. The request’s Exception property will be null.
+ ///
+ TimedOut
+ }
+
+ public delegate void OnRequestFinishedDelegate(HTTPRequest originalRequest, HTTPResponse response);
+ public delegate void OnDownloadProgressDelegate(HTTPRequest originalRequest, long downloaded, long downloadLength);
+ public delegate void OnUploadProgressDelegate(HTTPRequest originalRequest, long uploaded, long uploadLength);
+ public delegate bool OnBeforeRedirectionDelegate(HTTPRequest originalRequest, HTTPResponse response, Uri redirectUri);
+ public delegate void OnHeaderEnumerationDelegate(string header, List values);
+ public delegate void OnBeforeHeaderSendDelegate(HTTPRequest req);
+
+ ///
+ /// Called for every fragment of data downloaded from the server. Its return value indicates whether the plugin free to reuse the dataFragment array.
+ ///
+ /// The parent HTTPRequest object
+ /// The HTTPResponse object.
+ /// The downloaded data. The byte[] can be larger than the actual payload! Its valid length that can be used is in the dataFragmentLength param.
+ /// Length of the downloaded data.
+ public delegate bool OnStreamingDataDelegate(HTTPRequest request, HTTPResponse response, byte[] dataFragment, int dataFragmentLength);
+
+ public sealed class HTTPRequest : IEnumerator, IEnumerator
+ {
+ #region Statics
+
+ public static readonly byte[] EOL = { HTTPResponse.CR, HTTPResponse.LF };
+
+ ///
+ /// Cached uppercase values to save some cpu cycles and GC alloc per request.
+ ///
+ public static readonly string[] MethodNames = {
+ HTTPMethods.Get.ToString().ToUpper(),
+ HTTPMethods.Head.ToString().ToUpper(),
+ HTTPMethods.Post.ToString().ToUpper(),
+ HTTPMethods.Put.ToString().ToUpper(),
+ HTTPMethods.Delete.ToString().ToUpper(),
+ HTTPMethods.Patch.ToString().ToUpper(),
+ HTTPMethods.Merge.ToString().ToUpper(),
+ HTTPMethods.Options.ToString().ToUpper(),
+ HTTPMethods.Connect.ToString().ToUpper(),
+ };
+
+ ///
+ /// Size of the internal buffer, and upload progress will be fired when this size of data sent to the wire. Its default value is 4 KiB.
+ ///
+ public static int UploadChunkSize = 4 * 1024;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// The original request's Uri.
+ ///
+ public Uri Uri { get; set; }
+
+ ///
+ /// The method that how we want to process our request the server.
+ ///
+ public HTTPMethods MethodType { get; set; }
+
+ ///
+ /// The raw data to send in a POST request. If it set all other fields that added to this request will be ignored.
+ ///
+ public byte[] RawData { get; set; }
+
+ ///
+ /// The stream that the plugin will use to get the data to send out the server. When this property is set, no forms or the RawData property will be used
+ ///
+ public Stream UploadStream { get; set; }
+
+ ///
+ /// When set to true(its default value) the plugin will call the UploadStream's Dispose() function when finished uploading the data from it. Default value is true.
+ ///
+ public bool DisposeUploadStream { get; set; }
+
+ ///
+ /// If it's true, the plugin will use the Stream's Length property. Otherwise the plugin will send the data chunked. Default value is true.
+ ///
+ public bool UseUploadStreamLength { get; set; }
+
+ ///
+ /// Called after data sent out to the wire.
+ ///
+ public OnUploadProgressDelegate OnUploadProgress;
+
+ ///
+ /// Indicates that the connection should be open after the response received. If its true, then the internal TCP connections will be reused if it's possible. Default value is true.
+ /// The default value can be changed in the HTTPManager class. If you make rare request to the server it's should be changed to false.
+ ///
+ public bool IsKeepAlive
+ {
+ get { return isKeepAlive; }
+ set
+ {
+ if (State == HTTPRequestStates.Processing)
+ throw new NotSupportedException("Changing the IsKeepAlive property while processing the request is not supported.");
+ isKeepAlive = value;
+ }
+ }
+
+#if !BESTHTTP_DISABLE_CACHING
+ ///
+ /// With this property caching can be enabled/disabled on a per-request basis.
+ ///
+ public bool DisableCache
+ {
+ get { return disableCache; }
+ set
+ {
+ if (State == HTTPRequestStates.Processing)
+ throw new NotSupportedException("Changing the DisableCache property while processing the request is not supported.");
+ disableCache = value;
+ }
+ }
+
+ ///
+ /// It can be used with streaming. When set to true, no OnStreamingData event is called, the streamed content will be saved straight to the cache if all requirements are met(caching is enabled and there's a caching headers).
+ ///
+ public bool CacheOnly
+ {
+ get { return cacheOnly; }
+ set
+ {
+ if (State == HTTPRequestStates.Processing)
+ throw new NotSupportedException("Changing the CacheOnly property while processing the request is not supported.");
+ cacheOnly = value;
+ }
+ }
+#endif
+
+ ///
+ /// Maximum size of a data chunk that we want to receive when streaming is set. Its default value is 1 MB.
+ ///
+ public int StreamFragmentSize
+ {
+ get { return streamFragmentSize; }
+ set
+ {
+ if (State == HTTPRequestStates.Processing)
+ throw new NotSupportedException("Changing the StreamFragmentSize property while processing the request is not supported.");
+
+ if (value < 1)
+ throw new System.ArgumentException("StreamFragmentSize must be at least 1.");
+
+ streamFragmentSize = value;
+ }
+ }
+
+ ///
+ /// When set to true, StreamFragmentSize will be ignored and downloaded chunks will be sent immediately.
+ ///
+ public bool StreamChunksImmediately { get; set; }
+
+ ///
+ /// This property can be used to force the HTTPRequest to use an exact sized read buffer.
+ ///
+ public int ReadBufferSizeOverride { get; set; }
+
+ ///
+ /// Maximum unprocessed fragments allowed to queue up.
+ ///
+ public int MaxFragmentQueueLength { get; set; }
+
+ ///
+ /// The callback function that will be called when a request is fully processed or when any downloaded fragment is available if UseStreaming is true. Can be null for fire-and-forget requests.
+ ///
+ public OnRequestFinishedDelegate Callback { get; set; }
+
+ ///
+ /// When the request is queued for processing.
+ ///
+ public DateTime QueuedAt { get; internal set; }
+
+ public bool IsConnectTimedOut { get { return this.QueuedAt != DateTime.MinValue && DateTime.UtcNow - this.QueuedAt > this.ConnectTimeout; } }
+
+ ///
+ /// When the processing of the request started
+ ///
+ public DateTime ProcessingStarted { get; internal set; }
+
+ ///
+ /// Returns true if the time passed the Timeout setting since processing started.
+ ///
+ public bool IsTimedOut
+ {
+ get
+ {
+ DateTime now = DateTime.UtcNow;
+
+ return (!this.UseStreaming || (this.UseStreaming && this.EnableTimoutForStreaming)) &&
+ ((this.ProcessingStarted != DateTime.MinValue && now - this.ProcessingStarted > this.Timeout) ||
+ this.IsConnectTimedOut);
+ }
+ }
+
+ ///
+ /// Called for every fragment of data downloaded from the server. Return true if dataFrament is processed and the plugin can recycle the byte[].
+ ///
+ public OnStreamingDataDelegate OnStreamingData;
+
+ ///
+ /// This event is called when the plugin received and parsed all headers.
+ ///
+ public Action OnHeadersReceived;
+
+ ///
+ /// Number of times that the plugin retried the request.
+ ///
+ public int Retries { get; internal set; }
+
+ ///
+ /// Maximum number of tries allowed. To disable it set to 0. Its default value is 1 for GET requests, otherwise 0.
+ ///
+ public int MaxRetries { get; set; }
+
+ ///
+ /// True if Abort() is called on this request.
+ ///
+ public bool IsCancellationRequested { get; internal set; }
+
+ ///
+ /// Called when new data downloaded from the server.
+ /// The first parameter is the original HTTTPRequest object itself, the second parameter is the downloaded bytes while the third parameter is the content length.
+ /// There are download modes where we can't figure out the exact length of the final content. In these cases we just guarantee that the third parameter will be at least the size of the second one.
+ ///
+ public OnDownloadProgressDelegate OnDownloadProgress;
+
+ ///
+ /// Indicates that the request is redirected. If a request is redirected, the connection that served it will be closed regardless of the value of IsKeepAlive.
+ ///
+ public bool IsRedirected { get; internal set; }
+
+ ///
+ /// The Uri that the request redirected to.
+ ///
+ public Uri RedirectUri { get; internal set; }
+
+ ///
+ /// If redirected it contains the RedirectUri.
+ ///
+ public Uri CurrentUri { get { return IsRedirected ? RedirectUri : Uri; } }
+
+ ///
+ /// The response to the query.
+ /// If an exception occurred during reading of the response stream or can't connect to the server, this will be null!
+ ///
+ public HTTPResponse Response { get; internal set; }
+
+#if !BESTHTTP_DISABLE_PROXY
+ ///
+ /// Response from the Proxy server. It's null with transparent proxies.
+ ///
+ public HTTPResponse ProxyResponse { get; internal set; }
+#endif
+
+ ///
+ /// It there is an exception while processing the request or response the Response property will be null, and the Exception will be stored in this property.
+ ///
+ public Exception Exception { get; internal set; }
+
+ ///
+ /// Any object can be passed with the request with this property. (eq. it can be identified, etc.)
+ ///
+ public object Tag { get; set; }
+
+ ///
+ /// The UserName, Password pair that the plugin will use to authenticate to the remote server.
+ ///
+ public Credentials Credentials { get; set; }
+
+#if !BESTHTTP_DISABLE_PROXY
+ ///
+ /// True, if there is a Proxy object.
+ ///
+ public bool HasProxy { get { return Proxy != null && Proxy.UseProxyForAddress(this.CurrentUri); } }
+
+ ///
+ /// A web proxy's properties where the request must pass through.
+ ///
+ public Proxy Proxy { get; set; }
+#endif
+
+ ///
+ /// How many redirection supported for this request. The default is 10. 0 or a negative value means no redirection supported.
+ ///
+ public int MaxRedirects { get; set; }
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ ///
+ /// Use Bouncy Castle's code to handle the secure protocol instead of Mono's. You can try to set it true if you receive a "System.Security.Cryptography.CryptographicException: Unsupported hash algorithm" exception.
+ ///
+ public bool UseAlternateSSL { get; set; }
+#endif
+
+#if !BESTHTTP_DISABLE_COOKIES
+
+ ///
+ /// If true cookies will be added to the headers (if any), and parsed from the response. If false, all cookie operations will be ignored. It's default value is HTTPManager's IsCookiesEnabled.
+ ///
+ public bool IsCookiesEnabled { get; set; }
+
+ ///
+ /// Cookies that are added to this list will be sent to the server alongside withe the server sent ones. If cookies are disabled only these cookies will be sent.
+ ///
+ public List Cookies
+ {
+ get
+ {
+ if (customCookies == null)
+ customCookies = new List();
+ return customCookies;
+ }
+ set { customCookies = value; }
+ }
+
+ private List customCookies;
+#endif
+
+ ///
+ /// What form should used. Its default value is Automatic.
+ ///
+ public HTTPFormUsage FormUsage { get; set; }
+
+ ///
+ /// Current state of this request.
+ ///
+ public HTTPRequestStates State
+ {
+ get { return this._state; }
+ internal set
+ {
+ lock (this)
+ {
+ if (this._state != value)
+ {
+ //if (this._state >= HTTPRequestStates.Finished && value >= HTTPRequestStates.Finished)
+ // return;
+
+ this._state = value;
+
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, this._state));
+ }
+ }
+ }
+ }
+ private volatile HTTPRequestStates _state;
+
+ ///
+ /// How many times redirected.
+ ///
+ public int RedirectCount { get; internal set; }
+
+#if !NETFX_CORE
+ ///
+ /// Custom validator for an SslStream. This event will receive the original HTTPRequest, an X509Certificate and an X509Chain objects. It must return true if the certificate valid, false otherwise.
+ /// It's called in a thread! Not available on Windows Phone!
+ ///
+ public event System.Func CustomCertificationValidator;
+#endif
+
+ ///
+ /// Maximum time we wait to establish the connection to the target server. If set to TimeSpan.Zero or lower, no connect timeout logic is executed. Default value is 20 seconds.
+ ///
+ public TimeSpan ConnectTimeout { get; set; }
+
+ ///
+ /// Maximum time we want to wait to the request to finish after the connection is established. Default value is 60 seconds.
+ /// It's disabled for streaming requests! See .
+ ///
+ public TimeSpan Timeout { get; set; }
+
+ ///
+ /// Set to true to enable Timeouts on streaming request. Default value is false.
+ ///
+ public bool EnableTimoutForStreaming { get; set; }
+
+ ///
+ /// Enables safe read method when the response's length of the content is unknown. Its default value is enabled (true).
+ ///
+ public bool EnableSafeReadOnUnknownContentLength { get; set; }
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ ///
+ /// The ICertificateVerifyer implementation that the plugin will use to verify the server certificates when the request's UseAlternateSSL property is set to true.
+ ///
+ public SecureProtocol.Org.BouncyCastle.Crypto.Tls.ICertificateVerifyer CustomCertificateVerifyer { get; set; }
+
+ ///
+ /// The IClientCredentialsProvider implementation that the plugin will use to send client certificates when the request's UseAlternateSSL property is set to true.
+ ///
+ public SecureProtocol.Org.BouncyCastle.Crypto.Tls.IClientCredentialsProvider CustomClientCredentialsProvider { get; set; }
+
+ ///
+ /// With this property custom Server Name Indication entries can be sent to the server while negotiating TLS.
+ /// All added entries must conform to the rules defined in the RFC (https://tools.ietf.org/html/rfc3546#section-3.1), the plugin will not check the entries' validity!
+ /// This list will be sent to every server that the plugin must connect to while it tries to finish the request.
+ /// So for example if redirected to an another server, that new server will receive this list too!
+ ///
+ public List CustomTLSServerNameList { get; set; }
+#endif
+
+ ///
+ /// It's called before the plugin will do a new request to the new uri. The return value of this function will control the redirection: if it's false the redirection is aborted.
+ /// This function is called on a thread other than the main Unity thread!
+ ///
+ public event OnBeforeRedirectionDelegate OnBeforeRedirection
+ {
+ add { onBeforeRedirection += value; }
+ remove { onBeforeRedirection -= value; }
+ }
+ private OnBeforeRedirectionDelegate onBeforeRedirection;
+
+ ///
+ /// This event will be fired before the plugin will write headers to the wire. New headers can be added in this callback. This event is called on a non-Unity thread!
+ ///
+ public event OnBeforeHeaderSendDelegate OnBeforeHeaderSend
+ {
+ add { _onBeforeHeaderSend += value; }
+ remove { _onBeforeHeaderSend -= value; }
+ }
+ private OnBeforeHeaderSendDelegate _onBeforeHeaderSend;
+
+ ///
+ /// Logging context of the request.
+ ///
+ public LoggingContext Context { get; private set; }
+
+ ///
+ /// Timing information.
+ ///
+ public TimingCollector Timing { get; private set; }
+
+#if UNITY_WEBGL
+ ///
+ /// Its value will be set to the XmlHTTPRequest's withCredentials field. Its default value is HTTPManager.IsCookiesEnabled's value.
+ ///
+ public bool WithCredentials { get; set; }
+#endif
+
+ ///
+ ///
+ ///
+ internal SupportedProtocols ProtocolHandler { get; set; }
+
+ ///
+ /// Called when the current protocol is upgraded to an other. (HTTP => WebSocket for example)
+ ///
+ internal OnRequestFinishedDelegate OnUpgraded;
+
+ #region Internal Properties For Progress Report Support
+
+ ///
+ /// If it's true, the Callback will be called every time if we can send out at least one fragment.
+ ///
+ internal bool UseStreaming { get { return this.OnStreamingData != null; } }
+
+ ///
+ /// Will return the length of the UploadStream, or -1 if it's not supported.
+ ///
+ internal long UploadStreamLength
+ {
+ get
+ {
+ if (UploadStream == null || !UseUploadStreamLength)
+ return -1;
+
+ try
+ {
+ // This may will throw a NotSupportedException
+ return UploadStream.Length;
+ }
+ catch
+ {
+ // We will fall back to chunked
+ return -1;
+ }
+ }
+ }
+
+#if !UNITY_WEBGL || UNITY_EDITOR
+ ///
+ /// This action is called when a user calls the Abort function. Do not use it outside of the plugin!
+ ///
+ internal Action OnCancellationRequested;
+#endif
+
+ #endregion
+
+ #endregion
+
+ #region Privates
+
+ private bool isKeepAlive;
+#if !BESTHTTP_DISABLE_CACHING
+ private bool disableCache;
+ private bool cacheOnly;
+#endif
+ private int streamFragmentSize;
+ private bool useStreaming;
+
+ private Dictionary> Headers { get; set; }
+
+ ///
+ /// We will collect the fields and values to the FieldCollector through the AddField and AddBinaryData functions.
+ ///
+ private HTTPFormBase FieldCollector;
+
+ ///
+ /// When the request about to send the request we will create a specialized form implementation(url-encoded, multipart, or the legacy WWWForm based).
+ /// And we will use this instance to create the data that we will send to the server.
+ ///
+ private HTTPFormBase FormImpl;
+
+ #endregion
+
+ #region Constructors
+
+ #region Default Get Constructors
+
+ public HTTPRequest(Uri uri)
+ : this(uri, HTTPMethods.Get, HTTPManager.KeepAliveDefaultValue,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled
+#else
+ true
+#endif
+ , null)
+ {
+ }
+
+ public HTTPRequest(Uri uri, OnRequestFinishedDelegate callback)
+ : this(uri, HTTPMethods.Get, HTTPManager.KeepAliveDefaultValue,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled
+#else
+ true
+#endif
+ , callback)
+ {
+ }
+
+ public HTTPRequest(Uri uri, bool isKeepAlive, OnRequestFinishedDelegate callback)
+ : this(uri, HTTPMethods.Get, isKeepAlive,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled
+#else
+ true
+#endif
+
+ , callback)
+ {
+ }
+ public HTTPRequest(Uri uri, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
+ : this(uri, HTTPMethods.Get, isKeepAlive, disableCache, callback)
+ {
+ }
+
+ #endregion
+
+ public HTTPRequest(Uri uri, HTTPMethods methodType)
+ : this(uri, methodType, HTTPManager.KeepAliveDefaultValue,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
+#else
+ true
+#endif
+ , null)
+ {
+ }
+
+ public HTTPRequest(Uri uri, HTTPMethods methodType, OnRequestFinishedDelegate callback)
+ : this(uri, methodType, HTTPManager.KeepAliveDefaultValue,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
+#else
+ true
+#endif
+ , callback)
+ {
+ }
+
+ public HTTPRequest(Uri uri, HTTPMethods methodType, bool isKeepAlive, OnRequestFinishedDelegate callback)
+ : this(uri, methodType, isKeepAlive,
+#if !BESTHTTP_DISABLE_CACHING
+ HTTPManager.IsCachingDisabled || methodType != HTTPMethods.Get
+#else
+ true
+#endif
+ , callback)
+ {
+ }
+
+ public HTTPRequest(Uri uri, HTTPMethods methodType, bool isKeepAlive, bool disableCache, OnRequestFinishedDelegate callback)
+ {
+ this.Uri = uri;
+ this.MethodType = methodType;
+ this.IsKeepAlive = isKeepAlive;
+#if !BESTHTTP_DISABLE_CACHING
+ this.DisableCache = disableCache;
+#endif
+ this.Callback = callback;
+ this.StreamFragmentSize = 1024 * 1024;
+ this.MaxFragmentQueueLength = 10;
+
+ this.MaxRetries = methodType == HTTPMethods.Get ? 1 : 0;
+ this.MaxRedirects = 10;
+ this.RedirectCount = 0;
+#if !BESTHTTP_DISABLE_COOKIES
+ this.IsCookiesEnabled = HTTPManager.IsCookiesEnabled;
+#endif
+
+ this.State = HTTPRequestStates.Initial;
+
+ this.ConnectTimeout = HTTPManager.ConnectTimeout;
+ this.Timeout = HTTPManager.RequestTimeout;
+ this.EnableTimoutForStreaming = false;
+
+ this.EnableSafeReadOnUnknownContentLength = true;
+
+#if !BESTHTTP_DISABLE_PROXY
+ this.Proxy = HTTPManager.Proxy;
+#endif
+
+ this.UseUploadStreamLength = true;
+ this.DisposeUploadStream = true;
+
+#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
+ this.CustomCertificateVerifyer = HTTPManager.DefaultCertificateVerifyer;
+ this.CustomClientCredentialsProvider = HTTPManager.DefaultClientCredentialsProvider;
+ this.UseAlternateSSL = HTTPManager.UseAlternateSSLDefaultValue;
+#endif
+
+#if !NETFX_CORE
+ this.CustomCertificationValidator += HTTPManager.DefaultCertificationValidator;
+#endif
+
+#if UNITY_WEBGL && !BESTHTTP_DISABLE_COOKIES
+ this.WithCredentials = this.IsCookiesEnabled;
+#endif
+
+ this.Context = new LoggingContext(this);
+ this.Timing = new TimingCollector(this);
+ }
+
+ #endregion
+
+ #region Public Field Functions
+
+ ///
+ /// Add a field with a given string value.
+ ///
+ public void AddField(string fieldName, string value)
+ {
+ AddField(fieldName, value, System.Text.Encoding.UTF8);
+ }
+
+ ///
+ /// Add a field with a given string value.
+ ///
+ public void AddField(string fieldName, string value, System.Text.Encoding e)
+ {
+ if (FieldCollector == null)
+ FieldCollector = new HTTPFormBase();
+
+ FieldCollector.AddField(fieldName, value, e);
+ }
+
+ ///
+ /// Add a field with binary content to the form.
+ ///
+ public void AddBinaryData(string fieldName, byte[] content)
+ {
+ AddBinaryData(fieldName, content, null, null);
+ }
+
+ ///
+ /// Add a field with binary content to the form.
+ ///
+ public void AddBinaryData(string fieldName, byte[] content, string fileName)
+ {
+ AddBinaryData(fieldName, content, fileName, null);
+ }
+
+ ///
+ /// Add a field with binary content to the form.
+ ///
+ public void AddBinaryData(string fieldName, byte[] content, string fileName, string mimeType)
+ {
+ if (FieldCollector == null)
+ FieldCollector = new HTTPFormBase();
+
+ FieldCollector.AddBinaryData(fieldName, content, fileName, mimeType);
+ }
+
+ ///
+ /// Manually set a HTTP Form.
+ ///
+ public void SetForm(HTTPFormBase form)
+ {
+ FormImpl = form;
+ }
+
+ ///
+ /// Returns with the added form-fields or null if no one added.
+ ///
+ public List GetFormFields()
+ {
+ if (this.FieldCollector == null || this.FieldCollector.IsEmpty)
+ return null;
+
+ return new List(this.FieldCollector.Fields);
+ }
+
+ ///
+ /// Clears all data from the form.
+ ///
+ public void ClearForm()
+ {
+ FormImpl = null;
+ FieldCollector = null;
+ }
+
+ ///
+ /// Will create the form implementation based on the value of the FormUsage property.
+ ///
+ private HTTPFormBase SelectFormImplementation()
+ {
+ // Our form already created with a previous
+ if (FormImpl != null)
+ return FormImpl;
+
+ // No field added to this request yet
+ if (FieldCollector == null)
+ return null;
+
+ switch (FormUsage)
+ {
+ case HTTPFormUsage.Automatic:
+ // A really simple decision making: if there are at least one field with binary data, or a 'long' string value then we will choose a Multipart form.
+ // Otherwise Url Encoded form will be used.
+ if (FieldCollector.HasBinary || FieldCollector.HasLongValue)
+ goto case HTTPFormUsage.Multipart;
+ else
+ goto case HTTPFormUsage.UrlEncoded;
+
+ case HTTPFormUsage.UrlEncoded: FormImpl = new HTTPUrlEncodedForm(); break;
+ case HTTPFormUsage.Multipart: FormImpl = new HTTPMultiPartForm(); break;
+ }
+
+ // Copy the fields, and other properties to the new implementation
+ FormImpl.CopyFrom(FieldCollector);
+
+ return FormImpl;
+ }
+
+ #endregion
+
+ #region Header Management
+
+ #region General Management
+
+ ///
+ /// Adds a header and value pair to the Headers. Use it to add custom headers to the request.
+ ///
+ /// AddHeader("User-Agent', "FooBar 1.0")
+ public void AddHeader(string name, string value)
+ {
+ if (Headers == null)
+ Headers = new Dictionary>();
+
+ List values;
+ if (!Headers.TryGetValue(name, out values))
+ Headers.Add(name, values = new List(1));
+
+ values.Add(value);
+ }
+
+ ///
+ /// Removes any previously added values, and sets the given one.
+ ///
+ public void SetHeader(string name, string value)
+ {
+ if (Headers == null)
+ Headers = new Dictionary>();
+
+ List values;
+ if (!Headers.TryGetValue(name, out values))
+ Headers.Add(name, values = new List(1));
+
+ values.Clear();
+ values.Add(value);
+ }
+
+ ///
+ /// Removes the specified header. Returns true, if the header found and succesfully removed.
+ ///
+ ///
+ ///
+ public bool RemoveHeader(string name)
+ {
+ if (Headers == null)
+ return false;
+
+ return Headers.Remove(name);
+ }
+
+ ///
+ /// Returns true if the given head name is already in the Headers.
+ ///
+ public bool HasHeader(string name)
+ {
+ return Headers != null && Headers.ContainsKey(name);
+ }
+
+ ///
+ /// Returns the first header or null for the given header name.
+ ///
+ public string GetFirstHeaderValue(string name)
+ {
+ if (Headers == null)
+ return null;
+
+ List headers = null;
+ if (Headers.TryGetValue(name, out headers) && headers.Count > 0)
+ return headers[0];
+
+ return null;
+ }
+
+ ///
+ /// Returns all header values for the given header or null.
+ ///
+ public List GetHeaderValues(string name)
+ {
+ if (Headers == null)
+ return null;
+
+ List headers = null;
+ if (Headers.TryGetValue(name, out headers) && headers.Count > 0)
+ return headers;
+
+ return null;
+ }
+
+ ///
+ /// Removes all headers.
+ ///
+ public void RemoveHeaders()
+ {
+ if (Headers == null)
+ return;
+
+ Headers.Clear();
+ }
+
+ #endregion
+
+ #region Range Headers
+
+ ///
+ /// Sets the Range header to download the content from the given byte position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
+ ///
+ /// Start position of the download.
+ public void SetRangeHeader(long firstBytePos)
+ {
+ SetHeader("Range", string.Format("bytes={0}-", firstBytePos));
+ }
+
+ ///
+ /// Sets the Range header to download the content from the given byte position to the given last position. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35
+ ///
+ /// Start position of the download.
+ /// The end position of the download.
+ public void SetRangeHeader(long firstBytePos, long lastBytePos)
+ {
+ SetHeader("Range", string.Format("bytes={0}-{1}", firstBytePos, lastBytePos));
+ }
+
+ #endregion
+
+ public void EnumerateHeaders(OnHeaderEnumerationDelegate callback)
+ {
+ EnumerateHeaders(callback, false);
+ }
+
+ public void EnumerateHeaders(OnHeaderEnumerationDelegate callback, bool callBeforeSendCallback)
+ {
+#if !UNITY_WEBGL || UNITY_EDITOR
+ if (!HasHeader("Host"))
+ {
+ if (CurrentUri.Port == 80 || CurrentUri.Port == 443)
+ SetHeader("Host", CurrentUri.Host);
+ else
+ SetHeader("Host", CurrentUri.Authority);
+ }
+
+ if (IsRedirected && !HasHeader("Referer"))
+ AddHeader("Referer", Uri.ToString());
+
+ if (!HasHeader("Accept-Encoding"))
+#if BESTHTTP_DISABLE_GZIP
+ AddHeader("Accept-Encoding", "identity");
+#else
+ AddHeader("Accept-Encoding", "gzip, identity");
+#endif
+
+#if !BESTHTTP_DISABLE_PROXY
+ if (HasProxy && !HasHeader("Proxy-Connection"))
+ AddHeader("Proxy-Connection", IsKeepAlive ? "Keep-Alive" : "Close");
+#endif
+
+ if (!HasHeader("Connection"))
+ AddHeader("Connection", IsKeepAlive ? "Keep-Alive, TE" : "Close, TE");
+
+ if (IsKeepAlive && !HasHeader("Keep-Alive"))
+ {
+ // Send the server a slightly larger value to make sure it's not going to close sooner than the client
+ int seconds = (int)Math.Ceiling(HTTPManager.MaxConnectionIdleTime.TotalSeconds + 1);
+
+ AddHeader("Keep-Alive", "timeout=" + seconds);
+ }
+
+ if (!HasHeader("TE"))
+ AddHeader("TE", "identity");
+
+ if (!string.IsNullOrEmpty(HTTPManager.UserAgent) && !HasHeader("User-Agent"))
+ AddHeader("User-Agent", HTTPManager.UserAgent);
+#endif
+ long contentLength = -1;
+
+ if (UploadStream == null)
+ {
+ byte[] entityBody = GetEntityBody();
+ contentLength = entityBody != null ? entityBody.Length : 0;
+
+ if (RawData == null && (FormImpl != null || (FieldCollector != null && !FieldCollector.IsEmpty)))
+ {
+ SelectFormImplementation();
+ if (FormImpl != null)
+ FormImpl.PrepareRequest(this);
+ }
+ }
+ else
+ {
+ contentLength = UploadStreamLength;
+
+ if (contentLength == -1)
+ SetHeader("Transfer-Encoding", "Chunked");
+
+ if (!HasHeader("Content-Type"))
+ SetHeader("Content-Type", "application/octet-stream");
+ }
+
+ // Always set the Content-Length header if possible
+ // http://tools.ietf.org/html/rfc2616#section-4.4 : For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 compliant.
+ // 2018.06.03: Changed the condition so that content-length header will be included for zero length too.
+ if (
+#if !UNITY_WEBGL || UNITY_EDITOR
+ contentLength >= 0
+#else
+ contentLength != -1
+#endif
+ && !HasHeader("Content-Length"))
+ SetHeader("Content-Length", contentLength.ToString());
+
+#if !UNITY_WEBGL || UNITY_EDITOR
+#if !BESTHTTP_DISABLE_PROXY
+ // Proxy Authentication
+ if (HasProxy && Proxy.Credentials != null)
+ {
+ switch (Proxy.Credentials.Type)
+ {
+ case AuthenticationTypes.Basic:
+ // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
+ SetHeader("Proxy-Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Proxy.Credentials.UserName + ":" + Proxy.Credentials.Password))));
+ break;
+
+ case AuthenticationTypes.Unknown:
+ case AuthenticationTypes.Digest:
+ var digest = DigestStore.Get(Proxy.Address);
+ if (digest != null)
+ {
+ string authentication = digest.GenerateResponseHeader(this, Proxy.Credentials);
+ if (!string.IsNullOrEmpty(authentication))
+ SetHeader("Proxy-Authorization", authentication);
+ }
+
+ break;
+ }
+ }
+#endif
+
+#endif
+
+ // Server authentication
+ if (Credentials != null)
+ {
+ switch (Credentials.Type)
+ {
+ case AuthenticationTypes.Basic:
+ // With Basic authentication we don't want to wait for a challenge, we will send the hash with the first request
+ SetHeader("Authorization", string.Concat("Basic ", Convert.ToBase64String(Encoding.UTF8.GetBytes(Credentials.UserName + ":" + Credentials.Password))));
+ break;
+
+ case AuthenticationTypes.Unknown:
+ case AuthenticationTypes.Digest:
+ var digest = DigestStore.Get(this.CurrentUri);
+ if (digest != null)
+ {
+ string authentication = digest.GenerateResponseHeader(this, Credentials);
+ if (!string.IsNullOrEmpty(authentication))
+ SetHeader("Authorization", authentication);
+ }
+
+ break;
+ }
+ }
+
+ // Cookies.
+#if !BESTHTTP_DISABLE_COOKIES
+ // User added cookies are sent even when IsCookiesEnabled is set to false
+ List cookies = IsCookiesEnabled ? CookieJar.Get(CurrentUri) : null;
+
+ // Merge server sent cookies with user-set cookies
+ if (cookies == null || cookies.Count == 0)
+ cookies = this.customCookies;
+ else if (this.customCookies != null)
+ {
+ // Merge
+ int idx = 0;
+ while (idx < this.customCookies.Count)
+ {
+ Cookie customCookie = customCookies[idx];
+
+ int foundIdx = cookies.FindIndex(c => c.Name.Equals(customCookie.Name));
+ if (foundIdx >= 0)
+ cookies[foundIdx] = customCookie;
+ else
+ cookies.Add(customCookie);
+
+ idx++;
+ }
+ }
+
+ // http://tools.ietf.org/html/rfc6265#section-5.4
+ // -When the user agent generates an HTTP request, the user agent MUST NOT attach more than one Cookie header field.
+ if (cookies != null && cookies.Count > 0)
+ {
+ // Room for improvement:
+ // 2. The user agent SHOULD sort the cookie-list in the following order:
+ // * Cookies with longer paths are listed before cookies with shorter paths.
+ // * Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times.
+
+ bool first = true;
+ string cookieStr = string.Empty;
+
+ bool isSecureProtocolInUse = HTTPProtocolFactory.IsSecureProtocol(CurrentUri);
+
+ foreach (var cookie in cookies)
+ if (!cookie.IsSecure || (cookie.IsSecure && isSecureProtocolInUse))
+ {
+ if (!first)
+ cookieStr += "; ";
+ else
+ first = false;
+
+ cookieStr += cookie.ToString();
+
+ // 3. Update the last-access-time of each cookie in the cookie-list to the current date and time.
+ cookie.LastAccess = DateTime.UtcNow;
+ }
+
+ if (!string.IsNullOrEmpty(cookieStr))
+ SetHeader("Cookie", cookieStr);
+ }
+#endif
+
+ if (callBeforeSendCallback && _onBeforeHeaderSend != null)
+ {
+ try
+ {
+ _onBeforeHeaderSend(this);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("HTTPRequest", "OnBeforeHeaderSend", ex, this.Context);
+ }
+ }
+
+ // Write out the headers to the stream
+ if (callback != null && Headers != null)
+ foreach (var kvp in Headers)
+ callback(kvp.Key, kvp.Value);
+ }
+
+ ///
+ /// Writes out the Headers to the stream.
+ ///
+ private void SendHeaders(Stream stream)
+ {
+ EnumerateHeaders((header, values) =>
+ {
+ if (string.IsNullOrEmpty(header) || values == null)
+ return;
+
+ byte[] headerName = string.Concat(header, ": ").GetASCIIBytes();
+
+ for (int i = 0; i < values.Count; ++i)
+ {
+ if (string.IsNullOrEmpty(values[i]))
+ {
+ HTTPManager.Logger.Warning("HTTPRequest", string.Format("Null/empty value for header: {0}", header), this.Context);
+ continue;
+ }
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ VerboseLogging("Header - '" + header + "': '" + values[i] + "'");
+
+ byte[] valueBytes = values[i].GetASCIIBytes();
+
+ stream.WriteArray(headerName);
+ stream.WriteArray(valueBytes);
+ stream.WriteArray(EOL);
+
+ BufferPool.Release(valueBytes);
+ }
+
+ BufferPool.Release(headerName);
+ }, /*callBeforeSendCallback:*/ true);
+ }
+
+ ///
+ /// Returns a string representation of the headers.
+ ///
+ public string DumpHeaders()
+ {
+ using (var ms = new BufferPoolMemoryStream(5 * 1024))
+ {
+ SendHeaders(ms);
+ return ms.ToArray().AsciiToString();
+ }
+ }
+
+ ///
+ /// Returns with the bytes that will be sent to the server as the request's payload.
+ ///
+ /// Call this only after all form-fields are added!
+ public byte[] GetEntityBody()
+ {
+ if (RawData != null)
+ return RawData;
+
+ if (FormImpl != null || (FieldCollector != null && !FieldCollector.IsEmpty))
+ {
+ SelectFormImplementation();
+ if (FormImpl != null)
+ return FormImpl.GetData();
+ }
+
+ return null;
+ }
+
+ internal struct UploadStreamInfo
+ {
+ public readonly Stream Stream;
+ public readonly long Length;
+
+ public UploadStreamInfo(Stream stream, long length)
+ {
+ this.Stream = stream;
+ this.Length = length;
+ }
+ }
+
+ internal UploadStreamInfo GetUpStream()
+ {
+ byte[] data = RawData;
+
+ // We are sending forms? Then convert the form to a byte array
+ if (data == null && FormImpl != null)
+ data = FormImpl.GetData();
+
+ if (data != null || UploadStream != null)
+ {
+ // Make a new reference, as we will check the UploadStream property in the HTTPManager
+ Stream uploadStream = UploadStream;
+
+ long UploadLength = 0;
+
+ if (uploadStream == null)
+ {
+ // Make stream from the data. A BufferPoolMemoryStream could be used here,
+ // but because data comes from outside, we don't have control on its lifetime
+ // and might be gets reused without our knowledge.
+ uploadStream = new MemoryStream(data, 0, data.Length);
+
+ // Initialize progress report variable
+ UploadLength = data.Length;
+ }
+ else
+ UploadLength = UseUploadStreamLength ? UploadStreamLength : -1;
+
+ return new UploadStreamInfo(uploadStream, UploadLength);
+ }
+
+ return new UploadStreamInfo(null, 0);
+ }
+
+ #endregion
+
+ #region Internal Helper Functions
+
+ internal void SendOutTo(Stream stream)
+ {
+ // Under WEBGL EnumerateHeaders and GetEntityBody are used instead of this function.
+#if !UNITY_WEBGL || UNITY_EDITOR
+ string requestPathAndQuery =
+#if !BESTHTTP_DISABLE_PROXY
+ HasProxy ? this.Proxy.GetRequestPath(CurrentUri) :
+#endif
+ CurrentUri.GetRequestPathAndQueryURL();
+
+ string requestLine = string.Format("{0} {1} HTTP/1.1", MethodNames[(byte)MethodType], requestPathAndQuery);
+
+ if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
+ HTTPManager.Logger.Information("HTTPRequest", string.Format("Sending request: '{0}'", requestLine), this.Context);
+
+ // Create a buffer stream that will not close 'stream' when disposed or closed.
+ // buffersize should be larger than UploadChunkSize as it might be used for uploading user data and
+ // it should have enough room for UploadChunkSize data and additional chunk information.
+ using (WriteOnlyBufferedStream bufferStream = new WriteOnlyBufferedStream(stream, (int)(UploadChunkSize * 1.5f)))
+ {
+ byte[] requestLineBytes = requestLine.GetASCIIBytes();
+ bufferStream.WriteArray(requestLineBytes);
+ bufferStream.WriteArray(EOL);
+
+ BufferPool.Release(requestLineBytes);
+
+ // Write headers to the buffer
+ SendHeaders(bufferStream);
+ bufferStream.WriteArray(EOL);
+
+ // Send remaining data to the wire
+ bufferStream.Flush();
+
+ byte[] data = RawData;
+
+ // We are sending forms? Then convert the form to a byte array
+ if (data == null && FormImpl != null)
+ data = FormImpl.GetData();
+
+ if (data != null || UploadStream != null)
+ {
+ // Make a new reference, as we will check the UploadStream property in the HTTPManager
+ Stream uploadStream = UploadStream;
+
+ long UploadLength = 0;
+
+ if (uploadStream == null)
+ {
+ // Make stream from the data. A BufferPoolMemoryStream could be used here,
+ // but because data comes from outside, we don't have control on it's lifetime
+ // and might be gets reused without our knowledge.
+ uploadStream = new MemoryStream(data, 0, data.Length);
+
+ // Initialize progress report variable
+ UploadLength = data.Length;
+ }
+ else
+ UploadLength = UseUploadStreamLength ? UploadStreamLength : -1;
+
+ // Initialize the progress report variables
+ long Uploaded = 0;
+
+ // Upload buffer. First we will read the data into this buffer from the UploadStream, then write this buffer to our outStream
+ byte[] buffer = BufferPool.Get(UploadChunkSize, true);
+
+ // How many bytes was read from the UploadStream
+ int count = 0;
+ while ((count = uploadStream.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ // If we don't know the size, send as chunked
+ if (!UseUploadStreamLength)
+ {
+ byte[] countBytes = count.ToString("X").GetASCIIBytes();
+ bufferStream.WriteArray(countBytes);
+ bufferStream.WriteArray(EOL);
+
+ BufferPool.Release(countBytes);
+ }
+
+ // write out the buffer to the wire
+ bufferStream.Write(buffer, 0, count);
+
+ // chunk trailing EOL
+ if (!UseUploadStreamLength)
+ bufferStream.WriteArray(EOL);
+
+ // update how many bytes are uploaded
+ Uploaded += count;
+
+ // Write to the wire
+ bufferStream.Flush();
+
+ if (this.OnUploadProgress != null)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this, RequestEvents.UploadProgress, Uploaded, UploadLength));
+
+ if (this.IsCancellationRequested)
+ return;
+ }
+
+ BufferPool.Release(buffer);
+
+ // All data from the stream are sent, write the 'end' chunk if necessary
+ if (!UseUploadStreamLength)
+ {
+ byte[] noMoreChunkBytes = BufferPool.Get(1, true);
+ noMoreChunkBytes[0] = (byte)'0';
+ bufferStream.Write(noMoreChunkBytes, 0, 1);
+ bufferStream.WriteArray(EOL);
+ bufferStream.WriteArray(EOL);
+
+ BufferPool.Release(noMoreChunkBytes);
+ }
+
+ // Make sure all remaining data will be on the wire
+ bufferStream.Flush();
+
+ // Dispose the MemoryStream
+ if (UploadStream == null && uploadStream != null)
+ uploadStream.Dispose();
+ }
+ else
+ bufferStream.Flush();
+ } // bufferStream.Dispose
+
+ HTTPManager.Logger.Information("HTTPRequest", "'" + requestLine + "' sent out", this.Context);
+#endif
+ }
+
+ internal void UpgradeCallback()
+ {
+ if (Response == null || !Response.IsUpgraded)
+ return;
+
+ try
+ {
+ if (OnUpgraded != null)
+ OnUpgraded(this, Response);
+ }
+ catch (Exception ex)
+ {
+ HTTPManager.Logger.Exception("HTTPRequest", "UpgradeCallback", ex, this.Context);
+ }
+ }
+
+ internal bool CallOnBeforeRedirection(Uri redirectUri)
+ {
+ if (onBeforeRedirection != null)
+ return onBeforeRedirection(this, this.Response, redirectUri);
+
+ return true;
+ }
+
+ ///
+ /// Called on Unity's main thread just before processing it.
+ ///
+ internal void Prepare()
+ {
+
+ }
+
+#if !NETFX_CORE
+ internal bool CallCustomCertificationValidator(System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Security.Cryptography.X509Certificates.X509Chain chain)
+ {
+ if (CustomCertificationValidator != null)
+ return CustomCertificationValidator(this, cert, chain);
+ return true;
+ }
+#endif
+
+ #endregion
+
+ ///
+ /// Starts processing the request.
+ ///
+ public HTTPRequest Send()
+ {
+ this.IsCancellationRequested = false;
+
+ return HTTPManager.SendRequest(this);
+ }
+
+ ///
+ /// Aborts an already established connection, so no further download or upload are done.
+ ///
+ public void Abort()
+ {
+ VerboseLogging("Abort request!");
+
+ lock (this)
+ {
+ if (this.State >= HTTPRequestStates.Finished)
+ return;
+
+ this.IsCancellationRequested = true;
+
+ // If the response is an IProtocol implementation, call the protocol's cancellation.
+ IProtocol protocol = this.Response as IProtocol;
+ if (protocol != null)
+ protocol.CancellationRequested();
+
+ // There's a race-condition here, another thread might set it too.
+ this.Response = null;
+
+ // There's a race-condition here too, another thread might set it too.
+ // In this case, both state going to be queued up that we have to handle in RequestEvents.cs.
+ if (this.IsTimedOut)
+ {
+ this.State = this.IsConnectTimedOut ? HTTPRequestStates.ConnectionTimedOut : HTTPRequestStates.TimedOut;
+ }
+ else
+ this.State = HTTPRequestStates.Aborted;
+
+#if !UNITY_WEBGL || UNITY_EDITOR
+ if (this.OnCancellationRequested != null)
+ {
+ try
+ {
+ this.OnCancellationRequested(this);
+ }
+ catch { }
+ }
+#endif
+ }
+ }
+
+ ///
+ /// Resets the request for a state where switching MethodType is possible.
+ ///
+ public void Clear()
+ {
+ ClearForm();
+ RemoveHeaders();
+
+ this.IsRedirected = false;
+ this.RedirectCount = 0;
+ }
+
+ private void VerboseLogging(string str)
+ {
+ HTTPManager.Logger.Verbose("HTTPRequest", str, this.Context);
+ }
+
+ #region System.Collections.IEnumerator implementation
+
+ public object Current { get { return null; } }
+
+ public bool MoveNext()
+ {
+ return this.State < HTTPRequestStates.Finished;
+ }
+
+ public void Reset()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ HTTPRequest IEnumerator.Current
+ {
+ get { return this; }
+ }
+
+ public void Dispose()
+ {
+ if (UploadStream != null && DisposeUploadStream)
+ {
+ UploadStream.Dispose();
+ UploadStream = null;
+ }
+
+ if (Response != null)
+ Response.Dispose();
+ }
+ }
+}
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs.meta b/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs.meta
new file mode 100644
index 0000000000000000000000000000000000000000..5ad5cae2fe2708e7c49b5f7ab243bc08b9737264
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPRequest.cs.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: bb4a0cfc508761746bb8e11d32d4c8e0
+MonoImporter:
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Plugins/BestHTTP/Source/HTTPResponse.cs b/Assets/Plugins/BestHTTP/Source/HTTPResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bca830f98451b697b2b0ec56b438184668bb02a7
--- /dev/null
+++ b/Assets/Plugins/BestHTTP/Source/HTTPResponse.cs
@@ -0,0 +1,1231 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+#if !NETFX_CORE || UNITY_EDITOR
+ using System.Net.Sockets;
+#endif
+
+using UnityEngine;
+
+namespace BestHTTP
+{
+ #if !BESTHTTP_DISABLE_CACHING
+ using BestHTTP.Caching;
+ #endif
+
+ using BestHTTP.Extensions;
+
+ #if !BESTHTTP_DISABLE_COOKIES
+ using BestHTTP.Cookies;
+ #endif
+
+ using System.Threading;
+ using BestHTTP.Core;
+ using BestHTTP.PlatformSupport.Memory;
+ using BestHTTP.Logger;
+ using BestHTTP.Timings;
+
+ ///
+ ///
+ ///
+ public class HTTPResponse : IDisposable
+ {
+ internal const byte CR = 13;
+ internal const byte LF = 10;
+
+ ///
+ /// Minimum size of the read buffer.
+ ///
+ public static int MinReadBufferSize = 16 * 1024;
+
+ #region Public Properties
+
+ public int VersionMajor { get; protected set; }
+
+ public int VersionMinor { get; protected set; }
+
+ ///
+ /// The status code that sent from the server.
+ ///
+ public int StatusCode { get; protected set; }
+
+ ///
+ /// Returns true if the status code is in the range of [200..300[ or 304 (Not Modified)
+ ///
+ public bool IsSuccess { get { return (this.StatusCode >= 200 && this.StatusCode < 300) || this.StatusCode == 304; } }
+
+ ///
+ /// The message that sent along with the StatusCode from the server. You can check it for errors from the server.
+ ///
+ public string Message { get; protected set; }
+
+ ///
+ /// True if it's a streamed response.
+ ///
+ public bool IsStreamed { get; protected set; }
+
+#if !BESTHTTP_DISABLE_CACHING
+ ///
+ /// Indicates that the response body is read from the cache.
+ ///
+ public bool IsFromCache { get; internal set; }
+
+ ///
+ /// Provides information about the file used for caching the request.
+ ///
+ public HTTPCacheFileInfo CacheFileInfo { get; internal set; }
+
+ ///
+ /// Determines if this response is only stored to cache.
+ /// If both IsCacheOnly and IsStreamed are true, OnStreamingData isn't called.
+ ///
+ public bool IsCacheOnly { get; private set; }
+#endif
+
+ ///
+ /// True, if this is a response for a HTTPProxy request.
+ ///
+ public bool IsProxyResponse { get; private set; }
+
+ ///
+ /// The headers that sent from the server.
+ ///
+ public Dictionary> Headers { get; protected set; }
+
+ ///
+ /// The data that downloaded from the server. All Transfer and Content encodings decoded if any(eg. chunked, gzip, deflate).
+ ///
+ public byte[] Data { get; internal set; }
+
+ ///
+ /// The normal HTTP protocol is upgraded to an other.
+ ///
+ public bool IsUpgraded { get; protected set; }
+
+#if !BESTHTTP_DISABLE_COOKIES
+ ///
+ /// The cookies that the server sent to the client.
+ ///
+ public List Cookies { get; internal set; }
+#endif
+
+ ///
+ /// Cached, converted data.
+ ///
+ protected string dataAsText;
+
+ ///
+ /// The data converted to an UTF8 string.
+ ///
+ public string DataAsText
+ {
+ get
+ {
+ if (Data == null)
+ return string.Empty;
+
+ if (!string.IsNullOrEmpty(dataAsText))
+ return dataAsText;
+
+ return dataAsText = Encoding.UTF8.GetString(Data, 0, Data.Length);
+ }
+ }
+
+ ///
+ /// Cached converted data.
+ ///
+ protected Texture2D texture;
+
+ ///
+ /// The data loaded to a Texture2D.
+ ///
+ public Texture2D DataAsTexture2D
+ {
+ get
+ {
+ if (Data == null)
+ return null;
+
+ if (texture != null)
+ return texture;
+
+ texture = new Texture2D(0, 0, TextureFormat.RGBA32, false);
+ texture.LoadImage(Data, true);
+
+ return texture;
+ }
+ }
+
+ ///
+ /// True if the connection's stream will be closed manually. Used in custom protocols (WebSocket, EventSource).
+ ///
+ public bool IsClosedManually { get; protected set; }
+
+ public LoggingContext Context { get; private set; }
+
+ ///
+ /// Count of streaming data fragments sitting in the HTTPManager's request event queue.
+ ///
+#if UNITY_EDITOR
+ [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
+#endif
+ internal long UnprocessedFragments;
+
+ #endregion
+
+ #region Internal Fields
+
+ internal HTTPRequest baseRequest;
+
+ #endregion
+
+ #region Protected Properties And Fields
+
+ protected Stream Stream;
+
+ protected byte[] fragmentBuffer;
+ protected int fragmentBufferDataLength;
+#if !BESTHTTP_DISABLE_CACHING
+ protected Stream cacheStream;
+#endif
+ protected int allFragmentSize;
+
+ #endregion
+
+ protected HTTPResponse(HTTPRequest request, bool isFromCache)
+ {
+ this.baseRequest = request;
+#if !BESTHTTP_DISABLE_CACHING
+ this.IsFromCache = isFromCache;
+#endif
+ this.Context = new LoggingContext(this);
+ this.Context.Add("BaseRequest", request.Context);
+ this.Context.Add("IsFromCache", isFromCache);
+ }
+
+ public HTTPResponse(HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache, bool isProxyResponse = false)
+ {
+ this.baseRequest = request;
+ this.Stream = stream;
+ this.IsStreamed = isStreamed;
+
+#if !BESTHTTP_DISABLE_CACHING
+ this.IsFromCache = isFromCache;
+ this.IsCacheOnly = request.CacheOnly;
+#endif
+
+ this.IsProxyResponse = isProxyResponse;
+
+ this.IsClosedManually = false;
+
+ this.Context = new LoggingContext(this);
+ this.Context.Add("BaseRequest", request.GetHashCode());
+ this.Context.Add("IsStreamed", isStreamed);
+ this.Context.Add("IsFromCache", isFromCache);
+ }
+
+ public virtual bool Receive(long forceReadRawContentLength = -1, bool readPayloadData = true, bool sendUpgradedEvent = true)
+ {
+ if (this.baseRequest.IsCancellationRequested)
+ return false;
+
+ string statusLine = string.Empty;
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("Receive. forceReadRawContentLength: '{0:N0}', readPayloadData: '{1:N0}'", forceReadRawContentLength, readPayloadData));
+
+ // On WP platform we aren't able to determined sure enough whether the tcp connection is closed or not.
+ // So if we get an exception here, we need to recreate the connection.
+ try
+ {
+ // Read out 'HTTP/1.1' from the "HTTP/1.1 {StatusCode} {Message}"
+ statusLine = ReadTo(Stream, (byte)' ');
+ }
+ catch
+ {
+ if (baseRequest.IsCancellationRequested)
+ return false;
+
+ if (baseRequest.Retries >= baseRequest.MaxRetries)
+ {
+ HTTPManager.Logger.Warning("HTTPResponse", "Failed to read Status Line! Retry is enabled, returning with false.", this.Context, this.baseRequest.Context);
+ return false;
+ }
+
+ HTTPManager.Logger.Warning("HTTPResponse", "Failed to read Status Line! Retry is disabled, re-throwing exception.", this.Context, this.baseRequest.Context);
+ throw;
+ }
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("Status Line: '{0}'", statusLine));
+
+ if (string.IsNullOrEmpty(statusLine))
+ {
+ if (baseRequest.Retries >= baseRequest.MaxRetries)
+ return false;
+
+ throw new Exception("Network error! TCP Connection got closed before receiving any data!");
+ }
+
+ if (!this.IsProxyResponse)
+ baseRequest.Timing.Add(TimingEventNames.Waiting_TTFB);
+
+ string[] versions = statusLine.Split(new char[] { '/', '.' });
+ this.VersionMajor = int.Parse(versions[1]);
+ this.VersionMinor = int.Parse(versions[2]);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("HTTP Version: '{0}.{1}'", this.VersionMajor.ToString(), this.VersionMinor.ToString()));
+
+ int statusCode;
+ string statusCodeStr = NoTrimReadTo(Stream, (byte)' ', LF);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("Status Code: '{0}'", statusCodeStr));
+
+ if (baseRequest.Retries >= baseRequest.MaxRetries)
+ statusCode = int.Parse(statusCodeStr);
+ else if (!int.TryParse(statusCodeStr, out statusCode))
+ return false;
+
+ this.StatusCode = statusCode;
+
+ if (statusCodeStr.Length > 0 && (byte)statusCodeStr[statusCodeStr.Length - 1] != LF && (byte)statusCodeStr[statusCodeStr.Length - 1] != CR)
+ {
+ this.Message = ReadTo(Stream, LF);
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("Status Message: '{0}'", this.Message));
+ }
+ else
+ {
+ HTTPManager.Logger.Warning("HTTPResponse", "Skipping Status Message reading!", this.Context, this.baseRequest.Context);
+
+ this.Message = string.Empty;
+ }
+
+ //Read Headers
+ ReadHeaders(Stream);
+
+ if (!this.IsProxyResponse)
+ baseRequest.Timing.Add(TimingEventNames.Headers);
+
+ IsUpgraded = StatusCode == 101 && (HasHeaderWithValue("connection", "upgrade") || HasHeader("upgrade"));
+
+ if (IsUpgraded)
+ {
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging("Request Upgraded!");
+
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.Upgraded));
+ }
+
+ if (!readPayloadData)
+ return true;
+
+ if (this.StatusCode == 200 && this.IsProxyResponse)
+ return true;
+
+ return ReadPayload(forceReadRawContentLength);
+ }
+
+ protected bool ReadPayload(long forceReadRawContentLength)
+ {
+ // Reading from an already unpacked stream (eq. From a file cache or all responses under webgl)
+ if (forceReadRawContentLength != -1)
+ {
+ ReadRaw(Stream, forceReadRawContentLength);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging("ReadPayload Finished!");
+ return true;
+ }
+
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
+ // 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, and 304 responses and any response to a HEAD request)
+ // is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message.
+ if ((StatusCode >= 100 && StatusCode < 200) || StatusCode == 204 || StatusCode == 304 || baseRequest.MethodType == HTTPMethods.Head)
+ return true;
+
+#if (!UNITY_WEBGL || UNITY_EDITOR)
+ if (HasHeaderWithValue("transfer-encoding", "chunked"))
+ ReadChunked(Stream);
+ else
+#endif
+ {
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
+ // Case 3 in the above link.
+ List contentLengthHeaders = GetHeaderValues("content-length");
+ var contentRangeHeaders = GetHeaderValues("content-range");
+ if (contentLengthHeaders != null && contentRangeHeaders == null)
+ ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
+ else if (contentRangeHeaders != null)
+ {
+ if (contentLengthHeaders != null)
+ ReadRaw(Stream, long.Parse(contentLengthHeaders[0]));
+ else
+ {
+ HTTPRange range = GetRange();
+ ReadRaw(Stream, (range.LastBytePos - range.FirstBytePos) + 1);
+ }
+ }
+ else
+ ReadUnknownSize(Stream);
+ }
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging("ReadPayload Finished!");
+
+ return true;
+ }
+
+ #region Header Management
+
+ protected void ReadHeaders(Stream stream)
+ {
+ string headerName = ReadTo(stream, (byte)':', LF)/*.Trim()*/;
+ while (headerName != string.Empty)
+ {
+ string value = ReadTo(stream, LF);
+
+ if (HTTPManager.Logger.Level == Logger.Loglevels.All)
+ VerboseLogging(string.Format("Header - '{0}': '{1}'", headerName, value));
+
+ AddHeader(headerName, value);
+
+ headerName = ReadTo(stream, (byte)':', LF);
+ }
+
+ if (this.baseRequest.OnHeadersReceived != null)
+ RequestEventHelper.EnqueueRequestEvent(new RequestEventInfo(this.baseRequest, RequestEvents.Headers));
+ }
+
+ public void AddHeader(string name, string value)
+ {
+ name = name.ToLower();
+
+ if (Headers == null)
+ Headers = new Dictionary>();
+
+ List values;
+ if (!Headers.TryGetValue(name, out values))
+ Headers.Add(name, values = new List