aboutsummaryrefslogtreecommitdiffstats
path: root/tool
diff options
context:
space:
mode:
Diffstat (limited to 'tool')
-rw-r--r--tool/PGListUtil/README.md9
-rw-r--r--tool/PGListUtil/releases/PGListUtil.exebin0 -> 280064 bytes
-rw-r--r--tool/PGListUtil/releases/PGListUtil_en.exebin0 -> 280064 bytes
-rw-r--r--tool/PGListUtil/releases/README.md11
-rw-r--r--tool/PGListUtil/releases/readme.txt138
-rw-r--r--tool/PGListUtil/src/PGListUtil/PGListUtil.cpp9
-rw-r--r--tool/PGListUtil/src/PGListUtil/PGListUtil.exe.manifest22
-rw-r--r--tool/PGListUtil/src/PGListUtil/PGListUtil.h6
-rw-r--r--tool/PGListUtil/src/PGListUtil/ThreadExecute.cpp191
-rw-r--r--tool/PGListUtil/src/PGListUtil/ThreadExecute.h16
-rw-r--r--tool/PGListUtil/src/PGListUtil/WindowMain.cpp440
-rw-r--r--tool/PGListUtil/src/PGListUtil/WindowMain.h39
-rw-r--r--tool/PGListUtil/src/PGListUtil/resource.h42
-rw-r--r--tool/PGListUtil/src/PGListUtil/resource.rc100
-rw-r--r--tool/PGListUtil/src/PGListUtil/stdafx.h12
-rw-r--r--tool/PGListUtil/src/common/CErrorList.cpp171
-rw-r--r--tool/PGListUtil/src/common/CErrorList.h43
-rw-r--r--tool/PGListUtil/src/common/CFilter.cpp125
-rw-r--r--tool/PGListUtil/src/common/CFilter.h34
-rw-r--r--tool/PGListUtil/src/common/CIpList.cpp355
-rw-r--r--tool/PGListUtil/src/common/CIpList.h56
-rw-r--r--tool/PGListUtil/src/common/common.h48
-rw-r--r--tool/README.md2
-rw-r--r--tool/ansero_example.html92
-rw-r--r--tool/anti-cf_filter_adblock/generate.py48
-rw-r--r--tool/anti-cf_hosts_generator/generate.py47
-rw-r--r--tool/block_cloudflare_mitm_fx/LICENSE.md21
-rw-r--r--tool/block_cloudflare_mitm_fx/README.md14
-rw-r--r--tool/block_cloudflare_mitm_fx/src/LICENSE.txt21
-rw-r--r--tool/block_cloudflare_mitm_fx/src/icons/icon-16.pngbin0 -> 508 bytes
-rw-r--r--tool/block_cloudflare_mitm_fx/src/icons/icon-32.pngbin0 -> 1264 bytes
-rw-r--r--tool/block_cloudflare_mitm_fx/src/icons/icon-48.pngbin0 -> 1949 bytes
-rw-r--r--tool/block_cloudflare_mitm_fx/src/icons/icon-64.pngbin0 -> 2371 bytes
-rw-r--r--tool/block_cloudflare_mitm_fx/src/manifest.json42
-rw-r--r--tool/block_cloudflare_mitm_fx/src/setwhitelist.html35
-rw-r--r--tool/block_cloudflare_mitm_fx/src/setwhitelist.js158
-rw-r--r--tool/block_cloudflare_mitm_fx/src/stop_cf_mitm.js370
-rw-r--r--tool/block_cloudflare_mitm_fx/src/style.css12
-rw-r--r--tool/cf_email_decoder/.gitignore51
-rw-r--r--tool/cf_email_decoder/.gitkeep51
-rw-r--r--tool/cf_email_decoder/LICENSE119
-rw-r--r--tool/cf_email_decoder/README.md9
-rw-r--r--tool/cf_email_decoder/cfemail.user.js64
-rw-r--r--tool/cf_email_decoder/readme/compare_decoded_address.pngbin0 -> 3000 bytes
-rw-r--r--tool/cf_email_decoder/readme/compare_decoded_namedlink.pngbin0 -> 15097 bytes
-rw-r--r--tool/cf_email_decoder/readme/compare_encoded_address.pngbin0 -> 2442 bytes
-rw-r--r--tool/cf_email_decoder/readme/compare_encoded_namedlink.pngbin0 -> 53465 bytes
-rw-r--r--tool/cfemail.user.js64
-rw-r--r--tool/cloudflare.onemorestep.template.html62
-rw-r--r--tool/cloudflare_one_more_step.php6
-rw-r--r--tool/example.mdn_basedom_list.txt3060
-rw-r--r--tool/getCFDomainFromList.php37
-rw-r--r--tool/get_fqdn_tmg1.php24
-rw-r--r--tool/globalist/Globalist.py31
-rw-r--r--tool/globalist/ISSUES.md4
-rw-r--r--tool/globalist/README.md64
-rw-r--r--tool/globalist/globalist/__init__.py478
-rw-r--r--tool/globalist/globalist/__pycache__/__init__.cpython-36.pycbin0 -> 9716 bytes
-rw-r--r--tool/globalist/setup.py17
-rw-r--r--tool/irssi_cf_alturl.pl333
-rw-r--r--tool/mastodonwch/README.md105
-rw-r--r--tool/mastodonwch/cron.php80
-rw-r--r--tool/mastodonwch/index.php136
63 files changed, 7524 insertions, 0 deletions
diff --git a/tool/PGListUtil/README.md b/tool/PGListUtil/README.md
new file mode 100644
index 00000000..f1c4c42d
--- /dev/null
+++ b/tool/PGListUtil/README.md
@@ -0,0 +1,9 @@
+### PGListUtil
+
+This tool is useful to convert `CIDR list` to `Range format`, or remove unnecessary lines (such as duplicated ranges) automatically.
+The software was released in the [year 2010](https://live28.5ch.net/test/read.cgi/download/1268903774/10).
+
+Original author said: `You are free to modify or redistribute the source code`
+
+- [Source code](src)
+- [Releases](releases) \ No newline at end of file
diff --git a/tool/PGListUtil/releases/PGListUtil.exe b/tool/PGListUtil/releases/PGListUtil.exe
new file mode 100644
index 00000000..67fd1928
--- /dev/null
+++ b/tool/PGListUtil/releases/PGListUtil.exe
Binary files differ
diff --git a/tool/PGListUtil/releases/PGListUtil_en.exe b/tool/PGListUtil/releases/PGListUtil_en.exe
new file mode 100644
index 00000000..b8fe4508
--- /dev/null
+++ b/tool/PGListUtil/releases/PGListUtil_en.exe
Binary files differ
diff --git a/tool/PGListUtil/releases/README.md b/tool/PGListUtil/releases/README.md
new file mode 100644
index 00000000..f5c888df
--- /dev/null
+++ b/tool/PGListUtil/releases/README.md
@@ -0,0 +1,11 @@
+### Original files
+
+- [PGListUtil.exe](https://codeberg.org/crimeflare/stop_cloudflare/raw/branch/master/tool/PGListUtil/releases/PGListUtil.exe)
+- [readme.txt](readme.txt)
+
+
+
+### English
+
+- [PGListUtil_en.exe](https://codeberg.org/crimeflare/stop_cloudflare/raw/branch/master/tool/PGListUtil/releases/PGListUtil_en.exe)
+- [readme_en.txt](readme_en.txt)
diff --git a/tool/PGListUtil/releases/readme.txt b/tool/PGListUtil/releases/readme.txt
new file mode 100644
index 00000000..3bd0d70c
--- /dev/null
+++ b/tool/PGListUtil/releases/readme.txt
@@ -0,0 +1,138 @@
+
+
+PeerGuardian向けに作られたテキスト形式のリストファイルを
+条件に合わせて新しいファイルに出力することができます
+
+複数のリストファイルをひとつにまとめて出力することもできます
+
+
+===================================================================================
+
+
+コマンドライン引数にファイルパスを指定して起動すると、自動的に入力ファイルパスに追加できます
+複数のパス指定に対応しています
+
+コマンドに関しては「送る(SendTo)」にショートカットを作成して使用することを想定しています
+(お手数ですが手動で作成してください)
+
+リストファイルで1行のサイズが1024バイトを超えると正確な構文解析ができません
+
+
+
+設定はどこにも保存しないのでアンインストールする場合はそのまま削除で問題ありません
+
+ソースコードの改変や転載等は自由に行ってくれても構いません
+
+当ソフトの使用はあくまで自己責任でお願いします
+
+動作環境は(確認していませんが)Windows 2000以降のWindows NT系OS
+動作確認はWindodws XP SP3でのみ行っています
+
+
+===================================================================================
+
+
+主な機能
+
+
+・キャプションフィルター
+
+ 大文字と小文字は区別しません
+ 除外にチェックを入れると、一致したものを除外して出力するようになります
+ 言い方を変えれば一致しないものを出力するようになります
+
+ 何も入力しなければこの機能は無効となります
+
+
+・IPの重複を解消
+
+ デフォルトで開始IPと終了IPが一致するものを出力しません
+ test1:1.2.3.4-5.6.7.8
+ test2:1.2.3.4-5.6.7.8 → test1とtest2のどちらかは出力しません
+
+ 間に含まれるものは重複の対象外として普通に出力します
+ test1:1.2.3.4-5.6.7.100
+ test2:1.2.3.4-5.6.7.10 → 事実上重複ですがtest2は出力されます
+
+
+・ソート機能
+
+ キャプションとIPアドレスでリストをソートすることができます
+ この二つのソートは併用するとキャプション優勢のソートになります
+ 仕様上、重複IPを解消する場合はIPアドレスでソートを有効にしたほうが使用メモリを少なく抑えられます
+
+
+・保存モード
+
+ 「上書き」ファイルが存在する場合、以前のファイルの内容は破棄されます
+ 「追記」 ファイルが存在する場合、以前のファイルの終端に追記されます
+ いずれもファイルが存在しない場合は新しいファイルが作成されます
+
+
+・チェック機能
+
+ 以下のようなありがちなミスをチェックします
+
+ 構文エラー
+ キャプションの最後に : が無い
+ IPアドレスに4桁以上の数字
+ . と , の間違い
+ IPアドレス間の - が無い
+ マスク表記されたIP(例:0.0.0.0/24)
+ 行末の文字
+
+ 不正なIPアドレス
+ IPアドレスに256以上の数字
+ 開始IPが終了IPより大きい
+
+
+・自動修復
+
+ チェック時に「構文エラー (自動修復可)」と出たものは、ファイル出力時に自動的に修復、展開されます
+ :抜け、.と,の間違い、マスク表記されたIPなど
+ これらに当てはまっても数値が不正であればエラーになります
+
+
+他は概ねウィンドウに書かれている通りです
+
+
+===================================================================================
+
+
+更新履歴
+
+
+ver0.7.0
+自動修復機能を追加
+実行メニューに「チェック後、ファイル出力」を追加 (エラーが見つかったらファイル出力を行わない)
+ファイル出力時のIPアドレスのチェックを省略 (問答無用で出力するため「チェック後、ファイル出力」を推奨)
+その他細かい調整
+
+
+ver0.6.1
+視覚スタイルを適用
+入力ファイル追加ダイアログで、ドライブ直下より深いディレクトリでファイルを複数選択した場合、リストに正しく追加できなかった不具合を修正
+
+
+ver0.6.0
+GUIの見直し
+リソースエディタをResEditに変更 (コンパイラはWindows SDK 6以降で変更なし)
+(0.5.0に含まれるresファイルはVC2002で組んだものをWindows SDK 6でコンパイルしたもの)
+DLL化していた機能とチェック機能をUtilに統合し、CUI版チェッカーの配布を停止
+重複IP解消の可否を設定できるようにした
+入力ファイルリストの選択アイテムをDeleteキーで削除できるようにした
+その他細かい調整
+
+
+ver0.5.0
+名前をPG2ListUtilからPGListUtilに変更
+Windowsネイティブに移行 (.NET Frameworkは不要になりました)
+機能の一部をDLLに移行
+保存モード「保存しない」を削除
+構文エラーを吐かないようにした (チェックはCUI版のチェッカーを使用してください)
+キャプションフィルターをANDとORに分けた (今後同時に使用できるようにする可能性もあります)
+キャプションフィルターの正規表現を削除 (今後実装する可能性もあります)
+AND条件の仕様を変更 (文字列が重なる部分も一致するようにした)
+ファイルのドラッグ&ドロップを入力ファイルと出力ファイルに分けた
+
+
diff --git a/tool/PGListUtil/src/PGListUtil/PGListUtil.cpp b/tool/PGListUtil/src/PGListUtil/PGListUtil.cpp
new file mode 100644
index 00000000..6ff52c40
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/PGListUtil.cpp
@@ -0,0 +1,9 @@
+#include <windows.h>
+#include "WindowMain.h"
+#include "resource.h"
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
+ WindowMain::Init(hInstance);
+ DialogBox(hInstance, (LPTSTR)IDD_MAIN, NULL, (DLGPROC)(WindowMain::DlgProcMain));
+ return 0;
+}
diff --git a/tool/PGListUtil/src/PGListUtil/PGListUtil.exe.manifest b/tool/PGListUtil/src/PGListUtil/PGListUtil.exe.manifest
new file mode 100644
index 00000000..278d30f4
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/PGListUtil.exe.manifest
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="X86"
+ name="*.*.PGListUtil"
+ type="win32"
+ />
+ <description>Description</description>
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="X86"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+ </dependency>
+</assembly>
diff --git a/tool/PGListUtil/src/PGListUtil/PGListUtil.h b/tool/PGListUtil/src/PGListUtil/PGListUtil.h
new file mode 100644
index 00000000..cbe04418
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/PGListUtil.h
@@ -0,0 +1,6 @@
+#ifndef PGLISTUTIL_H
+#define PGLISTUTIL_H
+
+extern HINSTANCE hInst;
+
+#endif // PGLISTUTIL_H
diff --git a/tool/PGListUtil/src/PGListUtil/ThreadExecute.cpp b/tool/PGListUtil/src/PGListUtil/ThreadExecute.cpp
new file mode 100644
index 00000000..277ec42f
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/ThreadExecute.cpp
@@ -0,0 +1,191 @@
+#include "WindowMain.h"
+#include "resource.h"
+#include "../common/CErrorList.h"
+#include "../common/CIpList.h"
+
+namespace ThreadExecute {
+
+namespace {
+
+TCHAR g_tsTemp[STRLEN_TEMP];
+TCHAR g_tsPath[STRLEN_PATH];
+
+TCHAR g_tsFailedFileRead[STRLEN_RESULT];
+TCHAR g_tsFailedFileWrite[STRLEN_RESULT];
+TCHAR g_tsOutputComplete[STRLEN_RESULT];
+TCHAR g_tsCheckComplete[STRLEN_RESULT];
+TCHAR g_tsFoundError[STRLEN_RESULT];
+TCHAR g_tsNotFoundError[STRLEN_RESULT];
+TCHAR g_tsErrorSyntax[STRLEN_RESULT];
+TCHAR g_tsErrorIp[STRLEN_RESULT];
+TCHAR g_tsErrorSyntaxRestorable[STRLEN_RESULT];
+
+CHAR *GetMBSfromWS(WCHAR *ws) {
+ int i = WideCharToMultiByte(CP_THREAD_ACP, 0, ws, -1, NULL, 0, NULL, NULL) + 1;
+ if(0 < i) {
+ CHAR *mbs = new CHAR[i];
+ if(!WideCharToMultiByte(CP_THREAD_ACP, 0, ws, -1, mbs, i, NULL, NULL)) {
+ delete[] mbs;
+ mbs = NULL;
+ }
+ return mbs;
+ } else {
+ return NULL;
+ }
+}
+
+char *ReleaseMBS(char *mbs) {
+ if(mbs) {
+ delete[] mbs;
+ mbs = NULL;
+ }
+ return mbs;
+}
+
+bool ExecCheckInfile(HWND hDlg) {
+ char *mbsPath;
+ int incomp = 0;
+ pglu::error::CErrorList errlist;
+
+ WindowMain::Edit_Result_SetText(hDlg, NULL);
+ int iInfileCount = WindowMain::ListView_Infile_GetItemCount(hDlg);
+ for(int iCountInfile = 0; iCountInfile < iInfileCount; ++iCountInfile) {
+ WindowMain::ListView_Infile_GetItemText(hDlg, iCountInfile, g_tsPath);
+ WindowMain::Edit_Result_AppendText(hDlg, g_tsPath);
+ mbsPath = GetMBSfromWS(g_tsPath);
+ if(mbsPath && errlist.LoadListFile(mbsPath)) {
+ int iErrCount = errlist.Count();
+ if(0 < iErrCount) {
+ WindowMain::Edit_Result_AppendText(hDlg, g_tsFoundError);
+ lstrcpy(g_tsTemp, TEXT("\r\n"));
+ for(pglu::error::CError * err = errlist.GetNext(); err; err = errlist.GetNext()) {
+ switch(err->kind) {
+ case pglu::error::SYNTAX:
+ wsprintf(g_tsTemp + 2, g_tsErrorSyntax, err->line);
+ ++incomp;
+ break;
+ case pglu::error::IP:
+ wsprintf(g_tsTemp + 2, g_tsErrorIp, err->line);
+ ++incomp;
+ break;
+ case pglu::error::SYNTAX_RESTORABLE:
+ wsprintf(g_tsTemp + 2, g_tsErrorSyntaxRestorable, err->line);
+ break;
+ }
+ WindowMain::Edit_Result_AppendText(hDlg, g_tsTemp);
+ }
+ } else {
+ WindowMain::Edit_Result_AppendText(hDlg, g_tsNotFoundError);
+ }
+ mbsPath = ReleaseMBS(mbsPath);
+ } else {
+ WindowMain::Edit_Result_AppendText(hDlg, g_tsFailedFileRead);
+ ++incomp;
+ }
+ WindowMain::Edit_Result_AppendText(hDlg, TEXT("\r\n\r\n"));
+ errlist.Clear();
+ }
+ return (incomp == 0);
+}
+
+bool ExecOutput(HWND hDlg) {
+ bool succ = false;
+ char *mbsPath;
+ WindowMain::COption option;
+ pglu::ip::CIpList iplist;
+
+ // 設定取得
+ WindowMain::GetOption(hDlg, &option);
+
+ // フィルタ設定
+ WindowMain::Edit_Filter_GetText(hDlg, g_tsTemp);
+ if(0 < lstrlen(g_tsTemp)) {
+ char *mbsFilter = GetMBSfromWS(g_tsTemp);
+ if(mbsFilter) {
+ pglu::filter::EFilterMode filtMode = (option.filtAnd ? pglu::filter::AND : pglu::filter::OR);
+ iplist.SetFilter(mbsFilter, filtMode, option.filtDel);
+ mbsFilter = ReleaseMBS(mbsFilter);
+ }
+ }
+
+ // 読み込み
+ int iInfileCount = WindowMain::ListView_Infile_GetItemCount(hDlg);
+ for(int iCountInfile = 0; iCountInfile < iInfileCount; ++iCountInfile) {
+ WindowMain::ListView_Infile_GetItemText(hDlg, iCountInfile, g_tsPath);
+ mbsPath = GetMBSfromWS(g_tsPath);
+ if(mbsPath) {
+ succ = iplist.LoadListFile(mbsPath);
+ mbsPath = ReleaseMBS(mbsPath);
+ if(!succ) {
+ // 読み込み失敗
+ WindowMain::Edit_Result_SetText(hDlg, g_tsPath);
+ WindowMain::Edit_Result_SetText(hDlg, g_tsFailedFileRead);
+ return false;
+ }
+ }
+ }
+
+ // チェック ソート
+ iplist.CheckAndSort(option.sortCap, option.sortIp, option.delDupIp);
+
+ // 書き出し
+ WindowMain::Edit_Outfile_GetText(hDlg, g_tsPath);
+ mbsPath = GetMBSfromWS(g_tsPath);
+ if(mbsPath) {
+ succ = iplist.SaveListFile(mbsPath, option.saveAppend);
+ mbsPath = ReleaseMBS(mbsPath);
+ if(!succ) {
+ // 書き込み失敗
+ WindowMain::Edit_Result_SetText(hDlg, g_tsPath);
+ WindowMain::Edit_Result_SetText(hDlg, g_tsFailedFileWrite);
+ return false;
+ }
+ }
+
+ // 実行結果
+ int iIpCount = iplist.Count();
+ int iIpCountDisabled = iplist.CountDisabled();
+ wsprintf(g_tsTemp, g_tsOutputComplete, iIpCount, iIpCountDisabled, iIpCount - iIpCountDisabled);
+ WindowMain::Edit_Result_SetText(hDlg, g_tsTemp);
+
+ return true;
+}
+
+} // namespace
+
+BOOL Init(HINSTANCE hInstance) {
+ LoadString(hInstance, IDS_FAILED_FILE_READ, g_tsFailedFileRead, STRLEN_RESULT);
+ LoadString(hInstance, IDS_FAILED_FILE_WRITE, g_tsFailedFileWrite, STRLEN_RESULT);
+ LoadString(hInstance, IDS_OUTPUT_COMPLETE, g_tsOutputComplete, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_COMPLETE, g_tsCheckComplete, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_FOUND_ERROR, g_tsFoundError, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_NOTFOUND_ERROR, g_tsNotFoundError, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_ERROR_SYNTAX, g_tsErrorSyntax, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_ERROR_IP, g_tsErrorIp, STRLEN_RESULT);
+ LoadString(hInstance, IDS_CHECK_ERROR_SYNTAX_RESTORABLE, g_tsErrorSyntaxRestorable, STRLEN_RESULT);
+ return TRUE;
+}
+
+DWORD WINAPI ThreadExecCheckInfile(LPVOID lpParam) {
+ HWND hDlg = (HWND)lpParam;
+ ExecCheckInfile(hDlg);
+ WindowMain::Dlg_Executing(hDlg, FALSE);
+ ExitThread(TRUE);
+}
+
+DWORD WINAPI ThreadExecOutput(LPVOID lpParam) {
+ HWND hDlg = (HWND)lpParam;
+ ExecOutput(hDlg);
+ WindowMain::Dlg_Executing(hDlg, FALSE);
+ ExitThread(TRUE);
+}
+
+DWORD WINAPI ThreadExecCheckAndOutput(LPVOID lpParam) {
+ HWND hDlg = (HWND)lpParam;
+ if(ExecCheckInfile(hDlg))
+ ExecOutput(hDlg);
+ WindowMain::Dlg_Executing(hDlg, FALSE);
+ ExitThread(TRUE);
+}
+
+} // namespace ThreadExecute
diff --git a/tool/PGListUtil/src/PGListUtil/ThreadExecute.h b/tool/PGListUtil/src/PGListUtil/ThreadExecute.h
new file mode 100644
index 00000000..b34b6253
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/ThreadExecute.h
@@ -0,0 +1,16 @@
+#ifndef THREADEXECUTE_H
+#define THREADEXECUTE_H
+
+#include <windows.h>
+
+namespace ThreadExecute {
+
+BOOL Init(HINSTANCE hInstance);
+
+DWORD WINAPI ThreadExecCheckInfile(LPVOID);
+DWORD WINAPI ThreadExecOutput(LPVOID);
+DWORD WINAPI ThreadExecCheckAndOutput(LPVOID);
+
+}
+
+#endif // THREADEXECUTE_H
diff --git a/tool/PGListUtil/src/PGListUtil/WindowMain.cpp b/tool/PGListUtil/src/PGListUtil/WindowMain.cpp
new file mode 100644
index 00000000..67918d8c
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/WindowMain.cpp
@@ -0,0 +1,440 @@
+#include "stdafx.h"
+#include "WindowMain.h"
+#include "resource.h"
+#include "ThreadExecute.h"
+
+#define WM_USER_EXECUTING WM_USER
+
+namespace WindowMain {
+
+namespace {
+
+TCHAR g_tsTempA[STRLEN_TEMP];
+TCHAR g_tsTempB[STRLEN_TEMP];
+
+HINSTANCE g_hInst;
+
+OPENFILENAME g_ofn = { 0 };
+TCHAR g_tsExpFilt[STRLEN_EXPFILT];
+TCHAR g_tsPath[STRLEN_PATH];
+
+MENUITEMINFO g_mii = { 0 };
+
+TCHAR g_tsSetInfile[STRLEN_RESULT];
+TCHAR g_tsSetOutfile[STRLEN_RESULT];
+TCHAR g_tsExecuting[STRLEN_RESULT];
+
+WNDPROC WndProcListInfileDef;
+WNDPROC WndProcEditOutfileDef;
+
+//--------------------------------------
+// Function
+//--------------------------------------
+
+void Menu_SwapIfChecked(HMENU hMenuMain, int nItemSrc, int nItemCmp) {
+ GetMenuItemInfo(hMenuMain, nItemCmp, FALSE, &g_mii);
+ if(g_mii.fState & MFS_CHECKED) {
+ g_mii.fState = MFS_CHECKED;
+ SetMenuItemInfo(hMenuMain, nItemSrc, FALSE, &g_mii);
+ g_mii.fState = MFS_UNCHECKED;
+ SetMenuItemInfo(hMenuMain, nItemCmp, FALSE, &g_mii);
+ }
+}
+
+void ListView_Infile_AddItem(HWND hListInfile, LPTSTR tsPath, LPTSTR tsFile) {
+ LVITEM lvItem = { 0 };
+ lvItem.mask = LVIF_TEXT;
+
+ lvItem.iSubItem = 0;
+ lvItem.pszText = tsPath;
+ lvItem.iItem = ListView_InsertItem(hListInfile, &lvItem);
+
+ lvItem.iSubItem = 1;
+ lvItem.pszText = tsFile;
+ ListView_SetItem(hListInfile, &lvItem);
+}
+
+void ListView_Infile_AddItem(HWND hListInfile, LPTSTR tsPath) {
+ LPTSTR tsFile = tsPath;
+ for(LPTSTR tsP = tsFile; *tsP != TEXT('\0'); ++tsP)
+ if(*tsP == TEXT('\\'))
+ tsFile = tsP;
+ ListView_Infile_AddItem(hListInfile, tsPath, tsFile + 1);
+}
+
+void ListView_Infile_DelSelItem(HWND hListInfile) {
+ int i = -1;
+ int j = -1;
+
+ while((i = ListView_GetNextItem(hListInfile, i, LVNI_SELECTED)) != -1) {
+ j = i;
+ ListView_DeleteItem(hListInfile, i--);
+ }
+ if(j != -1) {
+ if(0 < (i = ListView_GetItemCount(hListInfile))) {
+ if(j < i) {
+ ListView_SetItemState(hListInfile, j, LVIS_SELECTED, LVIS_SELECTED);
+ } else {
+ ListView_SetItemState(hListInfile, i - 1, LVIS_SELECTED, LVIS_SELECTED);
+ }
+ }
+ }
+}
+
+void ListView_Infile_DelDup(HWND hListInfile) {
+ int i = ListView_GetItemCount(hListInfile);
+ if(1 < i) {
+ for(--i; 0 < i; --i) {
+ ListView_GetItemText(hListInfile, i, 0, g_tsTempA, STRLEN_TEMP);
+ ListView_GetItemText(hListInfile, i - 1, 0, g_tsTempB, STRLEN_TEMP);
+ if(lstrcmpi(g_tsTempA, g_tsTempB) == 0)
+ ListView_DeleteItem(hListInfile, i);
+ }
+ }
+}
+
+BOOL IsEmptyInfile(HWND hListInfile, HWND hEditResult) {
+ if(ListView_GetItemCount(hListInfile) < 1) {
+ SetWindowText(hEditResult, g_tsSetInfile);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+BOOL IsEmptyOutfile(HWND hEditOutfile, HWND hEditResult) {
+ if(GetWindowTextLength(hEditOutfile) < 1) {
+ SetWindowText(hEditResult, g_tsSetOutfile);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+//--------------------------------------
+// Sub Window Procedure
+//--------------------------------------
+
+LRESULT WndProcListInfileSub(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ int i, j;
+
+ switch(msg) {
+
+ case WM_KEYDOWN: {
+ if(wp == VK_DELETE)
+ ListView_Infile_DelSelItem(hWnd);
+ } break;
+
+ case WM_DROPFILES: {
+ j = DragQueryFile((HDROP)wp, -1, NULL, 0);
+ for(i = 0; i < j; ++i) {
+ DragQueryFile((HDROP)wp, i, g_tsTempA, STRLEN_TEMP);
+ ListView_Infile_AddItem(hWnd, g_tsTempA);
+ }
+ ListView_Infile_DelDup(hWnd);
+ } break;
+
+ default:
+ return CallWindowProc(WndProcListInfileDef, hWnd, msg, wp, lp);
+ }
+ return 0;
+}
+
+LRESULT WndProcEditOutfileSub(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ switch(msg) {
+
+ case WM_DROPFILES: {
+ DragQueryFile((HDROP)wp, 0, g_tsTempA, STRLEN_TEMP);
+ SetWindowText(hWnd, g_tsTempA);
+ } break;
+
+ default:
+ return CallWindowProc(WndProcEditOutfileDef, hWnd, msg, wp, lp);
+ }
+ return 0;
+}
+
+} // namespace
+
+//--------------------------------------
+// WindowMain
+//--------------------------------------
+
+BOOL Init(HINSTANCE hInstance) {
+ InitCommonControls();
+
+ g_hInst = hInstance;
+
+ // 文字列読み込み
+ LoadString(hInstance, IDS_SET_INFILE, g_tsSetInfile, STRLEN_RESULT);
+ LoadString(hInstance, IDS_SET_OUTFILE, g_tsSetOutfile, STRLEN_RESULT);
+ LoadString(hInstance, IDS_EXECUTING, g_tsExecuting, STRLEN_RESULT);
+ LoadString(hInstance, IDS_EXPFILT_PGLIST, g_tsExpFilt, STRLEN_EXPFILT);
+ // g_tsExpFiltは@をnull文字に変換
+ for(TCHAR *p = g_tsExpFilt; *p != TEXT('\0'); ++p)
+ if(*p == TEXT('@'))
+ *p = TEXT('\0');
+
+ // MENUITEMINFO設定
+ g_mii.cbSize = sizeof(g_mii);
+ g_mii.fMask = MIIM_STATE;
+
+ // OPENFILENAME設定
+ g_ofn.lStructSize = sizeof(g_ofn);
+ g_ofn.lpstrFilter = g_tsExpFilt;
+ g_ofn.nFilterIndex = 0;
+ g_ofn.lpstrFile = g_tsPath;
+ g_ofn.nMaxFile = STRLEN_PATH;
+
+ // 実行スレッド初期化
+ ThreadExecute::Init(hInstance);
+ return TRUE;
+}
+
+void Dlg_Executing(HWND hDlg, BOOL bExec) {
+ SendMessage(hDlg, WM_USER_EXECUTING, (WPARAM)bExec, 0);
+}
+
+int ListView_Infile_GetItemCount(HWND hDlg) {
+ return ListView_GetItemCount(GetDlgItem(hDlg, IDC_LISTVIEW_INFILE));
+}
+
+BOOL ListView_Infile_GetItemText(HWND hDlg, int nItem, LPTSTR tsPath) {
+ ListView_GetItemText(GetDlgItem(hDlg, IDC_LISTVIEW_INFILE), nItem, 0, tsPath, STRLEN_PATH);
+ return TRUE;
+}
+
+BOOL Edit_Outfile_GetText(HWND hDlg, LPTSTR tsPath) {
+ return GetDlgItemText(hDlg, IDC_EDIT_OUTFILE, tsPath, STRLEN_PATH);
+}
+
+BOOL Edit_Filter_GetText(HWND hDlg, LPTSTR tsText) {
+ return GetDlgItemText(hDlg, IDC_EDIT_FILTER, tsText, STRLEN_PATH);
+}
+
+void Edit_Result_SetText(HWND hDlg, LPCTSTR tsText) {
+ SetDlgItemText(hDlg, IDC_EDIT_RESULT, tsText);
+}
+
+BOOL GetOption(HWND hDlg, COption *option) {
+ HMENU hMenuMain = GetMenu(hDlg);
+
+ GetMenuItemInfo(hMenuMain, IDM_SORT_CAPTION, FALSE, &g_mii);
+ option->sortCap = ((g_mii.fState & MFS_CHECKED) != 0);
+
+ GetMenuItemInfo(hMenuMain, IDM_SORT_IP, FALSE, &g_mii);
+ option->sortIp = ((g_mii.fState & MFS_CHECKED) != 0);
+
+ GetMenuItemInfo(hMenuMain, IDM_CHECK_DUP_IP, FALSE, &g_mii);
+ option->delDupIp = ((g_mii.fState & MFS_CHECKED) != 0);
+
+ GetMenuItemInfo(hMenuMain, IDM_SAVE_APPEND, FALSE, &g_mii);
+ option->saveAppend = ((g_mii.fState & MFS_CHECKED) != 0);
+
+ option->filtAnd = (SendDlgItemMessage(hDlg, IDC_RADIO_FILTER_AND, BM_GETCHECK, 0, 0) == BST_CHECKED);
+ option->filtDel = (SendDlgItemMessage(hDlg, IDC_CHECK_FILTER_DEL, BM_GETCHECK, 0, 0) == BST_CHECKED);
+
+ return TRUE;
+}
+
+void Edit_Result_AppendText(HWND hDlg, LPCTSTR tsText) {
+ HWND hEditResult = GetDlgItem(hDlg, IDC_EDIT_RESULT);
+ int len = GetWindowTextLength(hEditResult);
+ SendMessage(hEditResult, EM_SETSEL, (WPARAM)len, (LPARAM)len);
+ SendMessage(hEditResult, EM_REPLACESEL, 0, (LPARAM)tsText);
+}
+
+BOOL CALLBACK DlgProcMain(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ static HMENU hMenuMain;
+ static HWND hListInfile;
+ static HWND hEditOutfile;
+ static HWND hEditFilter;
+ static HWND hEditResult;
+ static DWORD dwThreadId;
+
+ int i, j;
+
+ switch(msg) {
+
+ case WM_CLOSE: {
+ EndDialog(hWnd, 0);
+ } break;
+
+ case WM_INITDIALOG: {
+ HDC hDC;
+ RECT rect;
+ LVCOLUMN lvCol;
+
+ // 各ハンドル格納
+ hMenuMain = GetMenu(hWnd);
+ hListInfile = GetDlgItem(hWnd, IDC_LISTVIEW_INFILE);
+ hEditOutfile = GetDlgItem(hWnd, IDC_EDIT_OUTFILE);
+ hEditFilter = GetDlgItem(hWnd, IDC_EDIT_FILTER);
+ hEditResult = GetDlgItem(hWnd, IDC_EDIT_RESULT);
+
+ // ウィンドウ位置設定
+ hDC = GetDC(hWnd);
+ GetWindowRect(hWnd, &rect);
+ SetWindowPos(hWnd, HWND_TOP,
+ (GetDeviceCaps(hDC, HORZRES) - (rect.right - rect.left)) / 2,
+ (GetDeviceCaps(hDC, VERTRES) - (rect.bottom - rect.top)) / 2,
+ 0, 0, SWP_NOSIZE);
+ ReleaseDC(hWnd, hDC);
+
+ // リストビュー設定
+ ListView_SetExtendedListViewStyle(hListInfile,
+ LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_LABELTIP);//LVS_EX_INFOTIP);
+
+ int nWidthScroll = GetSystemMetrics(SM_CXVSCROLL);
+ int nWidth3DEdge = GetSystemMetrics(SM_CXEDGE);
+ GetWindowRect(hListInfile, &rect);
+
+ lvCol.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
+ lvCol.fmt = LVCFMT_LEFT;
+
+ lvCol.cx = 72;
+ lvCol.iSubItem = 0;
+ LoadString(g_hInst, IDS_FILE_PATH, g_tsTempA, STRLEN_TEMP);
+ lvCol.pszText = g_tsTempA;
+ ListView_InsertColumn(hListInfile, 0, &lvCol);
+
+ lvCol.cx = (rect.right - rect.left) - 72 - nWidthScroll - nWidth3DEdge * 2;
+ lvCol.iSubItem = 1;
+ LoadString(g_hInst, IDS_FILE_NAME, g_tsTempA, STRLEN_TEMP);
+ lvCol.pszText = g_tsTempA;
+ ListView_InsertColumn(hListInfile, 1, &lvCol);
+
+ // チェック状態
+ SendDlgItemMessage(hWnd, IDC_RADIO_FILTER_AND, BM_SETCHECK, BST_CHECKED, 0);
+
+ #ifdef UNICODE
+ // コマンドライン引数からリストへ追加
+ LPWSTR *wsCmds = CommandLineToArgvW(GetCommandLineW(), &j);
+ for(i = 1; i < j; ++i)
+ ListView_Infile_AddItem(hListInfile, wsCmds[i]);
+ LocalFree((HLOCAL)wsCmds);
+ ListView_Infile_DelDup(hListInfile);
+ #endif
+
+ // サブクラス
+ WndProcListInfileDef = (WNDPROC)GetWindowLong(hListInfile, GWL_WNDPROC);
+ SetWindowLong(hListInfile, GWL_WNDPROC, (LONG)WndProcListInfileSub);
+
+ WndProcEditOutfileDef = (WNDPROC)GetWindowLong(hEditOutfile, GWL_WNDPROC);
+ SetWindowLong(hEditOutfile, GWL_WNDPROC, (LONG)WndProcEditOutfileSub);
+ } break;
+
+ case WM_COMMAND: {
+ switch(LOWORD(wp)) {
+
+ case IDM_EXIT: {
+ EndDialog(hWnd, IDOK);
+ //PostMessage(hWnd, WM_CLOSE, 0, 0);
+ } break;
+
+ case IDM_SORT_CAPTION:
+ case IDM_SORT_IP:
+ case IDM_CHECK_DUP_IP: {
+ GetMenuItemInfo(hMenuMain, LOWORD(wp), FALSE, &g_mii);
+ g_mii.fState = ((g_mii.fState & MFS_CHECKED) ? MFS_UNCHECKED : MFS_CHECKED);
+ SetMenuItemInfo(hMenuMain, LOWORD(wp), FALSE, &g_mii);
+ } break;
+
+ case IDM_SAVE_REPLACE: {
+ Menu_SwapIfChecked(hMenuMain, IDM_SAVE_REPLACE, IDM_SAVE_APPEND);
+ } break;
+
+ case IDM_SAVE_APPEND: {
+ Menu_SwapIfChecked(hMenuMain, IDM_SAVE_APPEND, IDM_SAVE_REPLACE);
+ } break;
+
+ case IDM_CHECK_INFILE: {
+ if(IsEmptyInfile(hListInfile, hEditResult))
+ break;
+ SendMessage(hWnd, WM_USER_EXECUTING, (WPARAM)TRUE, 0);
+ CreateThread(NULL, 0, ThreadExecute::ThreadExecCheckInfile, (LPVOID)hWnd, 0, &dwThreadId);
+ } break;
+
+ case IDM_OUTPUT: {
+ if(IsEmptyInfile(hListInfile, hEditResult) || IsEmptyOutfile(hEditOutfile, hEditResult))
+ break;
+ SendMessage(hWnd, WM_USER_EXECUTING, (WPARAM)TRUE, 0);
+ CreateThread(NULL, 0, ThreadExecute::ThreadExecOutput, (LPVOID)hWnd, 0, &dwThreadId);
+ } break;
+
+ case IDM_CHECK_AND_OUTPUT: {
+ if(IsEmptyInfile(hListInfile, hEditResult) || IsEmptyOutfile(hEditOutfile, hEditResult))
+ break;
+ SendMessage(hWnd, WM_USER_EXECUTING, (WPARAM)TRUE, 0);
+ CreateThread(NULL, 0, ThreadExecute::ThreadExecCheckAndOutput, (LPVOID)hWnd, 0, &dwThreadId);
+ } break;
+
+ case IDC_BUTTON_INFILE_ADD: {
+ // C:\a.txt0
+ // C:\0a.txt0b.txt00
+ // C:\dir0a.txt0b.txt00
+ memset(g_tsPath, 0, STRLEN_PATH);
+ g_ofn.hwndOwner = hWnd;
+ g_ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY;
+ if(GetOpenFileName(&g_ofn)) {
+ if(g_tsPath[g_ofn.nFileOffset - 1] != TEXT('\0'))
+ ListView_Infile_AddItem(hListInfile, g_tsPath, g_tsPath + g_ofn.nFileOffset);
+ else {
+ lstrcpy(g_tsTempA, g_tsPath);
+ LPTSTR tsFile = g_tsTempA + g_ofn.nFileOffset - 1;
+ if(*(tsFile - 1) != TEXT('\\')) {
+ *tsFile = TEXT('\\');
+ ++tsFile;
+ }
+ for(LPTSTR tsP = g_tsPath + g_ofn.nFileOffset; *tsP != TEXT('\0'); ++tsP) {
+ lstrcpy(tsFile, tsP);
+ ListView_Infile_AddItem(hListInfile, g_tsTempA, tsFile);
+ while(*tsP != TEXT('\0'))
+ ++tsP;
+ }
+ }
+ ListView_Infile_DelDup(hListInfile);
+ }
+ } break;
+
+ case IDC_BUTTON_INFILE_DEL: {
+ ListView_Infile_DelSelItem(hListInfile);
+ } break;
+
+ case IDC_BUTTON_INFILE_CLEAR: {
+ ListView_DeleteAllItems(hListInfile);
+ } break;
+
+ case IDC_BUTTON_OUTFILE_REF: {
+ memset(g_tsPath, 0, STRLEN_PATH);
+ g_ofn.hwndOwner = hWnd;
+ g_ofn.Flags = OFN_EXPLORER;
+ if(GetSaveFileName(&g_ofn))
+ SetWindowText(hEditOutfile, g_tsPath);
+ } break;
+
+ case IDC_BUTTON_FILTER_CLEAR: {
+ SetWindowText(hEditFilter, NULL);
+ } break;
+
+ default:
+ return FALSE;
+ }
+ } break;
+
+ case WM_USER_EXECUTING: {
+ if((BOOL)wp) {
+ g_mii.fState = MFS_GRAYED;
+ SetWindowText(hEditResult, g_tsExecuting);
+ } else {
+ g_mii.fState = MFS_ENABLED;
+ }
+ SetMenuItemInfo(hMenuMain, 2, TRUE, &g_mii);
+ DrawMenuBar(hWnd);
+ } break;
+
+ default:
+ return FALSE;
+ }
+ return TRUE;
+}
+
+} // namespace WindowMain
diff --git a/tool/PGListUtil/src/PGListUtil/WindowMain.h b/tool/PGListUtil/src/PGListUtil/WindowMain.h
new file mode 100644
index 00000000..d02c3b7f
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/WindowMain.h
@@ -0,0 +1,39 @@
+#ifndef WINDOWMAIN_H
+#define WINDOWMAIN_H
+
+#include <windows.h>
+
+#define STRLEN_TEMP 1024
+#define STRLEN_PATH 1024
+#define STRLEN_EXPFILT 128
+#define STRLEN_RESULT 64
+
+namespace WindowMain {
+
+struct COption {
+ bool sortCap;
+ bool sortIp;
+ bool delDupIp;
+ bool saveAppend;
+ bool filtAnd;
+ bool filtDel;
+};
+
+BOOL Init(HINSTANCE hInstance);
+
+BOOL CALLBACK DlgProcMain(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
+
+void Dlg_Executing(HWND hDlg, BOOL bExec);
+int ListView_Infile_GetItemCount(HWND hDlg);
+BOOL ListView_Infile_GetItemText(HWND hDlg, int nItem, LPTSTR tsPath);
+BOOL Edit_Outfile_GetText(HWND hDlg, LPTSTR tsPath);
+BOOL Edit_Filter_GetText(HWND hDlg, LPTSTR tsText);
+
+BOOL GetOption(HWND hDlg, COption *option);
+
+void Edit_Result_SetText(HWND hDlg, LPCTSTR tsText);
+void Edit_Result_AppendText(HWND hDlg, LPCTSTR tsText);
+
+}
+
+#endif // WINDOWMAIN_H
diff --git a/tool/PGListUtil/src/PGListUtil/resource.h b/tool/PGListUtil/src/PGListUtil/resource.h
new file mode 100644
index 00000000..ca510e78
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/resource.h
@@ -0,0 +1,42 @@
+#ifndef IDC_STATIC
+#define IDC_STATIC (-1)
+#endif
+
+#define IDC_EDIT_FILTER 1001
+#define IDC_LISTVIEW_INFILE 1002
+#define IDC_BUTTON_INFILE_ADD 1004
+#define IDD_MAIN 1041
+#define IDR_MENU_MAIN 1041
+#define IDM_EXIT 40000
+#define IDS_SET_INFILE 40000
+#define IDM_SORT_CAPTION 40001
+#define IDS_SET_OUTFILE 40001
+#define IDM_SORT_IP 40002
+#define IDS_FAILED_FILE_READ 40002
+#define IDM_CHECK_DUP_IP 40003
+#define IDS_FAILED_FILE_WRITE 40003
+#define IDM_SAVE_REPLACE 40004
+#define IDS_FILE_PATH 40004
+#define IDM_SAVE_APPEND 40005
+#define IDS_FILE_NAME 40005
+#define IDM_CHECK_INFILE 40006
+#define IDS_EXPFILT_PGLIST 40006
+#define IDM_OUTPUT 40007
+#define IDS_OUTPUT_COMPLETE 40007
+#define IDM_CHECK_AND_OUTPUT 40008
+#define IDS_EXECUTING 40008
+#define IDS_CHECK_FOUND_ERROR 40009
+#define IDS_CHECK_NOTFOUND_ERROR 40010
+#define IDS_CHECK_COMPLETE 40011
+#define IDS_CHECK_ERROR_SYNTAX 40012
+#define IDS_CHECK_ERROR_IP 40013
+#define IDS_CHECK_ERROR_SYNTAX_RESTORABLE 40014
+#define IDC_EDIT_OUTFILE 40022
+#define IDC_EDIT_RESULT 40026
+#define IDC_BUTTON_INFILE_DEL 40027
+#define IDC_BUTTON_INFILE_CLEAR 40028
+#define IDC_BUTTON_OUTFILE_REF 40029
+#define IDC_BUTTON_FILTER_CLEAR 40030
+#define IDC_RADIO_FILTER_AND 40031
+#define IDC_RADIO_FILTER_OR 40032
+#define IDC_CHECK_FILTER_DEL 40033
diff --git a/tool/PGListUtil/src/PGListUtil/resource.rc b/tool/PGListUtil/src/PGListUtil/resource.rc
new file mode 100644
index 00000000..b8b5a25c
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/resource.rc
@@ -0,0 +1,100 @@
+// Generated by ResEdit 1.4.4.18
+// Copyright (C) 2006-2008
+// http://www.resedit.net
+
+#include "resource.h"
+#include <windows.h>
+#include <commctrl.h>
+#include <richedit.h>
+
+
+//
+// Menu resources
+//
+IDR_MENU_MAIN MENU
+{
+ POPUP "ファイル(&F)"
+ {
+ MENUITEM "終了(&X)", IDM_EXIT
+ }
+ POPUP "設定(&O)"
+ {
+ MENUITEM "キャプションでソート(&C)", IDM_SORT_CAPTION, CHECKED
+ MENUITEM "IPアドレスでソート(&I)", IDM_SORT_IP, CHECKED
+ MENUITEM SEPARATOR
+ MENUITEM "IPアドレスの重複を解消(&D)", IDM_CHECK_DUP_IP, CHECKED
+ MENUITEM SEPARATOR
+ MENUITEM "上書き保存(&R)", IDM_SAVE_REPLACE, CHECKED
+ MENUITEM "追記保存(&A)", IDM_SAVE_APPEND
+ }
+ POPUP "実行(&E)"
+ {
+ MENUITEM "入力ファイルをチェック(&C)", IDM_CHECK_INFILE
+ MENUITEM SEPARATOR
+ MENUITEM "ファイル出力(&O)", IDM_OUTPUT
+ MENUITEM SEPARATOR
+ MENUITEM "チェック後、ファイル出力(&S)", IDM_CHECK_AND_OUTPUT
+ }
+}
+
+
+
+//
+// Dialog resources
+//
+IDD_MAIN DIALOGEX 0, 0, 334, 228
+STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_GROUP | WS_POPUP | WS_SYSMENU
+EXSTYLE WS_EX_OVERLAPPEDWINDOW
+CAPTION "PGListUtil ver0.7.0"
+MENU IDR_MENU_MAIN
+FONT 9, "MS UI Gothic", 400, 0, 128
+{
+ CONTROL "", IDC_LISTVIEW_INFILE, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_REPORT | LVS_SORTASCENDING, 6, 16, 158, 118, WS_EX_ACCEPTFILES
+ PUSHBUTTON "追加", IDC_BUTTON_INFILE_ADD, 62, 138, 30, 16, BS_PUSHBUTTON
+ PUSHBUTTON "削除", IDC_BUTTON_INFILE_DEL, 98, 138, 30, 16, BS_PUSHBUTTON
+ PUSHBUTTON "クリア", IDC_BUTTON_INFILE_CLEAR, 134, 138, 30, 16, BS_PUSHBUTTON
+ EDITTEXT IDC_EDIT_OUTFILE, 6, 168, 122, 14, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+ PUSHBUTTON "参照", IDC_BUTTON_OUTFILE_REF, 134, 167, 30, 16, BS_PUSHBUTTON
+ EDITTEXT IDC_EDIT_FILTER, 6, 196, 122, 14, ES_AUTOHSCROLL
+ PUSHBUTTON "クリア", IDC_BUTTON_FILTER_CLEAR, 134, 195, 30, 16, BS_PUSHBUTTON
+ AUTORADIOBUTTON "AND", IDC_RADIO_FILTER_AND, 6, 212, 26, 10, WS_GROUP | WS_TABSTOP | BS_AUTORADIOBUTTON
+ AUTORADIOBUTTON "OR", IDC_RADIO_FILTER_OR, 34, 212, 26, 10, WS_TABSTOP | BS_AUTORADIOBUTTON
+ AUTOCHECKBOX "除外", IDC_CHECK_FILTER_DEL, 102, 212, 26, 10, BS_AUTOCHECKBOX
+ EDITTEXT IDC_EDIT_RESULT, 170, 16, 158, 206, WS_VSCROLL | ES_MULTILINE | ES_READONLY
+ LTEXT "入力ファイルリスト", IDC_STATIC, 6, 6, 48, 8, SS_LEFT
+ LTEXT "出力ファイル", IDC_STATIC, 6, 158, 34, 8, SS_LEFT
+ LTEXT "キャプションフィルター", IDC_STATIC, 6, 186, 55, 8, SS_LEFT
+ LTEXT "実行結果", IDC_STATIC, 170, 6, 28, 8, SS_LEFT
+}
+
+
+
+//
+// String Table resources
+//
+STRINGTABLE
+
+{
+ IDS_SET_INFILE "入力ファイルを設定してください"
+ IDS_SET_OUTFILE "出力ファイルを設定してください"
+ IDS_FAILED_FILE_READ " の読み込みに失敗しました"
+ IDS_FAILED_FILE_WRITE " の書き込みに失敗しました"
+ IDS_FILE_PATH "ファイルパス"
+ IDS_FILE_NAME "ファイル名"
+ IDS_EXPFILT_PGLIST "テキストファイル (*.txt)@*.txt@すべてのファイル (*.*)@*.*@@"
+ IDS_OUTPUT_COMPLETE "ファイル出力が完了しました\r\n\r\n入力数 = %d\r\n無効数 = %d\r\n出力数 = %d\r\n"
+ IDS_EXECUTING "実行中..."
+ IDS_CHECK_FOUND_ERROR " に以下のエラーが見つかりました"
+ IDS_CHECK_NOTFOUND_ERROR " に想定内のエラーは見つかりませんでした"
+ IDS_CHECK_COMPLETE "チェックが完了しました"
+ IDS_CHECK_ERROR_SYNTAX "[ %d 行目 ] 構文エラー"
+ IDS_CHECK_ERROR_IP "[ %d 行目 ] 不正なIPアドレス"
+ IDS_CHECK_ERROR_SYNTAX_RESTORABLE "[ %d 行目 ] 構文エラー (自動修復可)"
+}
+
+
+
+//
+// Manifest resources
+//
+1 RT_MANIFEST ".\\PGListUtil.exe.manifest"
diff --git a/tool/PGListUtil/src/PGListUtil/stdafx.h b/tool/PGListUtil/src/PGListUtil/stdafx.h
new file mode 100644
index 00000000..41aaf82a
--- /dev/null
+++ b/tool/PGListUtil/src/PGListUtil/stdafx.h
@@ -0,0 +1,12 @@
+#ifndef PGLU_STDAFX_H
+#define PGLU_STDAFX_H
+
+#include <windows.h>
+
+#ifdef __MINGW32__
+#define _WIN32_IE 0x0600
+#endif
+
+#include <commctrl.h>
+
+#endif // PGLU_STDAFX_H
diff --git a/tool/PGListUtil/src/common/CErrorList.cpp b/tool/PGListUtil/src/common/CErrorList.cpp
new file mode 100644
index 00000000..4dce97bc
--- /dev/null
+++ b/tool/PGListUtil/src/common/CErrorList.cpp
@@ -0,0 +1,171 @@
+#include <stdio.h>
+#include <boost/xpressive/xpressive.hpp>
+#include "CErrorList.h"
+
+namespace pglu {
+namespace error {
+
+CErrorList::CErrorList() :
+ m_pool(sizeof(CError)),
+ m_errFoot(&m_errHead),
+ m_errNext(&m_errHead),
+ m_count(0)
+{
+ m_errHead.line = 0;
+ m_errHead.kind = SYNTAX;
+ m_errHead.next = NULL;
+}
+
+CErrorList::~CErrorList() {
+ Clear();
+}
+
+void CErrorList::Clear() {
+ m_pool.purge_memory();
+ m_errHead.next = NULL;
+ m_errFoot = &m_errHead;
+ m_errNext = &m_errHead;
+ m_count = 0;
+}
+
+bool CErrorList::LoadListFile(const char *path) {
+ char buf[PGLU_LENGTH_FILELINE];
+ uint ip_begin1, ip_begin2, ip_begin3, ip_begin4;
+ uint ip_end1, ip_end2, ip_end3, ip_end4;
+ FILE *fp;
+ CError *err = m_errFoot;
+
+ using namespace boost::xpressive;
+
+ static cmatch match;
+ static mark_tag tagIp1(1), tagIp2(2), tagIp3(3), tagIp4(4), tagIp5(5), tagIp6(6), tagIp7(7), tagIp8(8);
+ static mark_tag tagSep(9), tagMask(10);
+
+ static cregex reSyntax =
+ as_xpr(':') >> *_s >>
+ (tagIp1 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp2 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp3 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp4 = repeat<1, 3>(_d)) >>
+ *_s >> as_xpr('-') >> *_s >>
+ (tagIp5 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp6 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp7 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp8 = repeat<1, 3>(_d)) >>
+ *_s >> (_ln | eos);
+
+ static cregex reSyntaxRestorable =
+ (tagIp1 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp2 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp3 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp4 = repeat<1, 3>(_d)) >> *_s >> (
+ (tagSep = +as_xpr('-')) >> *_s >>
+ (tagIp5 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp6 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp7 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp8 = repeat<1, 3>(_d))
+ |
+ (tagSep = +(set = '/', '\\')) >> *_s >>
+ (tagMask = repeat<1, 2>(_d))
+ ) >> *_s >> (_ln | eos);
+
+#define reIpError repeat<3>(+_d >> *_s >> (set = '.', ',') >> *_s) >> +_d
+
+ static cregex reSyntaxError =
+ reIpError >> *_s >> (
+ *as_xpr('-') >> *_s >> reIpError |
+ +(set = '/', '\\') >> *_s >> +_d
+ );
+
+ fp = fopen(path, "r");
+ if(fp == NULL)
+ return false;
+
+ for(int line = 1; fgets(buf, PGLU_LENGTH_FILELINE, fp); ++line) {
+
+ if(regex_search(buf, match, reSyntax)) {
+ if(!(
+ (ip_begin1 = ParseDigit3(match[1].first, match[1].second)) < 256 &&
+ (ip_begin2 = ParseDigit3(match[2].first, match[2].second)) < 256 &&
+ (ip_begin3 = ParseDigit3(match[3].first, match[3].second)) < 256 &&
+ (ip_begin4 = ParseDigit3(match[4].first, match[4].second)) < 256 &&
+ (ip_end1 = ParseDigit3(match[5].first, match[5].second)) < 256 &&
+ (ip_end2 = ParseDigit3(match[6].first, match[6].second)) < 256 &&
+ (ip_end3 = ParseDigit3(match[7].first, match[7].second)) < 256 &&
+ (ip_end4 = ParseDigit3(match[8].first, match[8].second)) < 256 &&
+ (uint)((ip_begin1 << 24) | (ip_begin2 << 16) | (ip_begin3 << 8) | ip_begin4) <=
+ (uint)((ip_end1 << 24) | (ip_end2 << 16) | (ip_end3 << 8) | ip_end4)
+ )) {
+ ++m_count;
+ err->next = (CError*)m_pool.malloc();
+ err = err->next;
+ err->line = line;
+ err->kind = IP;
+ }
+
+ } else if(regex_search(buf, match, reSyntaxRestorable)) {
+ ++m_count;
+ err->next = (CError*)m_pool.malloc();
+ err = err->next;
+ err->line = line;
+ if(*(match[9].first) == '-') {
+ if(
+ (ip_begin1 = ParseDigit3(match[1].first, match[1].second)) < 256 &&
+ (ip_begin2 = ParseDigit3(match[2].first, match[2].second)) < 256 &&
+ (ip_begin3 = ParseDigit3(match[3].first, match[3].second)) < 256 &&
+ (ip_begin4 = ParseDigit3(match[4].first, match[4].second)) < 256 &&
+ (ip_end1 = ParseDigit3(match[5].first, match[5].second)) < 256 &&
+ (ip_end2 = ParseDigit3(match[6].first, match[6].second)) < 256 &&
+ (ip_end3 = ParseDigit3(match[7].first, match[7].second)) < 256 &&
+ (ip_end4 = ParseDigit3(match[8].first, match[8].second)) < 256 &&
+ (uint)((ip_begin1 << 24) | (ip_begin2 << 16) | (ip_begin3 << 8) | ip_begin4) <=
+ (uint)((ip_end1 << 24) | (ip_end2 << 16) | (ip_end3 << 8) | ip_end4)
+ ) {
+ err->kind = SYNTAX_RESTORABLE;
+ } else {
+ err->kind = SYNTAX;
+ }
+ } else {
+ uint mask = ParseDigit3(match[10].first, match[10].second);
+ if(
+ ParseDigit3(match[1].first, match[1].second) < 256 &&
+ ParseDigit3(match[2].first, match[2].second) < 256 &&
+ ParseDigit3(match[3].first, match[3].second) < 256 &&
+ ParseDigit3(match[4].first, match[4].second) < 256 &&
+ mask < 33
+ ) {
+ err->kind = SYNTAX_RESTORABLE;
+ } else {
+ err->kind = SYNTAX;
+ }
+ }
+ } else if(regex_search(buf, reSyntaxError)) {
+ ++m_count;
+ err->next = (CError*)m_pool.malloc();
+ err = err->next;
+ err->line = line;
+ err->kind = SYNTAX;
+ }
+#ifdef __MINGW32__
+ ZeroString(buf);
+#endif
+ }
+
+ fclose(fp);
+ err->next = NULL;
+ m_errFoot = err;
+ return true;
+}
+
+int CErrorList::Count() {
+ return m_count;
+}
+
+CError * CErrorList::GetNext() {
+ if(m_errNext)
+ m_errNext = m_errNext->next;
+ return m_errNext;
+}
+
+} // namespace error
+} // namespace pglu
diff --git a/tool/PGListUtil/src/common/CErrorList.h b/tool/PGListUtil/src/common/CErrorList.h
new file mode 100644
index 00000000..5583bb8d
--- /dev/null
+++ b/tool/PGListUtil/src/common/CErrorList.h
@@ -0,0 +1,43 @@
+#ifndef CERRLIST_H
+#define CERRLIST_H
+
+#include <boost/pool/pool.hpp>
+#include "common.h"
+
+namespace pglu {
+namespace error {
+
+typedef enum _EErrKind {
+ SYNTAX,
+ IP,
+ SYNTAX_RESTORABLE
+} EErrKind;
+
+typedef struct _CError {
+ int line;
+ EErrKind kind;
+ _CError * next;
+} CError;
+
+class CErrorList {
+private:
+ boost::pool<> m_pool;
+ CError m_errHead;
+ CError * m_errFoot;
+ CError * m_errNext;
+ int m_count;
+
+public:
+ CErrorList();
+ ~CErrorList();
+
+ void Clear();
+ bool LoadListFile(const char *path);
+ int Count();
+ CError * GetNext();
+};
+
+} // namespace error
+} // namespace pglu
+
+#endif // CERRLIST_H
diff --git a/tool/PGListUtil/src/common/CFilter.cpp b/tool/PGListUtil/src/common/CFilter.cpp
new file mode 100644
index 00000000..a6235547
--- /dev/null
+++ b/tool/PGListUtil/src/common/CFilter.cpp
@@ -0,0 +1,125 @@
+#include <ctype.h>
+#include <string.h>
+#include "CFilter.h"
+
+namespace pglu {
+namespace filter {
+
+namespace {
+
+char *strichr(const char *str, int chr) {
+ const char *p = str;
+ chr = tolower((unsigned char)chr);
+ for(; tolower((unsigned char)(*p)) != chr; ++p)
+ if(*p == '\0')
+ return NULL;
+ return (char*)p;
+}
+
+char *stristr(const char *str, const char *pattern) {
+ if(*pattern == '\0')
+ return (char*)str;
+
+ const char *p = str;
+ size_t len = strlen(pattern);
+ for(; (p = strichr(p, pattern[0])) != NULL; ++p)
+ if(!strnicmp(p, pattern, len))
+ return (char*)p;
+ return NULL;
+}
+
+bool search_and(const char *str, const char *terms, const bool del) {
+ do {
+ if(!stristr(str, terms))
+ return del; // not found
+ while(*terms != '\0')
+ ++terms;
+ ++terms;
+ } while(*terms != '\0');
+ return !del; // found all
+}
+
+bool search_or(const char *str, const char *terms, const bool del) {
+ do {
+ if(stristr(str, terms))
+ return !del; // found
+ while(*terms != '\0')
+ ++terms;
+ ++terms;
+ } while(*terms != '\0');
+ return del; // not found
+}
+
+} // namespace
+
+//--------------------------------------
+// CFilter class
+//--------------------------------------
+
+CFilter::CFilter() :
+ m_terms(NULL)
+{
+}
+
+CFilter::CFilter(const char *strFilter, const EFilterMode mode, const bool del) :
+ m_terms(NULL)
+{
+ Assign(strFilter, mode, del);
+}
+
+CFilter::~CFilter() {
+ Clear();
+}
+
+void CFilter::Assign(const char *strFilter, const EFilterMode mode, const bool del) {
+ Clear();
+
+ if(mode == AND)
+ m_search = search_and;
+ else if(mode == OR)
+ m_search = search_or;
+ else
+ return;
+
+ m_mode = mode;
+ m_del = del;
+
+ m_terms = new char[strlen(strFilter) + 2];
+
+ while(*strFilter == ' ' || *strFilter == '\t')
+ ++strFilter;
+
+ char *strTerms = m_terms;
+ while(*strFilter != '\0') {
+ *(strTerms++) = *(strFilter++);
+ if(*strFilter == ' ' || *strFilter == '\t') {
+ *(strTerms++) = '\0';
+ do {
+ ++strFilter;
+ } while(*strFilter == ' ' || *strFilter == '\t');
+ }
+ }
+ *strTerms = '\0';
+ *(strTerms + 1) = '\0';
+}
+
+void CFilter::Clear() {
+ if(m_terms) {
+ delete[] m_terms;
+ m_terms = NULL;
+ }
+}
+
+bool CFilter::IsEmpty() {
+ if(m_terms)
+ return *m_terms == '\0';
+ else
+ return true;
+}
+
+bool CFilter::IsMatch(const char *str) {
+ return m_search(str, m_terms, m_del);
+}
+
+} // namespace filter
+} // namespace pglu
diff --git a/tool/PGListUtil/src/common/CFilter.h b/tool/PGListUtil/src/common/CFilter.h
new file mode 100644
index 00000000..5f52db28
--- /dev/null
+++ b/tool/PGListUtil/src/common/CFilter.h
@@ -0,0 +1,34 @@
+#ifndef CFILTER_H
+#define CFILTER_H
+
+namespace pglu {
+namespace filter {
+
+typedef enum _EFilterMode {
+ AND,
+ OR
+} EFilterMode;
+
+class CFilter {
+private:
+ char * m_terms;
+ EFilterMode m_mode;
+ bool m_del;
+
+ bool (* m_search)(const char *, const char *, const bool);
+
+public:
+ CFilter();
+ CFilter(const char *strFilter, const EFilterMode mode, const bool del);
+ ~CFilter();
+
+ void Assign(const char *strFilter, const EFilterMode mode, const bool del);
+ void Clear();
+ bool IsEmpty();
+ bool IsMatch(const char *str);
+};
+
+} // namespace filter
+} // namespace pglu
+
+#endif // CFILTER_H
diff --git a/tool/PGListUtil/src/common/CIpList.cpp b/tool/PGListUtil/src/common/CIpList.cpp
new file mode 100644
index 00000000..b9eaf800
--- /dev/null
+++ b/tool/PGListUtil/src/common/CIpList.cpp
@@ -0,0 +1,355 @@
+#include <stdio.h>
+#include <string.h>
+#include "CIpList.h"
+
+namespace pglu {
+namespace ip {
+
+namespace {
+
+inline void Ip_Swap(CIp **ipA, CIp **ipB) {
+ CIp *ip = *ipA;
+ *ipA = *ipB;
+ *ipB = ip;
+}
+
+void Ip_SortByIpQuick(CIp **ipBegin, CIp **ipEnd) {
+ CIp **ipA;
+ CIp **ipB;
+ int nGap = ipEnd - ipBegin;
+
+ if(nGap < 64)
+ return;
+
+ ipA = ipBegin + ((int)(nGap / 2));
+ if((*ipBegin)->ip64 > (*ipA)->ip64)
+ Ip_Swap(ipBegin, ipA);
+ if((*ipBegin)->ip64 > (*ipEnd)->ip64)
+ Ip_Swap(ipBegin, ipEnd);
+ if((*ipA)->ip64 > (*ipEnd)->ip64)
+ Ip_Swap(ipA, ipEnd);
+ ulong ip64 = (*ipA)->ip64;
+
+ ipB = ipEnd - 1;
+ Ip_Swap(ipA, ipB);
+ ipA = ipBegin;
+
+ for(; ; ) {
+ while((*(++ipA))->ip64 < ip64);
+ while((*(--ipB))->ip64 > ip64);
+ if(ipA > ipB)
+ break;
+ Ip_Swap(ipA, ipB);
+ }
+ Ip_Swap(ipA, ipEnd - 1);
+
+ Ip_SortByIpQuick(ipBegin, ipB);
+ Ip_SortByIpQuick(ipA + 1, ipEnd);
+}
+
+void Ip_SortByIpInsert(CIp **ipBegin, CIp **ipEnd) {
+ CIp **ipA = ipBegin + 1;
+ CIp **ipB;
+ CIp **ipAEnd = ipEnd + 1;
+ CIp **ipBEnd = ipBegin - 1;
+ ulong ip64;
+ for(; ipA != ipAEnd; ++ipA) {
+ ip64 = (*ipA)->ip64;
+ for(ipB = ipA - 1; ipB != ipBEnd && (*ipB)->ip64 > ip64; --ipB)
+ Ip_Swap(ipB, ipB + 1);
+ }
+}
+
+void Ip_SortByIp(CIp **ipBegin, CIp **ipEnd) {
+ Ip_SortByIpQuick(ipBegin, ipEnd);
+ Ip_SortByIpInsert(ipBegin, ipEnd);
+}
+
+CIp * Ip_SortByCaption(CIp *ipHeadA) {
+ if(!ipHeadA || !(ipHeadA->next))
+ return ipHeadA;
+
+ // split ipBを2倍で進めることでipAを中間位置に持っていく
+ CIp *ipA = ipHeadA;
+ CIp *ipB = ipHeadA->next->next;
+ while(ipB) {
+ ipA = ipA->next;
+ ipB = ipB->next;
+ if(ipB)
+ ipB = ipB->next;
+ }
+ CIp *ipHeadB = ipA->next;
+ ipA->next = NULL;
+
+ ipHeadA = Ip_SortByCaption(ipHeadA);
+ ipHeadB = Ip_SortByCaption(ipHeadB);
+
+ // merge
+ CIp ipMerged;
+ ipA = &ipMerged;
+ while(ipHeadA || ipHeadB) {
+ if(((ipHeadA && ipHeadB) && stricmp(ipHeadA->caption, ipHeadB->caption) <= 0) || !ipHeadB) {
+ ipA->next = ipHeadA;
+ ipHeadA = ipHeadA->next;
+ } else {
+ ipA->next = ipHeadB;
+ ipHeadB = ipHeadB->next;
+ }
+ ipA = ipA->next;
+ }
+ ipA->next = NULL;
+
+ return ipMerged.next;
+}
+
+} // namespace
+
+//--------------------------------------
+// CIpList class
+//--------------------------------------
+
+CIpList::CIpList() :
+ m_poolIp(sizeof(CIp)),
+ m_ipFoot(&m_ipHead),
+ m_count(0),
+ m_countDisabled(0)
+{
+ m_ipHead.caption = NULL;
+ m_ipHead.ip64 = 0L;
+ m_ipHead.next = NULL;
+}
+
+CIpList::~CIpList() {
+ Clear();
+}
+
+void CIpList::Clear() {
+ for(CIp *ip = m_ipHead.next; ip; ip = ip->next)
+ delete[] ip->caption;
+ m_poolIp.purge_memory();
+ m_ipHead.next = NULL;
+ m_ipFoot = &m_ipHead;
+ m_count = 0;
+ m_countDisabled = 0;
+ UnSetFilter();
+}
+
+void CIpList::SetFilter(const char *filter, const filter::EFilterMode mode, const bool del) {
+ m_filter.Assign(filter, mode, del);
+}
+
+void CIpList::UnSetFilter() {
+ m_filter.Clear();
+}
+
+CIp * CIpList::CreateIp(boost::xpressive::cmatch & match) {
+ CIp *ip = (CIp*)m_poolIp.malloc();
+
+ const char *capBegin = match.prefix().first;
+ const char *capEnd = match[9].first;
+ size_t lenCap = capEnd - capBegin;
+ char *chunk = new char[lenCap + 1];
+ memcpy(chunk, capBegin, lenCap);
+ *(chunk + lenCap) = '\0';
+ ip->caption = chunk;
+
+ uchar *ip8 = ip->ip8;
+ ip8[4] = ParseDigit3(match[4].first, match[4].second);
+ ip8[5] = ParseDigit3(match[3].first, match[3].second);
+ ip8[6] = ParseDigit3(match[2].first, match[2].second);
+ ip8[7] = ParseDigit3(match[1].first, match[1].second);
+
+ if(*(match[10].first) == '-') {
+ ip8[0] = ParseDigit3(match[8].first, match[8].second);
+ ip8[1] = ParseDigit3(match[7].first, match[7].second);
+ ip8[2] = ParseDigit3(match[6].first, match[6].second);
+ ip8[3] = ParseDigit3(match[5].first, match[5].second);
+ } else {
+ ip->ip32[0] = ip->ip32[1] | (0xFFFFFFFF >> ParseDigit3(match[11].first, match[11].second));
+ }
+
+ return ip;
+}
+
+bool CIpList::LoadListFile(const char *path) {
+ char buf[PGLU_LENGTH_FILELINE];
+ char colon;
+ char *colon_p;
+
+ using namespace boost::xpressive;
+
+ static cmatch match;
+ static mark_tag tagIp1(1), tagIp2(2), tagIp3(3), tagIp4(4), tagIp5(5), tagIp6(6), tagIp7(7), tagIp8(8);
+ static mark_tag tagColon(9), tagSep(10), tagMask(11);
+
+ static cregex reSyntax = // bos >> (tag = *_) >> // slower
+ (tagColon = as_xpr(':')) >> *_s >>
+ (tagIp1 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp2 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp3 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp4 = repeat<1, 3>(_d)) >>
+ *_s >> (tagSep = as_xpr('-')) >> *_s >>
+ (tagIp5 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp6 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp7 = repeat<1, 3>(_d)) >> as_xpr('.') >>
+ (tagIp8 = repeat<1, 3>(_d)) >>
+ *_s >> (_ln | eos);
+
+ static cregex reSyntaxRestorable =
+ (tagColon = !as_xpr(':')) >> *_s >>
+ (tagIp1 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp2 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp3 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp4 = repeat<1, 3>(_d)) >> *_s >> (
+ (tagSep = +as_xpr('-')) >> *_s >>
+ (tagIp5 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp6 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp7 = repeat<1, 3>(_d)) >> (set = '.', ',') >>
+ (tagIp8 = repeat<1, 3>(_d))
+ |
+ (tagSep = +(set = '/', '\\')) >> *_s >>
+ (tagMask = repeat<1, 2>(_d))
+ ) >> *_s >> (_ln | eos);
+
+ FILE *fp = fopen(path, "r");
+ if(fp == NULL)
+ return false;
+
+ CIp *ip = m_ipFoot;
+
+ if(m_filter.IsEmpty()) {
+ while(fgets(buf, PGLU_LENGTH_FILELINE, fp)) {
+
+ if(regex_search(buf, match, reSyntax) || regex_search(buf, match, reSyntaxRestorable)) {
+ ++m_count;
+ ip->next = CreateIp(match);
+ ip = ip->next;
+ }
+#ifdef __MINGW32__
+ ZeroString(buf);
+#endif
+ }
+ } else {
+ while(fgets(buf, PGLU_LENGTH_FILELINE, fp)) {
+
+ if(regex_search(buf, match, reSyntax) || regex_search(buf, match, reSyntaxRestorable)) {
+ colon_p = (char*)(match[9].first);
+ colon = *colon_p;
+ *colon_p = '\0';
+
+ if(m_filter.IsMatch(buf)) {
+ ++m_count;
+ ip->next = CreateIp(match);
+ ip = ip->next;
+ }
+ *colon_p = colon;
+ }
+#ifdef __MINGW32__
+ ZeroString(buf);
+#endif
+ }
+ }
+ fclose(fp);
+ ip->next = NULL;
+ m_ipFoot = ip;
+ return true;
+}
+
+bool CIpList::SaveListFile(const char *path, const bool append) {
+ uchar *ip8;
+
+ FILE *fp = fopen(path, (append ? "a" : "w"));
+ if(fp == NULL)
+ return false;
+
+ for(CIp *ip = m_ipHead.next; ip; ip = ip->next) {
+ // IPが0Lなら書き出さない
+ if(ip->ip64 != 0L) {
+ ip8 = ip->ip8;
+ fprintf(fp,
+ "%s:%u.%u.%u.%u-%u.%u.%u.%u\n",
+ ip->caption,
+ ip8[7], ip8[6], ip8[5], ip8[4],
+ ip8[3], ip8[2], ip8[1], ip8[0]
+ );
+ }
+ }
+ fclose(fp);
+ return true;
+}
+
+void CIpList::CheckAndSort(const bool sortCap, const bool sortIp, const bool delDupIp) {
+ CIp **ipBegin;
+ CIp **ipEnd;
+ CIp *ip;
+
+ if(m_count < 2)
+ return;
+
+ if(sortIp || delDupIp) {
+ // リストから配列を複製
+ CIp **ipSort = new CIp*[m_count];
+ ipBegin = ipSort;
+ for(ip = m_ipHead.next; ip; ip = ip->next)
+ *(ipBegin++) = ip;
+
+ // 配列をソート
+ Ip_SortByIp(ipSort, ipSort + m_count - 1);
+
+ if(delDupIp) {
+ // すでにIPが0Lなものを無効としてカウント
+ ipBegin = ipSort;
+ ipEnd = ipBegin + m_count;
+
+ for(; ipBegin != ipEnd && (*ipBegin)->ip64 == 0L; ++ipBegin)
+ ++m_countDisabled;
+ if(ipBegin == ipEnd)
+ goto END_SORT; // 全てのIPが0L
+
+ // 重複したIPは0Lにして無効としてカウント
+ for(--ipEnd; ipBegin != ipEnd; ++ipBegin)
+ if((*ipBegin)->ip64 == (*(ipBegin + 1))->ip64) {
+ (*ipBegin)->ip64 = 0L;
+ ++m_countDisabled;
+ }
+ }
+ if(sortIp) {
+ // 配列の中身を連結
+ ipBegin = ipSort;
+ ipEnd = ipSort + m_count;
+ ip = &m_ipHead;
+ while(ipBegin != ipEnd) {
+ ip->next = *(ipBegin++);
+ ip = ip->next;
+ }
+ ip->next = NULL;
+ }
+ END_SORT:
+ delete[] ipSort;
+ }
+
+ if(sortCap)
+ m_ipHead.next = Ip_SortByCaption(m_ipHead.next);
+
+ if(sortIp || sortCap) {
+ // m_ipFootを再設定
+ ip = m_ipHead.next;
+ if(ip) {
+ for(; ip->next; ip = ip->next);
+ m_ipFoot = ip;
+ } else {
+ m_ipFoot = &m_ipHead;
+ }
+ }
+}
+
+int CIpList::Count() {
+ return m_count;
+}
+
+int CIpList::CountDisabled() {
+ return m_countDisabled;
+}
+
+} // namespace ip
+} // namespace pglu
diff --git a/tool/PGListUtil/src/common/CIpList.h b/tool/PGListUtil/src/common/CIpList.h
new file mode 100644
index 00000000..591c3877
--- /dev/null
+++ b/tool/PGListUtil/src/common/CIpList.h
@@ -0,0 +1,56 @@
+#ifndef CIPLIST_H
+#define CIPLIST_H
+
+#include <boost/pool/pool.hpp>
+#include <boost/xpressive/xpressive.hpp>
+#include "common.h"
+#include "CFilter.h"
+
+namespace pglu {
+namespace ip {
+
+struct CIp {
+ char * caption;
+ union {
+ ulong ip64;
+ uint ip32[2];
+ uchar ip8[8];
+ };
+ CIp * next;
+};
+
+class CIpList {
+private:
+ boost::pool<> m_poolIp;
+ CIp m_ipHead;
+ CIp * m_ipFoot;
+
+ filter::CFilter m_filter;
+
+ int m_count;
+ int m_countDisabled;
+
+ CIp * CreateIp(boost::xpressive::cmatch & match);
+
+public:
+ CIpList();
+ ~CIpList();
+
+ void Clear();
+
+ void SetFilter(const char *filter, const filter::EFilterMode mode, const bool del);
+ void UnSetFilter();
+
+ bool LoadListFile(const char *path);
+ bool SaveListFile(const char *path, const bool append);
+
+ void CheckAndSort(const bool sortCap, const bool sortIp, const bool delDupIp);
+
+ int Count();
+ int CountDisabled();
+};
+
+} // namespace ip
+} // namespace pglu
+
+#endif // CIPLIST_H
diff --git a/tool/PGListUtil/src/common/common.h b/tool/PGListUtil/src/common/common.h
new file mode 100644
index 00000000..60530d5b
--- /dev/null
+++ b/tool/PGListUtil/src/common/common.h
@@ -0,0 +1,48 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#ifdef _MSC_VER
+
+typedef unsigned __int8 uchar;
+typedef unsigned __int32 uint;
+typedef unsigned __int64 ulong;
+
+# else
+
+#include <boost/cstdint.hpp>
+
+typedef uint8_t uchar;
+typedef uint32_t uint;
+typedef uint64_t ulong;
+
+#endif
+
+namespace pglu {
+
+#define PGLU_LENGTH_FILELINE 1024
+
+inline uint ParseDigit3(const char *begin, const char *end) {
+ switch(end - begin) {
+ case 3:
+ return ((*begin & 0xF) * 100) + ((*(begin + 1) & 0xF) * 10) + (*(begin + 2) & 0xF);
+ case 2:
+ return ((*begin & 0xF) * 10) + (*(begin + 1) & 0xF);
+ case 1:
+ return (*begin & 0xF);
+ default:
+ return 256;
+ }
+}
+
+#ifdef __MINGW32__
+
+inline void ZeroString(char *str) {
+ while(*str != '\0')
+ *(str++) = '\0';
+}
+
+#endif
+
+} // namespace pglu
+
+#endif // COMMON_H
diff --git a/tool/README.md b/tool/README.md
new file mode 100644
index 00000000..a9a19a09
--- /dev/null
+++ b/tool/README.md
@@ -0,0 +1,2 @@
+## Ilo / Skripto
+## Tool / Script
diff --git a/tool/ansero_example.html b/tool/ansero_example.html
new file mode 100644
index 00000000..54dc1e61
--- /dev/null
+++ b/tool/ansero_example.html
@@ -0,0 +1,92 @@
+<html>
+ <head>
+ <title>Search</title>
+ <style>
+body {
+ background: #f0f0f0
+}
+input#what {
+ width: 80%
+}
+span.srch_sect {
+ color: #2f4f4f
+}
+a.srch_link {
+ color: #4b0082
+}
+span.srch_url {
+ color: #20b2aa
+}
+a[onclick] {
+ cursor: pointer
+}
+ </style>
+ <script>
+
+// read API document
+let apiurl = 'https://api.nnpaefp7pkadbxxkhz2agtbv2a4g5sgo2fbmv3i7czaua354334uqqad.onion/_/ansero.php';
+let favurl = 'https://api.nnpaefp7pkadbxxkhz2agtbv2a4g5sgo2fbmv3i7czaua354334uqqad.onion/_/favicon.php?f=';
+let mylang = 'en-US';
+let hide_domain = []; // e.g. 'google.com'
+let hide_fqdn = []; // e.g. 'en.wikipedia.org'
+let removeMITMsites = false;
+
+function searchfor(key) {
+ document.getElementById('what').value = key;
+ search();
+}
+
+function search() {
+ let answer = '',
+ keyword = document.getElementById('what').value;
+ if (keyword.length < 2) {
+ return false;
+ }
+ fetch(apiurl, {
+ method: 'POST',
+ mode: 'cors',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: 't=json' + (removeMITMsites ? '&m' : '') + '&l=' + mylang + '&q=' + encodeURI(keyword)
+ }).then(r => r.json()).then(r => {
+ if (r.info.desc != undefined) {
+ answer += '<span class="srch_sect">';
+ answer += '' + r.info.desc + '<br>';
+ answer += '<a href="' + r.info.url + '" class="srch_link">' + r.info.title + '</a>';
+ answer += '</span><br><br>';
+ }
+ if (r.sgst.length > 0) {
+ answer += '<span class="srch_sect">Search other: <br>';
+ r.sgst.forEach(x => {
+ answer += '[<a class="srch_link" onclick="searchfor(\'' + x + '\');return false;">' + x + '</a>]<br>';
+ });
+ answer += '</span><br><br>';
+ }
+ r.res.forEach(x => {
+ if (!hide_fqdn.includes(x.fqdn) && !hide_domain.includes(x.dom)) {
+ answer += '<span class="srch_sect">';
+ answer += '<img src="' + favurl + (x.url.startsWith('https:') ? '1-' : '0-') + x.fqdn + '"> ';
+ answer += '<a href="' + x.url + '" class="srch_link">' + (x.mitm == 1 ? '[MITM!!] ' : '') + x.title + '</a><br>';
+ answer += '' + x.desc + '<br>';
+ answer += '<span class="srch_url">' + x.url + '</span></span>';
+ answer += '<br><br>';
+ }
+ });
+ document.getElementById('resultarea').innerHTML = answer;
+ }).catch(e => console.log(e));
+ return false;
+}
+
+ </script>
+ </head>
+ <body>
+ <form action="#" onsubmit="return search()">
+ <input type="text" id="what" placeholder="Search for..." minlength="2" required>
+ <input type="submit" value="Search">
+ </form>
+ <br>
+ <br>
+ <span id="resultarea"></span>
+ </body>
+</html> \ No newline at end of file
diff --git a/tool/anti-cf_filter_adblock/generate.py b/tool/anti-cf_filter_adblock/generate.py
new file mode 100644
index 00000000..680e26fd
--- /dev/null
+++ b/tool/anti-cf_filter_adblock/generate.py
@@ -0,0 +1,48 @@
+import glob
+import getopt
+import os
+import sys
+
+print("Anti-CF filter compiler for Adblock Plus/uBlock Origin/etc. v1.0")
+print("Licensed under CC0 1.0")
+print("")
+#functions
+def progress(value, filename):
+ sys.stdout.write('\033[2K\033[1G') #wipe the line before printing
+ sys.stdout.write("Compiling rules [%s] %s" % ("{:,}".format(value), filename))
+ sys.stdout.flush()
+
+opts, argv = getopt.getopt(sys.argv[1:], "o:d:")
+
+def process(outputf, inputd):
+ if not os.path.isdir(inputd):
+ print("Input not directory");
+ sys.exit(1)
+
+ with open(outputf, 'a') as outfile:
+ rulescom = 0 #counter for counting how much rules compiled
+
+ files = glob.glob(os.path.join(inputd, "*.txt")) #filter out anything but txt files
+
+ for f in files:
+ with open(f) as ruleso:
+ for line in ruleso:
+ outfile.write("||{}^$all\n".format(line.rstrip())) #this does the job
+ rulescom = rulescom + 1
+ progress(rulescom, f)
+
+
+
+if len(sys.argv) < 5: #check if theres argv
+ print("Usage: generate.py -o <output file> -d <input directory>")
+ print("Example: generate.py -o filter.txt -d ../../cloudflare_users/domains/")
+ sys.exit(1)
+
+#argv parsing
+for k, v in opts:
+ if k == '-o':
+ outputfile = v
+ if k == '-d':
+ inputdirectory = v
+
+process(outputfile, inputdirectory)
diff --git a/tool/anti-cf_hosts_generator/generate.py b/tool/anti-cf_hosts_generator/generate.py
new file mode 100644
index 00000000..4a5ca42e
--- /dev/null
+++ b/tool/anti-cf_hosts_generator/generate.py
@@ -0,0 +1,47 @@
+import glob
+import getopt
+import os
+import sys
+
+print("Anti-CF hosts file generator v1.0")
+print("Licensed under CC0 1.0")
+print("")
+
+#functions
+def progress(value, filename):
+ sys.stdout.write('\033[2K\033[1G') #wipe the line before printing
+ sys.stdout.write("Compiling rules [%s] %s" % ("{:,}".format(value), filename))
+ sys.stdout.flush()
+
+opts, argv = getopt.getopt(sys.argv[1:], "o:d:")
+
+def process(outputf, inputd):
+ if not os.path.isdir(inputd):
+ print("Input not directory");
+ sys.exit(1)
+
+ with open(outputf, 'a') as outfile:
+ rulescom = 0 #counter for counting how much rules compiled
+
+ files = glob.glob(os.path.join(inputd, "*.txt")) #filter out anything but txt files
+
+ for f in files:
+ with open(f) as ruleso:
+ for line in ruleso:
+ outfile.write("0.0.0.0 {}\n".format(line.rstrip())) #this does the job
+ rulescom = rulescom + 1
+ progress(rulescom, f)
+
+if len(sys.argv) < 5: #check if theres argv
+ print("Usage: generate.py -o <output file> -d <input directory>")
+ print("Example: generate.py -o filter.txt -d ../../cloudflare_users/domains/")
+ sys.exit(1)
+
+#argv parsing
+for k, v in opts:
+ if k == '-o':
+ outputfile = v
+ if k == '-d':
+ inputdirectory = v
+
+process(outputfile, inputdirectory)
diff --git a/tool/block_cloudflare_mitm_fx/LICENSE.md b/tool/block_cloudflare_mitm_fx/LICENSE.md
new file mode 100644
index 00000000..78663a99
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/LICENSE.md
@@ -0,0 +1,21 @@
+# MIT License
+
+Copyright (c) 2017窶2018 cypherpunk, nullius. All rights reserved.
+
+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.
diff --git a/tool/block_cloudflare_mitm_fx/README.md b/tool/block_cloudflare_mitm_fx/README.md
new file mode 100644
index 00000000..5693cb4c
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/README.md
@@ -0,0 +1,14 @@
+# Block Cloudflare MITM Attack
+
+The purpose of this browser add-on is to block Cloudflare sites.
+
+The TLS protocol promises end-to-end encryption between the client and an authenticated, identified endpoint server. The browser窶冱 lock icon is a UI widget which makes this promise to the user. Cloudflare is a mass-decryption chokepoint, which intercepts and decrypts the Web requests made by billions of people to millions of websites.
+
+- Prior discussion: [Tor Browser Bug #24351: Block Global Active Adversary Cloudflare](https://trac.torproject.org/projects/tor/ticket/24351)
+- Imported from [block_cloudflare_mitm_attack-1.0.10.1-an+fx.xpi](https://addons.mozilla.org/en-US/firefox/addon/block-cloudflare-mitm-attack/), by an anonymous cypherpunk. 窶彈Cyperpunks write code](https://www.activism.net/cypherpunk/manifesto.html).窶 Cheers!
+- [Original announcement](https://trac.torproject.org/projects/tor/ticket/24351#comment:25)
+- Thanks to [Debian Bug #831835](https://bugs.debian.org/831835) for some inspiration.
+
+-----
+
+- "[Bloku Cloudflaron MITM-Atakon](../../subfiles/about.bcma.md)" (BCMA2)
diff --git a/tool/block_cloudflare_mitm_fx/src/LICENSE.txt b/tool/block_cloudflare_mitm_fx/src/LICENSE.txt
new file mode 100644
index 00000000..fb406ef4
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-2018 cypherpunk, nullius. All rights reserved.
+
+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.
diff --git a/tool/block_cloudflare_mitm_fx/src/icons/icon-16.png b/tool/block_cloudflare_mitm_fx/src/icons/icon-16.png
new file mode 100644
index 00000000..9f24ca2e
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/icons/icon-16.png
Binary files differ
diff --git a/tool/block_cloudflare_mitm_fx/src/icons/icon-32.png b/tool/block_cloudflare_mitm_fx/src/icons/icon-32.png
new file mode 100644
index 00000000..4195c3b2
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/icons/icon-32.png
Binary files differ
diff --git a/tool/block_cloudflare_mitm_fx/src/icons/icon-48.png b/tool/block_cloudflare_mitm_fx/src/icons/icon-48.png
new file mode 100644
index 00000000..9bee2aa8
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/icons/icon-48.png
Binary files differ
diff --git a/tool/block_cloudflare_mitm_fx/src/icons/icon-64.png b/tool/block_cloudflare_mitm_fx/src/icons/icon-64.png
new file mode 100644
index 00000000..dd2958e9
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/icons/icon-64.png
Binary files differ
diff --git a/tool/block_cloudflare_mitm_fx/src/manifest.json b/tool/block_cloudflare_mitm_fx/src/manifest.json
new file mode 100644
index 00000000..c7481c49
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/manifest.json
@@ -0,0 +1,42 @@
+{
+ "manifest_version": 2,
+ "name": "Block Cloudflare MiTM Attack",
+ "description": "If the destination website use Cloudflare, block further request.",
+ "version": "1.0.10.1",
+ "homepage_url": "https://trac.torproject.org/projects/tor/ticket/24351",
+ "permissions": [
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>",
+ "storage",
+ "activeTab",
+ "tabs"
+ ],
+ "options_ui": {
+ "page": "setwhitelist.html",
+ "browser_style": false
+ },
+ "icons": {
+ "32": "icons/icon-32.png",
+ "48": "icons/icon-48.png",
+ "64": "icons/icon-64.png"
+ },
+ "browser_action": {
+ "browser_style": true,
+ "default_icon": {
+ "16": "icons/icon-16.png",
+ "32": "icons/icon-32.png"
+ }
+ },
+ "background": {
+ "scripts": [
+ "stop_cf_mitm.js"
+ ]
+ },
+ "applications": {
+ "gecko": {
+ "id": "{d86b44dd-ef12-4f28-ab1c-ea32664490ac}",
+ "strict_min_version": "52.0"
+ }
+ }
+} \ No newline at end of file
diff --git a/tool/block_cloudflare_mitm_fx/src/setwhitelist.html b/tool/block_cloudflare_mitm_fx/src/setwhitelist.html
new file mode 100644
index 00000000..7ca7e294
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/setwhitelist.html
@@ -0,0 +1,35 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="style.css">
+ </head>
+ <body>
+ <form>
+ [Whitelist]<br>
+ 1. Add FQDN or .FQDN you want to ignore. Click "Save".<br>
+ 2. Open new tab and visit whitelisted website.<br>
+ <small>(<i><b>.</b>mozilla.org</i> will allow <i>mozilla.org</i> and <i>*.mozilla.org</i>)</small><br>
+ <textarea cols="50" rows="12" id="myset_cfwhite" wrap="off"></textarea>
+ <br>
+ <label><input type="checkbox" id="myset_xautoclean"> Auto-remove whitelisted domain if it is no longer use MITM services</label><br><small>(Add-on will notify you)</small><br>
+ <br>
+ [Advanced]<br>
+ <label><input type="checkbox" id="myset_xign3p"> Ignore 3rd party resource (not recommend)</label><br>
+ <label><input type="checkbox" id="myset_xwhitemark"> Change whitelisted website's title, favicon, and website border</label><br>
+ <label><input type="checkbox" checked disabled> I don't like Man-in-the-middle attack.</label><br><!-- justajokedonttakethisseriouslyLOL //-->
+ <br>
+ Also detect:<br>
+ <label><input type="checkbox" id="myset_xincapsula"> Incapsula MiTM</label><br>
+ <label><input type="checkbox" id="myset_xgshield"> Google's Project Shield MiTM</label><br>
+ <label><input type="checkbox" id="myset_xsucuri"> Sucuri MiTM</label><br>
+ <br>
+ When MiTM attempt is detected:<br>
+ <label><input type="radio" name="acttype" id="myset_xsimplewarn_0" value="0"> Show security warning page</label><br>
+ <label><input type="radio" name="acttype" id="myset_xsimplewarn_1" value="1"> Just change title, favicon, and website border</label><br>
+ <label><input type="radio" name="acttype" id="myset_xsimplewarn_2" value="2"> Cancel request immediately</label><br>
+ <br>
+ <input type="submit" value=" Save ">
+ </form>
+ <script src="setwhitelist.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/tool/block_cloudflare_mitm_fx/src/setwhitelist.js b/tool/block_cloudflare_mitm_fx/src/setwhitelist.js
new file mode 100644
index 00000000..6db50e9f
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/setwhitelist.js
@@ -0,0 +1,158 @@
+function onError(e) {
+ console.log(`CFMITM_CFG Error:${e}`);
+}
+function saveWhitelist(e) {
+ e.preventDefault();
+ var cf_tmpdata = document.querySelector("#myset_cfwhite").value.split("\n");
+ for (var i = 0; i < cf_tmpdata.length; i++) {
+ if (!/^([0-9a-z.-]{1,})\.([a-z]{2,20})$/.test(cf_tmpdata[i]) || cf_tmpdata[i].includes("..") ||
+ cf_tmpdata[i].endsWith(".cloudflare.com") || cf_tmpdata[i] == 'cloudflare.com' ||
+ cf_tmpdata[i].endsWith(".incapsula.com") || cf_tmpdata[i] == 'incapsula.com' ||
+ cf_tmpdata[i].endsWith(".withgoogle.com") || cf_tmpdata[i].endsWith(".google.com")) {
+ cf_tmpdata[i] = '';
+ }
+ }
+ cf_tmpdata = cf_tmpdata.slice().sort(function (a, b) {
+ return a > b
+ }).reduce(function (a, b) {
+ if (a.slice(-1)[0] !== b) {
+ a.push(b);
+ };
+ return a;
+ }, []); // -duplicate
+ cf_tmpdata = cf_tmpdata.filter(v => v != '');
+ cf_tmpdata = cf_tmpdata.join("\n");
+ browser.storage.local.set({
+ myset_cfwhite: cf_tmpdata
+ });
+ document.querySelector("#myset_cfwhite").value = cf_tmpdata;
+ if (document.querySelector("#myset_xsimplewarn_1").checked) {
+ document.querySelector("#myset_xign3p").checked = false;
+ }
+ if (document.querySelector("#myset_xautoclean").checked) {
+ browser.storage.local.set({
+ myset_xautoclean: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xautoclean: "n"
+ });
+ }
+ if (document.querySelector("#myset_xincapsula").checked) {
+ browser.storage.local.set({
+ myset_xincapsula: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xincapsula: "n"
+ });
+ }
+ if (document.querySelector("#myset_xgshield").checked) {
+ browser.storage.local.set({
+ myset_xgshield: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xgshield: "n"
+ });
+ }
+ if (document.querySelector("#myset_xsucuri").checked) {
+ browser.storage.local.set({
+ myset_xsucuri: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xsucuri: "n"
+ });
+ }
+ if (document.querySelector("#myset_xign3p").checked) {
+ browser.storage.local.set({
+ myset_xign3p: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xign3p: "n"
+ });
+ }
+ if (document.querySelector("#myset_xwhitemark").checked) {
+ browser.storage.local.set({
+ myset_xwhitemark: "y"
+ });
+ } else {
+ browser.storage.local.set({
+ myset_xwhitemark: "n"
+ });
+ }
+ if (document.querySelector("#myset_xsimplewarn_0").checked) {
+ browser.storage.local.set({
+ myset_xsimplewarn: 0
+ });
+ }
+ if (document.querySelector("#myset_xsimplewarn_1").checked) {
+ browser.storage.local.set({
+ myset_xsimplewarn: 1
+ });
+ }
+ if (document.querySelector("#myset_xsimplewarn_2").checked) {
+ browser.storage.local.set({
+ myset_xsimplewarn: 2
+ });
+ }
+ browser.runtime.sendMessage({
+ relnow: 'go'
+ }).then(function (r) {}, onError);
+}
+function loadWhitelist() {
+ function setCurrentChoice(r) {
+ document.querySelector("#myset_cfwhite").value = r.myset_cfwhite || "";
+ if (r.myset_xautoclean == 'y') {
+ document.querySelector("#myset_xautoclean").checked = true;
+ } else {
+ document.querySelector("#myset_xautoclean").checked = false;
+ }
+ if (r.myset_xincapsula == 'y') {
+ document.querySelector("#myset_xincapsula").checked = true;
+ } else {
+ document.querySelector("#myset_xincapsula").checked = false;
+ }
+ if (r.myset_xgshield == 'y') {
+ document.querySelector("#myset_xgshield").checked = true;
+ } else {
+ document.querySelector("#myset_xgshield").checked = false;
+ }
+ if (r.myset_xsucuri == 'y') {
+ document.querySelector("#myset_xsucuri").checked = true;
+ } else {
+ document.querySelector("#myset_xsucuri").checked = false;
+ }
+ if (r.myset_xign3p == 'y') {
+ document.querySelector("#myset_xign3p").checked = true;
+ } else {
+ document.querySelector("#myset_xign3p").checked = false;
+ }
+ if (r.myset_xwhitemark == 'y') {
+ document.querySelector("#myset_xwhitemark").checked = true;
+ } else {
+ document.querySelector("#myset_xwhitemark").checked = false;
+ }
+ if (r.myset_xsimplewarn) {
+ switch (r.myset_xsimplewarn) {
+ case 1:
+ document.querySelector("#myset_xsimplewarn_1").checked = true;
+ break;
+ case 2:
+ document.querySelector("#myset_xsimplewarn_2").checked = true;
+ break;
+ default:
+ document.querySelector("#myset_xsimplewarn_0").checked = true;
+ break;
+ }
+ } else {
+ document.querySelector("#myset_xsimplewarn_0").checked = true;
+ }
+ }
+ var getting = browser.storage.local.get();
+ getting.then(setCurrentChoice, onError);
+}
+document.addEventListener("DOMContentLoaded", loadWhitelist);
+document.querySelector("form").addEventListener("submit", saveWhitelist); \ No newline at end of file
diff --git a/tool/block_cloudflare_mitm_fx/src/stop_cf_mitm.js b/tool/block_cloudflare_mitm_fx/src/stop_cf_mitm.js
new file mode 100644
index 00000000..f6939ce2
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/stop_cf_mitm.js
@@ -0,0 +1,370 @@
+var cfaddon_isdone = 0;
+var cf_ignore = [];
+var cf_history = [];
+var cf_dstarray = {};
+var wl_autoclean = 0;
+var stop_incapsula = 0;
+var stop_gshield = 0;
+var stop_sucuri = 0;
+var ign_thirdparty = 0;
+var do_markwhitelistsite = 0;
+var do_reaction = 0;
+var cfblockscreen = '';
+var cf_blocked_img = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAAACwAAAAAAQABAAACAkQBADs=';
+var cf_template_blocked = 'PGh0bWwgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPjxoZWFkPjx0aXRsZT5JbnNlY3VyZSBDb25uZWN0aW9uPC90aXRsZT48bWV0YSBjaGFyc2V0PSJ1dGYtOCI+DQo8bGluayBpZD0iZmF2aWNvbiIgcmVsPSJpY29uIiB0eXBlPSJpbWFnZS94LWljb24iIGhyZWY9ImRhdGE6aW1hZ2UveC1pY29uO2Jhc2U2NCxBQUFCQUFFQUVCQUFBQUVBSUFCb0JBQUFGZ0FBQUNnQUFBQVFBQUFBSUFBQUFBRUFJQUFBQUFBQUFBUUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT3prNE9LcE1Ta3E2VUU5UHVsUlRVN3BYVjFlNlcxdGJ1bDFkWGJwYlcxdTZXRmhZdWxWVlZicFRVMU82VUZCUXVreE1UTG90TFMyYkFBQUFPelkwTko3WTNOei92OXpoLzhIZTQvL0Q0ZWIveHVUcC84am02Ly9JNU9uL3grUG8vOGJqNmYvRTRlZi93dC9sLzhIZDQvKy8yZC8vM04vZi94a1pHWVUyTlRXZDJON2UveGVreGY4V3VlRC9GcnJoL3hhNzRmOFVyOVAvSWl3dS95SXNMdjhXczkvL0Y3RGUveGVyM1A4WHB0ci9JSlM2Lzk3ZzRmOFpHUm1DQUFBQVY5SFEwTzkxczd6L0Y4SGoveGJDNC84V3crVC9GTGJWL3lBckxmOGdLeTMvRnJyaC94YTEzLzhYc043L0ZxYlYvNUczdmYreHNiSFVBQUFBUlFBQUFDNW9aMmVWMk43Zi95V3R2LzhYeXViL0Zzcm0veGJKNXY4WnE4Zi9HYXZIL3hiQTQvOFd1K0gvRnJYZi96V2d1Zi9aMmRuL0x5OHZlQUFBQUJ3QUFBQUFBQUFBU3NiRnhkMmd5ODcvS3RUbS94blU2djhXME9qL0ZFSkkveFJDU1A4V3hlWC9Gci9qL3hXdzAvK3N3c1AvazVPVHRnQUFBRG9BQUFBQUFBQUFBQUFBQUNGRlJVVis2ZXZyL3pxMXZ2OHc1UEgvSjkvdi95SWlJdjhpSWlML0Zzcm0veGJFNVA5WHE3bi8xOWZYK0JrWkdXMEFBQUFLQUFBQUFBQUFBQUFBQUFBQUFBQUFQcmUydHNuQTN1RC9MTnJqL3kvbzh2OHJLeXYvS3lzci95dmI3djhtdmREL3hNL1AvM2QzZDZBQUFBQTBBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQlViR3h0dDhQRHcrRnJBdy84dTdQUC9ORFEwL3pRME5QOHczZS8vZ0x1Ly84akl5T2dBQUFCUkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU5KMmRuYlRTNCtUL0tkRFYvelE4UFA4MFFFSC9OTDNKLzlYWjJmOVdWbGFMQUFBQUtBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBa0FBQUJYMzkvZjczM0V4djh0NXZIL0xkcnAvNXJCdy8rMnRyYlVBQUFBUlFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBTG0xdGJaWGY1dWIvTExuRC8wUzd4UC9kM2QzL01URXhlQUFBQUJ3QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQkt4OGZIM2JIUDBmKzh6TTMvbDVlWHRnQUFBRG9BQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFJVEl5TW5hK3ZyN1RycTZ1eVJrWkdXMEFBQUFLQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBYkFBQUFSQUFBQUQ0QUFBQVZBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQS8vOEFBSUFCQUFBQUFBQUFBQUFBQUlBQkFBQ0FBd0FBd0FNQUFPQUhBQURnQndBQThBOEFBUEFQQUFENEh3QUErRDhBQVB3L0FBRCtmd0FBLy84QUFBPT0iPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj48IS0tQG5hbWVzcGFjZSBodG1sICJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIjtAbmFtZXNwYWNlIHh1bCAiaHR0cDovL3d3dy5tb3ppbGxhLm9yZy9rZXltYXN0ZXIvZ2F0ZWtlZXBlci90aGVyZS5pcy5vbmx5Lnh1bCI7KnwqOnJvb3R7LS1pbi1jb250ZW50LXBhZ2UtY29sb3I6IzQyNGU1YTstLWluLWNvbnRlbnQtcGFnZS1iYWNrZ3JvdW5kOiNmYmZiZmI7LS1pbi1jb250ZW50LXRleHQtY29sb3I6IzMzMzstLWluLWNvbnRlbnQtc2VsZWN0ZWQtdGV4dDojZmZmOy0taW4tY29udGVudC1oZWFkZXItYm9yZGVyLWNvbG9yOiNjOGM4Yzg7LS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kOiNmZmY7LS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLW9kZDojZjNmNmZhOy0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZC1ob3ZlcjojZWJlYmViOy0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZC1hY3RpdmU6I2RhZGFkYTstLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcjojYzFjMWMxOy0taW4tY29udGVudC1pdGVtLWhvdmVyOnJnYmEoMCwxNDksMjIxLDAuMjUpOy0taW4tY29udGVudC1pdGVtLXNlbGVjdGVkOiMwMDk1ZGQ7LS1pbi1jb250ZW50LWJvcmRlci1oaWdobGlnaHQ6I2ZmOTUwMDstLWluLWNvbnRlbnQtYm9yZGVyLWZvY3VzOiMwMDk1ZGQ7LS1pbi1jb250ZW50LWJvcmRlci1jb2xvcjojYzFjMWMxOy0taW4tY29udGVudC1jYXRlZ29yeS10ZXh0OiNjMWMxYzE7LS1pbi1jb250ZW50LWNhdGVnb3J5LWJvcmRlci1mb2N1czoxcHggZG90dGVkICNmZmY7LS1pbi1jb250ZW50LWNhdGVnb3J5LXRleHQtc2VsZWN0ZWQ6I2YyZjJmMjstLWluLWNvbnRlbnQtY2F0ZWdvcnktYmFja2dyb3VuZDojNDI0ZjVhOy0taW4tY29udGVudC1jYXRlZ29yeS1iYWNrZ3JvdW5kLWhvdmVyOiM1ZTY5NzI7LS1pbi1jb250ZW50LWNhdGVnb3J5LWJhY2tncm91bmQtYWN0aXZlOiMzNDNmNDg7LS1pbi1jb250ZW50LXRhYi1jb2xvcjojNDI0ZjVhOy0taW4tY29udGVudC1saW5rLWNvbG9yOiMwMDk1ZGQ7LS1pbi1jb250ZW50LWxpbmstY29sb3ItaG92ZXI6IzE3OGNlNTstLWluLWNvbnRlbnQtbGluay1jb2xvci1hY3RpdmU6I2ZmOTUwMDstLWluLWNvbnRlbnQtbGluay1jb2xvci12aXNpdGVkOiM1NTFhOGI7LS1pbi1jb250ZW50LXByaW1hcnktYnV0dG9uLWJhY2tncm91bmQ6IzAwOTVkZDstLWluLWNvbnRlbnQtcHJpbWFyeS1idXR0b24tYmFja2dyb3VuZC1ob3ZlcjojMDA4YWNiOy0taW4tY29udGVudC1wcmltYXJ5LWJ1dHRvbi1iYWNrZ3JvdW5kLWFjdGl2ZTojMDA2YjlkOy0taW4tY29udGVudC10YWJsZS1ib3JkZXItZGFyay1jb2xvcjojZDFkMWQxOy0taW4tY29udGVudC10YWJsZS1oZWFkZXItYmFja2dyb3VuZDojMDA5NWRkfWh0bWx8aHRtbCx4dWx8cGFnZSx4dWx8d2luZG93e2ZvbnQ6bWVzc2FnZS1ib3g7LW1vei1hcHBlYXJhbmNlOm5vbmU7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LXBhZ2UtYmFja2dyb3VuZCk7Y29sb3I6dmFyKC0taW4tY29udGVudC1wYWdlLWNvbG9yKX1odG1sfGJvZHl7Zm9udC1zaXplOjE1cHg7Zm9udC13ZWlnaHQ6bm9ybWFsO21hcmdpbjowfWh0bWx8aDF7Zm9udC1zaXplOjIuNWVtO2ZvbnQtd2VpZ2h0OmxpZ2h0ZXI7bGluZS1oZWlnaHQ6MS4yO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcik7bWFyZ2luOjA7bWFyZ2luLWJvdHRvbTouNWVtfWh0bWx8aHJ7Ym9yZGVyLXN0eWxlOnNvbGlkIG5vbmUgbm9uZSBub25lO2JvcmRlci1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJvcmRlci1jb2xvcil9eHVsfGNhcHRpb257LW1vei1hcHBlYXJhbmNlOm5vbmU7bWFyZ2luOjB9eHVsfGNhcHRpb24+eHVsfGNoZWNrYm94LHh1bHxjYXB0aW9uPnh1bHxsYWJlbHtmb250LXNpemU6MS4zcmVtO2ZvbnQtd2VpZ2h0OmJvbGQ7bGluZS1oZWlnaHQ6MjJweH14dWx8Y2FwdGlvbj54dWx8Y2hlY2tib3gseHVsfGNhcHRpb24+eHVsfGxhYmVse21hcmdpbjowIWltcG9ydGFudH0qfCoubWFpbi1jb250ZW50e3BhZGRpbmctdG9wOjQwcHg7cGFkZGluZy1pbmxpbmUtZW5kOjQ0cHg7cGFkZGluZy1ib3R0b206NDhweDtwYWRkaW5nLWlubGluZS1zdGFydDo0OHB4O292ZXJmbG93OmF1dG99eHVsfHByZWZwYW5lPnh1bHwqLmNvbnRlbnQtYm94e292ZXJmbG93OnZpc2libGV9eHVsfGdyb3VwYm94ey1tb3otYXBwZWFyYW5jZTpub25lO2JvcmRlcjowO21hcmdpbjoxNXB4IDAgMDtwYWRkaW5nLWlubGluZS1zdGFydDowO3BhZGRpbmctaW5saW5lLWVuZDowO2ZvbnQtc2l6ZToxLjI1cmVtfXh1bHxncm91cGJveCB4dWx8bGFiZWw6bm90KC5tZW51LWFjY2VsKTpub3QoLm1lbnUtdGV4dCk6bm90KC5pbmRlbnQpLHh1bHxncm91cGJveCB4dWx8ZGVzY3JpcHRpb257bWFyZ2luLWlubGluZS1zdGFydDowIWltcG9ydGFudDttYXJnaW4taW5saW5lLWVuZDowIWltcG9ydGFudH14dWx8dGFicGFuZWxzey1tb3otYXBwZWFyYW5jZTpub25lO2ZvbnQtc2l6ZToxLjI1cmVtO2xpbmUtaGVpZ2h0OjIycHg7Ym9yZGVyOjA7cGFkZGluZzowO2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7Y29sb3I6aW5oZXJpdH14dWx8dGFic3ttYXJnaW4tYm90dG9tOjE1cHg7Ym9yZGVyLXRvcDoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wYWdlLWJhY2tncm91bmQpfXh1bHwqLnRhYnMtbGVmdCx4dWx8Ki50YWJzLXJpZ2h0e2JvcmRlci1ib3R0b206MH14dWx8dGFiey1tb3otYXBwZWFyYW5jZTpub25lO21hcmdpbi10b3A6MDtwYWRkaW5nOjRweCAyMHB4O21pbi1oZWlnaHQ6NDRweDtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRhYi1jb2xvcik7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LXBhZ2UtYmFja2dyb3VuZCk7Ym9yZGVyLXdpZHRoOjA7Ym9yZGVyLXJhZGl1czowIWltcG9ydGFudDt0cmFuc2l0aW9uOmJhY2tncm91bmQtY29sb3IgNTBtcyBlYXNlIDBzfXh1bHx0YWI6aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWhvdmVyKX14dWx8dGFiW3NlbGVjdGVkXXtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtaG92ZXIpO3BhZGRpbmctYm90dG9tOjA7Ym9yZGVyLWJvdHRvbTo0cHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3JkZXItaGlnaGxpZ2h0KX14dWx8Ki50YWItdGV4dHtmb250LXNpemU6MS4zcmVtO2xpbmUtaGVpZ2h0OjIycHh9aHRtbHxidXR0b257cGFkZGluZzozcHg7Zm9udDppbmhlcml0fSp8YnV0dG9uLGh0bWx8c2VsZWN0LHh1bHxjb2xvcnBpY2tlclt0eXBlPSJidXR0b24iXSx4dWx8bWVudWxpc3R7LW1vei1hcHBlYXJhbmNlOm5vbmU7bWluLWhlaWdodDozMHB4O2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcik7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpOy1tb3otYm9yZGVyLXRvcC1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7LW1vei1ib3JkZXItcmlnaHQtY29sb3JzOm5vbmUhaW1wb3J0YW50Oy1tb3otYm9yZGVyLWJvdHRvbS1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7LW1vei1ib3JkZXItbGVmdC1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7Ym9yZGVyLXJhZGl1czoycHg7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LXBhZ2UtYmFja2dyb3VuZCl9aHRtbHxidXR0b246ZW5hYmxlZDpob3ZlcixodG1sfHNlbGVjdDplbmFibGVkOmhvdmVyLHh1bHxidXR0b246bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3Zlcix4dWx8Y29sb3JwaWNrZXJbdHlwZT0iYnV0dG9uIl06bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3Zlcix4dWx8bWVudWxpc3Q6bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtaG92ZXIpfWh0bWx8YnV0dG9uOmVuYWJsZWQ6aG92ZXI6YWN0aXZlLGh0bWx8c2VsZWN0OmVuYWJsZWQ6aG92ZXI6YWN0aXZlLHh1bHxidXR0b246bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3ZlcjphY3RpdmUseHVsfGNvbG9ycGlja2VyW3R5cGU9ImJ1dHRvbiJdOm5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXI6YWN0aXZlLHh1bHxtZW51bGlzdFtvcGVuPSJ0cnVlIl06bm90KFtkaXNhYmxlZD0idHJ1ZSJdKXtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtYWN0aXZlKX1odG1sfGJ1dHRvbjpkaXNhYmxlZCxodG1sfHNlbGVjdDpkaXNhYmxlZCx4dWx8YnV0dG9uW2Rpc2FibGVkPSJ0cnVlIl0seHVsfGNvbG9ycGlja2VyW3R5cGU9ImJ1dHRvbiJdW2Rpc2FibGVkPSJ0cnVlIl0seHVsfG1lbnVsaXN0W2Rpc2FibGVkPSJ0cnVlIl17b3BhY2l0eTouNX0qfGJ1dHRvbi5wcmltYXJ5e2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wcmltYXJ5LWJ1dHRvbi1iYWNrZ3JvdW5kKTtib3JkZXItY29sb3I6dHJhbnNwYXJlbnQ7Y29sb3I6dmFyKC0taW4tY29udGVudC1zZWxlY3RlZC10ZXh0KX1odG1sfGJ1dHRvbi5wcmltYXJ5OmVuYWJsZWQ6aG92ZXIseHVsfGJ1dHRvbi5wcmltYXJ5Om5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LXByaW1hcnktYnV0dG9uLWJhY2tncm91bmQtaG92ZXIpfWh0bWx8YnV0dG9uLnByaW1hcnk6ZW5hYmxlZDpob3ZlcjphY3RpdmUseHVsfGJ1dHRvbi5wcmltYXJ5Om5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXI6YWN0aXZle2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wcmltYXJ5LWJ1dHRvbi1iYWNrZ3JvdW5kLWFjdGl2ZSl9eHVsfGNvbG9ycGlja2VyW3R5cGU9ImJ1dHRvbiJde3BhZGRpbmc6NnB4O3dpZHRoOjUwcHh9eHVsfGJ1dHRvbj54dWx8Ki5idXR0b24tYm94e3BhZGRpbmctcmlnaHQ6MTBweCFpbXBvcnRhbnQ7cGFkZGluZy1sZWZ0OjEwcHghaW1wb3J0YW50fXh1bHxtZW51bGlzdD54dWx8Ki5tZW51bGlzdC1sYWJlbC1ib3g+eHVsfCoubWVudWxpc3QtaWNvbltzcmNde21hcmdpbi1pbmxpbmUtZW5kOjVweH14dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8Ki5idXR0b24tYm94Pnh1bHwqLmJ1dHRvbi1tZW51LWRyb3BtYXJrZXJ7LW1vei1hcHBlYXJhbmNlOm5vbmU7bWFyZ2luOjFweCAwO21hcmdpbi1pbmxpbmUtc3RhcnQ6MTBweDtwYWRkaW5nOjA7d2lkdGg6MTBweDtoZWlnaHQ6MTZweDtib3JkZXI6MDtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fXh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXB7LW1vei1hcHBlYXJhbmNlOm5vbmU7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpO2JvcmRlci1yYWRpdXM6MnB4O2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZCl9eHVsfG1lbnVsaXN0Pnh1bHxtZW51cG9wdXAgeHVsfG1lbnUseHVsfG1lbnVsaXN0Pnh1bHxtZW51cG9wdXAgeHVsfG1lbnVpdGVtLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXAgeHVsfG1lbnUseHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfG1lbnVwb3B1cCB4dWx8bWVudWl0ZW17LW1vei1hcHBlYXJhbmNlOm5vbmU7Zm9udC1zaXplOjFlbTtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRleHQtY29sb3IpO3BhZGRpbmctdG9wOi4yZW07cGFkZGluZy1ib3R0b206LjJlbTtwYWRkaW5nLWlubGluZS1zdGFydDoxMHB4O3BhZGRpbmctaW5saW5lLWVuZDozMHB4fXh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51Om5vdChbZGlzYWJsZWQ9InRydWUiXSlbX21vei1tZW51YWN0aXZlPSJ0cnVlIl0seHVsfG1lbnVsaXN0Pnh1bHxtZW51cG9wdXA+eHVsfG1lbnVpdGVtOm5vdChbZGlzYWJsZWQ9InRydWUiXSlbX21vei1tZW51YWN0aXZlPSJ0cnVlIl0seHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfG1lbnVwb3B1cD54dWx8bWVudTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW19tb3otbWVudWFjdGl2ZT0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnVpdGVtOm5vdChbZGlzYWJsZWQ9InRydWUiXSlbX21vei1tZW51YWN0aXZlPSJ0cnVlIl17Y29sb3I6dmFyKC0taW4tY29udGVudC10ZXh0LWNvbG9yKTtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtaXRlbS1ob3Zlcil9eHVsfG1lbnVsaXN0Pnh1bHxtZW51cG9wdXA+eHVsfG1lbnU6bm90KFtkaXNhYmxlZD0idHJ1ZSJdKVtzZWxlY3RlZD0idHJ1ZSJdLHh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51aXRlbTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW3NlbGVjdGVkPSJ0cnVlIl0seHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfG1lbnVwb3B1cD54dWx8bWVudTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW3NlbGVjdGVkPSJ0cnVlIl0seHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfG1lbnVwb3B1cD54dWx8bWVudWl0ZW06bm90KFtkaXNhYmxlZD0idHJ1ZSJdKVtzZWxlY3RlZD0idHJ1ZSJde2NvbG9yOnZhcigtLWluLWNvbnRlbnQtc2VsZWN0ZWQtdGV4dCk7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWl0ZW0tc2VsZWN0ZWQpfXh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51W2Rpc2FibGVkPSJ0cnVlIl0seHVsfG1lbnVsaXN0Pnh1bHxtZW51cG9wdXA+eHVsfG1lbnVpdGVtW2Rpc2FibGVkPSJ0cnVlIl0seHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfG1lbnVwb3B1cD54dWx8bWVudVtkaXNhYmxlZD0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnVpdGVtW2Rpc2FibGVkPSJ0cnVlIl17Y29sb3I6Izk5OTtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fXh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwIHh1bHxtZW51c2VwYXJhdG9yLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXAgeHVsfG1lbnVzZXBhcmF0b3J7LW1vei1hcHBlYXJhbmNlOm5vbmU7bWFyZ2luOjA7cGFkZGluZzowO2JvcmRlci10b3A6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7Ym9yZGVyLWJvdHRvbTowfWh0bWx8aW5wdXRbdHlwZT0idGV4dCJdLGh0bWx8dGV4dGFyZWEseHVsfHRleHRib3h7LW1vei1hcHBlYXJhbmNlOm5vbmU7Y29sb3I6dmFyKC0taW4tY29udGVudC10ZXh0LWNvbG9yKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7LW1vei1ib3JkZXItdG9wLWNvbG9yczpub25lIWltcG9ydGFudDstbW96LWJvcmRlci1yaWdodC1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7LW1vei1ib3JkZXItYm90dG9tLWNvbG9yczpub25lIWltcG9ydGFudDstbW96LWJvcmRlci1sZWZ0LWNvbG9yczpub25lIWltcG9ydGFudDtib3JkZXItcmFkaXVzOjJweDtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQpfXh1bHx0ZXh0Ym94e21pbi1oZWlnaHQ6MzBweDtwYWRkaW5nLXJpZ2h0OjEwcHg7cGFkZGluZy1sZWZ0OjEwcHh9eHVsfHRleHRib3gudHJlZS1pbnB1dHttaW4taGVpZ2h0OnVuc2V0O3BhZGRpbmctcmlnaHQ6dW5zZXQ7cGFkZGluZy1sZWZ0OnVuc2V0fWh0bWx8aW5wdXRbdHlwZT0idGV4dCJdLGh0bWx8dGV4dGFyZWF7Zm9udC1mYW1pbHk6aW5oZXJpdDtmb250LXNpemU6aW5oZXJpdDtwYWRkaW5nOjVweCAxMHB4fWh0bWx8aW5wdXRbdHlwZT0idGV4dCJdOmZvY3VzLGh0bWx8dGV4dGFyZWE6Zm9jdXMseHVsfHRleHRib3hbZm9jdXNlZF17Ym9yZGVyLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm9yZGVyLWZvY3VzKX1odG1sfGlucHV0W3R5cGU9InRleHQiXTpkaXNhYmxlZCxodG1sfHRleHRhcmVhOmRpc2FibGVkLHh1bHx0ZXh0Ym94W2Rpc2FibGVkPSJ0cnVlIl17b3BhY2l0eTouNX1odG1sfGEsLnRleHQtbGlua3tjb2xvcjp2YXIoLS1pbi1jb250ZW50LWxpbmstY29sb3IpO3RleHQtZGVjb3JhdGlvbjpub25lfWh0bWx8YTpob3ZlciwudGV4dC1saW5rOmhvdmVye2NvbG9yOnZhcigtLWluLWNvbnRlbnQtbGluay1jb2xvci1ob3Zlcik7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZX1odG1sfGE6dmlzaXRlZHtjb2xvcjp2YXIoLS1pbi1jb250ZW50LWxpbmstY29sb3ItdmlzaXRlZCl9aHRtbHxhOmhvdmVyOmFjdGl2ZSwudGV4dC1saW5rOmhvdmVyOmFjdGl2ZXtjb2xvcjp2YXIoLS1pbi1jb250ZW50LWxpbmstY29sb3ItYWN0aXZlKTt0ZXh0LWRlY29yYXRpb246bm9uZX1odG1sfGlucHV0W3R5cGU9ImNoZWNrYm94Il17b3BhY2l0eTowO3dpZHRoOjA7cG9pbnRlci1ldmVudHM6bm9uZTtwb3NpdGlvbjphYnNvbHV0ZX1odG1sfGlucHV0W3R5cGU9ImNoZWNrYm94Il0raHRtbHxsYWJlbDpiZWZvcmV7ZGlzcGxheTppbmxpbmUtYmxvY2s7Y29udGVudDoiIjt2ZXJ0aWNhbC1hbGlnbjptaWRkbGV9aHRtbHxpbnB1dFt0eXBlPSJjaGVja2JveCJdK2h0bWx8bGFiZWx7bGluZS1oZWlnaHQ6MH14dWx8Y2hlY2tib3h7bWFyZ2luLWlubGluZS1zdGFydDowfXh1bHwqLmNoZWNrYm94LWNoZWNrLGh0bWx8aW5wdXRbdHlwZT0iY2hlY2tib3giXStodG1sfGxhYmVsOmJlZm9yZXstbW96LWFwcGVhcmFuY2U6bm9uZTt3aWR0aDoyM3B4O2hlaWdodDoyM3B4O2JvcmRlci1yYWRpdXM6MnB4O2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTttYXJnaW4taW5saW5lLWVuZDoxMHB4O2JhY2tncm91bmQtY29sb3I6I2YxZjFmMTtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCgjZmZmLHJnYmEoMjU1LDI1NSwyNTUsMC44KSkhaW1wb3J0YW50O2JhY2tncm91bmQtcG9zaXRpb246Y2VudGVyIGNlbnRlcjtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7Ym94LXNoYWRvdzowIDFweCAxcHggMCAjZmZmLGluc2V0IDAgMnB4IDAgMCByZ2JhKDAsMCwwLDAuMDMpfXh1bHxjaGVja2JveDpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pOmhvdmVyPnh1bHwqLmNoZWNrYm94LWNoZWNrLGh0bWx8aW5wdXRbdHlwZT0iY2hlY2tib3giXTpub3QoOmRpc2FibGVkKStodG1sfGxhYmVsOmhvdmVyOmJlZm9yZXtib3JkZXItY29sb3I6dmFyKC0taW4tY29udGVudC1ib3JkZXItZm9jdXMpfXh1bHxjaGVja2JveFtkaXNhYmxlZD0idHJ1ZSJdPnh1bHwqLmNoZWNrYm94LWNoZWNrLGh0bWx8aW5wdXRbdHlwZT0iY2hlY2tib3giXTpkaXNhYmxlZCtodG1sfGxhYmVse29wYWNpdHk6LjV9eHVsfCouY2hlY2tib3gtbGFiZWwtYm94e21hcmdpbi1pbmxpbmUtc3RhcnQ6LTFweDtwYWRkaW5nLWlubGluZS1zdGFydDowfXh1bHxyaWNobGlzdGl0ZW0+eHVsfCouY2hlY2tib3gtY2hlY2t7bWFyZ2luOjNweCA2cHh9eHVsfHJhZGlve21hcmdpbi1pbmxpbmUtc3RhcnQ6MH14dWx8Ki5yYWRpby1jaGVja3stbW96LWFwcGVhcmFuY2U6bm9uZTt3aWR0aDoyM3B4O2hlaWdodDoyM3B4O2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtib3JkZXItcmFkaXVzOjUwJTttYXJnaW4taW5saW5lLWVuZDoxMHB4O2JhY2tncm91bmQtY29sb3I6I2YxZjFmMTtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCgjZmZmLHJnYmEoMjU1LDI1NSwyNTUsMC44MCkpO2JveC1zaGFkb3c6MCAxcHggMXB4IDAgI2ZmZixpbnNldCAwIDJweCAwIDAgcmdiYSgwLDAsMCwwLjAzKX14dWx8cmFkaW86bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3Zlcj54dWx8Ki5yYWRpby1jaGVja3tib3JkZXItY29sb3I6dmFyKC0taW4tY29udGVudC1ib3JkZXItZm9jdXMpfXh1bHxyYWRpb1tkaXNhYmxlZD0idHJ1ZSJdPnh1bHwqLnJhZGlvLWNoZWNre29wYWNpdHk6LjV9eHVsfCoucmFkaW8tbGFiZWwtYm94e21hcmdpbi1pbmxpbmUtc3RhcnQ6LTFweDttYXJnaW4taW5saW5lLWVuZDoxMHB4O3BhZGRpbmctaW5saW5lLXN0YXJ0OjB9KnwqI2NhdGVnb3JpZXN7LW1vei1hcHBlYXJhbmNlOm5vbmU7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LWJhY2tncm91bmQpO3BhZGRpbmctdG9wOjM5cHg7bWFyZ2luOjA7Ym9yZGVyLXdpZHRoOjB9KnwqLmNhdGVnb3J5ey1tb3otYXBwZWFyYW5jZTpub25lO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtY2F0ZWdvcnktdGV4dCk7Ym9yZGVyLWlubGluZS1lbmQtd2lkdGg6MDtwYWRkaW5nLWlubGluZS1zdGFydDoxNXB4O3BhZGRpbmctaW5saW5lLWVuZDoyMXB4O21pbi1oZWlnaHQ6NDBweDt0cmFuc2l0aW9uOmJhY2tncm91bmQtY29sb3IgMTUwbXN9KnwqLmNhdGVnb3J5OmhvdmVye2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1jYXRlZ29yeS1iYWNrZ3JvdW5kLWhvdmVyKX0qfCouY2F0ZWdvcnlbc2VsZWN0ZWRdLCp8Ki5jYXRlZ29yeS5zZWxlY3RlZHtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtY2F0ZWdvcnktYmFja2dyb3VuZC1hY3RpdmUpO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtY2F0ZWdvcnktdGV4dC1zZWxlY3RlZCk7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MTFweDtib3JkZXItaW5saW5lLXN0YXJ0OnNvbGlkIDRweCB2YXIoLS1pbi1jb250ZW50LWJvcmRlci1oaWdobGlnaHQpfSp8KiNjYXRlZ29yaWVzW2tleWJvYXJkLW5hdmlnYXRpb249InRydWUiXTotbW96LWZvY3VzcmluZz4qfCouY2F0ZWdvcnlbY3VycmVudF17Ym9yZGVyLXRvcDp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LWJvcmRlci1mb2N1cyk7Ym9yZGVyLWJvdHRvbTp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LWJvcmRlci1mb2N1cyl9KnwqLmNhdGVnb3J5LW5hbWV7bGluZS1oZWlnaHQ6MjJweDtmb250LXNpemU6MS4yNXJlbTtwYWRkaW5nLWJvdHRvbToycHg7cGFkZGluZy1pbmxpbmUtc3RhcnQ6OXB4O21hcmdpbjowOy1tb3otdXNlci1zZWxlY3Q6bm9uZX0qfCouY2F0ZWdvcnktaWNvbnt3aWR0aDoyNHB4O2hlaWdodDoyNHB4fSp8Ki5oZWFkZXJ7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgdmFyKC0taW4tY29udGVudC1oZWFkZXItYm9yZGVyLWNvbG9yKTttYXJnaW4taW5saW5lLWVuZDo0cHg7bWFyZ2luLWJvdHRvbToxNXB4O3BhZGRpbmctYm90dG9tOjE1cHg7LW1vei1ib3gtYWxpZ246YmFzZWxpbmV9KnwqLmhlYWRlci1uYW1le2ZvbnQtc2l6ZToyLjVyZW07Zm9udC13ZWlnaHQ6bm9ybWFsO2xpbmUtaGVpZ2h0OjQwcHg7bWFyZ2luOjA7LW1vei11c2VyLXNlbGVjdDpub25lfXh1bHxmaWxlZmllbGR7LW1vei1hcHBlYXJhbmNlOm5vbmU7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDtib3JkZXI6MDtwYWRkaW5nOjB9eHVsfCouZmlsZUZpZWxkQ29udGVudEJveHtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fXh1bHwqLmZpbGVGaWVsZEljb257bWFyZ2luLWlubGluZS1zdGFydDoxMHB4O21hcmdpbi1pbmxpbmUtZW5kOjB9eHVsfCouZmlsZUZpZWxkTGFiZWx7bWFyZ2luLWlubGluZS1zdGFydDotMjZweDtwYWRkaW5nLWlubGluZS1zdGFydDozNnB4fXh1bHx0ZXh0Ym94K3h1bHxidXR0b24seHVsfGZpbGVmaWVsZCt4dWx8YnV0dG9ue2JvcmRlci1pbmxpbmUtc3RhcnQ6bm9uZX14dWx8cmljaGxpc3Rib3gseHVsfGxpc3Rib3h7LW1vei1hcHBlYXJhbmNlOm5vbmU7bWFyZ2luLWlubGluZS1zdGFydDowO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZCk7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcil9eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLXJvdyx4dWx8bGlzdGJveCB4dWx8bGlzdGl0ZW17cGFkZGluZzouM2VtO21hcmdpbjowO2JvcmRlcjowO2JvcmRlci1yYWRpdXM6MDtiYWNrZ3JvdW5kLWltYWdlOm5vbmV9eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLXJvdyhob3ZlcikseHVsfGxpc3Rib3ggeHVsfGxpc3RpdGVtOmhvdmVye2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1pdGVtLWhvdmVyKX14dWx8dHJlZWNoaWxkcmVuOjotbW96LXRyZWUtcm93KHNlbGVjdGVkKSx4dWx8bGlzdGJveCB4dWx8bGlzdGl0ZW1bc2VsZWN0ZWQ9InRydWUiXXtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtaXRlbS1zZWxlY3RlZCk7Y29sb3I6dmFyKC0taW4tY29udGVudC1zZWxlY3RlZC10ZXh0KX14dWx8dHJlZXstbW96LWFwcGVhcmFuY2U6bm9uZTtmb250LXNpemU6MWVtO2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQpO21hcmdpbjowfXh1bHx0cmVlOi1tb3otZm9jdXNyaW5nLHh1bHxyaWNobGlzdGJveDotbW96LWZvY3VzcmluZ3tib3JkZXI6MXB4IGRvdHRlZCB2YXIoLS1pbi1jb250ZW50LWJvcmRlci1mb2N1cyl9eHVsfGxpc3RoZWFkZXIseHVsfHRyZWVjb2xzey1tb3otYXBwZWFyYW5jZTpub25lO2JvcmRlcjowO2JvcmRlci1ib3R0b206MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm9yZGVyLWNvbG9yKTtwYWRkaW5nOjB9LmF1dG9jb21wbGV0ZS10cmVlPnh1bHx0cmVlY29sc3tib3JkZXItYm90dG9tOm5vbmUhaW1wb3J0YW50fXh1bHx0cmVlY29sOm5vdChbaGlkZWhlYWRlcj0idHJ1ZSJdKSx4dWx8dHJlZWNvbHBpY2tlcnstbW96LWFwcGVhcmFuY2U6bm9uZTtib3JkZXI6MDtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtaG92ZXIpO2NvbG9yOiM4MDgwODA7cGFkZGluZzo1cHggMTBweH14dWx8dHJlZWNvbDpub3QoW2hpZGVoZWFkZXI9InRydWUiXSk6bm90KFtzb3J0YWJsZT0iZmFsc2UiXSk6aG92ZXIseHVsfHRyZWVjb2xwaWNrZXI6aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWFjdGl2ZSk7Y29sb3I6dmFyKC0taW4tY29udGVudC10ZXh0LWNvbG9yKX14dWx8dHJlZWNvbDpub3QoW2hpZGVoZWFkZXI9InRydWUiXSk6bm90KDpmaXJzdC1jaGlsZCkseHVsfHRyZWVjb2xwaWNrZXJ7Ym9yZGVyLWlubGluZS1zdGFydC13aWR0aDoxcHg7Ym9yZGVyLWlubGluZS1zdGFydC1zdHlsZTpzb2xpZDtib3JkZXItaW1hZ2U6bGluZWFyLWdyYWRpZW50KHRyYW5zcGFyZW50IDAsdHJhbnNwYXJlbnQgMjAlLCNjMWMxYzEgMjAlLCNjMWMxYzEgODAlLHRyYW5zcGFyZW50IDgwJSx0cmFuc3BhcmVudCAxMDAlKSAxIDF9eHVsfHRyZWVjb2w6bm90KFtoaWRlaGVhZGVyPSJ0cnVlIl0pPnh1bHwqLnRyZWVjb2wtc29ydGRpcmVjdGlvbltzb3J0RGlyZWN0aW9uXXt3aWR0aDoxOHB4O2hlaWdodDoxOHB4fXh1bHx0cmVlY29sOm5vdChbaGlkZWhlYWRlcj0idHJ1ZSJdKT54dWx8Ki50cmVlY29sLXNvcnRkaXJlY3Rpb25bc29ydERpcmVjdGlvbj0iYXNjZW5kaW5nIl17dHJhbnNmb3JtOnNjYWxlWSgtMSl9eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLXJvd3ttaW4taGVpZ2h0OjJlbX14dWx8dHJlZWNoaWxkcmVuOjotbW96LXRyZWUtY2VsbC10ZXh0e2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcil9eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLWNlbGwtdGV4dChzZWxlY3RlZCl7Y29sb3I6dmFyKC0taW4tY29udGVudC1zZWxlY3RlZC10ZXh0KX14dWx8Y2FwdGlvbntiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50fXh1bHxidXR0b24saHRtbHxidXR0b24seHVsfGNvbG9ycGlja2VyW3R5cGU9ImJ1dHRvbiJdLHh1bHxtZW51bGlzdHttYXJnaW46MnB4IDRweH14dWx8bWVudWxpc3Q6bm90KFtlZGl0YWJsZT0idHJ1ZSJdKT54dWx8Ki5tZW51bGlzdC1kcm9wbWFya2Vye21hcmdpbi10b3A6MXB4O21hcmdpbi1ib3R0b206MXB4fXh1bHxjaGVja2JveHtwYWRkaW5nLWlubGluZS1zdGFydDowfXh1bHwqLmJ1dHRvbi1ib3gseHVsfCoubWVudWxpc3QtbGFiZWwtYm94LHh1bHwqLnJhZGlvLWxhYmVsLWJveCx4dWx8Ki5jaGVja2JveC1sYWJlbC1ib3h7Ym9yZGVyLXN0eWxlOm5vbmV9eHVsfGJ1dHRvbjotbW96LWZvY3VzcmluZz54dWx8Ki5idXR0b24tYm94LHh1bHxtZW51bGlzdDotbW96LWZvY3VzcmluZz54dWx8Ki5tZW51bGlzdC1sYWJlbC1ib3gseHVsfHJhZGlvW2ZvY3VzZWQ9InRydWUiXT54dWx8Ki5yYWRpby1sYWJlbC1ib3gsaHRtbHxpbnB1dFt0eXBlPSJjaGVja2JveCJdOi1tb3otZm9jdXNyaW5nK2h0bWx8bGFiZWw6YmVmb3JlLHh1bHxjaGVja2JveDotbW96LWZvY3VzcmluZz54dWx8Ki5jaGVja2JveC1sYWJlbC1ib3h7b3V0bGluZToxcHggZG90dGVkfWJvZHl7ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtib3gtc2l6aW5nOmJvcmRlci1ib3g7bWluLWhlaWdodDoxMDB2aDtwYWRkaW5nLXRvcDowO3BhZGRpbmctYm90dG9tOjA7cGFkZGluZy1pbmxpbmUtc3RhcnQ6Y2FsYyg0OHB4KzQuNmVtKTtwYWRkaW5nLWlubGluZS1lbmQ6NDhweDthbGlnbi1pdGVtczpjZW50ZXI7anVzdGlmeS1jb250ZW50OmNlbnRlcn0uY29udGFpbmVye21pbi13aWR0aDoxM2VtO21heC13aWR0aDo1MmVtfS5jb250YWluZXIucmVzdG9yZS1jaG9zZW57ZGlzcGxheTpmbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjtmbGV4LWdyb3c6MTttYXJnaW46MTB2aCAwfS50aXRsZXtiYWNrZ3JvdW5kLXBvc2l0aW9uOmxlZnQgMDtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1zaXplOjEuNmVtO21hcmdpbi1pbmxpbmUtc3RhcnQ6LTIuM2VtO3BhZGRpbmctaW5saW5lLXN0YXJ0OjIuM2VtO2ZvbnQtc2l6ZToyLjVlbX0udGl0bGU6ZGlyKHJ0bCl7YmFja2dyb3VuZC1wb3NpdGlvbjpyaWdodCAwfS50aXRsZS10ZXh0e2JvcmRlci1ib3R0b206MXB4IHNvbGlkICNjMWMxYzE7Zm9udC1zaXplOmluaGVyaXQ7cGFkZGluZy1ib3R0b206LjRlbX0uYnV0dG9uLWNvbnRhaW5lcnttYXJnaW4tdG9wOjEuMmVtfS5idXR0b24tY29udGFpbmVyPmJ1dHRvbnttaW4td2lkdGg6MTUwcHh9LmJ1dHRvbi1jb250YWluZXI+YnV0dG9uOmZpcnN0LWNoaWxke21hcmdpbi1pbmxpbmUtc3RhcnQ6MH1ib2R5e2JhY2tncm91bmQtc2l6ZTo2NHB4IDMycHg7YmFja2dyb3VuZC1yZXBlYXQ6cmVwZWF0LXg7cGFkZGluZzo3NXB4IDA7bWluLXdpZHRoOjEzZW19LmJ1dHRvbi1jb250YWluZXJ7ZGlzcGxheTpmbGV4O2ZsZXgtZmxvdzpyb3cgd3JhcH0uYnV0dG9uLXNwYWNlcntmbGV4OjF9Ym9keXtiYWNrZ3JvdW5kLWltYWdlOmxpbmVhci1ncmFkaWVudCgtNDVkZWcsI2YwZDAwMCwjZjBkMDAwIDMzJSwjZmVkYzAwIDMzJSwjZmVkYzAwIDY2JSwjZjBkMDAwIDY2JSwjZjBkMDAwKX1hLGE6YWN0aXZlLGE6Zm9jdXN7b3V0bGluZTpub25lO2N1cnNvcjpkZWZhdWx0fXVse21hcmdpbjowLjFlbSAwO3BhZGRpbmc6MCAxZW07bGlzdC1zdHlsZTpub25lO31saTpiZWZvcmV7Y29udGVudDoiIjtib3JkZXItY29sb3I6dHJhbnNwYXJlbnQgIzExMTtib3JkZXItc3R5bGU6c29saWQ7Ym9yZGVyLXdpZHRoOjAuMzVlbSAwIDAuMzVlbSAwLjQ1ZW07ZGlzcGxheTpibG9jaztoZWlnaHQ6MDt3aWR0aDowO2xlZnQ6LTFlbTt0b3A6MWVtO3Bvc2l0aW9uOnJlbGF0aXZlfS8vLS0+PC9zdHlsZT4NCjwvaGVhZD48Ym9keT48ZGl2IGlkPSJlcnJvclBhZ2VDb250YWluZXIiIGNsYXNzPSJjb250YWluZXIiPg0KPGRpdiBjbGFzcz0idGl0bGUiPjxoMSBjbGFzcz0idGl0bGUtdGV4dCI+WW91ciBjb25uZWN0aW9uIGlzIG5vdCBzZWN1cmU8L2gxPjwvZGl2PjxkaXYgaWQ9ImVycm9yTG9uZ0NvbnRlbnQiPg0KPGRpdiBpZD0iZXJyb3JTaG9ydERlc2MiPjxwIGlkPSJlcnJvclNob3J0RGVzY1RleHQiPg0KVGhlIG93bmVyIG9mIHRoaXMgd2Vic2l0ZSBoYXMgY29uZmlndXJlZCB0aGVpciB3ZWJzaXRlIGltcHJvcGVybHkuDQpUaGUgY29ubmVjdGlvbiBiZXR3ZWVuIHlvdSBhbmQgPGI+JSVDRl9IT1NUTkFNRSUlPC9iPiBpcyBiZWluZyBNSVRNZWQgYnkgPGI+JSVDRl9QUk9ETkFNRSUlPC9iPi4NClRvIHByb3RlY3QgeW91ciBpbmZvcm1hdGlvbiBmcm9tIGJlaW5nIHN0b2xlbiwgdGhlIGFkZC1vbiBzdG9wcGVkIGZ1cnRoZXIgY29ubmVjdGlvbiB0byB0aGlzIHdlYnNpdGUuDQo8L3A+PC9kaXY+PGRpdiBpZD0iY2VydEVycm9yQW5kQ2FwdGl2ZVBvcnRhbEJ1dHRvbkNvbnRhaW5lciIgY2xhc3M9ImJ1dHRvbi1jb250YWluZXIiPg0KPHVsPg0KPGxpPjxhIGhyZWY9IiUlQ0ZfVVJMX0xBU1RPSyUlIj48Yj5HbyBCYWNrPC9iPjwvYT48L2xpPg0KPGxpPjxhIGhyZWY9Imh0dHBzOi8vd2ViLmFyY2hpdmUub3JnL3dlYi8lJUNGX1VSTF9JVFNNRSUlIj48Yj5UcnkgV2F5YmFjayBNYWNoaW5lPC9iPjwvYT48L2xpPg0KPC91bD4NCjxkaXYgY2xhc3M9ImJ1dHRvbi1zcGFjZXIiPjwvZGl2Pg0KPHVsPg0KPGxpPjxhIGhyZWY9Imh0dHBzOi8vMC4wLjAuMC9jZm1pdG1fYWRkb24vYWxsb3cvJSVDRl9XSElURVBBSVIlJSI+PGI+QWRkIEV4Y2VwdGlvbjwvYj48L2E+PC9saT4NCjxsaT48YSBocmVmPSJodHRwczovLzAuMC4wLjAvY2ZtaXRtX2FkZG9uL2FsbG93L3ZpZXdleGNlcHRpb25zP25vdyI+PGI+VmlldyBFeGNlcHRpb25zPC9iPjwvYT48L2xpPg0KPC91bD4NCjwvZGl2PjwvZGl2PjwvZGl2PjwvYm9keT48L2h0bWw+';
+var cf_template_wlnotify = 'PGh0bWwgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPjxoZWFkPjx0aXRsZT5Hb29kIENvbm5lY3Rpb248L3RpdGxlPjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PCEtLUBuYW1lc3BhY2UgaHRtbCAiaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCI7QG5hbWVzcGFjZSB4dWwgImh0dHA6Ly93d3cubW96aWxsYS5vcmcva2V5bWFzdGVyL2dhdGVrZWVwZXIvdGhlcmUuaXMub25seS54dWwiOyp8Kjpyb290ey0taW4tY29udGVudC1wYWdlLWNvbG9yOiM0MjRlNWE7LS1pbi1jb250ZW50LXBhZ2UtYmFja2dyb3VuZDojZmJmYmZiOy0taW4tY29udGVudC10ZXh0LWNvbG9yOiMzMzM7LS1pbi1jb250ZW50LXNlbGVjdGVkLXRleHQ6I2ZmZjstLWluLWNvbnRlbnQtaGVhZGVyLWJvcmRlci1jb2xvcjojYzhjOGM4Oy0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZDojZmZmOy0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZC1vZGQ6I2YzZjZmYTstLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtaG92ZXI6I2ViZWJlYjstLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQtYWN0aXZlOiNkYWRhZGE7LS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3I6I2MxYzFjMTstLWluLWNvbnRlbnQtaXRlbS1ob3ZlcjpyZ2JhKDAsMTQ5LDIyMSwwLjI1KTstLWluLWNvbnRlbnQtaXRlbS1zZWxlY3RlZDojMDA5NWRkOy0taW4tY29udGVudC1ib3JkZXItaGlnaGxpZ2h0OiNmZjk1MDA7LS1pbi1jb250ZW50LWJvcmRlci1mb2N1czojMDA5NWRkOy0taW4tY29udGVudC1ib3JkZXItY29sb3I6I2MxYzFjMTstLWluLWNvbnRlbnQtY2F0ZWdvcnktdGV4dDojYzFjMWMxOy0taW4tY29udGVudC1jYXRlZ29yeS1ib3JkZXItZm9jdXM6MXB4IGRvdHRlZCAjZmZmOy0taW4tY29udGVudC1jYXRlZ29yeS10ZXh0LXNlbGVjdGVkOiNmMmYyZjI7LS1pbi1jb250ZW50LWNhdGVnb3J5LWJhY2tncm91bmQ6IzQyNGY1YTstLWluLWNvbnRlbnQtY2F0ZWdvcnktYmFja2dyb3VuZC1ob3ZlcjojNWU2OTcyOy0taW4tY29udGVudC1jYXRlZ29yeS1iYWNrZ3JvdW5kLWFjdGl2ZTojMzQzZjQ4Oy0taW4tY29udGVudC10YWItY29sb3I6IzQyNGY1YTstLWluLWNvbnRlbnQtbGluay1jb2xvcjojMDA5NWRkOy0taW4tY29udGVudC1saW5rLWNvbG9yLWhvdmVyOiMxNzhjZTU7LS1pbi1jb250ZW50LWxpbmstY29sb3ItYWN0aXZlOiNmZjk1MDA7LS1pbi1jb250ZW50LWxpbmstY29sb3ItdmlzaXRlZDojNTUxYThiOy0taW4tY29udGVudC1wcmltYXJ5LWJ1dHRvbi1iYWNrZ3JvdW5kOiMwMDk1ZGQ7LS1pbi1jb250ZW50LXByaW1hcnktYnV0dG9uLWJhY2tncm91bmQtaG92ZXI6IzAwOGFjYjstLWluLWNvbnRlbnQtcHJpbWFyeS1idXR0b24tYmFja2dyb3VuZC1hY3RpdmU6IzAwNmI5ZDstLWluLWNvbnRlbnQtdGFibGUtYm9yZGVyLWRhcmstY29sb3I6I2QxZDFkMTstLWluLWNvbnRlbnQtdGFibGUtaGVhZGVyLWJhY2tncm91bmQ6IzAwOTVkZH1odG1sfGh0bWwseHVsfHBhZ2UseHVsfHdpbmRvd3tmb250Om1lc3NhZ2UtYm94Oy1tb3otYXBwZWFyYW5jZTpub25lO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wYWdlLWJhY2tncm91bmQpO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtcGFnZS1jb2xvcil9aHRtbHxib2R5e2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0Om5vcm1hbDttYXJnaW46MH1odG1sfGgxe2ZvbnQtc2l6ZToyLjVlbTtmb250LXdlaWdodDpsaWdodGVyO2xpbmUtaGVpZ2h0OjEuMjtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRleHQtY29sb3IpO21hcmdpbjowO21hcmdpbi1ib3R0b206LjVlbX1odG1sfGhye2JvcmRlci1zdHlsZTpzb2xpZCBub25lIG5vbmUgbm9uZTtib3JkZXItY29sb3I6dmFyKC0taW4tY29udGVudC1ib3JkZXItY29sb3IpfXh1bHxjYXB0aW9uey1tb3otYXBwZWFyYW5jZTpub25lO21hcmdpbjowfXh1bHxjYXB0aW9uPnh1bHxjaGVja2JveCx4dWx8Y2FwdGlvbj54dWx8bGFiZWx7Zm9udC1zaXplOjEuM3JlbTtmb250LXdlaWdodDpib2xkO2xpbmUtaGVpZ2h0OjIycHh9eHVsfGNhcHRpb24+eHVsfGNoZWNrYm94LHh1bHxjYXB0aW9uPnh1bHxsYWJlbHttYXJnaW46MCFpbXBvcnRhbnR9KnwqLm1haW4tY29udGVudHtwYWRkaW5nLXRvcDo0MHB4O3BhZGRpbmctaW5saW5lLWVuZDo0NHB4O3BhZGRpbmctYm90dG9tOjQ4cHg7cGFkZGluZy1pbmxpbmUtc3RhcnQ6NDhweDtvdmVyZmxvdzphdXRvfXh1bHxwcmVmcGFuZT54dWx8Ki5jb250ZW50LWJveHtvdmVyZmxvdzp2aXNpYmxlfXh1bHxncm91cGJveHstbW96LWFwcGVhcmFuY2U6bm9uZTtib3JkZXI6MDttYXJnaW46MTVweCAwIDA7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MDtwYWRkaW5nLWlubGluZS1lbmQ6MDtmb250LXNpemU6MS4yNXJlbX14dWx8Z3JvdXBib3ggeHVsfGxhYmVsOm5vdCgubWVudS1hY2NlbCk6bm90KC5tZW51LXRleHQpOm5vdCguaW5kZW50KSx4dWx8Z3JvdXBib3ggeHVsfGRlc2NyaXB0aW9ue21hcmdpbi1pbmxpbmUtc3RhcnQ6MCFpbXBvcnRhbnQ7bWFyZ2luLWlubGluZS1lbmQ6MCFpbXBvcnRhbnR9eHVsfHRhYnBhbmVsc3stbW96LWFwcGVhcmFuY2U6bm9uZTtmb250LXNpemU6MS4yNXJlbTtsaW5lLWhlaWdodDoyMnB4O2JvcmRlcjowO3BhZGRpbmc6MDtiYWNrZ3JvdW5kLWNvbG9yOnRyYW5zcGFyZW50O2NvbG9yOmluaGVyaXR9eHVsfHRhYnN7bWFyZ2luLWJvdHRvbToxNXB4O2JvcmRlci10b3A6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7Ym9yZGVyLWJvdHRvbToxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtcGFnZS1iYWNrZ3JvdW5kKX14dWx8Ki50YWJzLWxlZnQseHVsfCoudGFicy1yaWdodHtib3JkZXItYm90dG9tOjB9eHVsfHRhYnstbW96LWFwcGVhcmFuY2U6bm9uZTttYXJnaW4tdG9wOjA7cGFkZGluZzo0cHggMjBweDttaW4taGVpZ2h0OjQ0cHg7Y29sb3I6dmFyKC0taW4tY29udGVudC10YWItY29sb3IpO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wYWdlLWJhY2tncm91bmQpO2JvcmRlci13aWR0aDowO2JvcmRlci1yYWRpdXM6MCFpbXBvcnRhbnQ7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kLWNvbG9yIDUwbXMgZWFzZSAwc314dWx8dGFiOmhvdmVye2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZC1ob3Zlcil9eHVsfHRhYltzZWxlY3RlZF17YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWhvdmVyKTtwYWRkaW5nLWJvdHRvbTowO2JvcmRlci1ib3R0b206NHB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm9yZGVyLWhpZ2hsaWdodCl9eHVsfCoudGFiLXRleHR7Zm9udC1zaXplOjEuM3JlbTtsaW5lLWhlaWdodDoyMnB4fWh0bWx8YnV0dG9ue3BhZGRpbmc6M3B4O2ZvbnQ6aW5oZXJpdH0qfGJ1dHRvbixodG1sfHNlbGVjdCx4dWx8Y29sb3JwaWNrZXJbdHlwZT0iYnV0dG9uIl0seHVsfG1lbnVsaXN0ey1tb3otYXBwZWFyYW5jZTpub25lO21pbi1oZWlnaHQ6MzBweDtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRleHQtY29sb3IpO2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTstbW96LWJvcmRlci10b3AtY29sb3JzOm5vbmUhaW1wb3J0YW50Oy1tb3otYm9yZGVyLXJpZ2h0LWNvbG9yczpub25lIWltcG9ydGFudDstbW96LWJvcmRlci1ib3R0b20tY29sb3JzOm5vbmUhaW1wb3J0YW50Oy1tb3otYm9yZGVyLWxlZnQtY29sb3JzOm5vbmUhaW1wb3J0YW50O2JvcmRlci1yYWRpdXM6MnB4O2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wYWdlLWJhY2tncm91bmQpfWh0bWx8YnV0dG9uOmVuYWJsZWQ6aG92ZXIsaHRtbHxzZWxlY3Q6ZW5hYmxlZDpob3Zlcix4dWx8YnV0dG9uOm5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXIseHVsfGNvbG9ycGlja2VyW3R5cGU9ImJ1dHRvbiJdOm5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXIseHVsfG1lbnVsaXN0Om5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWhvdmVyKX1odG1sfGJ1dHRvbjplbmFibGVkOmhvdmVyOmFjdGl2ZSxodG1sfHNlbGVjdDplbmFibGVkOmhvdmVyOmFjdGl2ZSx4dWx8YnV0dG9uOm5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXI6YWN0aXZlLHh1bHxjb2xvcnBpY2tlclt0eXBlPSJidXR0b24iXTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pOmhvdmVyOmFjdGl2ZSx4dWx8bWVudWxpc3Rbb3Blbj0idHJ1ZSJdOm5vdChbZGlzYWJsZWQ9InRydWUiXSl7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWFjdGl2ZSl9aHRtbHxidXR0b246ZGlzYWJsZWQsaHRtbHxzZWxlY3Q6ZGlzYWJsZWQseHVsfGJ1dHRvbltkaXNhYmxlZD0idHJ1ZSJdLHh1bHxjb2xvcnBpY2tlclt0eXBlPSJidXR0b24iXVtkaXNhYmxlZD0idHJ1ZSJdLHh1bHxtZW51bGlzdFtkaXNhYmxlZD0idHJ1ZSJde29wYWNpdHk6LjV9KnxidXR0b24ucHJpbWFyeXtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtcHJpbWFyeS1idXR0b24tYmFja2dyb3VuZCk7Ym9yZGVyLWNvbG9yOnRyYW5zcGFyZW50O2NvbG9yOnZhcigtLWluLWNvbnRlbnQtc2VsZWN0ZWQtdGV4dCl9aHRtbHxidXR0b24ucHJpbWFyeTplbmFibGVkOmhvdmVyLHh1bHxidXR0b24ucHJpbWFyeTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pOmhvdmVye2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1wcmltYXJ5LWJ1dHRvbi1iYWNrZ3JvdW5kLWhvdmVyKX1odG1sfGJ1dHRvbi5wcmltYXJ5OmVuYWJsZWQ6aG92ZXI6YWN0aXZlLHh1bHxidXR0b24ucHJpbWFyeTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pOmhvdmVyOmFjdGl2ZXtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtcHJpbWFyeS1idXR0b24tYmFja2dyb3VuZC1hY3RpdmUpfXh1bHxjb2xvcnBpY2tlclt0eXBlPSJidXR0b24iXXtwYWRkaW5nOjZweDt3aWR0aDo1MHB4fXh1bHxidXR0b24+eHVsfCouYnV0dG9uLWJveHtwYWRkaW5nLXJpZ2h0OjEwcHghaW1wb3J0YW50O3BhZGRpbmctbGVmdDoxMHB4IWltcG9ydGFudH14dWx8bWVudWxpc3Q+eHVsfCoubWVudWxpc3QtbGFiZWwtYm94Pnh1bHwqLm1lbnVsaXN0LWljb25bc3JjXXttYXJnaW4taW5saW5lLWVuZDo1cHh9eHVsfGJ1dHRvblt0eXBlPSJtZW51Il0+eHVsfCouYnV0dG9uLWJveD54dWx8Ki5idXR0b24tbWVudS1kcm9wbWFya2Vyey1tb3otYXBwZWFyYW5jZTpub25lO21hcmdpbjoxcHggMDttYXJnaW4taW5saW5lLXN0YXJ0OjEwcHg7cGFkZGluZzowO3dpZHRoOjEwcHg7aGVpZ2h0OjE2cHg7Ym9yZGVyOjA7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudH14dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8bWVudXBvcHVwey1tb3otYXBwZWFyYW5jZTpub25lO2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtib3JkZXItcmFkaXVzOjJweDtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQpfXh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwIHh1bHxtZW51LHh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwIHh1bHxtZW51aXRlbSx4dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8bWVudXBvcHVwIHh1bHxtZW51LHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXAgeHVsfG1lbnVpdGVtey1tb3otYXBwZWFyYW5jZTpub25lO2ZvbnQtc2l6ZToxZW07Y29sb3I6dmFyKC0taW4tY29udGVudC10ZXh0LWNvbG9yKTtwYWRkaW5nLXRvcDouMmVtO3BhZGRpbmctYm90dG9tOi4yZW07cGFkZGluZy1pbmxpbmUtc3RhcnQ6MTBweDtwYWRkaW5nLWlubGluZS1lbmQ6MzBweH14dWx8bWVudWxpc3Q+eHVsfG1lbnVwb3B1cD54dWx8bWVudTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW19tb3otbWVudWFjdGl2ZT0idHJ1ZSJdLHh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51aXRlbTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW19tb3otbWVudWFjdGl2ZT0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnU6bm90KFtkaXNhYmxlZD0idHJ1ZSJdKVtfbW96LW1lbnVhY3RpdmU9InRydWUiXSx4dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8bWVudXBvcHVwPnh1bHxtZW51aXRlbTpub3QoW2Rpc2FibGVkPSJ0cnVlIl0pW19tb3otbWVudWFjdGl2ZT0idHJ1ZSJde2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcik7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWl0ZW0taG92ZXIpfXh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51Om5vdChbZGlzYWJsZWQ9InRydWUiXSlbc2VsZWN0ZWQ9InRydWUiXSx4dWx8bWVudWxpc3Q+eHVsfG1lbnVwb3B1cD54dWx8bWVudWl0ZW06bm90KFtkaXNhYmxlZD0idHJ1ZSJdKVtzZWxlY3RlZD0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnU6bm90KFtkaXNhYmxlZD0idHJ1ZSJdKVtzZWxlY3RlZD0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnVpdGVtOm5vdChbZGlzYWJsZWQ9InRydWUiXSlbc2VsZWN0ZWQ9InRydWUiXXtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXNlbGVjdGVkLXRleHQpO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1pdGVtLXNlbGVjdGVkKX14dWx8bWVudWxpc3Q+eHVsfG1lbnVwb3B1cD54dWx8bWVudVtkaXNhYmxlZD0idHJ1ZSJdLHh1bHxtZW51bGlzdD54dWx8bWVudXBvcHVwPnh1bHxtZW51aXRlbVtkaXNhYmxlZD0idHJ1ZSJdLHh1bHxidXR0b25bdHlwZT0ibWVudSJdPnh1bHxtZW51cG9wdXA+eHVsfG1lbnVbZGlzYWJsZWQ9InRydWUiXSx4dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8bWVudXBvcHVwPnh1bHxtZW51aXRlbVtkaXNhYmxlZD0idHJ1ZSJde2NvbG9yOiM5OTk7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudH14dWx8bWVudWxpc3Q+eHVsfG1lbnVwb3B1cCB4dWx8bWVudXNlcGFyYXRvcix4dWx8YnV0dG9uW3R5cGU9Im1lbnUiXT54dWx8bWVudXBvcHVwIHh1bHxtZW51c2VwYXJhdG9yey1tb3otYXBwZWFyYW5jZTpub25lO21hcmdpbjowO3BhZGRpbmc6MDtib3JkZXItdG9wOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpO2JvcmRlci1ib3R0b206MH1odG1sfGlucHV0W3R5cGU9InRleHQiXSxodG1sfHRleHRhcmVhLHh1bHx0ZXh0Ym94ey1tb3otYXBwZWFyYW5jZTpub25lO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcik7Ym9yZGVyOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJveC1ib3JkZXItY29sb3IpOy1tb3otYm9yZGVyLXRvcC1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7LW1vei1ib3JkZXItcmlnaHQtY29sb3JzOm5vbmUhaW1wb3J0YW50Oy1tb3otYm9yZGVyLWJvdHRvbS1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7LW1vei1ib3JkZXItbGVmdC1jb2xvcnM6bm9uZSFpbXBvcnRhbnQ7Ym9yZGVyLXJhZGl1czoycHg7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kKX14dWx8dGV4dGJveHttaW4taGVpZ2h0OjMwcHg7cGFkZGluZy1yaWdodDoxMHB4O3BhZGRpbmctbGVmdDoxMHB4fXh1bHx0ZXh0Ym94LnRyZWUtaW5wdXR7bWluLWhlaWdodDp1bnNldDtwYWRkaW5nLXJpZ2h0OnVuc2V0O3BhZGRpbmctbGVmdDp1bnNldH1odG1sfGlucHV0W3R5cGU9InRleHQiXSxodG1sfHRleHRhcmVhe2ZvbnQtZmFtaWx5OmluaGVyaXQ7Zm9udC1zaXplOmluaGVyaXQ7cGFkZGluZzo1cHggMTBweH1odG1sfGlucHV0W3R5cGU9InRleHQiXTpmb2N1cyxodG1sfHRleHRhcmVhOmZvY3VzLHh1bHx0ZXh0Ym94W2ZvY3VzZWRde2JvcmRlci1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJvcmRlci1mb2N1cyl9aHRtbHxpbnB1dFt0eXBlPSJ0ZXh0Il06ZGlzYWJsZWQsaHRtbHx0ZXh0YXJlYTpkaXNhYmxlZCx4dWx8dGV4dGJveFtkaXNhYmxlZD0idHJ1ZSJde29wYWNpdHk6LjV9aHRtbHxhLC50ZXh0LWxpbmt7Y29sb3I6dmFyKC0taW4tY29udGVudC1saW5rLWNvbG9yKTt0ZXh0LWRlY29yYXRpb246bm9uZX1odG1sfGE6aG92ZXIsLnRleHQtbGluazpob3Zlcntjb2xvcjp2YXIoLS1pbi1jb250ZW50LWxpbmstY29sb3ItaG92ZXIpO3RleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmV9aHRtbHxhOnZpc2l0ZWR7Y29sb3I6dmFyKC0taW4tY29udGVudC1saW5rLWNvbG9yLXZpc2l0ZWQpfWh0bWx8YTpob3ZlcjphY3RpdmUsLnRleHQtbGluazpob3ZlcjphY3RpdmV7Y29sb3I6dmFyKC0taW4tY29udGVudC1saW5rLWNvbG9yLWFjdGl2ZSk7dGV4dC1kZWNvcmF0aW9uOm5vbmV9aHRtbHxpbnB1dFt0eXBlPSJjaGVja2JveCJde29wYWNpdHk6MDt3aWR0aDowO3BvaW50ZXItZXZlbnRzOm5vbmU7cG9zaXRpb246YWJzb2x1dGV9aHRtbHxpbnB1dFt0eXBlPSJjaGVja2JveCJdK2h0bWx8bGFiZWw6YmVmb3Jle2Rpc3BsYXk6aW5saW5lLWJsb2NrO2NvbnRlbnQ6IiI7dmVydGljYWwtYWxpZ246bWlkZGxlfWh0bWx8aW5wdXRbdHlwZT0iY2hlY2tib3giXStodG1sfGxhYmVse2xpbmUtaGVpZ2h0OjB9eHVsfGNoZWNrYm94e21hcmdpbi1pbmxpbmUtc3RhcnQ6MH14dWx8Ki5jaGVja2JveC1jaGVjayxodG1sfGlucHV0W3R5cGU9ImNoZWNrYm94Il0raHRtbHxsYWJlbDpiZWZvcmV7LW1vei1hcHBlYXJhbmNlOm5vbmU7d2lkdGg6MjNweDtoZWlnaHQ6MjNweDtib3JkZXItcmFkaXVzOjJweDtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7bWFyZ2luLWlubGluZS1lbmQ6MTBweDtiYWNrZ3JvdW5kLWNvbG9yOiNmMWYxZjE7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQoI2ZmZixyZ2JhKDI1NSwyNTUsMjU1LDAuOCkpIWltcG9ydGFudDtiYWNrZ3JvdW5kLXBvc2l0aW9uOmNlbnRlciBjZW50ZXI7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JveC1zaGFkb3c6MCAxcHggMXB4IDAgI2ZmZixpbnNldCAwIDJweCAwIDAgcmdiYSgwLDAsMCwwLjAzKX14dWx8Y2hlY2tib3g6bm90KFtkaXNhYmxlZD0idHJ1ZSJdKTpob3Zlcj54dWx8Ki5jaGVja2JveC1jaGVjayxodG1sfGlucHV0W3R5cGU9ImNoZWNrYm94Il06bm90KDpkaXNhYmxlZCkraHRtbHxsYWJlbDpob3ZlcjpiZWZvcmV7Ym9yZGVyLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm9yZGVyLWZvY3VzKX14dWx8Y2hlY2tib3hbZGlzYWJsZWQ9InRydWUiXT54dWx8Ki5jaGVja2JveC1jaGVjayxodG1sfGlucHV0W3R5cGU9ImNoZWNrYm94Il06ZGlzYWJsZWQraHRtbHxsYWJlbHtvcGFjaXR5Oi41fXh1bHwqLmNoZWNrYm94LWxhYmVsLWJveHttYXJnaW4taW5saW5lLXN0YXJ0Oi0xcHg7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MH14dWx8cmljaGxpc3RpdGVtPnh1bHwqLmNoZWNrYm94LWNoZWNre21hcmdpbjozcHggNnB4fXh1bHxyYWRpb3ttYXJnaW4taW5saW5lLXN0YXJ0OjB9eHVsfCoucmFkaW8tY2hlY2t7LW1vei1hcHBlYXJhbmNlOm5vbmU7d2lkdGg6MjNweDtoZWlnaHQ6MjNweDtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7Ym9yZGVyLXJhZGl1czo1MCU7bWFyZ2luLWlubGluZS1lbmQ6MTBweDtiYWNrZ3JvdW5kLWNvbG9yOiNmMWYxZjE7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQoI2ZmZixyZ2JhKDI1NSwyNTUsMjU1LDAuODApKTtib3gtc2hhZG93OjAgMXB4IDFweCAwICNmZmYsaW5zZXQgMCAycHggMCAwIHJnYmEoMCwwLDAsMC4wMyl9eHVsfHJhZGlvOm5vdChbZGlzYWJsZWQ9InRydWUiXSk6aG92ZXI+eHVsfCoucmFkaW8tY2hlY2t7Ym9yZGVyLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm9yZGVyLWZvY3VzKX14dWx8cmFkaW9bZGlzYWJsZWQ9InRydWUiXT54dWx8Ki5yYWRpby1jaGVja3tvcGFjaXR5Oi41fXh1bHwqLnJhZGlvLWxhYmVsLWJveHttYXJnaW4taW5saW5lLXN0YXJ0Oi0xcHg7bWFyZ2luLWlubGluZS1lbmQ6MTBweDtwYWRkaW5nLWlubGluZS1zdGFydDowfSp8KiNjYXRlZ29yaWVzey1tb3otYXBwZWFyYW5jZTpub25lO2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1jYXRlZ29yeS1iYWNrZ3JvdW5kKTtwYWRkaW5nLXRvcDozOXB4O21hcmdpbjowO2JvcmRlci13aWR0aDowfSp8Ki5jYXRlZ29yeXstbW96LWFwcGVhcmFuY2U6bm9uZTtjb2xvcjp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LXRleHQpO2JvcmRlci1pbmxpbmUtZW5kLXdpZHRoOjA7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MTVweDtwYWRkaW5nLWlubGluZS1lbmQ6MjFweDttaW4taGVpZ2h0OjQwcHg7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kLWNvbG9yIDE1MG1zfSp8Ki5jYXRlZ29yeTpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtY2F0ZWdvcnktYmFja2dyb3VuZC1ob3Zlcil9KnwqLmNhdGVnb3J5W3NlbGVjdGVkXSwqfCouY2F0ZWdvcnkuc2VsZWN0ZWR7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LWJhY2tncm91bmQtYWN0aXZlKTtjb2xvcjp2YXIoLS1pbi1jb250ZW50LWNhdGVnb3J5LXRleHQtc2VsZWN0ZWQpO3BhZGRpbmctaW5saW5lLXN0YXJ0OjExcHg7Ym9yZGVyLWlubGluZS1zdGFydDpzb2xpZCA0cHggdmFyKC0taW4tY29udGVudC1ib3JkZXItaGlnaGxpZ2h0KX0qfCojY2F0ZWdvcmllc1trZXlib2FyZC1uYXZpZ2F0aW9uPSJ0cnVlIl06LW1vei1mb2N1c3Jpbmc+KnwqLmNhdGVnb3J5W2N1cnJlbnRde2JvcmRlci10b3A6dmFyKC0taW4tY29udGVudC1jYXRlZ29yeS1ib3JkZXItZm9jdXMpO2JvcmRlci1ib3R0b206dmFyKC0taW4tY29udGVudC1jYXRlZ29yeS1ib3JkZXItZm9jdXMpfSp8Ki5jYXRlZ29yeS1uYW1le2xpbmUtaGVpZ2h0OjIycHg7Zm9udC1zaXplOjEuMjVyZW07cGFkZGluZy1ib3R0b206MnB4O3BhZGRpbmctaW5saW5lLXN0YXJ0OjlweDttYXJnaW46MDstbW96LXVzZXItc2VsZWN0Om5vbmV9KnwqLmNhdGVnb3J5LWljb257d2lkdGg6MjRweDtoZWlnaHQ6MjRweH0qfCouaGVhZGVye2JvcmRlci1ib3R0b206MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtaGVhZGVyLWJvcmRlci1jb2xvcik7bWFyZ2luLWlubGluZS1lbmQ6NHB4O21hcmdpbi1ib3R0b206MTVweDtwYWRkaW5nLWJvdHRvbToxNXB4Oy1tb3otYm94LWFsaWduOmJhc2VsaW5lfSp8Ki5oZWFkZXItbmFtZXtmb250LXNpemU6Mi41cmVtO2ZvbnQtd2VpZ2h0Om5vcm1hbDtsaW5lLWhlaWdodDo0MHB4O21hcmdpbjowOy1tb3otdXNlci1zZWxlY3Q6bm9uZX14dWx8ZmlsZWZpZWxkey1tb3otYXBwZWFyYW5jZTpub25lO2JhY2tncm91bmQtY29sb3I6dHJhbnNwYXJlbnQ7Ym9yZGVyOjA7cGFkZGluZzowfXh1bHwqLmZpbGVGaWVsZENvbnRlbnRCb3h7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudH14dWx8Ki5maWxlRmllbGRJY29ue21hcmdpbi1pbmxpbmUtc3RhcnQ6MTBweDttYXJnaW4taW5saW5lLWVuZDowfXh1bHwqLmZpbGVGaWVsZExhYmVse21hcmdpbi1pbmxpbmUtc3RhcnQ6LTI2cHg7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MzZweH14dWx8dGV4dGJveCt4dWx8YnV0dG9uLHh1bHxmaWxlZmllbGQreHVsfGJ1dHRvbntib3JkZXItaW5saW5lLXN0YXJ0Om5vbmV9eHVsfHJpY2hsaXN0Ym94LHh1bHxsaXN0Ym94ey1tb3otYXBwZWFyYW5jZTpub25lO21hcmdpbi1pbmxpbmUtc3RhcnQ6MDtiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtYm94LWJhY2tncm91bmQpO2JvcmRlcjoxcHggc29saWQgdmFyKC0taW4tY29udGVudC1ib3gtYm9yZGVyLWNvbG9yKTtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRleHQtY29sb3IpfXh1bHx0cmVlY2hpbGRyZW46Oi1tb3otdHJlZS1yb3cseHVsfGxpc3Rib3ggeHVsfGxpc3RpdGVte3BhZGRpbmc6LjNlbTttYXJnaW46MDtib3JkZXI6MDtib3JkZXItcmFkaXVzOjA7YmFja2dyb3VuZC1pbWFnZTpub25lfXh1bHx0cmVlY2hpbGRyZW46Oi1tb3otdHJlZS1yb3coaG92ZXIpLHh1bHxsaXN0Ym94IHh1bHxsaXN0aXRlbTpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOnZhcigtLWluLWNvbnRlbnQtaXRlbS1ob3Zlcil9eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLXJvdyhzZWxlY3RlZCkseHVsfGxpc3Rib3ggeHVsfGxpc3RpdGVtW3NlbGVjdGVkPSJ0cnVlIl17YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWl0ZW0tc2VsZWN0ZWQpO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtc2VsZWN0ZWQtdGV4dCl9eHVsfHRyZWV7LW1vei1hcHBlYXJhbmNlOm5vbmU7Zm9udC1zaXplOjFlbTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWluLWNvbnRlbnQtYm94LWJvcmRlci1jb2xvcik7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kKTttYXJnaW46MH14dWx8dHJlZTotbW96LWZvY3VzcmluZyx4dWx8cmljaGxpc3Rib3g6LW1vei1mb2N1c3Jpbmd7Ym9yZGVyOjFweCBkb3R0ZWQgdmFyKC0taW4tY29udGVudC1ib3JkZXItZm9jdXMpfXh1bHxsaXN0aGVhZGVyLHh1bHx0cmVlY29sc3stbW96LWFwcGVhcmFuY2U6bm9uZTtib3JkZXI6MDtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1pbi1jb250ZW50LWJvcmRlci1jb2xvcik7cGFkZGluZzowfS5hdXRvY29tcGxldGUtdHJlZT54dWx8dHJlZWNvbHN7Ym9yZGVyLWJvdHRvbTpub25lIWltcG9ydGFudH14dWx8dHJlZWNvbDpub3QoW2hpZGVoZWFkZXI9InRydWUiXSkseHVsfHRyZWVjb2xwaWNrZXJ7LW1vei1hcHBlYXJhbmNlOm5vbmU7Ym9yZGVyOjA7YmFja2dyb3VuZC1jb2xvcjp2YXIoLS1pbi1jb250ZW50LWJveC1iYWNrZ3JvdW5kLWhvdmVyKTtjb2xvcjojODA4MDgwO3BhZGRpbmc6NXB4IDEwcHh9eHVsfHRyZWVjb2w6bm90KFtoaWRlaGVhZGVyPSJ0cnVlIl0pOm5vdChbc29ydGFibGU9ImZhbHNlIl0pOmhvdmVyLHh1bHx0cmVlY29scGlja2VyOmhvdmVye2JhY2tncm91bmQtY29sb3I6dmFyKC0taW4tY29udGVudC1ib3gtYmFja2dyb3VuZC1hY3RpdmUpO2NvbG9yOnZhcigtLWluLWNvbnRlbnQtdGV4dC1jb2xvcil9eHVsfHRyZWVjb2w6bm90KFtoaWRlaGVhZGVyPSJ0cnVlIl0pOm5vdCg6Zmlyc3QtY2hpbGQpLHh1bHx0cmVlY29scGlja2Vye2JvcmRlci1pbmxpbmUtc3RhcnQtd2lkdGg6MXB4O2JvcmRlci1pbmxpbmUtc3RhcnQtc3R5bGU6c29saWQ7Ym9yZGVyLWltYWdlOmxpbmVhci1ncmFkaWVudCh0cmFuc3BhcmVudCAwLHRyYW5zcGFyZW50IDIwJSwjYzFjMWMxIDIwJSwjYzFjMWMxIDgwJSx0cmFuc3BhcmVudCA4MCUsdHJhbnNwYXJlbnQgMTAwJSkgMSAxfXh1bHx0cmVlY29sOm5vdChbaGlkZWhlYWRlcj0idHJ1ZSJdKT54dWx8Ki50cmVlY29sLXNvcnRkaXJlY3Rpb25bc29ydERpcmVjdGlvbl17d2lkdGg6MThweDtoZWlnaHQ6MThweH14dWx8dHJlZWNvbDpub3QoW2hpZGVoZWFkZXI9InRydWUiXSk+eHVsfCoudHJlZWNvbC1zb3J0ZGlyZWN0aW9uW3NvcnREaXJlY3Rpb249ImFzY2VuZGluZyJde3RyYW5zZm9ybTpzY2FsZVkoLTEpfXh1bHx0cmVlY2hpbGRyZW46Oi1tb3otdHJlZS1yb3d7bWluLWhlaWdodDoyZW19eHVsfHRyZWVjaGlsZHJlbjo6LW1vei10cmVlLWNlbGwtdGV4dHtjb2xvcjp2YXIoLS1pbi1jb250ZW50LXRleHQtY29sb3IpfXh1bHx0cmVlY2hpbGRyZW46Oi1tb3otdHJlZS1jZWxsLXRleHQoc2VsZWN0ZWQpe2NvbG9yOnZhcigtLWluLWNvbnRlbnQtc2VsZWN0ZWQtdGV4dCl9eHVsfGNhcHRpb257YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudH14dWx8YnV0dG9uLGh0bWx8YnV0dG9uLHh1bHxjb2xvcnBpY2tlclt0eXBlPSJidXR0b24iXSx4dWx8bWVudWxpc3R7bWFyZ2luOjJweCA0cHh9eHVsfG1lbnVsaXN0Om5vdChbZWRpdGFibGU9InRydWUiXSk+eHVsfCoubWVudWxpc3QtZHJvcG1hcmtlcnttYXJnaW4tdG9wOjFweDttYXJnaW4tYm90dG9tOjFweH14dWx8Y2hlY2tib3h7cGFkZGluZy1pbmxpbmUtc3RhcnQ6MH14dWx8Ki5idXR0b24tYm94LHh1bHwqLm1lbnVsaXN0LWxhYmVsLWJveCx4dWx8Ki5yYWRpby1sYWJlbC1ib3gseHVsfCouY2hlY2tib3gtbGFiZWwtYm94e2JvcmRlci1zdHlsZTpub25lfXh1bHxidXR0b246LW1vei1mb2N1c3Jpbmc+eHVsfCouYnV0dG9uLWJveCx4dWx8bWVudWxpc3Q6LW1vei1mb2N1c3Jpbmc+eHVsfCoubWVudWxpc3QtbGFiZWwtYm94LHh1bHxyYWRpb1tmb2N1c2VkPSJ0cnVlIl0+eHVsfCoucmFkaW8tbGFiZWwtYm94LGh0bWx8aW5wdXRbdHlwZT0iY2hlY2tib3giXTotbW96LWZvY3VzcmluZytodG1sfGxhYmVsOmJlZm9yZSx4dWx8Y2hlY2tib3g6LW1vei1mb2N1c3Jpbmc+eHVsfCouY2hlY2tib3gtbGFiZWwtYm94e291dGxpbmU6MXB4IGRvdHRlZH1ib2R5e2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47Ym94LXNpemluZzpib3JkZXItYm94O21pbi1oZWlnaHQ6MTAwdmg7cGFkZGluZy10b3A6MDtwYWRkaW5nLWJvdHRvbTowO3BhZGRpbmctaW5saW5lLXN0YXJ0OmNhbGMoNDhweCs0LjZlbSk7cGFkZGluZy1pbmxpbmUtZW5kOjQ4cHg7YWxpZ24taXRlbXM6Y2VudGVyO2p1c3RpZnktY29udGVudDpjZW50ZXJ9LmNvbnRhaW5lcnttaW4td2lkdGg6MTNlbTttYXgtd2lkdGg6NTJlbX0uY29udGFpbmVyLnJlc3RvcmUtY2hvc2Vue2Rpc3BsYXk6ZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47ZmxleC1ncm93OjE7bWFyZ2luOjEwdmggMH0udGl0bGV7YmFja2dyb3VuZC1wb3NpdGlvbjpsZWZ0IDA7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JhY2tncm91bmQtc2l6ZToxLjZlbTttYXJnaW4taW5saW5lLXN0YXJ0Oi0yLjNlbTtwYWRkaW5nLWlubGluZS1zdGFydDoyLjNlbTtmb250LXNpemU6Mi41ZW19LnRpdGxlOmRpcihydGwpe2JhY2tncm91bmQtcG9zaXRpb246cmlnaHQgMH0udGl0bGUtdGV4dHtib3JkZXItYm90dG9tOjFweCBzb2xpZCAjYzFjMWMxO2ZvbnQtc2l6ZTppbmhlcml0O3BhZGRpbmctYm90dG9tOi40ZW19LmJ1dHRvbi1jb250YWluZXJ7bWFyZ2luLXRvcDoxLjJlbX0uYnV0dG9uLWNvbnRhaW5lcj5idXR0b257bWluLXdpZHRoOjE1MHB4fS5idXR0b24tY29udGFpbmVyPmJ1dHRvbjpmaXJzdC1jaGlsZHttYXJnaW4taW5saW5lLXN0YXJ0OjB9Ym9keXtiYWNrZ3JvdW5kLXNpemU6NjRweCAzMnB4O2JhY2tncm91bmQtcmVwZWF0OnJlcGVhdC14O3BhZGRpbmc6NzVweCAwO21pbi13aWR0aDoxM2VtfS5idXR0b24tY29udGFpbmVye2Rpc3BsYXk6ZmxleDtmbGV4LWZsb3c6cm93IHdyYXB9LmJ1dHRvbi1zcGFjZXJ7ZmxleDoxfWJvZHl7YmFja2dyb3VuZC1pbWFnZTpsaW5lYXItZ3JhZGllbnQoLTQ1ZGVnLCNmMGQwMDAsI2YwZDAwMCAzMyUsI2ZlZGMwMCAzMyUsI2ZlZGMwMCA2NiUsI2YwZDAwMCA2NiUsI2YwZDAwMCl9YSxhOmFjdGl2ZSxhOmZvY3Vze291dGxpbmU6bm9uZTtjdXJzb3I6ZGVmYXVsdH11bHttYXJnaW46MC4xZW0gMDtwYWRkaW5nOjAgMWVtO2xpc3Qtc3R5bGU6bm9uZTt9bGk6YmVmb3Jle2NvbnRlbnQ6IiI7Ym9yZGVyLWNvbG9yOnRyYW5zcGFyZW50ICMxMTE7Ym9yZGVyLXN0eWxlOnNvbGlkO2JvcmRlci13aWR0aDowLjM1ZW0gMCAwLjM1ZW0gMC40NWVtO2Rpc3BsYXk6YmxvY2s7aGVpZ2h0OjA7d2lkdGg6MDtsZWZ0Oi0xZW07dG9wOjFlbTtwb3NpdGlvbjpyZWxhdGl2ZX0vLy0tPjwvc3R5bGU+DQo8L2hlYWQ+PGJvZHk+PGRpdiBpZD0iZXJyb3JQYWdlQ29udGFpbmVyIiBjbGFzcz0iY29udGFpbmVyIj4NCjxkaXYgY2xhc3M9InRpdGxlIj48aDEgY2xhc3M9InRpdGxlLXRleHQiPkdvb2QgbmV3cyBmb3IgeW91PC9oMT48L2Rpdj48ZGl2IGlkPSJlcnJvckxvbmdDb250ZW50Ij4NCjxkaXYgaWQ9ImVycm9yU2hvcnREZXNjIj48cCBpZD0iZXJyb3JTaG9ydERlc2NUZXh0Ij4NClRoZSBvd25lciBvZiB0aGlzIHdlYnNpdGUgZGVjaWRlZCB0byBjb25maWd1cmUgdGhlaXIgd2Vic2l0ZSBwcm9wZXJseS4NClRoZSBjb25uZWN0aW9uIGJldHdlZW4geW91IGFuZCA8Yj4lJUNGX0hPU1ROQU1FJSU8L2I+IGlzIG5vIGxvbmdlciBNSVRNZWQgYnkgdGhlIGNvcnBvcmF0ZSBjZW5zb3JzaGlwIHNlcnZpY2VzLg0KVGhlIGFkZC1vbiByZW1vdmVkIDxiPiUlQ0ZfSE9TVE5BTUUlJTwvYj4gZnJvbSB5b3VyIHdoaXRlbGlzdC4gSWYgdGhpcyBoYXBwZW5lZCBhZ2FpbiwgcGxlYXNlIGNoZWNrIHlvdXIgd2hpdGVsaXN0Lg0KPC9wPjwvZGl2PjxkaXYgaWQ9ImNlcnRFcnJvckFuZENhcHRpdmVQb3J0YWxCdXR0b25Db250YWluZXIiIGNsYXNzPSJidXR0b24tY29udGFpbmVyIj4NCjx1bD4NCjxsaT48YSBocmVmPSJodHRwczovLzAuMC4wLjAvY2ZtaXRtX2FkZG9uL2FsbG93L3JlbG9hZD9ub3ciPjxiPkdvdCBpdCE8L2I+PC9hPjwvbGk+DQo8L3VsPg0KPC9kaXY+PC9kaXY+PC9kaXY+PC9ib2R5PjwvaHRtbD4=';
+function onError(e) {
+ console.log(`CFMITM Error:${e}`);
+}
+function whitelist_reload() {
+ browser.storage.local.get().then(function (w) {
+ if (w.myset_cfwhite) {
+ var tmp_whitelist = w.myset_cfwhite;
+ tmp_whitelist = tmp_whitelist.split("\n").filter(v => v != '');
+ cf_ignore = tmp_whitelist;
+ } else {
+ cf_ignore = [];
+ }
+ if (w.myset_xautoclean == 'y') {
+ wl_autoclean = 1;
+ } else {
+ wl_autoclean = 0;
+ }
+ if (w.myset_xincapsula == 'y') {
+ stop_incapsula = 1;
+ } else {
+ stop_incapsula = 0;
+ }
+ if (w.myset_xgshield == 'y') {
+ stop_gshield = 1;
+ } else {
+ stop_gshield = 0;
+ }
+ if (w.myset_xsucuri == 'y') {
+ stop_sucuri = 1;
+ } else {
+ stop_sucuri = 0;
+ }
+ if (w.myset_xign3p == 'y') {
+ ign_thirdparty = 1;
+ } else {
+ ign_thirdparty = 0;
+ }
+ if (w.myset_xwhitemark == 'y') {
+ do_markwhitelistsite = 1;
+ } else {
+ do_markwhitelistsite = 0;
+ }
+ if (w.myset_xsimplewarn) {
+ switch (w.myset_xsimplewarn) {
+ case 1:
+ do_reaction = 1;
+ break;
+ case 2:
+ do_reaction = 2;
+ break;
+ default:
+ do_reaction = 0;
+ break;
+ }
+ } else {
+ do_reaction = 0;
+ }
+ }, onError);
+}
+function is_domain_ignored(w) {
+ if (cf_ignore.includes(w)) {
+ return true;
+ }
+ var dotSTXfound = 0;
+ for (var f = 0; f < cf_ignore.length; f++) {
+ var _fv = cf_ignore[f];
+ if (!_fv.startsWith('.')) {
+ continue;
+ }
+ if (w == _fv.replace('.', '') || w.endsWith(_fv)) {
+ dotSTXfound = 1;
+ break;
+ }
+ }
+ if (dotSTXfound == 1) {
+ return true;
+ }
+ return false;
+}
+function analyzemydata(res) {
+ if (ign_thirdparty == 1) {
+ if (res.type != 'main_frame') {
+ return;
+ }
+ }
+ var cflink = document.createElement('a');
+ cflink.setAttribute('href', res.url);
+ var cf_hostname = cflink.hostname;
+ var cf_protocol = cflink.protocol;
+ var cf_gothead = res.responseHeaders;
+ var cf_tab_id = res.tabId;
+ cflink = null;
+ if (cf_hostname.endsWith('.cloudflare.com') || cf_hostname == 'cloudflare.com') {
+ return;
+ }
+ if (stop_incapsula == 1) {
+ if (cf_hostname.endsWith('.incapsula.com') || cf_hostname == 'incapsula.com') {
+ return;
+ }
+ }
+ if (stop_gshield == 1) {
+ if (cf_hostname.endsWith('.withgoogle.com') || cf_hostname.endsWith('.google.com')) {
+ return;
+ }
+ }
+ if (stop_sucuri == 1) {
+ if (cf_hostname.endsWith('.sucuri.net') || cf_hostname == 'sucuri.net') {
+ return;
+ }
+ }
+ if (cf_protocol != 'http:' && cf_protocol != 'https:') {
+ return;
+ }
+ if (cf_dstarray[cf_tab_id] == undefined) {
+ cf_dstarray[cf_tab_id] = [];
+ cf_dstarray[cf_tab_id]['cf'] = '';
+ cf_dstarray[cf_tab_id]['ok'] = '';
+ }
+ if (cf_hostname.length >= 4) {
+ var mitm_is = 0;
+ var mitm_cdnname = 'Cloudflare';
+ for (var i = 0; i < cf_gothead.length; i++) {
+ var cfv = cf_gothead[i];
+ var cfv_vname = cfv['name'];
+ if (cfv_vname != undefined) {
+ cfv_vname = cfv_vname.toLowerCase();
+ }
+ var cfv_vvalue = cfv['value'];
+ if (cfv_vvalue != undefined) {
+ cfv_vvalue = cfv_vvalue.toLowerCase();
+ }
+ if (cfv_vname == 'cf-ray' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ break;
+ }
+ if (cfv_vname == 'server' && cfv_vvalue.includes("cloudflare")) {
+ mitm_is = 1;
+ break;
+ }
+ if (cfv_vname == 'cf-cache-status' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ break;
+ }
+ if (cfv_vname == 'set-cookie' && cfv_vvalue.includes("__cfduid")) {
+ mitm_is = 1;
+ break;
+ }
+ if (stop_incapsula == 1) {
+ if (cfv_vname.includes("incap_") && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ mitm_cdnname = 'Incapsula';
+ break;
+ }
+ if (cfv_vname == 'x-iinfo' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ mitm_cdnname = 'Incapsula';
+ break;
+ }
+ if (cfv_vname == 'x-cdn' && cfv_vvalue == 'incapsula') {
+ mitm_is = 1;
+ mitm_cdnname = 'Incapsula';
+ break;
+ }
+ if (cfv_vname == 'set-cookie' && cfv_vvalue.includes("visid_incap_")) {
+ mitm_is = 1;
+ mitm_cdnname = 'Incapsula';
+ break;
+ }
+ }
+ if (stop_gshield == 1) {
+ if (cfv_vname == 'server' && cfv_vvalue == 'shield') {
+ mitm_is = 1;
+ mitm_cdnname = 'Google Project Shield';
+ break;
+ }
+ if (cfv_vname == 'x-shield-request-id' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ mitm_cdnname = 'Google Project Shield';
+ break;
+ }
+ }
+ if (stop_sucuri == 1) {
+ if (cfv_vname == 'x-sucuri-cache' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ mitm_cdnname = 'Sucuri';
+ break;
+ }
+ if (cfv_vname == 'x-sucuri-id' && cfv_vvalue != undefined) {
+ mitm_is = 1;
+ mitm_cdnname = 'Sucuri';
+ break;
+ }
+ if (cfv_vname == 'set-cookie' && cfv_vvalue.includes("sucuri-")) {
+ mitm_is = 1;
+ mitm_cdnname = 'Sucuri';
+ break;
+ }
+ }
+ }
+ if (mitm_is == 1) {
+ if (is_domain_ignored(cf_hostname)) {
+ if (do_markwhitelistsite == 1) {
+ do_reaction = 1;
+ } else {
+ return;
+ }
+ }
+ if (cf_history.length >= 10) {
+ cf_history = [];
+ }
+ if (!cf_history.includes(cf_hostname)) {
+ cf_history.push(cf_hostname);
+ }
+ console.log('SECURITY_WARN: ' + mitm_cdnname + ' MiTM Detected: ' + res.url);
+ if (do_reaction == 0) {
+ if (res.type == 'main_frame') {
+ cf_dstarray[cf_tab_id]['cf'] = res.url.split('?', 2)[0];
+ }
+ if (cf_dstarray[cf_tab_id]['cf'].length < 12) {
+ cf_dstarray[cf_tab_id]['cf'] = '';
+ } //something wrong
+ if (cf_dstarray[cf_tab_id]['ok'].length < 12) {
+ cf_dstarray[cf_tab_id]['ok'] = 'https://searxes.danwin1210.me/';
+ } //better than google
+ cfblockscreen = atob(cf_template_blocked);
+ cfblockscreen = cfblockscreen.replace('%%CF_HOSTNAME%%', cf_hostname);
+ cfblockscreen = cfblockscreen.replace('%%CF_PRODNAME%%', mitm_cdnname);
+ cfblockscreen = cfblockscreen.replace('%%CF_WHITEPAIR%%', cf_hostname + '?' + btoa(cf_hostname));
+ cfblockscreen = cfblockscreen.replace('%%CF_URL_LASTOK%%', cf_dstarray[cf_tab_id]['ok']);
+ cfblockscreen = cfblockscreen.replace('%%CF_URL_ITSME%%', cf_dstarray[cf_tab_id]['cf']);
+ cfblockscreen = 'document.documentElement.innerHTML=atob(\'' + btoa(cfblockscreen) + '\');window.stop();';
+ var blockingCFnow = browser.tabs.executeScript(res.tabId, {
+ code: cfblockscreen
+ });
+ blockingCFnow.then(() => {
+ return {
+ cancel: true
+ };
+ }, onError);
+ }
+ if (do_reaction == 1) {
+ cfblockscreen = 'if (!document.title.startsWith(\'[!!\') && !document.title.includes(\'!!]\')){var orig_dt=document.title;var orig_lh=\'' + cf_hostname + '\';setInterval(function(){if (orig_lh==location.hostname){var link=document.querySelector("link[rel*=\'icon\']")||document.createElement(\'link\');link.type=\'image/x-icon\';link.rel=\'icon\';link.href=\'data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOzk4OKpMSkq6UE9PulRTU7pXV1e6W1tbul1dXbpbW1u6WFhYulVVVbpTU1O6UFBQukxMTLotLS2bAAAAOzY0NJ7Y3Nz/v9zh/8He4//D4eb/xuTp/8jm6//I5On/x+Po/8bj6f/E4ef/wt/l/8Hd4/+/2d//3N/f/xkZGYU2NTWd2N7e/xekxf8WueD/Frrh/xa74f8Ur9P/Iiwu/yIsLv8Ws9//F7De/xer3P8Xptr/IJS6/97g4f8ZGRmCAAAAV9HQ0O91s7z/F8Hj/xbC4/8Ww+T/FLbV/yArLf8gKy3/Frrh/xa13/8XsN7/FqbV/5G3vf+xsbHUAAAARQAAAC5oZ2eV2N7f/yWtv/8Xyub/Fsrm/xbJ5v8Zq8f/GavH/xbA4/8Wu+H/FrXf/zWguf/Z2dn/Ly8veAAAABwAAAAAAAAASsbFxd2gy87/KtTm/xnU6v8W0Oj/FEJI/xRCSP8WxeX/Fr/j/xWw0/+swsP/k5OTtgAAADoAAAAAAAAAAAAAACFFRUV+6evr/zq1vv8w5PH/J9/v/yIiIv8iIiL/Fsrm/xbE5P9Xq7n/19fX+BkZGW0AAAAKAAAAAAAAAAAAAAAAAAAAPre2tsnA3uD/LNrj/y/o8v8rKyv/Kysr/yvb7v8mvdD/xM/P/3d3d6AAAAA0AAAAAAAAAAAAAAAAAAAAAAAAABUbGxtt8PDw+FrAw/8u7PP/NDQ0/zQ0NP8w3e//gLu//8jIyOgAAABRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANJ2dnbTS4+T/KdDV/zQ8PP80QEH/NL3J/9XZ2f9WVlaLAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAABX39/f733Exv8t5vH/Ldrp/5rBw/+2trbUAAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALm1tbZXf5ub/LLnD/0S7xP/d3d3/MTExeAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKx8fH3bHP0f+8zM3/l5eXtgAAADoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITIyMna+vr7Trq6uyRkZGW0AAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAARAAAAD4AAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAIABAAAAAAAAAAAAAIABAACAAwAAwAMAAOAHAADgBwAA8A8AAPAPAAD4HwAA+D8AAPw/AAD+fwAA//8AAA==\';document.getElementsByTagName(\'head\')[0].appendChild(link);document.body.style="border: 4px dashed #f0d000";if (!document.title.startsWith(\'[!!\') && !document.title.includes(\'!!]\')){document.title=\'[!!' + mitm_cdnname + '!!] \'+orig_dt;}}},2500);}';
+ var warningCFnow = browser.tabs.executeScript(res.tabId, {
+ code: cfblockscreen
+ });
+ warningCFnow.then(function () {}, onError);
+ }
+ if (do_reaction == 2) {
+ if (res.type == 'image') {
+ return {
+ redirectUrl: cf_blocked_img
+ };
+ } else {
+ if (res.type == 'main_frame') {
+ return {
+ redirectUrl: 'https://0.0.0.0/'
+ };
+ } else {
+ return {
+ cancel: true
+ };
+ }
+ }
+ }
+ } else {
+ if (res.type == 'main_frame') {
+ if (do_reaction == 0) {
+ cf_dstarray[cf_tab_id]['ok'] = res.url;
+ } //used by warning page
+ if (cf_ignore.includes(cf_hostname) && cf_gothead.length > 3) {
+ if (wl_autoclean == 1) {
+ cfblockscreen = atob(cf_template_wlnotify);
+ cfblockscreen = cfblockscreen.replace('%%CF_HOSTNAME%%', cf_hostname);
+ cfblockscreen = cfblockscreen.replace('%%CF_HOSTNAME%%', cf_hostname);
+ cfblockscreen = 'document.documentElement.innerHTML=atob(\'' + btoa(cfblockscreen) + '\');window.stop();';
+ var nomoreCFnow = browser.tabs.executeScript(res.tabId, {
+ code: cfblockscreen
+ });
+ nomoreCFnow.then(() => {
+ console.log('SECURITY_INFO: Removing from whitelist: ' + cf_hostname);
+ var _wi = cf_ignore.indexOf(cf_hostname);
+ if (_wi > -1) {
+ cf_ignore.splice(_wi, 1);
+ }
+ browser.storage.local.set({
+ myset_cfwhite: cf_ignore.join("\n")
+ });
+ return {
+ cancel: true
+ };
+ }, onError);
+ }
+ }
+ }
+ }
+ }
+ return;
+}
+function gotwhitelistrequest(r) {
+ var v_whitelist = r.url.replace('https://0.0.0.0/cfmitm_addon/allow/', '', ).split('?', 2);
+ if (v_whitelist.length == 2) {
+ if (v_whitelist[0] == 'viewexceptions' && v_whitelist[1] == 'now') {
+ browser.runtime.openOptionsPage().then(function () {}, onError);
+ }
+ if (v_whitelist[0] == 'reload' && v_whitelist[1] == 'now') {
+ browser.tabs.reload({
+ bypassCache: true
+ });
+ }
+ if (/^([0-9a-z.-]{4,200})$/.test(v_whitelist[0]) && v_whitelist[1] == btoa(v_whitelist[0])) { //cfinfection?originhash
+ if (cf_history.includes(v_whitelist[0])) {
+ if (!cf_ignore.includes(v_whitelist[0])) { // found in history and rule not found
+ console.log('CFMITM: Adding to whitelist: ' + v_whitelist[0]);
+ cf_ignore.push(v_whitelist[0]);
+ browser.storage.local.set({
+ myset_cfwhite: cf_ignore.join("\n")
+ }).then(function () {
+ browser.tabs.executeScript({
+ code: 'location.reload();'
+ });
+ }, onError);
+ }
+ }
+ }
+ }
+ return {
+ cancel: true
+ };
+}
+if (cfaddon_isdone == 0) {
+ cfaddon_isdone = 1;
+ whitelist_reload();
+}
+browser.webRequest.onHeadersReceived.addListener(analyzemydata, {
+ urls: ["http://*/*", "https://*/*"]
+}, ["blocking", "responseHeaders"]);
+browser.webRequest.onBeforeRequest.addListener(gotwhitelistrequest, {
+ urls: ["https://0.0.0.0/cfmitm_addon/*"]
+}, ["blocking"]);
+browser.runtime.onMessage.addListener(function (r, s, sr) {
+ if (r.relnow != undefined) {
+ whitelist_reload();
+ sr({
+ response: 'ok'
+ });
+ };
+ return true;
+});
+browser.browserAction.onClicked.addListener(function (t) {
+ browser.runtime.openOptionsPage().then(function () {}, onError);
+});
+browser.tabs.onRemoved.addListener(function (t, r) {
+ cf_dstarray[t] = [];
+ delete cf_dstarray[t];
+}); \ No newline at end of file
diff --git a/tool/block_cloudflare_mitm_fx/src/style.css b/tool/block_cloudflare_mitm_fx/src/style.css
new file mode 100644
index 00000000..539aebc5
--- /dev/null
+++ b/tool/block_cloudflare_mitm_fx/src/style.css
@@ -0,0 +1,12 @@
+body {
+ font: 13px Verdana
+}
+label {
+ -webkit-user-select: none;
+ -moz-user-select: none
+}
+textarea {
+ white-space: pre;
+ overflow-wrap: normal;
+ overflow-x: scroll
+} \ No newline at end of file
diff --git a/tool/cf_email_decoder/.gitignore b/tool/cf_email_decoder/.gitignore
new file mode 100644
index 00000000..c0df46a4
--- /dev/null
+++ b/tool/cf_email_decoder/.gitignore
@@ -0,0 +1,51 @@
+# ---> Emacs
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
diff --git a/tool/cf_email_decoder/.gitkeep b/tool/cf_email_decoder/.gitkeep
new file mode 100644
index 00000000..c0df46a4
--- /dev/null
+++ b/tool/cf_email_decoder/.gitkeep
@@ -0,0 +1,51 @@
+# ---> Emacs
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
diff --git a/tool/cf_email_decoder/LICENSE b/tool/cf_email_decoder/LICENSE
new file mode 100644
index 00000000..a343ccd4
--- /dev/null
+++ b/tool/cf_email_decoder/LICENSE
@@ -0,0 +1,119 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
+NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
+AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
+ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
+OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
+LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
+OR WORKS PROVIDED HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer exclusive
+Copyright and Related Rights (defined below) upon the creator and subsequent
+owner(s) (each and all, an "owner") of an original work of authorship and/or
+a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later claims
+of infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute
+to the Commons to promote the ideal of a free culture and the further production
+of creative, cultural and scientific works, or to gain reputation or greater
+distribution for their Work in part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with
+a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or
+her Copyright and Related Rights in the Work and the meaning and intended
+legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be protected
+by copyright and related or neighboring rights ("Copyright and Related Rights").
+Copyright and Related Rights include, but are not limited to, the following:
+
+i. the right to reproduce, adapt, distribute, perform, display, communicate,
+and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+iii. publicity and privacy rights pertaining to a person's image or likeness
+depicted in a Work;
+
+iv. rights protecting against unfair competition in regards to a Work, subject
+to the limitations in paragraph 4(a), below;
+
+v. rights protecting the extraction, dissemination, use and reuse of data
+in a Work;
+
+vi. database rights (such as those arising under Directive 96/9/EC of the
+European Parliament and of the Council of 11 March 1996 on the legal protection
+of databases, and under any national implementation thereof, including any
+amended or successor version of such directive); and
+
+vii. other similar, equivalent or corresponding rights throughout the world
+based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time extensions),
+(iii) in any current or future medium and for any number of copies, and (iv)
+for any purpose whatsoever, including without limitation commercial, advertising
+or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
+benefit of each member of the public at large and to the detriment of Affirmer's
+heirs and successors, fully intending that such Waiver shall not be subject
+to revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account Affirmer's
+express Statement of Purpose. In addition, to the extent the Waiver is so
+judged Affirmer hereby grants to each affected person a royalty-free, non
+transferable, non sublicensable, non exclusive, irrevocable and unconditional
+license to exercise Affirmer's Copyright and Related Rights in the Work (i)
+in all territories worldwide, (ii) for the maximum duration provided by applicable
+law or treaty (including future time extensions), (iii) in any current or
+future medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional purposes
+(the "License"). The License shall be deemed effective as of the date CC0
+was applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder of
+the License, and in such case Affirmer hereby affirms that he or she will
+not (i) exercise any of his or her remaining Copyright and Related Rights
+in the Work or (ii) assert any associated claims and causes of action with
+respect to the Work, in either case contrary to Affirmer's express Statement
+of Purpose.
+
+ 4. Limitations and Disclaimers.
+
+a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
+licensed or otherwise affected by this document.
+
+b. Affirmer offers the Work as-is and makes no representations or warranties
+of any kind concerning the Work, express, implied, statutory or otherwise,
+including without limitation warranties of title, merchantability, fitness
+for a particular purpose, non infringement, or the absence of latent or other
+defects, accuracy, or the present or absence of errors, whether or not discoverable,
+all to the greatest extent permissible under applicable law.
+
+c. Affirmer disclaims responsibility for clearing rights of other persons
+that may apply to the Work or any use thereof, including without limitation
+any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
+responsibility for obtaining any necessary consents, permissions or other
+rights required for any use of the Work.
+
+d. Affirmer understands and acknowledges that Creative Commons is not a party
+to this document and has no duty or obligation with respect to this CC0 or
+use of the Work.
diff --git a/tool/cf_email_decoder/README.md b/tool/cf_email_decoder/README.md
new file mode 100644
index 00000000..403da032
--- /dev/null
+++ b/tool/cf_email_decoder/README.md
@@ -0,0 +1,9 @@
+# cf\_email\_decoder
+cf\_email\_decoder is a userscript that decodes email addresses that are protected by Cloudflare
+
+## Comparisons
+
+| Encoded | Decoded |
+|-----------------------------------------|-----------------------------------------|
+|![](readme/compare_encoded_namedlink.png)|![](readme/compare_decoded_namedlink.png)|
+|![](readme/compare_encoded_address.png) |![](readme/compare_decoded_address.png) |
diff --git a/tool/cf_email_decoder/cfemail.user.js b/tool/cf_email_decoder/cfemail.user.js
new file mode 100644
index 00000000..673f898f
--- /dev/null
+++ b/tool/cf_email_decoder/cfemail.user.js
@@ -0,0 +1,64 @@
+// ==UserScript==
+// @name Decode Cloudflare-encoded email addresses
+// @namespace https://codeberg.org/smege1001/cf_email_decoder
+// @match *://*/*
+// @grant none
+// @version 1.2.2
+// @author smege1001
+// ==/UserScript==
+
+/**
+* @license CC0-1.0
+**/
+
+const emailprotectionURLHashRegex = /\/cdn-cgi\/l\/email-protection#([aA0-fF9]*)/;
+const emailprotectionURLNoHashRegex = /\/cdn-cgi\/l\/email-protection/; //hash is stored on data-cfemail
+
+function decodeEmail(hash) { //cloudflare email address decoder
+ var hashArray = []; //split the hash into bytes
+ for (var hAIndex = 0; hAIndex < hash.length; hAIndex += 2) {
+ hashArray.push(parseInt(hash.substring(hAIndex, hAIndex + 2), 16));
+ }
+
+ var decoded = "";
+ var key = hashArray[0]; //get the decode key
+
+ for (var index = 1; index < hashArray.length; index++) {
+ decoded += String.fromCharCode(hashArray[index] ^ key);
+ }
+ return decoded;
+}
+
+var links = document.querySelectorAll("a"); //get all the links
+
+for (var linksIndex = 0; linksIndex < links.length; linksIndex++) {
+ var link = links[linksIndex];
+ if (emailprotectionURLHashRegex.test(link.href)) {
+ var hash = link.href.match(emailprotectionURLHashRegex)[1];
+ var decodedEmail = decodeEmail(hash);
+
+ link.href = "mailto:" + decodedEmail; //replace the stupid email protection with just a mailto link
+
+ if (link.getElementsByClassName("__cf_email__").length > 0) {
+ var linkChild = link.getElementsByClassName("__cf_email__")[0];
+ linkChild.innerText = decodedEmail;
+
+ linkChild.removeAttribute("data-cfemail");
+ linkChild.classList.remove("__cf_email__");
+
+ if (linkChild.getAttribute("class") == "") linkChild.removeAttribute("class");
+ }
+ } else if (emailprotectionURLNoHashRegex.test(link.href) && link.hasAttribute("data-cfemail")) {
+ var hash = link.getAttribute("data-cfemail");
+ var decodedEmail = decodeEmail(hash);
+
+ link.href = "mailto:" + decodedEmail;
+ link.innerText = decodedEmail; //the inner text is just [email protected]
+
+ //remove the useless attributes
+ link.removeAttribute("data-cfemail");
+ link.classList.remove("__cf_email__");
+
+ if (link.getAttribute("class") == "") link.removeAttribute("class");
+ }
+}
diff --git a/tool/cf_email_decoder/readme/compare_decoded_address.png b/tool/cf_email_decoder/readme/compare_decoded_address.png
new file mode 100644
index 00000000..f570e429
--- /dev/null
+++ b/tool/cf_email_decoder/readme/compare_decoded_address.png
Binary files differ
diff --git a/tool/cf_email_decoder/readme/compare_decoded_namedlink.png b/tool/cf_email_decoder/readme/compare_decoded_namedlink.png
new file mode 100644
index 00000000..e20ca9d9
--- /dev/null
+++ b/tool/cf_email_decoder/readme/compare_decoded_namedlink.png
Binary files differ
diff --git a/tool/cf_email_decoder/readme/compare_encoded_address.png b/tool/cf_email_decoder/readme/compare_encoded_address.png
new file mode 100644
index 00000000..a59b988f
--- /dev/null
+++ b/tool/cf_email_decoder/readme/compare_encoded_address.png
Binary files differ
diff --git a/tool/cf_email_decoder/readme/compare_encoded_namedlink.png b/tool/cf_email_decoder/readme/compare_encoded_namedlink.png
new file mode 100644
index 00000000..8e0b3cca
--- /dev/null
+++ b/tool/cf_email_decoder/readme/compare_encoded_namedlink.png
Binary files differ
diff --git a/tool/cfemail.user.js b/tool/cfemail.user.js
new file mode 100644
index 00000000..673f898f
--- /dev/null
+++ b/tool/cfemail.user.js
@@ -0,0 +1,64 @@
+// ==UserScript==
+// @name Decode Cloudflare-encoded email addresses
+// @namespace https://codeberg.org/smege1001/cf_email_decoder
+// @match *://*/*
+// @grant none
+// @version 1.2.2
+// @author smege1001
+// ==/UserScript==
+
+/**
+* @license CC0-1.0
+**/
+
+const emailprotectionURLHashRegex = /\/cdn-cgi\/l\/email-protection#([aA0-fF9]*)/;
+const emailprotectionURLNoHashRegex = /\/cdn-cgi\/l\/email-protection/; //hash is stored on data-cfemail
+
+function decodeEmail(hash) { //cloudflare email address decoder
+ var hashArray = []; //split the hash into bytes
+ for (var hAIndex = 0; hAIndex < hash.length; hAIndex += 2) {
+ hashArray.push(parseInt(hash.substring(hAIndex, hAIndex + 2), 16));
+ }
+
+ var decoded = "";
+ var key = hashArray[0]; //get the decode key
+
+ for (var index = 1; index < hashArray.length; index++) {
+ decoded += String.fromCharCode(hashArray[index] ^ key);
+ }
+ return decoded;
+}
+
+var links = document.querySelectorAll("a"); //get all the links
+
+for (var linksIndex = 0; linksIndex < links.length; linksIndex++) {
+ var link = links[linksIndex];
+ if (emailprotectionURLHashRegex.test(link.href)) {
+ var hash = link.href.match(emailprotectionURLHashRegex)[1];
+ var decodedEmail = decodeEmail(hash);
+
+ link.href = "mailto:" + decodedEmail; //replace the stupid email protection with just a mailto link
+
+ if (link.getElementsByClassName("__cf_email__").length > 0) {
+ var linkChild = link.getElementsByClassName("__cf_email__")[0];
+ linkChild.innerText = decodedEmail;
+
+ linkChild.removeAttribute("data-cfemail");
+ linkChild.classList.remove("__cf_email__");
+
+ if (linkChild.getAttribute("class") == "") linkChild.removeAttribute("class");
+ }
+ } else if (emailprotectionURLNoHashRegex.test(link.href) && link.hasAttribute("data-cfemail")) {
+ var hash = link.getAttribute("data-cfemail");
+ var decodedEmail = decodeEmail(hash);
+
+ link.href = "mailto:" + decodedEmail;
+ link.innerText = decodedEmail; //the inner text is just [email protected]
+
+ //remove the useless attributes
+ link.removeAttribute("data-cfemail");
+ link.classList.remove("__cf_email__");
+
+ if (link.getAttribute("class") == "") link.removeAttribute("class");
+ }
+}
diff --git a/tool/cloudflare.onemorestep.template.html b/tool/cloudflare.onemorestep.template.html
new file mode 100644
index 00000000..d926a0b9
--- /dev/null
+++ b/tool/cloudflare.onemorestep.template.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html class="no-js">
+ <head>
+ <title>Attention Required! | Cloudflare</title>
+ <meta charset="UTF-8" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="robots" content="noindex, nofollow" />
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />
+ <style type="text/css">body{margin:0;padding:0}@font-face{font-family:'Open Sans';font-style:normal;font-weight:300;src:local("Open Sans Light"),local("OpenSans-Light"),}@font-face{font-family:'Open Sans';font-style:normal;font-weight:400;src:local("Open Sans"),local("OpenSans"),}@font-face{font-family:'Open Sans';font-style:normal;font-weight:600;src:local("Open Sans Semibold"),local("OpenSans-Semibold"),}@font-face{font-family:'Open Sans';font-style:normal;font-weight:700;src:local("Open Sans Bold"),local("OpenSans-Bold"),}@font-face{font-family:'Open Sans';font-style:italic;font-weight:300;src:local("Open Sans Light Italic"),local("OpenSansLight-Italic"),}@font-face{font-family:'Open Sans';font-style:italic;font-weight:400;src:local("Open Sans Italic"),local("OpenSans-Italic"),}#cf-wrapper a,#cf-wrapper abbr,#cf-wrapper article,#cf-wrapper aside,#cf-wrapper b,#cf-wrapper big,#cf-wrapper blockquote,#cf-wrapper body,#cf-wrapper canvas,#cf-wrapper caption,#cf-wrapper center,#cf-wrapper cite,#cf-wrapper code,#cf-wrapper dd,#cf-wrapper del,#cf-wrapper details,#cf-wrapper dfn,#cf-wrapper div,#cf-wrapper dl,#cf-wrapper dt,#cf-wrapper em,#cf-wrapper embed,#cf-wrapper fieldset,#cf-wrapper figcaption,#cf-wrapper figure,#cf-wrapper footer,#cf-wrapper form,#cf-wrapper h1,#cf-wrapper h2,#cf-wrapper h3,#cf-wrapper h4,#cf-wrapper h5,#cf-wrapper h6,#cf-wrapper header,#cf-wrapper hgroup,#cf-wrapper html,#cf-wrapper i,#cf-wrapper iframe,#cf-wrapper img,#cf-wrapper label,#cf-wrapper legend,#cf-wrapper li,#cf-wrapper mark,#cf-wrapper menu,#cf-wrapper nav,#cf-wrapper object,#cf-wrapper ol,#cf-wrapper output,#cf-wrapper p,#cf-wrapper pre,#cf-wrapper s,#cf-wrapper samp,#cf-wrapper section,#cf-wrapper small,#cf-wrapper span,#cf-wrapper strike,#cf-wrapper strong,#cf-wrapper sub,#cf-wrapper summary,#cf-wrapper sup,#cf-wrapper table,#cf-wrapper tbody,#cf-wrapper td,#cf-wrapper tfoot,#cf-wrapper th,#cf-wrapper thead,#cf-wrapper tr,#cf-wrapper tt,#cf-wrapper u,#cf-wrapper ul{margin:0;padding:0;border:0;font:inherit;font-size:100%;text-decoration:none;vertical-align:baseline}#cf-wrapper a img{border:0}#cf-wrapper article,#cf-wrapper aside,#cf-wrapper details,#cf-wrapper figcaption,#cf-wrapper figure,#cf-wrapper footer,#cf-wrapper header,#cf-wrapper hgroup,#cf-wrapper menu,#cf-wrapper nav,#cf-wrapper section,#cf-wrapper summary{display:block}#cf-wrapper .cf-columns:after,#cf-wrapper .cf-columns:before,#cf-wrapper .cf-section:after,#cf-wrapper .cf-section:before,#cf-wrapper .cf-wrapper:after,#cf-wrapper .cf-wrapper:before,#cf-wrapper .clearfix:after,#cf-wrapper .clearfix:before,#cf-wrapper section:after,#cf-wrapper section:before{content:" ";display:table}#cf-wrapper .cf-columns:after,#cf-wrapper .cf-section:after,#cf-wrapper .cf-wrapper:after,#cf-wrapper .clearfix:after,#cf-wrapper section:after{clear:both}#cf-wrapper{display:block;margin:0;padding:0;position:relative;text-align:left;width:100%;z-index:999999999;color:#404040!important;font-family:"Open Sans",Helvetica,Arial,sans-serif!important;font-size:15px!important;line-height:1.5!important;text-decoration:none!important;letter-spacing:normal;-webkit-tap-highlight-color:rgba(246,139,31,.3);-webkit-font-smoothing:antialiased}#cf-wrapper .cf-section,#cf-wrapper section{background:0;display:block;margin-bottom:2em;margin-top:2em}#cf-wrapper .cf-wrapper{margin-left:auto;margin-right:auto;width:90%}#cf-wrapper .cf-columns{display:block;list-style:none;padding:0;width:100%}#cf-wrapper .cf-columns img,#cf-wrapper .cf-columns input,#cf-wrapper .cf-columns object,#cf-wrapper .cf-columns select,#cf-wrapper .cf-columns textarea{max-width:100%}#cf-wrapper .cf-columns>.cf-column{float:left;padding-bottom:45px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@media screen and (min-width:49.2em){#cf-wrapper .cf-columns.cols-2>.cf-column:nth-child(n+3),#cf-wrapper .cf-columns.cols-3>.cf-column:nth-child(n+4),#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(n+3),#cf-wrapper .cf-columns.four>.cf-column:nth-child(n+3),#cf-wrapper .cf-columns.three>.cf-column:nth-child(n+4),#cf-wrapper .cf-columns.two>.cf-column:nth-child(n+3){padding-top:67.5px}#cf-wrapper .cf-columns>.cf-column{padding-bottom:0}#cf-wrapper .cf-columns.cols-2>.cf-column,#cf-wrapper .cf-columns.cols-4>.cf-column,#cf-wrapper .cf-columns.four>.cf-column,#cf-wrapper .cf-columns.two>.cf-column{padding-left:0;padding-right:22.5px;width:50%}#cf-wrapper .cf-columns.cols-2>.cf-column:nth-child(even),#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(even),#cf-wrapper .cf-columns.four>.cf-column:nth-child(even),#cf-wrapper .cf-columns.two>.cf-column:nth-child(even){padding-left:22.5px;padding-right:0}#cf-wrapper .cf-columns.cols-2>.cf-column:nth-child(odd),#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(odd),#cf-wrapper .cf-columns.four>.cf-column:nth-child(odd),#cf-wrapper .cf-columns.two>.cf-column:nth-child(odd){clear:left}#cf-wrapper .cf-columns.cols-3>.cf-column,#cf-wrapper .cf-columns.three>.cf-column{padding-left:30px;width:33.3333333333333%}#cf-wrapper .cf-columns.cols-3>.cf-column:first-child,#cf-wrapper .cf-columns.cols-3>.cf-column:nth-child(3n+1),#cf-wrapper .cf-columns.three>.cf-column:first-child,#cf-wrapper .cf-columns.three>.cf-column:nth-child(3n+1){clear:left;padding-left:0;padding-right:30px}#cf-wrapper .cf-columns.cols-3>.cf-column:nth-child(3n+2),#cf-wrapper .cf-columns.three>.cf-column:nth-child(3n+2){padding-left:15px;padding-right:15px}#cf-wrapper .cf-columns.cols-3>.cf-column:nth-child(-n+3),#cf-wrapper .cf-columns.three>.cf-column:nth-child(-n+3){padding-top:0}}@media screen and (min-width:66em){#cf-wrapper .cf-columns>.cf-column{padding-bottom:0}#cf-wrapper .cf-columns.cols-4>.cf-column,#cf-wrapper .cf-columns.four>.cf-column{padding-left:33.75px;width:25%}#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(odd),#cf-wrapper .cf-columns.four>.cf-column:nth-child(odd){clear:none}#cf-wrapper .cf-columns.cols-4>.cf-column:first-child,#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(4n+1),#cf-wrapper .cf-columns.four>.cf-column:first-child,#cf-wrapper .cf-columns.four>.cf-column:nth-child(4n+1){clear:left;padding-left:0;padding-right:33.75px}#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(4n+2),#cf-wrapper .cf-columns.four>.cf-column:nth-child(4n+2){padding-left:11.25px;padding-right:22.5px}#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(4n+3),#cf-wrapper .cf-columns.four>.cf-column:nth-child(4n+3){padding-left:22.5px;padding-right:11.25px}#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(n+5),#cf-wrapper .cf-columns.four>.cf-column:nth-child(n+5){padding-top:67.5px}#cf-wrapper .cf-columns.cols-4>.cf-column:nth-child(-n+4),#cf-wrapper .cf-columns.four>.cf-column:nth-child(-n+4){padding-top:0}}#cf-wrapper a{background:0;border:0;color:#2f7bbf;outline:0;text-decoration:none;-webkit-transition:all .15s ease;-moz-transition:all .15s ease;-o-transition:all .15s ease;transition:all .15s ease}#cf-wrapper a:hover{background:0;border:0;color:#f68b1f}#cf-wrapper a:focus{background:0;border:0;color:#62a1d8;outline:0}#cf-wrapper a:active{background:0;border:0;color:#c16508;outline:0}#cf-wrapper h1,#cf-wrapper h2,#cf-wrapper h3,#cf-wrapper h4,#cf-wrapper h5,#cf-wrapper h6,#cf-wrapper p{color:#404040;margin:0;padding:0}#cf-wrapper h1,#cf-wrapper h2,#cf-wrapper h3{font-weight:400}#cf-wrapper h4,#cf-wrapper h5,#cf-wrapper h6,#cf-wrapper strong{font-weight:600}#cf-wrapper h1{font-size:36px;line-height:1.2}#cf-wrapper h2{font-size:30px;line-height:1.3}#cf-wrapper h3{font-size:25px;line-height:1.3}#cf-wrapper h4{font-size:20px;line-height:1.3}#cf-wrapper h5{font-size:15px}#cf-wrapper h6{font-size:13px}#cf-wrapper ol,#cf-wrapper ul{list-style:none;margin-left:3em}#cf-wrapper ul{list-style-type:disc}#cf-wrapper ol{list-style-type:decimal}#cf-wrapper em{font-style:italic}#cf-wrapper .cf-subheadline{color:#999;font-weight:300}#cf-wrapper .cf-text-error{color:#bd2426}#cf-wrapper .cf-text-success{color:#9bca3e}#cf-wrapper ol+h2,#cf-wrapper ol+h3,#cf-wrapper ol+h4,#cf-wrapper ol+h5,#cf-wrapper ol+h6,#cf-wrapper ol+p,#cf-wrapper p+dl,#cf-wrapper p+ol,#cf-wrapper p+p,#cf-wrapper p+table,#cf-wrapper p+ul,#cf-wrapper ul+h2,#cf-wrapper ul+h3,#cf-wrapper ul+h4,#cf-wrapper ul+h5,#cf-wrapper ul+h6,#cf-wrapper ul+p{margin-top:1.5em}#cf-wrapper h1+p,#cf-wrapper p+h1,#cf-wrapper p+h2,#cf-wrapper p+h3,#cf-wrapper p+h4,#cf-wrapper p+h5,#cf-wrapper p+h6{margin-top:1.25em}#cf-wrapper h1+h2,#cf-wrapper h1+h3,#cf-wrapper h2+h3,#cf-wrapper h3+h4,#cf-wrapper h4+h5{margin-top:.25em}#cf-wrapper h2+p{margin-top:1em}#cf-wrapper h1+h4,#cf-wrapper h1+h5,#cf-wrapper h1+h6,#cf-wrapper h2+h4,#cf-wrapper h2+h5,#cf-wrapper h2+h6,#cf-wrapper h3+h5,#cf-wrapper h3+h6,#cf-wrapper h3+p,#cf-wrapper h4+p,#cf-wrapper h5+ol,#cf-wrapper h5+p,#cf-wrapper h5+ul{margin-top:.5em}#cf-wrapper .cf-btn{background-color:transparent;border:1px solid #999;color:#404040;font-size:14px;font-weight:400;line-height:1.2;margin:0;padding:.6em 1.33333em .53333em;-webkit-user-select:none;-moz-user-select:none;user-select:none;display:-moz-inline-stack;display:inline-block;vertical-align:middle;zoom:1;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:all .2s ease;-moz-transition:all .2s ease;-o-transition:all .2s ease;transition:all .2s ease}#cf-wrapper .cf-btn:hover{background-color:#bfbfbf;border:1px solid #737373;color:#fff}#cf-wrapper .cf-btn:focus{color:inherit;outline:0;-webkit-box-shadow:inset 0 0 4px rgba(0,0,0,.3);-moz-box-shadow:inset 0 0 4px rgba(0,0,0,.3);box-shadow:inset 0 0 4px rgba(0,0,0,.3)}#cf-wrapper .cf-btn.active,#cf-wrapper .cf-btn:active{background-color:#bfbfbf;border:1px solid #404040;color:#272727}#cf-wrapper .cf-btn::-moz-focus-inner{padding:0;border:0}#cf-wrapper .cf-btn .cf-caret{border-top-color:currentColor;margin-left:.25em;margin-top:.18333em}#cf-wrapper .cf-btn-primary{background-color:#2f7bbf;border:1px solid transparent;color:#fff}#cf-wrapper .cf-btn-primary:hover{background-color:#62a1d8;border:1px solid #2f7bbf;color:#fff}#cf-wrapper .cf-btn-primary.active,#cf-wrapper .cf-btn-primary:active,#cf-wrapper .cf-btn-primary:focus{background-color:#62a1d8;border:1px solid #163959;color:#fff}#cf-wrapper .cf-btn-danger,#cf-wrapper .cf-btn-error,#cf-wrapper .cf-btn-important{background-color:#bd2426;border-color:transparent;color:#fff}#cf-wrapper .cf-btn-danger:hover,#cf-wrapper .cf-btn-error:hover,#cf-wrapper .cf-btn-important:hover{background-color:#de5052;border-color:#bd2426;color:#fff}#cf-wrapper .cf-btn-danger.active,#cf-wrapper .cf-btn-danger:active,#cf-wrapper .cf-btn-danger:focus,#cf-wrapper .cf-btn-error.active,#cf-wrapper .cf-btn-error:active,#cf-wrapper .cf-btn-error:focus,#cf-wrapper .cf-btn-important.active,#cf-wrapper .cf-btn-important:active,#cf-wrapper .cf-btn-important:focus{background-color:#de5052;border-color:#521010;color:#fff}#cf-wrapper .cf-btn-accept,#cf-wrapper .cf-btn-success{background-color:#9bca3e;border:1px solid transparent;color:#fff}#cf-wrapper .cf-btn-accept:hover,#cf-wrapper .cf-btn-success:hover{background-color:#bada7a;border:1px solid #9bca3e;color:#fff}#cf-wrapper .active.cf-btn-accept,#cf-wrapper .cf-btn-accept:active,#cf-wrapper .cf-btn-accept:focus,#cf-wrapper .cf-btn-success.active,#cf-wrapper .cf-btn-success:active,#cf-wrapper .cf-btn-success:focus{background-color:#bada7a;border:1px solid #516b1d;color:#fff}#cf-wrapper .cf-btn-accept{color:transparent;font-size:0;height:36.38px;overflow:hidden;position:relative;text-indent:0;width:36.38px;white-space:nowrap}#cf-wrapper .cf-btn-accept span{background:0 -807px;display:block;font-size:1.75em;height:20px;left:50%;line-height:0;margin-left:-10px;margin-top:-10px;position:absolute;speak:none;top:50%;width:20px}#cf-wrapper input,#cf-wrapper select,#cf-wrapper textarea{background:#fff!important;border:1px solid #999!important;color:#404040!important;font-size:.86667em!important;line-height:1.24!important;margin:0 0 1em!important;max-width:100%!important;outline:0!important;padding:.45em .75em!important;vertical-align:middle!important;display:-moz-inline-stack;display:inline-block;zoom:1;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:all .2s ease;-moz-transition:all .2s ease;-o-transition:all .2s ease;transition:all .2s ease;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px}#cf-wrapper input:hover,#cf-wrapper select:hover,#cf-wrapper textarea:hover{border-color:gray}#cf-wrapper input:focus,#cf-wrapper select:focus,#cf-wrapper textarea:focus{border-color:#2f7bbf;outline:0;-webkit-box-shadow:0 0 8px rgba(47,123,191,.5);-moz-box-shadow:0 0 8px rgba(47,123,191,.5);box-shadow:0 0 8px rgba(47,123,191,.5)}#cf-wrapper fieldset{width:100%}#cf-wrapper label{display:block;font-size:13px;margin-bottom:.38333em}#cf-wrapper .cf-form-stacked .select2-container,#cf-wrapper .cf-form-stacked input,#cf-wrapper .cf-form-stacked select,#cf-wrapper .cf-form-stacked textarea{display:block;width:100%}#cf-wrapper .cf-form-stacked input[type=checkbox],#cf-wrapper .cf-form-stacked input[type=button],#cf-wrapper .cf-form-stacked input[type=submit]{display:-moz-inline-stack;display:inline-block;vertical-align:middle;zoom:1;width:auto}#cf-wrapper .cf-form-actions{text-align:right}#cf-wrapper .cf-alert{background-color:#f9b169;border:1px solid #904b06;color:#404040;font-size:13px;padding:7.5px 15px;position:relative;vertical-align:middle;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px}#cf-wrapper .cf-alert:empty{display:none}#cf-wrapper .cf-alert .cf-close{border:1px solid transparent;color:inherit;font-size:18.75px;line-height:1;padding:0;position:relative;right:-18.75px;top:0}#cf-wrapper .cf-alert .cf-close:hover{background-color:transparent;border-color:currentColor;color:inherit}#cf-wrapper .cf-alert-danger,#cf-wrapper .cf-alert-error{background-color:#de5052;border-color:#521010;color:#fff}#cf-wrapper .cf-alert-success{background-color:#bada7a;border-color:#516b1d;color:#516b1d}#cf-wrapper .cf-alert-warning{background-color:#f9b169;border-color:#904b06;color:#404040}#cf-wrapper .cf-alert-info{background-color:#62a1d8;border-color:#163959;color:#163959}#cf-wrapper .cf-alert-nonessential{background-color:#ebebeb;border-color:#999;color:#404040}#cf-wrapper .cf-icon-exclamation-sign{background:center no-repeat;height:54px;width:54px;display:-moz-inline-stack;display:inline-block;vertical-align:middle;zoom:1}#cf-wrapper h1 .cf-icon-exclamation-sign{margin-top:-10px}#cf-wrapper #cf-error-banner{background-color:#fff;border-bottom:3px solid #f68b1f;padding:15px 15px 20px;position:relative;z-index:999999999;-webkit-box-shadow:0 2px 8px rgba(0,0,0,.2);-moz-box-shadow:0 2px 8px rgba(0,0,0,.2);box-shadow:0 2px 8px rgba(0,0,0,.2)}#cf-wrapper #cf-error-banner h4,#cf-wrapper #cf-error-banner p{display:-moz-inline-stack;display:inline-block;vertical-align:bottom;zoom:1}#cf-wrapper #cf-error-banner h4{color:#2f7bbf;font-weight:400;font-size:20px;line-height:1;vertical-align:baseline}#cf-wrapper #cf-error-banner .cf-error-actions{margin-bottom:10px;text-align:center;width:100%}#cf-wrapper #cf-error-banner .cf-error-actions a{display:-moz-inline-stack;display:inline-block;vertical-align:middle;zoom:1}#cf-wrapper #cf-error-banner .cf-error-actions a+a{margin-left:10px}#cf-wrapper #cf-error-banner .cf-error-actions .cf-btn-accept,#cf-wrapper #cf-error-banner .cf-error-actions .cf-btn-success{color:#fff}#cf-wrapper #cf-error-banner .error-header-desc{text-align:left}#cf-wrapper #cf-error-banner .cf-close{color:#999;cursor:pointer;display:inline-block;font-size:34.5px;float:none;font-weight:700;height:22.5px;line-height:.6;overflow:hidden;position:absolute;right:20px;top:25px;text-indent:200%;width:22.5px}#cf-wrapper #cf-error-banner .cf-close:hover{color:gray}#cf-wrapper #cf-error-banner .cf-close:before{content:'\00D7';left:0;height:100%;position:absolute;text-align:center;text-indent:0;top:0;width:100%}#cf-inline-error-wrapper{-webkit-box-shadow:0 2px 10px rgba(0,0,0,.5);-moz-box-shadow:0 2px 10px rgba(0,0,0,.5);box-shadow:0 2px 10px rgba(0,0,0,.5)}#cf-wrapper #cf-error-details{background:#fff}#cf-wrapper #cf-error-details .cf-error-overview{padding:25px 0 0}#cf-wrapper #cf-error-details .cf-error-overview h1,#cf-wrapper #cf-error-details .cf-error-overview h2{font-weight:300}#cf-wrapper #cf-error-details .cf-error-overview h2{margin-top:0}#cf-wrapper #cf-error-details .cf-highlight{background:#ebebeb;overflow-x:hidden;padding:30px 0;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(0,#dedede),color-stop(3%,#ebebeb),color-stop(97%,#ebebeb),color-stop(100%,#dedede));background-image:-webkit-linear-gradient(top,#dedede 0,#ebebeb 3%,#ebebeb 97%,#dedede 100%);background-image:-moz-linear-gradient(top,#dedede 0,#ebebeb 3%,#ebebeb 97%,#dedede 100%);background-image:-o-linear-gradient(top,#dedede 0,#ebebeb 3%,#ebebeb 97%,#dedede 100%);background-image:linear-gradient(top,#dedede 0,#ebebeb 3%,#ebebeb 97%,#dedede 100%)}#cf-wrapper #cf-error-details .cf-highlight h3{color:#999;font-weight:300}#cf-wrapper #cf-error-details .cf-highlight .cf-column:last-child{padding-bottom:0}#cf-wrapper #cf-error-details .cf-highlight .cf-highlight-inverse{background-color:#fff;padding:15px;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px}#cf-wrapper #cf-error-details .cf-status-display h3{margin-top:.5em}#cf-wrapper #cf-error-details .cf-status-label{color:#9bca3e;font-size:1.46667em}#cf-wrapper #cf-error-details .cf-status-label,#cf-wrapper #cf-error-details .cf-status-name{display:inline}#cf-wrapper #cf-error-details .cf-status-item{display:block;position:relative;text-align:left}#cf-wrapper #cf-error-details .cf-status-item,#cf-wrapper #cf-error-details .cf-status-item.cf-column{padding-bottom:1.5em}#cf-wrapper #cf-error-details .cf-status-item.cf-error-source{display:block;text-align:center}#cf-wrapper #cf-error-details .cf-status-item.cf-error-source:after{bottom:-60px;content:'';display:none;border-bottom:18px solid #fff;border-left:20px solid transparent;border-right:20px solid transparent;height:0;left:50%;margin-left:-9px;position:absolute;right:50%;width:0}#cf-wrapper #cf-error-details .cf-status-item+.cf-status-item{border-top:1px solid #dedede;padding-top:1.5em}#cf-wrapper #cf-error-details .cf-status-item+.cf-status-item:before{background:0 -734px no-repeat;content:'';display:block;left:0;position:absolute;top:25.67px}#cf-wrapper #cf-error-details .cf-error-source .cf-icon-error-container{height:85px;margin-bottom:2.5em}#cf-wrapper #cf-error-details .cf-error-source .cf-status-label{color:#bd2426}#cf-wrapper #cf-error-details .cf-error-source .cf-icon{display:block}#cf-wrapper #cf-error-details .cf-error-source .cf-icon-status{bottom:-10px;left:50%;top:auto;right:auto}#cf-wrapper #cf-error-details .cf-error-source .cf-status-label,#cf-wrapper #cf-error-details .cf-error-source .cf-status-name{display:block}#cf-wrapper #cf-error-details .cf-icon-error-container{height:auto;position:relative}#cf-wrapper #cf-error-details .cf-icon-status{display:block;margin-left:-24px;position:absolute;top:0;right:0}#cf-wrapper #cf-error-details .cf-icon{display:none;margin:0 auto}#cf-wrapper #cf-error-details .cf-status-desc{display:block;height:22.5px;overflow:hidden;text-overflow:ellipsis;width:100%;white-space:nowrap}#cf-wrapper #cf-error-details .cf-status-desc:empty{display:none}#cf-wrapper #cf-error-details .cf-error-footer{padding:1.33333em 0;border-top:1px #ebebeb solid;text-align:center}#cf-wrapper #cf-error-details .cf-error-footer p{font-size:13px}#cf-wrapper #cf-error-details .cf-error-footer select{margin:0!important}#cf-wrapper #cf-error-details .cf-footer-item{display:block;margin-bottom:5px;text-align:left}#cf-wrapper #cf-error-details .cf-footer-separator{display:none}#cf-wrapper #cf-error-details .cf-captcha-info{margin-bottom:10px;position:relative;text-align:center}#cf-wrapper #cf-error-details .cf-captcha-image{height:57px;width:300px}#cf-wrapper #cf-error-details .cf-captcha-actions{margin-top:15px}#cf-wrapper #cf-error-details .cf-captcha-actions a{font-size:0;height:36.38px;overflow:hidden;padding-left:1.2em;padding-right:1.2em;position:relative;text-indent:-9999px;width:36.38px;white-space:nowrap}#cf-wrapper #cf-error-details .cf-captcha-actions a span{background:no-repeat;display:block;height:20px;left:50%;line-height:0;margin-left:-10px;margin-top:-10px;position:absolute;speak:none;top:50%;width:20px}#cf-wrapper #cf-error-details .cf-captcha-actions a.cf-icon-refresh span{background-position:0 -787px}#cf-wrapper #cf-error-details .cf-captcha-actions a.cf-icon-announce span{background-position:0 -767px}#cf-wrapper #cf-error-details .cf-captcha-actions a.cf-icon-question span{background-position:0 -827px}#cf-wrapper #cf-error-details .cf-screenshot-container{background:no-repeat #fff;max-height:400px;max-width:100%;overflow:hidden;padding-top:53px;width:960px;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;-ms-border-radius:5px 5px 0 0;-o-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}#cf-wrapper #cf-error-details .cf-screenshot-container .cf-no-screenshot{background:0 -175px;display:block;height:158px;left:25%;margin-top:-79px;overflow:hidden;position:relative;top:50%;width:178px}#cf-wrapper #cf-error-details .cf-captcha-container .cf-screenshot-container,#cf-wrapper #cf-error-details .cf-captcha-container .cf-screenshot-container img,#recaptcha-widget .cf-alert,#recaptcha-widget .recaptcha_only_if_audio,.cf-cookie-error{display:none}#cf-wrapper #cf-error-details .cf-screenshot-container .cf-no-screenshot.error{background:no-repeat;height:175px}#cf-wrapper #cf-error-details .cf-screenshot-container.cf-screenshot-full .cf-no-screenshot{left:50%;margin-left:-89px}.cf-captcha-info iframe{max-width:100%}#cf-wrapper .cf-icon,#cf-wrapper .cf-icon-status{background:no-repeat}#cf-wrapper .cf-icon-ok{background-position:0 -485px;height:48px;width:48px}#cf-wrapper .cf-icon-error{background-position:0 -533px;height:48px;width:48px}#cf-wrapper .cf-icon-browser{background-position:0 -581px;height:80px;width:100px}#cf-wrapper .cf-icon-cloud{background-position:0 -333px;height:77px;width:151px}#cf-wrapper .cf-icon-server{background-position:0 -410px;height:75px;width:95px}#cf-wrapper .cf-icon-railgun{background-position:0 -848px;height:81px;width:95px}#cf-wrapper .cf-caret{border:.33333em solid transparent;border-top-color:inherit;content:"";height:0;width:0;display:-moz-inline-stack;display:inline-block;vertical-align:middle;zoom:1}@media screen and (min-width:49.2em){#cf-wrapper #cf-error-details .cf-status-desc:empty,#cf-wrapper #cf-error-details .cf-status-item .cf-icon,#cf-wrapper #cf-error-details .cf-status-item.cf-error-source:after,#cf-wrapper #cf-error-details .cf-status-label,#cf-wrapper #cf-error-details .cf-status-name{display:block}#cf-wrapper .cf-wrapper{width:708px}#cf-wrapper #cf-error-banner{padding:20px 20px 25px}#cf-wrapper #cf-error-banner .cf-error-actions{margin-bottom:15px}#cf-wrapper #cf-error-banner .cf-error-header-desc h4{margin-right:.5em}#cf-wrapper #cf-error-details h1{font-size:4em}#cf-wrapper #cf-error-details .cf-error-overview{padding-top:2.33333em}#cf-wrapper #cf-error-details .cf-highlight{padding:4em 0}#cf-wrapper #cf-error-details .cf-status-item{text-align:center}#cf-wrapper #cf-error-details .cf-status-item,#cf-wrapper #cf-error-details .cf-status-item.cf-column{padding-bottom:0}#cf-wrapper #cf-error-details .cf-status-item+.cf-status-item{border:0;padding-top:0}#cf-wrapper #cf-error-details .cf-status-item+.cf-status-item:before{background-position:0 -544px;height:24.75px;margin-left:-37.5px;width:75px;-webkit-background-size:131.25px auto;-moz-background-size:131.25px auto;-o-background-size:131.25px auto;background-size:131.25px auto}#cf-wrapper #cf-error-details .cf-icon-error-container{height:85px;margin-bottom:2.5em}#cf-wrapper #cf-error-details .cf-icon-status{bottom:-10px;left:50%;top:auto;right:auto}#cf-wrapper #cf-error-details .cf-error-footer{padding:2.66667em 0}#cf-wrapper #cf-error-details .cf-footer-item,#cf-wrapper #cf-error-details .cf-footer-separator{display:-moz-inline-stack;display:inline-block;vertical-align:baseline;zoom:1}#cf-wrapper #cf-error-details .cf-footer-separator{padding:0 .25em}#cf-wrapper #cf-error-details .cf-status-item.cloudflare-status:before{margin-left:-50px}#cf-wrapper #cf-error-details .cf-status-item.cloudflare-status+.status-item:before{margin-left:-25px}#cf-wrapper #cf-error-details .cf-screenshot-container{height:400px;margin-bottom:-4em;max-width:none}#cf-wrapper #cf-error-details .cf-captcha-container .cf-screenshot-container,#cf-wrapper #cf-error-details .cf-captcha-container .cf-screenshot-container img{display:block}}@media screen and (min-width:66em){#cf-wrapper .cf-wrapper{width:960px}#cf-wrapper #cf-error-banner .cf-close{position:relative;right:auto;top:auto}#cf-wrapper #cf-error-banner .cf-details{white-space:nowrap}#cf-wrapper #cf-error-banner .cf-details-link{padding-right:.5em}#cf-wrapper #cf-error-banner .cf-error-actions{float:right;margin-bottom:0;text-align:left;width:auto}#cf-wrapper #cf-error-details .cf-status-item+.cf-status-item:before{background-position:0 -734px;height:33px;margin-left:-50px;width:100px;-webkit-background-size:auto;-moz-background-size:auto;-o-background-size:auto;background-size:auto}#cf-wrapper #cf-error-details .cf-status-item.cf-cloudflare-status:before{margin-left:-66.67px}#cf-wrapper #cf-error-details .cf-status-item.cf-cloudflare-status+.cf-status-item:before{margin-left:-37.5px}#cf-wrapper #cf-error-details .cf-captcha-image{float:left}#cf-wrapper #cf-error-details .cf-captcha-actions{position:absolute;top:0;right:0}}.no-js #cf-wrapper .js-only{display:none}#cf-wrapper #cf-error-details .heading-ray-id{font-family:monaco,courier,monospace;font-size:15px;white-space:nowrap}</style>
+ </head>
+ <body>
+ <div id="cf-wrapper">
+ <div id="cf-error-details" class="cf-error-details-wrapper">
+ <div class="cf-wrapper cf-header cf-error-overview">
+ <h1>One more step</h1>
+ <h2 class="cf-subheadline"><span>Please complete the security check to access</span> YOUR.CAMPAIGN.WEBSITE</h2>
+ </div>
+ <!-- /.header -->
+ <div class="cf-section cf-highlight cf-captcha-container">
+ <div class="cf-wrapper">
+ <div class="cf-columns two">
+ <div class="cf-column">
+ <div class="cf-highlight-inverse cf-form-stacked" onclick="alert('Cannot contact reCAPTCHA. Check your connection and try again');location.reload()">
+ <img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUFBQUFBQUFBQUFBQUHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwf/wgARCABMAS4DABEAAREAAhEA/8QAGwABAQEAAwEBAAAAAAAAAAAAAAQDAQUGAgf/xAAaAQEBAQEBAQEAAAAAAAAAAAAAAQIEAwUG/9oADAMAAAEQAhAAAAH9aPsAAAAAAAAAAAAAAAAAE5QaAAAAAAAAAAAAAAA6bs8en6vOrl6PS8evoEZQCcHJUcgAAAAAAAAAAA4PzT9R8vjx9fG+vR6vj3+qfE9BGUGBeDEzNgAAAAAAAAAAAY2eE7vD3HB0eX7s14em5NCMoMC8GJmdZNT3OEt2sRTeab6zbjU1dbnV9zg16q5AAAAAGR5Y5Mz1poCMoMC8GJmZS/SR1YnVrXLlrNObjZPLpVEdhQAAAAAHBEbm4BGUAnByVHIAAAAAAAAAAB1aSLzGdg0J19MRlBoAAAAAAAAAAAAACIFB9EKWLwakhQaAAAAAAAAAAAAAA65MJYK7I3Tq5fmvRxJWJwAAAAAAAAAAAAAAAAAD7P/EACQQAAEEAgEEAwEBAAAAAAAAAAIAAQMTBBIyBRARQBQwMyAi/9oACAEAAAEFAhFnbUVqK1FaitRWorUVqK1FaitRWorUVqK1FaitRWorUVqK1FaitRWorUVqK1FaitRWorUVqK1FaitRWoqT/LDx97Iy/jvJ1cIxxurYmSXlvPabiPEiYWviV8Svi9vJnfIkLp8kj52DLgn0OaWfK7TcR4y95Pzj4exJwGEAOPchyekRZUuF0yHBPtNxHjL3k/OPhmkQxvdFJBl5BvNJk3R5WRlNjzTZGVm5ckDzzmOM8mY5BNPKfyZ5lizSzZn2HwOIwYZ5AbeRzHz47TcR4y95Pzj4TQjM1bWjiRg1Q2NgAItjABzYYzEUAlDLitIZYEbo8MCccYAm+748KGMA/ibiPEhYmoiVESoi9mXIeJ2mkmJ8l2OJ8mQwKbVppRkbKPVTcR4+45w70wO2o+GZmRFBswAKYAZlNxHj7hY4kbYYMg6foo8dtJYbUHT4xYunC4qbi8jiriVxK4lcSuJXEriVxK4lcSuJXEriVxK4lcSuJXEriVxK4lcSuJXEriVxK4lcSuJXEriVxK4lcSuJXEmexf/EADkRAAECAgYEDQEJAAAAAAAAAAECEQASAyExQVFhECJQoQQTMlJicXKBkbHR0vDCMDNAYJKisrPB/9oACAECAQE/AfyLSUvF2pJGLwnhIUQJT4wFA7EpVmkXld8zhFCpNIiawv3VGqCGhNuwjWGhPB0JILqq+YaJQcYAb7JPKHUv+tZi0E80IG5Q+mAHfshY/bVD8voy/X7YUJZjakEgXEsHOLVEY34VhnpL/u2ut4xquqHthWqqXpBO/QrVSrJav40cK1Z+iopgplnGCyPxYLF+vekp8jFyhi26b3GBV+kI7hLv1RDcrNh4Te6DX6Q9ajzmfudvM5V9UWgg2G7c/wAqLVgwrWM3Sm3x5YQa3etySc30Pys/PHaXqB4t66L4Fg7Ijm9maLov2J/mxeblDW5tuf12X//EADcRAAECAgYHBgMJAAAAAAAAAAECEQADBBIhQVFhECIxMlJxgQUTUGKC0WCh8CMwQEKRkqKj4f/aAAgBAQEBPwH4Fo1EFJBaaEqG1JB2YweylAP3yf2mJ1BnyRWYLRim1uY2+CUWQmjSQLKzOs5+w/2F0+UqTOVL1lIIsNjisBWGXzxiiUlFISWBBG0c87wY7TlolyJYlpCQZhNmJHgQLEHAvEztGfMQpBEsBQYsDf6tEmnzJCAiXLkgclOTidaKRTZtJSlKwgAF9V/ulbi+UE6yfM8Gzouof5RweatGCTvWvgGNXJ3IN4Zs4U47sbFfahX9cNu5wg101vJX0fn9MJ1qnmEO4l9fxZtBGPu8XpOD/NvaDa+ayvqST+lscOT6Lhk9t9rPzdg99kPum8bPrpzFxEJ1E1Rw1dAsZrht0XJHD4lh6j0Toug7Y4/rh+DMMtNw8I//xAA6EAACAAQDBQUGAwgDAAAAAAABAgADERIhMTITIoGR8ARBUXHRECNhcqGxQEJiFCAkMDNDc+FSksH/2gAIAQAABj8CBIBMaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUaV5RpXlGleUVXCF8h+P3pRKnJq/6gtsWNP1f6gICZUw5K+FfI5f+xSuPh7R5+sL5CKsaCNX0PpGr6H0jV9D6fiyfy5KPh/uEkzPdiZ+bOlN6h+OEBXIZW0sO+n2Iia012mMJIWp8Aw9o8/WF8hEr/Ivtf5T9oT5R9vxL/KftAcVNDXHLDhAMwCuY68YMyb2ieWOQ3aAeAFuUM8t5jFlt3qeNe4D2jz9YXyESv8AIvtf5T9oT5R9ok2sV/iOzDA0wM9AR5EYEd4gdm7M0sXLPnFpql95pwalFaXhWY3fUYao7G80SRL7TUBVDXIwltMqXLUYEIcNmttRi2cGVI2K0lbS51ZsbiLbVZMCBnXd8Grgh7OJUv3Eqc20DNXahqS1tZbdJrMN2Y3DEqaJijs8zsyzNla1Rd+raW3Xfm2endp+aDszL3UvK7KdOds8Dsf6KmmE17hWu7u4idKVSzbKgbL3jouNPmz+hyhpcs9nulIrOWV7ZjNduIBMrLwXWdpnpNIm9o7NsgG7P2ebSYGJNRNazdZbPn36f8DDN2YSgqS0c7QMS5db7AVYbOi036TMTowxmPtB+zt2bs8xEtao2hmkf3Ct2G+bN7dGFm9/MbyP2gMRgesfAxQHCA1SWgXau/2jz9YXyESv8i+1/lP2hPlH2hVaoteXMw8Zbhx44VGPw8IE7G4IU+FGKnnuiOygF/4ckpljVGl72Hg5ypjSDMxqUs+FKk88YlrLmz5VstZJKlLpktNIeqHKp3ksYVNCIkvLLS9mmztW21k7laqk7uYtKnxqMIZtrOl3ps5gQgXoLqA1UkW3tQoUOOJMLJJa1dn4V92ysO79IrhyguJs6SWUI+zKi9RWgNytSlzUZLHx1ZQbJs+SpRJRWWVCmWlwC4qSuDEXIVfwYRuTJsgFQjiWQA6LWimqsVoCRdLKPT82AoJyFk92soqKWFUrZ3V3LmpaRnjWgp/O0ciR9jG6oH7g8/WF8hFGFRGn6n1jT9T6xp+p9fxL7lyqisTXHeJUAKFNcRj9jlAWrdl3WJwxNpGW1QG0VqdwHyiULrlot5VGKm/I3UISmDYkYGDvTgl81S3uLQqs6rs8C9woP6gIz+EJXtD786ZLuIlboQzaUogFzWAb1w8B3QE3u0i19GzGIelWuZBUZG3CvdHZyS2hWm7hI3sMSq2pTF8xl4ewefrC+Q/GmWw3iADuG3vopelnjRSe/LGLNjLtU4CwWg+IwpxEEWrQ54Z4Ux8cMPKMAB5fHE8zjH7OyVrjbsmKHGpNbLMzia5nHGBaqrQUFABQeHl8IICKAcCKCh7vth7B5+sL5D8a7kmpGAq1ooMyt1rHvBIqO6FpQAUwCgVAAw4uob6d9Y1yWxJ3pO7U27wXa7r7uDLQCposOGvW5u5yrBQaqt0sg0HgDkaGorB3qVlun/e3HP8AT9cxABEsjdwsNKBy5G+8w0aorjTCtICoZaaf7Vd5bt624LcQ1N4NSnlSB5+sWilBGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjGS9cYyXrjG9H//EACgQAQACAgEDAwMFAQAAAAAAAAEAESHxMUFR8BBhkSBAcTCBobHB0f/aAAgBAAABPyFLIBVBbSaxNYmsTWJrE1iaxNYmsTWJrE1iaxNYmsTWJrE1iaxNYmsTWJrE1iaxNYmsTWJrE1iaxNYmsTWJrE1iEMB3WMYp/wCTzHb79BODAg9zlSf1ki1BTAOqH9piQwEs7GrewouhOgsbyzRha5oUL9/XxPaPMdp4aH4+gooobBOH7njMuo0fZdGO/J+OAllhcUzBoWe4X161A8QuOYLEc2CzjJSwjYRbQkP9921tb9fE9o8x2nLw8+vhe6eJ7PuRZ8f9UdxjBRVrLANfvEBKwBwVi8vw4up135NXxwg/lyqrL3uBhCljlY9fE9o8x2nLw8+vhe6eJ7I6NLVku+qtTwERwwD7RSEJvmIsiXtdkBBu4VGc0VBQMWmIF7rUSHHYmKbsNKYxNMQBgzUNJumucVvOVcuukG+ExkFY2xxyvCv3phZlHANOYKzmWrGUBotazB/rtlCKBdONQwg5mBgh5FxVd+r6CwtBRUiuLN+q8N3SqFk/Ht7Dp+12VLpewaa+R+OIBFrjq32D/OM9rnDgoUOL6+vie0eY7Tl4efXwvdPE9kR0hLg1q2ZBBhbUrMXtVEYyKpV5UZqrw9GuywNktx3mdkuLFS3WMxhs8XkutVWJg/vMBFHuyBFY6HXxLVSrQLackVdc0jsxW9nLMAAcOxEbcxcMuCLLrqM2YhDO2uYIXrCjNRZoIZfEABWLL9RHaBiQJYkJIRyHZ2Fl4FpQ/rUglJZFm8PwHwBOfnvy/Lb/AD9Hie0eY7TwwPx9BRRQAAcH3HEcgeT2HqV2DhAWNc6tQ9PWTnFNLl3g37ZqiVxbxzl28GEQLoLa98UBLxJWpyikWLQwQKVyHpHARzrdGGVMkPXMup3XwvR4ntHmO33udS81Q261XkN4Wit58W4F0ufOerYt3PxqdKxYK6Bl0BwQWibWgBaUx1RT1VWJt89l8DbnjWwlfMEIGlGLdGIMSqBBWCBSdB6YnGDBPE9o8x2+9fFEZIQA0jyMC2CVNJ3AWWt0Cf8ADBX5OKjQCAd0JTKNkgq1spk4KkweZHBncsgOMExZAfbVyKK63CeYwKvl+Awah+RY51t6PE9oToWAu7r5JpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIxpGNIw9wFGcd/5n/9oADAMAAAEAAgAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAapAQSQAAAAAAAAAAAACEPAQCSAAAAAAAAAAAAAsBgQCSAFmE6LoAAAAAACSAQCSBOpo9usAAAAAACAAQSQAAAAAAAAAAAAcE20QAAAAAAAAAAAAAACSMSQAAAAAAAAAAAAAAcULjSAAAAAAAAAAAAAAAAAAT//EACcRAQACAQIFAwUBAAAAAAAAAAERITEAURBBUGGhcYGxIDBgkdHw/9oACAECAQE/EPwV2SnABeycn51AWmbhyJ21SYe/RFDaGD2wVvl41BqMguUku/h5TpXCyctNUrNdCEliRKze2huihJSJN+BZKy9v5oftTJuPgB+tM8WuSjG2EayAmaRh1NcIabiYl2XOgnEp50G0IxIG8XHlTvGSmexv3qCPJnBi5aYMZm4h8pxbWI8FuEUlELyEWkiIiIw5LFQSxM0rAoIG8AIzAAVxIS9bV1QJLdch3VsFAyy4jpHs9uzPvMz6zc7/AEs9DGX+t3fr0t//xAAmEQEAAgEDBAIBBQAAAAAAAAABESExAEFREFBhgXGRIDBgocHw/9oACAEBAQE/EP2LmX9E2gCMI4eGnIraTEexjnzpayLXYOUKffnUdjWAGGxslF2GjanJXXzRh9SJcuIVAk0qROPcWhIRAeEwm6v3OCBTL/QYCgCuxMeCgBwwzD4d9LPIwUMoVB9PQIhtVtwkJX6CoD9ObzmR0QT6bESWQrAkFCDwjSErgEWh7Clnf0iXwAeaFPYetIO4r/nn6xjfO17dwjk4fJwZiLys9HBDatCV5VVtdMykJLIxCBZW5KKfCi+RYzo6DnQ6eR3LoN2RPAFkvaOYspiPRt/vP44oo7GMPm7Wf//EACUQAQACAQMCBwEBAAAAAAAAAAEAESExwfBBYRAgMFBRcYFAkf/aAAgBAAABPxBKE0gBcourg0DBOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7TlO05TtOU7eNNPKdpynacp2iLiWkskyADkU6n77BaTwBYMNOzqxcosLrIeKCphbrjLvUdKHQFUjpaUuFaJjZJGi7AesBosHlmtQABC0XLoAFXsD46aWwAQCAiaIlifZ/SoFIAWroBq/kG6MDdC0vxY7VyoDRoEiKsAmMUoWGwGJAQhQzLLC0JnVWcARHb8GU6utS9hmtFvMMiNhswllY/J07xCPlsUzBYLAKYGOU4TZ8icoW7YZNYHOGg1sslb3UVI3n3S9SyZEypXT0prRbzBun+JxGlfWQ1CHI8htrqloNB06JLEgExIqmuR+EYoQyEhrIyaDmnNE2yhjHdzybayJVAgGOfoMGDLE/QE2oK9yNvQbT+BMKqACW/ZrNkXFgwyCe/fvjxCpczhx431eyqKIyipgqmAWq0AGVegRU98N5aqY+gbKQmBGwDqtLgF6EFaRyLAy0GDRddBUVgobZsybFcB+/wA7vpTWi3mCUatIxhk+hQOBflWxt0jGMghFZGHDmLxUoBAqu4UxIWrg0DgdIlXRq58P/d40oU85eOTHA6oBzyTkT41c/LDRBkFpdJrMJmdx7IEwvQgvOPgwFYRNRT7+ojQiqYjOW+v7VzdcADukUP8AtL1mLBEJBEQRHURwj1GMtS5P8ID/ACXN41AqfCynbzJrVBERpUyaIoR+nyaaKAgAABoAUB9H9BZUZi79CLBuTPHPjErTPq62PLvMrLXQO43UAwa9oofsd8MVunK5oGxi/hxcNpHRmjSUmbprfvYCc5FNcg0JFeGlwBJdaa+/aM6OvwoDa6fgLuKk1lnJs7cQi737UyYEYgLfoINVcIoFTAAAAABQBgAMAGh7BNaL9YxdN5e6cPFysBOl/CazMgg0i22D/HY70qLpRLprAWMqZSFsK+j/AAwzQ7wCM28Ylui5n+RAVRoWBgwYutb9lhQoUKFChQoUKFChQoUKFChQoUKFChQoUKFChQoUKFCqWVgufC21YF7T/9k=">
+ </div>
+ </div>
+ </div>
+ <!-- /.columns -->
+ </div>
+ </div>
+ <!-- /.captcha-container -->
+ <div class="cf-section cf-wrapper">
+ <div class="cf-columns two">
+ <div class="cf-column">
+ <h2>Why do I have to complete a CAPTCHA?</h2>
+ <p>WRITE SOMETHING HERE NOW!</p>
+ </div>
+ <div class="cf-column">
+ <h2>What can I do to prevent this in the future?</h2>
+ <p>WRITE SOMETHING HERE NOW!</p>
+ <p>WRITE SOMETHING HERE NOW!</p>
+ <p> WRITE SOMETHING HERE NOW!</p>
+ </div>
+ </div>
+ </div>
+ <!-- /.section -->
+ <div class="cf-error-footer cf-wrapper">
+ <p>
+ <span class="cf-footer-item">Cloudflare Ray ID: <strong>RANDIDAREA</strong></span>
+ <span class="cf-footer-separator">&bull;</span>
+ <span class="cf-footer-item"><span>Your IP</span>: 999.999.999.999</span>
+ <span class="cf-footer-separator">&bull;</span>
+ <span class="cf-footer-item"><span>Performance &amp; security by</span> <a href="https://www.cloudflare.com/?fuckCloudflare=true" id="brand_link" target="_blank">Cloudflare</a></span>
+ </p>
+ </div>
+ <!-- /.error-footer -->
+ </div>
+ <!-- /#cf-error-details -->
+ </div>
+ <!-- /#cf-wrapper -->
+ </body>
+</html> \ No newline at end of file
diff --git a/tool/cloudflare_one_more_step.php b/tool/cloudflare_one_more_step.php
new file mode 100644
index 00000000..ad8cd801
--- /dev/null
+++ b/tool/cloudflare_one_more_step.php
@@ -0,0 +1,6 @@
+<?php
+
+if (I_Love_Cloudflare() || I_Come_From_Cloudflare()) {
+ echo(file_get_contents('cloudflare.onemorestep.template.html'));
+ die;
+}
diff --git a/tool/example.mdn_basedom_list.txt b/tool/example.mdn_basedom_list.txt
new file mode 100644
index 00000000..f36500d6
--- /dev/null
+++ b/tool/example.mdn_basedom_list.txt
@@ -0,0 +1,3060 @@
+# Mastodon Preloma Domain
+# !! This is just a domain list.
+# !! This is NOT Cloudflare-only NOR Cloudflare-free server list!
+# !! You should read "getCFDomainFromList.php".
+# !!
+076.ne.jp
+0j0.jp
+0ko.me
+101010.pl
+1312.media
+18kink.com
+1929.com
+1d4.us
+1w6.org
+2mb.social
+2ndamendment.social
+2soc.net
+3615yunohost.fr
+39sounds.net
+3bk.jp
+3dots.lv
+3ends.info
+3x1t.eu
+440hz.social
+48645.tk
+4estate.media
+4eva.online
+551.social
+57n.org
+64.re
+6q0.net
+7144.party
+71m.us
+73k.us
+758.fm
+7thmagic.net
+850mb.net
+8mitsu.net
+9177xe.tokyo
+9mmtylenol.me
+9net.org
+9uelle.jp
+a-tak.com
+a.nom.pl
+a11y.info
+a2mi.social
+a3.pm
+aana.site
+aardlark.co
+aaronbsmith.com
+aaronpk.com
+abcang.net
+abdl.link
+abdl.social
+abeardedqueer.com
+abhlach.ie
+abigo.de
+abld.info
+abortu.com
+about-bitcoin.com
+absturztau.be
+abunchtell.com
+abyss.fun
+accela.online
+acg.mn
+acg.social
+adaizen.com
+adhoc.systems
+adlerweb.info
+adminit.cz
+adney.land
+adorable.space
+adthr.ee
+adwb.io
+adyxax.org
+aekrylov.me
+aetheri.ca
+afn.social
+afromunkee.xyz
+afu.social
+aidannyquist.com
+aire.ml
+airen-no-jikken.icu
+ajin.la
+akabe.co
+akanechan.love
+aki-null.net
+akionux.net
+akitadon.com
+albertj.nl
+albin.social
+aleph.land
+alexanderweb.de
+alexkeating.me
+alfter.us
+alglab.net
+algonoise.social
+alicia.ne.jp
+alid.pw
+alienlebarge.ch
+alixrossi.corsica
+all.de
+allnutt.net
+alnair.blue
+alohaloa.com
+altelectron.org.uk
+alterna-cloud.com
+alternanet.fr
+alternativebit.fr
+ama.ne.jp
+amanzi.nz
+amemiya.work
+americassweethe.art
+amicale.net
+amicidelbaretto.org
+amity.zone
+amnz.jp
+amplifie.red
+an6.us
+anarchism.online
+anarchism.space
+ancel.io
+ancientoch.info
+andersen.social
+anfora.xyz
+angraecumnote.net
+angristan.xyz
+angry.im
+angrybeanie.com
+anikore.xin
+anima-mystica.org
+animal.business
+animal.church
+animalliberation.social
+anime.website
+animeisgay.com
+anjara.eu
+anniqa.com
+another-guild.com
+anoxinon.de
+anqou.net
+anti-globalism.org
+anticapitalist.party
+antifa.gmbh
+antopie.org
+apdu.fr
+apertron.com
+apkfission.net
+appdot.net
+apreslanu.it
+april.org
+aqeeliz.com
+aqn.jp
+aqr.af
+aqraf.tokyo
+aquilenet.fr
+ar.al
+archie.party
+archous.net
+aria.company
+aria.dog
+arith.jp
+arjoranta.fi
+arkaic.com
+arkham.cafe
+arktos.social
+arkwoodpond.info
+arnip.org
+arqadium.com
+artalley.porn
+artchair.net
+artoot.xyz
+arukascloud.io
+ascraeus.org
+ashleystone.me.uk
+asmodeus.tokyo
+asonix.dog
+asrun.eu
+assortedflotsam.com
+asterism.xyz
+astrolovy.com
+at7s.me
+atcurio.com
+ateliershiori.moe
+atikoro.net
+atilla.im
+atroxen.com
+atx.social
+atypique.net
+au2pb.net
+aufdeine.art
+august-don.site
+auntiefasupply.co
+auri.ga
+aus.social
+ausglam.space
+auspol.cafe
+auttaja.io
+avareborn.de
+aventer.biz
+avidol.jp
+avoh.club
+awful.club
+awoo.pub
+awoo.space
+awoonet.org
+awooo.club
+awswan.com
+ax9.eu
+aximov.net
+az-men.com
+azarakko.net
+azuki-zenzai.net
+azurity.onl
+b-shock.org
+b101.me
+b3ta.social
+b612.icu
+b612.me
+babelut.be
+babuu.club
+babycastles.com
+babymetal.party
+backyard.cloud
+baconpotato.net
+bahnhof.cz
+bakusocial.com
+balafon.social
+ballpointcarrot.net
+banana.dog
+bananachips.club
+bangdream.space
+baraag.net
+barcamp.social
+barippi.com
+bark.house
+barkshark.tk
+bashell.com
+basstdn.jp
+baty.net
+bau-ha.us
+baucum.me
+baud.jp
+bbbdn.jp
+beach.city
+beamy-lake.social
+bear.community
+beep.computer
+beepboop.one
+beeping.town
+beerfactory.org
+beeslink.com
+begbie.party
+benborges.xyz
+benhutchings.com
+bennyp.org
+benpro.fr
+benward.social
+berezowski.de
+berkeley.edu
+berries.space
+bert.org
+bertel-numerique.re
+beudot07.net
+bghost.xyz
+bgme.me
+bibeogaem.zone
+bichomen.com
+bicyclemstdn.jp
+bida.im
+bigbox.red
+bigdickisbackintown.futbol
+bigshoulders.city
+bikeshed.party
+bilboed.tech
+bildung.social
+billyb.org
+binatang.nl
+binchan.xyz
+binfalse.de
+binfish.jp
+bionicbeer.com
+birb.haus
+birb.site
+bird.band
+birdriver.org
+birdsite.link
+bisby.xyz
+biscuit.town
+bitbank.cc
+bitcast.info
+bitcoinhackers.org
+bitpage.de
+bittoco.pw
+biwakodon.com
+blackice.online
+blacksketch.com
+blaise.ca
+blessedgeeks.org
+blimps.xyz
+blob.coffee
+blockriot.com
+blueadair.net
+bluecore.net
+bluex.im
+blurts.net
+boardgames.social
+bobcall.me
+bobek.cz
+boberts.me
+bof.space
+bofa.lol
+boitam.eu
+bona.space
+bonifacelabs.ca
+bonn.social
+bonzoesc.net
+bookbeezhive.com
+booktoot.club
+bookwor.ms
+boony.space
+boop.link
+boop.town
+boosterfive.com
+bortzmeyer.fr
+boss.taxi
+botdon.net
+botsin.space
+bovid.space
+boxp.tk
+boyslove.jp
+bradsease.com
+braintube.com
+brainvom.it
+brap.party
+brawner.social
+brcal.cz
+brechanegra.net
+breizh.me
+brennanbenkert.com
+bricin.net
+brick.camp
+brignell.co
+brined.fish
+brocks.social
+brokenbydesign.org
+brrrt.eu
+bruder.space
+bruniau.net
+bsd.moe
+bsd.network
+bubbleguts.social
+buckket.org
+buffalomesh.net
+bugs.social
+bullgit.party
+bulubulu.club
+bunny.blue
+bunt.social
+butash.net
+butterknifeestates.com
+butts.team
+bytemark.social
+c-cha.cc
+c2bdon.net
+c4103.com
+calc.news
+camixo.com
+canecreekstudio.org
+canine.cloud
+canislupus.im
+canned-death.us
+canor.kr
+cantos.social
+cap.moe
+capitalism.party
+captainark.net
+carbontwelve.jp
+cardina1.red
+care-tags.org
+carfulhony.com
+cartapus.eu
+cartodon.com
+caspar.social
+cat-from-outer.space
+cat.social
+cat6.network
+catboy.cafe
+catdon.life
+catgirl.science
+catgirl.space
+catgirl.zone
+catgirls.science
+catgirlsin.space
+catgram.jp
+cattesandbuttes.com
+cawfee.club
+cdstm.ch
+ced117.net
+ceilidh.space
+cemea.org
+centiworks.com
+cercle.jp
+cetialphafive.com
+cga.graphics
+cgx.me
+chabant.social
+chablis.social
+chaos.social
+chaosfield.at
+chapril.org
+charleshaws.com
+charmed.social
+chatstodon.com
+chatstorm.io
+cheerful.social
+chelsealaurel.com
+chemtrail.airforce
+chickenfan.club
+chicoca.net
+chimerae.org
+chinapedia.org
+chinwag.org
+chirno.tech
+chirpi.de
+chitter.xyz
+chocot.art
+chomp.life
+chorus.space
+chotto.moe
+chown.me
+chrisbeckstrom.com
+chrisbol.nl
+chromabits.com
+chromic.org
+cigarcabin.com
+cipherbliss.com
+cironnup.com
+cisti.org
+cityfellas.com
+clacks.link
+claris.cf
+claristdon.net
+click.ba.it
+clonewars.vet
+cloudfrancois.fr
+cloudns.cc
+cloutfla.re
+cmdr.social
+cmpwn.com
+cmx.im
+cncs.io
+cnet.site
+co-mastdn.ga
+coales.co
+cocoa.moe
+cocoronavi.com
+cocoronavi.net
+code4lib.social
+codingfield.com
+cofe.moe
+cofe.space
+coletivos.org
+colorid.es
+comfy.moe
+comics.town
+comicscamp.club
+comm.cx
+comm.network
+communia.org
+communist.accountant
+computerfairi.es
+computerfox.xyz
+confederac.io
+conquerworld.fr
+conradkramer.com
+consumium.org
+content.town
+cookdon.com
+cooleysekula.net
+corebreach.com
+cornichon.me
+corrigan.moe
+cosine.online
+cosmicanimal.jp
+cosplayer.com
+couchet.org
+counter.social
+coyote.social
+cpper.xyz
+craigstewart.de
+crakac.com
+crazynewworld.net
+crazynoisybizarre.town
+crazypanda.fr
+cre8thoughts.com
+creativecommons.org
+crime.group
+crinklefur.club
+crow.haus
+crows.tokyo
+crunchywatch.uk
+cryptids.online
+crypto-don.net
+crypto.church
+cryptodon.tokyo
+cryptodont.io
+crystal-lang.social
+csswg.org
+cthonic.club
+ctrlsocial.org
+ctseuro.com
+cucked.me
+cuezakuisgodofthe.world
+culturewar.us
+curmudgeon.cafe
+cursed.technology
+cute2d.xyz
+cutie-pies.club
+cyano.at
+cyber-tribal.com
+cybercoin.network
+cybergay.space
+cyberia.social
+cyberiacafe.club
+cybernetic.family
+cybr.es
+cybre.club
+cybre.ninja
+cybre.space
+cygnan.com
+cypherpunk.observer
+cypv4.com
+d.foundation
+d20hero.club
+dachary.org
+daemons.it
+daffodil-11.org
+daikoku-ya.org
+daizhige.org
+dancingbanana.party
+darkest-timeline.com
+darkpeak.org
+dasoran.net
+dat.cloud
+datamol.org
+davidpeach.co.uk
+dbatley.com
+dcu.ie
+ddns.net
+ddnss.org
+deadinsi.de
+deadsuperhero.com
+decepticons.eu.org
+decisiveliberty.news
+deeloves.me
+delbertbeta.cc
+delepine.info
+deleteyourfacebook.today
+delire.party
+deluxeplush.com
+demouliere.eu
+denkbrettl.org
+depertat.net
+dereferenced.org
+derguhl.de
+des-blogueurs.org
+design.systems
+desord.re
+desvox.es
+deutrino.net
+dev-wiki.de
+dev.host
+devanooga.com
+devfs.xyz
+device5.co.uk
+devosi.org
+dewp.space
+dgm.pw
+diaspodon.fr
+dice.camp
+dickgirlon.top
+dickshow.social
+didit.site
+die-partei-reutlingen.de
+die-partei.social
+diego.codes
+digforfire.org
+digineko.jp
+digipres.club
+digitalog.it
+diglateam3.com
+dip.jp
+directdon.jp
+dirtyoldmensclub.com
+diskseven.com
+dispersio.us
+displaced.social
+dissem.ch
+distsn.org
+divad.xyz
+dizl.de
+djanzu.tokyo
+djs.social
+dlun.ch
+dmrty.fr
+dnlsd.nom.es
+dns-cloud.net
+dobbs.town
+dogbottom.com
+dokasen.com
+doll.social
+donabeneko.jp
+donken.org
+donokoanoko.jp
+donphan.social
+donte.com.br
+donteatanimals.org
+doomicile.de
+dotdon.jp
+dotgr.id
+dotopia.dk
+downey.net
+dpost.jp
+draboros.net
+dracos.co.uk
+dragon.garden
+dragon.style
+dragonscave.space
+dre.casa
+dregin.com
+dresden.network
+drg.nz
+drif.moe
+drillion.net
+droogers.eu
+dropbear.xyz
+drumbum42.com
+drycat.fr
+dsci4.xyz
+dsmouse.net
+dtp-mstdn.jp
+duck.haus
+duckchat.me
+duckdns.org
+duk.space
+durel.org
+dustinwilson.com
+dyke.space
+dynlinux.io
+dynu.net
+dystopian.fun
+earfolds.com
+eastback.co.jp
+easydns.ca
+eay.social
+ebildungslabor.social
+ecodigital.social
+ecosteader.com
+ecurie.social
+eddyssofa.com
+edge4cube.work
+edgecats.network
+edolas.world
+edvgarbe.de
+efdn.club
+eggp.me
+ehe.ovh
+ehret.me
+eigadon.net
+eigenellies.space
+eigenmagic.net
+eizodon.jp
+ekesete.net
+ekimemo.info
+elao.com
+elb.cloud
+elbmatsch.de
+eldritch.cafe
+electriceye.info
+elefant.social
+elekk.xyz
+elephant.bike
+eletusk.club
+elict.net
+eliotberriot.com
+elizafox.space
+elle.systems
+elomatreb.eu
+elouworld.org
+elsacodelcoco.net
+elsdon.me
+elvish.cafe
+emacsen.net
+emastodon.com
+embassy.social
+embracing.space
+emo.world
+emory.coffee
+emptyair.space
+engineered.space
+epenguin.com
+ephemeral-arcadia.jp
+epic.haus
+epic.net
+epiktistes.com
+equestria.social
+ericbeckers.nl
+eris.social
+ernix.jp
+ertona.net
+esadhar.net
+esprit-fablab.org
+espuppet.com
+esquinafeliz.com
+etalab.gouv.fr
+etbus.ch
+eternawings.com
+eupublic.social
+euzilla.de
+ev-db.de
+evolix.org
+ewcmi.org
+exan.tech
+exclaimindustries.net
+exdc.net
+exited.eu
+exohunt.space
+expectnomore.net
+explosion.party
+expshift.com
+extremelyonline.website
+f-si.org
+f72u.net
+fab-l3.org
+fab.industries
+facil.services
+fairground.moe
+fairilu.net
+faisal.ec
+faisal.social
+faithcollapsing.com
+fami.ga
+familie-tux.de
+famille-link.fr
+fancy.org.uk
+fandom-mastodon.com
+fandom.ink
+fanfare.horse
+farend.co.jp
+fdlibre.eu
+feather.city
+federado.es
+federati.net
+fedi.be
+fedi.io
+fedi.me
+fedive.rs
+fediverse.network
+fedpi.de
+feed.casa
+feedbackloo.pw
+feedseer.com
+feeld.community
+felesitas.cloud
+fellies.social
+femme.energy
+ferrovipath.es
+ferrus.net
+fetishdon.tk
+ff14-mstdn.xyz
+ffm.social
+ffue.eu
+ffxiv-mastodon.com
+fgochiho.vip
+fikaverse.club
+fimidi.com
+finalesocial.com
+finotto.social
+firebrand.ml
+firedragonstudios.com
+fissionator.com
+fitopen.org
+fiveop.de
+fkpk.org
+fla.red
+flabs.org
+flagada.org
+floss.social
+flownative.social
+flr.social
+fluffel.io
+flussence.eu
+flyingcube.tech
+fnordon.de
+foederati.dk
+fono.jp
+foo.sx
+ford.id.au
+foresdon.jp
+fortressofdoom.me
+fossforward.com
+fossgang.org
+fosstodon.org
+foucry.net
+fourthestate.social
+foxesare.sexy
+foxfam.club
+foxiepa.ws
+framapiaf.org
+frankmeeuwsen.xyz
+free-friends.org
+freebeer.com
+freeculture.org
+freedom.horse
+freedombone.net
+freedomtothink.social
+freehold.earth
+freemyip.com
+freeradical.zone
+freespeech.host
+freespeechextremist.com
+freezepeach.xyz
+frei.social
+friend.camp
+friendzone.social
+friskypaws.social
+frogeye.fr
+froghat.ca
+frogmob.life
+frootmig.net
+frosch03.de
+frozen.social
+fsck.club
+fsck.jp
+fsf.org
+fshm.in
+fsi.rocks
+fu-jp.net
+fucking.soy
+fuckonthefirst.date
+fujii-yuji.net
+fullyautomatedluxurygayspacecommunism.party
+functional.cafe
+fundamentalbaptistchurch.tk
+funigtor.fr
+funkymonkey.org
+furden.co
+furries.world
+furry.nz
+fursona.net
+futa.moe
+futaba-works.com
+futuregadgetlab.cc
+futvretown.com
+fuwafuwa.moe
+fuyumori.net
+fybuk.com
+g-fukurowl.club
+g0v.social
+g3l.org
+galaxy.cat
+galenabell.com
+gamedev.place
+gameliberty.club
+gamelinks007.net
+gamemaking.social
+gammon.club
+gaos.org
+garakuta.online
+gargantia.fr
+gattai.net
+gauchiste.club
+gaybaby.club
+gaydog.mom
+gayhorse.club
+gbnet.net
+gchq.online
+gdgd.jp.net
+geeknews.chat
+geekyboo.com
+genco.me
+generally.online
+genesismachina.ca
+geno.social
+gensokyo.social
+gensokyo.town
+gentler.earth
+geofox.org
+geraffel.net
+gerotintin.com
+ggrel.net
+ggtea.org
+giammi.org
+giant.horse
+gibiris.org
+gidikroon.eu
+giggleplex.red
+gimme-sympathy.org
+gingadon.com
+gion.me
+giorgiocomai.eu
+girlcock.club
+girldick.icu
+gla.fit
+glaceon.social
+glammr.us
+glaoigh.space
+glaros.xyz
+glipglops.club
+glitch.pizza
+glitch.social
+glitterkitten.co.uk
+globalrevolution.tv
+globulous.com
+gloon.jp
+glorificatio.org
+glyphicality.tk
+gnatter.org
+gnlk.ovh
+gnosia.info
+gnous.eu
+gnusocial.cc
+gnusocial.club
+gnusocial.in
+gnusocial.li
+gnusocial.ml
+gnusocial.net
+gnusocial.no
+goblin.camp
+gochisou.photo
+godforsaken.website
+goldandblack.xyz
+gomasy.jp
+goodass.dog
+goodchristian.website
+goodvibes.fun
+goofs.space
+googoldon.net
+gorone.xyz
+gots9713.xyz
+gouge.re
+gougere.fr
+gould.cx
+goziline.com
+grand-duchy.net
+gravitas.cafe
+greatjustice.net
+greenish.red
+greenlifeplus.net
+greenpeace.ch
+greenpencil.social
+grenat.art
+grenland.social
+grimm.link
+grnetcloud.net
+grngl.link
+grobox.de
+groover.jp
+groovestomp.com
+grotz.me
+grurple.org
+gtn-works.com
+guany.in
+guddl.de
+guineapig.party
+guizzyordi.info
+guldner.eu
+gulp.cafe
+gunzfox.tk
+gurubert.de
+guse.fr
+gwomp.com
+gza.jp
+h-sozial.de
+h4x.group
+hachune.net
+hack13.me
+hackdezorg.nl
+hacked.im
+hackerlab.fr
+hackerposse.com
+hackers.town
+hackingand.coffee
+hackoon.com
+hacktivis.me
+haikudon.jp
+hairydiode.xyz
+hakai-macaron.com
+hakoai.com
+hakorena.net
+hal9.ooo
+hamtter.net
+handon.club
+hankchizljaw.io
+happens.horse
+hardwarepunk.de
+harpy.life
+hash.social
+hashtagueule.fr
+haskell.social
+hates.technology
+haun.jp
+hax0rbana.social
+hax0rz.lol
+haxx.us
+hazbo.co.uk
+hazel.cafe
+hcxp.co
+hearthtodon.com
+heathanderson.net
+heck.ooo
+hedgehoghunter.club
+heislandmine.work
+heldscal.la
+hellsite.site
+helpmedog.com
+hemmer.land
+henchmonkey.org
+hengsha.host
+hentai.social
+herds.eu
+herokuapp.com
+hewwo.net
+hexadon.net
+hexe.net
+heyquark.com
+hiauntie.com
+hidamari.blue
+hidden.blue
+hiddenpalace.zone
+hideo54.com
+high.cat
+hikaruaikawa.tk
+hiker.camp
+himastdon.club
+hinaloe.net
+hiruandon.life
+hispagatos.space
+hisso.li
+historyhorde.com
+hitchhiker.social
+hitorino.moe
+hlad.org
+hncj.me
+hodakov.me
+hodapp.club
+hodl.city
+hofud.com
+hoga.fr
+hokutodon.co
+holdmybeer.solutions
+holeliquors.com
+holenet.services
+hom.ph
+homecomputing.fr
+homeplex.tk
+homoo.social
+homunyan.com
+honey.church
+hongeipii.com
+horsecr.app
+horsemans.online
+hostdon.jp
+hostsharing.coop
+hostux.coffee
+hostux.news
+hostux.social
+hotwife.social
+houbahouba.de
+housecat.dog
+houston.chat
+hrsm.jp
+ht164.jp
+huber-67.fr
+hugopoi.net
+huideyeren.info
+huloop.com
+hulvr.com
+humanities.one
+humblr.social
+hunterjozwiak.com
+hurgen.com
+hxbus.net
+hydratrash.party
+hyenas.space
+hypno.church
+i-red.info
+i2p.rocks
+iaia.moe
+ialis.me
+iamastodon.gifu.jp
+iamveryti.red
+ibex.social
+iboys.tokyo
+ican.codes
+icante.ventures
+ice-lolly.social
+icewind.nl
+ichigo-hoshimiya.com
+ichiji.social
+ichinyo.site
+icioulaba.tk
+icmstdn.com
+icosahedron.website
+id.cc
+ideali.sh
+idf.social
+idlethumbs.social
+idolheaven.org
+idsdt.com
+iedred7584.com
+ieji.de
+iflab.org
+ignorelist.com
+iguanodon.net
+ihatebeinga.live
+ijs01140.com
+ikata.co
+ikebuku.ro
+ikeji.ma
+ilikefreedom.ro
+iliketoast.net
+ilja.space
+illegalpornography.com
+im-in.space
+imaginair.es
+imastags.com
+imastodon.blue
+imastodon.net
+imastodon.org
+imirhil.fr
+immae.eu
+imoimo.xyz
+in-visible.eu
+inanna.xyz
+inboxshare.com
+inchasgram.com
+incmplt.net
+incorrect.space
+inct-densan.club
+ind.ie
+indie.host
+indieweb.me
+indigedon.com
+inditoot.com
+indoorsman.ee
+indy.im
+indyjp.club
+info.tm
+infosec.exchange
+infra.de
+inframed.net
+ingobernable.net
+initialization.tech
+inkson.org
+inkstained.space
+inkweb.network
+inkwell.studio
+innerwebs.social
+inpocket.net
+inscope.social
+insolente.im
+insoumis.social
+intahnet.co.uk
+integritymo.de
+intensifi.es
+interconnected.systems
+interlinked.me
+internaut.club
+internot.no
+intriguing.website
+inux39.me
+ion.ovh
+ipfire.org
+irenala.edu.mg
+irishstew.org
+irkos.uk
+irrsinn.life
+irrwitz.com
+isaacsu.com
+ischool.social
+iscute.moe
+isharacomix.org
+isidai.com
+isleo.space
+isleoblivion.space
+isnotvery.social
+iszy.cc
+iteasy.club
+itp.io
+itras.by
+itsmy.app
+itsnero.com
+iut-larochelle.fr
+iwade.net
+iwatedon.net
+izayoiwind.net
+izzz.fr
+jablon.fr
+jackdaw.today
+jalgi.eus
+jamesgallagher.social
+jamieasefa.social
+jank.town
+janogdon.net
+japanties.org
+jasonscheirer.com
+javi.pro
+jawns.club
+jaws-ug.okinawa
+jcx.se
+jeena.net
+jemp.co
+jenkins.cc
+jerem.yt
+jergefelt.se
+jestemgraczem.pl
+jesuislibre.net
+jesusinthe.club
+jeuxthemes.ch
+jezra.net
+jibby.org
+jlelse.me
+jonathanselea.se
+jonkman.ca
+jonleibowitz.com
+jonleibowitz.social
+jonproulx.com
+jonspark.com
+jonwatson.ca
+jorts.horse
+josephburnett.social
+joyrex.net
+jpages.eu
+jpi.io
+jrm.cc
+jsonly.club
+jubi.life
+juggler.jp
+julianjulian.moe
+jun-ji.me
+junkhub.org
+justodon.net
+jxmsocial.xyz
+jynxd.it
+k-easti.tk
+k-moegle.de
+kabi.tk
+kadokawadon.jp
+kagolug.org
+kahl-com.de
+kaiba.ga
+kaiserslautern.chat
+kakijin.com
+kakudon.com
+kalasarn.se
+kalik.in
+kamiyacho.net
+kamp.site
+kampftoast.de
+kanagu.info
+kancolle.social
+kandar.in
+kanina.be
+kanoa.de
+kaonet-fr.net
+kavehmoravej.com
+kawaiyume.net
+kawamr.com
+kawauso.ml
+kawen.space
+kawi.fr
+kazvam.com
+kcmo.social
+kebree.fr
+keio.ac.jp
+kellum.me
+kemo.no
+kemoner-don.tokyo
+kemono-friends.info
+kemonodon.club
+kemoshi.co
+kenhou.info
+kensimon.io
+kerenon.com
+kermodebear.org
+kessai-otaku.club
+ketchupma.io
+kgtkr.net
+kher.nl
+kibousoft.co.jp
+kicou.info
+kif.rocks
+killmi.st
+kimamass.com
+kind.social
+kink.social
+kink.town
+kinky.business
+kinkyelephant.com
+kios.cc
+kirakiratter.com
+kirby-fans.com
+kirche.social
+kirishima.cloud
+kiritan.work
+kitahina.co
+kitamurakz.com
+kitetu.com
+kith.kitchen
+kithop.ca
+kitsuna.net
+kitsune.cafe
+kitsunet.net
+kitty.social
+kitty.town
+kitunetya-ya-mastdom.com
+klamath.jp
+klg-tree.jp
+klinikowski.pl
+knots.today
+knusper-land.de
+knusperfisch.de
+knzk.me
+kobold.space
+kokolor.es
+kokomo.space
+kokuda.org
+kollenberger.me
+komic.eu
+komittee.net
+kompost.cz
+konkon.click
+konosuke.jp
+kopiti.am
+koreadon.com
+kosebamse.com
+kosmos.social
+kotatsu.jp
+kotobank.ch
+kottman.xyz
+kou.hu
+koyu.space
+kpop.social
+krefeld.life
+kretschmann.social
+krinetzki.de
+ksite.de
+kuester7.com
+kujiu.org
+kuko.hamburg
+kurage.cc
+kuriuzu.tk
+kurosuke.org
+kuude.re
+kwat.chat
+kwlug.org
+kwmr.me
+kxn4t.tech
+kys.moe
+kyureki.jp
+l4.pm
+la-web-radio.eu
+la1.jp
+lab-kadokawa.com
+lab61.org
+lacolaco.net
+ladisquette.fr
+laevateinn.tk
+laiguana.org
+lain.moe
+lainon.life
+laitues.net
+lalafell.org
+lambertz.xyz
+lamowski.net
+lamp.institute
+lancaster.ac.uk
+langfamilie.de
+lanners.uk
+lardbucket.org
+lasanha.org
+laserdisc.party
+lasersword.club
+latransition.org
+laurakalbag.com
+lavafeld.org
+laverdu.re
+lavergne.online
+layer8.space
+lazer.pizza
+lazy8.social
+lazyatom.social
+lcy.moe
+le-palantir.com
+leafygalaxy.space
+lecker.coffee
+leftic.club
+leftlibertarian.club
+legal.social
+legfr.social
+lemarchand.io
+lemonade.moe
+leo.re.kr
+leobrown.net
+lertsenem.com
+lesbiab.space
+lesbianschool.com
+lescorpsdereve.space
+lespasquier.fr
+lethar.gy
+letsalllovela.in
+lewd.today
+lewd.town
+lexpierce.social
+lfsr.net
+lgbt.io
+lgbtq.cool
+lgy.fr
+liamcottam.co.uk
+libera.blue
+liberdon.com
+libertalia.world
+libertarianism.club
+libre-association.party
+libre-entreprise.com
+libre.audio
+libre.fi
+librelabucm.org
+librenet.co.za
+libretooth.gr
+libretux.com
+liebefeld.social
+liepajnieks.lv
+lieter.nl
+ligma.pro
+likeable.space
+liliso.com
+lily.network
+linc.systems
+linernotes.club
+linfiel.com
+linkov.net
+linss.com
+lintmx.com
+linux.pizza
+linuxgamecast.com
+linuxine.net
+linuxinthenight.com
+linuxjobs.social
+linuxlab.sh
+linuxquimper.org
+linuxrocks.online
+linuxserver.pro
+lio.one
+lithium03.info
+litodon.de
+livelaughlove.social
+livers.jp
+liveunix.org
+livewing.net
+lkw.tf
+llama.su
+llkdn.com
+loadaverage.org
+lobi.to
+localtalk.chat
+loci.onl
+logi.fun
+logilab.org
+logjam.city
+logorrhea.online
+loli.estate
+lolisandstuff.moe
+lollipopcloud.solutions
+lollygaggers.uk
+loma.ml
+lond.com.br
+loner.jp
+lono.space
+loonarmys.com
+loovto.net
+lor.sh
+lord.re
+loser.space
+lou.lt
+louderthanten.net
+loutre.info
+lovelive-mstdn.com
+lovers.town
+loves.pizza
+lovetux.net
+lowsamplingrate.eu
+lppfusion.com
+lsngl.us
+ltch.fr
+lubar.me
+lucci.xyz
+lumpen.work
+lunarpunk.space
+lurk.org
+luther.social
+lux.blue
+lw1.at
+lyker.jp
+lyng.space
+lynnesbian.space
+lynxpebbles.me
+lyoko.social
+m.atm.pl
+m0t0k1ch1.com
+m2.nz
+m2hq.net
+m544.net
+ma3ali.net
+mabe.space
+mabidn.net
+macbeth.cc
+machida.yokohama
+machteburch.social
+macmynatt.social
+macsnet.cz
+mad-scientist.club
+madcam.co.uk
+madogre.tk
+maelstrom.space
+maescool.be
+magicalgirl.party
+magitek-telescope.im
+magnificentbeardsfan.club
+maho-do.jp
+maho.app
+mailpile.is
+makerdon.org
+makestuff.club
+makimaki.jp
+mal.systems
+malfunctioning.technology
+malu.today
+maly.io
+mamemo.online
+mamot.fr
+mandodon.com
+mandps.net
+mangadon.info
+manhole.club
+manimani.cc
+manowar.social
+manulanglois.fr
+manwho.re
+maoh.company
+map-le.net
+maravitti.fr
+marchgenso.me
+mares.cafe
+mario.chat
+markegli.com
+maron.blue
+martijn.at
+mas.to
+masatodon.jp
+masdis.com
+mast.eu.org
+mast.moe
+mast.one
+mastalab.app
+mastd.me
+mastd.racing
+masthead.social
+masto-sport.us
+masto.host
+masto.media
+masto.ninja
+masto.pt
+masto.space
+masto.tech
+mastoc.net
+mastod.in
+mastoden.com
+mastodogs.social
+mastodol.jp
+mastodon-train.info
+mastodon.academy
+mastodon.art
+mastodon.at
+mastodon.bayern
+mastodon.by
+mastodon.cc
+mastodon.cloud
+mastodon.club
+mastodon.computer
+mastodon.design
+mastodon.earth
+mastodon.eus
+mastodon.hamburg
+mastodon.haus
+mastodon.hk
+mastodon.host
+mastodon.ie
+mastodon.in.th
+mastodon.is
+mastodon.kitchen
+mastodon.lt
+mastodon.lu
+mastodon.me.uk
+mastodon.mg
+mastodon.nara.jp
+mastodon.nl
+mastodon.oita.jp
+mastodon.org.es
+mastodon.org.uk
+mastodon.radio
+mastodon.rocks
+mastodon.scot
+mastodon.se
+mastodon.sk
+mastodon.social
+mastodon.studio
+mastodon.technology
+mastodon.to
+mastodon.top
+mastodon.uno
+mastodon.uy
+mastodon.vip
+mastodon.wakayama.jp
+mastodon.xyz
+mastodonhk.social
+mastodonsocial.ru
+mastodont.cat
+mastodontech.de
+mastodonten.de
+mastodontti.fi
+mastodooooooon.xyz
+mastodos.com
+mastofant.de
+mastoot.com
+mastportal.info
+matarillo.com
+matcha-soft.com
+matchdon.com
+matej-lach.me
+matereal.eu
+mathematicon.com
+mathstodon.xyz
+mathtod.online
+matitodon.com
+matrix.org
+matt-social.co.uk
+matthieuharle.com
+maud.io
+maw.network
+mayel.space
+mayfirst.org
+mcewan.io
+mckellar.org
+mckellar.social
+mcnamarii.town
+mctek.tk
+mctetsudou.net
+mcwhirter.io
+mdon.ee
+mecanis.me
+mechanicalmischief.com
+medicaldon.info
+medieval.jp
+meemu.org
+meganekeesu.tokyo
+mel.social
+melalandia.tk
+melb.social
+mellified.men
+melonpan.run
+meme.garden
+menf.in
+mental.af
+mental.social
+mentalhealth.social
+menzel-it.social
+meow.academy
+meow.social
+meows.life
+merkel.social
+merveilles.town
+meshspace.de
+mess.casa
+metaccount.de
+metadata.moe
+metalhead.club
+metastudio.org
+meww.xyz
+mfashby.net
+mftrhu.net
+mfz.jp
+mgub.yt
+mhc.social
+mhz.social
+michaonline.net
+michikora.com
+micromookie.com
+midnight-cms.com
+midyuki.net
+mieth.net
+migennes.net
+mikado-city.jp
+mikegerwitz.com
+mikorizal.org
+mikumikudance.cloud
+milky.coffee
+millennial.space
+millers.social
+mimikun.jp
+mimmoth.club
+mimumedon.com
+minet.net
+mingxingsex.com
+mini4wd-engineer.com
+ministryofinternet.eu
+miniwa.moe
+minohdon.jp
+minuit.xyz
+miri.my
+mirrored.social
+misell.cymru
+misosi.ru
+misskey.xyz
+mist.so
+mit.edu
+mitsurugi.org
+mitzelten.de
+miyacorata.net
+mjb.im
+mjd.id.au
+mk39.xyz
+mlmie.net
+mlpol.net
+mmodon.online
+mmorpg.social
+mnetwork.co.kr
+moar.wine
+mobile.co
+mochi.academy
+mochiwasadon.com
+mocknen.net
+mod-12.com
+modestymaise.com
+moe-max.jp
+moe.cat
+moeller.email
+moeplebs.online
+mofgao.space
+mofu2charger-listenradio.net
+monarch-pass.net
+monastery.social
+mond-basis.eu
+monkeydiesel.net
+mono.cafe
+monsiteinternet.org
+monsterpit.net
+monsterprom.space
+monstrous.church
+moonbutt.science
+mootech.eu
+morebeautifully.com
+moritzmahringer.de
+moriya.faith
+morphtown.de
+moshbox.jp
+moso.io
+mostodon.cloud
+mosw.work
+motcha.tech
+motogp.space
+moul.re
+mountain.exchange
+mouse.rocks
+moytura.org
+mr.am
+mrh.io
+mrow.space
+mrpetovan.com
+ms-olive.club
+mspsocial.net
+mstd.tokyo
+mstddntfdn.online
+mstdn-amadeus.tech
+mstdn-bike.net
+mstdn-jp.site
+mstdn-kanazawa.jp
+mstdn-newprofession.jp
+mstdn-sabage.fun
+mstdn-workers.com
+mstdn.at
+mstdn.beer
+mstdn.biz
+mstdn.blue
+mstdn.camera
+mstdn.cc
+mstdn.cloud
+mstdn.club
+mstdn.fm
+mstdn.fr
+mstdn.fukuoka.jp
+mstdn.guru
+mstdn.hokkaido.jp
+mstdn.hu
+mstdn.hyogo.jp
+mstdn.io
+mstdn.jp
+mstdn.love
+mstdn.miyazaki.jp
+mstdn.mx
+mstdn.ninja
+mstdn.onl
+mstdn.osaka
+mstdn.pw
+mstdn.si
+mstdn.su
+mstdn.tokyo
+mstdn.tw
+mstdn.vodka
+mstdnrs.club
+mstdon.app
+mtjm.eu
+muage.org
+mud.social
+mudl.us
+muenchen.social
+muenster.im
+muensterland.social
+muknown.jp
+mulot.org
+multicast.social
+multimob.be
+murder.town
+muses.gallery
+musicdn.jp
+mustardon.tokyo
+mutolo.social
+muy.moe
+mxchange.org
+mxin.org
+mxmaxime.ovh
+my-status.tk
+myconan.net
+myflog.net
+myfreecams.com
+myhome.cx
+myles.life
+mylinux.cz
+mynameisivan.ru
+mynetgear.com
+mynoghra.jp
+mytoot.de
+mytoot.net
+mywebprojects.co.uk
+mzn-potatochips.me
+n-sr.org
+n0.is
+n7mn.xyz
+nacika.com
+nadesiko-users.info
+nagoyadon.jp
+nah.re
+naii.io
+nakanod.net
+nakasange.net
+nakayoshi.tk
+nako.ne.jp
+nametaketakewo.net
+nanaaki.com
+nanamachi.net
+nanumvettithan.group
+naomi.moe
+naoy.fr
+nasqueron.org
+natalie.ee
+natter.wtf
+natudon-fishing.net
+nayukana.info
+ne.codes
+nebula.moe
+neckbeard.xyz
+neguse.net
+neigepluie.net
+neigh.horse
+nejiamasi.com
+nekomimi.jp
+nekonote.cc
+nekotodon.com
+nemsia.org
+neon.moe
+neonmidori.net
+neotag.net
+nerdculture.de
+nerdgobragh.org
+nere9.help
+neso.tech
+nestegg.net
+nesven.eu
+net-p.org
+net.scot
+nethole.us
+netstat.app
+networked.space
+netzkombin.at
+netzsphaere.xyz
+newsbots.eu
+nexus8.cf
+nghieng.net
+nhcrossing.com
+nhg.moe
+nico-bayati.de
+nielk1.com
+nightmare.zone
+nihilist.space
+nikhiljha.com
+nil.nu
+nildon.com
+nineties.cafe
+ninjawedding.org
+nintendojo.fr
+nipponalba.scot
+nitech.online
+nitiasa.com
+nitro.horse
+niu.moe
+niu.ne.jp
+nixeneko.info
+nixnet.xyz
+niyawe.de
+njp.asia
+no-monogatari.com
+no-trace.de
+noagendasocial.com
+nobert.zone
+nobody.social
+node.pk
+noela.moe
+noeldemartin.social
+noellabo.jp
+nof.st
+nofftopia.com
+nohost.me
+noip.me
+noise.social
+noisome.space
+noizycat.com
+nojober.work
+nologo.social
+nomadicista.org
+nomoresha.me
+nonexiste.net
+nonlinear.zone
+noob.party
+noon.social
+noraworld.jp
+norden.social
+nordgedanken.de
+norze.co
+nota.live
+notbird.site
+notjoshua.fr
+notreal.pw
+novadon.social
+novium.pw
+nrd.li
+nrkn.fr
+nshl.xyz
+nsnw.ca
+nth.io
+nudie.social
+nukaya.net
+nuked-the.uk
+nuklear.family
+nulled.red
+nutt.church
+nx-pod.de
+nyan.cafe
+nyan.lol
+nyerm.com
+nyoki.club
+nzoss.nz
+o-man.co
+o3o.ca
+oalm.gub.uy
+objektiv2.net
+occitanie.social
+occult.camp
+oceansocial.us
+ocr.social
+octodon.social
+odakyu.app
+oeru.org
+oftrolls.com
+ofuton.io
+ogspy.fr
+oh-kei.info
+ohgro.net
+ohnomy.rocks
+oi7.de
+ojitabi.club
+okaris.de
+okoyono.de
+oldbytes.space
+olds.town
+oldschool.community
+oltoko.de
+omaera.org
+omanko.porn
+ombreport.info
+omeganote.fr
+omochi.xyz
+onepointzero.com
+onerescue.org
+ongstar.jp
+onore.org
+onsen.tech
+onster.farm
+oook.fr
+openalgeria.org
+openbiblio.social
+opencloud.lu
+opencocon.org
+openweb.social
+openworlds.info
+opportunis.me
+opsecwin.com
+orange-order.co.uk
+oransns.com
+orcaverse.club
+ordinarius-fectum.net
+orenoshiro.site
+organicdesign.pub
+organizing.social
+oriongate.jp
+os.vu
+osm.social
+osm.town
+osyakasyama.me
+otadon.com
+otherkin.club
+otherreality.net
+otogamer.me
+otonomedia.org
+otoya.space
+otter.sh
+otter.tube
+otyakai.xyz
+ouda.space
+ouin.land
+oulipo.social
+oulu.im
+oupsman.fr
+ourempty.pub
+ovalerio.net
+owls.io
+oyler.social
+p-epsilon.com
+p4g.club
+pachyder.me
+pachyderme.net
+paddys.pub
+pafnooty.ru
+palaven.space
+panglossoft.fr
+panoptikum.io
+pantdon.site
+pantherx.social
+pao.moe
+paoon.social
+papey.fr
+paratxt.org
+parcel5.social
+parleur.net
+parrygod.club
+partecipa.digital
+partipirate.org
+party.at
+pasero.net
+patafisica.cc
+patch.cx
+paw.cafe
+pawbs.club
+pawoo.net
+pcgame.jp
+pcgamer.social
+pcsfield.com
+pdx.social
+pea.sh
+pede.rs
+peertube.fr
+pekepeke.work
+pendorwright.com
+pepsi.zone
+persadon.com
+peshane.net
+petras.space
+pfefferle.org
+pgh.social
+pgw.jp
+ph3j.com
+photodn.net
+photog.social
+photographer.pro
+phoxden.net
+phpc.social
+phreedom.tk
+pickle.zone
+pierrick.io
+pifke.social
+piggo.space
+piira.org
+pikachu.rocks
+pingupod.de
+pinkieduck.net
+piperrak.cc
+pipou.academy
+pirateparty.be
+pirati.ca
+pirati.cc
+pirati.cz
+pitman.social
+pizzadog.moe
+pla.social
+pla1.net
+place-of-refuge.com
+planet.moe
+planetaludico.com
+plankton.cz
+plasticmodels.tokyo
+playground.ws
+playvicious.social
+pleasehug.me
+plero.ma
+pleroma.aasg.name
+pleroma.cat
+pleroma.fr
+pleroma.io
+pleroma.lol
+pleroma.site
+plock.social
+ploud.fr
+plural.cafe
+plus.yt
+plush.army
+plush.city
+plusminus.io
+plustodon.net
+plx.chat
+pmpm.pw
+pointless.net
+pointless.one
+polarisfm.net
+politicaconciencia.org
+polonkai.eu
+polyamory.social
+polyglot.city
+polyohm.net
+ponseta.ga
+pony.social
+pony.style
+popindustry.org
+popula.social
+porntoot.com
+port0.xyz
+posadis.me
+postdon.com
+postmoderns.info
+potager.org
+potato.dog
+potproject.net
+pouet.ca
+pouet.it
+pouet.me
+pounced-on.me
+pounced.me
+powerlot.net
+poyo.me
+ppl.town
+pptdn.jp
+preciouslittle.life
+precure.fun
+precure.ml
+pretend.fun
+prettyqueer.online
+prfm.jp
+princess.industries
+pritter.work
+privacytools.io
+project-imas.cn
+project.social
+projectx.fun
+prolatio.xyz
+propulse.club
+prose.zone
+prostreamers.net
+pso2.club
+psychedelic.cat
+psychoactive.space
+psyopshop.com
+psyver.space
+pube.tk
+publicmedia.space
+puddle.town
+punktrash.club
+puppo.space
+puri.sm
+puyo.jp
+puz.fun
+pwarren.id.au
+pzn.lgbt
+qaf.men
+qcx.io
+qdon.space
+qiitadon.com
+qnmd.info
+qore.no
+qoto.org
+qowala.org
+qth.fr
+quarteredcircle.net
+quasi.social
+queer.af
+queer.garden
+queer.party
+queerenough.com
+queloud.net
+quentel.social
+quey.org
+quic.fr
+quine.codes
+quitter.cl
+quitter.es
+quitter.im
+quitter.pl
+quitter.pw
+quixotic.info
+qunagi.net
+quodverum.com
+qute.dog
+qwaser.fr
+r13w.social
+r3bl.social
+r3pek.org
+ra-phi.ch
+raa0121.info
+rabbit-house.me
+raccoon.network
+raconteur.ink
+radical.town
+radicalityincident.com
+radicle.space
+radiofree.cloud
+rafting.io
+raggedfeathers.com
+raildecake.fr
+rainyman.jp
+rane.sh
+ranranhome.info
+rapefeminists.network
+raptol.net
+raptorengineering.io
+rastapuls.com
+rbq.social
+rbs.io
+rcj-quantity.info
+rcsocial.net
+real-escape.jp
+realitytoots.com
+realmofespionage.xyz
+reclaim.technology
+recurrent.network
+redfish.ca
+redflag.social
+redgla.re
+redliberal.com
+redmine.jp
+redonion.social
+redroo.ml
+redwombat.social
+refactorcamp.org
+regastream.com
+remotenode.host
+reptilehouse.info
+res.ac
+reseau.education
+reseaujaune.com
+resize.club
+retro.social
+retrodon.jp
+rettiwtkcuf.social
+reve.land
+rhinoworks.info
+ries-web.de
+rights.ninja
+rinsuki.net
+rivals.space
+rjp.is
+rl5.nl
+rly.wtf
+rmxr.net
+road42.social
+robot-house.us
+robw.me
+rocketboom.io
+roconize.com
+rodent.io
+rodina-sucha.cz
+roeckoe.be
+roflcopter.fr
+ropo.jp
+rosariotech.com.br
+rosaryfaith.com
+rousset.nom.fr
+rout0r.org
+route66.social
+roxxers.xyz
+rstdn-p.com
+rt-trend.jp
+rta.run
+rthome.me
+rubber.social
+rubencito.net
+ruby.social
+ruhr.social
+rukin.me
+russnelson.com
+rustedneuron.com
+rutan.info
+rva.party
+ryanak.xyz
+ryecroft21.net
+s-up.net
+s10y.eu
+s7t.de
+sackheads.social
+safebook.space
+sahagyo.com
+sakaba.space
+sakamoto.gq
+sakura-rage.net
+sakuragawa.moe
+samnoble.org
+samsunginter.net
+sanam.xyz
+sander.social
+sandwich.net
+sanin.club
+sanin.link
+santoyo.io
+santsenques.cat
+sapphos.be
+sardo.work
+sasachi.tokyo
+sasakimasato.net
+sasbaen.be
+sastudio.jp
+satania.site
+satania.space
+savikin.me
+savvy.ch
+sawakai.space
+sbw.org
+scalie.business
+scalie.club
+sccn.club
+schieder.me
+schildt.social
+schlenz.ruhr
+schleuss.online
+schnatter.eu
+schoentoon.com
+scholar.social
+schoolidol.club
+schoolidol.pro
+schoollibraries.net
+schuppentier.org
+scicomm.xyz
+scifi.fyi
+scinan.science
+scintilla.social
+scramble.city
+scream.cloud
+scream.supply
+scream.zone
+screech.social
+scubadon.jp
+scuttle.org
+scuttlebug.space
+sdeu.fr
+sdf.org
+seacow.social
+sealion.club
+seattle.wa.us
+sebbo.net
+secline.de
+securemy.pw
+sedryk.info
+sehol.se
+seiyu.fun
+seizemeans.com
+sekiken.xyz
+sektori.org
+selfhosting.rocks
+selfy.army
+sencic.com
+senooken.jp
+sergal.org
+seriousposter.club
+serv-ops.com
+serval.club
+servus.at
+setho.org
+setl.ist
+sfbubble.net
+sfgreens.org
+sftblw.moe
+sgr.cc
+shade3d.jp
+shadowfacts.net
+shadowkat.net
+shadowverdon.info
+shareworx.net
+shelter.moe
+shestak.me
+shiftyeyed.net
+shigusegubu.club
+shillest.net
+shimaidon.net
+shinonomemilk.com
+shiro.dog
+shiroganedon.net
+shitasstits.life
+shitpost.institute
+shitposter.club
+shivering-isles.com
+shnoulle.net
+shpposter.club
+shr.today
+shrux.net
+shunderdo.me
+sideno.eu
+sigmaris.info
+signs.codes
+silba.me
+simcu.com
+simstodon.com
+sinblr.com
+sins.center
+sirousa.me
+sisters.pink
+sitedethib.com
+sizedon.com
+skaia.space
+skeleton.cool
+skiant.net
+skoji.jp
+skoops.social
+skrivel.se
+skynet.social
+sl-network.fr
+slat.org
+sldon.jp
+sleeping.town
+slice.zone
+slime.global
+slimed.org
+sludge.town
+smeap.com
+smilodon.news
+smuglo.li
+smurpspaek.de
+snabelen.no
+snaggletooth.life
+snakenode.eu
+snel.social
+sngsk.info
+snoot.com
+snoot.tube
+snopyta.org
+snouts.online
+snow-crash.org
+snowandtweet.jp
+socel.net
+social-copwell.fr
+social.bankmann.name
+social.coop
+social.im
+social.net.ua
+social.photo
+social.saarland
+sociala.me
+socialcoding.xyz
+socialjusticeoclock.org
+socialmast.xyz
+sockspls.social
+socnet.eu
+softi.city
+somsants.net
+sora.pub
+sornieth.xyz
+sosh.network
+soupwhale.com
+southflorida.social
+soykaf.com
+soysoftware.net
+sozen.network
+spacecowboy.cc
+spacekittens.ru
+spanner.works
+spaz.org
+speedfox.co.uk
+spliffito.com
+spod.ca
+spooky.camp
+sprinklerz.xyz
+spruchfest.de
+spun-industries.de
+spunkiedesign.com
+spydar007.com
+square-rooty.org
+squeak.live
+squeet.me
+squid.cafe
+squiggly.club
+squug.net
+ssc-web.net
+ssweeny.net
+stalin.rocks
+starapps-network.com
+stardew.city
+starlit.city
+starrevolution.org
+status.online
+stcpt.com
+steam-don.fun
+stellaria.network
+stenodon.jp
+stenoweb.net
+stephane-klein.info
+stephenson.cc
+stevestreza.com
+sthorp.com
+stinkt.online
+stnard.jp
+stoablick.de
+stoneartprod.xyz
+stopwatchingani.me
+stopwatchingus-heidelberg.de
+stormdragon.tk
+strangestack.com
+strangled.net
+strog.org
+stuartbutterworth.co.uk
+stuntkidz.org
+stupid.industries
+subak.club
+subvert.pw
+such.social
+suinot.org
+sukadon.cf
+sukebeneko.com
+sully.site
+summerlin.social
+summoners-riftodon.jp
+sunbeam.city
+sunet.se
+sungo.wtf
+sunshinegardens.org
+super-niche.club
+superspeed-fall.com
+supes.com
+supremesyntax.com
+surfnet.space
+surtdelcercle.cat
+sutera.fi
+suzume.life
+svallee.fr
+sven-joerns.de
+svnet.fr
+swearing.org
+sweetree.ga
+swingset.social
+switter.at
+swordlogic.com
+swungda.sh
+sxpert.org
+syasai.club
+syosetu.social
+syscaller.jp
+systemreboot.net
+systerserver.town
+syui.cf
+szy.io
+t-rg.ws
+t327.net
+tabletop.social
+taboulisme.com
+tackman.info
+tacocat.cc
+taiha.net
+tailburst.me
+taiyaki.online
+takeoverthe.world
+taker.fr
+taketodon.com
+takriz.org
+takyoji.xyz
+talajen.com
+tamag.org
+tamx.tk
+tanavega.xyz
+tank.im
+tantor.online
+tapi.cafe
+targaryen.house
+taroedon.com
+taruntarun.net
+tassaron.com
+tastefuldinosaurerotica.com
+tatooine.space
+taur.zone
+tavern.cafe
+tchncs.de
+tcit.fr
+tds.xyz
+teacoffee.life
+teamblackberry.jp
+teamtk.eu
+tebukuro.pw
+tech.lgbt
+techandbeer.social
+techdrive.top
+technomancy.space
+technopagans.de
+technosorcery.net
+techopolis.io
+tedomum.net
+tegedon.net
+tekton.network
+telmina.com
+telteltel.com
+tempr.net
+temsa.me
+tenforward.social
+tenka.love
+tenta.fun
+tentacle.social
+terrafirma.space
+terrible.graphics
+terusid.com
+teslam.in
+tessellated.space
+tetaneutral.net
+tetsumaki.net
+th23.org
+that.world
+the-orbit.net
+the-penguin.de
+the-pit.uk
+the-thirstiest-mawile.com
+the-www.eu
+theapex.social
+thechurchofmemes.com
+thecrimsontint.com
+thedisco.social
+thefourthdev.work
+thegreenclan.ca
+theha.us
+thehansfords.co.uk
+theicon.stream
+thejoyo.com
+thekurokuma.net
+theliturgists.com
+thelovebug.org
+themimitoof.fr
+themsp.org
+thequantumblockchainproject.com
+therealblue.de
+theres.life
+therope.space
+theru.org
+theshrub.org
+thespinning.top
+theubergroup.org
+thevillage.chat
+thevillastraylight.com
+theydonts.top
+thezone.zone
+thicc.horse
+thiesen.berlin
+thinaticsystem.com
+thoughtworks.com
+thraeryn.net
+thumped.com
+tiflolinux.org
+tilde.website
+tilde.zone
+timecube.club
+tinkeringwithalien.tech
+tinyfed.com
+tinysubversions.com
+tkmb.tokyo
+tkschland.de
+tkte.ch
+tmp-mstdn.cloud
+tmp1024.com
+tno.social
+toad-in-the-hole.net
+toaruhetare.net
+toco2.fun
+todon.nl
+toepi.moe
+tofu.city
+togart.de
+tokamstdn.jp
+tokyocameraclub.com
+tomica.me
+tomokiwakimoto.com
+toni.im
+toon.in
+tooot.im
+toot.berlin
+toot.blue
+toot.cafe
+toot.cat
+toot.center
+toot.chat
+toot.host
+toot.house
+toot.institute
+toot.io
+toot.koeln
+toot.love
+toot.lu
+toot.rodeo
+toot.shoes
+toot.si
+toot.site
+toot.style
+toot.wales
+toot.ws
+tooting.ai
+tooting.ch
+tootplanet.space
+toots.cloud
+toots.social
+tootux.tk
+topbug.net
+touha.me
+touhey.org
+training-fitness.fun
+transfurrmation.town
+transneptune.net
+travel-friends.chat
+travelling-lurtles.world
+travelpandas.fr
+tretkowski.de
+triathlon.one
+trickle.ink
+triggerphra.se
+triplebit.net
+troet.cafe
+troetco.de
+troll.academy
+troll.university
+trollian.space
+tron.buzz
+trshnet.de
+trunk.zone
+truongan.name.vn
+ts-novels.jp
+tsia.de
+tsr.cloud
+tsubamedon.com
+tsuki.network
+ttree.ch
+tuning.social
+turbo.chat
+turingtest.uk
+tusk.wtf
+tux.tf
+twingyeo.kr
+twinja.club
+twinkaga.in
+txsocial.club
+tyaku.com
+tycho.space
+tyil.nl
+tymoon.eu
+typica.us
+typodon.com
+typrout.ml
+tyronesama.moe
+tzi.fr
+tzyl.nl
+u-v.de
+u4u.org
+ubos.net
+ubuntu.social
+udn.jp
+uelfte.club
+uevemiage.ovh
+ufeff.club
+ugroza.ru
+ulman.social
+umastodon.jp
+umbriel.fr
+umeahackerspace.se
+umu.se
+umycode.com
+unasuke.com
+under-bank.blue
+undernet.uy
+underscore.world
+unextro.net
+unigiri.net
+unityjp-mastodon.tokyo
+unixporn.pro
+unixwolf.com
+unkworks.net
+unnerv.jp
+unreality.pink
+unsafe.space
+unshaped.space
+unsocial.pztrn.name
+untan.xyz
+unturf.com
+uoga.net
+up.edu.ph
+upsilo.net
+ur.gs
+ura-mstdn.com
+urakodon.com
+urawareds.org
+urbangender.net
+uri.life
+uribe.cafe
+ursinum.net
+urvogel.club
+usagicore.moe
+utgw.net
+utodon.jp
+utwente.nl
+uwah.moe
+uwu.social
+uzla.net
+v01d.app
+v1x3n.net
+va-11-hall-a.cafe
+vacuous.one
+valvin.fr
+vapedon.club
+vapers.jp
+vcity.network
+veg.ms
+vegible.club
+vernunftzentrum.de
+verwirrung.institute
+veryamt.com
+veryfabulo.us
+vgai.de
+vichar.me
+videogamesmen.com
+vidja.social
+vikings.net
+villanos.net
+vincanote.net
+vincentux.fr
+vinzv.space
+vipgirlfriend.xxx
+vis.social
+vivid-rabbit.com
+vixe.net
+vllm.net
+vlntn.pw
+vmrpc.net
+vocalodon.net
+void.garden
+voidfox.com
+voidptr.org
+voppe.it
+vorlon.space
+vulpine.club
+vvitches.live
+vy-let.software
+w3c.social
+w3rkhof.zone
+waferbaby.com
+waffle.tech
+wafflec.one
+waifu.social
+wakakyu.tk
+wakamesoba98.net
+wake.st
+walkers.social
+walkingmountains.fr
+wandering.shop
+wangdahoo.space
+waraiotoko.net
+waseda.ac.jp
+waterfowl.social
+waytoo.online
+waytt.cf
+weaponvsac.space
+webos.direct
+weeaboo.space
+weep.me
+weho.st
+weirdart.space
+weirder.earth
+weirdfishes.today
+weirdgone.pro
+welldn.net
+welovela.in
+welsea.xyz
+werefoxsoftware.com
+werewolf.biz
+westen.xyz
+weststar.name
+wetfish.space
+wetofu.top
+whatever.cz
+whatwaslostisfound.co.uk
+wherethedogsare.com
+whisper.tf
+whitespashe.uk
+whomst.dog
+wickenberg.nu
+widegamut.club
+wiegandtech.net
+wildeboer.net
+wildtree.jp
+willow.cafe
+windish.jp
+wirebug.ch
+witch.energy
+witchcraft.cafe
+witches.live
+witches.pub
+witches.social
+witches.town
+wiuwiu.de
+wjbolles.com
+wlw.pw
+wmer.icu
+wobscale.social
+wokka.be
+wokyleeks.com
+wolfgirl.engineering
+wolfpa.ws
+wonderdome.net
+wood-built21.net
+word.builders
+worlddominationplan.com
+worlddrive.club
+worldfactorydon.com
+worldtravel.photos
+wp-social.net
+writing.exchange
+wrk.ru
+wt21.de
+wubrg.social
+wug.fun
+wxcafe.net
+wxw.moe
+wytchmourne.com
+x0f.org
+x0r.be
+xa0.uk
+xaneaid.com
+xayah.net
+xindejiayuan.com
+xiph.org
+xkcd.network
+xmu.social
+xorkle.com
+xoxo.zone
+xpcoin.jp
+xps2.net
+xqz.ca
+xtenz.xyz
+xthemage.net
+xtremely.online
+xxoo.cat
+xxxtumblr.org
+xyzyx.org
+xz0.org
+y-y.li
+y-zu.org
+yahe.sh
+yajamon.xyz
+yakiniku.cloud
+yakitamago.info
+yakumo.foundation
+yakyudon.net
+yamachan.org
+yamagadon.com
+yamaken.jp
+yamanote.tokyo.jp
+yantene.net
+yatil.net
+ybk28.net
+yeehaw.town
+yellowmustard.club
+yeti-factory.org
+yiff.life
+yjsnpi.nu
+ykzts.technology
+yoavmoshe.com
+yocto.xyz
+yoghurthair.club
+yoitsu.moe
+yotie.blue
+yourrhythm.jp
+yousack.net
+yovm.com
+yrr0r.net
+yso.pet
+ytgrsua4.net
+ytringsrett.net
+yukari.cafe
+yukimochi.io
+yukimochi.jp
+yutacar.info
+yuwinet.win
+yvt.jp
+yysk.icu
+yyy.scot
+z-flag.work
+z0ne.moe
+z27.ch
+z80.moe
+zachcampau.com
+zaclys.com
+zaitcev.nu
+zakuac.com
+zapashcanon.fr
+zeitreisender.org
+zenbuempty.net
+zeppelin.flights
+zerojay.com
+zeteo.me
+zlg.space
+zmach1n3.com
+zmb.cm
+znark.us
+znw.social
+zoc.me
+zoddo.fr
+zom.bi
+zombienet.org
+zombocloud.com
+zoonior.app
+zrythm.org
+zuiho.moe
+zunda.ninja
+zuzax.com
+zzzoo.org \ No newline at end of file
diff --git a/tool/getCFDomainFromList.php b/tool/getCFDomainFromList.php
new file mode 100644
index 00000000..3b185522
--- /dev/null
+++ b/tool/getCFDomainFromList.php
@@ -0,0 +1,37 @@
+<?php
+/*
+WTFPL License
+
+ Run `php -f getCFDomainFromList.php` and wait for result.
+
+ This script will read INPUT_DOMAINS and add domain to OUTPUT_RESULT
+ if the target is in Cloudflare domain lists.
+
+ INPUT_DOMAINS is a list of domains. Do not list FQDN.
+
+*/
+
+ignore_user_abort(true);
+set_time_limit(0);
+
+// INPUT_DOMAINS EOF = must LF
+define('INPUT_DOMAINS', 'example.mdn_basedom_list.txt');// _base_ domain list to scan
+define('DIR_CFDOMAINS', 'split/');// path to /split/ directory (Cloudflare Domains)
+define('OUTPUT_RESULT', 'example.mastodon_cf.txt');// result
+
+if (!file_exists(DIR_CFDOMAINS.'cloudflare_0.txt')){print 'Edit DIR_CFDOMAINS';exit;}
+if (!file_exists(INPUT_DOMAINS)){print 'INPUT_DOMAINS not found';exit;}
+
+$result=array();
+
+foreach(explode("\n",file_get_contents(INPUT_DOMAINS)) as $line){
+ if (strlen($line)<4){continue;}
+ $letter=substr($line,0,1);
+ if (!preg_match("/^([a-z0-9]{1})$/",$letter)){continue;}
+ print $letter.'='.$line."\n";
+ if (in_array($line,explode("\n",file_get_contents(DIR_CFDOMAINS.'cloudflare_'.$letter.'.txt')))){$result[]=$line;}
+}
+
+print count($result)." found\n";
+file_put_contents(OUTPUT_RESULT,implode("\n",$result));
+print 'done';
diff --git a/tool/get_fqdn_tmg1.php b/tool/get_fqdn_tmg1.php
new file mode 100644
index 00000000..ad897ff5
--- /dev/null
+++ b/tool/get_fqdn_tmg1.php
@@ -0,0 +1,24 @@
+<?php
+//License: WTFPL
+
+define('F_INPUT','noncloudflarelist.txt');
+define('F_OUTPUT','fqdnlist.txt');
+
+if (!file_exists(F_INPUT)){
+ print 'File not found';
+ exit;
+}
+
+$result = array();
+
+foreach(explode("\n",file_get_contents(F_INPUT)) as $t){
+ $t = explode(' ',$t)[0];
+ if (preg_match("/^([a-z0-9\.-]{1,255})\.([a-z]{2,40})$/",$t)){
+ $result[] = $t;
+ }
+}
+
+$result = array_unique($result);
+file_put_contents(F_OUTPUT,implode("\n",$result));
+
+print 'Done';
diff --git a/tool/globalist/Globalist.py b/tool/globalist/Globalist.py
new file mode 100644
index 00000000..fc21b685
--- /dev/null
+++ b/tool/globalist/Globalist.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Globalist: manage a global repo via decentral git instances
+# you may peer with any number of other Globalist onions
+
+# Think onionshare, but with permanent onion addresses, P2P and DVCS
+
+# Python2/3. Dependencies:
+# - stem (torsocks pip install stem / via distro)
+# a recent version (>= 1.5.0) is needed for auth
+# - git must be installed
+# - torsocks must be installed
+# - tor must be up and running and the ControlPort open
+
+# Use scenario:
+# a) Run Tor.
+# b) Run the server in the background and schedule a job for pulling from peers.
+# it is a git server that listens on <your-identifier>.onion:9418
+# it's to be expected that peers uptime will intersect with yours
+# only a fraction of the time.
+# c) Globalist.py creates a git, which you may use to push and pull your own changes.
+
+# Bugs:
+# FIXME: clean up hidservauth entries on stop
+
+import globalist
+import sys
+
+if __name__=='__main__':
+ globalist.main(args=sys.argv[1:])
diff --git a/tool/globalist/ISSUES.md b/tool/globalist/ISSUES.md
new file mode 100644
index 00000000..80e85673
--- /dev/null
+++ b/tool/globalist/ISSUES.md
@@ -0,0 +1,4 @@
+version 0.0.6.2
+- HidServAuth entries are never cleaned up
+- ux: -X does not disable authentication
+
diff --git a/tool/globalist/README.md b/tool/globalist/README.md
new file mode 100644
index 00000000..2fa80387
--- /dev/null
+++ b/tool/globalist/README.md
@@ -0,0 +1,64 @@
+# Globalist
+Globalist provides distributed sharing of repositories without the need of central instances (like Microsoft GitHub).
+
+This is an attempt to ease the distribution of git repos, to overcome the risk of a central points of failure.
+
+Globalist stands for "Global List" and aims at replacing any EtherPads of more than transient value.
+
+It is also meant to evolve into an experimental distributed asynchronous wiki facility.
+
+Nodes can come and go, and network topology only depends on the peers entries in the nodes' config files. Changes that are merged by one's peers propagate by diffusion.
+
+The official repository can be found at https://codeberg.org/crimeflare/stop_cloudflare
+
+## Usage
+
+To use Globalist.py `python3` is needed. Either run it from globalist directory with `python3 Globalist.py` or or install it as described below.
+
+Per default an open tor ControlPort at 9151 without authentication is expected. You can choose another port with `-C`.
+For a list of option see `--help`.
+
+### Create repository
+
+Make a new directory and put this in the file ./repo.cfg (when creating a new repository instead of cloning from a peer, the list or indeed the repo.cfg file can remain empty)
+
+```
+[network]
+peers = <comma-separated list of onion domain names, with or without the suffix .onion>
+```
+
+For a public repository, no authentication is needed (option -_X_). In case authentication is used, prepend the secret as follows: somebody:secret@peeroniondomainname.onion
+
+For each shared repo, Globalist will create one .onion service. Note that it is possible to use either bare repos or not-bare repos.
+
+### Clone a repository
+
+To clone a bare repo:
+
+```
+Globalist.py -bc ...
+```
+
+To pull once from a bare repo:
+
+```
+Globalist.py -bp
+```
+
+## To install locally
+
+```
+./setup.py install --user
+```
+
+or
+
+```
+torsocks pip3 install -v -e .
+```
+
+## To do
+
+set default commit messages
+support signed commits
+push?
diff --git a/tool/globalist/globalist/__init__.py b/tool/globalist/globalist/__init__.py
new file mode 100644
index 00000000..d1326dc0
--- /dev/null
+++ b/tool/globalist/globalist/__init__.py
@@ -0,0 +1,478 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+__version__ = "0.0.6.2"
+
+try:
+ import ConfigParser as cp
+except:
+ import configparser as cp # python3
+import optparse as op
+import re
+import os
+import sys
+import json
+import subprocess
+
+import stem
+from stem.control import Controller
+
+# Usage:
+#
+# Make a directory.
+#
+# Put a configuration file repo.cfg listing some peers. Done.
+#
+# Initialize:
+# Either a) (git init repo/) ->
+# $ python Globalist.py -i
+# or b) (torsocks git clone git://example7abcdefgh.onion) ->
+# $ python Globalist.py -c
+#
+# Have fun:
+# Run server
+# $ python Globalist.py
+# Pull from peers once
+# $ python Globalist.py -p
+# Periodically pull, don't serve
+# $ python Globalist.py -pP 1800
+# Periodically pull and also serve
+# $ python Globalist.py -P 1800
+#
+# That's it.
+
+# One can simply check in a list of onions for open peering
+# as PEERS.txt ...
+
+# A word of CAUTION: anyone can commit anything
+# and there's no mechanism for permanently blacklisting
+# malicious peers (although one can simply remove them
+# as they crop up and roll back their changes).
+#
+# A future version of Globalist.py should introduce
+# signed commits + reputation system, when the need arises.
+
+# [network]
+# peers = example7abcdefgh.onion, example8abcdefgh.onion
+# (possibly prefixed with somebody:authkey@ ...)
+
+# when using -b (bare), merge remote changes locally after
+# git pull origin remote/origin/master.
+
+DEFAULT_CONTROLPORT = 9151
+
+STATUS = {'peers': None, 'socksport': None}
+
+OPTIONS = None
+
+def git(command):
+# print (command)
+ p = subprocess.Popen(["git"] + command)
+ return p
+
+def make_exportable(path):
+ subprocess.Popen(["touch", os.path.abspath(os.path.join(path, "git-daemon-export-ok")) ]).wait()
+
+def run_server(config, localport = 9418):
+ print ("Running git server on %s.onion" % config.get('onion', 'hostname'))
+ try:
+ authkey = config.get('onion', 'clientauth')
+ if authkey:
+ print ("Client auth is %s" % authkey)
+ except (KeyError, cp.NoOptionError) as e:
+ print ("No client auth")
+ print ("Git server local port is %d" % localport)
+ print ("You can now hand out this onion to prospective peers.")
+ print ("It will be re-used anytime Globalist starts in this directory.")
+
+ what = "repo"
+
+ if OPTIONS.o_bare:
+ make_exportable("repo.git")
+ what += ".git"
+ else:
+ make_exportable(os.path.join("repo",".git"))
+
+ gitdaemon = git(["daemon", "--base-path=%s" % os.path.abspath("."),
+ "--reuseaddr", "--verbose",
+ # there could be a global setting enabling write access??
+ "--disable=receive-pack",
+ "--listen=127.0.0.1", "--port=%d" % localport,
+ os.path.abspath(what)])
+ output = gitdaemon.communicate()[0]
+ print (output)
+ # then background this process
+
+def makeonion(controller, config, options):
+ # stem docs say: provide the password here if you set one:
+ controller.authenticate()
+ # todo catch UnreadableCookieFile(
+
+ onion = None
+
+ extra_kwargs = {}
+
+ if config.has_section('onion'):
+ print ("Attempting to use saved onion identity")
+ (keytype,key) = config.get('onion', 'key').split(':',1)
+
+ if options.o_auth:
+ try:
+ print ("Attempting to use saved clientauth")
+ extra_kwargs['basic_auth'] =\
+ dict([config.get('onion', 'clientauth').split(':',1)])
+ except (KeyError, cp.NoOptionError) as e:
+ print ("No client auth present, generating one")
+ extra_kwargs['basic_auth'] = {'somebody': None}
+ else:
+ print ("Not using clientauth.")
+
+ onion = controller.create_ephemeral_hidden_service(**extra_kwargs, ports={9418: options.a_localport}, discard_key=True, await_publication=options.o_ap, key_type=keytype, key_content=key)
+
+ else:
+ print ("I'm afraid we don't have an identity yet, creating one")
+
+ if options.o_auth:
+ extra_kwargs['basic_auth'] = {'somebody': None}
+
+ onion = controller.create_ephemeral_hidden_service(**extra_kwargs, ports={9418: options.a_localport}, discard_key=False, await_publication=options.o_ap)
+
+# print (onion)
+
+ print ("Tor controller says Onion OK")
+
+ if not onion.is_ok():
+ raise Exception('Failed to publish onion.')
+ else:
+ # perhaps avoid overwriting when already present?
+ for line in onion:
+ if line != "OK":
+ k, v = line.split('=', 1)
+ # we only request the key if the service is new
+ if k == "PrivateKey":
+ try:
+ config.add_section('onion')
+ except cp.DuplicateSectionError as e:
+ pass
+ config.set('onion', 'key', v)
+ if k == "ServiceID":
+ try:
+ config.add_section('onion')
+ except cp.DuplicateSectionError as e:
+ pass
+ config.set('onion', 'hostname', v)
+ if k == "ClientAuth":
+ try:
+ config.add_section('onion')
+ except cp.DuplicateSectionError as e:
+ pass
+ config.set('onion', 'clientauth', v)
+ config.write(open('repo.cfg', 'w'))
+
+
+def set_client_authentications(ls):
+ global OPTIONS
+ options = OPTIONS
+
+ controller = Controller.from_port(port = options.a_controlport)
+ controller.authenticate()
+ # is there no sane way to _append_ a multi-config option in Tor????
+ # control protocol badly misdesigned, nobody thought of concurrent access???!?
+ controller.set_caching(False)
+
+# except it doesn't work, the 650 message never arrives. why?
+# controller.add_event_listener(my_confchanged_listener, EventType.CONF_CHANGED)
+# SETEVENTS conf_changed
+
+ hsa = controller.get_conf_map('hidservauth')
+
+ for authpair in ls:
+ if authpair['auth'] and len(authpair['auth']):
+ hsa['hidservauth'].append('%s.onion %s' % (authpair['onion'], authpair['auth']))
+
+ hsa['hidservauth'] = list(set(hsa['hidservauth']))
+
+ controller.set_conf('hidservauth', hsa['hidservauth'])
+ controller.close()
+
+
+def getpeers(config):
+ if STATUS['peers']:
+ return STATUS['peers']
+
+ if config.has_section('network'):
+ peerslist = config.get('network', 'peers').split(',')
+ peers = []
+ authpairs = []
+
+ for peerentry in peerslist:
+
+ # extract what looks like an onion identifier
+ try:
+ authpair = re.findall('(?:(somebody:[A-Za-z0-9+/]{22})@)?([a-z2-8]{16})', peerentry)[0]
+
+ userpass = authpair[0].split(":",1)
+ if not userpass or not len(userpass)==2:
+ userpass = (None, None)
+
+ authpairs += [{'auth':userpass[1],
+ 'user':userpass[0], # somebody
+ 'onion':authpair[1]}]
+ peers += [authpair[1]]
+
+ except Exception as e:
+ print (e)
+
+ set_client_authentications(authpairs)
+
+ STATUS['peers'] = peers
+
+ return peers
+
+ else:
+ STATUS['peers'] = []
+
+ return []
+
+def clone(config):
+ peers = getpeers(config)
+
+ # FIXME: when the first fails, we should move on to the next..
+
+ what = "git://%s.onion/repo" % peers[0]
+ where = "repo"
+ how = []
+
+ if OPTIONS.o_bare:
+ what += ".git"
+ where += ".git"
+ how = ["--bare", "--mirror"]
+
+ cloneproc = subprocess.Popen(["torsocks", "-P", str(STATUS['socksport']), "git", "clone"] + how + [what, where])
+ if cloneproc.wait() != 0:
+ print ("Error cloning, exiting.")
+ return -1
+ else:
+ make_exportable(where)
+
+ # Make a local editable repo
+ try:
+ git(["clone", "repo", "repo.git"]).wait()
+ except:
+ print ("Failed to export repository, try to remove 'repo.git'.")
+
+ processes = []
+ for peer in peers[1:]:
+ processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo"), "pull", "git://%s.onion/repo" % peer])])
+ for (peer,proc) in processes:
+ if proc.wait() != 0:
+ print ("Error with %s" % peer)
+
+def pull(config):
+ peers = getpeers(config)
+
+ print ("Pulling from %s" % peers)
+
+ processes = []
+ for peer in peers:
+ processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo"), "pull", "git://%s.onion/repo" % peer])])
+ for (peer,proc) in processes:
+ if proc.wait() != 0:
+ print ("Error with %s" % peer)
+
+def fetch(config):
+ peers = getpeers(config)
+ print ("Fetching from %s" % peers)
+ processes = []
+ for peer in peers:
+ processes.append([peer, subprocess.Popen(["torsocks", "-P", STATUS['socksport'], "git", "-C", os.path.abspath("repo.git"), "fetch", "git://%s.onion/repo.git" % peer, '+refs/heads/*:refs/remotes/origin/*'])])
+
+ for (peer,proc) in processes:
+ if proc.wait() != 0:
+ print ("Error with %s" % peer)
+
+def init(config):
+ global OPTIONS # not needed for read access btw
+ options = OPTIONS
+
+ print ("Initializing ...")
+
+ if options.o_bare:
+ git(["init", "repo.git", "--bare"]).wait()
+ # Make a local editable repo
+ git(["clone", "repo.git", "repo"]).wait()
+
+ else:
+ git(["init", "repo"]).wait()
+
+ print ("Initialized")
+
+def main(args=[]):
+ # OptionParser is capable of printing a helpscreen
+ opt = op.OptionParser()
+
+ opt.add_option("-V", "--version", dest="o_version", action="store_true",
+ default=False, help="print version number")
+
+ opt.add_option("-i", "--init", dest="o_init", action="store_true",
+ default=False, help="make new empty repo")
+
+ opt.add_option("-b", "--bare", dest="o_bare", action="store_true",
+ default=False, help="use bare repos and fetch, not pull")
+
+ opt.add_option("-c", "--clone", dest="o_clone", action="store_true",
+ default=False, help="clone repo from 1st peer")
+
+ opt.add_option("-p", "--pull", dest="o_pull", action="store_true",
+ default=False, help="pull / fetch from peers and don't serve")
+
+ opt.add_option("-P", "--periodically-pull", dest="a_pull", action="store",
+ type="int", default=None, metavar="PERIOD",
+ help="pull / fetch from peers every n seconds")
+
+ opt.add_option("-L", "--local", dest="a_localport", action="store", type="int",
+ default=9418, metavar="PORT", help="local port for git daemon")
+
+ opt.add_option("-C", "--control-port", dest="a_controlport", action="store", type="int",
+ default=9151, metavar="PORT", help="Tor controlport")
+
+# opt.add_option("-CP", "--control-password", dest="a_controlpassword", action="store", type="int",
+# default="", help="Tor Control Password")
+
+# opt.add_option("-CC", "--control-cookie", dest="a_controlcookie", action="store", type="int",
+# default="", help="Tor Control Cookie")
+
+ opt.add_option("-a", "--await", dest="o_ap", action="store_true",
+ default=False, help="await publication of .onion in DHT before proceeding")
+
+ opt.add_option("-x", "--auth", action="store_true", default=True,
+ dest="o_auth", help="enable authentication (private)")
+
+ opt.add_option("-X", "--no-auth", action="store_false", default=True,
+ dest="o_auth", help="disable authentication (not private)")
+
+ (options, args) = opt.parse_args(args)
+
+ global OPTIONS
+ OPTIONS = options
+
+ if options.o_version:
+ print (__version__)
+ return 0
+
+ if options.o_auth and stem.__version__ < '1.5.0':
+ sys.stderr.write ("stem version >=1.5.0 required for auth\n")
+ return 1
+
+ if not options.a_controlport:
+ options.a_controlport = DEFAULT_CONTROLPORT
+
+ # Extract socksport via c.get_conf and use this (-P in torsocks)
+ # TODO implement authentication token / cookie
+ controller = Controller.from_port(port = options.a_controlport)
+ controller.authenticate()
+ if controller.get_conf('SocksPort'):
+ STATUS['socksport'] = controller.get_conf('SocksPort').split(" ",1)[0]
+ else:
+ STATUS['socksport'] = 9050
+ controller.close()
+
+ config = cp.ConfigParser()
+ cfgfile = None
+ try:
+ cfgfile = open('repo.cfg')
+ except FileNotFoundError as e:
+ print("Trying to make file repo.cfg")
+ try:
+ os.mknod("repo.cfg")
+ os.chmod("repo.cfg", 0o600)
+ cfgfile = open('repo.cfg')
+ except Exception as e:
+ print (e)
+ return 1
+
+ config.readfp(cfgfile)
+
+ try:
+ os.stat("repo.git")
+ if not options.o_bare:
+ print ("repo.git exists, setting -b implicitly")
+ # TODO -B to override
+ options.o_bare = True
+
+ except FileNotFoundError as e:
+ if not options.o_init and not options.o_clone and options.o_bare:
+ print ("./repo.git/ does not exist, try -ib or -cb")
+ return 1
+
+ try:
+ os.stat("repo")
+ except FileNotFoundError as e:
+ if not options.o_init and not options.o_clone and not options.o_bare:
+ print("./repo/ does not exist, try -i or -c")
+ return 1
+
+ except Exception as e:
+ print (e)
+ return 1
+
+ if options.o_init:
+ init(config)
+
+ peers = getpeers(config)
+
+ if options.o_clone:
+ if not len(peers):
+ print ("No peers, can't clone. Please enter a peer in repo.cfg")
+ clone(config)
+ return 1
+
+ threads = []
+
+ if options.a_pull:
+ if not len(peers):
+ print ("No peers, not starting pulling task.")
+
+ else:
+ import threading
+ from datetime import timedelta as td
+ from datetime import datetime
+
+ class T:
+ def __init__(self):
+ self.last = datetime.now()
+
+ def run(self):
+ if options.o_bare:
+ fetch(config)
+ else:
+ pull(config)
+ threading.Timer(options.a_pull, T.run, args=(self,)).start()
+
+ task = T()
+
+ t = threading.Thread(target=T.run, args=(task,))
+ t . setDaemon(True)
+ threads.append(t)
+ t.start()
+
+ # It's either pull(once) or serve. It's no problem running pull from
+ # another console while the server is up. It's no problem specifying
+ # periodic pull with either.
+
+ if options.o_pull and not options.a_pull:
+ if options.o_bare:
+ fetch(config)
+ else:
+ pull(config)
+
+ elif not options.o_pull:
+ controller = Controller.from_port(port = options.a_controlport)
+ makeonion(controller, config, options)
+ run_server(config, localport = options.a_localport)
+ controller.close()
+
+ for t in threads:
+ t.join()
+
+# TODO: should only generate a clientauth on a previously unauthenticated repo if requested by command line option
diff --git a/tool/globalist/globalist/__pycache__/__init__.cpython-36.pyc b/tool/globalist/globalist/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 00000000..fc0520c6
--- /dev/null
+++ b/tool/globalist/globalist/__pycache__/__init__.cpython-36.pyc
Binary files differ
diff --git a/tool/globalist/setup.py b/tool/globalist/setup.py
new file mode 100644
index 00000000..24542977
--- /dev/null
+++ b/tool/globalist/setup.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+
+from distutils.core import setup
+
+setup(
+ name='Globalist',
+ version='0.0.6.2',
+ description='Globalist distributed git onions',
+ author='fnordomat',
+# author_email='',
+ url='https://github.com/fnordomat/Globalist',
+ packages=['globalist'],
+ scripts=['Globalist.py'],
+ install_requires=['stem>=1.5.0'],
+ license='GPLv3'
+)
+
diff --git a/tool/irssi_cf_alturl.pl b/tool/irssi_cf_alturl.pl
new file mode 100644
index 00000000..5e395bad
--- /dev/null
+++ b/tool/irssi_cf_alturl.pl
@@ -0,0 +1,333 @@
+#!/usr/bin/perl -w
+# This Irssi script automatically check incoming http/https links
+# and replace it to archive one if it is MITMed.
+#
+# Irssi /set Options
+# you can view your current settigns by running "/set cflarealt" in Irssi
+#
+# /set cflarealt_debug <on|off> -- (off) if you have a problem try turning this on to debug
+# /set cflarealt_send2channel <on|off> -- (off) send the converted URL publicly to everyone in your channels
+# /set cflarealt_channels <"#channel1, #channel2, etc"> -- Channels to automatically convert. Empty Defaults to all
+#
+# /set cflarealt_shorturl_activate <on|off> -- (off) set it 'on' to use shortner
+# /set cflarealt_shorturl_min <40> -- (40) How long a url has to be to trigger automatic url shortening
+# /set cflarealt_shorturl_useonion <on|off> -- (off) set it 'on' to use .onion
+#
+# /set cflarealt_localdbpath <"string to path"> -- () '/path/database/split/'
+# /set cflarealt_uselocaldb <on|off> -- (off) if 'on', please set path to local database (or the script will die)
+#
+# /set cflarealt_printurl <on|off> -- (off) if 'on' print converted URL
+# /set cflarealt_donotsend <on|off> -- (off) if 'on' do not send converted URL
+#---------------------------------------------------------------------
+
+##use strict;
+
+use vars qw($VERSION %IRSSI);
+
+$VERSION = "20201110";
+%IRSSI = (
+
+ # Special thanks to: "eo, tsaavik"
+ authors => "Anonymous",
+ contact => 'anonymous@stop_cloudflare.nab',
+ name => "irssi_cf_alturl.pl",
+ description => "Cloudflare URL replacer",
+ license => "WTFPL",
+ changed => "$VERSION"
+);
+
+use Irssi;
+use Irssi::Irc;
+use LWP::Simple;
+use LWP::UserAgent;
+
+my (
+ $cfg_minurllen, $cfg_send2chan, $cfg_useshort, $cfg_shortonion,
+ $cfg_isdebug, $cfg_uselocaldb, $cfg_localdbpath, $cfg_chanlist
+);
+my ( $cfg_printurl, $cfg_donotsendurl );
+my @cached = ();
+
+sub setuphandler {
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_send2channel", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_send2channel") ) {
+ print "cflarealt: sending of shorturl's to public channels enabled";
+ $cfg_send2chan = 1;
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_shorturl_activate", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_shorturl_activate") ) {
+ print "cflarealt: URL shortner enabled";
+ $cfg_useshort = 1;
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_shorturl_useonion", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_shorturl_useonion") ) {
+ print "cflarealt: URL onion enabled";
+ $cfg_shortonion = 1;
+ }
+
+ Irssi::settings_add_str( "cflarealt", "cflarealt_channels", "" );
+ $cfg_chanlist = Irssi::settings_get_str("cflarealt_channels");
+ if ($cfg_chanlist) {
+ print "cflarealt: Following channels are now parsed $cfg_chanlist";
+ }
+
+ Irssi::settings_add_int( "cflarealt", "cflarealt_shorturl_min", 40 );
+ my $old_min_url_length = $cfg_minurllen;
+ $cfg_minurllen = Irssi::settings_get_int("cflarealt_shorturl_min");
+ if ( $cfg_minurllen != $old_min_url_length ) {
+ print "cflarealt: min_url_length sucessfully set to $cfg_minurllen";
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_debug", 0 );
+ my $old_debug = $cfg_isdebug;
+ $cfg_isdebug = Irssi::settings_get_bool("cflarealt_debug");
+ if ( $cfg_isdebug != $old_debug ) {
+ if ($cfg_isdebug) {
+ print "cflarealt: Debug Mode Enabled";
+ $cfg_isdebug = 1;
+ }
+ else {
+ print "cflarealt: Debug Mode Disabled";
+ $cfg_isdebug = 0;
+ }
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_uselocaldb", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_uselocaldb") ) {
+ print "cflarealt: Lookup Local DB enabled";
+ $cfg_uselocaldb = 1;
+ }
+
+ Irssi::settings_add_str( "cflarealt", "cflarealt_localdbpath", "" );
+ $cfg_localdbpath = Irssi::settings_get_str("cflarealt_localdbpath");
+ if ($cfg_localdbpath) {
+ print "cflarealt: DB path set to $cfg_localdbpath";
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_printurl", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_printurl") ) {
+ print "cflarealt: print URL enabled";
+ $cfg_printurl = 1;
+ }
+
+ Irssi::settings_add_bool( "cflarealt", "cflarealt_donotsend", 0 );
+ if ( Irssi::settings_get_bool("cflarealt_donotsend") ) {
+ print "cflarealt: dont-send enabled";
+ $cfg_donotsendurl = 1;
+ }
+
+}
+
+sub GotUrl {
+ my ( $server, $data, $nick, $addr, $target ) = @_;
+ if ( !$server || !$server->{connected} ) {
+ Irssi::print("Not connected to server");
+ return;
+ }
+ return unless ( goodchan($target) );
+ $data =~ s/^\s+//;
+ $data =~ s/\s+$//;
+ my @urls = ();
+ my @knownShortFQDN = ( 'tinyurl.com', 'bit.ly' );
+ my ( $url, $a, $return, $char, $ch ) = "";
+ my $same = 0;
+
+ return unless ( ( $data =~ /\bhttp\:/ ) || ( $data =~ /\bhttps\:/ ) );
+ deb("$target triggered GotUrl() with url: $data");
+
+ # split on whitespace and get the url(s) out
+ # done this way in case there are more than
+ # one url per line.
+ foreach ( split( /\s/, $data ) ) {
+ if ( ( $_ =~ /^http\:/ ) || ( $_ =~ /^https\:/ ) ) {
+ foreach $a (@urls) {
+ if ( $_ eq $a ) {
+
+ # incase they use the same url on the line.
+ $same = 1;
+ next;
+ }
+ }
+ if ( $same == 0 ) {
+ $same = 0;
+ push( @urls, $_ );
+ }
+ }
+ }
+
+ my ( $myurl, $fqdn, $junk, $mytype );
+ my ( $url, $browser, $response, $answer );
+ my ( $line, $ifoundit );
+
+ foreach (@urls) {
+ $myurl = $_;
+ ( $junk, $fqdn ) = split( /\/\//, $myurl, 2 );
+ ( $fqdn, $junk ) = split( /\//, $fqdn, 2 );
+ $mytype = '';
+
+ if ( length($fqdn) >= 4 ) {
+## Start of Act
+
+## ACT0. If ShortURL, expand it. (knownShortFQDN)
+ if ( grep( /^$fqdn$/, @knownShortFQDN ) ) {
+ deb("$target Expand $fqdn");
+ $browser = LWP::UserAgent->new;
+ $answer = HTTP::Request->new( GET => $myurl );
+ $response = $browser->request($answer);
+
+ if ( $response->is_success and $response->previous ) {
+ if ( $myurl ne $response->request->uri ) {
+ $junk = $response->request->uri;
+ if ( index( $junk, 'http' ) == 0 ) {
+ deb("$target Expanded $fqdn");
+ $myurl = $junk;
+ ( $junk, $fqdn ) = split( /\/\//, $myurl, 2 );
+ ( $fqdn, $junk ) = split( /\//, $fqdn, 2 );
+ }
+ }
+ }
+
+ }
+
+## ACT1: Update URL if Cloudflared
+ if ( grep( /^$fqdn$/, @cached ) ) {
+ deb("$target Found in Cache $fqdn");
+ $mytype = '^B^C3[Archive]^O ';
+ $myurl = 'https://web.archive.org/web/' . $myurl;
+ }
+ else {
+ if ( $cfg_uselocaldb == 1 ) {
+ deb("$target Lookup local DB about $fqdn");
+ open( CFSFILE,
+ $cfg_localdbpath
+ . "cloudflare_"
+ . substr( $fqdn, 0, 1 )
+ . ".txt" )
+ or die "file not found for $fqdn";
+ $ifoundit = 0;
+ while (<CFSFILE>) {
+ $line = $_;
+ $line =~ s/\R//g;
+ if ( $line eq $fqdn ) {
+ $ifoundit = 1;
+ last;
+ }
+ }
+ close CFSFILE;
+
+ if ( $ifoundit == 1 ) {
+ push( @cached, $fqdn );
+ $mytype = '^B^C3[Archive]^O ';
+ $myurl = 'https://web.archive.org/web/' . $myurl;
+ }
+ }
+ else {
+ deb("$target Asking API about $fqdn");
+ $answer = '';
+ $url =
+ 'https://api.nnpaefp7pkadbxxkhz2agtbv2a4g5sgo2fbmv3i7czaua354334uqqad.onion/_/ismitm.php?f='
+ . $fqdn;
+ $browser = LWP::UserAgent->new;
+ $browser->agent("Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0");
+ $response = $browser->get($url);
+ $answer = $response->content;
+ if ( $answer eq '[true,true]' ) {
+ push( @cached, $fqdn );
+ $mytype = '^B^C3[Archive]^O ';
+ $myurl = 'https://web.archive.org/web/' . $myurl;
+ }
+ }
+ }
+
+## ACT2: Short URL __if__ enabled and long
+ if ( $cfg_useshort == 1 ) {
+ if ( length($myurl) > $cfg_minurllen ) {
+ if ( $cfg_shortonion == 1 ) {
+ deb("$target Creating Short Onion for $myurl");
+ $url = 'http://hbfkuwcbzhcht33fetbiajuh7i6gqupgnyupxcmujiky34drzmpajrid.onion/?i=new&url=' . $myurl;
+ $browser = LWP::UserAgent->new;
+ $browser->agent("Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0");
+ $response = $browser->get($url);
+ $answer = $response->content;
+ if (
+ index( $answer,
+ 'http://hbfkuwcbzhcht33fetbiajuh7i6gqupgnyupxcmujiky34drzmpajrid.onion/?' ) == 0
+ )
+ {
+ if ( $mytype eq '' ) {
+ $mytype = '^B^C7[Onion]^O ';
+ }
+ else {
+ $mytype = '^B^C2[Onion,Archive]^O ';
+ }
+ $myurl = $answer;
+ }
+ }
+ else {
+ deb("$target Creating Short URL for $myurl");
+ $url =
+ 'https://ux.nu/api/short?format=plain&url=' . $myurl;
+ $browser = LWP::UserAgent->new;
+ $browser->agent("Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0");
+ $response = $browser->get($url);
+ $answer = $response->content;
+ if ( index( $answer, 'https://ux.nu/' ) == 0 ) {
+ if ( $mytype eq '' ) {
+ $mytype = '^B^C7[Short]^O ';
+ }
+ else {
+ $mytype = '^B^C2[Short,Archive]^O ';
+ }
+ $myurl = $answer;
+ }
+ }
+ }
+ }
+
+##ACT3: Result
+ if ( $cfg_printurl == 1 ) {
+ Irssi::print("URL: $mytype$myurl");
+ }
+
+ if ( $cfg_donotsendurl != 1 ) {
+ if ( $cfg_send2chan == 1 ) {
+ $server->command("msg $target $myurl");
+ }
+ else {
+ $server->print( "$target", "$mytype$myurl",
+ MSGLEVEL_CLIENTCRAP );
+ }
+ }
+
+## End of Act
+ }
+ deb("$target process done for input $myurl");
+ }
+
+## Cleanup cache
+ if ( $#cached > 500 ) {
+ @cached = ();
+ }
+
+ return;
+}
+
+sub deb($) {
+ Irssi::print(shift) if ( $cfg_isdebug == 1 );
+}
+
+sub goodchan {
+ my $chan = shift;
+ return ("OK") if ( !$cfg_chanlist );
+ foreach ( split( /\,/, $cfg_chanlist ) ) {
+ return ("$_") if ( $_ =~ /$chan/i );
+ }
+ return undef;
+}
+
+setuphandler();
+Irssi::signal_add( "setup changed", "setuphandler" );
+Irssi::signal_add_last( "message public", "GotUrl" );
+Irssi::signal_add_last( "ctcp action", "GotUrl" );
diff --git a/tool/mastodonwch/README.md b/tool/mastodonwch/README.md
new file mode 100644
index 00000000..0740a887
--- /dev/null
+++ b/tool/mastodonwch/README.md
@@ -0,0 +1,105 @@
+### Shared on Mastodon
+
+Who is sharing most Cloudflare links to other people?
+Oh, it's you?
+
+Congratulations, you are ranked in the top 50 of...
+
+![](https://codeberg.org/crimeflare/stop_cloudflare/raw/branch/master/image/clapclapclap.gif)
+
+(_Sarcasm? Of course. Stop sharing CF links already!_)
+
+What you can do? Tell them to stop using Cloudflare!
+
+
+### Some public reaction
+
+```
+'your little protest bot is making people upset,
+and i'm worried it might cause people to defederate from the instance,
+please tone it down'
+
+We need a new word for describing how fucked up this is,
+insanity just isn't doing it anymore.
+
+No shit it makes people upset *that's the fucking point*
+```
+-- [Jeff Cliff](http://qhtn4w2q36dojls2.onion/)
+
+
+```
+The bot doesn't care about you personally.
+It's doing a public service for your readers, who you otherwise sent into
+a netneutrality-hostile privacy-abusive walled-garden.
+
+It's to protect them so they are warned and have a trustworthy link to follow.
+```
+-- [resist1984](https://social.privacytools.io/@resist1984)
+
+
+```
+It doesn't matter if it is still online if I can't access it...
+so the bot is appreciated.
+```
+-- [Thufie](https://social.pixie.town/@thufie)
+
+
+### Code
+
+- [index.php](index.php): Main website to show the result
+- [cron.php](cron.php): Use it with your cronjob
+
+
+### Database
+
+- toot_cfsaid: Tweet URL which CF link was observed
+```
+id varchar(40) UNIQUE
+who varchar(80) INDEX
+url varchar(200) INDEX
+ym int(6) INDEX
+```
+
+- toot_notcf_fqdn: Unknown FQDN which was not listed as Cloudflare
+```
+fqdn varchar(200) UNIQUE
+dl int(1) INDEX
+```
+
+- toot_scanned: To make sure not to analyze same tweet again
+```
+id varchar(40) UNIQUE
+who varchar(80) INDEX
+iscf int(1) INDEX
+ym int(6) INDEX
+```
+
+- toot_sharefqdn: Just for counting FQDN
+```
+id varchar(40) UNIQUE
+fqdn varchar(200) INDEX
+ym int(6) INDEX
+```
+
+
+### Live demo
+
+If you want to see this in action: [Onion](http://stopcloudflare@avrynpc2q7pknqa3ucf5tvjcwad5nxvxgwnzvl2b6dx6uo4f7nc7zzqd.onion/mastodon/)
+
+
+
+### Live API
+
+`http://(onion)/mastodon/?who=a&json`
+
+`who`:
+- `a`, Show Top 100 (Shared any links)
+- `c`, Show Top 100 (Shared Cloudflare links)
+- `f`, Show Top 100 (Shared FQDN)
+- `u`, About Mastodon Users
+- `s`, About Mastodon Servers
+
+Example:
+`curl -x socks5h://127.0.0.1:9050 -G -H "Authorization: Basic c3RvcGNsb3VkZmxhcmU6" "http://-----.onion/mastodon/" -d "who=c" -d "json"`
+
+> `[{"rank":1,"who":"xxx","toots":"xxx"},{"rank":2,...` \ No newline at end of file
diff --git a/tool/mastodonwch/cron.php b/tool/mastodonwch/cron.php
new file mode 100644
index 00000000..b6a26f67
--- /dev/null
+++ b/tool/mastodonwch/cron.php
@@ -0,0 +1,80 @@
+<?php
+ignore_user_abort(true);
+set_time_limit(120);
+$toots = [];
+$mastodon_servers = ['your.server.fqdn' => 'https://your.server.fqdn/write_this_block_yourself', 'yours2.fqdn' => 'https://...'];
+shuffle_assoc($mastodon_servers);
+$date_ym = gmdate('Ym');
+$sqlme = @new mysqli('localhost', 'sseeccrreett', 'sseeccrreett', 'sseeccrreett');
+if ($sqlme->connect_errno)
+{
+ exit;
+}
+foreach ($mastodon_servers as $svF => $svU)
+{
+ $got = @json_decode(wget($svU) , true);
+ if (!is_array($got) || count($got) < 10 || !isset($got[0]))
+ {
+ continue;
+ }
+ foreach ($got as $g)
+ {
+ if (!isset($g['url']) || !preg_match("/write_this_block_yourself/", $g['url']))
+ {
+ continue;
+ }
+ if (!isset($g['content']) || strlen($g['content']) < 8)
+ {
+ continue;
+ }
+ if (!isset($g['account']) || !isset($g['account']['acct']) || !preg_match("/write_this_block_yourself/", $g['account']['acct']))
+ {
+ continue;
+ }
+ if (strpos($g['account']['acct'], '@') === false)
+ {
+ $g['account']['acct'] = $g['account']['acct'] . '@' . $svF;
+ }
+ $txt = array_unique(array_filter(array_map(function ($l)
+ {
+ $isURL = (preg_match("/write_this_block_yourself/", $l) && !preg_match("/write_this_block_yourself/", $l)) ? true : false;
+ return $isURL ? explode('/', explode('://', $l, 2) [1], 2) [0] : '';
+ }
+ , explode('"', strip_tags($g['content'], '<a>')))));
+ if (count($txt) == 0)
+ {
+ continue;
+ }
+ $cfFound = 0;
+ foreach ($txt as $fqdn)
+ {
+ $tmp_id2 = sha1($g['url'] . ';' . $fqdn);
+ $junk = $sqlme->query("INSERT IGNORE INTO toot_sharefqdn (id,fqdn,ym) VALUES ('{$tmp_id2}','{$fqdn}','{$date_ym}');");
+ if (is_known_cf(get_domainname($fqdn) [1]))
+ {
+ $cfFound = 1;
+ }
+ else
+ {
+ $junk = $sqlme->query("INSERT IGNORE INTO toot_notcf_fqdn (fqdn,dl) VALUES ('{$fqdn}','0');");
+ }
+ }
+ $toots[sha1($g['url']) ] = [$g['account']['acct'], $g['url'], $cfFound];
+ }
+}
+foreach ($toots as $k => $v)
+{
+ $junk = $sqlme->query("INSERT IGNORE INTO toot_scanned (id,who,iscf,ym) VALUES ('{$k}','{$v[0]}','{$v[2]}','{$date_ym}');");
+ if ($v[2] == 1)
+ {
+ $junk = $sqlme->query("INSERT IGNORE INTO toot_cfsaid (id,who,url,ym) VALUES ('{$k}','{$v[0]}','{$v[1]}','{$date_ym}');");
+ }
+}
+if (gmdate('G') == 0)
+{
+ $date_ym = gmdate('Ym', strtotime('7 months ago'));
+ $junk = $sqlme->query("DELETE FROM toot_scanned WHERE ym = '{$date_ym}';");
+ $junk = $sqlme->query("DELETE FROM toot_cfsaid WHERE ym = '{$date_ym}';");
+ $junk = $sqlme->query("DELETE FROM toot_sharefqdn WHERE ym = '{$date_ym}';");
+}
+file_put_contents('/onion/sseeccrreett/data/mastodon.toot.scan', ''); \ No newline at end of file
diff --git a/tool/mastodonwch/index.php b/tool/mastodonwch/index.php
new file mode 100644
index 00000000..39503053
--- /dev/null
+++ b/tool/mastodonwch/index.php
@@ -0,0 +1,136 @@
+<?php
+$sqlme = @new mysqli('localhost', 'sseeccrreett', 'sseeccrreett', 'sseeccrreett');
+if ($sqlme->connect_errno)
+{
+ exit;
+}
+$count_all = - 1;
+if ($r = $sqlme->query("SELECT count(id) FROM toot_scanned;"))
+{
+ $rr = mysqli_fetch_assoc($r);
+ $r->free();
+ $count_all = $rr['count(id)'];
+}
+$count_cf = - 1;
+if ($r = $sqlme->query("SELECT count(id) FROM toot_scanned WHERE iscf=1;"))
+{
+ $rr = mysqli_fetch_assoc($r);
+ $r->free();
+ $count_cf = $rr['count(id)'];
+}
+if ($count_all == - 1 || $count_cf == - 1)
+{
+ exit;
+}
+$count_acpct = round(($count_cf * 100) / $count_all, 2);
+$count_all = number_format($count_all);
+$count_cf = number_format($count_cf);
+$count_waitask = - 1;
+if ($r = $sqlme->query("SELECT count(fqdn) FROM toot_notcf_fqdn WHERE dl=0;"))
+{
+ $rr = mysqli_fetch_assoc($r);
+ $r->free();
+ $count_waitask = $rr['count(fqdn)'];
+}
+$count_waitask = number_format($count_waitask);
+$lastuptime = humanTiming(filemtime('/onion/sseeccrreett/data/mastodon.toot.scan'));
+$var_who = htmlspecialchars($_GET['who'], ENT_QUOTES);
+$resultHTML = '';
+if (write_this_block_yourself)
+{
+ $var_who = '';
+}
+if (preg_match("/write_this_block_yourself/", $var_who))
+{
+ $resultHTML .= '<br><table border=1>';
+ $person_a = 0;
+ if ($r = $sqlme->query("SELECT count(id) FROM toot_scanned WHERE who='{$var_who}';"))
+ {
+ $rr = mysqli_fetch_assoc($r);
+ $r->free();
+ $person_a = $rr['count(id)'];
+ }
+ $person_b = 0;
+ if ($r = $sqlme->query("SELECT count(id) FROM toot_scanned WHERE who='{$var_who}' AND iscf=1;"))
+ {
+ $rr = mysqli_fetch_assoc($r);
+ $r->free();
+ $person_b = $rr['count(id)'];
+ }
+ $person_c = round(($person_b * 100) / $person_a, 2);
+ if (is_nan($person_c))
+ {
+ $person_c = 0;
+ }
+ $person_a = number_format($person_a);
+ $person_b = number_format($person_b);
+ $resultHTML .= "<tr><th>About</th><th>{$var_who}</th></tr>";
+ $resultHTML .= "<tr><td>Toots has links</td><td>{$person_a}</td></tr>";
+ $resultHTML .= "<tr><td>&#11169; Toots has Cloudflare link</td><td>{$person_b} [<i>{$person_c}</i>%]</td></tr>";
+ $resultHTML .= "<tr><td> </td><td>Detected (LIMIT 100)</td></tr>";
+ if ($r = $sqlme->query("SELECT url FROM toot_cfsaid WHERE who='{$var_who}' LIMIT 100;"))
+ {
+ while ($row = mysqli_fetch_assoc($r))
+ {
+ $vurl = htmlspecialchars($row['url'], ENT_QUOTES);
+ $resultHTML .= "<tr><td></td><td><a href=\"{$vurl}\" target=\"_blank\">{$vurl}</a></td></tr>";
+ }
+ }
+ $resultHTML .= '</table>';
+}
+elseif ($var_who == 'a')
+{
+ $resultHTML .= '<br><table border=1><tr><th>Shared any links</th><th>Toots</th></tr>';
+ if ($r = $sqlme->query("SELECT who,count(*) as count FROM toot_scanned GROUP BY who ORDER BY count DESC LIMIT 100;"))
+ {
+ while ($row = mysqli_fetch_assoc($r))
+ {
+ $rcount = number_format($row['count']);
+ $resultHTML .= "<tr><td><a href=\"./?who={$row['who']}\">{$row['who']}</a></td><td>{$rcount}</td></tr>";
+ }
+ }
+ $resultHTML .= '</table>';
+}
+elseif ($var_who == 'c')
+{
+ $resultHTML .= '<br><table border=1><tr><th>Shared Cloudflare links</th><th>Toots</th></tr>';
+ if ($r = $sqlme->query("SELECT who,count(*) as count FROM toot_scanned WHERE iscf=1 GROUP BY who ORDER BY count DESC LIMIT 100;"))
+ {
+ while ($row = mysqli_fetch_assoc($r))
+ {
+ $rcount = number_format($row['count']);
+ $resultHTML .= "<tr><td><a href=\"./?who={$row['who']}\">{$row['who']}</a></td><td>{$rcount}</td></tr>";
+ }
+ }
+ $resultHTML .= '</table>';
+}
+elseif ($var_who == 'f')
+{
+ $resultHTML .= '<br><table border=1><tr><th>Shared FQDN</th><th>Cloudflare</th><th>Found in Toots</th></tr>';
+ if ($r = $sqlme->query("SELECT fqdn,count(*) as count FROM toot_sharefqdn GROUP BY fqdn ORDER BY count DESC LIMIT 50;"))
+ {
+ while ($row = mysqli_fetch_assoc($r))
+ {
+ $rcount = number_format($row['count']);
+ $riscf = is_known_cf(get_domainname($row['fqdn']) [1]) ? '<font color="red">Yes</font>' : 'No';
+ $resultHTML .= "<tr><td>{$row['fqdn']}</td><td>{$riscf}</td><td>{$rcount}</td></tr>";
+ }
+ }
+ $resultHTML .= '</table>';
+}
+else
+{
+ $resultHTML .= '<ul><li> <a href="./?who=a">Show Top 100 (Shared any links)</a></li>';
+ $resultHTML .= '<li> <a href="./?who=c">Show Top 100 (Shared Cloudflare links)</a></li>';
+ $resultHTML .= '<li> <a href="./?who=f">Show Top 50 (Shared FQDN)</a></li>';
+ $resultHTML .= '<li> <a href="#" onclick="location.href=\'./?who=\'+(prompt(\'user@example.com\')||\'\');">Search by Mastodon ID</a></li></ul>';
+}
+$finalpage = <<<HTMLRESULT
+<html><title>Shared on Mastodon</title><br><br><div>
+[ <a href="./">INDEX</a> ] Update <i>{$lastuptime} ago</i><br>
+<b>Toots has links</b>: {$count_all}<br>
+&#11169; <b>Toots has Cloudflare link</b>: {$count_cf} [<i>{$count_acpct}</i>%]<br>
+<b>Pending Investigation</b>: {$count_waitask}<br>
+<hr>{$resultHTML}</div></html>
+HTMLRESULT;
+echo (str_replace("\n", '', $finalpage)); \ No newline at end of file