const GDPROXY_NAME = "a gdproxy instance"; const ACCENT_COLOR = "#DD65E1"; const SERVICE_ACCOUNTS = [ // Enter service accounts' key herd { "type": "service_account", "project_id": "no", "private_key_id": "noo", "private_key": "hell no", "client_email": "you get the point now i hope", "client_id": "107802968794798221608", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/no" } ]; const DRIVES = [ { // Enter folder id id you want to narrow access to a folder or null "folderId": "1QIrFMjC1ITraZ2xyHjocY9c5LUlFZ-I0", // If null, folderId must not be null "teamDriveId": null, "name": GDPROXY_NAME, "useCache": true, // username:password pairs, empty to disable authentication "auth": [] }, { // Enter folder id if you want to narrow access to a folder or null "folderId": null, // If null, folderId must not be null "teamDriveId": "0AOmq9TyURQtXUk9PVA", "name": "tdroot", "useCache": true, // username:password pairs, empty to disable authentication "auth": [ "username:password", "horny:yes" ] }, { // Enter folder id if you want to narrow access to a folder or null "folderId": "1GrPj3xvy503vAEsuhs5NgogmeKLLMGoU", // If null, folderId must not be null "teamDriveId": "0AOmq9TyURQtXUk9PVA", "name": "tdnotroot", "useCache": false, // username:password pairs, empty to disable authentication "auth": [] } ]; // This is past the config part const CSS = `body { background-color: black; color: white; font-family: sans-serif; padding: 1em; } a { color: ${ACCENT_COLOR}; } div.error { border: 1px solid #fcc; background: #fee; padding: 0.5em 1em 0.5em 1em; color: black; text-align: center; } .text-right { text-align: right; } th { text-align: left; } th:nth-child(1) { width: 100%; }`; const PATH_RE = /^\/(\d+):\/(?:(.+)\/?)?$/; for (let i=0; i < DRIVES.length; i++) { if (DRIVES[i].useCache) { DRIVES[i].pathCache = {}; DRIVES[i].listCache = {}; } } let pageTokenCache = {}; let sa = SERVICE_ACCOUNTS[Math.floor(Math.random() * SERVICE_ACCOUNTS.length)]; let saBearer = null; let saBearerExpiry = -1; let saKey = sa.private_key.substring("-----BEGIN PRIVATE KEY-----".length, sa.private_key.length - "-----END PRIVATE KEY-----".length); // mmm yes no document.createElement function escapeHTML(text) { return text.replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replaceAll("'", "'"); } let escapeURL = encodeURIComponent; let unescapeURL = decodeURIComponent; Object.prototype.exists = Object.prototype.hasOwnProperty; function btoaurl(i) { return btoa(i).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); } // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#pkcs_8_import function str2ab(str) { const buf = new ArrayBuffer(str.length); const bufView = new Uint8Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; } function ab2str(ab) { let str = ""; ab = new Uint8Array(ab); for (let i=0; i < ab.byteLength; i++) { str += String.fromCharCode(ab[i]); } return str; } function buildErrorPage(nonce, title, message) { return `
Redirecting to ${escapeHTML(url.href)}
`; return new Response(content, { status: 301, statusText: "Moved Permanently", headers: { "Location": url.href, "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'`, "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=31536000" } }); } let parsed = url.pathname.match(PATH_RE); if (parsed === null || Number(parsed[1]) >= DRIVES.length) { return new Response(buildErrorPage(nonce, GDPROXY_NAME + " - Not Found", "404 Not Found"), { status: 404, statusText: "Not Found", headers: { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'`, "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=31536000" } }); } let drive = DRIVES[Number(parsed[1])]; if (drive.auth.length !== 0) { let authHeader = request.headers.get("Authorization"); if (!request.headers.has("Authorization") || !authHeader.startsWith("Basic ")) { return new Response(buildErrorPage(nonce, drive.name + " - Unauthorised", "Unauthorised"), { status: 401, statusText: "Unauthorised", headers: { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'`, "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=31536000", "WWW-Authenticate": `Basic realm="${drive.name}"` } }); } authHeader = atob(authHeader.replace("Basic ", "")); let allowed = false; for (let i=0; i < drive.auth.length; i++) { if (authHeader === drive.auth[i]) { allowed = true; break; } } if (!allowed) { return new Response(buildErrorPage(nonce, drive.name + " - Unauthorised", "Unauthorised"), { status: 401, statusText: "Unauthorised", headers: { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'`, "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=31536000", "WWW-Authenticate": `Basic realm="${drive.name}"` } }); } } if (Date.now() >= saBearerExpiry) { if (typeof saKey === "string") { saKey = await crypto.subtle.importKey( "pkcs8", str2ab(atob(saKey.replace("-", ""))), { "name": "RSASSA-PKCS1-v1_5", "hash": "SHA-256" }, false, ["sign"] ); } let iat = Math.floor(Date.now() / 1000); let exp = iat + 60 * 60; let token = btoaurl(JSON.stringify({"alg": "RS256", "typ": "JWT"})); token += "." + btoaurl(JSON.stringify({ "iss": sa.client_email, "aud": sa.token_uri, "scope": "https://www.googleapis.com/auth/drive.readonly", "exp": exp, "iat": iat })); let sig = btoaurl(ab2str(await crypto.subtle.sign("RSASSA-PKCS1-v1_5", saKey, str2ab(token)))); token += "." + sig; let params = new URLSearchParams(); params.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); params.append("assertion", token); let resp = await (await fetch(sa.token_uri, {"method": "POST", "body": params})).json(); saBearer = resp.access_token; saBearerExpiry = (exp - 60) * 1000; } let path = parsed[2]; if (typeof path === "undefined") { path = ""; } else { path = path.replaceAll(/^\/+|\/+$/g, ""); } let baseId = drive.folderId || drive.teamDriveId; let id = baseId; let mimeType = "application/vnd.google-apps.folder"; if (path !== "") { let resp = await pathToId(path, drive); if (resp === null) { return new Response(buildErrorPage(nonce, drive.name + " - Not Found", "404 Not Found"), { status: 404, statusText: "Not Found", headers: { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'`, "Referrer-Policy": "no-referrer", "Strict-Transport-Security": "max-age=31536000" } }); } id = resp.id; mimeType = resp.mimeType; } if (mimeType === "application/vnd.google-apps.folder") { let query = new URLSearchParams(url.search); let pagenum = Number(query.get("page") || "1"); let files = await listFiles(id, drive, pageTokenCache[`${id},${pagenum - 1}`]); if (typeof pageTokenCache[`${id},${pagenum - 1}`] === "undefined") { pagenum = 1; } if (typeof files.nextPageToken !== "undefined") { pageTokenCache[`${id},${pagenum}`] = files.nextPageToken; } let page = `Name | Size |
---|---|
.. | - |
${escapeHTML(name)} | ${size} |