├── .gitignore ├── LICENSE ├── README.md ├── dist ├── components │ ├── interfaces │ │ ├── logger-service.d.ts │ │ ├── logger-service.js │ │ ├── rules-data-source.d.ts │ │ ├── rules-data-source.js │ │ ├── state.d.ts │ │ └── state.js │ ├── proxy-middleware │ │ ├── constants.d.ts │ │ ├── constants.js │ │ ├── helpers │ │ │ ├── ctx_rq_namespace.d.ts │ │ │ ├── ctx_rq_namespace.js │ │ │ ├── handleUnreachableAddress.d.ts │ │ │ ├── handleUnreachableAddress.js │ │ │ ├── harObectCreator.d.ts │ │ │ ├── harObectCreator.js │ │ │ ├── http_helpers.d.ts │ │ │ ├── http_helpers.js │ │ │ ├── proxy_ctx_helper.d.ts │ │ │ ├── proxy_ctx_helper.js │ │ │ ├── response_helper.d.ts │ │ │ ├── response_helper.js │ │ │ ├── rule_processor_helper.d.ts │ │ │ └── rule_processor_helper.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── middlewares │ │ │ ├── amiusing_middleware.d.ts │ │ │ ├── amiusing_middleware.js │ │ │ ├── logger_middleware.d.ts │ │ │ ├── logger_middleware.js │ │ │ ├── rules_middleware.d.ts │ │ │ ├── rules_middleware.js │ │ │ ├── ssl_cert_middleware.d.ts │ │ │ ├── ssl_cert_middleware.js │ │ │ ├── state.d.ts │ │ │ └── state.js │ │ └── rule_action_processor │ │ │ ├── handle_mixed_response.d.ts │ │ │ ├── handle_mixed_response.js │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── modified_requests_pool.d.ts │ │ │ ├── modified_requests_pool.js │ │ │ ├── processors │ │ │ ├── block_processor.d.ts │ │ │ ├── block_processor.js │ │ │ ├── delay_processor.d.ts │ │ │ ├── delay_processor.js │ │ │ ├── insert_processor.d.ts │ │ │ ├── insert_processor.js │ │ │ ├── modify_header_processor.d.ts │ │ │ ├── modify_header_processor.js │ │ │ ├── modify_request_processor.d.ts │ │ │ ├── modify_request_processor.js │ │ │ ├── modify_response_processor.d.ts │ │ │ ├── modify_response_processor.js │ │ │ ├── modify_user_agent_processor.d.ts │ │ │ ├── modify_user_agent_processor.js │ │ │ ├── redirect_processor.d.ts │ │ │ └── redirect_processor.js │ │ │ ├── utils.d.ts │ │ │ └── utils.js │ └── ssl-proxying │ │ ├── ssl-proxying-manager.d.ts │ │ └── ssl-proxying-manager.js ├── constants │ ├── cert.d.ts │ └── cert.js ├── index.d.ts ├── index.js ├── lib │ └── proxy │ │ ├── bin │ │ ├── mitm-proxy.d.ts │ │ └── mitm-proxy.js │ │ ├── custom │ │ └── utils │ │ │ ├── checkInvalidHeaderChar.d.ts │ │ │ └── checkInvalidHeaderChar.js │ │ ├── index.d.ts │ │ ├── index.js │ │ └── lib │ │ ├── ca.d.ts │ │ ├── ca.js │ │ ├── middleware │ │ ├── decompress.d.ts │ │ ├── decompress.js │ │ ├── gunzip.d.ts │ │ ├── gunzip.js │ │ ├── wildcard.d.ts │ │ └── wildcard.js │ │ ├── proxy.d.ts │ │ └── proxy.js ├── rq-proxy-provider.d.ts ├── rq-proxy-provider.js ├── rq-proxy.d.ts ├── rq-proxy.js ├── test.d.ts ├── test.js ├── types │ ├── index.d.ts │ └── index.js └── utils │ ├── circularQueue.d.ts │ ├── circularQueue.js │ ├── helpers │ ├── rules-helper.d.ts │ └── rules-helper.js │ ├── index.d.ts │ └── index.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── interfaces │ │ ├── logger-service.ts │ │ ├── rules-data-source.ts │ │ └── state.ts │ ├── proxy-middleware │ │ ├── constants.js │ │ ├── helpers │ │ │ ├── ctx_rq_namespace.js │ │ │ ├── handleUnreachableAddress.js │ │ │ ├── harObectCreator.js │ │ │ ├── http_helpers.js │ │ │ ├── proxy_ctx_helper.js │ │ │ ├── response_helper.js │ │ │ └── rule_processor_helper.js │ │ ├── index.js │ │ ├── middlewares │ │ │ ├── amiusing_middleware.js │ │ │ ├── logger_middleware.js │ │ │ ├── rules_middleware.js │ │ │ ├── ssl_cert_middleware.js │ │ │ ├── state.d.ts │ │ │ └── state.ts │ │ └── rule_action_processor │ │ │ ├── handle_mixed_response.js │ │ │ ├── index.js │ │ │ ├── modified_requests_pool.js │ │ │ ├── processors │ │ │ ├── block_processor.js │ │ │ ├── delay_processor.js │ │ │ ├── insert_processor.js │ │ │ ├── modify_header_processor.js │ │ │ ├── modify_request_processor.js │ │ │ ├── modify_response_processor.js │ │ │ ├── modify_user_agent_processor.js │ │ │ └── redirect_processor.js │ │ │ └── utils.js │ └── ssl-proxying │ │ └── ssl-proxying-manager.ts ├── constants │ └── cert.ts ├── index.ts ├── lib │ └── proxy │ │ ├── bin │ │ └── mitm-proxy.js │ │ ├── custom │ │ └── utils │ │ │ └── checkInvalidHeaderChar.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── lib │ │ ├── ca.js │ │ ├── middleware │ │ │ ├── decompress.js │ │ │ ├── gunzip.js │ │ │ └── wildcard.js │ │ └── proxy.ts │ │ └── package.json ├── rq-proxy-provider.ts ├── rq-proxy.ts ├── test.ts ├── types │ └── index.ts └── utils │ ├── circularQueue.ts │ ├── helpers │ └── rules-helper.ts │ └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | # Build Files 22 | .webpack 23 | 24 | /requestly-master-1.0.0.tgz 25 | 26 | # /dist/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RQ-Proxy 2 | 3 | Proxy that gives superpowers to all the Requestly clients 4 | - android-sdk 5 | - desktop-app 6 | -------------------------------------------------------------------------------- /dist/components/interfaces/logger-service.d.ts: -------------------------------------------------------------------------------- 1 | interface ILoggerService { 2 | addLog: (log: any, requestHeaders: {}) => void; 3 | } 4 | export default ILoggerService; 5 | -------------------------------------------------------------------------------- /dist/components/interfaces/logger-service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/components/interfaces/rules-data-source.d.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RuleGroup } from "../../types"; 2 | interface IRulesDataSource { 3 | getRules: (requestHeaders: {}) => Promise; 4 | getGroups(requestHeaders: {}): Promise; 5 | } 6 | export default IRulesDataSource; 7 | -------------------------------------------------------------------------------- /dist/components/interfaces/rules-data-source.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/components/interfaces/state.d.ts: -------------------------------------------------------------------------------- 1 | export default interface IInitialState extends Record { 2 | sharedState?: Record; 3 | variables?: Record; 4 | } 5 | -------------------------------------------------------------------------------- /dist/components/interfaces/state.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/constants.d.ts: -------------------------------------------------------------------------------- 1 | export namespace RULE_ACTION { 2 | let REDIRECT: string; 3 | let MODIFY_HEADERS: string; 4 | let MODIFY_USER_AGENT: string; 5 | let BLOCK: string; 6 | let INSERT: string; 7 | let DELAY: string; 8 | let MODIFY_RESPONSE: string; 9 | let MODIFY_REQUEST: string; 10 | } 11 | export const RQ_INTERCEPTED_CONTENT_TYPES: string[]; 12 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RQ_INTERCEPTED_CONTENT_TYPES = exports.RULE_ACTION = void 0; 4 | exports.RULE_ACTION = { 5 | REDIRECT: "redirect", 6 | MODIFY_HEADERS: "modify_headers", 7 | MODIFY_USER_AGENT: "modify_user_agent", 8 | BLOCK: "block", 9 | INSERT: "insert", 10 | DELAY: "add_delay", 11 | MODIFY_RESPONSE: "modify_response", 12 | MODIFY_REQUEST: "modify_request", 13 | }; 14 | exports.RQ_INTERCEPTED_CONTENT_TYPES = [ 15 | "text/html", 16 | "text/plain", 17 | "text/javascript", 18 | "application/javascript", 19 | "application/x-javascript", 20 | "text/css", 21 | "application/css", 22 | "application/json", 23 | "application/manifest+json" 24 | ]; 25 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/ctx_rq_namespace.d.ts: -------------------------------------------------------------------------------- 1 | export default CtxRQNamespace; 2 | declare class CtxRQNamespace { 3 | original_request: {}; 4 | original_response: {}; 5 | final_request: {}; 6 | final_response: {}; 7 | consoleLogs: any[]; 8 | set_original_request: ({ method, path, host, port, headers, agent, body, query_params, }: { 9 | method?: any; 10 | path?: any; 11 | host?: any; 12 | port?: any; 13 | headers?: any; 14 | agent?: any; 15 | body?: any; 16 | query_params?: any; 17 | }) => void; 18 | set_original_response: ({ status_code, headers, body, query_params, }: { 19 | status_code?: any; 20 | headers?: any; 21 | body?: any; 22 | query_params?: any; 23 | }) => void; 24 | set_final_request: (proxyToServerRequestOptions: any) => void; 25 | set_final_response: ({ status_code, headers, body, }: { 26 | status_code?: any; 27 | headers?: any; 28 | body?: any; 29 | }) => void; 30 | /** 31 | * Note: 32 | * 1. Gives body only if called after request end 33 | * 2. Currently only works for JSON body because we only provide json targetting on body right now 34 | */ 35 | get_json_request_body: () => any; 36 | } 37 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/ctx_rq_namespace.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const harObectCreator_1 = require("./harObectCreator"); 4 | class CtxRQNamespace { 5 | constructor() { 6 | this.set_original_request = ({ method = null, path = null, host = null, port = null, headers = null, agent = null, body = null, query_params = null, }) => { 7 | if (headers) { 8 | this.original_request.headers = { ...headers }; 9 | } 10 | this.original_request.method = method || this.original_request.method; 11 | this.original_request.path = path || this.original_request.path; 12 | this.original_request.host = host || this.original_request.host; 13 | this.original_request.port = port || this.original_request.port; 14 | this.original_request.agent = agent || this.original_request.agent; 15 | this.original_request.body = body || this.original_request.body; 16 | this.original_request.query_params = 17 | query_params || this.original_request.query_params; 18 | }; 19 | this.set_original_response = ({ status_code = null, headers = null, body = null, query_params = null, }) => { 20 | if (headers) { 21 | this.original_response.headers = { ...headers }; 22 | } 23 | this.original_response.status_code = 24 | status_code || this.original_response.status_code; 25 | this.original_response.body = body || this.original_response.body; 26 | }; 27 | this.set_final_request = (proxyToServerRequestOptions) => { 28 | const { method, path, host, port, headers, agent, body, query_params, } = proxyToServerRequestOptions; 29 | if (headers) { 30 | this.final_request.headers = { ...headers }; 31 | } 32 | this.final_request.method = method || this.final_request.method; 33 | this.final_request.path = path || this.final_request.path; 34 | this.final_request.host = host || this.final_request.host; 35 | this.final_request.port = port || this.final_request.port; 36 | this.final_request.agent = agent || this.final_request.agent; 37 | this.final_request.body = body || this.final_request.body; 38 | this.final_request.query_params = 39 | query_params || this.final_request.query_params; 40 | this.final_request.requestHarObject = (0, harObectCreator_1.createRequestHarObject)(this.final_request.requestHarObject || {}, proxyToServerRequestOptions); 41 | }; 42 | this.set_final_response = ({ status_code = null, headers = null, body = null, }) => { 43 | if (headers) { 44 | this.final_response.headers = { ...headers }; 45 | } 46 | this.final_response.status_code = 47 | status_code || this.final_response.status_code; 48 | this.final_response.body = body || this.final_response.body; 49 | }; 50 | /** 51 | * Note: 52 | * 1. Gives body only if called after request end 53 | * 2. Currently only works for JSON body because we only provide json targetting on body right now 54 | */ 55 | this.get_json_request_body = () => { 56 | try { 57 | return JSON.parse(this.original_request.body); 58 | } 59 | catch (e) { 60 | /* Body is still buffer array */ 61 | } 62 | return null; 63 | }; 64 | this.original_request = { 65 | // method: ctx.clientToProxyRequest.method, 66 | // path: ctx.clientToProxyRequest.url, 67 | // host: hostPort.host, 68 | // port: hostPort.port, 69 | // headers: headers, 70 | // agent: ctx.isSSL ? self.httpsAgent : self.httpAgent, 71 | // body: body 72 | // query_params: query_params // json 73 | }; 74 | this.original_response = { 75 | // status_code, 76 | // headers, 77 | // body 78 | }; 79 | this.final_request = { 80 | // method: ctx.clientToProxyRequest.method, 81 | // path: ctx.clientToProxyRequest.url, 82 | // host: hostPort.host, 83 | // port: hostPort.port, 84 | // headers: headers, 85 | // agent: ctx.isSSL ? self.httpsAgent : self.httpAgent, 86 | // body: body 87 | // query_params: query_params // json 88 | }; 89 | this.final_response = { 90 | // status_code, 91 | // headers, 92 | // body 93 | }; 94 | this.consoleLogs = []; 95 | } 96 | } 97 | exports.default = CtxRQNamespace; 98 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/handleUnreachableAddress.d.ts: -------------------------------------------------------------------------------- 1 | export function isAddressUnreachableError(host: any): Promise; 2 | export function dataToServeUnreachablePage(host: any): { 3 | status: number; 4 | contentType: string; 5 | body: string; 6 | }; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/handleUnreachableAddress.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.isAddressUnreachableError = isAddressUnreachableError; 7 | exports.dataToServeUnreachablePage = dataToServeUnreachablePage; 8 | const dns_1 = __importDefault(require("dns")); 9 | function isAddressUnreachableError(host) { 10 | return new Promise((resolve, reject) => { 11 | dns_1.default.lookup(host, (err, address) => { 12 | if (err) { 13 | if (err.code === 'ENOTFOUND') { 14 | resolve(true); 15 | } 16 | else { 17 | reject(err); 18 | } 19 | } 20 | else { 21 | resolve(false); 22 | } 23 | }); 24 | }); 25 | } 26 | function dataToServeUnreachablePage(host) { 27 | return { 28 | status: 502, 29 | contentType: 'text/html', 30 | body: ` 31 | 32 | 33 | 34 | 35 | ERR_NAME_NOT_RESOLVED 36 | 37 | 74 | 75 | 76 |
77 |
:(
78 |

This site can’t be reached

79 |

The webpage at ${host}/ might be temporarily down or it may have moved permanently to a new web address.

80 |

ERR_NAME_NOT_RESOLVED

81 |
82 | 83 | 84 | `.trim() 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/harObectCreator.d.ts: -------------------------------------------------------------------------------- 1 | export function createRequestHarObject(requestHarObject: any, proxyToServerRequestOptions: any): { 2 | bodySize: number; 3 | headersSize: number; 4 | httpVersion: string; 5 | cookies: any[]; 6 | headers: any; 7 | method: any; 8 | queryString: any; 9 | url: any; 10 | postData: any; 11 | }; 12 | export function createHar(requestHeaders: any, method: any, protocol: any, host: any, path: any, requestBody: any, responseStatusCode: any, response: any, responseHeaders: any, requestParams: any): { 13 | log: { 14 | version: string; 15 | creator: {}; 16 | browser: {}; 17 | pages: any[]; 18 | entries: { 19 | startedDateTime: string; 20 | request: { 21 | bodySize: number; 22 | headersSize: number; 23 | httpVersion: string; 24 | cookies: any[]; 25 | headers: { 26 | name: string; 27 | value: any; 28 | }[]; 29 | method: any; 30 | queryString: any[]; 31 | url: string; 32 | postData: { 33 | mimeType: any; 34 | text: any; 35 | }; 36 | }; 37 | response: { 38 | status: any; 39 | httpVersion: string; 40 | cookies: any[]; 41 | headers: { 42 | name: string; 43 | value: any; 44 | }[]; 45 | content: { 46 | size: number; 47 | compression: number; 48 | mimeType: any; 49 | text: any; 50 | comment: string; 51 | }; 52 | headersSize: number; 53 | bodySize: number; 54 | comment: string; 55 | }; 56 | cache: {}; 57 | timings: {}; 58 | comment: string; 59 | }[]; 60 | comment: string; 61 | }; 62 | }; 63 | export function createHarEntry(requestHeaders: any, method: any, protocol: any, host: any, path: any, requestBody: any, responseStatusCode: any, response: any, responseHeaders: any, requestParams: any): { 64 | startedDateTime: string; 65 | request: { 66 | bodySize: number; 67 | headersSize: number; 68 | httpVersion: string; 69 | cookies: any[]; 70 | headers: { 71 | name: string; 72 | value: any; 73 | }[]; 74 | method: any; 75 | queryString: any[]; 76 | url: string; 77 | postData: { 78 | mimeType: any; 79 | text: any; 80 | }; 81 | }; 82 | response: { 83 | status: any; 84 | httpVersion: string; 85 | cookies: any[]; 86 | headers: { 87 | name: string; 88 | value: any; 89 | }[]; 90 | content: { 91 | size: number; 92 | compression: number; 93 | mimeType: any; 94 | text: any; 95 | comment: string; 96 | }; 97 | headersSize: number; 98 | bodySize: number; 99 | comment: string; 100 | }; 101 | cache: {}; 102 | timings: {}; 103 | comment: string; 104 | }; 105 | export function createHarRequest(requestHeaders: any, method: any, protocol: any, host: any, path: any, requestBody: any, requestParams: any): { 106 | bodySize: number; 107 | headersSize: number; 108 | httpVersion: string; 109 | cookies: any[]; 110 | headers: { 111 | name: string; 112 | value: any; 113 | }[]; 114 | method: any; 115 | queryString: any[]; 116 | url: string; 117 | postData: { 118 | mimeType: any; 119 | text: any; 120 | }; 121 | }; 122 | export function createHarResponse(responseStatusCode: any, response: any, responseHeaders: any): { 123 | status: any; 124 | httpVersion: string; 125 | cookies: any[]; 126 | headers: { 127 | name: string; 128 | value: any; 129 | }[]; 130 | content: { 131 | size: number; 132 | compression: number; 133 | mimeType: any; 134 | text: any; 135 | comment: string; 136 | }; 137 | headersSize: number; 138 | bodySize: number; 139 | comment: string; 140 | }; 141 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/harObectCreator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createHarResponse = exports.createHarRequest = exports.createHarEntry = exports.createHar = exports.createRequestHarObject = void 0; 4 | const createHarHeaders = (request_headers_obj) => { 5 | // convert headers obj to haar format 6 | const headers = []; 7 | if (request_headers_obj) { 8 | for (const key in request_headers_obj) { 9 | headers.push({ 10 | name: key, 11 | value: request_headers_obj[key], 12 | }); 13 | } 14 | } 15 | return headers; 16 | }; 17 | const createHarQueryStrings = (query_params) => { 18 | let res = []; 19 | Object.keys(query_params).forEach(query => { 20 | const query_value = query_params[query]; 21 | if (Array.isArray(query_value)) { 22 | query_value.forEach(val => { 23 | res.push({ 24 | "name": query, 25 | "value": val !== null && val !== void 0 ? val : "" 26 | }); 27 | }); 28 | } 29 | else { 30 | res.push({ 31 | "name": query, 32 | "value": query_value !== null && query_value !== void 0 ? query_value : "" 33 | }); 34 | } 35 | }); 36 | return res; 37 | }; 38 | const getContentType = (headers) => { 39 | let contentType = null; 40 | if (headers) { 41 | headers.forEach((item) => { 42 | if (item.name === "content-type") { 43 | contentType = item.value; 44 | } 45 | }); 46 | } 47 | return contentType; 48 | }; 49 | const buildHarPostParams = (contentType, body) => { 50 | if (contentType === "application/x-www-form-urlencoded") { 51 | return body 52 | .split("&") // Split by separators 53 | .map((keyValue) => { 54 | const [key, value] = keyValue.split("="); 55 | return { 56 | name: key, 57 | value, 58 | }; 59 | }); 60 | } 61 | else { 62 | // FormData has its own format 63 | // TODO Complete form data case -> where file is uploaded 64 | return { 65 | name: contentType, 66 | value: body, 67 | }; 68 | } 69 | }; 70 | const createHarPostData = (body, headers) => { 71 | // http://www.softwareishard.com/blog/har-12-spec/#postData 72 | if (!body) { 73 | return undefined; 74 | } 75 | const contentType = getContentType(headers); 76 | // if (!contentType) { 77 | // return undefined; 78 | // } 79 | // // console.log("contentType and Body", { contentType, body }, typeof body); 80 | // if ( 81 | // ["application/x-www-form-urlencoded", "multipart/form-data"].includes( 82 | // contentType 83 | // ) 84 | // ) { 85 | // return { 86 | // mimeType: contentType, 87 | // params: buildHarPostParams(contentType, body), 88 | // }; 89 | // } 90 | return { 91 | mimeType: contentType, // Let's assume by default content type is JSON 92 | text: body, 93 | }; 94 | }; 95 | // create standard request har object: http://www.softwareishard.com/blog/har-12-spec/#request 96 | // URL: https://github.com/hoppscotch/hoppscotch/blob/75ab7fdb00c0129ad42d45165bd3ad0af1faca2e/packages/hoppscotch-app/helpers/new-codegen/har.ts#L26 97 | const createRequestHarObject = (requestHarObject, proxyToServerRequestOptions) => { 98 | const { method, host, path, body, headers, agent, query_params } = proxyToServerRequestOptions; 99 | return { 100 | bodySize: -1, // TODO: calculate the body size 101 | headersSize: -1, // TODO: calculate the header size 102 | httpVersion: "HTTP/1.1", 103 | cookies: [], // TODO: add support for Cookies 104 | headers: requestHarObject.headers || createHarHeaders(headers), 105 | method: requestHarObject.method || method, 106 | queryString: requestHarObject.queryString || createHarQueryStrings(query_params), 107 | url: requestHarObject.url || ((agent === null || agent === void 0 ? void 0 : agent.protocol) || "http:") + "//" + host + path, 108 | postData: requestHarObject.postData || 109 | createHarPostData(body, requestHarObject.headers), 110 | }; 111 | }; 112 | exports.createRequestHarObject = createRequestHarObject; 113 | const createHar = (requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams) => { 114 | return { 115 | "log": { 116 | "version": "1.2", 117 | "creator": {}, 118 | "browser": {}, 119 | "pages": [], 120 | "entries": [(0, exports.createHarEntry)(requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams)], 121 | "comment": "" 122 | } 123 | }; 124 | }; 125 | exports.createHar = createHar; 126 | const createHarEntry = (requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams) => { 127 | return { 128 | // "pageref": "page_0", 129 | "startedDateTime": new Date().toISOString(), 130 | // "time": 50, 131 | "request": (0, exports.createHarRequest)(requestHeaders, method, protocol, host, path, requestBody, requestParams), 132 | "response": (0, exports.createHarResponse)(responseStatusCode, response, responseHeaders), 133 | "cache": {}, 134 | "timings": {}, 135 | // "serverIPAddress": "10.0.0.1", 136 | // "connection": "52492", 137 | "comment": "" 138 | }; 139 | }; 140 | exports.createHarEntry = createHarEntry; 141 | const createHarRequest = (requestHeaders, method, protocol, host, path, requestBody, requestParams) => { 142 | return { 143 | bodySize: -1, // TODO: calculate the body size 144 | headersSize: -1, // TODO: calculate the header size 145 | httpVersion: "HTTP/1.1", 146 | cookies: [], // TODO: add support for Cookies 147 | headers: createHarHeaders(requestHeaders), 148 | method: method, 149 | queryString: createHarQueryStrings(requestParams), 150 | url: protocol + "://" + host + path, 151 | postData: createHarPostData(requestBody, createHarHeaders(requestHeaders)), 152 | }; 153 | }; 154 | exports.createHarRequest = createHarRequest; 155 | const createHarResponse = (responseStatusCode, response, responseHeaders) => { 156 | return { 157 | "status": responseStatusCode, 158 | // "statusText": "OK", 159 | "httpVersion": "HTTP/1.1", 160 | "cookies": [], 161 | "headers": createHarHeaders(responseHeaders), 162 | "content": { 163 | "size": 33, 164 | "compression": 0, 165 | "mimeType": (responseHeaders && responseHeaders["content-type"]), 166 | "text": response, 167 | "comment": "" 168 | }, 169 | // "redirectURL": "", 170 | "headersSize": -1, 171 | "bodySize": -1, 172 | "comment": "" 173 | }; 174 | }; 175 | exports.createHarResponse = createHarResponse; 176 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/http_helpers.d.ts: -------------------------------------------------------------------------------- 1 | export function getEncoding(contentTypeHeader: any, buffer: any): any; 2 | export function bodyParser(contentTypeHeader: any, buffer: any): any; 3 | export function getContentType(contentTypeHeader: any): any; 4 | export function parseJsonBody(data: any): any; 5 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/http_helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.parseJsonBody = exports.getContentType = exports.bodyParser = exports.getEncoding = void 0; 7 | const charset_1 = __importDefault(require("charset")); 8 | const mime_types_1 = __importDefault(require("mime-types")); 9 | const getEncoding = (contentTypeHeader, buffer) => { 10 | const encoding = (0, charset_1.default)(contentTypeHeader, buffer) || mime_types_1.default.charset(contentTypeHeader) || "utf8"; 11 | return encoding; 12 | }; 13 | exports.getEncoding = getEncoding; 14 | const bodyParser = (contentTypeHeader, buffer) => { 15 | const encoding = (0, exports.getEncoding)(contentTypeHeader, buffer); 16 | try { 17 | return buffer.toString(encoding); 18 | } 19 | catch (error) { 20 | // some encodings are not supposed to be turned into string 21 | return buffer; 22 | } 23 | }; 24 | exports.bodyParser = bodyParser; 25 | const getContentType = (contentTypeHeader) => { 26 | var _a; 27 | return (_a = contentTypeHeader === null || contentTypeHeader === void 0 ? void 0 : contentTypeHeader.split(";")[0]) !== null && _a !== void 0 ? _a : null; 28 | }; 29 | exports.getContentType = getContentType; 30 | const parseJsonBody = (data) => { 31 | try { 32 | return JSON.parse(data); 33 | } 34 | catch (e) { 35 | /* Body is still buffer array */ 36 | } 37 | return data; 38 | }; 39 | exports.parseJsonBody = parseJsonBody; 40 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/proxy_ctx_helper.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param queryString e.g. ?a=1&b=2 or a=1 or '' 4 | * @returns object { paramName -> [value1, value2] } 5 | */ 6 | export function getQueryParamsMap(queryString: any): {}; 7 | export function get_request_url(ctx: any): string; 8 | export function get_original_request_headers(ctx: any): any; 9 | export function get_original_response_headers(ctx: any): any; 10 | export function is_request_preflight(ctx: any): boolean; 11 | export function get_response_options(ctx: any): { 12 | status_code: any; 13 | headers: any; 14 | }; 15 | export function get_request_options(ctx: any): any; 16 | export function get_json_query_params(ctx: any): {}; 17 | export function getRequestHeaders(ctx: any): any; 18 | export function getRequestContentTypeHeader(ctx: any): any; 19 | export function getResponseHeaders(ctx: any): any; 20 | export function getResponseContentTypeHeader(ctx: any): any; 21 | export function getResponseStatusCode(ctx: any): any; 22 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/proxy_ctx_helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getResponseStatusCode = exports.getResponseContentTypeHeader = exports.getResponseHeaders = exports.getRequestContentTypeHeader = exports.getRequestHeaders = exports.get_json_query_params = exports.get_request_options = exports.get_response_options = exports.is_request_preflight = exports.get_original_response_headers = exports.get_original_request_headers = exports.get_request_url = void 0; 4 | exports.getQueryParamsMap = getQueryParamsMap; 5 | const requestly_core_1 = require("@requestly/requestly-core"); 6 | function extractUrlComponent(url, name) { 7 | const myUrl = new URL(url); 8 | switch (name) { 9 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.URL: 10 | return url; 11 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.PROTOCOL: 12 | return myUrl.protocol; 13 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.HOST: 14 | return myUrl.host; 15 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.PATH: 16 | return myUrl.pathname; 17 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.QUERY: 18 | return myUrl.search; 19 | case requestly_core_1.CONSTANTS.URL_COMPONENTS.HASH: 20 | return myUrl.hash; 21 | } 22 | console.error("Invalid source key", url, name); 23 | } 24 | /** 25 | * 26 | * @param queryString e.g. ?a=1&b=2 or a=1 or '' 27 | * @returns object { paramName -> [value1, value2] } 28 | */ 29 | function getQueryParamsMap(queryString) { 30 | var map = {}, queryParams; 31 | if (!queryString || queryString === "?") { 32 | return map; 33 | } 34 | if (queryString[0] === "?") { 35 | queryString = queryString.substr(1); 36 | } 37 | queryParams = queryString.split("&"); 38 | queryParams.forEach(function (queryParam) { 39 | var paramName = queryParam.split("=")[0], paramValue = queryParam.split("=")[1]; 40 | // We are keeping value of param as array so that in future we can support multiple param values of same name 41 | // And we do not want to lose the params if url already contains multiple params of same name 42 | map[paramName] = map[paramName] || []; 43 | map[paramName].push(paramValue); 44 | }); 45 | return map; 46 | } 47 | const get_request_url = (ctx) => { 48 | return ((ctx.isSSL ? "https://" : "http://") + 49 | ctx.clientToProxyRequest.headers.host + 50 | ctx.clientToProxyRequest.url); 51 | }; 52 | exports.get_request_url = get_request_url; 53 | const get_original_request_headers = (ctx) => { 54 | // TODO: This needs to be fetched from ctx.clientToProxy headers 55 | return ctx.proxyToServerRequestOptions.headers; 56 | }; 57 | exports.get_original_request_headers = get_original_request_headers; 58 | const get_original_response_headers = (ctx) => { 59 | var _a; 60 | // TODO: This needs to be fetched from ctx.clientToProxy headers 61 | return ((_a = ctx === null || ctx === void 0 ? void 0 : ctx.serverToProxyResponse) === null || _a === void 0 ? void 0 : _a.headers) || {}; 62 | }; 63 | exports.get_original_response_headers = get_original_response_headers; 64 | const is_request_preflight = (ctx) => { 65 | if ( 66 | // Request method is OPTIONS 67 | ctx.clientToProxyRequest.method.toLowerCase() === "options" && 68 | // Has "origin" or "Origin" or "ORIGIN" header 69 | (ctx.clientToProxyRequest.headers["Origin"] || 70 | ctx.clientToProxyRequest.headers["origin"] || 71 | ctx.clientToProxyRequest.headers["ORIGIN"]) && 72 | // Has Access-Control-Request-Method header 73 | (ctx.clientToProxyRequest.headers["Access-Control-Request-Method"] || 74 | ctx.clientToProxyRequest.headers["access-control-request-method"] || 75 | ctx.clientToProxyRequest.headers["ACCESS-CONTROL-REQUEST-METHOD"])) 76 | return true; 77 | else 78 | return false; 79 | }; 80 | exports.is_request_preflight = is_request_preflight; 81 | const get_response_options = (ctx) => { 82 | const options = {}; 83 | options.status_code = ctx.serverToProxyResponse.statusCode; 84 | options.headers = ctx.serverToProxyResponse.headers; 85 | return options; 86 | }; 87 | exports.get_response_options = get_response_options; 88 | const get_request_options = (ctx) => { 89 | return { 90 | ...ctx.proxyToServerRequestOptions, 91 | query_params: (0, exports.get_json_query_params)(ctx), 92 | }; 93 | }; 94 | exports.get_request_options = get_request_options; 95 | const get_json_query_params = (ctx) => { 96 | const url = (0, exports.get_request_url)(ctx); 97 | let queryString = extractUrlComponent(url, requestly_core_1.CONSTANTS.URL_COMPONENTS.QUERY); 98 | return getQueryParamsMap(queryString) || null; 99 | }; 100 | exports.get_json_query_params = get_json_query_params; 101 | const getRequestHeaders = (ctx) => { 102 | if (ctx && ctx.proxyToServerRequestOptions) { 103 | return ctx.proxyToServerRequestOptions.headers || {}; 104 | } 105 | return {}; 106 | }; 107 | exports.getRequestHeaders = getRequestHeaders; 108 | const getRequestContentTypeHeader = (ctx) => { 109 | return (0, exports.getRequestHeaders)(ctx)["content-type"]; 110 | }; 111 | exports.getRequestContentTypeHeader = getRequestContentTypeHeader; 112 | const getResponseHeaders = (ctx) => { 113 | if (ctx && ctx.serverToProxyResponse) { 114 | return ctx.serverToProxyResponse.headers || {}; 115 | } 116 | return {}; 117 | }; 118 | exports.getResponseHeaders = getResponseHeaders; 119 | const getResponseContentTypeHeader = (ctx) => { 120 | return (0, exports.getResponseHeaders)(ctx)["content-type"]; 121 | }; 122 | exports.getResponseContentTypeHeader = getResponseContentTypeHeader; 123 | const getResponseStatusCode = (ctx) => { 124 | if (ctx && ctx.serverToProxyResponse) { 125 | return ctx.serverToProxyResponse.statusCode; 126 | } 127 | }; 128 | exports.getResponseStatusCode = getResponseStatusCode; 129 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/response_helper.d.ts: -------------------------------------------------------------------------------- 1 | export function isResponseHTML(ctx: any): boolean; 2 | export function parseStringToDOM(string: any): Document; 3 | export function parseDOMToString(DOM: any): any; 4 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/response_helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.parseDOMToString = exports.parseStringToDOM = exports.isResponseHTML = void 0; 4 | const isResponseHTML = (ctx) => { 5 | return (ctx.serverToProxyResponse.headers["content-type"] && 6 | ctx.serverToProxyResponse.headers["content-type"].indexOf("text/html") === 0); 7 | }; 8 | exports.isResponseHTML = isResponseHTML; 9 | const parseStringToDOM = (string) => { 10 | return new DOMParser().parseFromString(string, "text/html"); 11 | }; 12 | exports.parseStringToDOM = parseStringToDOM; 13 | const parseDOMToString = (DOM) => { 14 | return DOM.documentElement.outerHTML; 15 | // return new XMLSerializer().serializeToString(DOM); 16 | }; 17 | exports.parseDOMToString = parseDOMToString; 18 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/helpers/rule_processor_helper.d.ts: -------------------------------------------------------------------------------- 1 | export default RuleProcessorHelper; 2 | declare class RuleProcessorHelper { 3 | constructor(request_data?: any, response_data?: any); 4 | request_data: any; 5 | response_data: any; 6 | init_request_data: (request_data: any) => void; 7 | init_response_data: (response_data: any) => void; 8 | process_rules: (rules: any, is_response?: boolean) => any; 9 | process_rule: (rule: any, is_response?: boolean) => any; 10 | add_rule_details_to_action: (rule_action: any, rule: any) => any; 11 | process_url_modification_rule: (rule_processor: any, rule: any) => any; 12 | process_request_modification_rule: (rule_processor: any, rule: any) => any; 13 | process_response_modification_rule: (rule_processor: any, rule: any) => any; 14 | process_request_headers_modification_rule: (rule_processor: any, rule: any) => any; 15 | process_response_headers_modification_rule: (rule_processor: any, rule: any) => any; 16 | process_user_agent_modification_rule: (rule_processor: any, rule: any) => any; 17 | } 18 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/index.d.ts: -------------------------------------------------------------------------------- 1 | export namespace MIDDLEWARE_TYPE { 2 | let AMIUSING: string; 3 | let RULES: string; 4 | let LOGGER: string; 5 | let SSL_CERT: string; 6 | let GLOBAL_STATE: string; 7 | } 8 | export default ProxyMiddlewareManager; 9 | declare class ProxyMiddlewareManager { 10 | constructor(proxy: any, proxyConfig: any, rulesHelper: any, loggerService: any, sslConfigFetcher: any); 11 | config: {}; 12 | proxy: any; 13 | proxyConfig: any; 14 | rulesHelper: any; 15 | loggerService: any; 16 | sslConfigFetcher: any; 17 | init_config: (config?: {}) => void; 18 | init: (config?: {}) => void; 19 | request_handler_idx: number; 20 | init_request_handler: (fn: any, is_detachable?: boolean) => number; 21 | init_amiusing_handler: () => void; 22 | init_ssl_cert_handler: () => void; 23 | init_main_handler: () => void; 24 | init_ssl_tunneling_handler: () => void; 25 | init_handlers: () => void; 26 | } 27 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/amiusing_middleware.d.ts: -------------------------------------------------------------------------------- 1 | export default AmisuingMiddleware; 2 | declare class AmisuingMiddleware { 3 | constructor(is_active: any); 4 | is_active: any; 5 | on_request: (ctx: any) => Promise; 6 | } 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/amiusing_middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class AmisuingMiddleware { 4 | constructor(is_active) { 5 | this.on_request = async (ctx) => { 6 | if (!this.is_active) { 7 | return true; 8 | } 9 | if (ctx.proxyToServerRequestOptions.host === "amiusing.requestly.io") { 10 | Object.assign(ctx.proxyToServerRequestOptions.headers, { 11 | ["amiusingrequestly"]: "true", 12 | }); 13 | } 14 | }; 15 | this.is_active = is_active; 16 | } 17 | } 18 | exports.default = AmisuingMiddleware; 19 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/logger_middleware.d.ts: -------------------------------------------------------------------------------- 1 | export default LoggerMiddleware; 2 | declare class LoggerMiddleware { 3 | constructor(is_active: any, loggerService: any); 4 | is_active: any; 5 | loggerService: any; 6 | generate_curl_from_har: (requestHarObject: any) => string; 7 | send_network_log: (ctx: any, action_result_objs?: any[], requestState?: string) => void; 8 | createLog: (ctx: any, action_result_objs?: any[], requestState?: string) => { 9 | id: any; 10 | timestamp: number; 11 | finalHar: { 12 | log: { 13 | version: string; 14 | creator: {}; 15 | browser: {}; 16 | pages: any[]; 17 | entries: { 18 | startedDateTime: string; 19 | request: { 20 | bodySize: number; 21 | headersSize: number; 22 | httpVersion: string; 23 | cookies: any[]; 24 | headers: { 25 | name: string; 26 | value: any; 27 | }[]; 28 | method: any; 29 | queryString: any[]; 30 | url: string; 31 | postData: { 32 | mimeType: any; 33 | text: any; 34 | }; 35 | }; 36 | response: { 37 | status: any; 38 | httpVersion: string; 39 | cookies: any[]; 40 | headers: { 41 | name: string; 42 | value: any; 43 | }[]; 44 | content: { 45 | size: number; 46 | compression: number; 47 | mimeType: any; 48 | text: any; 49 | comment: string; 50 | }; 51 | headersSize: number; 52 | bodySize: number; 53 | comment: string; 54 | }; 55 | cache: {}; 56 | timings: {}; 57 | comment: string; 58 | }[]; 59 | comment: string; 60 | }; 61 | }; 62 | requestShellCurl: string; 63 | actions: any[]; 64 | consoleLogs: any; 65 | requestState: string; 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/logger_middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // import { cloneDeep } from "lodash"; 4 | // import HTTPSnippet from "httpsnippet"; 5 | const utils_1 = require("../rule_action_processor/utils"); 6 | const harObectCreator_1 = require("../helpers/harObectCreator"); 7 | // const url = require("url"); 8 | class LoggerMiddleware { 9 | constructor(is_active, loggerService) { 10 | this.generate_curl_from_har = (requestHarObject) => { 11 | if (!requestHarObject) { 12 | return ""; 13 | } 14 | let requestCurl = ""; 15 | // try { 16 | // const harObject = cloneDeep(requestHarObject); 17 | // requestCurl = new HTTPSnippet(harObject).convert("shell", "curl", { 18 | // indent: " ", 19 | // }); 20 | // } catch (err) { 21 | // console.error(`LoggerMiddleware.generate_curl_from_har Error: ${err}`); 22 | // } 23 | return requestCurl; 24 | }; 25 | this.send_network_log = (ctx, action_result_objs = [], requestState = "") => { 26 | // let query_params_string; 27 | // if (ctx.rq.final_request.query_params) { 28 | // query_params_string = JSON.stringify(ctx.rq.final_request.query_params); 29 | // } 30 | // const log = { 31 | // id: ctx.uuid, 32 | // timestamp: Math.floor(Date.now() / 1000), 33 | // url: url.parse( 34 | // (ctx.isSSL ? "https://" : "http://") + 35 | // ctx.clientToProxyRequest.headers.host + 36 | // ctx.clientToProxyRequest.url 37 | // ).href, 38 | // request: { 39 | // method: ctx.rq.final_request.method, 40 | // path: ctx.rq.final_request.host, 41 | // host: ctx.rq.final_request.host, 42 | // port: ctx.rq.final_request.port, 43 | // headers: ctx.rq.final_request.headers, 44 | // body: ctx.rq.final_request.body, 45 | // query_params: query_params_string, 46 | // }, 47 | // requestShellCurl: this.generate_curl_from_har( 48 | // ctx.rq.final_request.requestHarObject 49 | // ), 50 | // response: { 51 | // statusCode: ctx.rq.final_response.status_code, 52 | // headers: ctx.rq.final_response.headers || {}, 53 | // contentType: 54 | // (ctx.rq.final_response.headers && 55 | // ctx.rq.final_response.headers["content-type"] && 56 | // ctx.rq.final_response.headers["content-type"].includes(";") && 57 | // ctx.rq.final_response.headers["content-type"].split(";")[0]) || 58 | // (ctx.rq.final_response.headers && 59 | // ctx.rq.final_response.headers["content-type"]), 60 | // body: ctx.rq.final_response.body || null, 61 | // }, 62 | // actions: get_success_actions_from_action_results(action_result_objs), 63 | // }; 64 | // ipcRenderer.send("log-network-request", log); 65 | // TODO: Sending log for now. Ideally this should be har object 66 | this.loggerService.addLog(this.createLog(ctx, action_result_objs, requestState), ctx.rq.final_request.headers || {}); 67 | }; 68 | this.createLog = (ctx, action_result_objs = [], requestState = "") => { 69 | var _a, _b, _c; 70 | const protocol = ctx.isSSL ? "https" : "http"; 71 | const rqLog = { 72 | id: ctx.uuid, 73 | timestamp: Math.floor(Date.now() / 1000), 74 | finalHar: (0, harObectCreator_1.createHar)(ctx.rq.final_request.headers, ctx.rq.final_request.method, protocol, ctx.rq.final_request.host, ctx.rq.final_request.path, ctx.rq.final_request.body, ctx.rq.final_response.status_code, ctx.rq.final_response.body, ctx.rq.final_response.headers || {}, ctx.rq.final_request.query_params), 75 | requestShellCurl: this.generate_curl_from_har((_b = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.rq) === null || _a === void 0 ? void 0 : _a.final_request) === null || _b === void 0 ? void 0 : _b.requestHarObject), // TODO: Move this to client side 76 | actions: (0, utils_1.get_success_actions_from_action_results)(action_result_objs), 77 | consoleLogs: (_c = ctx === null || ctx === void 0 ? void 0 : ctx.rq) === null || _c === void 0 ? void 0 : _c.consoleLogs, 78 | requestState 79 | }; 80 | return rqLog; 81 | }; 82 | this.is_active = is_active; 83 | this.loggerService = loggerService; 84 | } 85 | } 86 | exports.default = LoggerMiddleware; 87 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/rules_middleware.d.ts: -------------------------------------------------------------------------------- 1 | export default RulesMiddleware; 2 | declare class RulesMiddleware { 3 | constructor(is_active: any, ctx: any, rulesHelper: any); 4 | is_active: any; 5 | rule_processor_helper: RuleProcessorHelper; 6 | rule_action_processor: RuleActionProcessor; 7 | rulesHelper: any; 8 | active_rules: any[]; 9 | on_request_actions: any[]; 10 | on_response_actions: any[]; 11 | action_result_objs: any[]; 12 | _init_request_data: (ctx: any) => void; 13 | request_data: any; 14 | _init_response_data: (ctx: any) => void; 15 | response_data: { 16 | response_headers: any; 17 | }; 18 | _update_request_data: (data: any) => void; 19 | _fetch_rules: () => Promise; 20 | _process_rules: (is_response?: boolean) => any; 21 | _update_action_result_objs: (action_result_objs?: any[]) => void; 22 | on_request: (ctx: any) => Promise; 26 | on_response: (ctx: any) => Promise; 30 | on_request_end: (ctx: any) => Promise; 34 | on_response_end: (ctx: any) => Promise; 38 | } 39 | import RuleProcessorHelper from "../helpers/rule_processor_helper"; 40 | import RuleActionProcessor from "../rule_action_processor"; 41 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/rules_middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const proxy_ctx_helper_1 = require("../helpers/proxy_ctx_helper"); 7 | const rule_processor_helper_1 = __importDefault(require("../helpers/rule_processor_helper")); 8 | const rule_action_processor_1 = __importDefault(require("../rule_action_processor")); 9 | class RulesMiddleware { 10 | constructor(is_active, ctx, rulesHelper) { 11 | this._init_request_data = (ctx) => { 12 | var _a, _b, _c, _d; 13 | this.request_data = { 14 | request_url: (0, proxy_ctx_helper_1.get_request_url)(ctx), 15 | request_headers: (0, proxy_ctx_helper_1.get_original_request_headers)(ctx), 16 | query_params: (_b = (_a = ctx.rq) === null || _a === void 0 ? void 0 : _a.original_request) === null || _b === void 0 ? void 0 : _b.query_params, 17 | method: (_d = (_c = ctx.rq) === null || _c === void 0 ? void 0 : _c.original_request) === null || _d === void 0 ? void 0 : _d.method, 18 | }; 19 | this.rule_processor_helper.init_request_data(this.request_data); 20 | }; 21 | this._init_response_data = (ctx) => { 22 | this.response_data = { 23 | response_headers: (0, proxy_ctx_helper_1.get_original_response_headers)(ctx), 24 | }; 25 | this.rule_processor_helper.init_response_data(this.response_data); 26 | }; 27 | this._update_request_data = (data) => { 28 | this.request_data = { 29 | ...this.request_data, 30 | ...data, 31 | }; 32 | this.rule_processor_helper.init_request_data(this.request_data); 33 | }; 34 | this._fetch_rules = async () => { 35 | var _a; 36 | this.active_rules = await this.rulesHelper.get_rules(true, ((_a = this.request_data) === null || _a === void 0 ? void 0 : _a.request_headers) || {}); 37 | }; 38 | /* 39 | @return: actions[] 40 | */ 41 | this._process_rules = (is_response = false) => { 42 | // https://github.com/requestly/requestly-master/issues/686 43 | // 1 time processing if we fix this issue 44 | let rule_actions = this.rule_processor_helper.process_rules(this.active_rules, is_response); 45 | // Filter out all the null actions 46 | rule_actions = rule_actions.filter((action) => !!action); 47 | return rule_actions; 48 | }; 49 | this._update_action_result_objs = (action_result_objs = []) => { 50 | if (action_result_objs) { 51 | this.action_result_objs = 52 | this.action_result_objs.concat(action_result_objs); 53 | } 54 | }; 55 | this.on_request = async (ctx) => { 56 | if (!this.is_active) { 57 | return []; 58 | } 59 | // TODO: Remove this. Hack to fix rule not fetching first time. 60 | await this._fetch_rules(); 61 | this.on_request_actions = this._process_rules(); 62 | const { action_result_objs, continue_request } = await this.rule_action_processor.process_actions(this.on_request_actions, ctx); 63 | this._update_action_result_objs(action_result_objs); 64 | return { action_result_objs, continue_request }; 65 | }; 66 | this.on_response = async (ctx) => { 67 | if (!this.is_active) { 68 | return []; 69 | } 70 | this._init_response_data(ctx); 71 | this.on_response_actions = this._process_rules(true); 72 | const { action_result_objs, continue_request } = await this.rule_action_processor.process_actions(this.on_response_actions, ctx); 73 | this._update_action_result_objs(action_result_objs); 74 | return { action_result_objs, continue_request }; 75 | }; 76 | this.on_request_end = async (ctx) => { 77 | if (!this.is_active) { 78 | return []; 79 | } 80 | this._update_request_data({ request_body: ctx.rq.get_json_request_body() }); 81 | this.on_request_actions = this._process_rules(); 82 | const { action_result_objs, continue_request } = await this.rule_action_processor.process_actions(this.on_request_actions, ctx); 83 | this._update_action_result_objs(action_result_objs); 84 | return { action_result_objs, continue_request }; 85 | }; 86 | this.on_response_end = async (ctx) => { 87 | if (!this.is_active) { 88 | return []; 89 | } 90 | const { action_result_objs, continue_request } = await this.rule_action_processor.process_actions(this.on_response_actions, ctx); 91 | this._update_action_result_objs(action_result_objs); 92 | return { action_result_objs, continue_request }; 93 | }; 94 | this.is_active = is_active; 95 | this.rule_processor_helper = new rule_processor_helper_1.default(); 96 | this.rule_action_processor = new rule_action_processor_1.default(); 97 | this.rulesHelper = rulesHelper; 98 | this._init_request_data(ctx); 99 | this.active_rules = []; 100 | this._fetch_rules(); 101 | // Keeping these 2 separate because of request and response headers 102 | // from rule processor are triggers during different proxy hooks. 103 | // These can be combined into 1 if we change the actions returned 104 | // by modify headers rule processor 105 | // TODO: @sahil865gupta UPGRADE MODIFY HEADER ACTIONS 106 | // https://github.com/requestly/requestly-master/issues/686 107 | this.on_request_actions = []; 108 | this.on_response_actions = []; 109 | this.action_result_objs = []; 110 | } 111 | } 112 | exports.default = RulesMiddleware; 113 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/ssl_cert_middleware.d.ts: -------------------------------------------------------------------------------- 1 | export default SslCertMiddleware; 2 | declare class SslCertMiddleware { 3 | constructor(is_active: any, rootCertPath: any); 4 | is_active: any; 5 | rootCertPath: any; 6 | on_request: (ctx: any) => Promise; 7 | } 8 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/ssl_cert_middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fs = require("fs"); 4 | class SslCertMiddleware { 5 | constructor(is_active, rootCertPath) { 6 | this.on_request = async (ctx) => { 7 | if (!this.is_active) { 8 | return true; 9 | } 10 | if (ctx.clientToProxyRequest.headers.host == "requestly.io" && 11 | ctx.clientToProxyRequest.url.indexOf("/ssl") == 0) { 12 | ctx.proxyToClientResponse.writeHead(200, { 13 | "Content-Type": "application/x-x509-ca-cert", 14 | "Content-Disposition": "attachment;filename=RQProxyCA.pem.crt", 15 | }); 16 | const certificateString = fs.readFileSync(this.rootCertPath); 17 | ctx.proxyToClientResponse.end(certificateString); 18 | } 19 | }; 20 | this.is_active = is_active; 21 | this.rootCertPath = rootCertPath; 22 | } 23 | } 24 | exports.default = SslCertMiddleware; 25 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/state.d.ts: -------------------------------------------------------------------------------- 1 | export declare class State { 2 | private state; 3 | constructor(sharedState: Record, envVars: Record); 4 | setSharedState(newSharedState: Record): void; 5 | getSharedStateRef(): Record; 6 | getSharedStateCopy(): any; 7 | setVariables(newVariables: Record): void; 8 | getVariablesRef(): Record; 9 | getVariablesCopy(): any; 10 | } 11 | export default class GlobalStateProvider { 12 | private static instance; 13 | static initInstance(sharedState?: Record, envVars?: Record): State; 14 | static getInstance(): State; 15 | } 16 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/middlewares/state.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.State = void 0; 4 | const lodash_1 = require("lodash"); 5 | class State { 6 | constructor(sharedState, envVars) { 7 | this.state = { 8 | variables: envVars, 9 | sharedState, 10 | }; 11 | } 12 | setSharedState(newSharedState) { 13 | this.state.sharedState = newSharedState; 14 | } 15 | getSharedStateRef() { 16 | return this.state.sharedState; 17 | } 18 | getSharedStateCopy() { 19 | return (0, lodash_1.cloneDeep)(this.state.sharedState); 20 | } 21 | setVariables(newVariables) { 22 | this.state.variables = newVariables; 23 | } 24 | getVariablesRef() { 25 | return this.state.variables; 26 | } 27 | getVariablesCopy() { 28 | return (0, lodash_1.cloneDeep)(this.state.variables); 29 | } 30 | } 31 | exports.State = State; 32 | class GlobalStateProvider { 33 | static initInstance(sharedState = {}, envVars = {}) { 34 | if (!GlobalStateProvider.instance) { 35 | GlobalStateProvider.instance = new State(sharedState !== null && sharedState !== void 0 ? sharedState : {}, envVars !== null && envVars !== void 0 ? envVars : {}); 36 | } 37 | return GlobalStateProvider.instance; 38 | } 39 | static getInstance() { 40 | if (!GlobalStateProvider.instance) { 41 | console.error("[GlobalStateProvider]", "Init first"); 42 | } 43 | return GlobalStateProvider.instance; 44 | } 45 | } 46 | exports.default = GlobalStateProvider; 47 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/handle_mixed_response.d.ts: -------------------------------------------------------------------------------- 1 | export default handleMixedResponse; 2 | declare function handleMixedResponse(ctx: any, destinationUrl: any): Promise<{ 3 | status: boolean; 4 | response_data: { 5 | headers: { 6 | "Cache-Control": string; 7 | }; 8 | status_code: number; 9 | body: any; 10 | }; 11 | } | { 12 | status: boolean; 13 | response_data: { 14 | headers: { 15 | "content-type": any; 16 | "Content-Length": number; 17 | "Cache-Control": string; 18 | } | { 19 | "Cache-Control": string; 20 | "content-type"?: undefined; 21 | "Content-Length"?: undefined; 22 | }; 23 | status_code: number; 24 | body: string; 25 | }; 26 | } | { 27 | status: boolean; 28 | response_data?: undefined; 29 | }>; 30 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/handle_mixed_response.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 __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | const axios = require("axios"); 30 | const parser = require("ua-parser-js"); 31 | const fs_1 = __importDefault(require("fs")); 32 | const Sentry = __importStar(require("@sentry/browser")); 33 | const mime = require('mime-types'); 34 | const handleMixedResponse = async (ctx, destinationUrl) => { 35 | var _a, _b, _c; 36 | // Handling mixed response from safari 37 | let user_agent_str = null; 38 | user_agent_str = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.clientToProxyRequest) === null || _a === void 0 ? void 0 : _a.headers["user-agent"]; 39 | const user_agent = (_c = (_b = parser(user_agent_str)) === null || _b === void 0 ? void 0 : _b.browser) === null || _c === void 0 ? void 0 : _c.name; 40 | const LOCAL_DOMAINS = ["localhost", "127.0.0.1"]; 41 | if (ctx.isSSL && destinationUrl.includes("http:")) { 42 | if (user_agent === "Safari" || 43 | !LOCAL_DOMAINS.some((domain) => destinationUrl.includes(domain))) { 44 | try { 45 | const resp = await axios.get(destinationUrl, { 46 | headers: { 47 | "Cache-Control": "no-cache", 48 | }, 49 | }); 50 | return { 51 | status: true, 52 | response_data: { 53 | headers: { "Cache-Control": "no-cache" }, 54 | status_code: 200, 55 | body: resp.data, 56 | }, 57 | }; 58 | } 59 | catch (e) { 60 | Sentry.captureException(e); 61 | return { 62 | status: true, 63 | response_data: { 64 | headers: { "Cache-Control": "no-cache" }, 65 | status_code: 502, 66 | body: e.response ? e.response.data : null, 67 | }, 68 | }; 69 | } 70 | } 71 | } 72 | if (destinationUrl === null || destinationUrl === void 0 ? void 0 : destinationUrl.startsWith("file://")) { 73 | const path = destinationUrl.slice(7); 74 | try { 75 | const buffers = fs_1.default.readFileSync(path); 76 | const mimeType = mime.lookup(path); 77 | const bodyContent = buffers.toString("utf-8"); // assuming utf-8 encoding 78 | const headers = mimeType ? { 79 | "content-type": mimeType, 80 | "Content-Length": Buffer.byteLength(bodyContent), 81 | "Cache-Control": "no-cache" 82 | } : { 83 | "Cache-Control": "no-cache" 84 | }; 85 | headers["access-control-allow-origin"] = "*"; 86 | headers["access-control-allow-credentials"] = "true"; 87 | headers["access-control-allow-methods"] = "*"; 88 | headers["access-control-allow-headers"] = "*"; 89 | return { 90 | status: true, 91 | response_data: { 92 | headers, 93 | status_code: 200, 94 | body: buffers.toString("utf-8"), // assuming utf-8 encoding 95 | }, 96 | }; 97 | } 98 | catch (err) { 99 | Sentry.captureException(err); 100 | // log for live debugging 101 | console.log("error in openning local file", err); 102 | return { 103 | status: true, 104 | response_data: { 105 | headers: { "Cache-Control": "no-cache" }, 106 | status_code: 502, 107 | body: err === null || err === void 0 ? void 0 : err.message, 108 | }, 109 | }; 110 | } 111 | } 112 | return { status: false }; 113 | }; 114 | exports.default = handleMixedResponse; 115 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/index.d.ts: -------------------------------------------------------------------------------- 1 | export default RuleActionProcessor; 2 | declare class RuleActionProcessor { 3 | process_actions: (rule_actions: any, ctx: any) => Promise<{ 4 | action_result_objs: any[]; 5 | continue_request: boolean; 6 | }>; 7 | post_process_actions: (action_result_objs: any, ctx: any) => boolean; 8 | process_action: (rule_action: any, ctx: any) => Promise<{ 9 | action: any; 10 | success: boolean; 11 | post_process_data: any; 12 | }>; 13 | } 14 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const constants_1 = require("../constants"); 7 | const redirect_processor_1 = __importDefault(require("./processors/redirect_processor")); 8 | const modify_header_processor_1 = __importDefault(require("./processors/modify_header_processor")); 9 | const modify_user_agent_processor_1 = __importDefault(require("./processors/modify_user_agent_processor")); 10 | const delay_processor_1 = __importDefault(require("./processors/delay_processor")); 11 | const block_processor_1 = __importDefault(require("./processors/block_processor")); 12 | const modify_response_processor_1 = __importDefault(require("./processors/modify_response_processor")); 13 | const modify_request_processor_1 = __importDefault(require("./processors/modify_request_processor")); 14 | const insert_processor_1 = __importDefault(require("./processors/insert_processor")); 15 | const utils_1 = require("./utils"); 16 | class RuleActionProcessor { 17 | constructor() { 18 | /* 19 | @return: Whether to continue with the proxy callback. & of all the continue_request from actions 20 | */ 21 | this.process_actions = async (rule_actions, ctx) => { 22 | /* 23 | action_result_objs = [{action: {}, result: {}}}] 24 | */ 25 | const action_result_objs = await Promise.all(rule_actions.map(async (action) => { 26 | let action_result_obj = await this.process_action(action, ctx); 27 | return action_result_obj; 28 | })); 29 | let continue_request = true; 30 | continue_request = this.post_process_actions(action_result_objs, ctx); 31 | return { action_result_objs, continue_request }; 32 | }; 33 | this.post_process_actions = (action_result_objs, ctx) => { 34 | let continue_request = true; 35 | action_result_objs.forEach((action_result) => { 36 | if (!continue_request) 37 | return; // Already finished the request 38 | if (!action_result.post_process_data) 39 | return; // No post processing 40 | const status_code = action_result.post_process_data.status_code || 200; 41 | const headers = action_result.post_process_data.headers || {}; 42 | let body = action_result.post_process_data.body || null; 43 | // console.log("Log", ctx.rq.original_request); 44 | if (typeof (body) !== 'string') { 45 | body = JSON.stringify(body); 46 | } 47 | ctx.proxyToClientResponse.writeHead(status_code, headers).end(body); 48 | ctx.rq.request_finished = true; 49 | ctx.rq.set_final_response({ 50 | status_code: status_code, 51 | headers: headers, 52 | body: body, 53 | }); 54 | continue_request = false; 55 | }); 56 | return continue_request; 57 | }; 58 | /* 59 | @return: Whether to continue with the proxy callback 60 | */ 61 | this.process_action = async (rule_action, ctx) => { 62 | let action_result = (0, utils_1.build_action_processor_response)(rule_action, false); 63 | if (!rule_action) { 64 | return action_result; 65 | } 66 | switch (rule_action.action) { 67 | case constants_1.RULE_ACTION.REDIRECT: 68 | action_result = (0, redirect_processor_1.default)(rule_action, ctx); 69 | break; 70 | case constants_1.RULE_ACTION.MODIFY_HEADERS: 71 | action_result = (0, modify_header_processor_1.default)(rule_action, ctx); 72 | break; 73 | case constants_1.RULE_ACTION.MODIFY_USER_AGENT: 74 | action_result = (0, modify_user_agent_processor_1.default)(rule_action, ctx); 75 | case constants_1.RULE_ACTION.DELAY: 76 | action_result = await (0, delay_processor_1.default)(rule_action, ctx); 77 | break; 78 | case constants_1.RULE_ACTION.BLOCK: 79 | action_result = (0, block_processor_1.default)(rule_action, ctx); 80 | break; 81 | case constants_1.RULE_ACTION.MODIFY_REQUEST: 82 | action_result = (0, modify_request_processor_1.default)(rule_action, ctx); 83 | break; 84 | case constants_1.RULE_ACTION.MODIFY_RESPONSE: 85 | action_result = await (0, modify_response_processor_1.default)(rule_action, ctx); 86 | break; 87 | case constants_1.RULE_ACTION.INSERT: 88 | action_result = (0, insert_processor_1.default)(rule_action, ctx); 89 | default: 90 | break; 91 | } 92 | return action_result; 93 | }; 94 | } 95 | } 96 | exports.default = RuleActionProcessor; 97 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/modified_requests_pool.d.ts: -------------------------------------------------------------------------------- 1 | export default modifiedRequestsPool; 2 | declare const modifiedRequestsPool: ModifiedRequestsPool; 3 | declare class ModifiedRequestsPool { 4 | queue: any; 5 | add(url: any): void; 6 | isURLModified(url: any): boolean; 7 | } 8 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/modified_requests_pool.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const circularQueue_1 = __importDefault(require("../../../utils/circularQueue")); 7 | class ModifiedRequestsPool { 8 | constructor() { 9 | this.queue = new circularQueue_1.default(100); 10 | } 11 | add(url) { 12 | url = url.replace(/www\./g, ""); 13 | this.queue.enQueue(url); 14 | // logger.log(this.queue); 15 | } 16 | isURLModified(url) { 17 | if (!url) 18 | return false; 19 | // logger.log("Current Url : ", url); 20 | // logger.log(JSON.stringify(this.queue), "Looper"); 21 | let tempUrl = url; 22 | if (url.endsWith("/")) { 23 | tempUrl = tempUrl.slice(0, tempUrl.length - 1); 24 | } 25 | else { 26 | tempUrl = tempUrl + "/"; 27 | } 28 | // logger.log(tempUrl); 29 | return (this.queue.getElementIndex(url) >= 0 || 30 | this.queue.getElementIndex(tempUrl) >= 0); 31 | } 32 | } 33 | const modifiedRequestsPool = new ModifiedRequestsPool(); 34 | exports.default = modifiedRequestsPool; 35 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/block_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_block_action; 2 | declare function process_block_action(action: any, ctx: any): Promise<{ 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }>; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/block_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const utils_1 = require("../utils"); 5 | const process_block_action = async (action, ctx) => { 6 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST]; 7 | if (!allowed_handlers.includes(ctx.currentHandler)) { 8 | return (0, utils_1.build_action_processor_response)(action, false); 9 | } 10 | return (0, utils_1.build_action_processor_response)(action, true, (0, utils_1.build_post_process_data)(418 /** Move this to temporarily out of coffee (503) if causes issues in someone production use case. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 */, { "Cache-Control": "no-cache" }, "Access to this URL has been blocked by Requestly")); 11 | }; 12 | exports.default = process_block_action; 13 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/delay_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_delay_action; 2 | declare function process_delay_action(action: any, ctx: any): Promise<{ 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }>; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/delay_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const utils_1 = require("../utils"); 5 | const resolveAfterDelay = async (durationInMs) => { 6 | return new Promise((resolve) => { 7 | setTimeout(() => { 8 | resolve("resolved"); 9 | }, durationInMs); 10 | }); 11 | }; 12 | const process_delay_action = async (action, ctx) => { 13 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST]; 14 | if (!allowed_handlers.includes(ctx.currentHandler)) { 15 | return (0, utils_1.build_action_processor_response)(action, false); 16 | } 17 | await resolveAfterDelay(action.delay); 18 | return (0, utils_1.build_action_processor_response)(action, true); 19 | }; 20 | exports.default = process_delay_action; 21 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/insert_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_insert_action; 2 | declare function process_insert_action(action: any, ctx: any): { 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/insert_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const requestly_core_1 = require("@requestly/requestly-core"); 5 | const response_helper_1 = require("../../helpers/response_helper"); 6 | const utils_1 = require("../utils"); 7 | const process_insert_action = (action, ctx) => { 8 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_RESPONSE_END]; 9 | if (!allowed_handlers.includes(ctx.currentHandler)) { 10 | return (0, utils_1.build_action_processor_response)(action, false); 11 | } 12 | const success = insert_scripts(ctx, action); 13 | return (0, utils_1.build_action_processor_response)(action, success); 14 | }; 15 | const modify_response = (ctx, new_resp) => { 16 | ctx.rq_response_body = new_resp; 17 | }; 18 | const insert_scripts = (ctx, action) => { 19 | if ((0, response_helper_1.isResponseHTML)(ctx)) { 20 | const fullResponseBodyString = ctx.rq_response_body; 21 | // Double check to ensure content is HTML 22 | if (/<\/?[a-z][\s\S]*>/i.test(fullResponseBodyString)) { 23 | // Parse Response body (string) to DOM 24 | let incomingDOM = (0, response_helper_1.parseStringToDOM)(fullResponseBodyString); 25 | // Modify DOM to inject scripts 26 | handleCSSScripts(action.data.cssScripts, incomingDOM); 27 | handleJSLibraries(action.data.libraries, incomingDOM); 28 | handleJSScripts(action.data.jsScripts, incomingDOM); 29 | // Parse back DOM to String to forward client as response body 30 | const final_response_body = (0, response_helper_1.parseDOMToString)(incomingDOM); 31 | modify_response(ctx, final_response_body); 32 | return true; 33 | } 34 | } 35 | return false; 36 | }; 37 | /** 38 | * Handles all CSS Scripts 39 | */ 40 | const handleCSSScripts = (cssScripts, incomingDOM) => { 41 | cssScripts.forEach((script) => { 42 | includeCSS(script, incomingDOM); 43 | }); 44 | }; 45 | /** 46 | * Prepares to inject a CSS Scripts 47 | * @returns {string} HTML Content 48 | */ 49 | const includeCSS = (script, incomingDOM) => { 50 | if (script.type === requestly_core_1.CONSTANTS.SCRIPT_TYPES.URL) { 51 | addRemoteCSS(script.value, incomingDOM); 52 | } 53 | else if (script.type === requestly_core_1.CONSTANTS.SCRIPT_TYPES.CODE) { 54 | embedCSS(script.value, incomingDOM); 55 | } 56 | }; 57 | const addRemoteCSS = (src, incomingDOM) => { 58 | var link = incomingDOM.createElement("link"); 59 | link.href = src; 60 | link.type = "text/css"; 61 | link.rel = "stylesheet"; 62 | link.className = getScriptClassAttribute(); 63 | (incomingDOM.head || incomingDOM.documentElement).appendChild(link); 64 | }; 65 | const getScriptClassAttribute = () => { 66 | return requestly_core_1.CONSTANTS.PUBLIC_NAMESPACE + "SCRIPT"; 67 | }; 68 | const embedCSS = (css, incomingDOM) => { 69 | var style = incomingDOM.createElement("style"); 70 | style.type = "text/css"; 71 | style.appendChild(incomingDOM.createTextNode(css)); 72 | style.className = getScriptClassAttribute(); 73 | (incomingDOM.head || incomingDOM.documentElement).appendChild(style); 74 | }; 75 | const handleJSLibraries = (libraries, incomingDOM) => { 76 | addLibraries(libraries, null, incomingDOM); 77 | }; 78 | const addLibraries = (libraries, indexArg, incomingDOM) => { 79 | var index = indexArg || 0; 80 | if (index >= libraries.length) { 81 | return; 82 | } 83 | var libraryKey = libraries[index]; 84 | var library = requestly_core_1.CONSTANTS.SCRIPT_LIBRARIES[libraryKey]; 85 | var addNextLibraries = () => { 86 | addLibraries(libraries, index + 1, incomingDOM); 87 | }; 88 | if (library) { 89 | addRemoteJS(library.src, addNextLibraries, incomingDOM); 90 | } 91 | else { 92 | addNextLibraries(); 93 | } 94 | }; 95 | const addRemoteJS = (src, callback, incomingDOM) => { 96 | var script = incomingDOM.createElement("script"); 97 | // NOT WORKING 98 | // if (typeof callback === "function") { 99 | // script.onload = callback 100 | // } 101 | script.type = "text/javascript"; 102 | script.src = src; 103 | script.className = getScriptClassAttribute(); 104 | (incomingDOM.head || incomingDOM.documentElement).appendChild(script); 105 | // HOTFIX 106 | callback(); 107 | }; 108 | const handleJSScripts = (jsScripts, incomingDOM) => { 109 | var prePageLoadScripts = []; 110 | var postPageLoadScripts = []; 111 | jsScripts.forEach((script) => { 112 | if (script.loadTime === requestly_core_1.CONSTANTS.SCRIPT_LOAD_TIME.BEFORE_PAGE_LOAD) { 113 | prePageLoadScripts.push(script); 114 | } 115 | else { 116 | postPageLoadScripts.push(script); 117 | } 118 | }); 119 | includeJSScriptsInOrder(prePageLoadScripts, () => { 120 | includeJSScriptsInOrder(postPageLoadScripts, null, null, incomingDOM); 121 | }, null, incomingDOM); 122 | }; 123 | const includeJSScriptsInOrder = (scripts, callback, indexArg, incomingDOM) => { 124 | var index = indexArg || 0; 125 | if (index >= scripts.length) { 126 | typeof callback === "function" && callback(); 127 | return; 128 | } 129 | includeJS(scripts[index], () => { 130 | includeJSScriptsInOrder(scripts, callback, index + 1, incomingDOM); 131 | }, incomingDOM); 132 | }; 133 | const includeJS = (script, callback, incomingDOM) => { 134 | if (script.type === requestly_core_1.CONSTANTS.SCRIPT_TYPES.URL) { 135 | addRemoteJS(script.value, callback, incomingDOM); 136 | return; 137 | } 138 | if (script.type === requestly_core_1.CONSTANTS.SCRIPT_TYPES.CODE) { 139 | executeJS(script.value, null, incomingDOM); 140 | } 141 | typeof callback === "function" && callback(); 142 | }; 143 | const executeJS = (code, shouldRemove, incomingDOM) => { 144 | const script = incomingDOM.createElement("script"); 145 | script.type = "text/javascript"; 146 | script.className = getScriptClassAttribute(); 147 | script.appendChild(incomingDOM.createTextNode(code)); 148 | const parent = incomingDOM.head || incomingDOM.documentElement; 149 | parent.appendChild(script); 150 | if (shouldRemove) { 151 | parent.removeChild(script); 152 | } 153 | }; 154 | exports.default = process_insert_action; 155 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_header_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_modify_header_action; 2 | declare function process_modify_header_action(action: any, ctx: any): { 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_header_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const proxy_ctx_helper_1 = require("../../helpers/proxy_ctx_helper"); 5 | const utils_1 = require("../utils"); 6 | const process_modify_header_action = (action, ctx) => { 7 | const allowed_handlers = [ 8 | proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST, 9 | proxy_1.PROXY_HANDLER_TYPE.ON_RESPONSE, 10 | proxy_1.PROXY_HANDLER_TYPE.ON_ERROR, 11 | ]; 12 | if (!allowed_handlers.includes(ctx.currentHandler)) { 13 | return (0, utils_1.build_action_processor_response)(action, false); 14 | } 15 | if (ctx.currentHandler == proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST) { 16 | modify_request_headers(action, ctx); 17 | } 18 | else if (ctx.currentHandler === proxy_1.PROXY_HANDLER_TYPE.ON_RESPONSE || ctx.currentHandler === proxy_1.PROXY_HANDLER_TYPE.ON_ERROR) { 19 | modify_response_headers(action, ctx); 20 | } 21 | return (0, utils_1.build_action_processor_response)(action, true); 22 | }; 23 | const modify_request_headers = (action, ctx) => { 24 | const newRequestHeaders = action.newHeaders; 25 | for (var headerName in ctx.proxyToServerRequestOptions.headers) { 26 | if (ctx.proxyToServerRequestOptions.headers.hasOwnProperty(headerName)) { 27 | delete ctx.proxyToServerRequestOptions.headers[headerName]; 28 | } 29 | } 30 | // Set Request Headers 31 | newRequestHeaders.forEach((pair) => (ctx.proxyToServerRequestOptions.headers[pair.name] = pair.value)); 32 | }; 33 | const modify_response_headers = (action, ctx) => { 34 | ctx.serverToProxyResponse = ctx.serverToProxyResponse || {}; 35 | ctx.serverToProxyResponse.headers = ctx.serverToProxyResponse.headers || {}; 36 | // {"header1":"val1", "header2":"val2"} 37 | const originalResponseHeadersObject = ctx.serverToProxyResponse.headers; 38 | // ["header1","header2"] 39 | const originalResponseHeadersObjectKeys = Object.keys(originalResponseHeadersObject); 40 | // [{name:"header1", value:"val1"},{name:"header2", value:"val2"}] 41 | const originalResponseHeadersObjectKeysValuePairs = originalResponseHeadersObjectKeys.map((key) => { 42 | return { 43 | name: key, 44 | value: originalResponseHeadersObject[key], 45 | }; 46 | }); 47 | const requestURL = (0, proxy_ctx_helper_1.get_request_url)(ctx); 48 | const getRequestOrigin = () => { 49 | // array [{ name: header_name,value: header_val }] -> {headerName1:"value1",headerName2 :"value2"} 50 | const originalRequestHeadersConvertedObject = Object.assign({}, ...originalRequestHeaders.map((header) => ({ 51 | [header.name]: header.value, 52 | }))); 53 | if (originalRequestHeadersConvertedObject["Origin"]) 54 | return originalRequestHeadersConvertedObject["Origin"]; 55 | if (originalRequestHeadersConvertedObject["origin"]) 56 | return originalRequestHeadersConvertedObject["origin"]; 57 | if (originalRequestHeadersConvertedObject["ORIGIN"]) 58 | return originalRequestHeadersConvertedObject["ORIGIN"]; 59 | return "*"; 60 | }; 61 | const { newHeaders: newResponseHeaders } = action; 62 | // Set Response headers 63 | // Clear all existing Response headers (to handle "remove header" case) 64 | for (var headerName in ctx.serverToProxyResponse.headers) { 65 | if (ctx.serverToProxyResponse.headers.hasOwnProperty(headerName)) { 66 | delete ctx.serverToProxyResponse.headers[headerName]; 67 | } 68 | } 69 | // Set new values 70 | newResponseHeaders.forEach((pair) => (ctx.serverToProxyResponse.headers[pair.name] = pair.value)); 71 | }; 72 | exports.default = process_modify_header_action; 73 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_request_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_modify_request_action; 2 | declare function process_modify_request_action(action: any, ctx: any): { 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_request_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const requestly_core_1 = require("@requestly/requestly-core"); 5 | const proxy_ctx_helper_1 = require("../../helpers/proxy_ctx_helper"); 6 | const utils_1 = require("../utils"); 7 | const utils_2 = require("../../../../utils"); 8 | const process_modify_request_action = (action, ctx) => { 9 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST_END]; 10 | if (!allowed_handlers.includes(ctx.currentHandler)) { 11 | return (0, utils_1.build_action_processor_response)(action, false); 12 | } 13 | if (action.requestType && 14 | action.requestType === requestly_core_1.CONSTANTS.REQUEST_BODY_TYPES.CODE) { 15 | modify_request_using_code(action, ctx); 16 | return (0, utils_1.build_action_processor_response)(action, true); 17 | } 18 | else { 19 | modify_request(ctx, action.request); 20 | return (0, utils_1.build_action_processor_response)(action, true); 21 | } 22 | }; 23 | const modify_request = (ctx, new_req) => { 24 | if (new_req) 25 | ctx.rq_request_body = new_req; 26 | }; 27 | const modify_request_using_code = async (action, ctx) => { 28 | let userFunction = null; 29 | try { 30 | userFunction = (0, utils_2.getFunctionFromString)(action.request); 31 | } 32 | catch (error) { 33 | // User has provided an invalid function 34 | return modify_request(ctx, "Can't parse Requestly function. Please recheck. Error Code 7201. Actual Error: " + 35 | error.message); 36 | } 37 | if (!userFunction || typeof userFunction !== "function") { 38 | // User has provided an invalid function 39 | return modify_request(ctx, "Can't parse Requestly function. Please recheck. Error Code 944."); 40 | } 41 | // Everything good so far. Now try to execute user's function 42 | let finalRequest = null; 43 | try { 44 | const args = { 45 | method: ctx.clientToProxyRequest 46 | ? ctx.clientToProxyRequest.method 47 | ? ctx.clientToProxyRequest.method 48 | : null 49 | : null, 50 | request: ctx.rq_request_body, 51 | body: ctx.rq_request_body, 52 | url: (0, proxy_ctx_helper_1.get_request_url)(ctx), 53 | requestHeaders: ctx.clientToProxyRequest.headers, 54 | }; 55 | try { 56 | args.bodyAsJson = JSON.parse(args.request); 57 | } 58 | catch (_a) { 59 | /*Do nothing -- could not parse body as JSON */ 60 | } 61 | finalRequest = await (0, utils_2.executeUserFunction)(ctx, userFunction, args); 62 | if (finalRequest && typeof finalRequest === "string") { 63 | return modify_request(ctx, finalRequest); 64 | } 65 | else 66 | throw new Error("Returned value is not a string"); 67 | } 68 | catch (error) { 69 | // Function parsed but failed to execute 70 | return modify_request(ctx, "Can't execute Requestly function. Please recheck. Error Code 187. Actual Error: " + 71 | error.message); 72 | } 73 | }; 74 | exports.default = process_modify_request_action; 75 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_response_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_modify_response_action; 2 | declare function process_modify_response_action(action: any, ctx: any): Promise<{ 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }>; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_user_agent_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_modify_user_agent_action; 2 | declare function process_modify_user_agent_action(action: any, ctx: any): { 3 | action: any; 4 | success: boolean; 5 | post_process_data: any; 6 | }; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/modify_user_agent_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const proxy_1 = require("../../../../lib/proxy"); 4 | const utils_1 = require("../utils"); 5 | const process_modify_user_agent_action = (action, ctx) => { 6 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST]; 7 | if (!allowed_handlers.includes(ctx.currentHandler)) { 8 | return (0, utils_1.build_action_processor_response)(action, false); 9 | } 10 | if (ctx.currentHandler == proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST) { 11 | modify_request_headers(action, ctx); 12 | return (0, utils_1.build_action_processor_response)(action, true); 13 | } 14 | }; 15 | const modify_request_headers = (action, ctx) => { 16 | const newRequestHeaders = action.newRequestHeaders; 17 | for (var headerName in ctx.proxyToServerRequestOptions.headers) { 18 | if (ctx.proxyToServerRequestOptions.headers.hasOwnProperty(headerName)) { 19 | delete ctx.proxyToServerRequestOptions.headers[headerName]; 20 | } 21 | } 22 | // Set Request Headers 23 | newRequestHeaders.forEach((pair) => (ctx.proxyToServerRequestOptions.headers[pair.name] = pair.value)); 24 | }; 25 | exports.default = process_modify_user_agent_action; 26 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/redirect_processor.d.ts: -------------------------------------------------------------------------------- 1 | export default process_redirect_action; 2 | declare function process_redirect_action(action: any, ctx: any): Promise; 7 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/processors/redirect_processor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const proxy_1 = require("../../../../lib/proxy"); 7 | const proxy_ctx_helper_1 = require("../../helpers/proxy_ctx_helper"); 8 | const modified_requests_pool_1 = __importDefault(require("../modified_requests_pool")); 9 | const handle_mixed_response_1 = __importDefault(require("../handle_mixed_response")); 10 | const utils_1 = require("../utils"); 11 | // adding util to get origin header for handling cors 12 | const getRequestOrigin = (ctx) => { 13 | const originalRequestHeaders = ctx.rq.original_request.headers || {}; 14 | return (originalRequestHeaders["Origin"] || 15 | originalRequestHeaders["origin"] || 16 | originalRequestHeaders["ORIGIN"]); 17 | }; 18 | const process_redirect_action = async (action, ctx) => { 19 | const allowed_handlers = [proxy_1.PROXY_HANDLER_TYPE.ON_REQUEST]; 20 | if (!allowed_handlers.includes(ctx.currentHandler)) { 21 | return (0, utils_1.build_action_processor_response)(action, false); 22 | } 23 | const current_url = (0, proxy_ctx_helper_1.get_request_url)(ctx); 24 | const new_url = action.url; 25 | const request_url = current_url.replace(/www\./g, ""); 26 | // Skip if already redirected 27 | if (modified_requests_pool_1.default.isURLModified(request_url)) { 28 | // Do nothing 29 | return (0, utils_1.build_action_processor_response)(action, false); 30 | } 31 | else { 32 | modified_requests_pool_1.default.add(new_url); 33 | } 34 | const { status: isMixedResponse, response_data } = await (0, handle_mixed_response_1.default)(ctx, new_url); 35 | if (isMixedResponse) { 36 | return (0, utils_1.build_action_processor_response)(action, true, (0, utils_1.build_post_process_data)(response_data.status_code, response_data.headers, response_data.body)); 37 | } 38 | // If this is a pre-flight request, don't redirect it 39 | if ((0, proxy_ctx_helper_1.is_request_preflight)(ctx)) 40 | return true; 41 | return (0, utils_1.build_action_processor_response)(action, true, (0, utils_1.build_post_process_data)(307, { 42 | "Cache-Control": "no-cache", 43 | "Access-Control-Allow-Origin": getRequestOrigin(ctx) || "*", 44 | "Access-Control-Allow-Credentials": "true", 45 | Location: new_url, 46 | }, null)); 47 | }; 48 | exports.default = process_redirect_action; 49 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/utils.d.ts: -------------------------------------------------------------------------------- 1 | export function build_action_processor_response(action: any, success?: boolean, post_process_data?: any): { 2 | action: any; 3 | success: boolean; 4 | post_process_data: any; 5 | }; 6 | export function build_post_process_data(status_code: any, headers: any, body: any): { 7 | status_code: any; 8 | headers: any; 9 | body: any; 10 | }; 11 | export function get_success_actions_from_action_results(action_result_objs?: any[]): any[]; 12 | export function get_file_contents(file_path: any): string; 13 | -------------------------------------------------------------------------------- /dist/components/proxy-middleware/rule_action_processor/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.get_file_contents = exports.get_success_actions_from_action_results = exports.build_post_process_data = exports.build_action_processor_response = void 0; 7 | const fs_1 = __importDefault(require("fs")); 8 | const build_action_processor_response = (action, success = false, post_process_data = null) => { 9 | let resp = { 10 | action: action, 11 | success: success, 12 | post_process_data: post_process_data, 13 | }; 14 | return resp; 15 | }; 16 | exports.build_action_processor_response = build_action_processor_response; 17 | const build_post_process_data = (status_code, headers, body) => { 18 | if (!status_code && !headers && !body) { 19 | return null; 20 | } 21 | let data = { 22 | status_code, 23 | headers, 24 | body, 25 | }; 26 | return data; 27 | }; 28 | exports.build_post_process_data = build_post_process_data; 29 | const get_success_actions_from_action_results = (action_result_objs = []) => { 30 | if (!action_result_objs) { 31 | return []; 32 | } 33 | // BY default success is false 34 | const success_action_results_objs = action_result_objs.filter((obj) => obj && obj && obj.success === true); 35 | return success_action_results_objs.map((obj) => obj.action); 36 | }; 37 | exports.get_success_actions_from_action_results = get_success_actions_from_action_results; 38 | const get_file_contents = (file_path) => { 39 | return fs_1.default.readFileSync(file_path, "utf-8"); 40 | }; 41 | exports.get_file_contents = get_file_contents; 42 | -------------------------------------------------------------------------------- /dist/components/ssl-proxying/ssl-proxying-manager.d.ts: -------------------------------------------------------------------------------- 1 | declare class SSLProxyingManager { 2 | } 3 | export default SSLProxyingManager; 4 | -------------------------------------------------------------------------------- /dist/components/ssl-proxying/ssl-proxying-manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // import { ISource, SSLProxyingJsonObj } from "lib/storage/types/ssl-proxying"; 3 | // import BaseConfigFetcher from "renderer/lib/fetcher/base"; 4 | // TODO: @sahil fix this by adding type.d.ts file 5 | //@ts-ignore 6 | // import { RULE_PROCESSOR } from "@requestly/requestly-core"; 7 | Object.defineProperty(exports, "__esModule", { value: true }); 8 | // TODO: @sahil to add this 9 | class SSLProxyingManager { 10 | } 11 | exports.default = SSLProxyingManager; 12 | -------------------------------------------------------------------------------- /dist/constants/cert.d.ts: -------------------------------------------------------------------------------- 1 | export declare const certConfig: { 2 | CERT_NAME: string; 3 | CERT_VALIDITY: { 4 | START_BEFORE: number; 5 | END_AFTER: number; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /dist/constants/cert.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.certConfig = void 0; 4 | exports.certConfig = { 5 | CERT_NAME: "RQProxyCA", 6 | CERT_VALIDITY: { 7 | // Number of days - before the current date - Keep minimum 1 to avoid 12am date change issues 8 | START_BEFORE: 1, 9 | // Number of days - after the current date - Keep minimum 1 to avoid 12am date change issues 10 | // CAUTION : Increasing this count might affect current app users 11 | END_AFTER: 365, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import RQProxy from "./rq-proxy"; 2 | import RQProxyProvider from "./rq-proxy-provider"; 3 | export { RQProxy, RQProxyProvider, }; 4 | export default RQProxy; 5 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.RQProxyProvider = exports.RQProxy = void 0; 7 | const rq_proxy_1 = __importDefault(require("./rq-proxy")); 8 | exports.RQProxy = rq_proxy_1.default; 9 | const rq_proxy_provider_1 = __importDefault(require("./rq-proxy-provider")); 10 | exports.RQProxyProvider = rq_proxy_provider_1.default; 11 | exports.default = rq_proxy_1.default; 12 | -------------------------------------------------------------------------------- /dist/lib/proxy/bin/mitm-proxy.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | export {}; 3 | -------------------------------------------------------------------------------- /dist/lib/proxy/bin/mitm-proxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var yargs = require("yargs"); 4 | var debug = require("debug")("http-mitm-proxy:bin"); 5 | var args = yargs 6 | .alias("h", "help") 7 | .alias("h", "?") 8 | .options("port", { 9 | default: 80, 10 | describe: "HTTP Port.", 11 | }) 12 | .alias("p", "port") 13 | .options("host", { 14 | describe: "HTTP Listen Interface.", 15 | }).argv; 16 | if (args.help) { 17 | yargs.showHelp(); 18 | process.exit(-1); 19 | } 20 | var proxy = require("../lib/proxy")(); 21 | proxy.onError(function (ctx, err, errorKind) { 22 | debug(errorKind, err); 23 | }); 24 | proxy.listen(args, function (err) { 25 | if (err) { 26 | debug("Failed to start listening on port " + args.port, err); 27 | return process.exit(1); 28 | } 29 | debug("proxy listening on " + args.port); 30 | }); 31 | -------------------------------------------------------------------------------- /dist/lib/proxy/custom/utils/checkInvalidHeaderChar.d.ts: -------------------------------------------------------------------------------- 1 | export = checkInvalidHeaderChar; 2 | /** 3 | * True if val contains an invalid field-vchar 4 | * field-value = *( field-content / obs-fold ) 5 | * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 6 | * field-vchar = VCHAR / obs-text 7 | */ 8 | declare function checkInvalidHeaderChar(val: any): boolean; 9 | -------------------------------------------------------------------------------- /dist/lib/proxy/custom/utils/checkInvalidHeaderChar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @sagar - Polyfill for _http_common.js 3 | */ 4 | const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 5 | /** 6 | * True if val contains an invalid field-vchar 7 | * field-value = *( field-content / obs-fold ) 8 | * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 9 | * field-vchar = VCHAR / obs-text 10 | */ 11 | function checkInvalidHeaderChar(val) { 12 | return headerCharRegex.test(val); 13 | } 14 | module.exports = checkInvalidHeaderChar; 15 | -------------------------------------------------------------------------------- /dist/lib/proxy/index.d.ts: -------------------------------------------------------------------------------- 1 | export default DefaultExport; 2 | export { Proxy, PROXY_HANDLER_TYPE, gunzip, decompress, wildcard }; 3 | -------------------------------------------------------------------------------- /dist/lib/proxy/index.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 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.wildcard = exports.decompress = exports.gunzip = exports.PROXY_HANDLER_TYPE = exports.Proxy = void 0; 27 | const proxy_1 = __importStar(require("./lib/proxy")); 28 | Object.defineProperty(exports, "Proxy", { enumerable: true, get: function () { return proxy_1.Proxy; } }); 29 | Object.defineProperty(exports, "PROXY_HANDLER_TYPE", { enumerable: true, get: function () { return proxy_1.PROXY_HANDLER_TYPE; } }); 30 | Object.defineProperty(exports, "gunzip", { enumerable: true, get: function () { return proxy_1.gunzip; } }); 31 | Object.defineProperty(exports, "decompress", { enumerable: true, get: function () { return proxy_1.decompress; } }); 32 | Object.defineProperty(exports, "wildcard", { enumerable: true, get: function () { return proxy_1.wildcard; } }); 33 | exports.default = proxy_1.default; 34 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/ca.d.ts: -------------------------------------------------------------------------------- 1 | export = CA; 2 | declare class CA { 3 | onCARegenerated(callback: any): void; 4 | randomSerialNumber(): string; 5 | generateCA(callback: any): void; 6 | loadCA(callback: any): void; 7 | generateServerCertificateKeys(hosts: any, cb: any): void; 8 | getCACertPath(): string; 9 | } 10 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/decompress.d.ts: -------------------------------------------------------------------------------- 1 | export function onResponse(ctx: any, callback: any): any; 2 | export function onRequest(ctx: any, callback: any): any; 3 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/decompress.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var zlib = require("zlib"); 3 | const decompressMiddleware = { 4 | onResponse: function (ctx, callback) { 5 | if (ctx.serverToProxyResponse.headers["content-encoding"] && 6 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 7 | "gzip") { 8 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 9 | ctx.addResponseFilter(zlib.createGunzip()); 10 | } 11 | else if (ctx.serverToProxyResponse.headers["content-encoding"] && 12 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 13 | "br") { 14 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 15 | ctx.addResponseFilter(zlib.createBrotliDecompress()); 16 | } 17 | return callback(); 18 | }, 19 | onRequest: function (ctx, callback) { 20 | ctx.proxyToServerRequestOptions.headers["accept-encoding"] = "gzip"; 21 | return callback(); 22 | }, 23 | }; 24 | module.exports = decompressMiddleware; 25 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/gunzip.d.ts: -------------------------------------------------------------------------------- 1 | export function onResponse(ctx: any, callback: any): any; 2 | export function onRequest(ctx: any, callback: any): any; 3 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/gunzip.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var zlib = require("zlib"); 3 | const gunzipMiddleware = { 4 | onResponse: function (ctx, callback) { 5 | if (ctx.serverToProxyResponse.headers["content-encoding"] && 6 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 7 | "gzip") { 8 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 9 | ctx.addResponseFilter(zlib.createGunzip()); 10 | } 11 | return callback(); 12 | }, 13 | onRequest: function (ctx, callback) { 14 | ctx.proxyToServerRequestOptions.headers["accept-encoding"] = "gzip"; 15 | return callback(); 16 | }, 17 | }; 18 | module.exports = gunzipMiddleware; 19 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/wildcard.d.ts: -------------------------------------------------------------------------------- 1 | export function onCertificateRequired(hostname: any, callback: any): any; 2 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/middleware/wildcard.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * group1: subdomain 4 | * group2: domain.ext 5 | * exclude short domains (length < 4) to avoid catching double extensions (ex: net.au, co.uk, ...) 6 | */ 7 | const HOSTNAME_REGEX = /^(.+)(\.[^\.]{4,}(\.[^\.]{1,3})*\.[^\.]+)$/; 8 | const wildcaredMiddleware = { 9 | onCertificateRequired: function (hostname, callback) { 10 | var rootHost = hostname; 11 | if (HOSTNAME_REGEX.test(hostname)) { 12 | rootHost = hostname.replace(/^[^\.]+\./, ""); 13 | } 14 | return callback(null, { 15 | keyFile: this.sslCaDir + "/keys/_." + rootHost + ".key", 16 | certFile: this.sslCaDir + "/certs/_." + rootHost + ".pem", 17 | hosts: ["*." + rootHost, rootHost], 18 | }); 19 | }, 20 | }; 21 | module.exports = wildcaredMiddleware; 22 | -------------------------------------------------------------------------------- /dist/lib/proxy/lib/proxy.d.ts: -------------------------------------------------------------------------------- 1 | declare var async: any; 2 | declare var net: any; 3 | declare var http: any; 4 | declare var https: any; 5 | declare var util: any; 6 | declare var fs: any; 7 | declare var path: any; 8 | declare var events: any; 9 | declare var WebSocket: { 10 | prototype: WebSocket; 11 | new (url: string | URL, protocols?: string | string[]): WebSocket; 12 | readonly CONNECTING: 0; 13 | readonly OPEN: 1; 14 | readonly CLOSING: 2; 15 | readonly CLOSED: 3; 16 | }; 17 | declare var url: any; 18 | declare var semaphore: any; 19 | declare var CA: any; 20 | declare var Sentry: any; 21 | declare const checkInvalidHeaderChar: any; 22 | declare const debug: any; 23 | declare const uuidv4: any; 24 | declare var Proxy: any; 25 | declare const PROXY_HANDLER_TYPE: { 26 | ON_REQUEST: string; 27 | ON_REQUEST_HEADERS: string; 28 | ON_REQUEST_DATA: string; 29 | ON_REQUEST_END: string; 30 | ON_RESPONSE: string; 31 | ON_RESPONSE_HEADERS: string; 32 | ON_RESPONSE_DATA: string; 33 | ON_RESPONSE_END: string; 34 | ON_ERROR: string; 35 | }; 36 | declare var ProxyFinalRequestFilter: (proxy: any, ctx: any) => void; 37 | declare var ProxyFinalResponseFilter: (proxy: any, ctx: any) => any; 38 | -------------------------------------------------------------------------------- /dist/rq-proxy-provider.d.ts: -------------------------------------------------------------------------------- 1 | import RQProxy from "./rq-proxy"; 2 | import ILoggerService from "./components/interfaces/logger-service"; 3 | import IRulesDataSource from "./components/interfaces/rules-data-source"; 4 | import IInitialState from "./components/interfaces/state"; 5 | import { ProxyConfig } from "./types"; 6 | declare class RQProxyProvider { 7 | static rqProxyInstance: any; 8 | static createInstance: (proxyConfig: ProxyConfig, rulesDataSource: IRulesDataSource, loggerService: ILoggerService, initialGlobalState?: IInitialState) => void; 9 | static getInstance: () => RQProxy; 10 | } 11 | export default RQProxyProvider; 12 | -------------------------------------------------------------------------------- /dist/rq-proxy-provider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const rq_proxy_1 = __importDefault(require("./rq-proxy")); 7 | class RQProxyProvider { 8 | } 9 | RQProxyProvider.rqProxyInstance = null; 10 | // TODO: rulesDataSource can be static here 11 | RQProxyProvider.createInstance = (proxyConfig, rulesDataSource, loggerService, initialGlobalState) => { 12 | RQProxyProvider.rqProxyInstance = new rq_proxy_1.default(proxyConfig, rulesDataSource, loggerService, initialGlobalState); 13 | }; 14 | RQProxyProvider.getInstance = () => { 15 | if (!RQProxyProvider.rqProxyInstance) { 16 | console.error("Instance need to be created first"); 17 | } 18 | return RQProxyProvider.rqProxyInstance; 19 | }; 20 | exports.default = RQProxyProvider; 21 | -------------------------------------------------------------------------------- /dist/rq-proxy.d.ts: -------------------------------------------------------------------------------- 1 | import IRulesDataSource from "./components/interfaces/rules-data-source"; 2 | import { ProxyConfig } from "./types"; 3 | import RulesHelper from "./utils/helpers/rules-helper"; 4 | import ProxyMiddlewareManager from "./components/proxy-middleware"; 5 | import ILoggerService from "./components/interfaces/logger-service"; 6 | import IInitialState from "./components/interfaces/state"; 7 | import { State } from "./components/proxy-middleware/middlewares/state"; 8 | declare class RQProxy { 9 | proxy: any; 10 | proxyMiddlewareManager: ProxyMiddlewareManager; 11 | rulesHelper: RulesHelper; 12 | loggerService: ILoggerService; 13 | globalState: State; 14 | constructor(proxyConfig: ProxyConfig, rulesDataSource: IRulesDataSource, loggerService: ILoggerService, initialGlobalState?: IInitialState); 15 | initProxy: (proxyConfig: ProxyConfig) => void; 16 | doSomething: () => void; 17 | } 18 | export default RQProxy; 19 | -------------------------------------------------------------------------------- /dist/rq-proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const proxy_1 = __importDefault(require("./lib/proxy")); 7 | const rules_helper_1 = __importDefault(require("./utils/helpers/rules-helper")); 8 | const proxy_middleware_1 = __importDefault(require("./components/proxy-middleware")); 9 | const state_1 = __importDefault(require("./components/proxy-middleware/middlewares/state")); 10 | class RQProxy { 11 | constructor(proxyConfig, rulesDataSource, loggerService, initialGlobalState) { 12 | var _a, _b; 13 | this.initProxy = (proxyConfig) => { 14 | console.log("initProxy"); 15 | console.log(proxyConfig); 16 | // @ts-ignore 17 | this.proxy = new proxy_1.default(); 18 | if (proxyConfig.onCARegenerated) { 19 | this.proxy.onCARegenerated(proxyConfig.onCARegenerated); 20 | } 21 | // console.log(this.proxy); 22 | this.proxy.listen({ 23 | port: proxyConfig.port, 24 | sslCaDir: proxyConfig.certPath, 25 | host: "0.0.0.0", 26 | }, (err) => { 27 | console.log("Proxy Listen"); 28 | if (err) { 29 | console.log(err); 30 | } 31 | else { 32 | console.log("Proxy Started"); 33 | this.proxyMiddlewareManager = new proxy_middleware_1.default(this.proxy, proxyConfig, this.rulesHelper, this.loggerService, null); 34 | this.proxyMiddlewareManager.init(); 35 | } 36 | }); 37 | // For Testing // 38 | this.proxy.onRequest(function (ctx, callback) { 39 | if (ctx.clientToProxyRequest.headers.host == 'example.com') { 40 | ctx.use(proxy_1.default.gunzip); 41 | ctx.onResponseData(function (ctx, chunk, callback) { 42 | chunk = new Buffer("

Hello There

"); 43 | return callback(null, chunk); 44 | }); 45 | } 46 | return callback(); 47 | }); 48 | // 49 | }; 50 | this.doSomething = () => { 51 | console.log("do something"); 52 | }; 53 | this.initProxy(proxyConfig); 54 | this.rulesHelper = new rules_helper_1.default(rulesDataSource); 55 | this.loggerService = loggerService; 56 | this.globalState = state_1.default.initInstance((_a = initialGlobalState === null || initialGlobalState === void 0 ? void 0 : initialGlobalState.sharedState) !== null && _a !== void 0 ? _a : {}, (_b = initialGlobalState === null || initialGlobalState === void 0 ? void 0 : initialGlobalState.variables) !== null && _b !== void 0 ? _b : {}); 57 | } 58 | } 59 | exports.default = RQProxy; 60 | -------------------------------------------------------------------------------- /dist/test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const axios_1 = __importDefault(require("axios")); 7 | const rq_proxy_provider_1 = __importDefault(require("./rq-proxy-provider")); 8 | console.log("start"); 9 | const proxyConfig = { 10 | port: 8281, 11 | // @ts-ignore 12 | certPath: "/Users/sahilgupta/Library/Application\\ Support/Electron/.requestly-certs-temp", 13 | // TODO: MOVE THIS IN RQ PROXY 14 | rootCertPath: "/Users/sahilgupta/Library/Application\\ Support/Electron/.requestly-certs-temp/certs/ca.pem" 15 | }; 16 | class RulesDataSource { 17 | constructor() { 18 | this.getRules = async (requestHeaders) => { 19 | return [ 20 | { 21 | "creationDate": 1648800254537, 22 | "description": "", 23 | "groupId": "", 24 | "id": "Headers_br050", 25 | "isSample": false, 26 | "name": "Test Header Rule", 27 | "objectType": "rule", 28 | "pairs": [ 29 | { 30 | "header": "abc", 31 | "value": "abc value", 32 | "type": "Add", 33 | "target": "Request", 34 | "source": { 35 | "filters": {}, 36 | "key": "Url", 37 | "operator": "Contains", 38 | "value": "example" 39 | }, 40 | "id": "lussg" 41 | }, 42 | { 43 | "header": "abc", 44 | "value": "bac value", 45 | "type": "Add", 46 | "target": "Response", 47 | "source": { 48 | "filters": {}, 49 | "key": "Url", 50 | "operator": "Contains", 51 | "value": "example" 52 | }, 53 | "id": "be1k6" 54 | } 55 | ], 56 | "ruleType": "Headers", 57 | "status": "Active", 58 | "createdBy": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 59 | "currentOwner": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 60 | "lastModifiedBy": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 61 | "modificationDate": 1648800283699, 62 | "lastModified": 1648800283699 63 | } 64 | ]; 65 | }; 66 | this.getGroups = async (requestHeaders) => { 67 | return [ 68 | { 69 | id: "1", 70 | status: "Inactive" 71 | } 72 | ]; 73 | }; 74 | } 75 | } 76 | class LoggerService { 77 | constructor() { 78 | this.addLog = (log, requestHeaders) => { 79 | console.log(JSON.stringify(log, null, 4)); 80 | const headers = { 81 | "device_id": "test_device", 82 | "sdk_id": "7jcFc1g5j7ozfSXe7lc6", 83 | }; 84 | // TODO: Keeping this as Strong for now to avoid changes in UI 85 | log.finalHar = JSON.stringify(log.finalHar); 86 | (0, axios_1.default)({ 87 | method: "post", 88 | url: "http://localhost:5001/requestly-dev/us-central1/addSdkLog", // local 89 | headers, 90 | data: log 91 | }).then(() => { 92 | console.log("Successfully added log"); 93 | }).catch(error => { 94 | console.log(`Could not add Log`); 95 | }); 96 | }; 97 | } 98 | } 99 | // RQProxyProvider.getInstance().doSomething(); 100 | rq_proxy_provider_1.default.createInstance(proxyConfig, new RulesDataSource(), new LoggerService()); 101 | rq_proxy_provider_1.default.getInstance().doSomething(); 102 | console.log("end"); 103 | -------------------------------------------------------------------------------- /dist/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface ProxyConfig { 2 | [x: string]: any; 3 | port: Number; 4 | certPath: String; 5 | rootCertPath: String; 6 | onCARegenerated?: Function; 7 | } 8 | export interface Rule { 9 | id: string; 10 | } 11 | export interface RuleGroup { 12 | id: String; 13 | status: String; 14 | } 15 | -------------------------------------------------------------------------------- /dist/types/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /dist/utils/circularQueue.d.ts: -------------------------------------------------------------------------------- 1 | declare const Queue: any; 2 | export default Queue; 3 | -------------------------------------------------------------------------------- /dist/utils/circularQueue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const Queue = function (maxSize) { 4 | this.reset = function () { 5 | this.head = -1; 6 | this.queue = []; 7 | }; 8 | this.reset(); 9 | this.maxSize = maxSize || Queue.MAX_SIZE; 10 | this.increment = function (number) { 11 | return (number + 1) % this.maxSize; 12 | }; 13 | }; 14 | Queue.MAX_SIZE = Math.pow(2, 53) - 1; 15 | Queue.prototype.enQueue = function (record) { 16 | this.head = this.increment(this.head); 17 | this.queue[this.head] = record; 18 | }; 19 | /** 20 | * @param record Record to look for 21 | * @returns Number Position of record in the queue otherwise -1 22 | */ 23 | Queue.prototype.getElementIndex = function (record) { 24 | return this.queue.indexOf(record); 25 | }; 26 | Queue.prototype.print = function () { 27 | for (var i = 0; i <= this.head; i++) { 28 | console.log(this.queue[i]); 29 | } 30 | }; 31 | exports.default = Queue; 32 | -------------------------------------------------------------------------------- /dist/utils/helpers/rules-helper.d.ts: -------------------------------------------------------------------------------- 1 | import IRulesDataSource from "../../components/interfaces/rules-data-source"; 2 | declare class RulesHelper { 3 | rulesDataSource: IRulesDataSource; 4 | constructor(rulesDataSource: IRulesDataSource); 5 | is_group_active: (group: any) => boolean; 6 | is_rule_active: (rule: any, active_group_ids?: any[]) => boolean; 7 | get_groups: (is_active?: boolean, requestHeaders?: {}) => Promise; 8 | get_group_ids: (is_active?: boolean, requestHeaders?: {}) => Promise; 9 | get_rules: (is_active?: boolean, requestHeaders?: {}) => Promise; 10 | } 11 | export default RulesHelper; 12 | -------------------------------------------------------------------------------- /dist/utils/helpers/rules-helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class RulesHelper { 4 | // TODO: this can be static class too 5 | constructor(rulesDataSource) { 6 | this.is_group_active = (group) => { 7 | if (!group) { 8 | return false; 9 | } 10 | return group.status == "Active"; 11 | }; 12 | this.is_rule_active = (rule, active_group_ids = []) => { 13 | if (!rule) { 14 | return false; 15 | } 16 | let status = false; 17 | status = rule.status == "Active"; 18 | // No group case || active group case 19 | if (rule.groupId == "" || active_group_ids.includes(rule.groupId)) { 20 | return status; 21 | } 22 | return false; 23 | }; 24 | this.get_groups = async (is_active = false, requestHeaders = {}) => { 25 | let groups = await this.rulesDataSource.getGroups(requestHeaders) || []; 26 | if (is_active === true) { 27 | groups = groups.filter((group) => this.is_group_active(group)); 28 | } 29 | return groups; 30 | }; 31 | this.get_group_ids = async (is_active = false, requestHeaders = {}) => { 32 | const groups = await this.get_groups(is_active, requestHeaders); 33 | return groups.map((group) => group.id); 34 | }; 35 | this.get_rules = async (is_active = false, requestHeaders = {}) => { 36 | let rules = await this.rulesDataSource.getRules(requestHeaders) || []; 37 | let active_group_ids = await this.get_group_ids(true, requestHeaders) || []; 38 | if (is_active === true) { 39 | rules = rules.filter((rule) => this.is_rule_active(rule, active_group_ids)); 40 | } 41 | // Sorting Rules By id asc 42 | rules = rules.sort((rule1, rule2) => { 43 | return ("" + rule1.id).localeCompare(rule2.id); 44 | }); 45 | return rules; 46 | }; 47 | this.rulesDataSource = rulesDataSource; 48 | } 49 | } 50 | exports.default = RulesHelper; 51 | -------------------------------------------------------------------------------- /dist/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const getFunctionFromString: (functionStringEscaped: any) => any; 2 | export declare function executeUserFunction(ctx: any, functionString: string, args: any): Promise; 3 | -------------------------------------------------------------------------------- /dist/utils/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.getFunctionFromString = void 0; 7 | exports.executeUserFunction = executeUserFunction; 8 | const util_1 = require("util"); 9 | const capture_console_logs_1 = __importDefault(require("capture-console-logs")); 10 | const state_1 = __importDefault(require("../components/proxy-middleware/middlewares/state")); 11 | // Only used for verification now. For execution, we regenerate the function in executeUserFunction with the sharedState 12 | const getFunctionFromString = function (functionStringEscaped) { 13 | return new Function(`return ${functionStringEscaped}`)(); 14 | }; 15 | exports.getFunctionFromString = getFunctionFromString; 16 | /* Expects that the functionString has already been validated to be representing a proper function */ 17 | async function executeUserFunction(ctx, functionString, args) { 18 | const generateFunctionWithSharedState = function (functionStringEscaped) { 19 | const SHARED_STATE_VAR_NAME = "$sharedState"; 20 | const sharedState = state_1.default.getInstance().getSharedStateCopy(); 21 | return new Function(`${SHARED_STATE_VAR_NAME}`, `return { func: ${functionStringEscaped}, updatedSharedState: ${SHARED_STATE_VAR_NAME}}`)(sharedState); 22 | }; 23 | const { func: generatedFunction, updatedSharedState } = generateFunctionWithSharedState(functionString); 24 | const consoleCapture = new capture_console_logs_1.default(); 25 | consoleCapture.start(true); 26 | let finalResponse = generatedFunction(args); 27 | if (util_1.types.isPromise(finalResponse)) { 28 | finalResponse = await finalResponse; 29 | } 30 | consoleCapture.stop(); 31 | const consoleLogs = consoleCapture.getCaptures(); 32 | ctx.rq.consoleLogs.push(...consoleLogs); 33 | /** 34 | * If we use GlobalState.getSharedStateRef instead of GlobalState.getSharedStateCopy 35 | * then this update is completely unnecessary. 36 | * Because then the function gets a reference to the global states, 37 | * and any changes made inside the userFunction will directly be reflected there. 38 | * 39 | * But we are using it here to make the data flow obvious as we read this code. 40 | */ 41 | state_1.default.getInstance().setSharedState(updatedSharedState); 42 | if (typeof finalResponse === "object") { 43 | finalResponse = JSON.stringify(finalResponse); 44 | } 45 | return finalResponse; 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@requestly/requestly-proxy", 3 | "version": "1.3.10", 4 | "description": "Proxy that gives superpowers to all the Requestly clients", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "type": "commonjs", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1", 10 | "dev": "nodemon src/test.ts", 11 | "build": "tsc", 12 | "watch": "tsc --watch" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/requestly/rq-proxy.git" 17 | }, 18 | "exports": { 19 | ".": { 20 | "require": "./dist/index.js", 21 | "types": "./dist/index.d.ts", 22 | "import": "./dist/index.js", 23 | "default": "./dist/index.js" 24 | } 25 | }, 26 | "author": "", 27 | "license": "ISC", 28 | "dependencies": { 29 | "@requestly/requestly-core": "^1.1.0", 30 | "@sentry/browser": "^8.33.1", 31 | "async": "^3.2.5", 32 | "axios": "^1.7.2", 33 | "capture-console-logs": "^1.0.1", 34 | "charset": "^1.0.1", 35 | "debug": "^4.3.2", 36 | "httpsnippet": "^3.0.4", 37 | "lodash": "^4.17.21", 38 | "mime-types": "^2.1.35", 39 | "mkdirp": "^0.5.5", 40 | "node-forge": "^1.3.0", 41 | "semaphore": "^1.1.0", 42 | "ua-parser-js": "^1.0.37", 43 | "url": "^0.11.3", 44 | "uuid": "^8.3.2", 45 | "ws": "^8.16.0" 46 | }, 47 | "devDependencies": { 48 | "@types/har-format": "^1.2.15", 49 | "@types/node": "^20.12.12", 50 | "ajv": "^8.13.0", 51 | "nodemon": "^3.1.0", 52 | "type-fest": "^2.12.2", 53 | "typescript": "^5.6.3" 54 | }, 55 | "files": [ 56 | "dist" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/components/interfaces/logger-service.ts: -------------------------------------------------------------------------------- 1 | interface ILoggerService { 2 | addLog: (log: any, requestHeaders: {}) => void; 3 | } 4 | 5 | export default ILoggerService; 6 | -------------------------------------------------------------------------------- /src/components/interfaces/rules-data-source.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RuleGroup } from "../../types"; 2 | 3 | interface IRulesDataSource { 4 | getRules: (requestHeaders: {}) => Promise 5 | getGroups(requestHeaders: {}): Promise 6 | } 7 | 8 | export default IRulesDataSource; 9 | -------------------------------------------------------------------------------- /src/components/interfaces/state.ts: -------------------------------------------------------------------------------- 1 | export default interface IInitialState extends Record { 2 | sharedState?: Record; 3 | variables?: Record; 4 | } -------------------------------------------------------------------------------- /src/components/proxy-middleware/constants.js: -------------------------------------------------------------------------------- 1 | export const RULE_ACTION = { 2 | REDIRECT: "redirect", 3 | MODIFY_HEADERS: "modify_headers", 4 | MODIFY_USER_AGENT: "modify_user_agent", 5 | BLOCK: "block", 6 | INSERT: "insert", 7 | DELAY: "add_delay", 8 | MODIFY_RESPONSE: "modify_response", 9 | MODIFY_REQUEST: "modify_request", 10 | }; 11 | 12 | export const RQ_INTERCEPTED_CONTENT_TYPES = [ 13 | "text/html", 14 | "text/plain", 15 | "text/javascript", 16 | "application/javascript", 17 | "application/x-javascript", 18 | "text/css", 19 | "application/css", 20 | "application/json", 21 | "application/manifest+json" 22 | ]; 23 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/ctx_rq_namespace.js: -------------------------------------------------------------------------------- 1 | import { createRequestHarObject } from "./harObectCreator"; 2 | 3 | class CtxRQNamespace { 4 | constructor() { 5 | this.original_request = { 6 | // method: ctx.clientToProxyRequest.method, 7 | // path: ctx.clientToProxyRequest.url, 8 | // host: hostPort.host, 9 | // port: hostPort.port, 10 | // headers: headers, 11 | // agent: ctx.isSSL ? self.httpsAgent : self.httpAgent, 12 | // body: body 13 | // query_params: query_params // json 14 | }; 15 | this.original_response = { 16 | // status_code, 17 | // headers, 18 | // body 19 | }; 20 | 21 | this.final_request = { 22 | // method: ctx.clientToProxyRequest.method, 23 | // path: ctx.clientToProxyRequest.url, 24 | // host: hostPort.host, 25 | // port: hostPort.port, 26 | // headers: headers, 27 | // agent: ctx.isSSL ? self.httpsAgent : self.httpAgent, 28 | // body: body 29 | // query_params: query_params // json 30 | }; 31 | this.final_response = { 32 | // status_code, 33 | // headers, 34 | // body 35 | }; 36 | 37 | 38 | this.consoleLogs = [] 39 | } 40 | 41 | set_original_request = ({ 42 | method = null, 43 | path = null, 44 | host = null, 45 | port = null, 46 | headers = null, 47 | agent = null, 48 | body = null, 49 | query_params = null, 50 | }) => { 51 | if (headers) { 52 | this.original_request.headers = { ...headers }; 53 | } 54 | 55 | this.original_request.method = method || this.original_request.method; 56 | this.original_request.path = path || this.original_request.path; 57 | this.original_request.host = host || this.original_request.host; 58 | this.original_request.port = port || this.original_request.port; 59 | this.original_request.agent = agent || this.original_request.agent; 60 | this.original_request.body = body || this.original_request.body; 61 | this.original_request.query_params = 62 | query_params || this.original_request.query_params; 63 | }; 64 | 65 | set_original_response = ({ 66 | status_code = null, 67 | headers = null, 68 | body = null, 69 | query_params = null, 70 | }) => { 71 | if (headers) { 72 | this.original_response.headers = { ...headers }; 73 | } 74 | 75 | this.original_response.status_code = 76 | status_code || this.original_response.status_code; 77 | this.original_response.body = body || this.original_response.body; 78 | }; 79 | 80 | set_final_request = (proxyToServerRequestOptions) => { 81 | const { 82 | method, 83 | path, 84 | host, 85 | port, 86 | headers, 87 | agent, 88 | body, 89 | query_params, 90 | } = proxyToServerRequestOptions; 91 | if (headers) { 92 | this.final_request.headers = { ...headers }; 93 | } 94 | 95 | this.final_request.method = method || this.final_request.method; 96 | this.final_request.path = path || this.final_request.path; 97 | this.final_request.host = host || this.final_request.host; 98 | this.final_request.port = port || this.final_request.port; 99 | this.final_request.agent = agent || this.final_request.agent; 100 | this.final_request.body = body || this.final_request.body; 101 | this.final_request.query_params = 102 | query_params || this.final_request.query_params; 103 | this.final_request.requestHarObject = createRequestHarObject( 104 | this.final_request.requestHarObject || {}, 105 | proxyToServerRequestOptions 106 | ); 107 | }; 108 | 109 | set_final_response = ({ 110 | status_code = null, 111 | headers = null, 112 | body = null, 113 | }) => { 114 | if (headers) { 115 | this.final_response.headers = { ...headers }; 116 | } 117 | 118 | this.final_response.status_code = 119 | status_code || this.final_response.status_code; 120 | this.final_response.body = body || this.final_response.body; 121 | }; 122 | 123 | /** 124 | * Note: 125 | * 1. Gives body only if called after request end 126 | * 2. Currently only works for JSON body because we only provide json targetting on body right now 127 | */ 128 | get_json_request_body = () => { 129 | try { 130 | return JSON.parse(this.original_request.body); 131 | } catch (e) { 132 | /* Body is still buffer array */ 133 | } 134 | return null; 135 | }; 136 | } 137 | 138 | export default CtxRQNamespace; 139 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/handleUnreachableAddress.js: -------------------------------------------------------------------------------- 1 | import dns from 'dns'; 2 | 3 | export function isAddressUnreachableError(host) { 4 | return new Promise((resolve, reject) => { 5 | dns.lookup(host, (err, address) => { 6 | if (err) { 7 | if (err.code === 'ENOTFOUND') { 8 | resolve(true); 9 | } else { 10 | reject(err); 11 | } 12 | } else { 13 | resolve(false); 14 | } 15 | }); 16 | }); 17 | } 18 | 19 | export function dataToServeUnreachablePage(host) { 20 | return { 21 | status: 502, 22 | contentType: 'text/html', 23 | body: ` 24 | 25 | 26 | 27 | 28 | ERR_NAME_NOT_RESOLVED 29 | 30 | 67 | 68 | 69 |
70 |
:(
71 |

This site can’t be reached

72 |

The webpage at ${host}/ might be temporarily down or it may have moved permanently to a new web address.

73 |

ERR_NAME_NOT_RESOLVED

74 |
75 | 76 | 77 | `.trim() 78 | }; 79 | } -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/harObectCreator.js: -------------------------------------------------------------------------------- 1 | const createHarHeaders = (request_headers_obj) => { 2 | // convert headers obj to haar format 3 | const headers = []; 4 | if (request_headers_obj) { 5 | for (const key in request_headers_obj) { 6 | headers.push({ 7 | name: key, 8 | value: request_headers_obj[key], 9 | }); 10 | } 11 | } 12 | return headers; 13 | }; 14 | 15 | const createHarQueryStrings = (query_params) => { 16 | let res = [] 17 | 18 | Object.keys(query_params).forEach(query => { 19 | const query_value = query_params[query] 20 | if(Array.isArray(query_value)) { 21 | query_value.forEach(val => { 22 | res.push({ 23 | "name" : query, 24 | "value": val ?? "" 25 | }) 26 | }) 27 | } else { 28 | res.push({ 29 | "name" : query, 30 | "value": query_value ?? "" 31 | }) 32 | } 33 | }) 34 | 35 | return res 36 | }; 37 | 38 | const getContentType = (headers) => { 39 | let contentType = null; 40 | if (headers) { 41 | headers.forEach((item) => { 42 | if (item.name === "content-type") { 43 | contentType = item.value; 44 | } 45 | }); 46 | } 47 | return contentType; 48 | }; 49 | 50 | const buildHarPostParams = (contentType, body) => { 51 | if (contentType === "application/x-www-form-urlencoded") { 52 | return body 53 | .split("&") // Split by separators 54 | .map((keyValue) => { 55 | const [key, value] = keyValue.split("="); 56 | return { 57 | name: key, 58 | value, 59 | }; 60 | }); 61 | } else { 62 | // FormData has its own format 63 | // TODO Complete form data case -> where file is uploaded 64 | return { 65 | name: contentType, 66 | value: body, 67 | }; 68 | } 69 | }; 70 | 71 | const createHarPostData = (body, headers) => { 72 | // http://www.softwareishard.com/blog/har-12-spec/#postData 73 | if (!body) { 74 | return undefined; 75 | } 76 | 77 | const contentType = getContentType(headers); 78 | // if (!contentType) { 79 | // return undefined; 80 | // } 81 | // // console.log("contentType and Body", { contentType, body }, typeof body); 82 | // if ( 83 | // ["application/x-www-form-urlencoded", "multipart/form-data"].includes( 84 | // contentType 85 | // ) 86 | // ) { 87 | // return { 88 | // mimeType: contentType, 89 | // params: buildHarPostParams(contentType, body), 90 | // }; 91 | // } 92 | return { 93 | mimeType: contentType, // Let's assume by default content type is JSON 94 | text: body, 95 | }; 96 | }; 97 | 98 | // create standard request har object: http://www.softwareishard.com/blog/har-12-spec/#request 99 | // URL: https://github.com/hoppscotch/hoppscotch/blob/75ab7fdb00c0129ad42d45165bd3ad0af1faca2e/packages/hoppscotch-app/helpers/new-codegen/har.ts#L26 100 | export const createRequestHarObject = ( 101 | requestHarObject, 102 | proxyToServerRequestOptions 103 | ) => { 104 | const { 105 | method, 106 | host, 107 | path, 108 | body, 109 | headers, 110 | agent, 111 | query_params 112 | } = proxyToServerRequestOptions; 113 | 114 | return { 115 | bodySize: -1, // TODO: calculate the body size 116 | headersSize: -1, // TODO: calculate the header size 117 | httpVersion: "HTTP/1.1", 118 | cookies: [], // TODO: add support for Cookies 119 | headers: requestHarObject.headers || createHarHeaders(headers), 120 | method: requestHarObject.method || method, 121 | queryString: requestHarObject.queryString || createHarQueryStrings(query_params), 122 | url: 123 | requestHarObject.url || (agent?.protocol || "http:") + "//" + host + path, 124 | postData: 125 | requestHarObject.postData || 126 | createHarPostData(body, requestHarObject.headers), 127 | }; 128 | }; 129 | 130 | 131 | export const createHar = (requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams) => { 132 | return { 133 | "log": { 134 | "version" : "1.2", 135 | "creator" : {}, 136 | "browser" : {}, 137 | "pages": [], 138 | "entries": [createHarEntry(requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams)], 139 | "comment": "" 140 | } 141 | }; 142 | } 143 | 144 | export const createHarEntry = (requestHeaders, method, protocol, host, path, requestBody, responseStatusCode, response, responseHeaders, requestParams) => { 145 | return { 146 | // "pageref": "page_0", 147 | "startedDateTime": new Date().toISOString(), 148 | // "time": 50, 149 | "request": createHarRequest(requestHeaders, method, protocol, host, path, requestBody, requestParams), 150 | "response": createHarResponse(responseStatusCode, response, responseHeaders), 151 | "cache": {}, 152 | "timings": {}, 153 | // "serverIPAddress": "10.0.0.1", 154 | // "connection": "52492", 155 | "comment": "" 156 | }; 157 | } 158 | 159 | export const createHarRequest = (requestHeaders, method, protocol, host, path, requestBody, requestParams) => { 160 | return { 161 | bodySize: -1, // TODO: calculate the body size 162 | headersSize: -1, // TODO: calculate the header size 163 | httpVersion: "HTTP/1.1", 164 | cookies: [], // TODO: add support for Cookies 165 | headers: createHarHeaders(requestHeaders), 166 | method: method, 167 | queryString: createHarQueryStrings(requestParams), 168 | url: 169 | protocol + "://" + host + path, 170 | postData: 171 | createHarPostData(requestBody, createHarHeaders(requestHeaders)), 172 | }; 173 | } 174 | 175 | export const createHarResponse = (responseStatusCode, response, responseHeaders) => { 176 | return { 177 | "status": responseStatusCode, 178 | // "statusText": "OK", 179 | "httpVersion": "HTTP/1.1", 180 | "cookies": [], 181 | "headers": createHarHeaders(responseHeaders), 182 | "content": { 183 | "size": 33, 184 | "compression": 0, 185 | "mimeType": (responseHeaders && responseHeaders["content-type"]), 186 | "text": response, 187 | "comment": "" 188 | }, 189 | // "redirectURL": "", 190 | "headersSize" : -1, 191 | "bodySize" : -1, 192 | "comment" : "" 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/http_helpers.js: -------------------------------------------------------------------------------- 1 | import charset from "charset"; 2 | import mime from "mime-types"; 3 | 4 | 5 | export const getEncoding = (contentTypeHeader, buffer) => { 6 | const encoding = charset(contentTypeHeader, buffer) || mime.charset(contentTypeHeader) || "utf8"; 7 | return encoding; 8 | } 9 | 10 | export const bodyParser = (contentTypeHeader, buffer) => { 11 | const encoding = getEncoding(contentTypeHeader, buffer); 12 | try { 13 | return buffer.toString(encoding); 14 | } catch (error) { 15 | // some encodings are not supposed to be turned into string 16 | return buffer; 17 | } 18 | } 19 | 20 | 21 | export const getContentType = (contentTypeHeader) => { 22 | return contentTypeHeader?.split(";")[0] ?? null; 23 | }; 24 | 25 | export const parseJsonBody = (data) => { 26 | try { 27 | return JSON.parse(data); 28 | } catch (e) { 29 | /* Body is still buffer array */ 30 | } 31 | return data; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/proxy_ctx_helper.js: -------------------------------------------------------------------------------- 1 | import { 2 | CONSTANTS as GLOBAL_CONSTANTS, 3 | } from "@requestly/requestly-core" 4 | 5 | 6 | function extractUrlComponent(url, name) { // need this in proxy 7 | const myUrl = new URL(url); 8 | 9 | switch (name) { 10 | case GLOBAL_CONSTANTS.URL_COMPONENTS.URL: 11 | return url; 12 | case GLOBAL_CONSTANTS.URL_COMPONENTS.PROTOCOL: 13 | return myUrl.protocol; 14 | case GLOBAL_CONSTANTS.URL_COMPONENTS.HOST: 15 | return myUrl.host; 16 | case GLOBAL_CONSTANTS.URL_COMPONENTS.PATH: 17 | return myUrl.pathname; 18 | case GLOBAL_CONSTANTS.URL_COMPONENTS.QUERY: 19 | return myUrl.search; 20 | case GLOBAL_CONSTANTS.URL_COMPONENTS.HASH: 21 | return myUrl.hash; 22 | } 23 | 24 | console.error("Invalid source key", url, name); 25 | } 26 | 27 | /** 28 | * 29 | * @param queryString e.g. ?a=1&b=2 or a=1 or '' 30 | * @returns object { paramName -> [value1, value2] } 31 | */ 32 | export function getQueryParamsMap(queryString) { 33 | var map = {}, 34 | queryParams; 35 | 36 | if (!queryString || queryString === "?") { 37 | return map; 38 | } 39 | 40 | if (queryString[0] === "?") { 41 | queryString = queryString.substr(1); 42 | } 43 | 44 | queryParams = queryString.split("&"); 45 | 46 | queryParams.forEach(function (queryParam) { 47 | var paramName = queryParam.split("=")[0], 48 | paramValue = queryParam.split("=")[1]; 49 | 50 | // We are keeping value of param as array so that in future we can support multiple param values of same name 51 | // And we do not want to lose the params if url already contains multiple params of same name 52 | map[paramName] = map[paramName] || []; 53 | map[paramName].push(paramValue); 54 | }); 55 | 56 | return map; 57 | } 58 | 59 | 60 | export const get_request_url = (ctx) => { 61 | return ( 62 | (ctx.isSSL ? "https://" : "http://") + 63 | ctx.clientToProxyRequest.headers.host + 64 | ctx.clientToProxyRequest.url 65 | ); 66 | }; 67 | 68 | export const get_original_request_headers = (ctx) => { 69 | // TODO: This needs to be fetched from ctx.clientToProxy headers 70 | return ctx.proxyToServerRequestOptions.headers; 71 | }; 72 | 73 | export const get_original_response_headers = (ctx) => { 74 | // TODO: This needs to be fetched from ctx.clientToProxy headers 75 | return ctx?.serverToProxyResponse?.headers || {}; 76 | }; 77 | 78 | export const is_request_preflight = (ctx) => { 79 | if ( 80 | // Request method is OPTIONS 81 | ctx.clientToProxyRequest.method.toLowerCase() === "options" && 82 | // Has "origin" or "Origin" or "ORIGIN" header 83 | (ctx.clientToProxyRequest.headers["Origin"] || 84 | ctx.clientToProxyRequest.headers["origin"] || 85 | ctx.clientToProxyRequest.headers["ORIGIN"]) && 86 | // Has Access-Control-Request-Method header 87 | (ctx.clientToProxyRequest.headers["Access-Control-Request-Method"] || 88 | ctx.clientToProxyRequest.headers["access-control-request-method"] || 89 | ctx.clientToProxyRequest.headers["ACCESS-CONTROL-REQUEST-METHOD"]) 90 | ) 91 | return true; 92 | else return false; 93 | }; 94 | 95 | export const get_response_options = (ctx) => { 96 | const options = {}; 97 | 98 | options.status_code = ctx.serverToProxyResponse.statusCode; 99 | options.headers = ctx.serverToProxyResponse.headers; 100 | 101 | return options; 102 | }; 103 | 104 | export const get_request_options = (ctx) => { 105 | return { 106 | ...ctx.proxyToServerRequestOptions, 107 | query_params: get_json_query_params(ctx), 108 | }; 109 | }; 110 | 111 | export const get_json_query_params = (ctx) => { 112 | const url = get_request_url(ctx); 113 | let queryString = extractUrlComponent(url, GLOBAL_CONSTANTS.URL_COMPONENTS.QUERY); 114 | return getQueryParamsMap(queryString) || null; 115 | }; 116 | 117 | export const getRequestHeaders = (ctx) => { 118 | if (ctx && ctx.proxyToServerRequestOptions) { 119 | return ctx.proxyToServerRequestOptions.headers || {}; 120 | } 121 | 122 | return {}; 123 | }; 124 | 125 | export const getRequestContentTypeHeader = (ctx) => { 126 | return getRequestHeaders(ctx)["content-type"]; 127 | }; 128 | 129 | export const getResponseHeaders = (ctx) => { 130 | if (ctx && ctx.serverToProxyResponse) { 131 | return ctx.serverToProxyResponse.headers || {}; 132 | } 133 | 134 | return {}; 135 | }; 136 | 137 | export const getResponseContentTypeHeader = (ctx) => { 138 | return getResponseHeaders(ctx)["content-type"]; 139 | }; 140 | 141 | export const getResponseStatusCode = (ctx) => { 142 | if (ctx && ctx.serverToProxyResponse) { 143 | return ctx.serverToProxyResponse.statusCode; 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/helpers/response_helper.js: -------------------------------------------------------------------------------- 1 | export const isResponseHTML = (ctx) => { 2 | return ( 3 | ctx.serverToProxyResponse.headers["content-type"] && 4 | ctx.serverToProxyResponse.headers["content-type"].indexOf("text/html") === 0 5 | ); 6 | }; 7 | 8 | export const parseStringToDOM = (string) => { 9 | return new DOMParser().parseFromString(string, "text/html"); 10 | }; 11 | 12 | export const parseDOMToString = (DOM) => { 13 | return DOM.documentElement.outerHTML; 14 | // return new XMLSerializer().serializeToString(DOM); 15 | }; -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/amiusing_middleware.js: -------------------------------------------------------------------------------- 1 | class AmisuingMiddleware { 2 | constructor(is_active) { 3 | this.is_active = is_active; 4 | } 5 | 6 | on_request = async (ctx) => { 7 | if (!this.is_active) { 8 | return true; 9 | } 10 | 11 | if(ctx.proxyToServerRequestOptions.host === "amiusing.requestly.io") { 12 | Object.assign(ctx.proxyToServerRequestOptions.headers, { 13 | ["amiusingrequestly"]: "true", 14 | }); 15 | } 16 | 17 | }; 18 | } 19 | 20 | export default AmisuingMiddleware; 21 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/logger_middleware.js: -------------------------------------------------------------------------------- 1 | // import { cloneDeep } from "lodash"; 2 | // import HTTPSnippet from "httpsnippet"; 3 | import { get_success_actions_from_action_results } from "../rule_action_processor/utils"; 4 | import { createHar } from "../helpers/harObectCreator"; 5 | // const url = require("url"); 6 | 7 | class LoggerMiddleware { 8 | constructor(is_active, loggerService) { 9 | this.is_active = is_active; 10 | this.loggerService = loggerService; 11 | } 12 | 13 | generate_curl_from_har = (requestHarObject) => { 14 | if (!requestHarObject) { 15 | return ""; 16 | } 17 | let requestCurl = ""; 18 | // try { 19 | // const harObject = cloneDeep(requestHarObject); 20 | // requestCurl = new HTTPSnippet(harObject).convert("shell", "curl", { 21 | // indent: " ", 22 | // }); 23 | // } catch (err) { 24 | // console.error(`LoggerMiddleware.generate_curl_from_har Error: ${err}`); 25 | // } 26 | return requestCurl; 27 | }; 28 | 29 | send_network_log = (ctx, action_result_objs = [],requestState="") => { 30 | // let query_params_string; 31 | // if (ctx.rq.final_request.query_params) { 32 | // query_params_string = JSON.stringify(ctx.rq.final_request.query_params); 33 | // } 34 | // const log = { 35 | // id: ctx.uuid, 36 | // timestamp: Math.floor(Date.now() / 1000), 37 | // url: url.parse( 38 | // (ctx.isSSL ? "https://" : "http://") + 39 | // ctx.clientToProxyRequest.headers.host + 40 | // ctx.clientToProxyRequest.url 41 | // ).href, 42 | // request: { 43 | // method: ctx.rq.final_request.method, 44 | // path: ctx.rq.final_request.host, 45 | // host: ctx.rq.final_request.host, 46 | // port: ctx.rq.final_request.port, 47 | // headers: ctx.rq.final_request.headers, 48 | // body: ctx.rq.final_request.body, 49 | // query_params: query_params_string, 50 | // }, 51 | // requestShellCurl: this.generate_curl_from_har( 52 | // ctx.rq.final_request.requestHarObject 53 | // ), 54 | // response: { 55 | // statusCode: ctx.rq.final_response.status_code, 56 | // headers: ctx.rq.final_response.headers || {}, 57 | // contentType: 58 | // (ctx.rq.final_response.headers && 59 | // ctx.rq.final_response.headers["content-type"] && 60 | // ctx.rq.final_response.headers["content-type"].includes(";") && 61 | // ctx.rq.final_response.headers["content-type"].split(";")[0]) || 62 | // (ctx.rq.final_response.headers && 63 | // ctx.rq.final_response.headers["content-type"]), 64 | // body: ctx.rq.final_response.body || null, 65 | // }, 66 | // actions: get_success_actions_from_action_results(action_result_objs), 67 | // }; 68 | // ipcRenderer.send("log-network-request", log); 69 | // TODO: Sending log for now. Ideally this should be har object 70 | this.loggerService.addLog(this.createLog(ctx, action_result_objs, requestState), ctx.rq.final_request.headers || {}) 71 | }; 72 | 73 | createLog = (ctx, action_result_objs = [], requestState="") => { 74 | const protocol = ctx.isSSL ? "https" : "http"; 75 | const rqLog = { 76 | id: ctx.uuid, 77 | timestamp: Math.floor(Date.now() / 1000), 78 | finalHar: createHar( 79 | ctx.rq.final_request.headers, 80 | ctx.rq.final_request.method, 81 | protocol, 82 | ctx.rq.final_request.host, 83 | ctx.rq.final_request.path, 84 | ctx.rq.final_request.body, 85 | ctx.rq.final_response.status_code, 86 | ctx.rq.final_response.body, 87 | ctx.rq.final_response.headers || {}, 88 | ctx.rq.final_request.query_params 89 | ), 90 | requestShellCurl: this.generate_curl_from_har(ctx?.rq?.final_request?.requestHarObject), // TODO: Move this to client side 91 | actions: get_success_actions_from_action_results(action_result_objs), 92 | consoleLogs : ctx?.rq?.consoleLogs, 93 | requestState 94 | } 95 | return rqLog; 96 | } 97 | } 98 | 99 | export default LoggerMiddleware; 100 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/rules_middleware.js: -------------------------------------------------------------------------------- 1 | import { 2 | get_request_url, 3 | get_original_request_headers, 4 | get_original_response_headers, 5 | } from "../helpers/proxy_ctx_helper"; 6 | import RuleProcessorHelper from "../helpers/rule_processor_helper"; 7 | import RuleActionProcessor from "../rule_action_processor"; 8 | 9 | class RulesMiddleware { 10 | constructor(is_active, ctx, rulesHelper) { 11 | this.is_active = is_active; 12 | 13 | this.rule_processor_helper = new RuleProcessorHelper(); 14 | this.rule_action_processor = new RuleActionProcessor(); 15 | 16 | this.rulesHelper = rulesHelper; 17 | 18 | this._init_request_data(ctx); 19 | this.active_rules = []; 20 | this._fetch_rules(); 21 | 22 | // Keeping these 2 separate because of request and response headers 23 | // from rule processor are triggers during different proxy hooks. 24 | // These can be combined into 1 if we change the actions returned 25 | // by modify headers rule processor 26 | // TODO: @sahil865gupta UPGRADE MODIFY HEADER ACTIONS 27 | // https://github.com/requestly/requestly-master/issues/686 28 | this.on_request_actions = []; 29 | this.on_response_actions = []; 30 | 31 | this.action_result_objs = []; 32 | } 33 | 34 | _init_request_data = (ctx) => { 35 | this.request_data = { 36 | request_url: get_request_url(ctx), 37 | request_headers: get_original_request_headers(ctx), 38 | query_params: ctx.rq?.original_request?.query_params, 39 | method: ctx.rq?.original_request?.method, 40 | }; 41 | this.rule_processor_helper.init_request_data(this.request_data); 42 | }; 43 | 44 | _init_response_data = (ctx) => { 45 | this.response_data = { 46 | response_headers: get_original_response_headers(ctx), 47 | }; 48 | this.rule_processor_helper.init_response_data(this.response_data); 49 | }; 50 | _update_request_data = (data) => { 51 | this.request_data = { 52 | ...this.request_data, 53 | ...data, 54 | }; 55 | this.rule_processor_helper.init_request_data(this.request_data); 56 | }; 57 | 58 | _fetch_rules = async () => { 59 | this.active_rules = await this.rulesHelper.get_rules( 60 | true, 61 | this.request_data?.request_headers || {} 62 | ); 63 | }; 64 | 65 | /* 66 | @return: actions[] 67 | */ 68 | _process_rules = (is_response = false) => { 69 | // https://github.com/requestly/requestly-master/issues/686 70 | // 1 time processing if we fix this issue 71 | let rule_actions = this.rule_processor_helper.process_rules( 72 | this.active_rules, 73 | is_response 74 | ); 75 | 76 | // Filter out all the null actions 77 | rule_actions = rule_actions.filter((action) => !!action); 78 | 79 | return rule_actions; 80 | }; 81 | 82 | _update_action_result_objs = (action_result_objs = []) => { 83 | if (action_result_objs) { 84 | this.action_result_objs = 85 | this.action_result_objs.concat(action_result_objs); 86 | } 87 | }; 88 | 89 | on_request = async (ctx) => { 90 | if (!this.is_active) { 91 | return []; 92 | } 93 | 94 | // TODO: Remove this. Hack to fix rule not fetching first time. 95 | await this._fetch_rules(); 96 | 97 | this.on_request_actions = this._process_rules(); 98 | 99 | const { action_result_objs, continue_request } = 100 | await this.rule_action_processor.process_actions( 101 | this.on_request_actions, 102 | ctx 103 | ); 104 | this._update_action_result_objs(action_result_objs); 105 | return { action_result_objs, continue_request }; 106 | }; 107 | 108 | on_response = async (ctx) => { 109 | if (!this.is_active) { 110 | return []; 111 | } 112 | 113 | this._init_response_data(ctx); 114 | this.on_response_actions = this._process_rules(true); 115 | 116 | const { action_result_objs, continue_request } = 117 | await this.rule_action_processor.process_actions( 118 | this.on_response_actions, 119 | ctx 120 | ); 121 | 122 | this._update_action_result_objs(action_result_objs); 123 | return { action_result_objs, continue_request }; 124 | }; 125 | 126 | on_request_end = async (ctx) => { 127 | if (!this.is_active) { 128 | return []; 129 | } 130 | this._update_request_data({ request_body: ctx.rq.get_json_request_body() }); 131 | 132 | this.on_request_actions = this._process_rules(); 133 | 134 | const { action_result_objs, continue_request } = 135 | await this.rule_action_processor.process_actions( 136 | this.on_request_actions, 137 | ctx 138 | ); 139 | 140 | this._update_action_result_objs(action_result_objs); 141 | return { action_result_objs, continue_request }; 142 | }; 143 | 144 | on_response_end = async (ctx) => { 145 | if (!this.is_active) { 146 | return []; 147 | } 148 | 149 | const { action_result_objs, continue_request } = 150 | await this.rule_action_processor.process_actions( 151 | this.on_response_actions, 152 | ctx 153 | ); 154 | 155 | this._update_action_result_objs(action_result_objs); 156 | return { action_result_objs, continue_request }; 157 | }; 158 | } 159 | 160 | export default RulesMiddleware; 161 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/ssl_cert_middleware.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | class SslCertMiddleware { 4 | constructor(is_active, rootCertPath) { 5 | this.is_active = is_active; 6 | this.rootCertPath = rootCertPath; 7 | } 8 | 9 | on_request = async (ctx) => { 10 | if (!this.is_active) { 11 | return true; 12 | } 13 | 14 | if ( 15 | ctx.clientToProxyRequest.headers.host == "requestly.io" && 16 | ctx.clientToProxyRequest.url.indexOf("/ssl") == 0 17 | ) { 18 | ctx.proxyToClientResponse.writeHead(200, { 19 | "Content-Type": "application/x-x509-ca-cert", 20 | "Content-Disposition": "attachment;filename=RQProxyCA.pem.crt", 21 | }); 22 | const certificateString = fs.readFileSync(this.rootCertPath); 23 | ctx.proxyToClientResponse.end(certificateString); 24 | } 25 | }; 26 | } 27 | 28 | export default SslCertMiddleware; 29 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/state.d.ts: -------------------------------------------------------------------------------- 1 | import IInitialGlobalState from '../../interfaces/state'; 2 | 3 | declare class GlobalState { 4 | private state: IInitialGlobalState; 5 | constructor(initialState?: IInitialGlobalState); 6 | 7 | setSharedState(newSharedState: Record): void; 8 | getSharedStateRef(): Record; 9 | getSharedStateCopy(): Record; 10 | setVariables(newVariables: Record): void; 11 | getVariablesRef(): Record; 12 | getVariablesCopy(): Record; 13 | } 14 | 15 | declare const ProxyGlobal: GlobalState; 16 | 17 | export { GlobalState as default, ProxyGlobal }; -------------------------------------------------------------------------------- /src/components/proxy-middleware/middlewares/state.ts: -------------------------------------------------------------------------------- 1 | import {cloneDeep} from 'lodash'; 2 | 3 | interface IState extends Record { 4 | sharedState?: Record; 5 | variables?: Record; 6 | } 7 | 8 | export class State { 9 | private state: IState; 10 | constructor(sharedState: Record, envVars: Record) { 11 | this.state = { 12 | variables: envVars, 13 | sharedState, 14 | }; 15 | } 16 | 17 | setSharedState(newSharedState: Record) { 18 | this.state.sharedState = newSharedState; 19 | } 20 | 21 | getSharedStateRef() { 22 | return this.state.sharedState; 23 | } 24 | 25 | getSharedStateCopy() { 26 | return cloneDeep(this.state.sharedState); 27 | } 28 | 29 | setVariables(newVariables: Record) { 30 | this.state.variables = newVariables; 31 | } 32 | 33 | getVariablesRef() { 34 | return this.state.variables; 35 | } 36 | 37 | getVariablesCopy() { 38 | return cloneDeep(this.state.variables); 39 | } 40 | } 41 | 42 | export default class GlobalStateProvider { 43 | private static instance: State 44 | static initInstance(sharedState: Record = {}, envVars: Record = {}) { 45 | if (!GlobalStateProvider.instance) { 46 | GlobalStateProvider.instance = new State(sharedState ?? {}, envVars ?? {}); 47 | } 48 | 49 | return GlobalStateProvider.instance; 50 | } 51 | 52 | static getInstance() { 53 | if (!GlobalStateProvider.instance) { 54 | console.error("[GlobalStateProvider]", "Init first"); 55 | } 56 | 57 | return GlobalStateProvider.instance; 58 | } 59 | } -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/handle_mixed_response.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const parser = require("ua-parser-js"); 3 | import fs from "fs"; 4 | import * as Sentry from "@sentry/browser"; 5 | const mime = require('mime-types'); 6 | 7 | const handleMixedResponse = async (ctx, destinationUrl) => { 8 | // Handling mixed response from safari 9 | let user_agent_str = null; 10 | user_agent_str = ctx?.clientToProxyRequest?.headers["user-agent"]; 11 | const user_agent = parser(user_agent_str)?.browser?.name; 12 | const LOCAL_DOMAINS = ["localhost", "127.0.0.1"]; 13 | 14 | if (ctx.isSSL && destinationUrl.includes("http:")) { 15 | if ( 16 | user_agent === "Safari" || 17 | !LOCAL_DOMAINS.some((domain) => destinationUrl.includes(domain)) 18 | ) { 19 | try { 20 | const resp = await axios.get(destinationUrl, { 21 | headers: { 22 | "Cache-Control": "no-cache", 23 | }, 24 | }); 25 | 26 | return { 27 | status: true, 28 | response_data: { 29 | headers: { "Cache-Control": "no-cache" }, 30 | status_code: 200, 31 | body: resp.data, 32 | }, 33 | }; 34 | } catch (e) { 35 | Sentry.captureException(e); 36 | return { 37 | status: true, 38 | response_data: { 39 | headers: { "Cache-Control": "no-cache" }, 40 | status_code: 502, 41 | body: e.response ? e.response.data : null, 42 | }, 43 | }; 44 | } 45 | } 46 | } 47 | 48 | if(destinationUrl?.startsWith("file://")) { 49 | const path = destinationUrl.slice(7) 50 | try { 51 | const buffers = fs.readFileSync(path); 52 | const mimeType = mime.lookup(path) 53 | const bodyContent = buffers.toString("utf-8") // assuming utf-8 encoding 54 | const headers = mimeType ? { 55 | "content-type": mimeType, 56 | "Content-Length": Buffer.byteLength(bodyContent), 57 | "Cache-Control": "no-cache" 58 | } : { 59 | "Cache-Control": "no-cache" 60 | } 61 | 62 | headers["access-control-allow-origin"] = "*"; 63 | headers["access-control-allow-credentials"] = "true"; 64 | headers["access-control-allow-methods"] = "*"; 65 | headers["access-control-allow-headers"] = "*"; 66 | 67 | return { 68 | status: true, 69 | response_data: { 70 | headers, 71 | status_code: 200, 72 | body: buffers.toString("utf-8"), // assuming utf-8 encoding 73 | }, 74 | }; 75 | } catch (err) { 76 | Sentry.captureException(err); 77 | // log for live debugging 78 | console.log("error in openning local file", err) 79 | return { 80 | status: true, 81 | response_data: { 82 | headers: { "Cache-Control": "no-cache" }, 83 | status_code: 502, 84 | body: err?.message, 85 | }, 86 | }; 87 | } 88 | } 89 | return { status: false }; 90 | }; 91 | 92 | export default handleMixedResponse; 93 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/index.js: -------------------------------------------------------------------------------- 1 | import { RULE_ACTION } from "../constants"; 2 | import process_redirect_action from "./processors/redirect_processor"; 3 | import process_modify_header_action from "./processors/modify_header_processor"; 4 | import process_modify_user_agent_action from "./processors/modify_user_agent_processor"; 5 | import process_delay_action from "./processors/delay_processor"; 6 | import process_block_action from "./processors/block_processor"; 7 | import process_modify_response_action from "./processors/modify_response_processor"; 8 | import process_modify_request_action from "./processors/modify_request_processor"; 9 | import process_insert_action from "./processors/insert_processor"; 10 | import { build_action_processor_response } from "./utils"; 11 | 12 | class RuleActionProcessor { 13 | constructor() {} 14 | 15 | /* 16 | @return: Whether to continue with the proxy callback. & of all the continue_request from actions 17 | */ 18 | process_actions = async (rule_actions, ctx) => { 19 | /* 20 | action_result_objs = [{action: {}, result: {}}}] 21 | */ 22 | const action_result_objs = await Promise.all( 23 | rule_actions.map(async (action) => { 24 | let action_result_obj = await this.process_action(action, ctx); 25 | return action_result_obj; 26 | }) 27 | ); 28 | 29 | let continue_request = true; 30 | continue_request = this.post_process_actions(action_result_objs, ctx); 31 | 32 | return { action_result_objs, continue_request }; 33 | }; 34 | 35 | post_process_actions = (action_result_objs, ctx) => { 36 | let continue_request = true; 37 | 38 | action_result_objs.forEach((action_result) => { 39 | if (!continue_request) return; // Already finished the request 40 | if (!action_result.post_process_data) return; // No post processing 41 | 42 | const status_code = action_result.post_process_data.status_code || 200; 43 | const headers = action_result.post_process_data.headers || {}; 44 | let body = action_result.post_process_data.body || null; 45 | 46 | // console.log("Log", ctx.rq.original_request); 47 | if(typeof(body) !== 'string') { 48 | body = JSON.stringify(body); 49 | } 50 | 51 | ctx.proxyToClientResponse.writeHead(status_code, headers).end(body); 52 | ctx.rq.request_finished = true; 53 | 54 | ctx.rq.set_final_response({ 55 | status_code: status_code, 56 | headers: headers, 57 | body: body, 58 | }); 59 | continue_request = false; 60 | }); 61 | return continue_request; 62 | }; 63 | 64 | /* 65 | @return: Whether to continue with the proxy callback 66 | */ 67 | process_action = async (rule_action, ctx) => { 68 | let action_result = build_action_processor_response(rule_action, false); 69 | 70 | if (!rule_action) { 71 | return action_result; 72 | } 73 | 74 | switch (rule_action.action) { 75 | case RULE_ACTION.REDIRECT: 76 | action_result = process_redirect_action(rule_action, ctx); 77 | break; 78 | case RULE_ACTION.MODIFY_HEADERS: 79 | action_result = process_modify_header_action(rule_action, ctx); 80 | break; 81 | case RULE_ACTION.MODIFY_USER_AGENT: 82 | action_result = process_modify_user_agent_action(rule_action, ctx); 83 | case RULE_ACTION.DELAY: 84 | action_result = await process_delay_action(rule_action, ctx); 85 | break; 86 | case RULE_ACTION.BLOCK: 87 | action_result = process_block_action(rule_action, ctx); 88 | break; 89 | case RULE_ACTION.MODIFY_REQUEST: 90 | action_result = process_modify_request_action(rule_action, ctx); 91 | break; 92 | case RULE_ACTION.MODIFY_RESPONSE: 93 | action_result = await process_modify_response_action(rule_action, ctx); 94 | break; 95 | case RULE_ACTION.INSERT: 96 | action_result = process_insert_action(rule_action, ctx); 97 | default: 98 | break; 99 | } 100 | 101 | return action_result; 102 | }; 103 | } 104 | 105 | export default RuleActionProcessor; 106 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/modified_requests_pool.js: -------------------------------------------------------------------------------- 1 | import Queue from "../../../utils/circularQueue"; 2 | 3 | class ModifiedRequestsPool { 4 | constructor() { 5 | this.queue = new Queue(100); 6 | } 7 | 8 | add(url) { 9 | url = url.replace(/www\./g, ""); 10 | this.queue.enQueue(url); 11 | // logger.log(this.queue); 12 | } 13 | 14 | isURLModified(url) { 15 | if (!url) return false; 16 | // logger.log("Current Url : ", url); 17 | // logger.log(JSON.stringify(this.queue), "Looper"); 18 | 19 | let tempUrl = url; 20 | if (url.endsWith("/")) { 21 | tempUrl = tempUrl.slice(0, tempUrl.length - 1); 22 | } else { 23 | tempUrl = tempUrl + "/"; 24 | } 25 | // logger.log(tempUrl); 26 | return ( 27 | this.queue.getElementIndex(url) >= 0 || 28 | this.queue.getElementIndex(tempUrl) >= 0 29 | ); 30 | } 31 | } 32 | 33 | const modifiedRequestsPool = new ModifiedRequestsPool(); 34 | 35 | export default modifiedRequestsPool; 36 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/block_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | import { 3 | build_action_processor_response, 4 | build_post_process_data, 5 | } from "../utils"; 6 | 7 | const process_block_action = async (action, ctx) => { 8 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_REQUEST]; 9 | 10 | if (!allowed_handlers.includes(ctx.currentHandler)) { 11 | return build_action_processor_response(action, false); 12 | } 13 | 14 | return build_action_processor_response( 15 | action, 16 | true, 17 | build_post_process_data( 18 | 418 /** Move this to temporarily out of coffee (503) if causes issues in someone production use case. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418 */, 19 | { "Cache-Control": "no-cache" }, 20 | "Access to this URL has been blocked by Requestly" 21 | ) 22 | ); 23 | }; 24 | 25 | export default process_block_action; 26 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/delay_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | import { build_action_processor_response } from "../utils"; 3 | 4 | const resolveAfterDelay = async (durationInMs) => { 5 | return new Promise((resolve) => { 6 | setTimeout(() => { 7 | resolve("resolved"); 8 | }, durationInMs); 9 | }); 10 | }; 11 | 12 | const process_delay_action = async (action, ctx) => { 13 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_REQUEST]; 14 | 15 | if (!allowed_handlers.includes(ctx.currentHandler)) { 16 | return build_action_processor_response(action, false); 17 | } 18 | 19 | await resolveAfterDelay(action.delay); 20 | return build_action_processor_response(action, true); 21 | }; 22 | 23 | export default process_delay_action; 24 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/insert_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | 3 | import { 4 | CONSTANTS as GLOBAL_CONSTANTS, 5 | } from "@requestly/requestly-core"; 6 | import { 7 | isResponseHTML, 8 | parseDOMToString, 9 | parseStringToDOM, 10 | } from "../../helpers/response_helper"; 11 | import { build_action_processor_response } from "../utils"; 12 | 13 | const process_insert_action = (action, ctx) => { 14 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_RESPONSE_END]; 15 | 16 | if (!allowed_handlers.includes(ctx.currentHandler)) { 17 | return build_action_processor_response(action, false); 18 | } 19 | 20 | const success = insert_scripts(ctx, action); 21 | return build_action_processor_response(action, success); 22 | }; 23 | 24 | const modify_response = (ctx, new_resp) => { 25 | ctx.rq_response_body = new_resp; 26 | }; 27 | 28 | const insert_scripts = (ctx, action) => { 29 | if (isResponseHTML(ctx)) { 30 | const fullResponseBodyString = ctx.rq_response_body; 31 | 32 | // Double check to ensure content is HTML 33 | if (/<\/?[a-z][\s\S]*>/i.test(fullResponseBodyString)) { 34 | // Parse Response body (string) to DOM 35 | let incomingDOM = parseStringToDOM(fullResponseBodyString); 36 | 37 | // Modify DOM to inject scripts 38 | handleCSSScripts(action.data.cssScripts, incomingDOM); 39 | handleJSLibraries(action.data.libraries, incomingDOM); 40 | handleJSScripts(action.data.jsScripts, incomingDOM); 41 | 42 | // Parse back DOM to String to forward client as response body 43 | const final_response_body = parseDOMToString(incomingDOM); 44 | modify_response(ctx, final_response_body); 45 | 46 | return true; 47 | } 48 | } 49 | return false; 50 | }; 51 | 52 | /** 53 | * Handles all CSS Scripts 54 | */ 55 | const handleCSSScripts = (cssScripts, incomingDOM) => { 56 | cssScripts.forEach((script) => { 57 | includeCSS(script, incomingDOM); 58 | }); 59 | }; 60 | 61 | /** 62 | * Prepares to inject a CSS Scripts 63 | * @returns {string} HTML Content 64 | */ 65 | const includeCSS = (script, incomingDOM) => { 66 | if (script.type === GLOBAL_CONSTANTS.SCRIPT_TYPES.URL) { 67 | addRemoteCSS(script.value, incomingDOM); 68 | } else if (script.type === GLOBAL_CONSTANTS.SCRIPT_TYPES.CODE) { 69 | embedCSS(script.value, incomingDOM); 70 | } 71 | }; 72 | 73 | const addRemoteCSS = (src, incomingDOM) => { 74 | var link = incomingDOM.createElement("link"); 75 | link.href = src; 76 | link.type = "text/css"; 77 | link.rel = "stylesheet"; 78 | link.className = getScriptClassAttribute(); 79 | (incomingDOM.head || incomingDOM.documentElement).appendChild(link); 80 | }; 81 | 82 | const getScriptClassAttribute = () => { 83 | return GLOBAL_CONSTANTS.PUBLIC_NAMESPACE + "SCRIPT"; 84 | }; 85 | 86 | const embedCSS = (css, incomingDOM) => { 87 | var style = incomingDOM.createElement("style"); 88 | style.type = "text/css"; 89 | style.appendChild(incomingDOM.createTextNode(css)); 90 | style.className = getScriptClassAttribute(); 91 | (incomingDOM.head || incomingDOM.documentElement).appendChild(style); 92 | }; 93 | 94 | const handleJSLibraries = (libraries, incomingDOM) => { 95 | addLibraries(libraries, null, incomingDOM); 96 | }; 97 | 98 | const addLibraries = (libraries, indexArg, incomingDOM) => { 99 | var index = indexArg || 0; 100 | 101 | if (index >= libraries.length) { 102 | return; 103 | } 104 | 105 | var libraryKey = libraries[index]; 106 | var library = GLOBAL_CONSTANTS.SCRIPT_LIBRARIES[libraryKey]; 107 | var addNextLibraries = () => { 108 | addLibraries(libraries, index + 1, incomingDOM); 109 | }; 110 | 111 | if (library) { 112 | addRemoteJS(library.src, addNextLibraries, incomingDOM); 113 | } else { 114 | addNextLibraries(); 115 | } 116 | }; 117 | 118 | const addRemoteJS = (src, callback, incomingDOM) => { 119 | var script = incomingDOM.createElement("script"); 120 | // NOT WORKING 121 | // if (typeof callback === "function") { 122 | // script.onload = callback 123 | // } 124 | script.type = "text/javascript"; 125 | script.src = src; 126 | script.className = getScriptClassAttribute(); 127 | (incomingDOM.head || incomingDOM.documentElement).appendChild(script); 128 | 129 | // HOTFIX 130 | callback(); 131 | }; 132 | 133 | const handleJSScripts = (jsScripts, incomingDOM) => { 134 | var prePageLoadScripts = []; 135 | var postPageLoadScripts = []; 136 | 137 | jsScripts.forEach((script) => { 138 | if ( 139 | script.loadTime === GLOBAL_CONSTANTS.SCRIPT_LOAD_TIME.BEFORE_PAGE_LOAD 140 | ) { 141 | prePageLoadScripts.push(script); 142 | } else { 143 | postPageLoadScripts.push(script); 144 | } 145 | }); 146 | 147 | includeJSScriptsInOrder( 148 | prePageLoadScripts, 149 | () => { 150 | includeJSScriptsInOrder(postPageLoadScripts, null, null, incomingDOM); 151 | }, 152 | null, 153 | incomingDOM 154 | ); 155 | }; 156 | 157 | const includeJSScriptsInOrder = (scripts, callback, indexArg, incomingDOM) => { 158 | var index = indexArg || 0; 159 | 160 | if (index >= scripts.length) { 161 | typeof callback === "function" && callback(); 162 | return; 163 | } 164 | 165 | includeJS( 166 | scripts[index], 167 | () => { 168 | includeJSScriptsInOrder(scripts, callback, index + 1, incomingDOM); 169 | }, 170 | incomingDOM 171 | ); 172 | }; 173 | 174 | const includeJS = (script, callback, incomingDOM) => { 175 | if (script.type === GLOBAL_CONSTANTS.SCRIPT_TYPES.URL) { 176 | addRemoteJS(script.value, callback, incomingDOM); 177 | return; 178 | } 179 | 180 | if (script.type === GLOBAL_CONSTANTS.SCRIPT_TYPES.CODE) { 181 | executeJS(script.value, null, incomingDOM); 182 | } 183 | 184 | typeof callback === "function" && callback(); 185 | }; 186 | 187 | const executeJS = (code, shouldRemove, incomingDOM) => { 188 | const script = incomingDOM.createElement("script"); 189 | script.type = "text/javascript"; 190 | script.className = getScriptClassAttribute(); 191 | script.appendChild(incomingDOM.createTextNode(code)); 192 | const parent = incomingDOM.head || incomingDOM.documentElement; 193 | parent.appendChild(script); 194 | if (shouldRemove) { 195 | parent.removeChild(script); 196 | } 197 | }; 198 | 199 | export default process_insert_action; 200 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/modify_header_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | import { get_request_url } from "../../helpers/proxy_ctx_helper"; 3 | import { build_action_processor_response } from "../utils"; 4 | 5 | const process_modify_header_action = (action, ctx) => { 6 | const allowed_handlers = [ 7 | PROXY_HANDLER_TYPE.ON_REQUEST, 8 | PROXY_HANDLER_TYPE.ON_RESPONSE, 9 | PROXY_HANDLER_TYPE.ON_ERROR, 10 | ]; 11 | 12 | if (!allowed_handlers.includes(ctx.currentHandler)) { 13 | return build_action_processor_response(action, false); 14 | } 15 | 16 | if (ctx.currentHandler == PROXY_HANDLER_TYPE.ON_REQUEST) { 17 | modify_request_headers(action, ctx); 18 | } else if (ctx.currentHandler === PROXY_HANDLER_TYPE.ON_RESPONSE || ctx.currentHandler === PROXY_HANDLER_TYPE.ON_ERROR) { 19 | modify_response_headers(action, ctx); 20 | } 21 | return build_action_processor_response(action, true); 22 | }; 23 | 24 | const modify_request_headers = (action, ctx) => { 25 | const newRequestHeaders = action.newHeaders; 26 | for (var headerName in ctx.proxyToServerRequestOptions.headers) { 27 | if (ctx.proxyToServerRequestOptions.headers.hasOwnProperty(headerName)) { 28 | delete ctx.proxyToServerRequestOptions.headers[headerName]; 29 | } 30 | } 31 | 32 | // Set Request Headers 33 | newRequestHeaders.forEach( 34 | (pair) => (ctx.proxyToServerRequestOptions.headers[pair.name] = pair.value) 35 | ); 36 | }; 37 | 38 | const modify_response_headers = (action, ctx) => { 39 | ctx.serverToProxyResponse = ctx.serverToProxyResponse || {} 40 | ctx.serverToProxyResponse.headers = ctx.serverToProxyResponse.headers || {} 41 | // {"header1":"val1", "header2":"val2"} 42 | const originalResponseHeadersObject = ctx.serverToProxyResponse.headers; 43 | // ["header1","header2"] 44 | const originalResponseHeadersObjectKeys = Object.keys( 45 | originalResponseHeadersObject 46 | ); 47 | // [{name:"header1", value:"val1"},{name:"header2", value:"val2"}] 48 | const originalResponseHeadersObjectKeysValuePairs = originalResponseHeadersObjectKeys.map( 49 | (key) => { 50 | return { 51 | name: key, 52 | value: originalResponseHeadersObject[key], 53 | }; 54 | } 55 | ); 56 | 57 | const requestURL = get_request_url(ctx); 58 | const getRequestOrigin = () => { 59 | // array [{ name: header_name,value: header_val }] -> {headerName1:"value1",headerName2 :"value2"} 60 | const originalRequestHeadersConvertedObject = Object.assign( 61 | {}, 62 | ...originalRequestHeaders.map((header) => ({ 63 | [header.name]: header.value, 64 | })) 65 | ); 66 | 67 | if (originalRequestHeadersConvertedObject["Origin"]) 68 | return originalRequestHeadersConvertedObject["Origin"]; 69 | if (originalRequestHeadersConvertedObject["origin"]) 70 | return originalRequestHeadersConvertedObject["origin"]; 71 | if (originalRequestHeadersConvertedObject["ORIGIN"]) 72 | return originalRequestHeadersConvertedObject["ORIGIN"]; 73 | return "*"; 74 | }; 75 | 76 | const { newHeaders: newResponseHeaders } = action; 77 | // Set Response headers 78 | 79 | // Clear all existing Response headers (to handle "remove header" case) 80 | for (var headerName in ctx.serverToProxyResponse.headers) { 81 | if (ctx.serverToProxyResponse.headers.hasOwnProperty(headerName)) { 82 | delete ctx.serverToProxyResponse.headers[headerName]; 83 | } 84 | } 85 | 86 | // Set new values 87 | newResponseHeaders.forEach( 88 | (pair) => (ctx.serverToProxyResponse.headers[pair.name] = pair.value) 89 | ); 90 | }; 91 | 92 | export default process_modify_header_action; 93 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/modify_request_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | 3 | import { 4 | CONSTANTS as GLOBAL_CONSTANTS, 5 | } from "@requestly/requestly-core"; 6 | import { get_request_url } from "../../helpers/proxy_ctx_helper"; 7 | import { build_action_processor_response } from "../utils"; 8 | import { executeUserFunction, getFunctionFromString } from "../../../../utils"; 9 | 10 | const process_modify_request_action = (action, ctx) => { 11 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_REQUEST_END]; 12 | 13 | if (!allowed_handlers.includes(ctx.currentHandler)) { 14 | return build_action_processor_response(action, false); 15 | } 16 | 17 | if ( 18 | action.requestType && 19 | action.requestType === GLOBAL_CONSTANTS.REQUEST_BODY_TYPES.CODE 20 | ) { 21 | modify_request_using_code(action, ctx); 22 | return build_action_processor_response(action, true); 23 | } else { 24 | modify_request(ctx, action.request); 25 | return build_action_processor_response(action, true); 26 | } 27 | }; 28 | 29 | const modify_request = (ctx, new_req) => { 30 | if (new_req) ctx.rq_request_body = new_req; 31 | }; 32 | 33 | const modify_request_using_code = async (action, ctx) => { 34 | let userFunction = null; 35 | try { 36 | userFunction = getFunctionFromString(action.request); 37 | } catch (error) { 38 | // User has provided an invalid function 39 | return modify_request( 40 | ctx, 41 | "Can't parse Requestly function. Please recheck. Error Code 7201. Actual Error: " + 42 | error.message 43 | ); 44 | } 45 | 46 | if (!userFunction || typeof userFunction !== "function") { 47 | // User has provided an invalid function 48 | return modify_request( 49 | ctx, 50 | "Can't parse Requestly function. Please recheck. Error Code 944." 51 | ); 52 | } 53 | 54 | // Everything good so far. Now try to execute user's function 55 | let finalRequest = null; 56 | 57 | try { 58 | const args = { 59 | method: ctx.clientToProxyRequest 60 | ? ctx.clientToProxyRequest.method 61 | ? ctx.clientToProxyRequest.method 62 | : null 63 | : null, 64 | request: ctx.rq_request_body, 65 | body: ctx.rq_request_body, 66 | url: get_request_url(ctx), 67 | requestHeaders: ctx.clientToProxyRequest.headers, 68 | }; 69 | 70 | try { 71 | args.bodyAsJson = JSON.parse(args.request); 72 | } catch { 73 | /*Do nothing -- could not parse body as JSON */ 74 | } 75 | 76 | finalRequest = await executeUserFunction(ctx, userFunction, args) 77 | 78 | if (finalRequest && typeof finalRequest === "string") { 79 | return modify_request(ctx, finalRequest); 80 | } else throw new Error("Returned value is not a string"); 81 | } catch (error) { 82 | // Function parsed but failed to execute 83 | return modify_request( 84 | ctx, 85 | "Can't execute Requestly function. Please recheck. Error Code 187. Actual Error: " + 86 | error.message 87 | ); 88 | } 89 | }; 90 | 91 | export default process_modify_request_action; -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/modify_response_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | 3 | import { 4 | CONSTANTS as GLOBAL_CONSTANTS, 5 | } from "@requestly/requestly-core"; 6 | import { getResponseContentTypeHeader, getResponseHeaders, get_request_url } from "../../helpers/proxy_ctx_helper"; 7 | import { build_action_processor_response, build_post_process_data, get_file_contents } from "../utils"; 8 | import { getContentType, parseJsonBody } from "../../helpers/http_helpers"; 9 | import { executeUserFunction, getFunctionFromString } from "../../../../utils"; 10 | import { RQ_INTERCEPTED_CONTENT_TYPES } from "../../constants"; 11 | 12 | const process_modify_response_action = async (action, ctx) => { 13 | const allowed_handlers = [ 14 | PROXY_HANDLER_TYPE.ON_REQUEST, 15 | PROXY_HANDLER_TYPE.ON_REQUEST_END, 16 | PROXY_HANDLER_TYPE.ON_RESPONSE_END, 17 | PROXY_HANDLER_TYPE.ON_ERROR 18 | ]; 19 | 20 | if (!allowed_handlers.includes(ctx.currentHandler)) { 21 | return build_action_processor_response(action, false); 22 | } 23 | 24 | if( 25 | ctx.currentHandler === PROXY_HANDLER_TYPE.ON_REQUEST || 26 | ctx.currentHandler === PROXY_HANDLER_TYPE.ON_REQUEST_END 27 | ) { 28 | if(action.serveWithoutRequest) { 29 | let contentType, finalBody; 30 | if(action.responseType === GLOBAL_CONSTANTS.RESPONSE_BODY_TYPES.LOCAL_FILE) { 31 | try { 32 | finalBody = get_file_contents(action.response); 33 | } catch (err) { 34 | console.log("Error reading file", err) 35 | return build_action_processor_response(action, false); 36 | } 37 | } else if (action.responseType === GLOBAL_CONSTANTS.RESPONSE_BODY_TYPES.STATIC) { 38 | finalBody = action.response 39 | } else { 40 | return build_action_processor_response(action, false); 41 | } 42 | 43 | try { 44 | const parsedResponse = JSON.parse(finalBody) 45 | if(action.responseType === GLOBAL_CONSTANTS.RESPONSE_BODY_TYPES.STATIC) { 46 | finalBody = parsedResponse; 47 | } 48 | contentType = "application/json"; 49 | } catch { 50 | contentType = "text/plain" 51 | } 52 | 53 | const status = action.statusCode || 200 54 | 55 | const finalHeaders = { 56 | "content-type": contentType, 57 | "access-control-allow-origin": "*", 58 | "access-control-allow-methods": "*", 59 | "access-control-allow-headers": "*", 60 | "access-control-allow-credentials": "true", 61 | } 62 | modify_response(ctx, finalBody, status) 63 | return build_action_processor_response( 64 | action, 65 | true, 66 | build_post_process_data( 67 | status, 68 | finalHeaders, 69 | finalBody, 70 | ) 71 | ) 72 | } 73 | 74 | return build_action_processor_response(action, false); 75 | } 76 | 77 | if ( 78 | action.responseType && 79 | action.responseType === GLOBAL_CONSTANTS.RESPONSE_BODY_TYPES.CODE 80 | ) { 81 | const contentTypeHeader = getResponseContentTypeHeader(ctx); 82 | const contentType = getContentType(contentTypeHeader); 83 | if (RQ_INTERCEPTED_CONTENT_TYPES.includes(contentType) || contentType == null) { 84 | await modify_response_using_code(action, ctx); 85 | delete_breaking_headers(ctx); 86 | return build_action_processor_response(action, true); 87 | } 88 | 89 | // Sentry not working 90 | // Sentry.captureException(new Error(`Content Type ${contentType} not supported for modification in programmatic mode`)); 91 | console.log(`Content Type ${contentType} not supported for modification in programmatic mode`); 92 | return build_action_processor_response(action, false); 93 | } else if ( 94 | action.responseType === GLOBAL_CONSTANTS.RESPONSE_BODY_TYPES.LOCAL_FILE 95 | ) { 96 | modify_response_using_local(action, ctx); 97 | delete_breaking_headers(ctx); 98 | return build_action_processor_response(action, true); 99 | } else { 100 | modify_response(ctx, action.response, action.statusCode); 101 | delete_breaking_headers(ctx); 102 | return build_action_processor_response(action, true); 103 | } 104 | }; 105 | 106 | const delete_breaking_headers = (ctx) => { 107 | delete getResponseHeaders(ctx)['content-length']; 108 | } 109 | 110 | const modify_response = (ctx, new_resp, status_code) => { 111 | ctx.rq_response_body = new_resp; 112 | ctx.rq_response_status_code = status_code; 113 | }; 114 | 115 | const modify_response_using_local = (action, ctx) => { 116 | let data; 117 | try { 118 | data = get_file_contents(action.response) 119 | modify_response(ctx, data, action.statusCode); 120 | } catch (err) { 121 | console.log("Error reading file", err) 122 | } 123 | }; 124 | 125 | const modify_response_using_code = async (action, ctx) => { 126 | let userFunction = null; 127 | try { 128 | userFunction = getFunctionFromString(action.response); 129 | } catch (error) { 130 | // User has provided an invalid function 131 | return modify_response( 132 | ctx, 133 | "Can't parse Requestly function. Please recheck. Error Code 7201. Actual Error: " + 134 | error.message 135 | ); 136 | } 137 | 138 | if (!userFunction || typeof userFunction !== "function") { 139 | // User has provided an invalid function 140 | return modify_response( 141 | ctx, 142 | "Can't parse Requestly function. Please recheck. Error Code 944." 143 | ); 144 | } 145 | 146 | // Everything good so far. Now try to execute user's function 147 | let finalResponse = null; 148 | 149 | try { 150 | const args = { 151 | method: ctx.clientToProxyRequest 152 | ? ctx.clientToProxyRequest.method 153 | ? ctx.clientToProxyRequest.method 154 | : null 155 | : null, 156 | response: ctx?.rq_parsed_response_body, 157 | url: get_request_url(ctx), 158 | responseType: ctx?.serverToProxyResponse?.headers?.["content-type"], 159 | requestHeaders: ctx.clientToProxyRequest.headers, 160 | requestData: parseJsonBody(ctx.rq?.final_request?.body) || null, 161 | statusCode: ctx.serverToProxyResponse.statusCode, 162 | }; 163 | 164 | try { 165 | args.responseJSON = JSON.parse(args.response); 166 | } catch { 167 | /*Do nothing -- could not parse body as JSON */ 168 | } 169 | 170 | finalResponse = await executeUserFunction(ctx, action.response, args) 171 | 172 | if (finalResponse && typeof finalResponse === "string") { 173 | return modify_response(ctx, finalResponse, action.statusCode); 174 | } else throw new Error("Returned value is not a string"); 175 | } catch (error) { 176 | // Function parsed but failed to execute 177 | return modify_response( 178 | ctx, 179 | "Can't execute Requestly function. Please recheck. Error Code 187. Actual Error: " + 180 | error.message 181 | ); 182 | } 183 | }; 184 | 185 | export default process_modify_response_action; 186 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/modify_user_agent_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | import { build_action_processor_response } from "../utils"; 3 | 4 | const process_modify_user_agent_action = (action, ctx) => { 5 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_REQUEST]; 6 | 7 | if (!allowed_handlers.includes(ctx.currentHandler)) { 8 | return build_action_processor_response(action, false); 9 | } 10 | 11 | if (ctx.currentHandler == PROXY_HANDLER_TYPE.ON_REQUEST) { 12 | modify_request_headers(action, ctx); 13 | return build_action_processor_response(action, true); 14 | } 15 | }; 16 | 17 | const modify_request_headers = (action, ctx) => { 18 | const newRequestHeaders = action.newRequestHeaders; 19 | for (var headerName in ctx.proxyToServerRequestOptions.headers) { 20 | if (ctx.proxyToServerRequestOptions.headers.hasOwnProperty(headerName)) { 21 | delete ctx.proxyToServerRequestOptions.headers[headerName]; 22 | } 23 | } 24 | 25 | // Set Request Headers 26 | newRequestHeaders.forEach( 27 | (pair) => (ctx.proxyToServerRequestOptions.headers[pair.name] = pair.value) 28 | ); 29 | }; 30 | 31 | export default process_modify_user_agent_action; 32 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/processors/redirect_processor.js: -------------------------------------------------------------------------------- 1 | import { PROXY_HANDLER_TYPE } from "../../../../lib/proxy"; 2 | import { 3 | get_request_url, 4 | is_request_preflight, 5 | } from "../../helpers/proxy_ctx_helper"; 6 | import modifiedRequestsPool from "../modified_requests_pool"; 7 | import handleMixedResponse from "../handle_mixed_response"; 8 | import { 9 | build_action_processor_response, 10 | build_post_process_data, 11 | } from "../utils"; 12 | 13 | // adding util to get origin header for handling cors 14 | const getRequestOrigin = (ctx) => { 15 | const originalRequestHeaders = ctx.rq.original_request.headers || {}; 16 | return ( 17 | originalRequestHeaders["Origin"] || 18 | originalRequestHeaders["origin"] || 19 | originalRequestHeaders["ORIGIN"] 20 | ); 21 | }; 22 | 23 | const process_redirect_action = async (action, ctx) => { 24 | const allowed_handlers = [PROXY_HANDLER_TYPE.ON_REQUEST]; 25 | 26 | if (!allowed_handlers.includes(ctx.currentHandler)) { 27 | return build_action_processor_response(action, false); 28 | } 29 | 30 | const current_url = get_request_url(ctx); 31 | const new_url = action.url; 32 | 33 | const request_url = current_url.replace(/www\./g, ""); 34 | 35 | // Skip if already redirected 36 | if (modifiedRequestsPool.isURLModified(request_url)) { 37 | // Do nothing 38 | return build_action_processor_response(action, false); 39 | } else { 40 | modifiedRequestsPool.add(new_url); 41 | } 42 | 43 | const { status: isMixedResponse, response_data } = await handleMixedResponse( 44 | ctx, 45 | new_url 46 | ); 47 | 48 | if (isMixedResponse) { 49 | return build_action_processor_response( 50 | action, 51 | true, 52 | build_post_process_data( 53 | response_data.status_code, 54 | response_data.headers, 55 | response_data.body 56 | ) 57 | ); 58 | } 59 | 60 | // If this is a pre-flight request, don't redirect it 61 | if (is_request_preflight(ctx)) return true; 62 | 63 | return build_action_processor_response( 64 | action, 65 | true, 66 | build_post_process_data( 67 | 307, 68 | { 69 | "Cache-Control": "no-cache", 70 | "Access-Control-Allow-Origin": getRequestOrigin(ctx) || "*", 71 | "Access-Control-Allow-Credentials": "true", 72 | Location: new_url, 73 | }, 74 | null 75 | ) 76 | ); 77 | }; 78 | 79 | export default process_redirect_action; 80 | -------------------------------------------------------------------------------- /src/components/proxy-middleware/rule_action_processor/utils.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export const build_action_processor_response = ( 4 | action, 5 | success = false, 6 | post_process_data = null 7 | ) => { 8 | let resp = { 9 | action: action, 10 | success: success, 11 | post_process_data: post_process_data, 12 | }; 13 | 14 | return resp; 15 | }; 16 | 17 | export const build_post_process_data = (status_code, headers, body) => { 18 | if (!status_code && !headers && !body) { 19 | return null; 20 | } 21 | 22 | let data = { 23 | status_code, 24 | headers, 25 | body, 26 | }; 27 | 28 | return data; 29 | }; 30 | 31 | export const get_success_actions_from_action_results = ( 32 | action_result_objs = [] 33 | ) => { 34 | if (!action_result_objs) { 35 | return []; 36 | } 37 | 38 | // BY default success is false 39 | const success_action_results_objs = action_result_objs.filter( 40 | (obj) => obj && obj && obj.success === true 41 | ); 42 | 43 | return success_action_results_objs.map((obj) => obj.action); 44 | }; 45 | 46 | export const get_file_contents = (file_path) => { 47 | return fs.readFileSync(file_path, "utf-8"); 48 | } -------------------------------------------------------------------------------- /src/components/ssl-proxying/ssl-proxying-manager.ts: -------------------------------------------------------------------------------- 1 | // import { ISource, SSLProxyingJsonObj } from "lib/storage/types/ssl-proxying"; 2 | // import BaseConfigFetcher from "renderer/lib/fetcher/base"; 3 | // TODO: @sahil fix this by adding type.d.ts file 4 | //@ts-ignore 5 | // import { RULE_PROCESSOR } from "@requestly/requestly-core"; 6 | 7 | 8 | // TODO: @sahil to add this 9 | class SSLProxyingManager { 10 | // configFetcher: BaseConfigFetcher; 11 | 12 | // constructor(configFetcher: BaseConfigFetcher) { 13 | // this.configFetcher = configFetcher; 14 | // } 15 | 16 | // isSslProxyingActive = (urlOrigin: string): boolean => { 17 | // const config: SSLProxyingJsonObj = this.configFetcher.getConfig(); 18 | 19 | // if (config.enabledAll === false) { 20 | // const inclusionListSuccess: boolean = this.checkStatusWithInclusionList( 21 | // config, 22 | // urlOrigin 23 | // ); 24 | // if (inclusionListSuccess) { 25 | // console.log(`${urlOrigin} inclusion List`); 26 | // return true; 27 | // } 28 | 29 | // return false; 30 | // } else { 31 | // const exclusionListSuccess: boolean = this.checkStatusWithExclusionList( 32 | // config, 33 | // urlOrigin 34 | // ); 35 | // if (exclusionListSuccess) { 36 | // console.log(`${urlOrigin} exclusion List`); 37 | // return false; 38 | // } 39 | 40 | // return true; 41 | // } 42 | // }; 43 | 44 | // checkStatusWithInclusionList = ( 45 | // config: SSLProxyingJsonObj, 46 | // urlOrigin: string 47 | // ): boolean => { 48 | // const inclusionListSources: ISource[] = Object.values( 49 | // config.inclusionList || {} 50 | // ); 51 | // return this.checkStatusWithList(inclusionListSources, urlOrigin); 52 | // }; 53 | 54 | // checkStatusWithExclusionList = ( 55 | // config: SSLProxyingJsonObj, 56 | // urlOrigin: string 57 | // ): boolean => { 58 | // const exclusionListSources: ISource[] = Object.values( 59 | // config.exclusionList || {} 60 | // ); 61 | // return this.checkStatusWithList(exclusionListSources, urlOrigin); 62 | // }; 63 | 64 | // checkStatusWithList = ( 65 | // sourceObjs: ISource[] = [], 66 | // urlOrigin: string = "" 67 | // ): boolean => { 68 | // return sourceObjs.some((sourceObj) => 69 | // this.checkStatusForSource(sourceObj, urlOrigin) 70 | // ); 71 | // }; 72 | 73 | // checkStatusForSource = ( 74 | // sourceObject: ISource, 75 | // urlOrigin: string 76 | // ): boolean => { 77 | // const result = RULE_PROCESSOR.RuleMatcher.matchUrlWithRuleSource( 78 | // sourceObject, 79 | // urlOrigin 80 | // ); 81 | // if (result === "") { 82 | // return true; 83 | // } 84 | // return false; 85 | // }; 86 | } 87 | 88 | export default SSLProxyingManager; 89 | -------------------------------------------------------------------------------- /src/constants/cert.ts: -------------------------------------------------------------------------------- 1 | export const certConfig = { 2 | CERT_NAME: "RQProxyCA", 3 | CERT_VALIDITY: { 4 | // Number of days - before the current date - Keep minimum 1 to avoid 12am date change issues 5 | START_BEFORE: 1, 6 | // Number of days - after the current date - Keep minimum 1 to avoid 12am date change issues 7 | // CAUTION : Increasing this count might affect current app users 8 | END_AFTER: 365, 9 | }, 10 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import RQProxy from "./rq-proxy"; 2 | import RQProxyProvider from "./rq-proxy-provider"; 3 | 4 | export { 5 | RQProxy, 6 | RQProxyProvider, 7 | }; 8 | 9 | export default RQProxy; 10 | -------------------------------------------------------------------------------- /src/lib/proxy/bin/mitm-proxy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | var yargs = require("yargs"); 6 | var debug = require("debug")("http-mitm-proxy:bin"); 7 | 8 | var args = yargs 9 | .alias("h", "help") 10 | .alias("h", "?") 11 | .options("port", { 12 | default: 80, 13 | describe: "HTTP Port.", 14 | }) 15 | .alias("p", "port") 16 | .options("host", { 17 | describe: "HTTP Listen Interface.", 18 | }).argv; 19 | 20 | if (args.help) { 21 | yargs.showHelp(); 22 | process.exit(-1); 23 | } 24 | 25 | var proxy = require("../lib/proxy")(); 26 | proxy.onError(function (ctx, err, errorKind) { 27 | debug(errorKind, err); 28 | }); 29 | proxy.listen(args, function (err) { 30 | if (err) { 31 | debug("Failed to start listening on port " + args.port, err); 32 | return process.exit(1); 33 | } 34 | debug("proxy listening on " + args.port); 35 | }); 36 | -------------------------------------------------------------------------------- /src/lib/proxy/custom/utils/checkInvalidHeaderChar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @sagar - Polyfill for _http_common.js 3 | */ 4 | 5 | const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 6 | /** 7 | * True if val contains an invalid field-vchar 8 | * field-value = *( field-content / obs-fold ) 9 | * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 10 | * field-vchar = VCHAR / obs-text 11 | */ 12 | function checkInvalidHeaderChar(val) { 13 | return headerCharRegex.test(val); 14 | } 15 | 16 | module.exports = checkInvalidHeaderChar; 17 | -------------------------------------------------------------------------------- /src/lib/proxy/index.js: -------------------------------------------------------------------------------- 1 | import DefaultExport, {Proxy, PROXY_HANDLER_TYPE, gunzip, decompress, wildcard} from "./lib/proxy" 2 | 3 | export default DefaultExport; 4 | 5 | export {Proxy, PROXY_HANDLER_TYPE, gunzip, decompress, wildcard} -------------------------------------------------------------------------------- /src/lib/proxy/lib/middleware/decompress.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var zlib = require("zlib"); 4 | 5 | const decompressMiddleware = { 6 | onResponse: function (ctx, callback) { 7 | if ( 8 | ctx.serverToProxyResponse.headers["content-encoding"] && 9 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 10 | "gzip" 11 | ) { 12 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 13 | ctx.addResponseFilter(zlib.createGunzip()); 14 | } else if ( 15 | ctx.serverToProxyResponse.headers["content-encoding"] && 16 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 17 | "br" 18 | ) { 19 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 20 | ctx.addResponseFilter(zlib.createBrotliDecompress()); 21 | } 22 | return callback(); 23 | }, 24 | onRequest: function (ctx, callback) { 25 | ctx.proxyToServerRequestOptions.headers["accept-encoding"] = "gzip"; 26 | return callback(); 27 | }, 28 | }; 29 | 30 | module.exports = decompressMiddleware; 31 | -------------------------------------------------------------------------------- /src/lib/proxy/lib/middleware/gunzip.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var zlib = require("zlib"); 4 | 5 | const gunzipMiddleware = { 6 | onResponse: function (ctx, callback) { 7 | if ( 8 | ctx.serverToProxyResponse.headers["content-encoding"] && 9 | ctx.serverToProxyResponse.headers["content-encoding"].toLowerCase() == 10 | "gzip" 11 | ) { 12 | delete ctx.serverToProxyResponse.headers["content-encoding"]; 13 | ctx.addResponseFilter(zlib.createGunzip()); 14 | } 15 | return callback(); 16 | }, 17 | onRequest: function (ctx, callback) { 18 | ctx.proxyToServerRequestOptions.headers["accept-encoding"] = "gzip"; 19 | return callback(); 20 | }, 21 | }; 22 | 23 | module.exports = gunzipMiddleware; 24 | -------------------------------------------------------------------------------- /src/lib/proxy/lib/middleware/wildcard.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * group1: subdomain 5 | * group2: domain.ext 6 | * exclude short domains (length < 4) to avoid catching double extensions (ex: net.au, co.uk, ...) 7 | */ 8 | const HOSTNAME_REGEX = /^(.+)(\.[^\.]{4,}(\.[^\.]{1,3})*\.[^\.]+)$/; 9 | 10 | const wildcaredMiddleware = { 11 | onCertificateRequired: function (hostname, callback) { 12 | var rootHost = hostname; 13 | if (HOSTNAME_REGEX.test(hostname)) { 14 | rootHost = hostname.replace(/^[^\.]+\./, ""); 15 | } 16 | return callback(null, { 17 | keyFile: this.sslCaDir + "/keys/_." + rootHost + ".key", 18 | certFile: this.sslCaDir + "/certs/_." + rootHost + ".pem", 19 | hosts: ["*." + rootHost, rootHost], 20 | }); 21 | }, 22 | }; 23 | 24 | module.exports = wildcaredMiddleware; 25 | -------------------------------------------------------------------------------- /src/lib/proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rq-mitm-proxy", 3 | "version": "1.0.0", 4 | "description": "MITM Proxy for Requestly", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "bin": { 11 | "http-mitm-proxy": "./bin/mitm-proxy.js" 12 | }, 13 | "author": "Sachin Jain", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /src/rq-proxy-provider.ts: -------------------------------------------------------------------------------- 1 | import RQProxy from "./rq-proxy"; 2 | import ILoggerService from "./components/interfaces/logger-service"; 3 | import IRulesDataSource from "./components/interfaces/rules-data-source"; 4 | import IInitialState from "./components/interfaces/state"; 5 | import { ProxyConfig } from "./types"; 6 | 7 | class RQProxyProvider { 8 | static rqProxyInstance:any = null; 9 | 10 | // TODO: rulesDataSource can be static here 11 | static createInstance = (proxyConfig: ProxyConfig, rulesDataSource: IRulesDataSource, loggerService: ILoggerService, initialGlobalState?: IInitialState) => { 12 | RQProxyProvider.rqProxyInstance = new RQProxy(proxyConfig, rulesDataSource, loggerService, initialGlobalState); 13 | } 14 | 15 | static getInstance = (): RQProxy => { 16 | if(!RQProxyProvider.rqProxyInstance) { 17 | console.error("Instance need to be created first"); 18 | } 19 | 20 | return RQProxyProvider.rqProxyInstance; 21 | } 22 | } 23 | 24 | export default RQProxyProvider; 25 | -------------------------------------------------------------------------------- /src/rq-proxy.ts: -------------------------------------------------------------------------------- 1 | import IRulesDataSource from "./components/interfaces/rules-data-source"; 2 | import Proxy from "./lib/proxy"; 3 | import { ProxyConfig } from "./types"; 4 | import RulesHelper from "./utils/helpers/rules-helper"; 5 | import ProxyMiddlewareManager from "./components/proxy-middleware"; 6 | import ILoggerService from "./components/interfaces/logger-service"; 7 | import IInitialState from "./components/interfaces/state"; 8 | import GlobalStateProvider, {State} from "./components/proxy-middleware/middlewares/state"; 9 | 10 | 11 | class RQProxy { 12 | proxy: any; 13 | proxyMiddlewareManager!: ProxyMiddlewareManager; 14 | 15 | rulesHelper: RulesHelper; 16 | loggerService: ILoggerService; 17 | globalState: State; 18 | 19 | constructor( 20 | proxyConfig: ProxyConfig, 21 | rulesDataSource: IRulesDataSource, 22 | loggerService: ILoggerService, 23 | initialGlobalState?: IInitialState 24 | ) { 25 | this.initProxy(proxyConfig); 26 | 27 | this.rulesHelper = new RulesHelper(rulesDataSource); 28 | this.loggerService = loggerService; 29 | this.globalState = GlobalStateProvider.initInstance(initialGlobalState?.sharedState ?? {}, initialGlobalState?.variables ?? {}); 30 | } 31 | 32 | initProxy = (proxyConfig: ProxyConfig) => { 33 | console.log("initProxy"); 34 | console.log(proxyConfig); 35 | // @ts-ignore 36 | this.proxy = new Proxy(); 37 | if(proxyConfig.onCARegenerated) { 38 | this.proxy.onCARegenerated(proxyConfig.onCARegenerated) 39 | } 40 | // console.log(this.proxy); 41 | this.proxy.listen( 42 | { 43 | port: proxyConfig.port, 44 | sslCaDir: proxyConfig.certPath, 45 | host: "0.0.0.0", 46 | }, 47 | (err: any) => { 48 | console.log("Proxy Listen"); 49 | if(err) { 50 | console.log(err); 51 | } else { 52 | console.log("Proxy Started"); 53 | this.proxyMiddlewareManager = new ProxyMiddlewareManager(this.proxy, proxyConfig, this.rulesHelper, this.loggerService, null); 54 | this.proxyMiddlewareManager.init(); 55 | } 56 | } 57 | ); 58 | 59 | 60 | // For Testing // 61 | this.proxy.onRequest(function(ctx, callback) { 62 | if (ctx.clientToProxyRequest.headers.host == 'example.com') { 63 | ctx.use(Proxy.gunzip); 64 | ctx.onResponseData(function(ctx, chunk, callback) { 65 | chunk = new Buffer("

Hello There

"); 66 | return callback(null, chunk); 67 | }); 68 | } 69 | return callback(); 70 | }); 71 | // 72 | } 73 | 74 | doSomething = () => { 75 | console.log("do something"); 76 | } 77 | } 78 | 79 | export default RQProxy; 80 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import ILoggerService from "./components/interfaces/logger-service"; 3 | import IRulesDataSource from "./components/interfaces/rules-data-source"; 4 | import RQProxyProvider from "./rq-proxy-provider"; 5 | import { ProxyConfig } from "./types"; 6 | 7 | 8 | console.log("start"); 9 | 10 | const proxyConfig: ProxyConfig = { 11 | port: 8281, 12 | // @ts-ignore 13 | certPath: "/Users/sahilgupta/Library/Application\\ Support/Electron/.requestly-certs-temp", 14 | // TODO: MOVE THIS IN RQ PROXY 15 | rootCertPath: "/Users/sahilgupta/Library/Application\\ Support/Electron/.requestly-certs-temp/certs/ca.pem" 16 | } 17 | 18 | 19 | class RulesDataSource implements IRulesDataSource { 20 | getRules = async (requestHeaders) => { 21 | return [ 22 | { 23 | "creationDate": 1648800254537, 24 | "description": "", 25 | "groupId": "", 26 | "id": "Headers_br050", 27 | "isSample": false, 28 | "name": "Test Header Rule", 29 | "objectType": "rule", 30 | "pairs": [ 31 | { 32 | "header": "abc", 33 | "value": "abc value", 34 | "type": "Add", 35 | "target": "Request", 36 | "source": { 37 | "filters": {}, 38 | "key": "Url", 39 | "operator": "Contains", 40 | "value": "example" 41 | }, 42 | "id": "lussg" 43 | }, 44 | { 45 | "header": "abc", 46 | "value": "bac value", 47 | "type": "Add", 48 | "target": "Response", 49 | "source": { 50 | "filters": {}, 51 | "key": "Url", 52 | "operator": "Contains", 53 | "value": "example" 54 | }, 55 | "id": "be1k6" 56 | } 57 | ], 58 | "ruleType": "Headers", 59 | "status": "Active", 60 | "createdBy": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 61 | "currentOwner": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 62 | "lastModifiedBy": "9cxfwgyBXKQxj9lU14GiTO5KTNY2", 63 | "modificationDate": 1648800283699, 64 | "lastModified": 1648800283699 65 | } 66 | ]; 67 | } 68 | 69 | getGroups = async (requestHeaders) => { 70 | return [ 71 | { 72 | id: "1", 73 | status: "Inactive" 74 | } 75 | ]; 76 | } 77 | } 78 | 79 | class LoggerService implements ILoggerService { 80 | addLog = (log: any, requestHeaders: {}) => { 81 | console.log(JSON.stringify(log, null, 4)); 82 | const headers = { 83 | "device_id": "test_device", 84 | "sdk_id": "7jcFc1g5j7ozfSXe7lc6", 85 | }; 86 | // TODO: Keeping this as Strong for now to avoid changes in UI 87 | log.finalHar = JSON.stringify(log.finalHar); 88 | 89 | axios({ 90 | method: "post", 91 | url : "http://localhost:5001/requestly-dev/us-central1/addSdkLog", // local 92 | headers, 93 | data: log 94 | }).then(() => { 95 | console.log("Successfully added log"); 96 | }).catch(error => { 97 | console.log(`Could not add Log`); 98 | }) 99 | }; 100 | } 101 | 102 | // RQProxyProvider.getInstance().doSomething(); 103 | RQProxyProvider.createInstance(proxyConfig, new RulesDataSource(), new LoggerService()); 104 | RQProxyProvider.getInstance().doSomething(); 105 | 106 | console.log("end"); -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface ProxyConfig { 2 | [x: string]: any; 3 | port: Number; 4 | certPath: String; 5 | rootCertPath: String; 6 | onCARegenerated?: Function; 7 | } 8 | 9 | export interface Rule { 10 | id: string; 11 | } 12 | 13 | export interface RuleGroup { 14 | id: String; 15 | status: String; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/circularQueue.ts: -------------------------------------------------------------------------------- 1 | const Queue: any = function (maxSize) { 2 | this.reset = function () { 3 | this.head = -1; 4 | this.queue = []; 5 | }; 6 | 7 | this.reset(); 8 | this.maxSize = maxSize || Queue.MAX_SIZE; 9 | 10 | this.increment = function (number) { 11 | return (number + 1) % this.maxSize; 12 | }; 13 | }; 14 | 15 | Queue.MAX_SIZE = Math.pow(2, 53) - 1; 16 | 17 | Queue.prototype.enQueue = function (record) { 18 | this.head = this.increment(this.head); 19 | this.queue[this.head] = record; 20 | }; 21 | 22 | /** 23 | * @param record Record to look for 24 | * @returns Number Position of record in the queue otherwise -1 25 | */ 26 | Queue.prototype.getElementIndex = function (record) { 27 | return this.queue.indexOf(record); 28 | }; 29 | 30 | Queue.prototype.print = function () { 31 | for (var i = 0; i <= this.head; i++) { 32 | console.log(this.queue[i]); 33 | } 34 | }; 35 | 36 | export default Queue; 37 | -------------------------------------------------------------------------------- /src/utils/helpers/rules-helper.ts: -------------------------------------------------------------------------------- 1 | import IRulesDataSource from "../../components/interfaces/rules-data-source"; 2 | 3 | class RulesHelper { 4 | rulesDataSource: IRulesDataSource; 5 | 6 | // TODO: this can be static class too 7 | constructor(rulesDataSource: IRulesDataSource) { 8 | this.rulesDataSource = rulesDataSource; 9 | } 10 | 11 | is_group_active = (group) => { 12 | if (!group) { 13 | return false; 14 | } 15 | 16 | return group.status == "Active"; 17 | }; 18 | 19 | is_rule_active = (rule, active_group_ids = []) => { 20 | if (!rule) { 21 | return false; 22 | } 23 | 24 | let status = false; 25 | status = rule.status == "Active"; 26 | 27 | // No group case || active group case 28 | if (rule.groupId == "" || active_group_ids.includes(rule.groupId)) { 29 | return status; 30 | } 31 | return false; 32 | }; 33 | 34 | get_groups = async (is_active = false, requestHeaders = {}) => { 35 | let groups = await this.rulesDataSource.getGroups(requestHeaders) || []; 36 | 37 | if (is_active === true) { 38 | groups = groups.filter((group) => this.is_group_active(group)); 39 | } 40 | 41 | return groups; 42 | }; 43 | 44 | get_group_ids = async (is_active = false, requestHeaders = {}) => { 45 | const groups = await this.get_groups(is_active, requestHeaders); 46 | return groups.map((group) => group.id); 47 | }; 48 | 49 | get_rules = async (is_active = false, requestHeaders = {}) => { 50 | let rules = await this.rulesDataSource.getRules(requestHeaders) || []; 51 | let active_group_ids = await this.get_group_ids(true, requestHeaders) || []; 52 | 53 | if (is_active === true) { 54 | rules = rules.filter((rule) => this.is_rule_active(rule, active_group_ids)); 55 | } 56 | 57 | // Sorting Rules By id asc 58 | rules = rules.sort((rule1, rule2) => { 59 | return ("" + rule1.id).localeCompare(rule2.id); 60 | }); 61 | 62 | return rules; 63 | }; 64 | } 65 | 66 | export default RulesHelper; 67 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { types } from "util"; 2 | import ConsoleCapture from "capture-console-logs"; 3 | import GlobalStateProvider from "../components/proxy-middleware/middlewares/state"; 4 | 5 | // Only used for verification now. For execution, we regenerate the function in executeUserFunction with the sharedState 6 | export const getFunctionFromString = function (functionStringEscaped) { 7 | return new Function(`return ${functionStringEscaped}`)(); 8 | }; 9 | 10 | 11 | /* Expects that the functionString has already been validated to be representing a proper function */ 12 | export async function executeUserFunction(ctx, functionString: string, args) { 13 | 14 | const generateFunctionWithSharedState = function (functionStringEscaped) { 15 | 16 | const SHARED_STATE_VAR_NAME = "$sharedState"; 17 | 18 | const sharedState = GlobalStateProvider.getInstance().getSharedStateCopy(); 19 | 20 | return new Function(`${SHARED_STATE_VAR_NAME}`, `return { func: ${functionStringEscaped}, updatedSharedState: ${SHARED_STATE_VAR_NAME}}`)(sharedState); 21 | }; 22 | 23 | const {func: generatedFunction, updatedSharedState} = generateFunctionWithSharedState(functionString); 24 | 25 | const consoleCapture = new ConsoleCapture() 26 | consoleCapture.start(true) 27 | 28 | let finalResponse = generatedFunction(args); 29 | 30 | if (types.isPromise(finalResponse)) { 31 | finalResponse = await finalResponse; 32 | } 33 | 34 | consoleCapture.stop() 35 | const consoleLogs = consoleCapture.getCaptures() 36 | 37 | ctx.rq.consoleLogs.push(...consoleLogs) 38 | 39 | /** 40 | * If we use GlobalState.getSharedStateRef instead of GlobalState.getSharedStateCopy 41 | * then this update is completely unnecessary. 42 | * Because then the function gets a reference to the global states, 43 | * and any changes made inside the userFunction will directly be reflected there. 44 | * 45 | * But we are using it here to make the data flow obvious as we read this code. 46 | */ 47 | GlobalStateProvider.getInstance().setSharedState(updatedSharedState); 48 | 49 | if (typeof finalResponse === "object") { 50 | finalResponse = JSON.stringify(finalResponse); 51 | } 52 | 53 | return finalResponse; 54 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "outDir": "./dist/", 6 | "moduleResolution": "node", 7 | "verbatimModuleSyntax": false, 8 | "esModuleInterop": true, 9 | "allowJs": true, 10 | "declaration": true, 11 | }, 12 | "include": [ 13 | "src" 14 | ] 15 | } --------------------------------------------------------------------------------