-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathindex.ts
More file actions
134 lines (113 loc) Β· 4.54 KB
/
index.ts
File metadata and controls
134 lines (113 loc) Β· 4.54 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/** Proxy requests for files and thumbnails in public albums. */
export default {
async fetch(request: Request) {
switch (request.method) {
case "OPTIONS":
return handleOPTIONS(request);
case "GET":
return handleGET(request);
default:
console.log(`Unsupported HTTP method ${request.method}`);
return new Response(null, { status: 405 });
}
},
} satisfies ExportedHandler;
const handleOPTIONS = (request: Request) => {
const origin = request.headers.get("Origin");
if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin);
return new Response("", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers":
"X-Auth-Access-Token, X-Auth-Access-Token-JWT, X-Client-Package, X-Client-Version",
"Access-Control-Max-Age": "86400",
},
});
};
const isAllowedOrigin = (origin: string | null) => {
const allowed = ["albums.ente.io", "albums.ente.sh", "localhost"];
if (!origin) return false;
try {
const url = new URL(origin);
return allowed.includes(url.hostname);
} catch {
// origin is likely an invalid URL
return false;
}
};
const handleGET = async (request: Request) => {
const url = new URL(request.url);
const fileID = url.searchParams.get("fileID");
if (!fileID) return new Response(null, { status: 400 });
let accessToken = request.headers.get("X-Auth-Access-Token");
if (accessToken === undefined) {
console.warn("Using deprecated accessToken query param");
accessToken = url.searchParams.get("accessToken");
}
if (!accessToken) {
console.error("No accessToken provided");
// return new Response(null, { status: 400 });
}
let accessTokenJWT = request.headers.get("X-Auth-Access-Token-JWT");
if (accessTokenJWT === undefined) {
console.warn("Using deprecated accessTokenJWT query param");
accessTokenJWT = url.searchParams.get("accessTokenJWT");
}
const pathname = url.pathname;
const params = new URLSearchParams();
if (accessToken) params.set("accessToken", accessToken);
if (accessTokenJWT) params.set("accessTokenJWT", accessTokenJWT);
const headers = {
"X-Client-Package": request.headers.get("X-Client-Package") ?? "",
"X-Client-Version": request.headers.get("X-Client-Version") ?? "",
"User-Agent": request.headers.get("User-Agent") ?? "",
"X-Forwarded-For": request.headers.get("CF-Connecting-IP") ?? "",
"CF-IPCountry": request.headers.get("CF-IPCountry") ?? "",
};
let response = await fetch(
`https://api.ente.io/public-collection/files${pathname}${fileID}?${params.toString()}`,
{ headers },
);
if (!response.ok) console.log("Upstream error", response.status);
response = new Response(response.body, response);
response.headers.set("Access-Control-Allow-Origin", "*");
hardenResponseHeaders(response.headers);
return response;
};
const hardenResponseHeaders = (headers: Headers) => {
headers.set("X-Content-Type-Options", "nosniff");
headers.set("Content-Security-Policy", "sandbox allow-downloads");
headers.set("X-Frame-Options", "DENY");
headers.set("Referrer-Policy", "no-referrer");
const contentType = headers.get("Content-Type");
if (contentType && isHTMLLikeContentType(contentType)) {
enforceAttachmentDisposition(headers);
}
};
const isHTMLLikeContentType = (contentType: string) => {
const normalized = contentType.split(";")[0]?.trim().toLowerCase();
if (!normalized) return false;
return (
normalized === "text/html" ||
normalized === "application/xhtml+xml" ||
normalized === "image/svg+xml" ||
normalized === "text/xml" ||
normalized === "application/xml"
);
};
const enforceAttachmentDisposition = (headers: Headers) => {
const contentDisposition = headers.get("Content-Disposition");
if (contentDisposition && /attachment/i.test(contentDisposition)) {
return;
}
const filenameParams = contentDisposition
?.split(";")
.map((part) => part.trim())
.filter((part, index) => {
if (index === 0) return false;
return /^filename/i.test(part);
});
const disposition = ["attachment", ...(filenameParams ?? [])].join("; ");
headers.set("Content-Disposition", disposition);
};