aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/ApiManagers/FireflyManager.ts224
-rw-r--r--src/server/ApiManagers/UploadManager.ts7
-rw-r--r--src/server/DashSession/DashSessionAgent.ts4
-rw-r--r--src/server/DashUploadUtils.ts21
-rw-r--r--src/server/SharedMediaTypes.ts3
-rw-r--r--src/server/server_Initialization.ts220
6 files changed, 235 insertions, 244 deletions
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e75ede9df..e934e635b 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -6,6 +6,7 @@ import * as path from 'path';
import { DashUserModel } from '../authentication/DashUserModel';
import { DashUploadUtils } from '../DashUploadUtils';
import { _error, _invalid, _success, Method } from '../RouteManager';
+import { Upload } from '../SharedMediaTypes';
import { Directory, filesDirectory } from '../SocketData';
import ApiManager, { Registration } from './ApiManager';
@@ -70,54 +71,44 @@ export default class FireflyManager extends ApiManager {
);
uploadImageToDropbox = (fileUrl: string, user: DashUserModel | undefined, dbx = new Dropbox({ accessToken: user?.dropboxToken || '' })) =>
- new Promise<string | Error>((res, rej) =>
+ new Promise<string>((resolve, reject) => {
fs.readFile(path.join(filesDirectory, `${Directory.images}/${path.basename(fileUrl)}`), undefined, (err, contents) => {
if (err) {
- console.log('Error: ', err);
- rej();
- } else {
- dbx.filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
- .then(response => {
- dbx.filesGetTemporaryLink({ path: response.result.path_display ?? '' })
- .then(link => res(link.result.link))
- .catch(e => res(new Error(e.toString())));
- })
- .catch(e => {
+ return reject(new Error('Error reading file:' + err.message));
+ }
+
+ const uploadToDropbox = (dropboxClient: Dropbox) =>
+ dropboxClient
+ .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
+ .then(response =>
+ dropboxClient
+ .filesGetTemporaryLink({ path: response.result.path_display ?? '' })
+ .then(link => resolve(link.result.link))
+ .catch(linkErr => reject(new Error('Failed to get temporary link: ' + linkErr.message)))
+ )
+ .catch(uploadErr => {
if (user?.dropboxRefresh) {
- console.log('*********** try refresh dropbox for: ' + user.email + ' ***********');
- this.refreshDropboxToken(user).then(token => {
- if (!token) {
- console.log('Dropbox error: cannot refresh token');
- res(new Error(e.toString()));
- } else {
- const dbxNew = new Dropbox({ accessToken: user.dropboxToken || '' });
- dbxNew
- .filesUpload({ path: `/Apps/browndash/${path.basename(fileUrl)}`, contents })
- .then(response => {
- dbxNew
- .filesGetTemporaryLink({ path: response.result.path_display ?? '' })
- .then(link => res(link.result.link))
- .catch(linkErr => res(new Error(linkErr.toString())));
- })
- .catch(uploadErr => {
- console.log('Dropbox error:', uploadErr);
- res(new Error(uploadErr.toString()));
- });
- }
- });
+ console.log('Attempting to refresh Dropbox token for user:', user.email);
+ this.refreshDropboxToken(user)
+ .then(token => {
+ if (!token) return reject(new Error('Failed to refresh Dropbox token.' + user.email));
+
+ const dbxNew = new Dropbox({ accessToken: token });
+ uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message)));
+ })
+ .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message)));
} else {
- console.log('Dropbox error:', e);
- res(new Error(e.toString()));
+ reject(new Error('Dropbox error: ' + uploadErr.message));
}
});
- }
- })
- );
+
+ uploadToDropbox(dbx);
+ });
+ });
generateImage = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, seed?: number) => {
let body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}} }`;
if (seed) {
- console.log('RECEIVED SEED', seed);
body = `{ "prompt": "${prompt}", "size": { "width": ${width}, "height": ${height}}, "seeds": [${seed}]}`;
}
const fetched = this.getBearerToken().then(response =>
@@ -290,44 +281,116 @@ export default class FireflyManager extends ApiManager {
register({
method: Method.POST,
subscription: '/queryFireflyImageFromStructure',
+ secureHandler: ({ req, res }) =>
+ new Promise<void>(resolver =>
+ (req.body.styleUrl
+ ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel)
+ : Promise.resolve(undefined)
+ )
+ .then(styleUrl =>
+ this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel)
+ .then(dropboxStructureUrl =>
+ ({ styleUrl, structureUrl: dropboxStructureUrl })
+ )
+
+ )
+ .then(uploads =>
+ this.generateImageFromStructure(
+ req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl
+ ).then(images =>
+ Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url))))
+ .then(dashImages =>
+ (dashImages.every(img => img instanceof Error))
+ ? _invalid(res, dashImages[0]!.message)
+ : _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error))))
+ )
+ )
+ )
+ .catch(e => {
+ _invalid(res, e.message);
+ resolver();
+ })
+ ), // prettier-ignore
+ });
+
+ register({
+ method: Method.POST,
+ subscription: '/outpaintImage',
secureHandler: ({ req, res }) =>
- new Promise<void>(resolver => {
- (req.body.styleUrl ? this.uploadImageToDropbox(req.body.styleUrl, req.user as DashUserModel) : Promise.resolve(undefined))
- .then(styleUrl => {
- if (styleUrl instanceof Error) {
- _invalid(res, styleUrl.message);
- throw new Error('Error uploading images to dropbox');
- }
- this.uploadImageToDropbox(req.body.structureUrl, req.user as DashUserModel)
- .then(dropboxStructureUrl => {
- if (dropboxStructureUrl instanceof Error) {
- _invalid(res, dropboxStructureUrl.message);
- throw new Error('Error uploading images to dropbox');
- }
- return { styleUrl, structureUrl: dropboxStructureUrl };
- })
- .then(uploads =>
- this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl)
- .then(images => {
- Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url))))
- .then(dashImages => {
- if (dashImages.every(img => img instanceof Error)) _invalid(res, dashImages[0]!.message);
- else _success(res, JSON.stringify(dashImages.filter(img => !(img instanceof Error))));
- })
- .then(resolver);
- })
- .catch(e => {
- _invalid(res, e.message);
- resolver();
+ new Promise<void>(resolver =>
+ this.uploadImageToDropbox(req.body.imageUrl, req.user as DashUserModel)
+ .then(uploadUrl =>
+ this.getBearerToken()
+ .then(tokenResponse => tokenResponse?.json())
+ .then((tokenData: { access_token: string }) =>
+ fetch('https://firefly-api.adobe.io/v3/images/expand', {
+ method: 'POST',
+ headers: [
+ ['Content-Type', 'application/json'],
+ ['Accept', 'application/json'],
+ ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
+ ['Authorization', `Bearer ${tokenData.access_token}`],
+ ],
+ body: JSON.stringify({
+ image: {
+ source: { url: uploadUrl },
+ },
+ size: {
+ width: Math.round(req.body.newDimensions.width),
+ height: Math.round(req.body.newDimensions.height),
+ },
+ prompt: req.body.prompt ?? '',
+ numVariations: 1,
+ placement: {
+ inset: {
+ left: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2),
+ top: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2),
+ right: 0, // Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2),
+ bottom: 0, // Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2),
+ },
+ alignment: {
+ horizontal: req.body.halignment || 'center',
+ vertical: req.body.valignment || 'center',
+ },
+ },
+ }),
+ })
+ .then(expandResp => expandResp?.json())
+ .then(expandData => {
+ if (expandData.error_code || !expandData.outputs?.[0]?.image?.url) {
+ console.error('Firefly validation error:', expandData);
+ _error(res, expandData.message ?? 'Failed to generate image');
+ } else {
+ return DashUploadUtils.UploadImage(expandData.outputs[0].image.url)
+ .then((info: Upload.ImageInformation | Error) => {
+ if (info instanceof Error) {
+ _invalid(res, info.message);
+ } else {
+ _success(res, { url: info.accessPaths.agnostic.client });
+ }
+ })
+ .catch(uploadErr => {
+ console.error('DashUpload Error:', uploadErr);
+ _error(res, 'Failed to upload generated image.');
+ });
+ }
})
- );
- })
- .catch(() => {
- /* do nothing */
+ )
+ )
+ .catch(e => {
+ _invalid(res, e.message);
resolver();
- });
- }),
+ })
+ ),
});
+
+ /* register({
+ method: Method.POST
+ subscription: '/queryFireflyOutpaint',
+ secureHandler: ({req, res}) =>
+ this.outpaintImage()
+ })*/
+
register({
method: Method.POST,
subscription: '/queryFireflyImage',
@@ -354,23 +417,6 @@ export default class FireflyManager extends ApiManager {
)
),
});
- register({
- method: Method.POST,
- subscription: '/expandImage',
- secureHandler: ({ req, res }) =>
- this.uploadImageToDropbox(req.body.file, req.user as DashUserModel).then(uploadUrl =>
- uploadUrl instanceof Error
- ? _invalid(res, uploadUrl.message)
- : this.expandImage(uploadUrl, req.body.prompt).then(text => {
- if (text.error_code) _error(res, text.message);
- else
- DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
- if (info instanceof Error) _invalid(res, info.message);
- else _success(res, info);
- });
- })
- ),
- });
// construct this url and send user to it. It will allow them to authorize their dropbox account and will send the resulting token to our endpoint /refreshDropbox
// https://www.dropbox.com/oauth2/authorize?client_id=DROPBOX_CLIENT_ID&response_type=code&token_access_type=offline&redirect_uri=http://localhost:1050/refreshDropbox
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index c9d5df547..7c55e4a42 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -126,11 +126,8 @@ export default class UploadManager extends ApiManager {
secureHandler: async ({ req, res }) => {
const { sources } = req.body;
if (Array.isArray(sources)) {
- const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
- res.send(results);
- return;
- }
- res.send();
+ res.send(await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))));
+ } else res.send();
},
});
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 891316b80..8688ec049 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -213,9 +213,9 @@ export class DashSessionAgent extends AppliedSessionAgent {
// indicate success or failure
mainLog(`${error === null ? green('successfully dispatched') : red('failed to dispatch')} ${zipName} to ${cyan(to)}`);
error && mainLog(red(error.message));
- } catch (error: any) {
+ } catch (error: unknown) {
mainLog(red('unable to dispatch zipped backup...'));
- mainLog(red(error.message));
+ mainLog(red((error as { message: string }).message));
}
}
}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index a2747257a..f76371b0d 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -221,9 +221,7 @@ export namespace DashUploadUtils {
const parseExifData = async (source: string) => {
const image = await request.get(source, { encoding: null });
const { /* data, */ error } = await new Promise<{ data: ExifData; error: string | undefined }>(resolve => {
- new ExifImage({ image }, (exifError, data) => {
- resolve({ data, error: exifError?.message });
- });
+ new ExifImage({ image }, (exifError, data) => resolve({ data, error: exifError?.message }));
});
return error ? { data: undefined, error } : { data: await exifr.parse(image), error };
};
@@ -295,7 +293,7 @@ export namespace DashUploadUtils {
try {
// Compute the native width and height ofthe image with an npm module
- const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl);
+ const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl).catch(() => ({ width: 0, height: 0 }));
// Bundle up the information into an object
return {
source,
@@ -307,7 +305,6 @@ export namespace DashUploadUtils {
...results,
};
} catch (e: unknown) {
- console.log(e);
return new Error(e ? e.toString?.() : 'unkown error');
}
};
@@ -450,14 +447,12 @@ export namespace DashUploadUtils {
* 3) the size of the image, in bytes (4432130)
* 4) the content type of the image, i.e. image/(jpeg | png | ...)
*/
- export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> => {
- const result = await InspectImage(source);
- if (result instanceof Error) {
- return { name: result.name, message: result.message };
- }
- const outputFile = filename || result.filename || '';
- return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false);
- };
+ export const UploadImage = (source: string, filename?: string, prefix: string = ''): Promise<Upload.ImageInformation | Error> =>
+ InspectImage(source).then(async result =>
+ result instanceof Error
+ ? ({ name: result.name, message: result.message } as Error) //
+ : UploadInspectedImage(result, filename || result.filename || '', prefix, isLocal().exec(source) || source.startsWith('data:') ? true : false)
+ );
type md5 = 'md5';
type falsetype = false;
diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts
index 9aa4b120f..43a9ce963 100644
--- a/src/server/SharedMediaTypes.ts
+++ b/src/server/SharedMediaTypes.ts
@@ -19,6 +19,9 @@ export enum AudioAnnoState {
}
export namespace Upload {
+ export function isTextInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
+ return 'rawText' in uploadResponse;
+ }
export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation {
return 'nativeWidth' in uploadResponse;
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index a56ab5d18..514e2ce1e 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,19 +1,15 @@
import * as bodyParser from 'body-parser';
-import * as brotli from 'brotli';
import { blue, yellow } from 'colors';
import * as flash from 'connect-flash';
import * as MongoStoreConnect from 'connect-mongo';
-import * as cors from 'cors';
import * as express from 'express';
import * as expressFlash from 'express-flash';
import * as session from 'express-session';
import { createServer } from 'https';
import * as passport from 'passport';
-import * as request from 'request';
import * as webpack from 'webpack';
import * as wdm from 'webpack-dev-middleware';
import * as whm from 'webpack-hot-middleware';
-import * as zlib from 'zlib';
import * as config from '../../webpack.config';
import { logPort } from './ActionUtilities';
import RouteManager from './RouteManager';
@@ -23,6 +19,8 @@ import { SSL } from './apis/google/CredentialsLoader';
import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
import { Database } from './database';
import { WebSocket } from './websocket';
+import axios from 'axios';
+import { JSDOM } from 'jsdom';
/* RouteSetter is a wrapper around the server that prevents the server
from being exposed. */
@@ -84,142 +82,96 @@ function buildWithMiddleware(server: express.Express) {
return server;
}
-function registerEmbeddedBrowseRelativePathHandler(server: express.Express) {
- server.use('*', (req, res) => {
- // res.setHeader('Access-Control-Allow-Origin', '*');
- // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
- // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
- const relativeUrl = req.originalUrl;
- if (!res.headersSent && req.headers.referer?.includes('corsProxy')) {
- if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home
- // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
- try {
- const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
- const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
- const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart)
- const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
- const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl;
- res.redirect(redirectUrl);
- } catch (e) {
- console.log('Error embed: ', e);
+function registerCorsProxy(server: express.Express) {
+ // .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
+ server.use('/corsproxy', async (req, res) => {
+ try {
+ // Extract URL from either query param or path
+ let targetUrl: string;
+
+ if (req.query.url) {
+ // Case 1: URL passed as query parameter (/corsproxy?url=...)
+ targetUrl = req.query.url as string;
+ } else {
+ // Case 2: URL passed as path (/corsproxy/http://example.com)
+ const path = req.originalUrl.replace(/^\/corsproxy\/?/, '');
+ targetUrl = decodeURIComponent(path);
+
+ // Add protocol if missing (assuming https as default)
+ if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
+ targetUrl = `https://${targetUrl}`;
+ }
+ }
+
+ if (!targetUrl) {
+ res.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666">
+ <title>Error</title>
+ <div align="center"><h1>Failed to load: ${targetUrl} </h1></div>
+ <p>URL is required</p>
+ </body></html>`);
+ // res.status(400).json({ error: 'URL is required' });
+ return;
}
- } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) {
- // detect search query and use default search engine
- res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl));
- } else {
- res.status(404).json({ error: 'no such file or endpoint: try /home /logout /login' });
- }
- });
-}
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function proxyServe(req: any, requrl: string, response: any) {
- // eslint-disable-next-line @typescript-eslint/no-require-imports
- const htmlBodyMemoryStream = new (require('memorystream'))();
- let wasinBrFormat = false;
- const sendModifiedBody = () => {
- const header = response.headers['content-encoding'];
- const refToCors = (match: string, tag: string, sym: string, href: string) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`;
- // const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`;
- if (header) {
+ // Validate URL format
try {
- const bodyStream = htmlBodyMemoryStream.read();
- if (bodyStream) {
- const htmlInputText = wasinBrFormat ? Buffer.from(brotli.decompress(bodyStream)) : header.includes('gzip') ? zlib.gunzipSync(bodyStream) : bodyStream;
- const htmlText = htmlInputText
- .toString('utf8')
- .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>')
- .replace(/(src|href)=(['"])(https?[^\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.."
- // .replace(/= *"\/([^"]*)"/g, relpathToCors)
- .replace(/data-srcset="[^"]*"/g, '')
- .replace(/srcset="[^"]*"/g, '')
- .replace(/target="_blank"/g, '');
- response.send(header?.includes('gzip') ? zlib.gzipSync(htmlText) : htmlText);
- } else {
- req.pipe(request(requrl))
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => console.log('requrl ', e))
- .pipe(response)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => console.log('response pipe error', e));
- console.log('EMPTY body:' + req.url);
- }
+ new URL(targetUrl);
} catch (e) {
- console.log('ERROR?: ', e);
- }
- } else {
- req.pipe(htmlBodyMemoryStream)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => console.log('html body memorystream error', e))
- .pipe(response)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => console.log('html body memory stream response error', e));
- }
- };
- const retrieveHTTPBody = () => {
- // req.headers.cookie = '';
- req.pipe(request(requrl))
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => {
- console.log(`CORS url error: ${requrl}`, e);
- response.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666">
+ res.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666">
<title>Error</title>
- <div align="center"><h1>Failed to load: ${requrl} </h1></div>
+ <div align="center"><h1>Failed to load: ${targetUrl} </h1></div>
<p>${e}</p>
</body></html>`);
- })
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('response', (res: any) => {
- res.headers;
- const headers = Object.keys(res.headers);
- const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
- headers.forEach(headerName => {
- const header = res.headers[headerName];
- if (Array.isArray(header)) {
- res.headers[headerName] = header.filter(h => !headerCharRegex.test(h));
- } else if (headerCharRegex.test(header || '')) {
- delete res.headers[headerName];
- } else res.headers[headerName] = header;
- if (headerName === 'content-encoding') {
- wasinBrFormat = res.headers[headerName] === 'br';
- res.headers[headerName] = 'gzip';
- }
+ //res.status(400).json({ error: 'Invalid URL format' });
+ return;
+ }
+
+ const response = await axios.get(targetUrl as string, {
+ headers: { 'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0' },
+ responseType: 'text',
+ });
+
+ const baseUrl = new URL(targetUrl as string);
+
+ if (response.headers['content-type']?.includes('text/html')) {
+ const dom = new JSDOM(response.data);
+ const document = dom.window.document;
+
+ // Process all elements with href/src
+ const elements = document.querySelectorAll('[href],[src]');
+ elements.forEach(elem => {
+ const attrs = [];
+ if (elem.hasAttribute('href')) attrs.push('href');
+ if (elem.hasAttribute('src')) attrs.push('src');
+
+ attrs.forEach(attr => {
+ const originalUrl = elem.getAttribute(attr);
+ if (!originalUrl || originalUrl.startsWith('http://') || originalUrl.startsWith('https://') || originalUrl.startsWith('data:') || /^[a-z]+:/.test(originalUrl)) {
+ return;
+ }
+
+ const resolvedUrl = new URL(originalUrl, baseUrl).toString();
+ elem.setAttribute(attr, resolvedUrl);
+ });
});
- res.headers['x-permitted-cross-domain-policies'] = 'all';
- res.headers['x-frame-options'] = '';
- res.headers['content-security-policy'] = '';
- response.headers = response._headers = res.headers;
- })
- .on('end', sendModifiedBody)
- .pipe(htmlBodyMemoryStream)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- .on('error', (e: any) => console.log('http body pipe error', e));
- };
- retrieveHTTPBody();
-}
-function registerCorsProxy(server: express.Express) {
- server.use('/corsProxy', async (req, res) => {
- res.setHeader('Access-Control-Allow-Origin', '*');
- res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
- res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
- const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : '';
- let requrlraw = decodeURIComponent(req.url.substring(1));
- const qsplit = requrlraw.split('?q=');
- const newqsplit = requrlraw.split('&q=');
- if (qsplit.length > 1 && newqsplit.length > 1) {
- const lastq = newqsplit[newqsplit.length - 1];
- requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1];
- }
- const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw;
- // cors weirdness here...
- // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative,
- // then we redirect again to the cors referer and just add the relative path.
- if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) {
- res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl);
- } else {
- proxyServe(req, requrl, res);
+ // Handle base tag
+ const baseTags = document.querySelectorAll('base');
+ baseTags.forEach(tag => tag.remove());
+
+ const newBase = document.createElement('base');
+ newBase.setAttribute('href', `${baseUrl}/`);
+ document.head.insertBefore(newBase, document.head.firstChild);
+
+ response.data = dom.serialize();
+ }
+
+ res.set({
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': response.headers['content-type'],
+ }).send(response.data);
+ } catch (error: unknown) {
+ res.status(500).json({ error: 'Proxy error', details: (error as { message: string }).message });
}
});
}
@@ -255,13 +207,11 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
app.use(whm(compiler));
app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
+ // app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc)
- registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
+ registerCorsProxy(app); // this adds a /corsproxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies
isRelease && !SSL.Loaded && SSL.exit();
routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc)
- registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content
isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort));
const server = isRelease ? createServer(SSL.Credentials, app) : app;
await new Promise<void>(resolve => {