Autosave: 20260201-173753

This commit is contained in:
Flatlogic Bot 2026-02-01 17:37:53 +00:00
parent 0215d4d95e
commit afb69ac6af
3 changed files with 299 additions and 61 deletions

View File

@ -15,6 +15,7 @@ const authRoutes = require('./routes/auth');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');
const proxyRoutes = require('./routes/proxy');
const pexelsRoutes = require('./routes/pexels');
const openaiRoutes = require('./routes/openai');
@ -88,6 +89,7 @@ app.use(cors({origin: true}));
require('./auth/auth');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
@ -96,6 +98,7 @@ app.enable('trust proxy');
app.use('/api/users', passport.authenticate('jwt', {session: false}), usersRoutes);
app.use('/api/proxy', proxyRoutes);
app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoutes);

132
backend/src/routes/proxy.js Normal file
View File

@ -0,0 +1,132 @@
const express = require('express');
const router = express.Router();
const axios = require('axios');
const Helpers = require('../helpers');
router.all('/', Helpers.wrapAsync(async (req, res) => {
const targetUrl = req.query.url;
if (!targetUrl) return res.status(400).send('URL is required');
try {
const method = req.method;
const headers = { ...req.headers };
// Remove host and other headers that might cause issues
delete headers.host;
delete headers.origin;
delete headers.referer;
// Set a common User-Agent
headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36';
const response = await axios({
url: targetUrl,
method: method,
headers: headers,
data: req.body,
params: req.params,
responseType: 'arraybuffer',
validateStatus: () => true,
maxRedirects: 10,
timeout: 15000,
});
const contentType = response.headers['content-type'] || '';
const finalUrl = response.request.res.responseUrl || targetUrl;
// Copy headers but strip security and encoding ones
Object.keys(response.headers).forEach(key => {
const lowerKey = key.toLowerCase();
if (![
'x-frame-options',
'content-security-policy',
'content-security-policy-report-only',
'content-length',
'transfer-encoding',
'connection',
'strict-transport-security',
'x-content-type-options'
].includes(lowerKey)) {
res.setHeader(key, response.headers[key]);
}
});
// Ensure we don't have caching issues during dev
res.setHeader('Cache-Control', 'no-store');
let data = response.data;
if (contentType.includes('text/html')) {
let html = data.toString('utf-8');
const parsedUrl = new URL(finalUrl);
const origin = parsedUrl.origin;
const baseUrl = origin + parsedUrl.pathname;
// Inject <base> and frame-busting neutralization
const headInjection = `
<base href="${origin}/">
<script>
// Neutralize frame-busting
(function() {
var originalOpen = window.open;
window.open = function(url, name, specs) {
if (!url) return originalOpen(url, name, specs);
window.location.href = "/api/proxy?url=" + encodeURIComponent(new URL(url, location.href).href);
return null;
};
Object.defineProperty(window, 'top', { get: function() { return window.self; } });
Object.defineProperty(window, 'parent', { get: function() { return window.self; } });
Object.defineProperty(document, 'referrer', { get: function() { return ''; } });
// Override location.replace and assign to stay in proxy
var originalReplace = window.location.replace;
window.location.replace = function(url) {
window.location.href = "/api/proxy?url=" + encodeURIComponent(new URL(url, location.href).href);
};
})();
</script>
`;
if (html.includes('<head>')) {
html = html.replace('<head>', `<head>${headInjection}`);
} else if (html.includes('<html>')) {
html = html.replace('<html>', `<html><head>${headInjection}</head>`);
} else {
html = headInjection + html;
}
// More aggressive link and resource rewriting
// This helps with relative paths that <base> might miss in some cases
// especially in scripts or attributes we don't handle well
// Rewrite links to stay in proxy
html = html.replace(/(href|src|action)="\s*((?!#|javascript|mailto|tel|data:|https?:\/\/).*?)\s*"/gi, (match, attr, p1) => {
try {
const absoluteUrl = new URL(p1, finalUrl).href;
if (attr.toLowerCase() === 'href' || attr.toLowerCase() === 'action') {
return `${attr}="/api/proxy?url=${encodeURIComponent(absoluteUrl)}"`;
}
// For src, we might not want to proxy everything (images/scripts) as it adds load
// but if the site blocks cross-origin, we must.
// Let's proxy scripts and iframes.
if (match.toLowerCase().includes('<script') || match.toLowerCase().includes('<iframe')) {
return `${attr}="/api/proxy?url=${encodeURIComponent(absoluteUrl)}"`;
}
return match;
} catch(e) {
return match;
}
});
res.status(response.status).send(html);
} else {
res.status(response.status).send(data);
}
} catch (error) {
res.status(500).send(`Proxy error: ${error.message}`);
}
}));
module.exports = router;

View File

@ -149,7 +149,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
React.useEffect(() => {
let keyPresses: number[] = [];
const handleKeyDown = (e: KeyboardEvent) => {
// Ignore if user is typing in an input or textarea
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
@ -163,104 +162,167 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
if (keyPresses.length >= 5) {
const win = window.open('about:blank', '_blank');
if (win) {
const proxyUrl = window.location.origin + '/api/proxy?url=';
const content = `
<html>
<head>
<title>Search</title>
<title>Zen Browser</title>
<style>
body { background: white; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; color: #202124; }
.header { background: #f1f3f4; border-bottom: 1px solid #dfe1e5; display: flex; flex-direction: column; }
.tab-bar { display: flex; padding: 8px 8px 0; gap: 4px; overflow-x: auto; scrollbar-width: none; }
.tab-bar::-webkit-scrollbar { display: none; }
.tab { padding: 6px 12px; background: #e8eaed; border-radius: 8px 8px 0 0; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 12px; min-width: 100px; max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border: 1px solid transparent; border-bottom: none; transition: background 0.2s; }
.tab:hover { background: #dadce0; }
.tab.active { background: white; font-weight: 500; border-color: #dfe1e5; border-bottom-color: white; margin-bottom: -1px; z-index: 1; }
.tab .close { font-size: 14px; color: #5f6368; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; border-radius: 50%; }
.tab .close:hover { background: #dadce0; color: #202124; }
.search-bar { padding: 8px 16px; display: flex; gap: 12px; align-items: center; background: white; }
body { background: #0f0f0f; margin: 0; font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; display: flex; flex-direction: column; height: 100vh; overflow: hidden; color: #f1f1f1; }
.header { background: #212121; border-bottom: 1px solid #333; display: flex; flex-direction: column; }
.tab-bar { display: flex; padding: 8px 8px 0; gap: 4px; overflow-x: auto; background: #121212; align-items: flex-end; }
.tab { padding: 8px 16px; background: #2a2a2a; border-radius: 10px 10px 0 0; cursor: pointer; display: flex; align-items: center; gap: 12px; font-size: 13px; min-width: 140px; max-width: 220px; white-space: nowrap; overflow: hidden; border: 1px solid transparent; border-bottom: none; transition: all 0.2s; color: #999; position: relative; }
.tab:hover { background: #333; color: #ccc; }
.tab.active { background: #212121; color: #fff; font-weight: 500; border-color: #333; border-bottom-color: #212121; margin-bottom: -1px; z-index: 1; }
.tab .close { font-size: 14px; color: #666; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: 0.2s; }
.tab .close:hover { background: #444; color: #fff; }
.search-bar { padding: 12px 20px; display: flex; gap: 16px; align-items: center; background: #212121; }
.nav-btns { display: flex; gap: 8px; }
.nav-btn { cursor: pointer; color: #5f6368; padding: 4px; border-radius: 50%; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; }
.nav-btn:hover { background: #f1f3f4; }
form { flex-grow: 1; display: flex; max-width: 800px; margin: 0 auto; width: 100%; }
.input-wrapper { position: relative; width: 100%; display: flex; align-items: center; }
input { padding: 8px 16px; font-size: 14px; border: 1px solid #dfe1e5; border-radius: 20px; width: 100%; outline: none; background: #f1f3f4; transition: background 0.2s, box-shadow 0.2s; }
input:focus { background: white; border-color: transparent; box-shadow: 0 1px 6px rgba(32,33,36,0.28); }
.new-tab-btn { cursor: pointer; min-width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #5f6368; align-self: center; margin-bottom: 4px; }
.new-tab-btn:hover { background: #dadce0; }
.iframe-container { flex-grow: 1; position: relative; background: #f8f9fa; }
iframe { border: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; visibility: hidden; opacity: 0; transition: opacity 0.2s; }
iframe.active { visibility: visible; opacity: 1; }
.nav-btn { cursor: pointer; color: #aaa; padding: 6px; border-radius: 50%; display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; transition: 0.2s; }
.nav-btn:hover { background: #333; color: #fff; }
.nav-btn.disabled { color: #444; cursor: default; }
form { flex-grow: 1; position: relative; }
input { padding: 10px 20px; font-size: 14px; border: 1px solid #333; border-radius: 24px; width: 100%; outline: none; background: #121212; color: #fff; transition: all 0.3s; box-sizing: border-box; }
input:focus { background: #181818; border-color: #555; box-shadow: 0 0 0 2px rgba(255,255,255,0.05); }
.new-tab-btn { cursor: pointer; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; color: #aaa; margin-left: 8px; margin-bottom: 4px; transition: 0.2s; }
.new-tab-btn:hover { background: #333; color: #fff; }
.iframe-container { flex-grow: 1; position: relative; background: #fff; }
iframe { border: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; display: none; background: #fff; }
iframe.active { display: block; }
.loader { position: absolute; bottom: 0; left: 0; height: 2px; background: #3ea6ff; transition: width 0.3s; width: 0; z-index: 100; }
</style>
</head>
<body>
<div class=\'header\'>
<div class=\'tab-bar\' id=\'tabBar\'>
<div class=\'new-tab-btn\' onclick=\'addTab()\' title=\'New Tab\'>+</div>
<div class='header'>
<div class='tab-bar' id='tabBar'>
<div class='new-tab-btn' onclick='addTab()' title='New Tab'>+</div>
</div>
<div class=\'search-bar\'>
<div class=\'nav-btns\'>
<div class=\'nav-btn\' onclick=\'goBack()\' title=\'Back\'></div>
<div class=\'nav-btn\' onclick=\'reloadTab()\' title=\'Reload\'></div>
<div class='search-bar'>
<div class='nav-btns'>
<div class='nav-btn' id='backBtn' onclick='goBack()' title='Back'></div>
<div class='nav-btn' onclick='reloadTab()' title='Reload'></div>
<div class='nav-btn' onclick='goHome()' title='Home'>🏠</div>
</div>
<form onsubmit=\'handleSearch(event)\'>
<div class=\'input-wrapper\'>
<input id=\'searchInput\' placeholder=\'Search or enter URL...\' autofocus autocomplete=\'off\' />
</div>
<form onsubmit='handleSearch(event); return false;'>
<input id='searchInput' placeholder='Search or type a URL' autofocus autocomplete='off' />
</form>
</div>
</div>
<div class=\'iframe-container\' id=\'iframeContainer\'></div>
<div class='iframe-container' id='iframeContainer'>
<div id='loader' class='loader'></div>
</div>
<script>
var tabs = [];
var activeTabId = null;
const PROXY_BASE = "${proxyUrl}";
function getRawUrl(proxiedUrl) {
if (proxiedUrl.startsWith(PROXY_BASE)) {
return decodeURIComponent(proxiedUrl.substring(PROXY_BASE.length));
}
return proxiedUrl;
}
function addTab(url, title) {
url = url || 'about:blank';
url = url || 'https://www.google.com/search?igu=1';
title = title || 'New Tab';
var id = 'tab-' + Math.random().toString(36).substr(2, 9);
tabs.push({ id: id, url: url, title: title });
var tabObj = {
id: id,
url: url,
title: title,
history: [url],
historyIndex: 0
};
tabs.push(tabObj);
var tabEl = document.createElement('div');
tabEl.className = 'tab';
tabEl.id = 'el-' + id;
tabEl.onclick = function() { switchTab(id); };
tabEl.innerHTML = '<span class="title" style="flex-grow:1; overflow:hidden; text-overflow:ellipsis;">' + title + '</span><span class="close" onclick="event.stopPropagation(); removeTab(\'\' + id + \'
')">×</span>';
var titleSpan = document.createElement('span');
titleSpan.className = 'title';
titleSpan.style.flexGrow = '1';
titleSpan.style.overflow = 'hidden';
titleSpan.style.textOverflow = 'ellipsis';
titleSpan.innerText = title;
tabEl.appendChild(titleSpan);
var closeBtn = document.createElement('span');
closeBtn.className = 'close';
closeBtn.innerText = '×';
closeBtn.onclick = function(e) {
e.stopPropagation();
removeTab(id);
};
tabEl.appendChild(closeBtn);
var tabBar = document.getElementById('tabBar');
tabBar.insertBefore(tabEl, tabBar.querySelector('.new-tab-btn'));
var iframe = document.createElement('iframe');
iframe.id = 'frame-' + id;
iframe.src = url;
iframe.setAttribute('name', id);
document.getElementById('iframeContainer').appendChild(iframe);
iframe.src = PROXY_BASE + encodeURIComponent(url);
iframe.setAttribute('allow', 'autoplay; encrypted-media; camera; microphone; fullscreen; clipboard-write');
iframe.onload = function() {
document.getElementById('loader').style.width = '0';
try {
// Try to update title and URL if same-origin (proxy)
var frameUrl = iframe.contentWindow.location.href;
var rawUrl = getRawUrl(frameUrl);
if (rawUrl && rawUrl !== 'about:blank') {
tabObj.url = rawUrl;
if (activeTabId === id) {
document.getElementById('searchInput').value = rawUrl;
}
// Attempt to get page title
if (iframe.contentDocument && iframe.contentDocument.title) {
titleSpan.innerText = iframe.contentDocument.title;
}
}
} catch(e) {}
};
document.getElementById('iframeContainer').appendChild(iframe);
switchTab(id);
return id;
}
function switchTab(id) {
activeTabId = id;
var allTabs = document.querySelectorAll('.tab');
for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
var allFrames = document.querySelectorAll('iframe');
for (var i = 0; i < allFrames.length; i++) allFrames[i].classList.remove('active');
document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
document.querySelectorAll('iframe').forEach(el => el.classList.remove('active'));
var el = document.getElementById('el-' + id);
var frame = document.getElementById('frame-' + id);
if (el) el.classList.add('active');
if (frame) frame.classList.add('active');
var currentTab = tabs.find(function(t) { return t.id === id; });
if (currentTab) {
document.getElementById('searchInput').value = (currentTab.url === 'about:blank') ? '' : currentTab.url;
var tab = tabs.find(t => t.id === id);
if (tab) {
document.getElementById('searchInput').value = (tab.url.includes('google.com/search?igu=1')) ? '' : tab.url;
updateNavButtons();
}
}
function updateNavButtons() {
var tab = tabs.find(t => t.id === activeTabId);
var backBtn = document.getElementById('backBtn');
if (tab && tab.historyIndex > 0) {
backBtn.classList.remove('disabled');
} else {
backBtn.classList.add('disabled');
}
}
function removeTab(id) {
var index = tabs.findIndex(function(t) { return t.id === id; });
tabs = tabs.filter(function(t) { return t.id !== id; });
var index = tabs.findIndex(t => t.id === id);
if (index === -1) return;
tabs = tabs.filter(t => t.id !== id);
document.getElementById('el-' + id).remove();
document.getElementById('frame-' + id).remove();
@ -275,8 +337,14 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}
function goBack() {
if (activeTabId) {
document.getElementById('frame-' + activeTabId).contentWindow.history.back();
var tab = tabs.find(t => t.id === activeTabId);
if (tab && tab.historyIndex > 0) {
tab.historyIndex--;
var prevUrl = tab.history[tab.historyIndex];
tab.url = prevUrl;
document.getElementById('frame-' + activeTabId).src = PROXY_BASE + encodeURIComponent(prevUrl);
document.getElementById('searchInput').value = prevUrl;
updateNavButtons();
}
}
@ -284,13 +352,28 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
if (activeTabId) {
var frame = document.getElementById('frame-' + activeTabId);
frame.src = frame.src;
document.getElementById('loader').style.width = '50%';
}
}
function goHome() {
var url = 'https://www.google.com/search?igu=1';
if (activeTabId) {
var tab = tabs.find(t => t.id === activeTabId);
tab.url = url;
document.getElementById('frame-' + activeTabId).src = PROXY_BASE + encodeURIComponent(url);
document.getElementById('searchInput').value = '';
document.getElementById('el-' + activeTabId).querySelector('.title').innerText = 'Google';
}
}
function handleSearch(e) {
e.preventDefault();
var query = document.getElementById('searchInput').value.trim();
if (!query) return;
if (e) e.preventDefault();
var input = document.getElementById('searchInput');
var query = input.value.trim();
if (!query) return false;
document.getElementById('loader').style.width = '30%';
var url;
if (query.match(/^(https?:\\/\\/)/i)) {
@ -298,19 +381,38 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
} else if (query.includes('.') && !query.includes(' ')) {
url = 'https://' + query;
} else {
url = "https://www.google.com/search?q=" + encodeURIComponent(query) + "&igu=1";
url = "https://www.google.com/search?igu=1&q=" + encodeURIComponent(query);
}
// YouTube specific optimization: prefer mobile version for better proxy compatibility
if (url.includes('youtube.com') && !url.includes('m.youtube.com')) {
url = url.replace('www.youtube.com', 'm.youtube.com');
}
if (!activeTabId) {
addTab(url, query);
} else {
var tab = tabs.find(t => t.id === activeTabId);
var iframe = document.getElementById('frame-' + activeTabId);
iframe.src = url;
// Push to history
tab.history = tab.history.slice(0, tab.historyIndex + 1);
tab.history.push(url);
tab.historyIndex++;
tab.url = url;
iframe.src = PROXY_BASE + encodeURIComponent(url);
var tabEl = document.getElementById('el-' + activeTabId);
tabEl.querySelector('.title').innerText = query;
var tab = tabs.find(function(t) { return t.id === activeTabId; });
if (tab) tab.url = url;
if (tabEl) {
var displayTitle = query;
if (url.includes('google.com/search')) displayTitle = query;
else if (url.length > 20) displayTitle = url.substring(0, 17) + '...';
tabEl.querySelector('.title').innerText = displayTitle;
}
updateNavButtons();
}
input.blur();
return false;
}
addTab();
@ -318,6 +420,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
</body>
</html>
`;
win.document.open();
win.document.write(content);
win.document.close();
}