-
Notifications
You must be signed in to change notification settings - Fork 130
Expand file tree
/
Copy path_worker.js
More file actions
133 lines (111 loc) · 3.45 KB
/
Copy path_worker.js
File metadata and controls
133 lines (111 loc) · 3.45 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
import { AwsClient } from "aws4fetch";
const HOMEPAGE = "https://github.com/milkey-mouse/git-lfs-s3-proxy";
const EXPIRY = 3600;
const MIME = "application/vnd.git-lfs+json";
const METHOD_FOR = {
upload: "PUT",
download: "GET",
};
async function sign(s3, bucket, path, method) {
const info = { method };
const signed = await s3.sign(
new Request(`https://${bucket}/${path}?X-Amz-Expires=${EXPIRY}`, info),
{ aws: { signQuery: true } },
);
return signed.url;
}
function parseAuthorization(req) {
const auth = req.headers.get("Authorization");
if (!auth) {
throw new Response(null, { status: 401 });
}
const [scheme, encoded] = auth.split(" ");
if (scheme !== "Basic" || !encoded) {
throw new Response(null, { status: 400 });
}
const buffer = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
const decoded = new TextDecoder().decode(buffer);
const index = decoded.indexOf(":");
if (index === -1) {
throw new Response(null, { status: 400 });
}
return { user: decoded.slice(0, index), pass: decoded.slice(index + 1) };
}
async function fetch(req, env) {
const url = new URL(req.url);
if (url.pathname == "/") {
if (req.method === "GET") {
return Response.redirect(HOMEPAGE, 302);
} else {
return new Response(null, { status: 405, headers: { Allow: "GET" } });
}
}
if (!url.pathname.endsWith("/objects/batch")) {
return new Response(null, { status: 404 });
}
if (req.method !== "POST") {
return new Response(null, { status: 405, headers: { Allow: "POST" } });
}
// in practice, we'd rather not break out-of-spec clients not setting these
/*if (!req.headers.get("Accept").startsWith(MIME)
|| !req.headers.get("Content-Type").startsWith(MIME)) {
return new Response(null, { status: 406 });
}*/
const { user, pass } = parseAuthorization(req);
let s3Options = { accessKeyId: user, secretAccessKey: pass };
const segments = url.pathname.split("/").slice(1, -2);
let params = {};
let bucketIdx = 0;
for (const segment of segments) {
const sliceIdx = segment.indexOf("=");
if (sliceIdx === -1) {
break;
} else {
const key = decodeURIComponent(segment.slice(0, sliceIdx));
const val = decodeURIComponent(segment.slice(sliceIdx + 1));
s3Options[key] = val;
bucketIdx++;
}
}
const s3 = new AwsClient(s3Options);
const bucket = segments.slice(bucketIdx).join("/");
const expires_in = params.expiry || env.EXPIRY || EXPIRY;
const { objects, operation, hash_algo = "sha256" } = await req.json();
if (hash_algo !== "sha256") {
return new Response(
JSON.stringify({
message: `Hash algorithm '${hash_algo}' is not supported. Only 'sha256' is currently supported.`,
}),
{
status: 409,
headers: { "Content-Type": "application/vnd.git-lfs+json" },
},
);
}
const method = METHOD_FOR[operation];
const response = JSON.stringify({
transfer: "basic",
hash_algo: "sha256",
objects: await Promise.all(
objects.map(async ({ oid, size }) => ({
oid,
size,
authenticated: true,
actions: {
[operation]: {
href: await sign(s3, bucket, oid, method),
expires_in,
},
},
})),
),
});
return new Response(response, {
status: 200,
headers: {
"Cache-Control": "no-store",
"Content-Type": "application/vnd.git-lfs+json",
},
});
}
export default { fetch };