-
Notifications
You must be signed in to change notification settings - Fork 585
Expand file tree
/
Copy pathroute.ts
More file actions
110 lines (92 loc) · 3.35 KB
/
route.ts
File metadata and controls
110 lines (92 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { NextResponse } from 'next/server';
import { Headers } from 'node-fetch';
import { fetchResource, StatusError } from './feature';
import { errors } from './feature/errors';
import { checkURLForPrivateIP, isHTTPProtocol } from './feature/ip';
type Params = { params: object }
const USER_AGENT = process.env.NEXT_PUBLIC_METADATA_USER_AGENT ?? 'Solana Explorer';
const MAX_SIZE = process.env.NEXT_PUBLIC_METADATA_MAX_CONTENT_SIZE
? Number(process.env.NEXT_PUBLIC_METADATA_MAX_CONTENT_SIZE)
: 1_000_000; // 1 000 000 bytes
const TIMEOUT = process.env.NEXT_PUBLIC_METADATA_TIMEOUT
? Number(process.env.NEXT_PUBLIC_METADATA_TIMEOUT)
: 10_000; // 10s
/**
* Respond with error in a JSON format
*/
function respondWithError(status: keyof typeof errors, message?: string) {
return NextResponse.json({ error: message ?? errors[status].message }, { status });
}
export async function GET(
request: Request,
{ params: _params }: Params,
) {
const isProxyEnabled = process.env.NEXT_PUBLIC_METADATA_ENABLED === 'true';
if (!isProxyEnabled) {
return respondWithError(404);
}
let uriParam: string;
try {
const url = new URL(request.url);
const queryParam = url.searchParams.get('uri');
if (!queryParam) {
throw new Error('Absent URI');
}
uriParam = decodeURIComponent(queryParam);
const parsedUrl = new URL(uriParam);
// check that uri has supported protocol despite of any other checks
if (!isHTTPProtocol(parsedUrl)) {
console.error('Unsupported protocol', parsedUrl.protocol);
return respondWithError(400);
}
const isPrivate = await checkURLForPrivateIP(parsedUrl);
if (isPrivate) {
console.error('Private IP detected', parsedUrl.hostname);
return respondWithError(403);
}
} catch (_error) {
console.error(_error);
return respondWithError(400);
}
const headers = new Headers({
'Content-Type': 'application/json; charset=utf-8',
'User-Agent': USER_AGENT
});
let data;
let resourceHeaders: Headers;
try {
const response = await fetchResource(uriParam, headers, TIMEOUT, MAX_SIZE);
data = response.data;
resourceHeaders = response.headers;
} catch (e) {
const status = (e as StatusError)?.status;
switch (status) {
case 413:
case 415:
case 500:
case 504: {
return respondWithError(status);
}
default:
return respondWithError(500);
}
}
// preserve original cache-control headers
const responseHeaders = {
'Cache-Control': resourceHeaders.get('cache-control') ?? 'no-cache',
'Content-Length': resourceHeaders.get('content-length') as string,
'Content-Type': resourceHeaders.get('content-type') ?? 'application/json, charset=utf-8',
Etag: resourceHeaders.get('etag') ?? 'no-etag',
};
if (data instanceof ArrayBuffer) {
return new NextResponse(data, {
headers: responseHeaders,
});
} else if (resourceHeaders.get('content-type')?.startsWith('application/json')) {
return NextResponse.json(data, {
headers: responseHeaders,
});
} else {
return respondWithError(415);
}
}