2023-02-20 12:18:37 +01:00
|
|
|
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
|
|
|
2023-04-13 08:45:44 +02:00
|
|
|
interface BucketInfo {
|
|
|
|
bucket?: string | null;
|
|
|
|
err?: string;
|
|
|
|
}
|
|
|
|
|
2023-06-08 11:02:12 +02:00
|
|
|
export function isUrlFromBucket(fileUrl: string, bucketName: string, bucketBaseUrl = ''): boolean {
|
2023-05-31 11:26:44 +02:00
|
|
|
const url = new URL(fileUrl);
|
|
|
|
|
|
|
|
// Check if the file URL is using a base URL (e.g. a CDN).
|
|
|
|
// In this case, check if the file URL starts with the same base URL as the bucket URL.
|
|
|
|
if (bucketBaseUrl) {
|
|
|
|
const baseUrl = new URL(bucketBaseUrl);
|
|
|
|
return url.href.startsWith(baseUrl.href);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { bucket } = getBucketFromAwsUrl(fileUrl);
|
|
|
|
|
|
|
|
if (bucket) {
|
|
|
|
return bucket === bucketName;
|
|
|
|
}
|
|
|
|
|
|
|
|
// File URL might be of an S3-compatible provider. (or an invalid URL)
|
|
|
|
// In this case, check if the bucket name appears in the URL host or path.
|
|
|
|
// e.g. https://minio.example.com/bucket-name/object-key
|
|
|
|
// e.g. https://bucket.nyc3.digitaloceanspaces.com/folder/img.png
|
|
|
|
return url.host.startsWith(`${bucketName}.`) || url.pathname.includes(`/${bucketName}/`);
|
|
|
|
}
|
|
|
|
|
2023-02-20 12:18:37 +01:00
|
|
|
/**
|
|
|
|
* Parse the bucket name from a URL.
|
|
|
|
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
|
|
|
|
*
|
|
|
|
* @param {string} fileUrl - the URL to parse
|
|
|
|
* @returns {object} result
|
|
|
|
* @returns {string} result.bucket - the bucket name
|
2023-04-13 08:45:44 +02:00
|
|
|
* @returns {string} result.err - if any
|
2023-02-20 12:18:37 +01:00
|
|
|
*/
|
2023-05-31 11:26:44 +02:00
|
|
|
function getBucketFromAwsUrl(fileUrl: string): BucketInfo {
|
|
|
|
const url = new URL(fileUrl);
|
2023-02-20 12:18:37 +01:00
|
|
|
|
|
|
|
// S3://<bucket-name>/<key>
|
2023-05-31 11:26:44 +02:00
|
|
|
if (url.protocol === 's3:') {
|
|
|
|
const bucket = url.host;
|
2023-02-20 12:18:37 +01:00
|
|
|
|
|
|
|
if (!bucket) {
|
2023-05-31 11:26:44 +02:00
|
|
|
return { err: `Invalid S3 url: no bucket: ${url}` };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
return { bucket };
|
|
|
|
}
|
|
|
|
|
2023-05-31 11:26:44 +02:00
|
|
|
if (!url.host) {
|
|
|
|
return { err: `Invalid S3 url: no hostname: ${url}` };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
|
2023-05-31 11:26:44 +02:00
|
|
|
const matches = url.host.match(ENDPOINT_PATTERN);
|
2023-02-20 12:18:37 +01:00
|
|
|
if (!matches) {
|
2023-05-31 11:26:44 +02:00
|
|
|
return { err: `Invalid S3 url: hostname does not appear to be a valid S3 endpoint: ${url}` };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const prefix = matches[1];
|
|
|
|
// https://s3.amazonaws.com/<bucket-name>
|
|
|
|
if (!prefix) {
|
2023-05-31 11:26:44 +02:00
|
|
|
if (url.pathname === '/') {
|
2023-02-20 12:18:37 +01:00
|
|
|
return { bucket: null };
|
|
|
|
}
|
|
|
|
|
2023-05-31 11:26:44 +02:00
|
|
|
const index = url.pathname.indexOf('/', 1);
|
2023-02-20 12:18:37 +01:00
|
|
|
|
|
|
|
// https://s3.amazonaws.com/<bucket-name>
|
|
|
|
if (index === -1) {
|
2023-05-31 11:26:44 +02:00
|
|
|
return { bucket: url.pathname.substring(1) };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// https://s3.amazonaws.com/<bucket-name>/
|
2023-05-31 11:26:44 +02:00
|
|
|
if (index === url.pathname.length - 1) {
|
|
|
|
return { bucket: url.pathname.substring(1, index) };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// https://s3.amazonaws.com/<bucket-name>/key
|
2023-05-31 11:26:44 +02:00
|
|
|
return { bucket: url.pathname.substring(1, index) };
|
2023-02-20 12:18:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// https://<bucket-name>.s3.amazonaws.com/
|
|
|
|
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
|
|
}
|