Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

206 lines
6.0 KiB
TypeScript

import { serverFetch as fetch } from '@rocket.chat/server-fetch';
import { HTTP } from 'meteor/http';
import { URL } from 'meteor/url';
import { truncate } from '../../../lib/utils/stringUtils';
// Code extracted from https://github.com/meteor/meteor/blob/master/packages/deprecated/http
// Modified to:
// - Respect proxy envvars such as HTTP_PROXY and NO_PROXY
// - Respect HTTP_DEFAULT_TIMEOUT envvar or use 20s when it is not set
const envTimeout = parseInt(process.env.HTTP_DEFAULT_TIMEOUT || '', 10);
const defaultTimeout = !isNaN(envTimeout) ? envTimeout : 20000;
type HttpCallOptions = {
content?: string | URLSearchParams;
data?: Record<string, any>;
query?: string;
params?: Record<string, string>;
auth?: string;
headers?: Record<string, string>;
timeout?: number;
followRedirects?: boolean;
referrer?: string;
integrity?: string;
};
// eslint-disable-next-line @typescript-eslint/naming-convention
interface HTTPResponse {
statusCode?: number;
headers?: { [id: string]: string };
content?: string;
data?: any;
ok?: boolean;
redirected?: boolean;
}
type callbackFn = (error: Error | undefined, result?: HTTPResponse) => void;
// Fill in `response.data` if the content-type is JSON.
function populateData(response: Record<string, any>): void {
// Read Content-Type header, up to a ';' if there is one.
// A typical header might be "application/json; charset=utf-8"
// or just "application/json".
const contentType = (response.headers['content-type'] || ';').split(';')[0];
// Only try to parse data as JSON if server sets correct content type.
if (['application/json', 'text/javascript', 'application/javascript', 'application/x-javascript'].indexOf(contentType) >= 0) {
try {
response.data = JSON.parse(response.content);
} catch (err) {
response.data = null;
}
} else {
response.data = null;
}
}
function makeErrorByStatus(statusCode: number, content: string): Error {
let message = `failed [${statusCode}]`;
if (content) {
message += `${truncate(content.replace(/\n/g, ' '), 500)}`;
}
return new Error(message);
}
function _call(httpMethod: string, url: string, options: HttpCallOptions, callback: callbackFn): void {
const method = (httpMethod || '').toUpperCase();
if (!/^https?:\/\//.test(url)) {
throw new Error('url must be absolute and start with http:// or https://');
}
const headers: Record<string, string> = {};
let { content } = options;
if (!('timeout' in options)) {
options.timeout = defaultTimeout;
}
if (options.data) {
content = JSON.stringify(options.data);
headers['Content-Type'] = 'application/json';
}
let paramsForUrl;
let paramsForBody;
if (content || method === 'GET' || method === 'HEAD') {
paramsForUrl = options.params;
} else {
paramsForBody = options.params;
}
const newUrl = URL._constructUrl(url, options.query, paramsForUrl);
if (options.auth) {
if (options.auth.indexOf(':') < 0) {
throw new Error('auth option should be of the form "username:password"');
}
const base64 = Buffer.from(options.auth, 'ascii').toString('base64');
headers.Authorization = `Basic ${base64}`;
}
if (paramsForBody) {
const data = new URLSearchParams();
Object.entries(paramsForBody).forEach(([key, value]) => {
data.append(key, value);
});
content = data;
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
const { headers: receivedHeaders } = options;
if (receivedHeaders) {
Object.keys(receivedHeaders).forEach((key) => {
headers[key] = receivedHeaders[key];
});
}
// wrap callback to add a 'response' property on an error, in case
// we have both (http 4xx/5xx error, which has a response payload)
const wrappedCallback = ((cb: callbackFn): { (error: Error | undefined, response?: HTTPResponse): void } => {
let called = false;
return (error: Error | undefined, response?: HTTPResponse): void => {
if (!called) {
called = true;
if (error && response) {
(error as any).response = response;
}
cb(error, response);
}
};
})(callback);
// is false if false, otherwise always true
const followRedirects = options.followRedirects === false ? 'manual' : 'follow';
const requestOptions = {
method,
jar: false,
timeout: options.timeout,
body: content,
redirect: followRedirects,
referrer: options.referrer,
integrity: options.integrity,
headers,
ignoreSsrfValidation: true,
} as const;
fetch(newUrl, requestOptions)
.then(async (res) => {
const content = await res.text();
const response: HTTPResponse = {};
response.statusCode = res.status;
response.content = `${content}`;
// fetch headers don't allow simple read using bracket notation
// so we iterate their entries and assign them to a new Object
response.headers = {};
for (const entry of (res.headers as any).entries()) {
const [key, val] = entry;
response.headers[key] = val;
}
response.ok = res.ok;
response.redirected = res.redirected;
populateData(response);
if (response.statusCode >= 400) {
const error = makeErrorByStatus(response.statusCode, response.content);
wrappedCallback(error, response);
} else {
wrappedCallback(undefined, response);
}
})
.catch((err) => wrappedCallback(err));
}
function httpCallAsync(httpMethod: string, url: string, options: HttpCallOptions, callback: callbackFn): void;
function httpCallAsync(httpMethod: string, url: string, callback: callbackFn): void;
function httpCallAsync(httpMethod: string, url: string, optionsOrCallback: HttpCallOptions | callbackFn = {}, callback?: callbackFn): void {
// If the options argument was omitted, adjust the arguments:
if (!callback && typeof optionsOrCallback === 'function') {
return _call(httpMethod, url, {}, optionsOrCallback);
}
return _call(httpMethod, url, optionsOrCallback as HttpCallOptions, callback as callbackFn);
}
export const httpCall = async (httpMethod: string, url: string, options: HttpCallOptions) => {
return new Promise((resolve, reject) => {
httpCallAsync.bind(HTTP)(httpMethod, url, options, (error, result) => {
if (error) {
return reject(error);
}
resolve(result);
});
});
};