mirror of
https://github.com/anticensority/runet-censorship-bypass.git
synced 2024-11-23 18:03:44 +03:00
did the survey, modified extension
This commit is contained in:
parent
6c134cd48f
commit
2bc2412689
1
.gitignore
vendored
Executable file
1
.gitignore
vendored
Executable file
|
@ -0,0 +1 @@
|
|||
node_modules
|
1
Benchmark/.gitignore
vendored
Executable file
1
Benchmark/.gitignore
vendored
Executable file
|
@ -0,0 +1 @@
|
|||
bin
|
9823
Benchmark/Inputs/blocked-hosts.txt
Normal file
9823
Benchmark/Inputs/blocked-hosts.txt
Normal file
File diff suppressed because it is too large
Load Diff
9523
Benchmark/Inputs/blocked-ips.txt
Executable file
9523
Benchmark/Inputs/blocked-ips.txt
Executable file
File diff suppressed because it is too large
Load Diff
10000
Benchmark/Inputs/missed.txt
Executable file
10000
Benchmark/Inputs/missed.txt
Executable file
File diff suppressed because it is too large
Load Diff
47
Benchmark/Output.txt
Executable file
47
Benchmark/Output.txt
Executable file
|
@ -0,0 +1,47 @@
|
|||
blocked-hosts-reversed-binary.js: 371 bytes, 3633268 ns, Missed: 331 bytes, 959025 ns
|
||||
blocked-hosts-switch.js: 35 bytes, 861416 ns, Missed: 54 bytes, 1009980 ns
|
||||
blocked-hosts-binary.js: 355 bytes, 3741660 ns, Missed: 315 bytes, 1101475 ns
|
||||
blocked-hosts-plain-switch.js: 368 bytes, 4595046 ns, Missed: 327 bytes, 1181185 ns
|
||||
|
||||
blocked-ips-indexOf.js: 30 bytes, 4744851 ns, Missed: 50 bytes, 949647 ns
|
||||
blocked-ips-switch.js: 30 bytes, 1324520 ns, Missed: 50 bytes, 954300 ns
|
||||
blocked-ips-binary.js: 30 bytes, 3528872 ns, Missed: 50 bytes, 955016 ns
|
||||
======================
|
||||
blocked-hosts-reversed-binary.js: 60 bytes, 3810837 ns, Missed: 71 bytes, 971006 ns
|
||||
blocked-hosts-switch.js: 90 bytes, 881944 ns, Missed: 54 bytes, 987749 ns
|
||||
blocked-hosts-binary.js: 352 bytes, 3715816 ns, Missed: 311 bytes, 1015559 ns
|
||||
blocked-hosts-plain-switch.js: 367 bytes, 5108743 ns, Missed: 322 bytes, 1057506 ns
|
||||
|
||||
blocked-ips-switch.js: 32 bytes, 1331517 ns, Missed: 48 bytes, 952596 ns
|
||||
blocked-ips-indexOf.js: 36 bytes, 4863773 ns, Missed: 50 bytes, 956231 ns
|
||||
blocked-ips-binary.js: 33 bytes, 3724425 ns, Missed: 52 bytes, 958463 ns
|
||||
======================
|
||||
blocked-hosts-plain-switch.js: 367 bytes, 3895654 ns, Missed: 218 bytes, 590817 ns
|
||||
blocked-hosts-binary.js: 351 bytes, 4559283 ns, Missed: 207 bytes, 595531 ns
|
||||
blocked-hosts-reversed-binary.js: 367 bytes, 3757800 ns, Missed: 96 bytes, 607930 ns
|
||||
blocked-hosts-switch.js: 85 bytes, 909670 ns, Missed: 82 bytes, 665152 ns
|
||||
|
||||
blocked-ips-switch.js: 34 bytes, 1326365 ns, Missed: 72 bytes, 583463 ns
|
||||
blocked-ips-binary.js: 36 bytes, 3567749 ns, Missed: 77 bytes, 583504 ns
|
||||
blocked-ips-indexOf.js: 37 bytes, 4794414 ns, Missed: 77 bytes, 584845 ns
|
||||
======================
|
||||
blocked-hosts-binary.js: 355 bytes, 3669894 ns, Missed: 207 bytes, 593995 ns
|
||||
blocked-hosts-plain-switch.js: 363 bytes, 3977687 ns, Missed: 223 bytes, 594579 ns
|
||||
blocked-hosts-switch.js: 89 bytes, 874404 ns, Missed: 81 bytes, 608442 ns
|
||||
blocked-hosts-reversed-binary.js: 54 bytes, 3706841 ns, Missed: 96 bytes, 622309 ns
|
||||
|
||||
blocked-ips-indexOf.js: 35 bytes, 4839284 ns, Missed: 78 bytes, 587725 ns
|
||||
blocked-ips-switch.js: 43 bytes, 1346437 ns, Missed: 77 bytes, 599972 ns
|
||||
blocked-ips-binary.js: 33 bytes, 3651593 ns, Missed: 80 bytes, 621814 ns
|
||||
======================
|
||||
blocked-hosts-binary.js: 352 bytes, 3711255 ns, Missed: 207 bytes, 590903 ns
|
||||
blocked-hosts-switch.js: 91 bytes, 863216 ns, Missed: 77 bytes, 592086 ns
|
||||
blocked-hosts-reversed-binary.js: 371 bytes, 3690426 ns, Missed: 99 bytes, 595659 ns
|
||||
blocked-hosts-plain-switch.js: 367 bytes, 3933146 ns, Missed: 218 bytes, 618715 ns
|
||||
|
||||
blocked-ips-indexOf.js: 34 bytes, 4861233 ns, Missed: 77 bytes, 592716 ns
|
||||
blocked-ips-switch.js: 30 bytes, 1339607 ns, Missed: 84 bytes, 595159 ns
|
||||
blocked-ips-binary.js: 31 bytes, 3552007 ns, Missed: 76 bytes, 603872 ns
|
||||
======================
|
||||
blocked-hosts-binary-trie.js: 364 bytes, 27134555ns, Missed: 218 bytes, 725467 ns
|
||||
|
119
Benchmark/Program.cs
Normal file
119
Benchmark/Program.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using PacProxyUsage;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class Program
|
||||
{
|
||||
|
||||
public static string RunTest(string hostsFile, string pacName, bool ifMissing = false) {
|
||||
using(StreamReader hostsReader = new StreamReader(hostsFile))
|
||||
{
|
||||
string pacUri = "http://localhost:8080/"+pacName+"?"+new Random().Next();
|
||||
GC.Collect();
|
||||
long memBefore = GC.GetTotalMemory(true);
|
||||
|
||||
var myTimer = new QueryPerfCounter();
|
||||
myTimer.Start();
|
||||
|
||||
int iterations = 0;
|
||||
string host;
|
||||
while( (host = hostsReader.ReadLine()) != null )
|
||||
{
|
||||
string uri = Proxy.GetProxyForUrlUsingPac( "http://"+host, pacUri );
|
||||
if (ifMissing && uri != null || !ifMissing && uri == null)
|
||||
Console.WriteLine("DIRECT !" + host + "!");
|
||||
//else Console.WriteLine("PROXY !" + uri + "!"); // For DEBUG.
|
||||
++iterations;
|
||||
}
|
||||
|
||||
myTimer.Stop();
|
||||
// Calculate time per iteration in nanoseconds
|
||||
double duration = myTimer.Duration(iterations);
|
||||
|
||||
long memAfter = GC.GetTotalMemory(false);
|
||||
long memUsage = memAfter - memBefore;
|
||||
double memPerAddr = memUsage / (double) iterations;
|
||||
|
||||
var resultNs = Convert.ToInt32(duration).ToString();
|
||||
var resultBytes = Convert.ToInt32(memPerAddr).ToString();
|
||||
return string.Format( "{0} bytes, {1} ns", resultBytes, resultNs );
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length != 1 || !Directory.Exists(args[0]))
|
||||
{
|
||||
Console.WriteLine("Arguments: PACs-dir.\nPac scripts must be served from the root of localhost:8080.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool ifAppend = true;
|
||||
using (StreamWriter resultsWriter = new StreamWriter("Output.txt", ifAppend))
|
||||
{
|
||||
resultsWriter.WriteLine("======================");
|
||||
|
||||
var pacsPath = args[0];
|
||||
string [] pacs = Directory.GetFiles(pacsPath);
|
||||
|
||||
foreach(string pacPath in pacs)
|
||||
{
|
||||
string pacName = Path.GetFileName(pacPath);
|
||||
|
||||
Regex r = new Regex(@"^(\w+-\w+)", RegexOptions.IgnoreCase);
|
||||
Match m = r.Match(pacName);
|
||||
var prefix = m.Groups[1].ToString();
|
||||
var hostsFile = "./Inputs/"+prefix + ".txt";
|
||||
var missedFile = "./Inputs/missed.txt";
|
||||
|
||||
var results = pacName+":\t"+RunTest(hostsFile, pacName) + ", Missed: "+RunTest(missedFile, pacName, true);
|
||||
Console.WriteLine(results);
|
||||
resultsWriter.WriteLine(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class QueryPerfCounter
|
||||
{
|
||||
[DllImport("KERNEL32")]
|
||||
private static extern bool QueryPerformanceCounter(
|
||||
out long lpPerformanceCount);
|
||||
|
||||
[DllImport("Kernel32.dll")]
|
||||
private static extern bool QueryPerformanceFrequency(out long lpFrequency);
|
||||
|
||||
private long start;
|
||||
private long stop;
|
||||
private long frequency;
|
||||
Decimal multiplier = new Decimal(1.0e9);
|
||||
|
||||
public QueryPerfCounter()
|
||||
{
|
||||
if (QueryPerformanceFrequency(out frequency) == false)
|
||||
{
|
||||
// Frequency not supported
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
QueryPerformanceCounter(out start);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
QueryPerformanceCounter(out stop);
|
||||
}
|
||||
|
||||
public double Duration(int iterations)
|
||||
{
|
||||
return ((((double)(stop - start)* (double) multiplier) / (double) frequency)/iterations);
|
||||
}
|
||||
}
|
46
Benchmark/Proxy.cs
Executable file
46
Benchmark/Proxy.cs
Executable file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
|
||||
namespace PacProxyUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for Proxy.
|
||||
/// </summary>
|
||||
public class Proxy
|
||||
{
|
||||
public Proxy()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return proxy for requested Url
|
||||
/// </summary>
|
||||
/// <param name="sUrl">url</param>
|
||||
/// <param name="sPacUri">Uri to PAC file</param>
|
||||
/// <returns>proxy info</returns>
|
||||
public static string GetProxyForUrlUsingPac ( string DestinationUrl, string PacUri ){
|
||||
|
||||
IntPtr WinHttpSession = Win32Api.WinHttpOpen("User", Win32Api.WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, IntPtr.Zero, IntPtr.Zero, 0);
|
||||
|
||||
Win32Api.WINHTTP_AUTOPROXY_OPTIONS ProxyOptions = new Win32Api.WINHTTP_AUTOPROXY_OPTIONS();
|
||||
Win32Api.WINHTTP_PROXY_INFO ProxyInfo = new Win32Api.WINHTTP_PROXY_INFO();
|
||||
|
||||
ProxyOptions.fAutoLoginIfChallenged = true;
|
||||
ProxyOptions.dwFlags = Win32Api.WINHTTP_AUTOPROXY_CONFIG_URL;
|
||||
ProxyOptions.dwAutoDetectFlags = (Win32Api.WINHTTP_AUTO_DETECT_TYPE_DHCP | Win32Api.WINHTTP_AUTO_DETECT_TYPE_DNS_A);
|
||||
ProxyOptions.lpszAutoConfigUrl = PacUri;
|
||||
|
||||
// Get Proxy
|
||||
bool IsSuccess = Win32Api.WinHttpGetProxyForUrl( WinHttpSession, DestinationUrl, ref ProxyOptions, ref ProxyInfo );
|
||||
|
||||
Win32Api.WinHttpCloseHandle(WinHttpSession);
|
||||
|
||||
if ( IsSuccess ){
|
||||
return ProxyInfo.lpszProxy;
|
||||
}else {
|
||||
Console.WriteLine("Error: {0}", Win32Api.GetLastError() );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
204
Benchmark/Win32Api.cs
Executable file
204
Benchmark/Win32Api.cs
Executable file
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PacProxyUsage
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for Win32Api.
|
||||
/// </summary>
|
||||
public class Win32Api
|
||||
{
|
||||
#region AutoProxy Constants
|
||||
/// <summary>
|
||||
/// Applies only when setting proxy information
|
||||
/// </summary>
|
||||
public const int WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0;
|
||||
/// <summary>
|
||||
/// Internet accessed through a direct connection
|
||||
/// </summary>
|
||||
public const int WINHTTP_ACCESS_TYPE_NO_PROXY = 1;
|
||||
/// <summary>
|
||||
/// Internet accessed using a proxy
|
||||
/// </summary>
|
||||
public const int WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3;
|
||||
/// <summary>
|
||||
/// Attempt to automatically discover the URL of the
|
||||
/// PAC file using both DHCP and DNS queries to the local network.
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001;
|
||||
/// <summary>
|
||||
/// Download the PAC file from the URL in the WINHTTP_AUTOPROXY_OPTIONS structure.
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002;
|
||||
/// <summary>
|
||||
/// Executes the Web Proxy Auto-Discovery (WPAD) protocol in-process instead of
|
||||
/// delegating to an out-of-process WinHTTP AutoProxy Service, if available.
|
||||
/// This flag must be combined with one of the other flags
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTOPROXY_RUN_INPROCESS = 0x00010000;
|
||||
/// <summary>
|
||||
/// By default, WinHTTP is configured to fall back to auto-discover a proxy
|
||||
/// in-process. If this fallback behavior is undesirable in the event that
|
||||
/// an out-of-process discovery fails, it can be disabled using this flag.
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = 0x00020000;
|
||||
/// <summary>
|
||||
/// Use DHCP to locate the proxy auto-configuration file.
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001;
|
||||
/// <summary>
|
||||
/// Use DNS to attempt to locate the proxy auto-configuration file at a
|
||||
/// well-known location on the domain of the local computer
|
||||
/// </summary>
|
||||
public const int WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002;
|
||||
#endregion
|
||||
|
||||
#region Proxy Structures
|
||||
/// <summary>
|
||||
/// The structure is used to indicate to the WinHttpGetProxyForURL
|
||||
/// function whether to specify the URL of the Proxy Auto-Configuration
|
||||
/// (PAC) file or to automatically locate the URL with DHCP or DNS
|
||||
/// queries to the network
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
||||
public struct WINHTTP_AUTOPROXY_OPTIONS {
|
||||
/// <summary>
|
||||
/// Mechanisms should be used to obtain the PAC file
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public int dwFlags;
|
||||
/// <summary>
|
||||
/// If dwflags includes the WINHTTP_AUTOPROXY_AUTO_DETECT flag,
|
||||
/// then dwAutoDetectFlags specifies what protocols are to be
|
||||
/// used to locate the PAC file. If both the DHCP and DNS auto
|
||||
/// detect flags are specified, then DHCP is used first;
|
||||
/// if no PAC URL is discovered using DHCP, then DNS is used.
|
||||
/// If dwflags does not include the WINHTTP_AUTOPROXY_AUTO_DETECT
|
||||
/// flag, then dwAutoDetectFlags must be zero.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public int dwAutoDetectFlags;
|
||||
/// <summary>
|
||||
/// If dwflags includes the WINHTTP_AUTOPROXY_CONFIG_URL flag, the
|
||||
/// lpszAutoConfigUrl must point to a null-terminated Unicode string
|
||||
/// that contains the URL of the proxy auto-configuration (PAC) file.
|
||||
/// If dwflags does not include the WINHTTP_AUTOPROXY_CONFIG_URL flag,
|
||||
/// then lpszAutoConfigUrl must be NULL.
|
||||
/// </summary>
|
||||
public string lpszAutoConfigUrl;
|
||||
/// <summary>
|
||||
/// Reserved for future use; must be NULL.
|
||||
/// </summary>
|
||||
public IntPtr lpvReserved;
|
||||
/// <summary>
|
||||
/// Reserved for future use; must be zero.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public int dwReserved;
|
||||
/// <summary>
|
||||
/// Specifies whether the client's domain credentials should be automatically
|
||||
/// sent in response to an NTLM or Negotiate Authentication challenge when
|
||||
/// WinHTTP requests the PAC file.
|
||||
/// If this flag is TRUE, credentials should automatically be sent in response
|
||||
/// to an authentication challenge. If this flag is FALSE and authentication
|
||||
/// is required to download the PAC file, the WinHttpGetProxyForUrl fails.
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool fAutoLoginIfChallenged;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The structure contains the session or default proxy configuration.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
||||
public struct WINHTTP_PROXY_INFO {
|
||||
/// <summary>
|
||||
/// Unsigned long integer value that contains the access type
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.U4)]
|
||||
public int dwAccessType;
|
||||
/// <summary>
|
||||
/// Pointer to a string value that contains the proxy server list
|
||||
/// </summary>
|
||||
public string lpszProxy;
|
||||
/// <summary>
|
||||
/// Pointer to a string value that contains the proxy bypass list
|
||||
/// </summary>
|
||||
public string lpszProxyBypass;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region WinHttp
|
||||
/// <summary>
|
||||
/// This function implements the Web Proxy Auto-Discovery (WPAD) protocol
|
||||
/// for automatically configuring the proxy settings for an HTTP request.
|
||||
/// The WPAD protocol downloads a Proxy Auto-Configuration (PAC) file,
|
||||
/// which is a script that identifies the proxy server to use for a given
|
||||
/// target URL. PAC files are typically deployed by the IT department within
|
||||
/// a corporate network environment. The URL of the PAC file can either be
|
||||
/// specified explicitly or WinHttpGetProxyForUrl can be instructed to
|
||||
/// automatically discover the location of the PAC file on the local network.
|
||||
/// </summary>
|
||||
/// <param name="hSession">The WinHTTP session handle returned by the WinHttpOpen function</param>
|
||||
/// <param name="lpcwszUrl">A pointer to a null-terminated Unicode string that contains the
|
||||
/// URL of the HTTP request that the application is preparing to send.</param>
|
||||
/// <param name="pAutoProxyOptions">A pointer to a WINHTTP_AUTOPROXY_OPTIONS structure that
|
||||
/// specifies the auto-proxy options to use.</param>
|
||||
/// <param name="pProxyInfo">A pointer to a WINHTTP_PROXY_INFO structure that receives the
|
||||
/// proxy setting. This structure is then applied to the request handle using the
|
||||
/// WINHTTP_OPTION_PROXY option.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("winhttp.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||||
public static extern bool WinHttpGetProxyForUrl(
|
||||
IntPtr hSession,
|
||||
string lpcwszUrl,
|
||||
ref WINHTTP_AUTOPROXY_OPTIONS pAutoProxyOptions,
|
||||
ref WINHTTP_PROXY_INFO pProxyInfo);
|
||||
|
||||
/// <summary>
|
||||
/// The function initializes, for an application, the use of WinHTTP
|
||||
/// functions and returns a WinHTTP-session handle
|
||||
/// </summary>
|
||||
/// <param name="pwszUserAgent">A pointer to a string variable that contains the name of the
|
||||
/// application or entity calling the WinHTTP functions.</param>
|
||||
/// <param name="dwAccessType">Type of access required. This can be one of the following values</param>
|
||||
/// <param name="pwszProxyName"> A pointer to a string variable that contains the name of the
|
||||
/// proxy server to use when proxy access is specified by setting dwAccessType to
|
||||
/// WINHTTP_ACCESS_TYPE_NAMED_PROXY. The WinHTTP functions recognize only CERN type proxies for HTTP.
|
||||
/// If dwAccessType is not set to WINHTTP_ACCESS_TYPE_NAMED_PROXY, this parameter must be set
|
||||
/// to WINHTTP_NO_PROXY_NAME</param>
|
||||
/// <param name="pwszProxyBypass">A pointer to a string variable that contains an optional list
|
||||
/// of host names or IP addresses, or both, that should not be routed through the proxy when
|
||||
/// dwAccessType is set to WINHTTP_ACCESS_TYPE_NAMED_PROXY. The list can contain wildcard characters.
|
||||
/// Do not use an empty string, because the WinHttpOpen function uses it as the proxy bypass list.
|
||||
/// If this parameter specifies the "<local>" macro as the only entry, this function bypasses
|
||||
/// any host name that does not contain a period. If dwAccessType is not set to WINHTTP_ACCESS_TYPE_NAMED_PROXY,
|
||||
/// this parameter must be set to WINHTTP_NO_PROXY_BYPASS.</param>
|
||||
/// <param name="dwFlags">Unsigned long integer value that contains the flags that indicate various options
|
||||
/// affecting the behavior of this function</param>
|
||||
/// <returns>Returns a valid session handle if successful, or NULL otherwise</returns>
|
||||
[DllImport("winhttp.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||||
public static extern IntPtr WinHttpOpen(
|
||||
string pwszUserAgent,
|
||||
int dwAccessType,
|
||||
IntPtr pwszProxyName,
|
||||
IntPtr pwszProxyBypass,
|
||||
int dwFlags
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// The function closes a single HINTERNET handle
|
||||
/// </summary>
|
||||
/// <param name="hInternet">Valid HINTERNET handle to be closed.</param>
|
||||
/// <returns>Returns TRUE if the handle is successfully closed, or FALSE otherwise</returns>
|
||||
[DllImport("winhttp.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||||
public static extern bool WinHttpCloseHandle(IntPtr hInternet);
|
||||
|
||||
#endregion
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int GetLastError();
|
||||
|
||||
}
|
||||
}
|
10
Benchmark/project.json
Normal file
10
Benchmark/project.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": "0.1-alpha",
|
||||
"authors": ["ilyaigpetrov"],
|
||||
"description": "A wonderful library that does nice stuff",
|
||||
|
||||
"code": ["**/*.cs"],
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
}
|
||||
}
|
14
Benchmark/project.lock.json
Executable file
14
Benchmark/project.lock.json
Executable file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"locked": false,
|
||||
"version": 2,
|
||||
"targets": {
|
||||
"DNX,Version=v4.5.1": {},
|
||||
"DNX,Version=v4.5.1/win7-x86": {},
|
||||
"DNX,Version=v4.5.1/win7-x64": {}
|
||||
},
|
||||
"libraries": {},
|
||||
"projectFileDependencyGroups": {
|
||||
"": [],
|
||||
"DNX,Version=v4.5.1": []
|
||||
}
|
||||
}
|
3
Extensions/Chrome/README.md
Executable file
3
Extensions/Chrome/README.md
Executable file
|
@ -0,0 +1,3 @@
|
|||
# antizapret-chrome-extension
|
||||
|
||||
РосКомНадзор заблокировал очередной сайт?
|
788
Extensions/Chrome/antizapret-chrome-extension/background.js
Executable file
788
Extensions/Chrome/antizapret-chrome-extension/background.js
Executable file
|
@ -0,0 +1,788 @@
|
|||
'use strict';
|
||||
|
||||
var Antizapret = {
|
||||
csvMirrors: [
|
||||
'https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv',
|
||||
'http://sourceforge.net/p/z-i/code-0/HEAD/tree/dump.csv?format=raw',
|
||||
'https://www.assembla.com/spaces/z-i/git/source/master/dump.csv?_format=raw'
|
||||
],
|
||||
lastUpdate: new Date(0),
|
||||
updatePeriodInMinutes: 7*60,
|
||||
rescheduleUpdate: function(period) {
|
||||
var checkNewBlocked = 'Check for new blocked sites';
|
||||
var period = period || this.updatePeriodInMinutes;
|
||||
chrome.alarms.clearAll( (ifWasCleared) => {
|
||||
|
||||
chrome.alarms.create(checkNewBlocked, {
|
||||
delayInMinutes: period
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener( alarm => {
|
||||
if (alarm.name === checkNewBlocked)
|
||||
this.checkForUpdate();
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
checkForUpdates: function(url, cb) {
|
||||
|
||||
if (!url)
|
||||
return this._useTheMirrorsLuke(this.checkForUpdates, url, cb);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
var ifAsync = true;
|
||||
req.open('HEAD', url, ifAsync);
|
||||
req.onload = event => {
|
||||
if (req.status === 404)
|
||||
return cb(req.status);
|
||||
|
||||
var date = Date.parse(req.getResponseHeader('Date'));
|
||||
if (date > this.lastUpdate)
|
||||
return this.update(url, cb);
|
||||
|
||||
return cb(null);
|
||||
};
|
||||
req.send(null);
|
||||
},
|
||||
update: function(url, cb) {
|
||||
|
||||
if (!url)
|
||||
return this._useTheMirrorsLuke(this.update, url, cb);
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
req.onload = event => {
|
||||
this.lastUpdate = Date.now();
|
||||
this.applyCsv( req.responseText );
|
||||
this.rescheduleUpdate();
|
||||
return cb(null, req.responseText);
|
||||
};
|
||||
req.send(null);
|
||||
},
|
||||
applyCsv: function(csv) {
|
||||
var pac = generatePac(csv);
|
||||
console.log(pac);
|
||||
var config = {
|
||||
mode: "pac_script",
|
||||
pacScript: {
|
||||
data: pac
|
||||
}
|
||||
};
|
||||
|
||||
chrome.proxy.settings.set(
|
||||
{value: config, scope: 'regular'}, function() {}
|
||||
);
|
||||
|
||||
},
|
||||
_useTheMirrorsLuke: function(method, url, cb) {
|
||||
|
||||
if (!cb)
|
||||
if (typeof url === 'function') {
|
||||
cb = url;
|
||||
url = undefined;
|
||||
} else cb = () => {};
|
||||
|
||||
var it;
|
||||
if (typeof url === 'string')
|
||||
it = [url][Symbol.iterator]();
|
||||
else
|
||||
it = url || this.csvMirrors[Symbol.iterator]();
|
||||
|
||||
var self = this;
|
||||
function cbb(err, res) {
|
||||
var cur = it.next();
|
||||
if (cur.done || !err)
|
||||
return cb(err, res);
|
||||
|
||||
method.call(self, cur.value, cbb);
|
||||
}
|
||||
|
||||
var url = it.next().value;
|
||||
return method.call(this, url, cbb);
|
||||
}
|
||||
};
|
||||
|
||||
chrome.runtime.onInstalled.addListener( details => {
|
||||
switch(details.reason) {
|
||||
case 'install':
|
||||
case 'update':
|
||||
Antizapret.checkForUpdates()
|
||||
}
|
||||
});
|
||||
|
||||
chrome.browserAction.onClicked.addListener( tab => Antizapret.checkForUpdates() );
|
||||
|
||||
//==============GENERATE-PACS.JS============================
|
||||
|
||||
function generatePac(input) {
|
||||
|
||||
var columnsSep = ';';
|
||||
var valuesSep = /\s*\|\s*/g;
|
||||
|
||||
var ips = [], hosts = [], urls = [], orgs = [], date, reason;
|
||||
|
||||
var lines = input.trim().split(/\r?\n/g).slice(1);
|
||||
lines.forEach( function(line) {
|
||||
var values = line.split( columnsSep );
|
||||
var newIps = values.shift().split( valuesSep );
|
||||
var newHosts = values.shift().split( valuesSep ).map( punycode.toASCII ).map( function(h) { return h.replace(/\.+$/g); } );
|
||||
var newUrls = values.shift().split( valuesSep );
|
||||
var newOrgs = values.shift().split( valuesSep );
|
||||
var newDate = values.pop();
|
||||
var newReason = values.join(';');
|
||||
ips.push.apply(ips, newIps);
|
||||
hosts.push.apply(hosts, newHosts);
|
||||
|
||||
});
|
||||
|
||||
function toHash(arr) {
|
||||
var res = {};
|
||||
arr.forEach( function(el) { res[el] = true; } );
|
||||
return res;
|
||||
}
|
||||
|
||||
var ipsHash = toHash(ips);
|
||||
var hostsHash = toHash(hosts);
|
||||
|
||||
// Remove duplicates and sort.
|
||||
var ips = Object.keys(ipsHash).sort();
|
||||
var hosts = Object.keys(hostsHash).sort();
|
||||
|
||||
function FindProxyForURL(url, host) {
|
||||
// ProstoVPN.AntiZapret PAC-ip File
|
||||
// Generated on Sun Nov 22 10:12:29 MSK 2015
|
||||
|
||||
// The whole PAC script is reevaluated on each call of this function.
|
||||
|
||||
host = host.replace(/\.+$/).toLowerCase(); // E.g. WinHTTP may be nasty.
|
||||
|
||||
// HTTPS proxy is a HTTP proxy over SSL. It is NOT CONNECT proxy!
|
||||
// Supported only in Chrome and Firefox.
|
||||
// http://www.chromium.org/developers/design-documents/secure-web-proxy
|
||||
// This is to bypass FULL DPI
|
||||
var isIE = /*@cc_on!@*/!1;
|
||||
var viaProxy = isIE
|
||||
? 'PROXY proxy.antizapret.prostovpn.org:3128; DIRECT'
|
||||
: 'HTTPS proxy.antizapret.prostovpn.org:3143; PROXY proxy.antizapret.prostovpn.org:3128; DIRECT';
|
||||
|
||||
return IFPROXY() ? viaProxy : 'DIRECT';
|
||||
}
|
||||
|
||||
var pacTemplate = FindProxyForURL.toString();
|
||||
|
||||
function stringifyCall() {
|
||||
var fun = arguments[0];
|
||||
var args = [].slice.call( arguments, 1 )
|
||||
.map( function(a) { return typeof a !== 'string' ? JSON.stringify(a) : a; } ).join(', ');
|
||||
return '('+fun+')('+args+')';
|
||||
}
|
||||
|
||||
// REVERSED HOSTS SWITCH
|
||||
|
||||
function populateTrie(trie, doms) {
|
||||
|
||||
var dom = doms.pop();
|
||||
if (!doms.length || doms.length === 1 && doms[0] === 'www') {
|
||||
trie[''] = trie[''] || [];
|
||||
trie[''].push( dom )
|
||||
return trie;
|
||||
}
|
||||
|
||||
if (trie[''] && trie[''].indexOf(dom) !== -1) // Subdomain of a blocked domain.
|
||||
return trie;
|
||||
|
||||
trie[dom] = trie[dom] || {};
|
||||
|
||||
populateTrie( trie[dom], doms );
|
||||
return trie;
|
||||
}
|
||||
|
||||
var trie = {};
|
||||
hosts.forEach( function(host) {
|
||||
var doms = host.replace(/\.+$/).split('.');
|
||||
populateTrie(trie, doms);
|
||||
});
|
||||
|
||||
function trieToSwitch(trie, indent) {
|
||||
|
||||
var _indent = indent || '';
|
||||
var indent = _indent + ' ';
|
||||
var keys = Object.keys(trie).sort();
|
||||
|
||||
if (!trie[''] && keys.length === 1) {
|
||||
var key = keys[0];
|
||||
return _indent + 'if (doms.pop() === "'+key+'")\n'+ trieToSwitch(trie[key], indent);
|
||||
}
|
||||
|
||||
var cases = '';
|
||||
if (trie['']) {
|
||||
var values = trie[''].sort();
|
||||
|
||||
if (values.length === 1 && keys.length === 1)
|
||||
return _indent + 'return doms.pop() === "'+values[0]+'";\n';
|
||||
|
||||
cases =
|
||||
values.filter( function(v) { return v; } ).map( function(val) { return indent +'case "'+val+'":\n'; } ).join('') + indent +' return true;\n';
|
||||
|
||||
delete trie[''];
|
||||
keys = Object.keys(trie).sort();
|
||||
}
|
||||
|
||||
cases += keys.filter( function(k) { return k; } ).map(
|
||||
function(key) {
|
||||
var tmp = trieToSwitch( trie[key], indent+' ');
|
||||
if (!/^\s*return/.test(tmp))
|
||||
tmp += indent+' break;\n';
|
||||
return indent +'case "'+key+'":\n' +tmp;
|
||||
}).join('');
|
||||
|
||||
return ''
|
||||
+ _indent +'switch( doms.pop() ) {\n'
|
||||
+ cases
|
||||
+ _indent +'}\n';
|
||||
}
|
||||
|
||||
function ifProxyByTrie(host) {
|
||||
var doms = host.replace(/\.+$/).split('.');
|
||||
"{SWITCH}"
|
||||
return false;
|
||||
}
|
||||
|
||||
var ifProxyByTrieStr = ifProxyByTrie.toString().replace('"{SWITCH}"', trieToSwitch(trie, ' '));
|
||||
|
||||
return pacTemplate
|
||||
.replace( 'IFPROXY()', stringifyCall(ifProxyByTrieStr, 'host') );
|
||||
}
|
||||
|
||||
//==============PUNYCODE====================================
|
||||
|
||||
/*! https://mths.be/punycode v1.3.2 by @mathias */
|
||||
;(function(root) {
|
||||
|
||||
/** Detect free variables */
|
||||
var freeExports = typeof exports == 'object' && exports &&
|
||||
!exports.nodeType && exports;
|
||||
var freeModule = typeof module == 'object' && module &&
|
||||
!module.nodeType && module;
|
||||
var freeGlobal = typeof global == 'object' && global;
|
||||
if (
|
||||
freeGlobal.global === freeGlobal ||
|
||||
freeGlobal.window === freeGlobal ||
|
||||
freeGlobal.self === freeGlobal
|
||||
) {
|
||||
root = freeGlobal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `punycode` object.
|
||||
* @name punycode
|
||||
* @type Object
|
||||
*/
|
||||
var punycode,
|
||||
|
||||
/** Highest positive signed 32-bit float value */
|
||||
maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
|
||||
|
||||
/** Bootstring parameters */
|
||||
base = 36,
|
||||
tMin = 1,
|
||||
tMax = 26,
|
||||
skew = 38,
|
||||
damp = 700,
|
||||
initialBias = 72,
|
||||
initialN = 128, // 0x80
|
||||
delimiter = '-', // '\x2D'
|
||||
|
||||
/** Regular expressions */
|
||||
regexPunycode = /^xn--/,
|
||||
regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
|
||||
regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
|
||||
|
||||
/** Error messages */
|
||||
errors = {
|
||||
'overflow': 'Overflow: input needs wider integers to process',
|
||||
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
|
||||
'invalid-input': 'Invalid input'
|
||||
},
|
||||
|
||||
/** Convenience shortcuts */
|
||||
baseMinusTMin = base - tMin,
|
||||
floor = Math.floor,
|
||||
stringFromCharCode = String.fromCharCode,
|
||||
|
||||
/** Temporary variable */
|
||||
key;
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* A generic error utility function.
|
||||
* @private
|
||||
* @param {String} type The error type.
|
||||
* @returns {Error} Throws a `RangeError` with the applicable error message.
|
||||
*/
|
||||
function error(type) {
|
||||
throw new RangeError(errors[type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic `Array#map` utility function.
|
||||
* @private
|
||||
* @param {Array} array The array to iterate over.
|
||||
* @param {Function} callback The function that gets called for every array
|
||||
* item.
|
||||
* @returns {Array} A new array of values returned by the callback function.
|
||||
*/
|
||||
function map(array, fn) {
|
||||
var length = array.length;
|
||||
var result = [];
|
||||
while (length--) {
|
||||
result[length] = fn(array[length]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple `Array#map`-like wrapper to work with domain name strings or email
|
||||
* addresses.
|
||||
* @private
|
||||
* @param {String} domain The domain name or email address.
|
||||
* @param {Function} callback The function that gets called for every
|
||||
* character.
|
||||
* @returns {Array} A new string of characters returned by the callback
|
||||
* function.
|
||||
*/
|
||||
function mapDomain(string, fn) {
|
||||
var parts = string.split('@');
|
||||
var result = '';
|
||||
if (parts.length > 1) {
|
||||
// In email addresses, only the domain name should be punycoded. Leave
|
||||
// the local part (i.e. everything up to `@`) intact.
|
||||
result = parts[0] + '@';
|
||||
string = parts[1];
|
||||
}
|
||||
// Avoid `split(regex)` for IE8 compatibility. See #17.
|
||||
string = string.replace(regexSeparators, '\x2E');
|
||||
var labels = string.split('.');
|
||||
var encoded = map(labels, fn).join('.');
|
||||
return result + encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array containing the numeric code points of each Unicode
|
||||
* character in the string. While JavaScript uses UCS-2 internally,
|
||||
* this function will convert a pair of surrogate halves (each of which
|
||||
* UCS-2 exposes as separate characters) into a single code point,
|
||||
* matching UTF-16.
|
||||
* @see `punycode.ucs2.encode`
|
||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
||||
* @memberOf punycode.ucs2
|
||||
* @name decode
|
||||
* @param {String} string The Unicode input string (UCS-2).
|
||||
* @returns {Array} The new array of code points.
|
||||
*/
|
||||
function ucs2decode(string) {
|
||||
var output = [],
|
||||
counter = 0,
|
||||
length = string.length,
|
||||
value,
|
||||
extra;
|
||||
while (counter < length) {
|
||||
value = string.charCodeAt(counter++);
|
||||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||||
// high surrogate, and there is a next character
|
||||
extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
|
||||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||||
} else {
|
||||
// unmatched surrogate; only append this code unit, in case the next
|
||||
// code unit is the high surrogate of a surrogate pair
|
||||
output.push(value);
|
||||
counter--;
|
||||
}
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string based on an array of numeric code points.
|
||||
* @see `punycode.ucs2.decode`
|
||||
* @memberOf punycode.ucs2
|
||||
* @name encode
|
||||
* @param {Array} codePoints The array of numeric code points.
|
||||
* @returns {String} The new Unicode string (UCS-2).
|
||||
*/
|
||||
function ucs2encode(array) {
|
||||
return map(array, function(value) {
|
||||
var output = '';
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
|
||||
value = 0xDC00 | value & 0x3FF;
|
||||
}
|
||||
output += stringFromCharCode(value);
|
||||
return output;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a basic code point into a digit/integer.
|
||||
* @see `digitToBasic()`
|
||||
* @private
|
||||
* @param {Number} codePoint The basic numeric code point value.
|
||||
* @returns {Number} The numeric value of a basic code point (for use in
|
||||
* representing integers) in the range `0` to `base - 1`, or `base` if
|
||||
* the code point does not represent a value.
|
||||
*/
|
||||
function basicToDigit(codePoint) {
|
||||
if (codePoint - 48 < 10) {
|
||||
return codePoint - 22;
|
||||
}
|
||||
if (codePoint - 65 < 26) {
|
||||
return codePoint - 65;
|
||||
}
|
||||
if (codePoint - 97 < 26) {
|
||||
return codePoint - 97;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a digit/integer into a basic code point.
|
||||
* @see `basicToDigit()`
|
||||
* @private
|
||||
* @param {Number} digit The numeric value of a basic code point.
|
||||
* @returns {Number} The basic code point whose value (when used for
|
||||
* representing integers) is `digit`, which needs to be in the range
|
||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
||||
* used; else, the lowercase form is used. The behavior is undefined
|
||||
* if `flag` is non-zero and `digit` has no uppercase form.
|
||||
*/
|
||||
function digitToBasic(digit, flag) {
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bias adaptation function as per section 3.4 of RFC 3492.
|
||||
* https://tools.ietf.org/html/rfc3492#section-3.4
|
||||
* @private
|
||||
*/
|
||||
function adapt(delta, numPoints, firstTime) {
|
||||
var k = 0;
|
||||
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
||||
delta += floor(delta / numPoints);
|
||||
for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
|
||||
delta = floor(delta / baseMinusTMin);
|
||||
}
|
||||
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
|
||||
* symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycode string of ASCII-only symbols.
|
||||
* @returns {String} The resulting string of Unicode symbols.
|
||||
*/
|
||||
function decode(input) {
|
||||
// Don't use UCS-2
|
||||
var output = [],
|
||||
inputLength = input.length,
|
||||
out,
|
||||
i = 0,
|
||||
n = initialN,
|
||||
bias = initialBias,
|
||||
basic,
|
||||
j,
|
||||
index,
|
||||
oldi,
|
||||
w,
|
||||
k,
|
||||
digit,
|
||||
t,
|
||||
/** Cached calculation results */
|
||||
baseMinusT;
|
||||
|
||||
// Handle the basic code points: let `basic` be the number of input code
|
||||
// points before the last delimiter, or `0` if there is none, then copy
|
||||
// the first basic code points to the output.
|
||||
|
||||
basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) {
|
||||
basic = 0;
|
||||
}
|
||||
|
||||
for (j = 0; j < basic; ++j) {
|
||||
// if it's not a basic code point
|
||||
if (input.charCodeAt(j) >= 0x80) {
|
||||
error('not-basic');
|
||||
}
|
||||
output.push(input.charCodeAt(j));
|
||||
}
|
||||
|
||||
// Main decoding loop: start just after the last delimiter if any basic code
|
||||
// points were copied; start at the beginning otherwise.
|
||||
|
||||
for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
|
||||
|
||||
// `index` is the index of the next character to be consumed.
|
||||
// Decode a generalized variable-length integer into `delta`,
|
||||
// which gets added to `i`. The overflow checking is easier
|
||||
// if we increase `i` as we go, then subtract off its starting
|
||||
// value at the end to obtain `delta`.
|
||||
for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
|
||||
|
||||
if (index >= inputLength) {
|
||||
error('invalid-input');
|
||||
}
|
||||
|
||||
digit = basicToDigit(input.charCodeAt(index++));
|
||||
|
||||
if (digit >= base || digit > floor((maxInt - i) / w)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
i += digit * w;
|
||||
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
|
||||
|
||||
if (digit < t) {
|
||||
break;
|
||||
}
|
||||
|
||||
baseMinusT = base - t;
|
||||
if (w > floor(maxInt / baseMinusT)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
w *= baseMinusT;
|
||||
|
||||
}
|
||||
|
||||
out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi == 0);
|
||||
|
||||
// `i` was supposed to wrap around from `out` to `0`,
|
||||
// incrementing `n` each time, so we'll fix that now:
|
||||
if (floor(i / out) > maxInt - n) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
n += floor(i / out);
|
||||
i %= out;
|
||||
|
||||
// Insert `n` at position `i` of the output
|
||||
output.splice(i++, 0, n);
|
||||
|
||||
}
|
||||
|
||||
return ucs2encode(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string of Unicode symbols (e.g. a domain name label) to a
|
||||
* Punycode string of ASCII-only symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The string of Unicode symbols.
|
||||
* @returns {String} The resulting Punycode string of ASCII-only symbols.
|
||||
*/
|
||||
function encode(input) {
|
||||
var n,
|
||||
delta,
|
||||
handledCPCount,
|
||||
basicLength,
|
||||
bias,
|
||||
j,
|
||||
m,
|
||||
q,
|
||||
k,
|
||||
t,
|
||||
currentValue,
|
||||
output = [],
|
||||
/** `inputLength` will hold the number of code points in `input`. */
|
||||
inputLength,
|
||||
/** Cached calculation results */
|
||||
handledCPCountPlusOne,
|
||||
baseMinusT,
|
||||
qMinusT;
|
||||
|
||||
// Convert the input in UCS-2 to Unicode
|
||||
input = ucs2decode(input);
|
||||
|
||||
// Cache the length
|
||||
inputLength = input.length;
|
||||
|
||||
// Initialize the state
|
||||
n = initialN;
|
||||
delta = 0;
|
||||
bias = initialBias;
|
||||
|
||||
// Handle the basic code points
|
||||
for (j = 0; j < inputLength; ++j) {
|
||||
currentValue = input[j];
|
||||
if (currentValue < 0x80) {
|
||||
output.push(stringFromCharCode(currentValue));
|
||||
}
|
||||
}
|
||||
|
||||
handledCPCount = basicLength = output.length;
|
||||
|
||||
// `handledCPCount` is the number of code points that have been handled;
|
||||
// `basicLength` is the number of basic code points.
|
||||
|
||||
// Finish the basic string - if it is not empty - with a delimiter
|
||||
if (basicLength) {
|
||||
output.push(delimiter);
|
||||
}
|
||||
|
||||
// Main encoding loop:
|
||||
while (handledCPCount < inputLength) {
|
||||
|
||||
// All non-basic code points < n have been handled already. Find the next
|
||||
// larger one:
|
||||
for (m = maxInt, j = 0; j < inputLength; ++j) {
|
||||
currentValue = input[j];
|
||||
if (currentValue >= n && currentValue < m) {
|
||||
m = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
|
||||
// but guard against overflow
|
||||
handledCPCountPlusOne = handledCPCount + 1;
|
||||
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
delta += (m - n) * handledCPCountPlusOne;
|
||||
n = m;
|
||||
|
||||
for (j = 0; j < inputLength; ++j) {
|
||||
currentValue = input[j];
|
||||
|
||||
if (currentValue < n && ++delta > maxInt) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
if (currentValue == n) {
|
||||
// Represent delta as a generalized variable-length integer
|
||||
for (q = delta, k = base; /* no condition */; k += base) {
|
||||
t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
|
||||
if (q < t) {
|
||||
break;
|
||||
}
|
||||
qMinusT = q - t;
|
||||
baseMinusT = base - t;
|
||||
output.push(
|
||||
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
|
||||
);
|
||||
q = floor(qMinusT / baseMinusT);
|
||||
}
|
||||
|
||||
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
||||
bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
|
||||
delta = 0;
|
||||
++handledCPCount;
|
||||
}
|
||||
}
|
||||
|
||||
++delta;
|
||||
++n;
|
||||
|
||||
}
|
||||
return output.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Punycode string representing a domain name or an email address
|
||||
* to Unicode. Only the Punycoded parts of the input will be converted, i.e.
|
||||
* it doesn't matter if you call it on a string that has already been
|
||||
* converted to Unicode.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycoded domain name or email address to
|
||||
* convert to Unicode.
|
||||
* @returns {String} The Unicode representation of the given Punycode
|
||||
* string.
|
||||
*/
|
||||
function toUnicode(input) {
|
||||
return mapDomain(input, function(string) {
|
||||
return regexPunycode.test(string)
|
||||
? decode(string.slice(4).toLowerCase())
|
||||
: string;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Unicode string representing a domain name or an email address to
|
||||
* Punycode. Only the non-ASCII parts of the domain name will be converted,
|
||||
* i.e. it doesn't matter if you call it with a domain that's already in
|
||||
* ASCII.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The domain name or email address to convert, as a
|
||||
* Unicode string.
|
||||
* @returns {String} The Punycode representation of the given domain name or
|
||||
* email address.
|
||||
*/
|
||||
function toASCII(input) {
|
||||
return mapDomain(input, function(string) {
|
||||
return regexNonASCII.test(string)
|
||||
? 'xn--' + encode(string)
|
||||
: string;
|
||||
});
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/** Define the public API */
|
||||
punycode = {
|
||||
/**
|
||||
* A string representing the current Punycode.js version number.
|
||||
* @memberOf punycode
|
||||
* @type String
|
||||
*/
|
||||
'version': '1.3.2',
|
||||
/**
|
||||
* An object of methods to convert from JavaScript's internal character
|
||||
* representation (UCS-2) to Unicode code points, and back.
|
||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
||||
* @memberOf punycode
|
||||
* @type Object
|
||||
*/
|
||||
'ucs2': {
|
||||
'decode': ucs2decode,
|
||||
'encode': ucs2encode
|
||||
},
|
||||
'decode': decode,
|
||||
'encode': encode,
|
||||
'toASCII': toASCII,
|
||||
'toUnicode': toUnicode
|
||||
};
|
||||
|
||||
/** Expose `punycode` */
|
||||
// Some AMD build optimizers, like r.js, check for specific condition patterns
|
||||
// like the following:
|
||||
if (
|
||||
typeof define == 'function' &&
|
||||
typeof define.amd == 'object' &&
|
||||
define.amd
|
||||
) {
|
||||
define('punycode', function() {
|
||||
return punycode;
|
||||
});
|
||||
} else if (freeExports && freeModule) {
|
||||
if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
|
||||
freeModule.exports = punycode;
|
||||
} else { // in Narwhal or RingoJS v0.7.0-
|
||||
for (key in punycode) {
|
||||
punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
|
||||
}
|
||||
}
|
||||
} else { // in Rhino or a web browser
|
||||
root.punycode = punycode;
|
||||
}
|
||||
|
||||
}(this));
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
|
||||
"name": "Антизапрет",
|
||||
"description": "РосКомНадзор заблокировал очередной сайт? Ты не нарушаешь закон! Ты не ребёнок!",
|
||||
"description": "Безопасный Интернет от РосКомНадзор.",
|
||||
"version": "0.1",
|
||||
|
||||
"browser_action": {
|
||||
|
@ -10,7 +10,13 @@
|
|||
"default_title": "Обсуждай на https://reddit.com/r/ru"
|
||||
},
|
||||
"permissions": [
|
||||
"proxy"
|
||||
"proxy",
|
||||
"storage",
|
||||
"alarms",
|
||||
|
||||
"http://*/",
|
||||
"https://*/",
|
||||
"ftp://*/"
|
||||
],
|
||||
"icons": { "16": "unlock.ico",
|
||||
"128": "unlock.png" },
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
4
README.md
Normal file → Executable file
4
README.md
Normal file → Executable file
|
@ -1,2 +1,2 @@
|
|||
# antizapret-chrome-extension
|
||||
РосКомНадзор заблокировал очередной сайт? Ты не нарушаешь закон! Ты не ребёнок!
|
||||
# Антизапрет PAC-скрипт
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var config = {
|
||||
mode: "pac_script",
|
||||
pacScript: {
|
||||
url: "http://antizapret.prostovpn.org/proxy.pac"
|
||||
}
|
||||
};
|
||||
|
||||
chrome.proxy.settings.set(
|
||||
{value: config, scope: 'regular'},
|
||||
function() {}
|
||||
);
|
||||
|
||||
chrome.browserAction.onClicked.addListener(function(tab) {
|
||||
chrome.tabs.create( {url: "http://antizapret.prostovpn.org"} )
|
||||
});
|
331
generate-PACs.js
Normal file
331
generate-PACs.js
Normal file
|
@ -0,0 +1,331 @@
|
|||
var fs = require('fs');
|
||||
|
||||
var input = fs.readFileSync('dump.csv').toString();
|
||||
var outputDir = 'generated-PACs';
|
||||
|
||||
try {
|
||||
fs.mkdirSync( outputDir );
|
||||
} catch(e) {
|
||||
if ( e.code != 'EEXIST' ) throw e;
|
||||
}
|
||||
|
||||
var punycode = require('punycode')
|
||||
|
||||
/*
|
||||
CVS Format:
|
||||
|
||||
IP(s);host(s);URL(s);organization(s);reason;yyyy-mm-dd
|
||||
|
||||
* multiple values are joind by " | "
|
||||
* url may have wierd protocol, e.g.: Newcamd525://
|
||||
|
||||
*/
|
||||
|
||||
var columnsSep = ';';
|
||||
var valuesSep = /\s*\|\s*/g;
|
||||
|
||||
var ips = [], hosts = [], urls = [], orgs = [], date, reason;
|
||||
|
||||
for(var line of input.trim().split(/\r?\n/g).slice(1)) {
|
||||
var values = line.split( columnsSep );
|
||||
var newIps = values.shift().split( valuesSep );
|
||||
var newHosts = values.shift().split( valuesSep ).map( punycode.toASCII ).map( host => host.replace(/\.+$/g) );
|
||||
var newUrls = values.shift().split( valuesSep );
|
||||
var newOrgs = values.shift().split( valuesSep );
|
||||
var newDate = values.pop();
|
||||
var newReason = values.join(';');
|
||||
ips.push.apply(ips, newIps);
|
||||
hosts.push.apply(hosts, newHosts);
|
||||
}
|
||||
|
||||
function toHash(arr) {
|
||||
var res = {};
|
||||
arr.forEach( el => res[el] = true );
|
||||
return res;
|
||||
}
|
||||
|
||||
var ipsHash = toHash(ips);
|
||||
var hostsHash = toHash(hosts);
|
||||
|
||||
// Remove duplicates and sort.
|
||||
var ips = Object.keys(ipsHash).sort();
|
||||
var hosts = Object.keys(hostsHash).sort();
|
||||
|
||||
function FindProxyForURL(url, host) {
|
||||
// ProstoVPN.AntiZapret PAC-ip File
|
||||
// Generated on Sun Nov 22 10:12:29 MSK 2015
|
||||
|
||||
// The whole PAC script is reevaluated on each call of this function.
|
||||
|
||||
host = host.replace(/\.+$/).toLowerCase(); // E.g. WinHTTP may be nasty.
|
||||
|
||||
// HTTPS proxy is a HTTP proxy over SSL. It is NOT CONNECT proxy!
|
||||
// Supported only in Chrome and Firefox.
|
||||
// http://www.chromium.org/developers/design-documents/secure-web-proxy
|
||||
// This is to bypass FULL DPI
|
||||
var isIE = /*@cc_on!@*/!1;
|
||||
var viaProxy = isIE
|
||||
? 'PROXY proxy.antizapret.prostovpn.org:3128; DIRECT'
|
||||
: 'HTTPS proxy.antizapret.prostovpn.org:3143; PROXY proxy.antizapret.prostovpn.org:3128; DIRECT';
|
||||
|
||||
return IFPROXY() ? viaProxy : 'DIRECT';
|
||||
}
|
||||
|
||||
var pacTemplate = FindProxyForURL.toString();
|
||||
|
||||
function stringifyCall() {
|
||||
var fun = arguments[0];
|
||||
var args = [].slice.call( arguments, 1 )
|
||||
.map( a => typeof a !== 'string' ? JSON.stringify(a) : a ).join(', ');
|
||||
return '('+fun+')('+args+')';
|
||||
}
|
||||
|
||||
function produceOutput() {
|
||||
var args = [].slice.call( arguments )
|
||||
var scriptName = args.shift();
|
||||
|
||||
var script = pacTemplate
|
||||
.replace( 'IFPROXY()', stringifyCall.apply(this, args) );
|
||||
fs.writeFileSync(outputDir +'/'+ scriptName +'.js', script);
|
||||
}
|
||||
|
||||
// BLOCKED IPS ARRAY
|
||||
|
||||
function ifProxyByIp(host, blockedIpsArray) {
|
||||
|
||||
// Internet Explorer
|
||||
if (!Array.prototype.indexOf)
|
||||
Array.prototype.indexOf = function(obj, start) {
|
||||
for (var i = (start || 0), j = this.length; i < j; i++)
|
||||
if (this[i] === obj) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return blockedIpsArray.indexOf( dnsResolve(host) ) !== -1
|
||||
}
|
||||
|
||||
produceOutput('blocked-ips-indexOf', ifProxyByIp, 'host', ips);
|
||||
|
||||
// BLOCKED IPS SWITCH
|
||||
|
||||
function ifProxyBySwitch(host) {
|
||||
switch( dnsResolve(host) ) {
|
||||
/*{CASES}*/
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var cases = ips.map( ip => 'case "'+ip+'":' ).join('\n') +'\nreturn true;';
|
||||
var ifProxySwitchStr = ifProxyBySwitch.toString().replace('/*{CASES}*/', cases);
|
||||
|
||||
produceOutput('blocked-ips-switch', ifProxySwitchStr, 'host' );
|
||||
|
||||
// BLOCKED IPS BINARY
|
||||
|
||||
function ifBinaryFound(target, sortedArray) {
|
||||
var istart = 0;
|
||||
var iend = sortedArray.length - 1;
|
||||
|
||||
while (istart < iend) {
|
||||
var imid = istart + Math.floor( (iend - istart)*0.5 );
|
||||
if (target > sortedArray[imid])
|
||||
istart = imid + 1;
|
||||
else
|
||||
iend = imid;
|
||||
}
|
||||
|
||||
return target === sortedArray[iend];
|
||||
}
|
||||
|
||||
produceOutput('blocked-ips-binary', ifBinaryFound, 'dnsResolve(host)', ips);
|
||||
|
||||
// BLOCKED HOSTS BINARY
|
||||
produceOutput('blocked-hosts-binary', ifBinaryFound, 'host', hosts);
|
||||
|
||||
// REVERSED BINARY
|
||||
|
||||
var reverse = str => str.split('').reverse().join('');
|
||||
var reversedHosts = hosts.map( reverse ).sort();
|
||||
|
||||
function ifReversedBinaryFound(host, sortedArray) {
|
||||
target = host.split('').reverse().join('');
|
||||
var istart = 0;
|
||||
var iend = sortedArray.length - 1;
|
||||
|
||||
while (istart < iend) {
|
||||
var imid = istart + Math.floor( (iend - istart)*0.5 );
|
||||
if (target > sortedArray[imid])
|
||||
istart = imid + 1;
|
||||
else
|
||||
iend = imid;
|
||||
}
|
||||
return dnsDomainIs( host, sortedArray[iend].split('').reverse().join('') );
|
||||
}
|
||||
|
||||
produceOutput('blocked-hosts-reversed-binary', ifReversedBinaryFound, 'host', reversedHosts);
|
||||
|
||||
// REVERSED HOSTS SWITCH
|
||||
|
||||
function populateTrie(trie, doms) {
|
||||
|
||||
var dom = doms.pop();
|
||||
if (!doms.length || doms.length === 1 && doms[0] === 'www') {
|
||||
trie[''] = trie[''] || [];
|
||||
trie[''].push( dom )
|
||||
return trie;
|
||||
}
|
||||
|
||||
if (trie[''] && trie[''].indexOf(dom) !== -1) // Subdomain of a blocked domain.
|
||||
return trie;
|
||||
|
||||
trie[dom] = trie[dom] || {};
|
||||
|
||||
populateTrie( trie[dom], doms );
|
||||
return trie;
|
||||
}
|
||||
|
||||
var trie = {};
|
||||
for(var host of hosts) {
|
||||
var doms = host.split('.');
|
||||
populateTrie(trie, doms);
|
||||
}
|
||||
|
||||
function trieToSwitch(trie, indent) {
|
||||
var _indent = indent || '';
|
||||
var indent = _indent + ' ';
|
||||
var keys = Object.keys(trie).sort();
|
||||
|
||||
if (!trie[''] && keys.length === 1) {
|
||||
var key = keys[0];
|
||||
return _indent + 'if (doms.pop() === "'+key+'")\n'+ trieToSwitch(trie[key], indent);
|
||||
}
|
||||
|
||||
var cases = '';
|
||||
if (trie['']) {
|
||||
var values = trie[''].sort();
|
||||
|
||||
if (values.length === 1 && keys.length === 1)
|
||||
return _indent + 'return doms.pop() === "'+values[0]+'";\n';
|
||||
|
||||
cases =
|
||||
values.filter( v => v ).map( val => indent +'case "'+val+'":\n' ).join('') + indent +' return true;\n';
|
||||
|
||||
delete trie[''];
|
||||
keys = Object.keys(trie).sort();
|
||||
}
|
||||
|
||||
cases += keys.filter( k => k ).map(
|
||||
key => {
|
||||
var tmp = trieToSwitch( trie[key], indent+' ');
|
||||
if (!/^\s*return/.test(tmp))
|
||||
tmp += indent+' break;\n';
|
||||
|
||||
return indent +'case "'+key+'":\n' +tmp;
|
||||
}
|
||||
).join('');
|
||||
|
||||
return ''
|
||||
+ _indent +'switch( doms.pop() ) {\n'
|
||||
+ cases
|
||||
+ _indent +'}\n';
|
||||
}
|
||||
|
||||
function ifProxyByTrie(host) {
|
||||
//SWITCH
|
||||
return false;
|
||||
}
|
||||
|
||||
var ifProxyByTrieStr = ifProxyByTrie.toString().replace('//SWITCH', trieToSwitch(trie, ' '));
|
||||
|
||||
produceOutput('blocked-hosts-switch', ifProxyByTrieStr, 'host' );
|
||||
|
||||
/* REVERSED HOSTS BINARY TRIE
|
||||
|
||||
function populateBinTrie(trie, doms) {
|
||||
var dom = doms.pop();
|
||||
if (!doms.length || doms.length === 1 && doms[0] === 'www') {
|
||||
trie[dom] = 'blocked';
|
||||
return trie;
|
||||
}
|
||||
|
||||
if (trie[dom] === 'blocked') // Subdomain of a blocked domain.
|
||||
return trie;
|
||||
|
||||
trie[dom] = trie[dom] || {};
|
||||
populateBinTrie( trie[dom], doms );
|
||||
return trie;
|
||||
}
|
||||
|
||||
var trie = {};
|
||||
for(var host of hosts) {
|
||||
var doms = host.split('.');
|
||||
populateBinTrie(trie, doms);
|
||||
}
|
||||
|
||||
function trie2sorted(trie) {
|
||||
if (trie === 'blocked')
|
||||
return trie;
|
||||
|
||||
var keys = Object.keys(trie).sort();
|
||||
return keys.map( key => trie[key] !== 'blocked' ? [ key, trie2sorted( trie[key] ) ] : [key] );
|
||||
}
|
||||
|
||||
var sortedTrie = trie2sorted(trie);
|
||||
|
||||
function ifProxyByBinTrie(host, sortedTrie) {
|
||||
|
||||
var doms = host.split('.');
|
||||
|
||||
function ifBinaryBlocked(sortedTrie) {
|
||||
var target = doms.pop();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
var istart = 0;
|
||||
var iend = sortedTrie.length - 1;
|
||||
|
||||
while (istart < iend) {
|
||||
var imid = istart + Math.floor( (iend - istart)*0.5 );
|
||||
if (target > sortedTrie[imid][0])
|
||||
istart = imid + 1;
|
||||
else
|
||||
iend = imid;
|
||||
}
|
||||
|
||||
if (target !== sortedTrie[iend][0])
|
||||
return false;
|
||||
|
||||
return sortedTrie[iend].length < 2 ? true : ifBinaryBlocked( sortedTrie[iend][1] );
|
||||
}
|
||||
|
||||
return ifBinaryBlocked(sortedTrie)
|
||||
}
|
||||
|
||||
produceOutput('blocked-hosts-binary-trie', ifProxyByBinTrie, 'host', sortedTrie);
|
||||
|
||||
*/
|
||||
|
||||
// BLOCKED HOSTS PLAIN SWITCH
|
||||
|
||||
function ifProxyByPlainSwitch(host) {
|
||||
|
||||
function ifBlocked(host) {
|
||||
switch( host ) {
|
||||
//CASES
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var doms = host.split('.');
|
||||
|
||||
for( var endi = doms.length; endi >= 0; --endi )
|
||||
if (ifBlocked( doms.slice( endi ).join('.') ))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cases = hosts.map( host => 'case "'+host+'":' ).join('\n') +'\nreturn true;';
|
||||
var tmp = ifProxyByPlainSwitch.toString().replace('//CASES', cases);
|
||||
|
||||
produceOutput('blocked-hosts-plain-switch', tmp, 'host' );
|
46
generated-PACs/blocked-hosts-binary-trie.js
Executable file
46
generated-PACs/blocked-hosts-binary-trie.js
Executable file
File diff suppressed because one or more lines are too long
32
generated-PACs/blocked-hosts-binary.js
Executable file
32
generated-PACs/blocked-hosts-binary.js
Executable file
File diff suppressed because one or more lines are too long
9857
generated-PACs/blocked-hosts-plain-switch.js
Executable file
9857
generated-PACs/blocked-hosts-plain-switch.js
Executable file
File diff suppressed because it is too large
Load Diff
32
generated-PACs/blocked-hosts-reversed-binary.js
Executable file
32
generated-PACs/blocked-hosts-reversed-binary.js
Executable file
File diff suppressed because one or more lines are too long
10130
generated-PACs/blocked-hosts-switch.js
Executable file
10130
generated-PACs/blocked-hosts-switch.js
Executable file
File diff suppressed because it is too large
Load Diff
32
generated-PACs/blocked-ips-binary.js
Executable file
32
generated-PACs/blocked-ips-binary.js
Executable file
File diff suppressed because one or more lines are too long
30
generated-PACs/blocked-ips-indexOf.js
Executable file
30
generated-PACs/blocked-ips-indexOf.js
Executable file
File diff suppressed because one or more lines are too long
9548
generated-PACs/blocked-ips-switch.js
Executable file
9548
generated-PACs/blocked-ips-switch.js
Executable file
File diff suppressed because it is too large
Load Diff
20
package.json
Executable file
20
package.json
Executable file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "subjective-good-is-evil",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate": "node generate-PACs.js",
|
||||
"prestart": "npm run generate",
|
||||
"preserve": "npm run kill",
|
||||
"serve": "START /B http-server generated-PACs",
|
||||
"bench": "cd Benchmark && dnx run ../generated-PACs",
|
||||
"start": "npm run bench",
|
||||
"kill": "taskkill /F /IM node.exe"
|
||||
},
|
||||
"author": "ilyaigpetrov",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"http-server": "^0.8.5"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user