enzostvs HF Staff commited on
Commit
118f72b
·
1 Parent(s): 65fb0ee

remove proxy route

Browse files
app/api/proxy/[...path]/route.ts DELETED
@@ -1,547 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function GET(
4
- req: NextRequest,
5
- { params }: { params: Promise<{ path: string[] }> }
6
- ) {
7
- const pathParams = await params;
8
- const pathSegments = pathParams.path || [];
9
-
10
- // Get the target URL from query parameter
11
- const targetUrl = req.nextUrl.searchParams.get("url");
12
-
13
- if (!targetUrl) {
14
- return NextResponse.json(
15
- { error: "Missing 'url' query parameter" },
16
- { status: 400 }
17
- );
18
- }
19
-
20
- try {
21
- // Parse the target URL
22
- const url = new URL(targetUrl);
23
-
24
- // Only allow static.hf.space domains for security
25
- if (!url.hostname.endsWith(".static.hf.space")) {
26
- return NextResponse.json(
27
- { error: "Only static.hf.space domains are allowed" },
28
- { status: 403 }
29
- );
30
- }
31
-
32
- // Construct the actual target URL
33
- // If path segments exist, use them; otherwise use "/" for root
34
- const targetPath = pathSegments.length > 0
35
- ? "/" + pathSegments.join("/")
36
- : "/";
37
-
38
- // Merge query parameters from the request URL with the target URL's search params
39
- const requestSearchParams = req.nextUrl.searchParams;
40
- const targetSearchParams = new URLSearchParams(url.search);
41
-
42
- // Copy all query params except 'url' to the target URL
43
- requestSearchParams.forEach((value, key) => {
44
- if (key !== "url") {
45
- targetSearchParams.set(key, value);
46
- }
47
- });
48
-
49
- const searchString = targetSearchParams.toString();
50
- const fullTargetUrl = `${url.protocol}//${url.hostname}${targetPath}${searchString ? `?${searchString}` : ""}`;
51
-
52
- // Fetch the content from the target URL
53
- const response = await fetch(fullTargetUrl, {
54
- headers: {
55
- "User-Agent": "Mozilla/5.0 (compatible; DeepSite/1.0)",
56
- },
57
- redirect: "follow", // Follow redirects automatically
58
- });
59
-
60
- if (!response.ok) {
61
- return NextResponse.json(
62
- { error: `Failed to fetch: ${response.statusText}` },
63
- { status: response.status }
64
- );
65
- }
66
-
67
- let contentType = response.headers.get("content-type") || "";
68
- const content = await response.text();
69
-
70
- // Detect content type from file extension if not properly set
71
- if (!contentType || contentType === "text/plain" || contentType === "application/octet-stream") {
72
- const fileExtension = pathSegments.length > 0
73
- ? pathSegments[pathSegments.length - 1].split(".").pop()?.toLowerCase()
74
- : "";
75
-
76
- if (fileExtension === "js") {
77
- contentType = "application/javascript";
78
- } else if (fileExtension === "css") {
79
- contentType = "text/css";
80
- } else if (fileExtension === "html" || fileExtension === "htm") {
81
- contentType = "text/html";
82
- } else if (fileExtension === "json") {
83
- contentType = "application/json";
84
- }
85
- }
86
-
87
- // Get the base proxy URL
88
- // Extract the base path from the request URL
89
- const requestUrl = new URL(req.url);
90
- // Find where "/api/proxy" starts in the pathname
91
- const proxyIndex = requestUrl.pathname.indexOf("/api/proxy");
92
- const basePath = proxyIndex > 0 ? requestUrl.pathname.substring(0, proxyIndex) : "";
93
- const proxyBaseUrl = `${basePath}/api/proxy`;
94
- const targetUrlParam = `?url=${encodeURIComponent(`https://${url.hostname}`)}`;
95
-
96
- // Rewrite URLs in HTML content
97
- let processedContent = content;
98
-
99
- if (contentType.includes("text/html")) {
100
- // Rewrite relative URLs and URLs pointing to the same domain
101
- processedContent = rewriteUrls(
102
- content,
103
- url.hostname,
104
- proxyBaseUrl,
105
- targetUrlParam,
106
- pathSegments
107
- );
108
-
109
- // Inject script to intercept JavaScript-based navigation
110
- processedContent = injectNavigationInterceptor(
111
- processedContent,
112
- url.hostname,
113
- proxyBaseUrl,
114
- targetUrlParam
115
- );
116
- } else if (contentType.includes("text/css")) {
117
- // Rewrite URLs in CSS
118
- processedContent = rewriteCssUrls(
119
- content,
120
- url.hostname,
121
- proxyBaseUrl,
122
- targetUrlParam
123
- );
124
- } else if (contentType.includes("application/javascript") || contentType.includes("text/javascript")) {
125
- // For component files and most JavaScript, don't rewrite URLs
126
- // Only rewrite if needed (for fetch calls, etc.)
127
- // Most component JavaScript files don't need URL rewriting
128
- // Only rewrite if the file contains fetch calls with relative URLs
129
- if (content.includes("fetch(") && content.match(/fetch\(["'][^"']*[^\/]\./)) {
130
- processedContent = rewriteJsUrls(
131
- content,
132
- url.hostname,
133
- proxyBaseUrl,
134
- targetUrlParam
135
- );
136
- } else {
137
- // Don't modify component JavaScript files - they should work as-is
138
- processedContent = content;
139
- }
140
- }
141
-
142
- // Ensure JavaScript files have charset specified
143
- let finalContentType = contentType;
144
- if (contentType.includes("javascript") && !contentType.includes("charset")) {
145
- finalContentType = contentType.includes(";")
146
- ? contentType + "; charset=utf-8"
147
- : contentType + "; charset=utf-8";
148
- }
149
-
150
- // Return the processed content with appropriate headers
151
- return new NextResponse(processedContent, {
152
- status: 200,
153
- headers: {
154
- "Content-Type": finalContentType,
155
- "X-Content-Type-Options": "nosniff",
156
- // Don't set X-Frame-Options for JavaScript files - they need to execute
157
- ...(contentType.includes("text/html") && { "X-Frame-Options": "SAMEORIGIN" }),
158
- // Remove CORS restrictions since we're proxying
159
- "Access-Control-Allow-Origin": "*",
160
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
161
- "Access-Control-Allow-Headers": "Content-Type",
162
- },
163
- });
164
- } catch (error: any) {
165
- console.error("Proxy error:", error);
166
- return NextResponse.json(
167
- { error: error.message || "Proxy error occurred" },
168
- { status: 500 }
169
- );
170
- }
171
- }
172
-
173
- function rewriteUrls(
174
- html: string,
175
- targetHost: string,
176
- proxyBaseUrl: string,
177
- targetUrlParam: string,
178
- currentPathSegments: string[] = []
179
- ): string {
180
- let processed = html;
181
-
182
- // Get the current directory path for resolving relative URLs
183
- const currentDir = currentPathSegments.length > 0
184
- ? "/" + currentPathSegments.slice(0, -1).join("/") + "/"
185
- : "/";
186
-
187
- // Rewrite relative URLs in href attributes
188
- processed = processed.replace(
189
- /href=["']([^"']+)["']/gi,
190
- (match, urlStr) => {
191
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
192
- // Absolute URL - rewrite if it's the same domain
193
- try {
194
- const urlObj = new URL(urlStr);
195
- if (urlObj.hostname === targetHost) {
196
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
197
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
198
- return `href="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
199
- }
200
- } catch {
201
- // Invalid URL, keep as is
202
- }
203
- return match;
204
- } else if (urlStr.startsWith("//")) {
205
- // Protocol-relative URL
206
- try {
207
- const urlObj = new URL(`https:${urlStr}`);
208
- if (urlObj.hostname === targetHost) {
209
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
210
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
211
- return `href="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
212
- }
213
- } catch {
214
- // Invalid URL, keep as is
215
- }
216
- return match;
217
- } else if (urlStr.startsWith("#") || urlStr.startsWith("javascript:") || urlStr.startsWith("mailto:") || urlStr.startsWith("tel:")) {
218
- // Hash links, javascript:, mailto:, tel: - keep as is
219
- return match;
220
- } else {
221
- // Relative URL - resolve it properly
222
- let resolvedPath: string;
223
- if (urlStr.startsWith("/")) {
224
- // Absolute path relative to root
225
- resolvedPath = urlStr;
226
- } else if (urlStr.startsWith("components/") || urlStr.startsWith("images/") || urlStr.startsWith("videos/") || urlStr.startsWith("audio/")) {
227
- // Paths starting with known directories should be treated as absolute from root
228
- resolvedPath = "/" + urlStr;
229
- } else {
230
- // Relative path - resolve relative to current directory
231
- resolvedPath = currentDir + urlStr;
232
- // Normalize the path (remove ./ and ../)
233
- const parts = resolvedPath.split("/");
234
- const normalized: string[] = [];
235
- for (const part of parts) {
236
- if (part === "..") {
237
- normalized.pop();
238
- } else if (part !== "." && part !== "") {
239
- normalized.push(part);
240
- }
241
- }
242
- resolvedPath = "/" + normalized.join("/");
243
- }
244
- const [path, search] = resolvedPath.split("?");
245
- const pathPart = path === "/" ? "" : path;
246
- const searchPart = search ? `&${search}` : "";
247
- return `href="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
248
- }
249
- }
250
- );
251
-
252
- // Rewrite relative URLs in src attributes
253
- processed = processed.replace(
254
- /src=["']([^"']+)["']/gi,
255
- (match, urlStr) => {
256
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
257
- try {
258
- const urlObj = new URL(urlStr);
259
- if (urlObj.hostname === targetHost) {
260
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
261
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
262
- return `src="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
263
- }
264
- } catch {
265
- // Invalid URL, keep as is
266
- }
267
- return match;
268
- } else if (urlStr.startsWith("//")) {
269
- try {
270
- const urlObj = new URL(`https:${urlStr}`);
271
- if (urlObj.hostname === targetHost) {
272
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
273
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
274
- return `src="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
275
- }
276
- } catch {
277
- // Invalid URL, keep as is
278
- }
279
- return match;
280
- } else if (urlStr.startsWith("data:") || urlStr.startsWith("blob:")) {
281
- // Data URLs and blob URLs - keep as is
282
- return match;
283
- } else {
284
- // Relative URL - resolve it properly
285
- let resolvedPath: string;
286
- if (urlStr.startsWith("/")) {
287
- // Absolute path relative to root
288
- resolvedPath = urlStr;
289
- } else if (urlStr.startsWith("components/") || urlStr.startsWith("images/") || urlStr.startsWith("videos/") || urlStr.startsWith("audio/")) {
290
- // Paths starting with known directories should be treated as absolute from root
291
- resolvedPath = "/" + urlStr;
292
- } else {
293
- // Relative path - resolve relative to current directory
294
- resolvedPath = currentDir + urlStr;
295
- // Normalize the path (remove ./ and ../)
296
- const parts = resolvedPath.split("/");
297
- const normalized: string[] = [];
298
- for (const part of parts) {
299
- if (part === "..") {
300
- normalized.pop();
301
- } else if (part !== "." && part !== "") {
302
- normalized.push(part);
303
- }
304
- }
305
- resolvedPath = "/" + normalized.join("/");
306
- }
307
- const [path, search] = resolvedPath.split("?");
308
- const pathPart = path === "/" ? "" : path;
309
- const searchPart = search ? `&${search}` : "";
310
- return `src="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
311
- }
312
- }
313
- );
314
-
315
- // Rewrite URLs in action attributes (forms)
316
- processed = processed.replace(
317
- /action=["']([^"']+)["']/gi,
318
- (match, urlStr) => {
319
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
320
- try {
321
- const urlObj = new URL(urlStr);
322
- if (urlObj.hostname === targetHost) {
323
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
324
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
325
- return `action="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
326
- }
327
- } catch {
328
- // Invalid URL, keep as is
329
- }
330
- return match;
331
- } else if (urlStr.startsWith("//")) {
332
- try {
333
- const urlObj = new URL(`https:${urlStr}`);
334
- if (urlObj.hostname === targetHost) {
335
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
336
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
337
- return `action="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
338
- }
339
- } catch {
340
- // Invalid URL, keep as is
341
- }
342
- return match;
343
- } else {
344
- // Relative URL - rewrite to proxy
345
- const cleanUrl = urlStr.startsWith("/") ? urlStr : `/${urlStr}`;
346
- const [path, search] = cleanUrl.split("?");
347
- const pathPart = path === "/" ? "" : path;
348
- const searchPart = search ? `&${search}` : "";
349
- return `action="${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}"`;
350
- }
351
- }
352
- );
353
-
354
- // Rewrite URLs in CSS url() functions within style tags
355
- processed = processed.replace(
356
- /<style[^>]*>([\s\S]*?)<\/style>/gi,
357
- (match, cssContent) => {
358
- const rewrittenCss = rewriteCssUrls(cssContent, targetHost, proxyBaseUrl, targetUrlParam);
359
- return match.replace(cssContent, rewrittenCss);
360
- }
361
- );
362
-
363
- return processed;
364
- }
365
-
366
- function injectNavigationInterceptor(
367
- html: string,
368
- targetHost: string,
369
- proxyBaseUrl: string,
370
- targetUrlParam: string
371
- ): string {
372
- // Escape strings for safe injection
373
- const escapeJs = (str: string) => {
374
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
375
- };
376
-
377
- const escapedProxyBaseUrl = escapeJs(proxyBaseUrl);
378
- const escapedTargetUrlParam = escapeJs(targetUrlParam);
379
- const escapedTargetHost = escapeJs(targetHost);
380
-
381
- const interceptorScript = `
382
- <script>
383
- (function() {
384
- const proxyBaseUrl = "${escapedProxyBaseUrl}";
385
- const targetUrlParam = "${escapedTargetUrlParam}";
386
- const targetHost = "${escapedTargetHost}";
387
-
388
- // Helper function to convert URL to proxy URL
389
- function convertToProxyUrl(url) {
390
- try {
391
- const urlObj = typeof url === 'string' ? new URL(url, window.location.href) : url;
392
- if (urlObj.hostname === targetHost || urlObj.hostname === window.location.hostname) {
393
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
394
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
395
- return proxyBaseUrl + pathPart + targetUrlParam + (searchPart ? "&" + searchPart : "");
396
- }
397
- return typeof url === 'string' ? url : urlObj.href;
398
- } catch (e) {
399
- // Relative URL or invalid URL
400
- const urlStr = typeof url === 'string' ? url : url.href || '';
401
- const pathPart = urlStr.startsWith("/") ? urlStr.split("?")[0] : "/" + urlStr.split("?")[0];
402
- const searchPart = urlStr.includes("?") ? urlStr.split("?")[1] : "";
403
- return proxyBaseUrl + pathPart + targetUrlParam + (searchPart ? "&" + searchPart : "");
404
- }
405
- }
406
-
407
- // Intercept window.location.replace()
408
- const originalReplace = window.location.replace.bind(window.location);
409
- window.location.replace = function(url) {
410
- const proxyUrl = convertToProxyUrl(url);
411
- originalReplace(proxyUrl);
412
- };
413
-
414
- // Intercept window.location.assign()
415
- const originalAssign = window.location.assign.bind(window.location);
416
- window.location.assign = function(url) {
417
- const proxyUrl = convertToProxyUrl(url);
418
- originalAssign(proxyUrl);
419
- };
420
-
421
- // Intercept direct assignment to location.href using a proxy
422
- // Since we can't override window.location, we intercept href assignments
423
- // Try to intercept href setter, but handle gracefully if it's not configurable
424
- try {
425
- let locationHrefDescriptor = Object.getOwnPropertyDescriptor(window.location, 'href');
426
- if (locationHrefDescriptor && locationHrefDescriptor.set && locationHrefDescriptor.configurable) {
427
- const originalHrefSetter = locationHrefDescriptor.set;
428
- Object.defineProperty(window.location, 'href', {
429
- get: locationHrefDescriptor.get,
430
- set: function(url) {
431
- const proxyUrl = convertToProxyUrl(url);
432
- originalHrefSetter.call(window.location, proxyUrl);
433
- },
434
- configurable: true,
435
- enumerable: true
436
- });
437
- }
438
- } catch (e) {
439
- // If we can't intercept href setter, that's okay - replace() and assign() are intercepted
440
- console.warn('Could not intercept location.href setter:', e);
441
- }
442
- })();
443
- </script>
444
- `;
445
-
446
- // Inject script before closing </head> or before </body>
447
- if (html.includes("</head>")) {
448
- return html.replace("</head>", interceptorScript + "</head>");
449
- } else if (html.includes("</body>")) {
450
- return html.replace("</body>", interceptorScript + "</body>");
451
- } else {
452
- return interceptorScript + html;
453
- }
454
- }
455
-
456
- function rewriteCssUrls(
457
- css: string,
458
- targetHost: string,
459
- proxyBaseUrl: string,
460
- targetUrlParam: string
461
- ): string {
462
- return css.replace(
463
- /url\(["']?([^"')]+)["']?\)/gi,
464
- (match, urlStr) => {
465
- // Remove quotes if present
466
- const cleanUrl = urlStr.replace(/^["']|["']$/g, "");
467
-
468
- if (cleanUrl.startsWith("http://") || cleanUrl.startsWith("https://")) {
469
- try {
470
- const urlObj = new URL(cleanUrl);
471
- if (urlObj.hostname === targetHost) {
472
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
473
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
474
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
475
- }
476
- } catch {
477
- // Invalid URL, keep as is
478
- }
479
- return match;
480
- } else if (cleanUrl.startsWith("//")) {
481
- try {
482
- const urlObj = new URL(`https:${cleanUrl}`);
483
- if (urlObj.hostname === targetHost) {
484
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
485
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
486
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
487
- }
488
- } catch {
489
- // Invalid URL, keep as is
490
- }
491
- return match;
492
- } else if (cleanUrl.startsWith("data:") || cleanUrl.startsWith("blob:")) {
493
- // Data URLs and blob URLs - keep as is
494
- return match;
495
- } else {
496
- // Relative URL - rewrite to proxy
497
- const cleanUrlPath = cleanUrl.startsWith("/") ? cleanUrl : `/${cleanUrl}`;
498
- const [path, search] = cleanUrlPath.split("?");
499
- const pathPart = path === "/" ? "" : path;
500
- const searchPart = search ? `&${search}` : "";
501
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
502
- }
503
- }
504
- );
505
- }
506
-
507
- function rewriteJsUrls(
508
- js: string,
509
- targetHost: string,
510
- proxyBaseUrl: string,
511
- targetUrlParam: string
512
- ): string {
513
- // This is a basic implementation - JavaScript URL rewriting is complex
514
- // For now, we'll handle common patterns like fetch() and XMLHttpRequest
515
- let processed = js;
516
-
517
- // Rewrite fetch() calls with relative URLs
518
- processed = processed.replace(
519
- /fetch\(["']([^"']+)["']\)/gi,
520
- (match, urlStr) => {
521
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
522
- try {
523
- const urlObj = new URL(urlStr);
524
- if (urlObj.hostname === targetHost) {
525
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
526
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
527
- return `fetch("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
528
- }
529
- } catch {
530
- // Invalid URL, keep as is
531
- }
532
- return match;
533
- } else if (!urlStr.startsWith("//") && !urlStr.startsWith("data:") && !urlStr.startsWith("blob:")) {
534
- // Relative URL - rewrite to proxy
535
- const cleanUrl = urlStr.startsWith("/") ? urlStr : `/${urlStr}`;
536
- const [path, search] = cleanUrl.split("?");
537
- const pathPart = path === "/" ? "" : path;
538
- const searchPart = search ? `&${search}` : "";
539
- return `fetch("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
540
- }
541
- return match;
542
- }
543
- );
544
-
545
- return processed;
546
- }
547
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/api/proxy/route.ts DELETED
@@ -1,557 +0,0 @@
1
- import { NextRequest, NextResponse } from "next/server";
2
-
3
- export async function GET(req: NextRequest) {
4
- // Get the target URL from query parameter
5
- const targetUrl = req.nextUrl.searchParams.get("url");
6
-
7
- if (!targetUrl) {
8
- return NextResponse.json(
9
- { error: "Missing 'url' query parameter" },
10
- { status: 400 }
11
- );
12
- }
13
-
14
- try {
15
- // Parse the target URL
16
- const url = new URL(targetUrl);
17
-
18
- // Only allow static.hf.space domains for security
19
- if (!url.hostname.endsWith(".static.hf.space")) {
20
- return NextResponse.json(
21
- { error: "Only static.hf.space domains are allowed" },
22
- { status: 403 }
23
- );
24
- }
25
-
26
- // Use the pathname from the URL query param, or "/" for root
27
- const targetPath = url.pathname || "/";
28
-
29
- // Merge query parameters from the request URL with the target URL's search params
30
- const requestSearchParams = req.nextUrl.searchParams;
31
- const targetSearchParams = new URLSearchParams(url.search);
32
-
33
- // Copy all query params except 'url' to the target URL
34
- requestSearchParams.forEach((value, key) => {
35
- if (key !== "url") {
36
- targetSearchParams.set(key, value);
37
- }
38
- });
39
-
40
- const searchString = targetSearchParams.toString();
41
- const fullTargetUrl = `${url.protocol}//${url.hostname}${targetPath}${searchString ? `?${searchString}` : ""}`;
42
-
43
- // Fetch the content from the target URL
44
- const response = await fetch(fullTargetUrl, {
45
- headers: {
46
- "User-Agent": "Mozilla/5.0 (compatible; DeepSite/1.0)",
47
- },
48
- redirect: "follow", // Follow redirects automatically
49
- });
50
-
51
- if (!response.ok) {
52
- return NextResponse.json(
53
- { error: `Failed to fetch: ${response.statusText}` },
54
- { status: response.status }
55
- );
56
- }
57
-
58
- let contentType = response.headers.get("content-type") || "";
59
- const content = await response.text();
60
-
61
- // Detect content type from URL path if not properly set
62
- if (!contentType || contentType === "text/plain" || contentType === "application/octet-stream") {
63
- const urlPath = url.pathname || "";
64
- const fileExtension = urlPath.split(".").pop()?.toLowerCase();
65
-
66
- if (fileExtension === "js") {
67
- contentType = "application/javascript";
68
- } else if (fileExtension === "css") {
69
- contentType = "text/css";
70
- } else if (fileExtension === "html" || fileExtension === "htm") {
71
- contentType = "text/html";
72
- } else if (fileExtension === "json") {
73
- contentType = "application/json";
74
- }
75
- }
76
-
77
- // Get the base proxy URL
78
- const requestUrl = new URL(req.url);
79
- const proxyIndex = requestUrl.pathname.indexOf("/api/proxy");
80
- const basePath = proxyIndex > 0 ? requestUrl.pathname.substring(0, proxyIndex) : "";
81
- const proxyBaseUrl = `${basePath}/api/proxy`;
82
- // Build the base target URL with the path included
83
- const baseTargetUrl = `https://${url.hostname}${targetPath}`;
84
- const targetUrlParam = `?url=${encodeURIComponent(baseTargetUrl)}`;
85
-
86
- // Rewrite URLs in HTML content
87
- let processedContent = content;
88
-
89
- if (contentType.includes("text/html")) {
90
- // Rewrite relative URLs and URLs pointing to the same domain
91
- processedContent = rewriteUrls(
92
- content,
93
- url.hostname,
94
- proxyBaseUrl,
95
- baseTargetUrl,
96
- [] // Empty path segments for root route
97
- );
98
-
99
- // Inject script to intercept JavaScript-based navigation
100
- processedContent = injectNavigationInterceptor(
101
- processedContent,
102
- url.hostname,
103
- proxyBaseUrl,
104
- targetUrlParam
105
- );
106
- } else if (contentType.includes("text/css")) {
107
- // Rewrite URLs in CSS
108
- processedContent = rewriteCssUrls(
109
- content,
110
- url.hostname,
111
- proxyBaseUrl,
112
- targetUrlParam
113
- );
114
- } else if (contentType.includes("application/javascript") || contentType.includes("text/javascript")) {
115
- // For component files and most JavaScript, don't rewrite URLs
116
- // Only rewrite if needed (for fetch calls, etc.)
117
- // Most component JavaScript files don't need URL rewriting
118
- // Only rewrite if the file contains fetch calls with relative URLs
119
- if (content.includes("fetch(") && content.match(/fetch\(["'][^"']*[^\/]\./)) {
120
- processedContent = rewriteJsUrls(
121
- content,
122
- url.hostname,
123
- proxyBaseUrl,
124
- targetUrlParam
125
- );
126
- } else {
127
- // Don't modify component JavaScript files - they should work as-is
128
- processedContent = content;
129
- }
130
- }
131
-
132
- // Ensure JavaScript files have charset specified
133
- let finalContentType = contentType;
134
- if (contentType.includes("javascript") && !contentType.includes("charset")) {
135
- finalContentType = contentType.includes(";")
136
- ? contentType + "; charset=utf-8"
137
- : contentType + "; charset=utf-8";
138
- }
139
-
140
- // Return the processed content with appropriate headers
141
- return new NextResponse(processedContent, {
142
- status: 200,
143
- headers: {
144
- "Content-Type": finalContentType,
145
- "X-Content-Type-Options": "nosniff",
146
- // Don't set X-Frame-Options for JavaScript files - they need to execute
147
- ...(contentType.includes("text/html") && { "X-Frame-Options": "SAMEORIGIN" }),
148
- // Remove CORS restrictions since we're proxying
149
- "Access-Control-Allow-Origin": "*",
150
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
151
- "Access-Control-Allow-Headers": "Content-Type",
152
- },
153
- });
154
- } catch (error: any) {
155
- console.error("Proxy error:", error);
156
- return NextResponse.json(
157
- { error: error.message || "Proxy error occurred" },
158
- { status: 500 }
159
- );
160
- }
161
- }
162
-
163
- // Import the helper functions from the catch-all route
164
- // We'll need to move these to a shared file or duplicate them here
165
- // For now, let's duplicate them to avoid refactoring
166
-
167
- function rewriteUrls(
168
- html: string,
169
- targetHost: string,
170
- proxyBaseUrl: string,
171
- baseTargetUrl: string,
172
- currentPathSegments: string[] = []
173
- ): string {
174
- let processed = html;
175
-
176
- // Get the current directory path for resolving relative URLs
177
- const currentDir = currentPathSegments.length > 0
178
- ? "/" + currentPathSegments.slice(0, -1).join("/") + "/"
179
- : "/";
180
-
181
- // Helper function to build proxy URL with path in query parameter
182
- const buildProxyUrl = (path: string, search?: string) => {
183
- const fullTargetUrl = `https://${targetHost}${path}`;
184
- const encodedUrl = encodeURIComponent(fullTargetUrl);
185
- const searchPart = search ? `&${search}` : "";
186
- return `${proxyBaseUrl}?url=${encodedUrl}${searchPart}`;
187
- };
188
-
189
- // Rewrite relative URLs in href attributes
190
- processed = processed.replace(
191
- /href=["']([^"']+)["']/gi,
192
- (match, urlStr) => {
193
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
194
- // Absolute URL - rewrite if it's the same domain
195
- try {
196
- const urlObj = new URL(urlStr);
197
- if (urlObj.hostname === targetHost) {
198
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
199
- return `href="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
200
- }
201
- } catch {
202
- // Invalid URL, keep as is
203
- }
204
- return match;
205
- } else if (urlStr.startsWith("//")) {
206
- // Protocol-relative URL
207
- try {
208
- const urlObj = new URL(`https:${urlStr}`);
209
- if (urlObj.hostname === targetHost) {
210
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
211
- return `href="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
212
- }
213
- } catch {
214
- // Invalid URL, keep as is
215
- }
216
- return match;
217
- } else if (urlStr.startsWith("#") || urlStr.startsWith("javascript:") || urlStr.startsWith("mailto:") || urlStr.startsWith("tel:")) {
218
- // Hash links, javascript:, mailto:, tel: - keep as is
219
- return match;
220
- } else {
221
- // Relative URL - resolve it properly
222
- let resolvedPath: string;
223
- if (urlStr.startsWith("/")) {
224
- // Absolute path relative to root
225
- resolvedPath = urlStr;
226
- } else if (urlStr.startsWith("components/") || urlStr.startsWith("images/") || urlStr.startsWith("videos/") || urlStr.startsWith("audio/")) {
227
- // Paths starting with known directories should be treated as absolute from root
228
- resolvedPath = "/" + urlStr;
229
- } else {
230
- // Relative path - resolve relative to current directory
231
- resolvedPath = currentDir + urlStr;
232
- // Normalize the path (remove ./ and ../)
233
- const parts = resolvedPath.split("/");
234
- const normalized: string[] = [];
235
- for (const part of parts) {
236
- if (part === "..") {
237
- normalized.pop();
238
- } else if (part !== "." && part !== "") {
239
- normalized.push(part);
240
- }
241
- }
242
- resolvedPath = "/" + normalized.join("/");
243
- }
244
- const [path, search] = resolvedPath.split("?");
245
- const normalizedPath = path === "/" ? "/" : path;
246
- return `href="${buildProxyUrl(normalizedPath, search)}"`;
247
- }
248
- }
249
- );
250
-
251
- // Rewrite relative URLs in src attributes
252
- processed = processed.replace(
253
- /src=["']([^"']+)["']/gi,
254
- (match, urlStr) => {
255
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
256
- try {
257
- const urlObj = new URL(urlStr);
258
- if (urlObj.hostname === targetHost) {
259
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
260
- return `src="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
261
- }
262
- } catch {
263
- // Invalid URL, keep as is
264
- }
265
- return match;
266
- } else if (urlStr.startsWith("//")) {
267
- try {
268
- const urlObj = new URL(`https:${urlStr}`);
269
- if (urlObj.hostname === targetHost) {
270
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
271
- return `src="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
272
- }
273
- } catch {
274
- // Invalid URL, keep as is
275
- }
276
- return match;
277
- } else if (urlStr.startsWith("data:") || urlStr.startsWith("blob:")) {
278
- // Data URLs and blob URLs - keep as is
279
- return match;
280
- } else {
281
- // Relative URL - resolve it properly
282
- let resolvedPath: string;
283
- if (urlStr.startsWith("/")) {
284
- // Absolute path relative to root
285
- resolvedPath = urlStr;
286
- } else if (urlStr.startsWith("components/") || urlStr.startsWith("images/") || urlStr.startsWith("videos/") || urlStr.startsWith("audio/")) {
287
- // Paths starting with known directories should be treated as absolute from root
288
- resolvedPath = "/" + urlStr;
289
- } else {
290
- // Relative path - resolve relative to current directory
291
- resolvedPath = currentDir + urlStr;
292
- // Normalize the path (remove ./ and ../)
293
- const parts = resolvedPath.split("/");
294
- const normalized: string[] = [];
295
- for (const part of parts) {
296
- if (part === "..") {
297
- normalized.pop();
298
- } else if (part !== "." && part !== "") {
299
- normalized.push(part);
300
- }
301
- }
302
- resolvedPath = "/" + normalized.join("/");
303
- }
304
- const [path, search] = resolvedPath.split("?");
305
- const normalizedPath = path === "/" ? "/" : path;
306
- return `src="${buildProxyUrl(normalizedPath, search)}"`;
307
- }
308
- }
309
- );
310
-
311
- // Rewrite URLs in action attributes (forms)
312
- processed = processed.replace(
313
- /action=["']([^"']+)["']/gi,
314
- (match, urlStr) => {
315
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
316
- try {
317
- const urlObj = new URL(urlStr);
318
- if (urlObj.hostname === targetHost) {
319
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
320
- return `action="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
321
- }
322
- } catch {
323
- // Invalid URL, keep as is
324
- }
325
- return match;
326
- } else if (urlStr.startsWith("//")) {
327
- try {
328
- const urlObj = new URL(`https:${urlStr}`);
329
- if (urlObj.hostname === targetHost) {
330
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
331
- return `action="${buildProxyUrl(urlObj.pathname, searchPart)}"`;
332
- }
333
- } catch {
334
- // Invalid URL, keep as is
335
- }
336
- return match;
337
- } else {
338
- // Relative URL - resolve it properly
339
- let resolvedPath: string;
340
- if (urlStr.startsWith("/")) {
341
- resolvedPath = urlStr;
342
- } else if (urlStr.startsWith("components/") || urlStr.startsWith("images/") || urlStr.startsWith("videos/") || urlStr.startsWith("audio/")) {
343
- resolvedPath = "/" + urlStr;
344
- } else {
345
- resolvedPath = currentDir + urlStr;
346
- const parts = resolvedPath.split("/");
347
- const normalized: string[] = [];
348
- for (const part of parts) {
349
- if (part === "..") {
350
- normalized.pop();
351
- } else if (part !== "." && part !== "") {
352
- normalized.push(part);
353
- }
354
- }
355
- resolvedPath = "/" + normalized.join("/");
356
- }
357
- const [path, search] = resolvedPath.split("?");
358
- const normalizedPath = path === "/" ? "/" : path;
359
- return `action="${buildProxyUrl(normalizedPath, search)}"`;
360
- }
361
- }
362
- );
363
-
364
- // Rewrite URLs in CSS url() functions within style tags
365
- processed = processed.replace(
366
- /<style[^>]*>([\s\S]*?)<\/style>/gi,
367
- (match, cssContent) => {
368
- const rewrittenCss = rewriteCssUrls(cssContent, targetHost, proxyBaseUrl, baseTargetUrl);
369
- return match.replace(cssContent, rewrittenCss);
370
- }
371
- );
372
-
373
- return processed;
374
- }
375
-
376
- function injectNavigationInterceptor(
377
- html: string,
378
- targetHost: string,
379
- proxyBaseUrl: string,
380
- targetUrlParam: string
381
- ): string {
382
- // Escape strings for safe injection
383
- const escapeJs = (str: string) => {
384
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
385
- };
386
-
387
- const escapedProxyBaseUrl = escapeJs(proxyBaseUrl);
388
- const escapedTargetUrlParam = escapeJs(targetUrlParam);
389
- const escapedTargetHost = escapeJs(targetHost);
390
-
391
- const interceptorScript = `
392
- <script>
393
- (function() {
394
- const proxyBaseUrl = "${escapedProxyBaseUrl}";
395
- const targetUrlParam = "${escapedTargetUrlParam}";
396
- const targetHost = "${escapedTargetHost}";
397
-
398
- // Helper function to convert URL to proxy URL
399
- function convertToProxyUrl(url) {
400
- try {
401
- const urlObj = typeof url === 'string' ? new URL(url, window.location.href) : url;
402
- if (urlObj.hostname === targetHost || urlObj.hostname === window.location.hostname) {
403
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
404
- const searchPart = urlObj.search ? urlObj.search.substring(1) : "";
405
- return proxyBaseUrl + pathPart + targetUrlParam + (searchPart ? "&" + searchPart : "");
406
- }
407
- return typeof url === 'string' ? url : urlObj.href;
408
- } catch (e) {
409
- // Relative URL or invalid URL
410
- const urlStr = typeof url === 'string' ? url : url.href || '';
411
- const pathPart = urlStr.startsWith("/") ? urlStr.split("?")[0] : "/" + urlStr.split("?")[0];
412
- const searchPart = urlStr.includes("?") ? urlStr.split("?")[1] : "";
413
- return proxyBaseUrl + pathPart + targetUrlParam + (searchPart ? "&" + searchPart : "");
414
- }
415
- }
416
-
417
- // Intercept window.location.replace()
418
- const originalReplace = window.location.replace.bind(window.location);
419
- window.location.replace = function(url) {
420
- const proxyUrl = convertToProxyUrl(url);
421
- originalReplace(proxyUrl);
422
- };
423
-
424
- // Intercept window.location.assign()
425
- const originalAssign = window.location.assign.bind(window.location);
426
- window.location.assign = function(url) {
427
- const proxyUrl = convertToProxyUrl(url);
428
- originalAssign(proxyUrl);
429
- };
430
-
431
- // Intercept direct assignment to location.href using a proxy
432
- // Since we can't override window.location, we intercept href assignments
433
- // Try to intercept href setter, but handle gracefully if it's not configurable
434
- try {
435
- let locationHrefDescriptor = Object.getOwnPropertyDescriptor(window.location, 'href');
436
- if (locationHrefDescriptor && locationHrefDescriptor.set && locationHrefDescriptor.configurable) {
437
- const originalHrefSetter = locationHrefDescriptor.set;
438
- Object.defineProperty(window.location, 'href', {
439
- get: locationHrefDescriptor.get,
440
- set: function(url) {
441
- const proxyUrl = convertToProxyUrl(url);
442
- originalHrefSetter.call(window.location, proxyUrl);
443
- },
444
- configurable: true,
445
- enumerable: true
446
- });
447
- }
448
- } catch (e) {
449
- // If we can't intercept href setter, that's okay - replace() and assign() are intercepted
450
- console.warn('Could not intercept location.href setter:', e);
451
- }
452
- })();
453
- </script>
454
- `;
455
-
456
- // Inject script before closing </head> or before </body>
457
- if (html.includes("</head>")) {
458
- return html.replace("</head>", interceptorScript + "</head>");
459
- } else if (html.includes("</body>")) {
460
- return html.replace("</body>", interceptorScript + "</body>");
461
- } else {
462
- return interceptorScript + html;
463
- }
464
- }
465
-
466
- function rewriteCssUrls(
467
- css: string,
468
- targetHost: string,
469
- proxyBaseUrl: string,
470
- targetUrlParam: string
471
- ): string {
472
- return css.replace(
473
- /url\(["']?([^"')]+)["']?\)/gi,
474
- (match, urlStr) => {
475
- // Remove quotes if present
476
- const cleanUrl = urlStr.replace(/^["']|["']$/g, "");
477
-
478
- if (cleanUrl.startsWith("http://") || cleanUrl.startsWith("https://")) {
479
- try {
480
- const urlObj = new URL(cleanUrl);
481
- if (urlObj.hostname === targetHost) {
482
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
483
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
484
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
485
- }
486
- } catch {
487
- // Invalid URL, keep as is
488
- }
489
- return match;
490
- } else if (cleanUrl.startsWith("//")) {
491
- try {
492
- const urlObj = new URL(`https:${cleanUrl}`);
493
- if (urlObj.hostname === targetHost) {
494
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
495
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
496
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
497
- }
498
- } catch {
499
- // Invalid URL, keep as is
500
- }
501
- return match;
502
- } else if (cleanUrl.startsWith("data:") || cleanUrl.startsWith("blob:")) {
503
- // Data URLs and blob URLs - keep as is
504
- return match;
505
- } else {
506
- // Relative URL - rewrite to proxy
507
- const cleanUrlPath = cleanUrl.startsWith("/") ? cleanUrl : `/${cleanUrl}`;
508
- const [path, search] = cleanUrlPath.split("?");
509
- const pathPart = path === "/" ? "" : path;
510
- const searchPart = search ? `&${search}` : "";
511
- return `url("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
512
- }
513
- }
514
- );
515
- }
516
-
517
- function rewriteJsUrls(
518
- js: string,
519
- targetHost: string,
520
- proxyBaseUrl: string,
521
- targetUrlParam: string
522
- ): string {
523
- // This is a basic implementation - JavaScript URL rewriting is complex
524
- // For now, we'll handle common patterns like fetch() and XMLHttpRequest
525
- let processed = js;
526
-
527
- // Rewrite fetch() calls with relative URLs
528
- processed = processed.replace(
529
- /fetch\(["']([^"']+)["']\)/gi,
530
- (match, urlStr) => {
531
- if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
532
- try {
533
- const urlObj = new URL(urlStr);
534
- if (urlObj.hostname === targetHost) {
535
- const pathPart = urlObj.pathname === "/" ? "" : urlObj.pathname;
536
- const searchPart = urlObj.search ? `&${urlObj.search.substring(1)}` : "";
537
- return `fetch("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
538
- }
539
- } catch {
540
- // Invalid URL, keep as is
541
- }
542
- return match;
543
- } else if (!urlStr.startsWith("//") && !urlStr.startsWith("data:") && !urlStr.startsWith("blob:")) {
544
- // Relative URL - rewrite to proxy
545
- const cleanUrl = urlStr.startsWith("/") ? urlStr : `/${urlStr}`;
546
- const [path, search] = cleanUrl.split("?");
547
- const pathPart = path === "/" ? "" : path;
548
- const searchPart = search ? `&${search}` : "";
549
- return `fetch("${proxyBaseUrl}${pathPart}${targetUrlParam}${searchPart}")`;
550
- }
551
- return match;
552
- }
553
- );
554
-
555
- return processed;
556
- }
557
-