├── .gitignore ├── README.md ├── dist ├── index.d.ts ├── index.js ├── turnt.d.ts └── turnt.js ├── golang ├── build-linux.bat ├── build-macos.bat ├── build-windows.bat ├── common.go ├── go.mod ├── go.sum ├── goturnt.dll ├── goturnt.dylib ├── goturnt.h ├── goturnt.so └── main.go ├── package-lock.json ├── package.json ├── src ├── index.ts └── turnt.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # vuepress v2.x temp and cache directory 103 | .temp 104 | .cache 105 | 106 | # Docusaurus cache and generated files 107 | .docusaurus 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | # Stores VSCode versions used for testing VSCode extensions 122 | .vscode-test 123 | .vscode 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .idea 132 | idea/* 133 | 134 | test.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # turnt-tls 2 | 3 | This is an advanced NodeJS implementation of Golang's utls repository with session support provided through [tough-cookie](https://www.npmjs.com/package/tough-cookie). It's syntax is similar to that of npm's popular (deprecated) request package. Additionally, it supports pseudo-header order, gzip, deflate, and brotli. 4 | 5 | ## Installation 6 | ``` 7 | npm i turnt-tls 8 | ``` 9 | 10 | ## Setup 11 | 1. Clone the repository 12 | 2. Install the necessary dependencies with `npm install` 13 | 3. Run and build the test case `npm run start` 14 | 15 | ## What is TLS/SSL and why is it important? 16 | Please read the following writeups for a better understanding of TLS/SSL Handshakes and why JA3 can fingerprint them. 17 | 18 | 1. [Salesforce Article](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/) 19 | 2. [Darktrace Article](https://darktrace.com/blog/beyond-the-hash-how-unsupervised-machine-learning-unlocks-the-true-power-of-ja3) 20 | 3. [Medium Article](https://infosecwriteups.com/demystifying-ja3-one-handshake-at-a-time-c80b04ccb393) 21 | 22 | ## Support 23 | Currently, the turnt-tls client supports the following methods: `GET`, `POST`, `FORM`, `PUT`, and `PUTFORM`. 24 | 25 | **Note: FORM is essentially a request with query parameters. We just decided this warranted a separate method.** 26 | 27 | ## Use Case 28 | ``` 29 | var turnt = require("turnt-tls"); 30 | var jar = require("tough-cookie"); 31 | var cookieJar = new jar.CookieJar(); 32 | 33 | async function checkTLS() { 34 | let options = { 35 | method: 'GET', 36 | cookieJar: cookieJar, 37 | headers: { 38 | 'Connection': 'keep-alive', 39 | 'Pragma': 'no-cache', 40 | 'Cache-Control': 'no-cache', 41 | 'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"', 42 | 'sec-ch-ua-mobile': '?0', 43 | 'DNT': '1', 44 | 'Upgrade-Insecure-Requests': '1', 45 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', 46 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 47 | 'Sec-Fetch-Site': 'none', 48 | 'Sec-Fetch-Mode': 'navigate', 49 | 'Sec-Fetch-User': '?1', 50 | 'Sec-Fetch-Dest': 'document', 51 | 'Accept-Language': 'en-US,en;q=0.9' 52 | }, 53 | proxy: "user:pass@host:port", 54 | } 55 | 56 | let GetResp = await turnt.turnt("https://incolumitas.com/pages/TLS-Fingerprint/", options) 57 | 58 | console.log(GetResp.status) 59 | } 60 | checkTLS(); 61 | ``` 62 | 63 | ## Notes 64 | Much of the syntax is similar to the popular but deprecated [requests](https://www.npmjs.com/package/request) package. 65 | 66 | I have also taken the liberty to provide pre-made binaries that will run on MacOS, Linux, and Windows, however you are free to build your own. You can do so by running `./golang/build.bat`. (Windows only) 67 | 68 | Anyone is welcome to contribute to this module to implement any more http methods or functionality. Just make a pull request and I will review it :) 69 | 70 | ## Reflections 71 | I would like to give an enormous shoutout to [Will](https://github.com/missingsemi) for his contributions to this repository. Please go check out his work! 72 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __generator = (this && this.__generator) || function (thisArg, body) { 12 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 13 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 14 | function verb(n) { return function (v) { return step([n, v]); }; } 15 | function step(op) { 16 | if (f) throw new TypeError("Generator is already executing."); 17 | while (_) try { 18 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 19 | if (y = 0, t) op = [op[0] & 2, t.value]; 20 | switch (op[0]) { 21 | case 0: case 1: t = op; break; 22 | case 4: _.label++; return { value: op[1], done: false }; 23 | case 5: _.label++; y = op[1]; op = [0]; continue; 24 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 25 | default: 26 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 27 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 28 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 29 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 30 | if (t[2]) _.ops.pop(); 31 | _.trys.pop(); continue; 32 | } 33 | op = body.call(thisArg, _); 34 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 35 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 36 | } 37 | }; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | var turnt_1 = require("./turnt"); 40 | var tough_cookie_1 = require("tough-cookie"); 41 | var cookieJar = new tough_cookie_1.CookieJar(); 42 | // testTLS(); 43 | // testPeetTLS(); 44 | testZalando(); 45 | function testTLS() { 46 | return __awaiter(this, void 0, void 0, function () { 47 | var options, GetResp; 48 | return __generator(this, function (_a) { 49 | switch (_a.label) { 50 | case 0: 51 | options = { 52 | method: 'GET', 53 | cookieJar: cookieJar, 54 | headers: { 55 | 'Connection': 'keep-alive', 56 | 'Pragma': 'no-cache', 57 | 'Cache-Control': 'no-cache', 58 | 'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"', 59 | 'sec-ch-ua-mobile': '?0', 60 | 'DNT': '1', 61 | 'Upgrade-Insecure-Requests': '1', 62 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 63 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 64 | 'Sec-Fetch-Site': 'none', 65 | 'Sec-Fetch-Mode': 'navigate', 66 | 'Sec-Fetch-User': '?1', 67 | 'Sec-Fetch-Dest': 'document', 68 | 'Accept-Language': 'en-US,en;q=0.9' 69 | }, 70 | }; 71 | return [4 /*yield*/, (0, turnt_1.turnt)("https://incolumitas.com/pages/TLS-Fingerprint/", options) 72 | // console.log(GetResp.body); 73 | ]; 74 | case 1: 75 | GetResp = _a.sent(); 76 | // console.log(GetResp.body); 77 | console.log(GetResp.status); 78 | return [2 /*return*/]; 79 | } 80 | }); 81 | }); 82 | } 83 | function testPeetTLS() { 84 | return __awaiter(this, void 0, void 0, function () { 85 | var options, response; 86 | return __generator(this, function (_a) { 87 | switch (_a.label) { 88 | case 0: 89 | options = { 90 | method: 'GET', 91 | cookieJar: cookieJar, 92 | headers: { 93 | // 'authority': 'tls.peet.ws', 94 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 95 | 'accept-language': 'en-US,en;q=0.9', 96 | 'cache-control': 'max-age=0', 97 | 'dnt': '1', 98 | 'sec-ch-ua': '"Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"', 99 | 'sec-ch-ua-mobile': '?0', 100 | 'sec-ch-ua-platform': '"macOS"', 101 | 'sec-fetch-dest': 'document', 102 | 'sec-fetch-mode': 'navigate', 103 | 'sec-fetch-site': 'none', 104 | 'sec-fetch-user': '?1', 105 | 'upgrade-insecure-requests': '1', 106 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', 107 | } 108 | }; 109 | return [4 /*yield*/, (0, turnt_1.turnt)("https://tls.peet.ws/api/all", options)]; 110 | case 1: 111 | response = _a.sent(); 112 | console.log(response.body); 113 | return [2 /*return*/]; 114 | } 115 | }); 116 | }); 117 | } 118 | function testZalando() { 119 | return __awaiter(this, void 0, void 0, function () { 120 | var options, zalandoResponse; 121 | return __generator(this, function (_a) { 122 | switch (_a.label) { 123 | case 0: 124 | options = { 125 | method: "GET", 126 | cookieJar: cookieJar, 127 | headers: { 128 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 129 | 'accept-encoding': 'gzip, deflate, br', 130 | 'accept-language': 'en-US,en;q=0.9', 131 | 'cache-control': 'no-cache', 132 | 'dnt': '1', 133 | 'pragma': 'no-cache', 134 | 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"', 135 | 'sec-ch-ua-mobile': '?0', 136 | 'sec-ch-ua-platform': '"Windows"', 137 | 'sec-fetch-dest': 'document', 138 | 'sec-fetch-mode': 'navigate', 139 | 'sec-fetch-site': 'none', 140 | 'sec-fetch-user': '?1', 141 | 'upgrade-insecure-requests': '1', 142 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', 143 | }, 144 | }; 145 | return [4 /*yield*/, (0, turnt_1.turnt)("https://en.zalando.de/?_rfl=de", options)]; 146 | case 1: 147 | zalandoResponse = _a.sent(); 148 | console.log(zalandoResponse.body.toString("utf8")); 149 | console.log(zalandoResponse.status); 150 | return [2 /*return*/]; 151 | } 152 | }); 153 | }); 154 | } 155 | -------------------------------------------------------------------------------- /dist/turnt.d.ts: -------------------------------------------------------------------------------- 1 | export declare function turnt(url: string, options: any): any; 2 | -------------------------------------------------------------------------------- /dist/turnt.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 26 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 27 | return new (P || (P = Promise))(function (resolve, reject) { 28 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 29 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 30 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 31 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 32 | }); 33 | }; 34 | var __generator = (this && this.__generator) || function (thisArg, body) { 35 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 36 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 37 | function verb(n) { return function (v) { return step([n, v]); }; } 38 | function step(op) { 39 | if (f) throw new TypeError("Generator is already executing."); 40 | while (_) try { 41 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 42 | if (y = 0, t) op = [op[0] & 2, t.value]; 43 | switch (op[0]) { 44 | case 0: case 1: t = op; break; 45 | case 4: _.label++; return { value: op[1], done: false }; 46 | case 5: _.label++; y = op[1]; op = [0]; continue; 47 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 48 | default: 49 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 50 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 51 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 52 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 53 | if (t[2]) _.ops.pop(); 54 | _.trys.pop(); continue; 55 | } 56 | op = body.call(thisArg, _); 57 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 58 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 59 | } 60 | }; 61 | var __importDefault = (this && this.__importDefault) || function (mod) { 62 | return (mod && mod.__esModule) ? mod : { "default": mod }; 63 | }; 64 | Object.defineProperty(exports, "__esModule", { value: true }); 65 | exports.turnt = void 0; 66 | var ffi = __importStar(require("ffi-napi")); 67 | var path_1 = __importDefault(require("path")); 68 | function turnt(url, options) { 69 | var _this = this; 70 | return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { 71 | var DOptions, validMethods, GoRequests, headers, cookies, body, res, result, header; 72 | return __generator(this, function (_a) { 73 | switch (_a.label) { 74 | case 0: 75 | DOptions = { 76 | body: '', 77 | cookies: {}, 78 | cookieJar: null, 79 | headers: {}, 80 | method: 'GET', 81 | proxy: null, 82 | timeout: 0, 83 | }; 84 | Object.assign(DOptions, options); 85 | options = DOptions; 86 | // Validation 87 | options["method"] = options["method"].toUpperCase(); 88 | validMethods = ["GET", "POST", "FORM", "PUT", "PUTFORM"]; 89 | if (!validMethods.includes(options["method"])) 90 | reject(new Error("Method must be either GET, POST, FORM, PUT, or PUTFORM.")); 91 | if (Number.isNaN(options["timeout"])) 92 | reject(new Error("Timeout must be a number.")); 93 | if (typeof options["headers"] !== "object") 94 | reject(new Error("Headers must be an object.")); 95 | if (CountAttributes(options["cookies"]) != 0 && options["cookieJar"] != null) 96 | reject(new Error("Only cookies or cookieJar may be set.")); 97 | if (typeof options["cookies"] !== "object") 98 | reject(new Error("Cookies must be an object.")); 99 | if (options["cookieJar"] !== null && typeof options["cookieJar"] !== "object") 100 | reject(new Error("cookiesJar must be a tough-cookie CookieJar.")); 101 | if (options["body"] == null) 102 | options["body"] = ''; 103 | if (options["method"] == "GET" && options["body"] != '') 104 | reject(new Error("Get requests can not have bodies.")); 105 | if (options["proxy"] == null) 106 | options["proxy"] = ""; 107 | GoRequests = ffi.Library(path_1.default.join(__dirname, '../golang/goturnt'), { 108 | 'CreateRequest': ['string', ['string', 'string', 'string', 'string', 'string', 'string']] 109 | }); 110 | headers = options["headers"]; 111 | body = options["body"]; 112 | if (!(options.cookieJar != null && options.cookie == undefined)) return [3 /*break*/, 2]; 113 | return [4 /*yield*/, convertCookies(url, options.cookieJar)]; 114 | case 1: 115 | cookies = _a.sent(); 116 | return [3 /*break*/, 3]; 117 | case 2: 118 | cookies = options["cookies"]; 119 | _a.label = 3; 120 | case 3: 121 | // There's no point in spoofing an ssl signature if ssl isn't used. 122 | if (!url.startsWith("https")) 123 | throw new Error('Only https is supported.'); 124 | res = ""; 125 | if (Buffer.isBuffer(body)) 126 | body = body.toString(); 127 | if (typeof body === 'object') 128 | body = FormEncode(body); 129 | // console.log(url) 130 | // console.log(JSON.stringify(headers)) 131 | // console.log(JSON.stringify(cookies)) 132 | // console.log(body) 133 | // console.log(options["method"]) 134 | // console.log(options["proxy"]) 135 | res = GoRequests.CreateRequest(url, JSON.stringify(headers), JSON.stringify(cookies), body, options["method"], options["proxy"]); 136 | result = JSON.parse(res); 137 | for (header in result["headers"]) { 138 | result["headers"][header] = result["headers"][header][0]; 139 | } 140 | if (options.cookieJar != null && options.cookie == undefined) 141 | setCookies(result.cookies, options.cookieJar, url); 142 | resolve(result); 143 | return [2 /*return*/]; 144 | } 145 | }); 146 | }); }); 147 | } 148 | exports.turnt = turnt; 149 | function FormEncode(body) { 150 | var res = ""; 151 | for (var key in body) { 152 | if (Object.prototype.hasOwnProperty.call(body, key)) { 153 | res += encodeURIComponent(key) + "=" + encodeURIComponent(body[key].toString()) + "&"; 154 | } 155 | } 156 | return res.slice(0, -1); 157 | } 158 | function CountAttributes(obj) { 159 | var count = 0; 160 | for (var k in obj) 161 | if (obj.hasOwnProperty(k)) 162 | count++; 163 | return count; 164 | } 165 | function convertCookies(url, cookieJar) { 166 | return __awaiter(this, void 0, void 0, function () { 167 | var cookieArray; 168 | var _this = this; 169 | return __generator(this, function (_a) { 170 | switch (_a.label) { 171 | case 0: 172 | cookieArray = {}; 173 | return [4 /*yield*/, cookieJar.getCookiesSync(url).forEach(function (cookie) { return __awaiter(_this, void 0, void 0, function () { 174 | var _a, _b; 175 | return __generator(this, function (_c) { 176 | switch (_c.label) { 177 | case 0: 178 | _a = cookieArray; 179 | _b = cookie.key; 180 | return [4 /*yield*/, cookie.value]; 181 | case 1: 182 | _a[_b] = _c.sent(); 183 | return [2 /*return*/]; 184 | } 185 | }); 186 | }); })]; 187 | case 1: 188 | _a.sent(); 189 | return [2 /*return*/, cookieArray]; 190 | } 191 | }); 192 | }); 193 | } 194 | function setCookies(cookies, cookieJar, url) { 195 | return __awaiter(this, void 0, void 0, function () { 196 | return __generator(this, function (_a) { 197 | if (cookies == null) 198 | return [2 /*return*/]; 199 | cookies.forEach(function (cookie) { 200 | cookieJar.setCookieSync(CookieToRaw(cookie), url); 201 | }); 202 | return [2 /*return*/]; 203 | }); 204 | }); 205 | } 206 | function CookieToRaw(cookieObj) { 207 | return "".concat(cookieObj.Name, "=").concat(cookieObj.Value); 208 | } 209 | -------------------------------------------------------------------------------- /golang/build-linux.bat: -------------------------------------------------------------------------------- 1 | echo Building so ... 2 | go build -o goturnt.so -buildmode=c-shared 3 | echo Completed so -------------------------------------------------------------------------------- /golang/build-macos.bat: -------------------------------------------------------------------------------- 1 | echo Building dylib ... 2 | go build -o goturnt.dylib -buildmode=c-shared 3 | echo Completed dylib -------------------------------------------------------------------------------- /golang/build-windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Building dll ... 3 | go build -o goturnt.dll -buildmode=c-shared 4 | echo Completed dll -------------------------------------------------------------------------------- /golang/common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "strings" 9 | 10 | http "github.com/adam-0001/fhttp" 11 | "github.com/adam-0001/fhttp/cookiejar" 12 | ) 13 | 14 | func CreateHeaders(req *http.Request, headers string, method string) { 15 | // Default values for headers. 16 | //req.Header.Set("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") 17 | // req.Header.Set("accept-encoding", "gzip, deflate, br") 18 | //req.Header.Set("accept-language", "en-US,en;q=0.9") 19 | //req.Header.Set("cache-control", "no-cache") 20 | //req.Header.Set("connection", "keep-alive") 21 | // content-length header is set outside this function. 22 | 23 | if method == "FORM" || method == "POST" { 24 | req.Header.Set("content-type", "application/x-www-form-urlencoded") 25 | } 26 | 27 | // host header set automatically 28 | // origin header should probably be manually set but as a default can just be the base path of the destination. 29 | if method == "FORM" { 30 | // a little indirect, but ensures that the port number doesn't come with. 31 | u, _ := url.Parse(req.Host) 32 | req.Header.Set("origin", "https://"+u.Hostname()) 33 | } 34 | 35 | // Default user-agent 36 | //req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36") 37 | 38 | var hmap map[string]string 39 | var hArray []string 40 | 41 | if headers != "{}" { 42 | req.Header = http.Header{} 43 | } 44 | 45 | json.Unmarshal([]byte(headers), &hmap) 46 | 47 | strArr := strings.Split(headers, "\",\"") 48 | 49 | for _, v := range strArr { 50 | v = strings.ReplaceAll(v, "\"", "") 51 | v = strings.ReplaceAll(v, "{", "") 52 | v = strings.ToLower(v) 53 | str := strings.Split(v, ":") 54 | hArray = append(hArray, str[0]) 55 | //oMap.Set(str[0], "header") 56 | } 57 | 58 | for k, v := range hmap { 59 | req.Header.Set(k, v) 60 | } 61 | 62 | for _, val := range hArray { 63 | req.Header.Add(http.HeaderOrderKey, val) 64 | } 65 | } 66 | 67 | func FillCookieJar(j *cookiejar.Jar, ru string, s string) { 68 | // Slice that the cookies string get parsed into 69 | var cookieArray map[string]interface{} 70 | // Set of cookie objects that gets passed to the cookiejar 71 | var cookieObjs []*http.Cookie 72 | // Parses json cookies string into cookieArray 73 | json.Unmarshal([]byte(s), &cookieArray) 74 | 75 | // Turns the raw domain url into a url object 76 | u, err := url.Parse(ru) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | // Loops through each cookie in the cookieArray slice and adds it to the object array. 82 | for k, v := range cookieArray { 83 | cookieObjs = append(cookieObjs, new(http.Cookie)) 84 | cookieObjs[len(cookieObjs)-1].Name = fmt.Sprintf("%v", k) 85 | cookieObjs[len(cookieObjs)-1].Value = fmt.Sprintf("%v", v) 86 | } 87 | 88 | // Fills the cookies jar. 89 | j.SetCookies(u, cookieObjs) 90 | 91 | } 92 | -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/turnt-tls 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/adam-0001/cclient v0.0.0-20220213092753-c0fd9d56454b 7 | github.com/adam-0001/fhttp v0.0.0-20220213090405-ca9c15c7e216 8 | github.com/adam-0001/utls v0.0.0-20220213090505-ec0bc74ee2c4 9 | ) 10 | 11 | require ( 12 | github.com/dsnet/compress v0.0.1 // indirect 13 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect 14 | gitlab.com/yawning/utls.git v0.0.12-1 // indirect 15 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect 16 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 17 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 18 | golang.org/x/text v0.3.7 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /golang/go.sum: -------------------------------------------------------------------------------- 1 | github.com/adam-0001/cclient v0.0.0-20220213092753-c0fd9d56454b h1:1/zMA50cWsshaDpnJNwjOuNLK+OD3LYtOGPTyYyRsdo= 2 | github.com/adam-0001/cclient v0.0.0-20220213092753-c0fd9d56454b/go.mod h1:5DM2toG1VVGkoAtckOKfmDDh5biwLeVKdZ9ejMT6bLA= 3 | github.com/adam-0001/fhttp v0.0.0-20220213090405-ca9c15c7e216 h1:dmuBi12YEGi8KkTSs/hLIm7XwNNd6/+cSLTRm17Jhf0= 4 | github.com/adam-0001/fhttp v0.0.0-20220213090405-ca9c15c7e216/go.mod h1:vuhZaUSgIOaJSL8fWvM2kf6ZX8jhgblORp4abbrEZOI= 5 | github.com/adam-0001/utls v0.0.0-20220213090505-ec0bc74ee2c4 h1:RASIO+LMqpb5CBVlXhrbkhQp9vsHxHBQ/1qf6f2KSls= 6 | github.com/adam-0001/utls v0.0.0-20220213090505-ec0bc74ee2c4/go.mod h1:fwvD6KFUV3BebzRd9VnmAZOTn5i9kvBz0eg1yM+Jb68= 7 | github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 8 | github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= 9 | github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= 10 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 11 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 12 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 13 | github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= 14 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= 15 | gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= 16 | gitlab.com/yawning/utls.git v0.0.12-1 h1:RL6O0MP2YI0KghuEU/uGN6+8b4183eqNWoYgx7CXD0U= 17 | gitlab.com/yawning/utls.git v0.0.12-1/go.mod h1:3ONKiSFR9Im/c3t5RKmMJTVdmZN496FNyk3mjrY1dyo= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= 21 | golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 22 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 23 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 24 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 25 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= 26 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 34 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 36 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 39 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 40 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 41 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 42 | -------------------------------------------------------------------------------- /golang/goturnt.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HypePhilosophy/turnt-tls/512510878ffbe9c5fe970e47a7a05ee8e3824dfc/golang/goturnt.dll -------------------------------------------------------------------------------- /golang/goturnt.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HypePhilosophy/turnt-tls/512510878ffbe9c5fe970e47a7a05ee8e3824dfc/golang/goturnt.dylib -------------------------------------------------------------------------------- /golang/goturnt.h: -------------------------------------------------------------------------------- 1 | /* Code generated by cmd/cgo; DO NOT EDIT. */ 2 | 3 | /* package github.com/turnt-tls */ 4 | 5 | 6 | #line 1 "cgo-builtin-export-prolog" 7 | 8 | #include 9 | 10 | #ifndef GO_CGO_EXPORT_PROLOGUE_H 11 | #define GO_CGO_EXPORT_PROLOGUE_H 12 | 13 | #ifndef GO_CGO_GOSTRING_TYPEDEF 14 | typedef struct { const char *p; ptrdiff_t n; } _GoString_; 15 | #endif 16 | 17 | #endif 18 | 19 | /* Start of preamble from import "C" comments. */ 20 | 21 | 22 | 23 | 24 | /* End of preamble from import "C" comments. */ 25 | 26 | 27 | /* Start of boilerplate cgo prologue. */ 28 | #line 1 "cgo-gcc-export-header-prolog" 29 | 30 | #ifndef GO_CGO_PROLOGUE_H 31 | #define GO_CGO_PROLOGUE_H 32 | 33 | typedef signed char GoInt8; 34 | typedef unsigned char GoUint8; 35 | typedef short GoInt16; 36 | typedef unsigned short GoUint16; 37 | typedef int GoInt32; 38 | typedef unsigned int GoUint32; 39 | typedef long long GoInt64; 40 | typedef unsigned long long GoUint64; 41 | typedef GoInt64 GoInt; 42 | typedef GoUint64 GoUint; 43 | typedef size_t GoUintptr; 44 | typedef float GoFloat32; 45 | typedef double GoFloat64; 46 | #ifdef _MSC_VER 47 | #include 48 | typedef _Fcomplex GoComplex64; 49 | typedef _Dcomplex GoComplex128; 50 | #else 51 | typedef float _Complex GoComplex64; 52 | typedef double _Complex GoComplex128; 53 | #endif 54 | 55 | /* 56 | static assertion to make sure the file is being used on architecture 57 | at least with matching size of GoInt. 58 | */ 59 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; 60 | 61 | #ifndef GO_CGO_GOSTRING_TYPEDEF 62 | typedef _GoString_ GoString; 63 | #endif 64 | typedef void *GoMap; 65 | typedef void *GoChan; 66 | typedef struct { void *t; void *v; } GoInterface; 67 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 68 | 69 | #endif 70 | 71 | /* End of boilerplate cgo prologue. */ 72 | 73 | #ifdef __cplusplus 74 | extern "C" { 75 | #endif 76 | 77 | extern __declspec(dllexport) char* CreateRequest(char* urlC, char* headerC, char* cookiesC, char* bodyC, char* methodC, char* proxyC); 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | -------------------------------------------------------------------------------- /golang/goturnt.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HypePhilosophy/turnt-tls/512510878ffbe9c5fe970e47a7a05ee8e3824dfc/golang/goturnt.so -------------------------------------------------------------------------------- /golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | import ( 6 | "bytes" 7 | "compress/gzip" 8 | "compress/zlib" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/adam-0001/fhttp/cookiejar" 12 | "io" 13 | "log" 14 | "net/url" 15 | "os" 16 | "strings" 17 | 18 | "github.com/adam-0001/cclient" 19 | http "github.com/adam-0001/fhttp" 20 | tls "github.com/adam-0001/utls" 21 | ) 22 | 23 | func main() { 24 | url := "https://en.zalando.de/?_rfl=de" 25 | headers := "{\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\",\"accept-language\":\"en-US,en;q=0.9\",\"cache-control\":\"max-age=0\",\"dnt\":\"1\",\"sec-ch-ua\":\"\\\"Chromium\\\";v=\\\"106\\\", \\\"Google Chrome\\\";v=\\\"106\\\", \\\"Not;A=Brand\\\";v=\\\"99\\\"\",\"sec-ch-ua-mobile\":\"?0\",\"sec-ch-ua-platform\":\"\\\"macOS\\\"\",\"sec-fetch-dest\":\"document\",\"sec-fetch-mode\":\"navigate\",\"sec-fetch-site\":\"none\",\"sec-fetch-user\":\"?1\",\"upgrade-insecure-requests\":\"1\",\"user-agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36\"}" 26 | cookies := "{}" 27 | body := "" 28 | method := "GET" 29 | proxy := "" 30 | 31 | if err := CreateRequest(C.CString(url), C.CString(headers), C.CString(cookies), C.CString(body), C.CString(method), C.CString(proxy)); err != nil { 32 | fmt.Println(err) 33 | } 34 | } 35 | 36 | //export CreateRequest 37 | func CreateRequest(urlC *C.char, headerC *C.char, cookiesC *C.char, bodyC *C.char, methodC *C.char, proxyC *C.char) *C.char { 38 | // Turns parameters into go compatible types. 39 | dest := C.GoString(urlC) 40 | header := C.GoString(headerC) 41 | cookies := C.GoString(cookiesC) 42 | body := C.GoString(bodyC) 43 | method := C.GoString(methodC) 44 | proxy := C.GoString(proxyC) 45 | 46 | // reqMethod is needed because FORM is just a POST request, but the later code needs to be able to differentiate between the two. 47 | reqMethod := method 48 | if reqMethod == "FORM" { 49 | reqMethod = "POST" 50 | } 51 | 52 | var err error 53 | var client http.Client 54 | 55 | // Creates the http client and initialises the request. 56 | if proxy != "" { 57 | client, err = cclient.NewClient(tls.HelloChrome_Auto, proxy, true, 6) 58 | } else { 59 | client, err = cclient.NewClient(tls.HelloChrome_Auto, "", true, 6) 60 | } 61 | 62 | if err != nil { 63 | log.Fatal("Error creating client: ", err) 64 | } 65 | 66 | var req *http.Request 67 | if reqMethod == "GET" { 68 | req, _ = http.NewRequest(http.MethodGet, dest, nil) 69 | } else { 70 | req, err = http.NewRequest(http.MethodPost, dest, strings.NewReader(body)) 71 | } 72 | 73 | CreateHeaders(req, header, method) 74 | 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | // Fills the cookie jar 80 | jar, err := cookiejar.New(new(cookiejar.Options)) 81 | 82 | if cookies != "" { 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | FillCookieJar(jar, dest, cookies) 88 | client.Jar = jar 89 | } 90 | 91 | // Sends the request defined above. 92 | resp, err := client.Do(req) 93 | 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | 98 | defer resp.Body.Close() 99 | // Check that the server actually sent compressed data 100 | var reader io.ReadCloser 101 | 102 | if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { 103 | fmt.Println("Gzip") 104 | reader, err = gzip.NewReader(resp.Body) 105 | if err != nil { 106 | log.Println("Error decompressing gzip response, trying zlib") 107 | reader, err = zlib.NewReader(resp.Body) 108 | if err != nil { 109 | log.Println("Error decompressing zlib response, trying raw") 110 | reader = resp.Body 111 | } 112 | } 113 | 114 | defer reader.Close() 115 | } else { 116 | fmt.Println("Raw") 117 | reader = resp.Body 118 | } 119 | 120 | //switch resp.Header.Get("Content-Encoding") { 121 | //case "gzip": 122 | // reader, err = gzip.NewReader(resp.Body) 123 | // defer reader.Close() 124 | //default: 125 | // reader = resp.Body 126 | //} 127 | 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | // Turns response body into a string, converts to cstring and returns. 132 | 133 | bodySlice, _ := io.ReadAll(reader) 134 | bodyStr := string(bodySlice) 135 | 136 | urlParsed, _ := url.Parse(dest) 137 | 138 | respMap := make(map[string]interface{}) 139 | respMap["headers"] = resp.Header 140 | respMap["cookies"] = client.Jar.Cookies(urlParsed) 141 | respMap["body"] = bodyStr 142 | respMap["status"] = resp.StatusCode 143 | respMap["location"] = resp.Request.URL.String() 144 | 145 | respJson, _ := json.Marshal(respMap) 146 | 147 | return C.CString(string(respJson)) 148 | } 149 | 150 | func readGzip(content []byte) error { 151 | var buf *bytes.Buffer = bytes.NewBuffer(content) 152 | 153 | gRead, err := zlib.NewReader(buf) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | if t, err := io.Copy(os.Stdout, gRead); err != nil { 159 | fmt.Println(t) 160 | return err 161 | } 162 | 163 | if err := gRead.Close(); err != nil { 164 | return err 165 | } 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turnt-tls", 3 | "version": "1.1.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "turnt-tls", 9 | "version": "1.1.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@types/tough-cookie": "^4.0.0", 13 | "ffi-napi": "^4.0.3", 14 | "path": "^0.12.7", 15 | "tough-cookie": "^4.0.0", 16 | "turnt-tls": "^1.1.1" 17 | }, 18 | "devDependencies": { 19 | "@types/ffi-napi": "^2.4.3", 20 | "@types/node": "^14.14.35", 21 | "typescript": "^4.2.3" 22 | } 23 | }, 24 | "node_modules/@types/ffi-napi": { 25 | "version": "2.4.3", 26 | "resolved": "https://registry.npmjs.org/@types/ffi-napi/-/ffi-napi-2.4.3.tgz", 27 | "integrity": "sha512-lkSpnVM3252+IAxwqmX5zPh8Nugzz7rtTcLC8tnGaY48n/5kmrO0AV3OVQDMgSKPI5X4I3vx0yeArfGV6PlwCQ==", 28 | "dev": true, 29 | "dependencies": { 30 | "@types/node": "*", 31 | "@types/ref-napi": "*", 32 | "@types/ref-struct-di": "*" 33 | } 34 | }, 35 | "node_modules/@types/node": { 36 | "version": "14.18.31", 37 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.31.tgz", 38 | "integrity": "sha512-vQAnaReSQkEDa8uwAyQby8bYGKu84R/deEc6mg5T8fX6gzCn8QW6rziSgsti1fNvsrswKUKPnVTi7uoB+u62Mw==", 39 | "dev": true 40 | }, 41 | "node_modules/@types/ref-napi": { 42 | "version": "3.0.5", 43 | "resolved": "https://registry.npmjs.org/@types/ref-napi/-/ref-napi-3.0.5.tgz", 44 | "integrity": "sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw==", 45 | "dev": true, 46 | "dependencies": { 47 | "@types/node": "*" 48 | } 49 | }, 50 | "node_modules/@types/ref-struct-di": { 51 | "version": "1.1.7", 52 | "resolved": "https://registry.npmjs.org/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz", 53 | "integrity": "sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g==", 54 | "dev": true, 55 | "dependencies": { 56 | "@types/ref-napi": "*" 57 | } 58 | }, 59 | "node_modules/@types/tough-cookie": { 60 | "version": "4.0.2", 61 | "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", 62 | "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" 63 | }, 64 | "node_modules/debug": { 65 | "version": "4.3.4", 66 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 67 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 68 | "dependencies": { 69 | "ms": "2.1.2" 70 | }, 71 | "engines": { 72 | "node": ">=6.0" 73 | }, 74 | "peerDependenciesMeta": { 75 | "supports-color": { 76 | "optional": true 77 | } 78 | } 79 | }, 80 | "node_modules/ffi-napi": { 81 | "version": "4.0.3", 82 | "resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz", 83 | "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", 84 | "hasInstallScript": true, 85 | "dependencies": { 86 | "debug": "^4.1.1", 87 | "get-uv-event-loop-napi-h": "^1.0.5", 88 | "node-addon-api": "^3.0.0", 89 | "node-gyp-build": "^4.2.1", 90 | "ref-napi": "^2.0.1 || ^3.0.2", 91 | "ref-struct-di": "^1.1.0" 92 | }, 93 | "engines": { 94 | "node": ">=10" 95 | } 96 | }, 97 | "node_modules/get-symbol-from-current-process-h": { 98 | "version": "1.0.2", 99 | "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", 100 | "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==" 101 | }, 102 | "node_modules/get-uv-event-loop-napi-h": { 103 | "version": "1.0.6", 104 | "resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz", 105 | "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", 106 | "dependencies": { 107 | "get-symbol-from-current-process-h": "^1.0.1" 108 | } 109 | }, 110 | "node_modules/inherits": { 111 | "version": "2.0.3", 112 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 113 | "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" 114 | }, 115 | "node_modules/ms": { 116 | "version": "2.1.2", 117 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 118 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 119 | }, 120 | "node_modules/node-addon-api": { 121 | "version": "3.2.1", 122 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", 123 | "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" 124 | }, 125 | "node_modules/node-gyp-build": { 126 | "version": "4.5.0", 127 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", 128 | "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", 129 | "bin": { 130 | "node-gyp-build": "bin.js", 131 | "node-gyp-build-optional": "optional.js", 132 | "node-gyp-build-test": "build-test.js" 133 | } 134 | }, 135 | "node_modules/path": { 136 | "version": "0.12.7", 137 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 138 | "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", 139 | "dependencies": { 140 | "process": "^0.11.1", 141 | "util": "^0.10.3" 142 | } 143 | }, 144 | "node_modules/process": { 145 | "version": "0.11.10", 146 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 147 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 148 | "engines": { 149 | "node": ">= 0.6.0" 150 | } 151 | }, 152 | "node_modules/psl": { 153 | "version": "1.9.0", 154 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 155 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 156 | }, 157 | "node_modules/punycode": { 158 | "version": "2.1.1", 159 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 160 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 161 | "engines": { 162 | "node": ">=6" 163 | } 164 | }, 165 | "node_modules/querystringify": { 166 | "version": "2.2.0", 167 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 168 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 169 | }, 170 | "node_modules/ref-napi": { 171 | "version": "3.0.3", 172 | "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", 173 | "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", 174 | "hasInstallScript": true, 175 | "dependencies": { 176 | "debug": "^4.1.1", 177 | "get-symbol-from-current-process-h": "^1.0.2", 178 | "node-addon-api": "^3.0.0", 179 | "node-gyp-build": "^4.2.1" 180 | }, 181 | "engines": { 182 | "node": ">= 10.0" 183 | } 184 | }, 185 | "node_modules/ref-struct-di": { 186 | "version": "1.1.1", 187 | "resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz", 188 | "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", 189 | "dependencies": { 190 | "debug": "^3.1.0" 191 | } 192 | }, 193 | "node_modules/ref-struct-di/node_modules/debug": { 194 | "version": "3.2.7", 195 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 196 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 197 | "dependencies": { 198 | "ms": "^2.1.1" 199 | } 200 | }, 201 | "node_modules/requires-port": { 202 | "version": "1.0.0", 203 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 204 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 205 | }, 206 | "node_modules/tough-cookie": { 207 | "version": "4.1.2", 208 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", 209 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", 210 | "dependencies": { 211 | "psl": "^1.1.33", 212 | "punycode": "^2.1.1", 213 | "universalify": "^0.2.0", 214 | "url-parse": "^1.5.3" 215 | }, 216 | "engines": { 217 | "node": ">=6" 218 | } 219 | }, 220 | "node_modules/turnt-tls": { 221 | "version": "1.1.1", 222 | "resolved": "https://registry.npmjs.org/turnt-tls/-/turnt-tls-1.1.1.tgz", 223 | "integrity": "sha512-RX0Yn32AQ8ok8MPuLElZTr13N/ndFnadIaOrdUyHxl1HbEHWLuWGRQR2rOiFBvIXMLzCSBjiMmNskZxSyWDu4w==", 224 | "dependencies": { 225 | "@types/tough-cookie": "^4.0.0", 226 | "ffi-napi": "^4.0.3", 227 | "path": "^0.12.7", 228 | "tough-cookie": "^4.0.0", 229 | "turnt-tls": "^1.0.2" 230 | } 231 | }, 232 | "node_modules/typescript": { 233 | "version": "4.8.4", 234 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 235 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 236 | "dev": true, 237 | "bin": { 238 | "tsc": "bin/tsc", 239 | "tsserver": "bin/tsserver" 240 | }, 241 | "engines": { 242 | "node": ">=4.2.0" 243 | } 244 | }, 245 | "node_modules/universalify": { 246 | "version": "0.2.0", 247 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", 248 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", 249 | "engines": { 250 | "node": ">= 4.0.0" 251 | } 252 | }, 253 | "node_modules/url-parse": { 254 | "version": "1.5.10", 255 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 256 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 257 | "dependencies": { 258 | "querystringify": "^2.1.1", 259 | "requires-port": "^1.0.0" 260 | } 261 | }, 262 | "node_modules/util": { 263 | "version": "0.10.4", 264 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 265 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 266 | "dependencies": { 267 | "inherits": "2.0.3" 268 | } 269 | } 270 | }, 271 | "dependencies": { 272 | "@types/ffi-napi": { 273 | "version": "2.4.3", 274 | "resolved": "https://registry.npmjs.org/@types/ffi-napi/-/ffi-napi-2.4.3.tgz", 275 | "integrity": "sha512-lkSpnVM3252+IAxwqmX5zPh8Nugzz7rtTcLC8tnGaY48n/5kmrO0AV3OVQDMgSKPI5X4I3vx0yeArfGV6PlwCQ==", 276 | "dev": true, 277 | "requires": { 278 | "@types/node": "*", 279 | "@types/ref-napi": "*", 280 | "@types/ref-struct-di": "*" 281 | } 282 | }, 283 | "@types/node": { 284 | "version": "14.18.31", 285 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.31.tgz", 286 | "integrity": "sha512-vQAnaReSQkEDa8uwAyQby8bYGKu84R/deEc6mg5T8fX6gzCn8QW6rziSgsti1fNvsrswKUKPnVTi7uoB+u62Mw==", 287 | "dev": true 288 | }, 289 | "@types/ref-napi": { 290 | "version": "3.0.5", 291 | "resolved": "https://registry.npmjs.org/@types/ref-napi/-/ref-napi-3.0.5.tgz", 292 | "integrity": "sha512-u+L/RdwTuJes3pDypOVR/MtcqzoULu8Z8yulP6Tw5z7eXV1ba1llizNVFtI/m2iPfDy/dPPt+3ar1QCgonTzsw==", 293 | "dev": true, 294 | "requires": { 295 | "@types/node": "*" 296 | } 297 | }, 298 | "@types/ref-struct-di": { 299 | "version": "1.1.7", 300 | "resolved": "https://registry.npmjs.org/@types/ref-struct-di/-/ref-struct-di-1.1.7.tgz", 301 | "integrity": "sha512-nnHR26qrCnQqxwHTv+rqzu/hGgDZl45TUs4bO6ZjpuC8/M2JoXFxk63xrWmAmqsLe55oxOgAWssyr3YHAMY89g==", 302 | "dev": true, 303 | "requires": { 304 | "@types/ref-napi": "*" 305 | } 306 | }, 307 | "@types/tough-cookie": { 308 | "version": "4.0.2", 309 | "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", 310 | "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==" 311 | }, 312 | "debug": { 313 | "version": "4.3.4", 314 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 315 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 316 | "requires": { 317 | "ms": "2.1.2" 318 | } 319 | }, 320 | "ffi-napi": { 321 | "version": "4.0.3", 322 | "resolved": "https://registry.npmjs.org/ffi-napi/-/ffi-napi-4.0.3.tgz", 323 | "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", 324 | "requires": { 325 | "debug": "^4.1.1", 326 | "get-uv-event-loop-napi-h": "^1.0.5", 327 | "node-addon-api": "^3.0.0", 328 | "node-gyp-build": "^4.2.1", 329 | "ref-napi": "^2.0.1 || ^3.0.2", 330 | "ref-struct-di": "^1.1.0" 331 | } 332 | }, 333 | "get-symbol-from-current-process-h": { 334 | "version": "1.0.2", 335 | "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", 336 | "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==" 337 | }, 338 | "get-uv-event-loop-napi-h": { 339 | "version": "1.0.6", 340 | "resolved": "https://registry.npmjs.org/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz", 341 | "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", 342 | "requires": { 343 | "get-symbol-from-current-process-h": "^1.0.1" 344 | } 345 | }, 346 | "inherits": { 347 | "version": "2.0.3", 348 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 349 | "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" 350 | }, 351 | "ms": { 352 | "version": "2.1.2", 353 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 354 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 355 | }, 356 | "node-addon-api": { 357 | "version": "3.2.1", 358 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", 359 | "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" 360 | }, 361 | "node-gyp-build": { 362 | "version": "4.5.0", 363 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", 364 | "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==" 365 | }, 366 | "path": { 367 | "version": "0.12.7", 368 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 369 | "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", 370 | "requires": { 371 | "process": "^0.11.1", 372 | "util": "^0.10.3" 373 | } 374 | }, 375 | "process": { 376 | "version": "0.11.10", 377 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 378 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" 379 | }, 380 | "psl": { 381 | "version": "1.9.0", 382 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", 383 | "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" 384 | }, 385 | "punycode": { 386 | "version": "2.1.1", 387 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 388 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 389 | }, 390 | "querystringify": { 391 | "version": "2.2.0", 392 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 393 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 394 | }, 395 | "ref-napi": { 396 | "version": "3.0.3", 397 | "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", 398 | "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", 399 | "requires": { 400 | "debug": "^4.1.1", 401 | "get-symbol-from-current-process-h": "^1.0.2", 402 | "node-addon-api": "^3.0.0", 403 | "node-gyp-build": "^4.2.1" 404 | } 405 | }, 406 | "ref-struct-di": { 407 | "version": "1.1.1", 408 | "resolved": "https://registry.npmjs.org/ref-struct-di/-/ref-struct-di-1.1.1.tgz", 409 | "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", 410 | "requires": { 411 | "debug": "^3.1.0" 412 | }, 413 | "dependencies": { 414 | "debug": { 415 | "version": "3.2.7", 416 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 417 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 418 | "requires": { 419 | "ms": "^2.1.1" 420 | } 421 | } 422 | } 423 | }, 424 | "requires-port": { 425 | "version": "1.0.0", 426 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 427 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 428 | }, 429 | "tough-cookie": { 430 | "version": "4.1.2", 431 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", 432 | "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", 433 | "requires": { 434 | "psl": "^1.1.33", 435 | "punycode": "^2.1.1", 436 | "universalify": "^0.2.0", 437 | "url-parse": "^1.5.3" 438 | } 439 | }, 440 | "turnt-tls": { 441 | "version": "1.1.1", 442 | "resolved": "https://registry.npmjs.org/turnt-tls/-/turnt-tls-1.1.1.tgz", 443 | "integrity": "sha512-RX0Yn32AQ8ok8MPuLElZTr13N/ndFnadIaOrdUyHxl1HbEHWLuWGRQR2rOiFBvIXMLzCSBjiMmNskZxSyWDu4w==", 444 | "requires": { 445 | "@types/tough-cookie": "^4.0.0", 446 | "ffi-napi": "^4.0.3", 447 | "path": "^0.12.7", 448 | "tough-cookie": "^4.0.0", 449 | "turnt-tls": "^1.0.2" 450 | } 451 | }, 452 | "typescript": { 453 | "version": "4.8.4", 454 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 455 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 456 | "dev": true 457 | }, 458 | "universalify": { 459 | "version": "0.2.0", 460 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", 461 | "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" 462 | }, 463 | "url-parse": { 464 | "version": "1.5.10", 465 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 466 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 467 | "requires": { 468 | "querystringify": "^2.1.1", 469 | "requires-port": "^1.0.0" 470 | } 471 | }, 472 | "util": { 473 | "version": "0.10.4", 474 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 475 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 476 | "requires": { 477 | "inherits": "2.0.3" 478 | } 479 | } 480 | } 481 | } 482 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turnt-tls", 3 | "version": "1.1.2", 4 | "description": "", 5 | "main": "dist/turnt.js", 6 | "scripts": { 7 | "start": "tsc && node dist/index.js", 8 | "build-binaries": "cd golang && sh build-linux.bat && sh build-windows.bat && sh build-macos.bat && cd .." 9 | }, 10 | "author": "HypePhilosophy", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@types/tough-cookie": "^4.0.0", 14 | "ffi-napi": "^4.0.3", 15 | "path": "^0.12.7", 16 | "tough-cookie": "^4.0.0", 17 | "turnt-tls": "^1.1.1" 18 | }, 19 | "devDependencies": { 20 | "@types/ffi-napi": "^2.4.3", 21 | "@types/node": "^14.14.35", 22 | "typescript": "^4.2.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { turnt } from "./turnt"; 2 | import { CookieJar } from "tough-cookie"; 3 | 4 | var cookieJar = new CookieJar(); 5 | 6 | // testTLS(); 7 | // testPeetTLS(); 8 | testZalando(); 9 | async function testTLS() { 10 | let options = { 11 | method: 'GET', 12 | cookieJar: cookieJar, 13 | headers: { 14 | 'Connection': 'keep-alive', 15 | 'Pragma': 'no-cache', 16 | 'Cache-Control': 'no-cache', 17 | 'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"', 18 | 'sec-ch-ua-mobile': '?0', 19 | 'DNT': '1', 20 | 'Upgrade-Insecure-Requests': '1', 21 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36', 22 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 23 | 'Sec-Fetch-Site': 'none', 24 | 'Sec-Fetch-Mode': 'navigate', 25 | 'Sec-Fetch-User': '?1', 26 | 'Sec-Fetch-Dest': 'document', 27 | 'Accept-Language': 'en-US,en;q=0.9' 28 | }, 29 | } 30 | 31 | let GetResp = await turnt("https://incolumitas.com/pages/TLS-Fingerprint/", options) 32 | 33 | // console.log(GetResp.body); 34 | 35 | console.log(GetResp.status) 36 | } 37 | 38 | async function testPeetTLS() { 39 | let options = { 40 | method: 'GET', 41 | cookieJar: cookieJar, 42 | headers: { 43 | // 'authority': 'tls.peet.ws', 44 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 45 | 'accept-language': 'en-US,en;q=0.9', 46 | 'cache-control': 'max-age=0', 47 | 'dnt': '1', 48 | 'sec-ch-ua': '"Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"', 49 | 'sec-ch-ua-mobile': '?0', 50 | 'sec-ch-ua-platform': '"macOS"', 51 | 'sec-fetch-dest': 'document', 52 | 'sec-fetch-mode': 'navigate', 53 | 'sec-fetch-site': 'none', 54 | 'sec-fetch-user': '?1', 55 | 'upgrade-insecure-requests': '1', 56 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36', 57 | } 58 | } 59 | 60 | let response = await turnt("https://tls.peet.ws/api/all", options) 61 | console.log(response.body) 62 | } 63 | 64 | async function testZalando() { 65 | const options = { 66 | method: "GET", 67 | cookieJar: cookieJar, 68 | headers: { 69 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 70 | 'accept-encoding': 'gzip, deflate, br', 71 | 'accept-language': 'en-US,en;q=0.9', 72 | 'cache-control': 'no-cache', 73 | 'dnt': '1', 74 | 'pragma': 'no-cache', 75 | 'sec-ch-ua': '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"', 76 | 'sec-ch-ua-mobile': '?0', 77 | 'sec-ch-ua-platform': '"Windows"', 78 | 'sec-fetch-dest': 'document', 79 | 'sec-fetch-mode': 'navigate', 80 | 'sec-fetch-site': 'none', 81 | 'sec-fetch-user': '?1', 82 | 'upgrade-insecure-requests': '1', 83 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', 84 | }, 85 | }; 86 | 87 | // Produces correct response body 88 | // const fingerPrintResponse = await turnt( 89 | // "https://incolumitas.com/pages/TLS-Fingerprint/", 90 | // options 91 | // ); 92 | 93 | // console.log(fingerPrintResponse.body); 94 | // console.log(fingerPrintResponse.status); 95 | 96 | // Produces encrypted response body 97 | const zalandoResponse = await turnt("https://en.zalando.de/?_rfl=de", options); 98 | 99 | console.log(zalandoResponse.body.toString("utf8")); 100 | console.log(zalandoResponse.status); 101 | } 102 | -------------------------------------------------------------------------------- /src/turnt.ts: -------------------------------------------------------------------------------- 1 | import * as ffi from 'ffi-napi' 2 | import path from 'path' 3 | 4 | export function turnt(url: string, options: any) : any { 5 | return new Promise (async (resolve, reject) => { 6 | 7 | 8 | let DOptions = { 9 | body: '', 10 | cookies: {}, 11 | cookieJar: null, 12 | headers: {}, 13 | method: 'GET', 14 | proxy: null, 15 | timeout: 0, 16 | } 17 | Object.assign(DOptions, options) 18 | options = DOptions; 19 | 20 | // Validation 21 | options["method"] = options["method"].toUpperCase() 22 | const validMethods = ["GET", "POST", "FORM", "PUT", "PUTFORM"]; 23 | if (!validMethods.includes(options["method"])) 24 | reject(new Error("Method must be either GET, POST, FORM, PUT, or PUTFORM.")); 25 | if (Number.isNaN(options["timeout"])) reject(new Error("Timeout must be a number.")); 26 | if (typeof options["headers"] !== "object") reject(new Error("Headers must be an object.")); 27 | 28 | if (CountAttributes(options["cookies"]) != 0 && options["cookieJar"] != null) reject(new Error("Only cookies or cookieJar may be set.")) 29 | 30 | if (typeof options["cookies"] !== "object") reject(new Error("Cookies must be an object.")); 31 | if (options["cookieJar"] !== null && typeof options["cookieJar"] !== "object") reject(new Error("cookiesJar must be a tough-cookie CookieJar.")) 32 | 33 | if (options["body"] == null) options["body"] = ''; 34 | if (options["method"] == "GET" && options["body"] != '') reject(new Error("Get requests can not have bodies.")); 35 | 36 | if (options["proxy"] == null) options["proxy"] = ""; 37 | 38 | // Loads DLL 39 | 40 | let GoRequests = ffi.Library(path.join(__dirname, '../golang/goturnt'), { 41 | 'CreateRequest': ['string', ['string','string','string','string','string', 'string']] 42 | }) 43 | 44 | 45 | let headers = options["headers"]; 46 | let cookies; 47 | let body = options["body"]; 48 | // * Switch between cookieJar and default cookies 49 | 50 | if(options.cookieJar != null && options.cookie == undefined) { 51 | cookies = await convertCookies(url, options.cookieJar); 52 | } else { 53 | cookies = options["cookies"]; 54 | } 55 | 56 | // There's no point in spoofing an ssl signature if ssl isn't used. 57 | if (!url.startsWith("https")) 58 | throw new Error('Only https is supported.'); 59 | 60 | let res = ""; 61 | 62 | 63 | 64 | if (Buffer.isBuffer(body)) body = body.toString(); 65 | if (typeof body === 'object') body = FormEncode(body); 66 | 67 | // console.log(url) 68 | // console.log(JSON.stringify(headers)) 69 | // console.log(JSON.stringify(cookies)) 70 | // console.log(body) 71 | // console.log(options["method"]) 72 | // console.log(options["proxy"]) 73 | res = GoRequests.CreateRequest(url, JSON.stringify(headers), JSON.stringify(cookies), body, options["method"], options["proxy"]) 74 | 75 | let result = JSON.parse(res); 76 | 77 | for (var header in result["headers"]) { 78 | result["headers"][header] = result["headers"][header][0] 79 | } 80 | if(options.cookieJar != null && options.cookie == undefined) setCookies(result.cookies, options.cookieJar, url); 81 | resolve(result); 82 | 83 | }); 84 | } 85 | 86 | 87 | function FormEncode (body : { [key: string]: string | number }) : string { 88 | let res = "" 89 | 90 | for (var key in body) { 91 | if (Object.prototype.hasOwnProperty.call(body, key)) { 92 | res += encodeURIComponent(key) + "=" + encodeURIComponent(body[key].toString()) + "&"; 93 | } 94 | } 95 | 96 | return res.slice(0, -1); 97 | 98 | } 99 | 100 | function CountAttributes (obj : Object) { 101 | let count = 0; 102 | for (var k in obj) if (obj.hasOwnProperty(k)) count++; 103 | return count; 104 | } 105 | 106 | async function convertCookies(url: string, cookieJar: any) { 107 | var cookieArray: any; 108 | cookieArray = {}; 109 | await cookieJar.getCookiesSync(url).forEach(async (cookie: any) => { 110 | cookieArray[cookie.key] = await cookie.value; 111 | }); 112 | return cookieArray; 113 | } 114 | 115 | async function setCookies(cookies: any[], cookieJar: any, url: string) { 116 | if (cookies == null) return; 117 | cookies.forEach((cookie: {[key: string]: string | number}) => { 118 | cookieJar.setCookieSync(CookieToRaw(cookie), url); 119 | }); 120 | } 121 | 122 | function CookieToRaw(cookieObj: any) { 123 | return `${cookieObj.Name}=${cookieObj.Value}` 124 | } 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | "lib": ["es6","dom"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 44 | 45 | /* Module Resolution Options */ 46 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "resolveJsonModule": true, /* Include modules imported with '.json' extension */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | --------------------------------------------------------------------------------