This repository has been archived by the owner on May 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
136 lines (121 loc) · 4.61 KB
/
index.js
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
135
136
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
const {
S3Client,
WriteGetObjectResponseCommand,
} = require("@aws-sdk/client-s3");
// Get object from presigned URL
const axios = require("axios");
// Parse URL
const url = require("url");
// Image manipulation, including exif removal
const sharp = require("sharp");
// Read raw exif data
const exifReader = require("exif-reader");
// S3 client NodeJs SDK v3
const s3Client = new S3Client({ region: process.env.AWS_REGION });
const sharpSupportedTypes = [
"image/jpeg",
"image/png",
"image/webp",
"image/gif",
"image/avif",
"image/tiff",
"image/svg",
];
// WriteGetObjectResponseCommand must be returned to ObjectLambda
// otherwise OL waits until the Lambda timeout then returns an error to the client
async function returnObjectLambda({
requestRoute,
requestToken,
statusCode = 200,
additionalArgs,
}) {
const olResponseInput = {
RequestRoute: requestRoute,
RequestToken: requestToken,
StatusCode: statusCode,
...additionalArgs,
};
const olResponseCmd = new WriteGetObjectResponseCommand(olResponseInput);
await s3Client.send(olResponseCmd);
}
// expected to be called as part of an object lambda (event.getObjectContext)
// if the object is an image, returns either the image without exif or the exif data itself based on the flags
exports.handler = async (event) => {
// console.debug(`Event: ${JSON.stringify(event)}`);
// Input received from S3 Object Lambda
const objectGetContext = event.getObjectContext;
// route and token are used to send back the modified object
const requestRoute = objectGetContext.outputRoute;
const requestToken = objectGetContext.outputToken;
// Presigned URL for the requested object generated by S3 Object Lambda
const presignedUrl = objectGetContext.inputS3Url;
// S3 URL
const s3Url = event.userRequest?.url;
try {
// parse the S3 URL
const s3UrlParsed = url.parse(s3Url, true);
// Read the object from the presigned URL generated by S3 Object Lambda
const s3ObjRsp = await axios.get(presignedUrl, {
responseType: "arraybuffer",
});
// if the content type is not supported by Sharp, throw an error
const s3ContentType = s3ObjRsp.headers?.["content-type"];
if (!sharpSupportedTypes.includes(s3ContentType)) {
throw new Error(`File type ${s3ContentType} is not supported`);
}
// create sharp image object
const s3ImageBuffer = Buffer.from(s3ObjRsp.data);
const s3SharpImage = sharp(s3ImageBuffer);
// return exif if requesting it, otherwise return the image without exif data
const return_exif = s3UrlParsed.query?.showExif || false;
if (
typeof return_exif === "string" &&
return_exif.toLowerCase() === "true"
) {
const metadata = await s3SharpImage.metadata();
const exifText =
typeof metadata === "undefined" ||
typeof metadata?.exif === "undefined"
? {}
: exifReader(metadata.exif);
await returnObjectLambda({
requestRoute: requestRoute,
requestToken: requestToken,
additionalArgs: {
Body: JSON.stringify(exifText),
ContentType: "application/json",
},
});
return { StatusCode: 200 };
} else {
// rewrite the image to buffer, and return it to Object Lambda
// by default all metadata including exif is removed, unless withMetadata() is specified
const newImageBuffer = await s3SharpImage.toBuffer();
await returnObjectLambda({
requestRoute: requestRoute,
requestToken: requestToken,
additionalArgs: {
Body: newImageBuffer,
ContentType: s3ContentType,
},
});
return { StatusCode: 200 };
}
} catch (e) {
// return an error to Object Lambda if there is an exception
// an error response requires ContentLength = 0 to be sent
await returnObjectLambda({
requestRoute: requestRoute,
requestToken: requestToken,
statusCode: 500,
additionalArgs: {
ErrorCode: "InternalLambdaError",
ErrorMessage: e.message,
ContentLength: 0,
},
});
return { StatusCode: 500 };
}
};