Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/components/proxy-middleware/middlewares/rules_middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {
} from "../helpers/proxy_ctx_helper";
import RuleProcessorHelper from "../helpers/rule_processor_helper";
import RuleActionProcessor from "../rule_action_processor";
import { PROXY_HANDLER_TYPE } from "../../../lib/proxy";
import { RULE_ACTION } from "../constants";
import process_modify_header_action from "../rule_action_processor/processors/modify_header_processor";
import * as Sentry from "@sentry/browser";

class RulesMiddleware {
constructor(is_active, ctx, rulesHelper) {
Expand Down Expand Up @@ -79,6 +83,28 @@ class RulesMiddleware {
return rule_actions;
};

_applyResponseHeaderRulesForRedirect = (ctx, destUrl) => {
const prevHandler = ctx.currentHandler;
try {
this._init_response_data(ctx); // Reads fetched headers from ctx.serverToProxyResponse
ctx.currentHandler = PROXY_HANDLER_TYPE.ON_RESPONSE;

const modifyHeaderActions = this._process_rules(true)
.filter((action) => action?.action === RULE_ACTION.MODIFY_HEADERS);
// Process actions and collect their result objects
const actionResults = modifyHeaderActions.map((action) =>
process_modify_header_action(action, ctx)
);

// Register results so the UI logs both rules as applied
this._update_action_result_objs(actionResults);
} catch (e) {
Sentry.captureException(e);
} finally {
ctx.currentHandler = prevHandler;
}
};

_update_action_result_objs = (action_result_objs = []) => {
if (action_result_objs) {
this.action_result_objs =
Expand All @@ -96,6 +122,9 @@ class RulesMiddleware {

this.on_request_actions = this._process_rules();

ctx.rq.applyResponseHeaderRulesForRedirect = (destUrl) =>
this._applyResponseHeaderRulesForRedirect(ctx, destUrl);

const { action_result_objs, continue_request } =
await this.rule_action_processor.process_actions(
this.on_request_actions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,36 @@
const axios = require("axios");
const parser = require("ua-parser-js");
import fs from "fs";
import * as Sentry from "@sentry/browser";
const mime = require('mime-types');

const handleMixedResponse = async (ctx, destinationUrl) => {
// Handling mixed response from safari
let user_agent_str = null;
user_agent_str = ctx?.clientToProxyRequest?.headers["user-agent"];
const user_agent = parser(user_agent_str)?.browser?.name;
const LOCAL_DOMAINS = ["localhost", "127.0.0.1"];

if (ctx.isSSL && destinationUrl.includes("http:")) {
if (
user_agent === "Safari" ||
!LOCAL_DOMAINS.some((domain) => destinationUrl.includes(domain))
) {
try {
const resp = await axios.get(destinationUrl, {
headers: {
"Cache-Control": "no-cache",
},
});

return {
status: true,
response_data: {
headers: { "Cache-Control": "no-cache" },
status_code: 200,
body: resp.data,
},
};
} catch (e) {
Sentry.captureException(e);
return {
status: true,
response_data: {
headers: { "Cache-Control": "no-cache" },
status_code: 502,
body: e.response ? e.response.data : null,
},
};
}
try {
const resp = await axios.get(destinationUrl, {
responseType: "arraybuffer",
decompress: false, // 2. CRITICAL: Prevent Axios from unwrapping gzip
validateStatus: () => true,
headers: { "Cache-Control": "no-cache" },
});
return {
status: true,
response_data: {
status_code: resp.status,
headers: resp.headers, // real upstream headers (content-encoding already stripped by axios)
body: resp.data, // Buffer
},
};
} catch (e) {
// Only transport failures reach here now (DNS, refused, timeout, redirect loop).
Sentry.captureException(e);
return {
status: true,
response_data: {
headers: e.response ? e.response.headers : { "Cache-Control": "no-cache" },
status_code: e.response ? e.response.status : 502,
body: e.response ? e.response.data : null,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
}
}

Expand Down
13 changes: 10 additions & 3 deletions src/components/proxy-middleware/rule_action_processor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ class RuleActionProcessor {

const status_code = action_result.post_process_data.status_code || 200;
const headers = action_result.post_process_data.headers || {};
let body = action_result.post_process_data.body || null;
let body = action_result.post_process_data.body;
if (body === undefined) body = null;

// console.log("Log", ctx.rq.original_request);
if(typeof(body) !== 'string') {
if (body instanceof ArrayBuffer) {
body = Buffer.from(body);
} else if (body && body.buffer instanceof ArrayBuffer && !Buffer.isBuffer(body)) {
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
}

if (body !== null && !Buffer.isBuffer(body) && typeof body !== "string") {
body = JSON.stringify(body);
}

Expand Down Expand Up @@ -75,7 +82,7 @@ class RuleActionProcessor {

switch (rule_action.action) {
case RULE_ACTION.REDIRECT:
action_result = process_redirect_action(rule_action, ctx);
action_result = await process_redirect_action(rule_action, ctx);
break;
case RULE_ACTION.MODIFY_HEADERS:
action_result = process_modify_header_action(rule_action, ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import handleMixedResponse from "../handle_mixed_response";
import {
build_action_processor_response,
build_post_process_data,
stripHopByHopHeaders,
} from "../utils";

// adding util to get origin header for handling cors
Expand Down Expand Up @@ -46,14 +47,37 @@ const process_redirect_action = async (action, ctx) => {
);

if (isMixedResponse) {
// Feed the fetched response into the response context, then run the existing
// response Modify Headers processor against the destination URL.
ctx.serverToProxyResponse = {
statusCode: response_data.status_code,
headers: { ...(response_data.headers || {}) },
};
if (typeof ctx.rq.applyResponseHeaderRulesForRedirect === "function") {
ctx.rq.applyResponseHeaderRulesForRedirect(new_url);
}
const headers = stripHopByHopHeaders(ctx.serverToProxyResponse.headers);

// Recalculate content-length for the flattened buffer
let body = response_data.body;
if (body !== null && body !== undefined) {
if (Buffer.isBuffer(body)) {
headers['content-length'] = body.length;
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
headers['content-length'] = body.byteLength;
} else if (typeof body === 'string') {
headers['content-length'] = Buffer.byteLength(body);
} else {
headers['content-length'] = Buffer.byteLength(JSON.stringify(body));
}
Comment thread
dinex-dev marked this conversation as resolved.
} else {
headers["content-length"] = 0;
}

return build_action_processor_response(
action,
true,
build_post_process_data(
response_data.status_code,
response_data.headers,
response_data.body
)
build_post_process_data(response_data.status_code, headers, body)
);
}

Expand Down
13 changes: 12 additions & 1 deletion src/components/proxy-middleware/rule_action_processor/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,15 @@ export const getHost = (ctx) => {

export const get_file_contents = (file_path) => {
return fs.readFileSync(file_path, "utf-8");
}
};

export const stripHopByHopHeaders = (headers) => {
const out = { ...(headers || {}) };
for (const key of Object.keys(out)) {
const k = key.toLowerCase();
if (k === "content-length" || k === "transfer-encoding") {
delete out[key];
}
}
return out;
};
Loading