Skip to content

Instantly share code, notes, and snippets.

@nabilfreeman
Last active March 7, 2023 20:31
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save nabilfreeman/c0dec56f149fcfb4a8e704ae672b204e to your computer and use it in GitHub Desktop.
Save nabilfreeman/c0dec56f149fcfb4a8e704ae672b204e to your computer and use it in GitHub Desktop.
Redirect to trailing slashes on CloudFront with AWS Lambda. (all this because S3 uses 302 redirects instead of 301)
'use strict';
const path = require('path')
const redirect = new_url => {
return {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{
key: 'Location',
value: new_url,
}],
},
};
};
exports.handler = (event, context, callback) => {
//get request object
const { request } = event.Records[0].cf
//if this url has uppercase letters in it, we need to direct to the all-lowercase version.
const regex = /[A-Z]/g;
//if we get a truthy result from the regex match, we need to 301 to the lowercase url.
if(request.uri.match(regex)){
//lowercase the url.
let lowercase_url = `${request.uri.toLowerCase()}/`;
if(request.querystring) {
lowercase_url += `?${request.querystring}`;
}
//debug.
console.log(`Rewriting ${request.uri} to ${lowercase_url}...`);
//create HTTP redirect...
return callback(null, redirect(lowercase_url));
}
//we need to determine if this request has an extension.
const extension = path.extname(request.uri);
//path.extname returns an empty string when there's no extension.
//if there is an extension on this request, continue without doing anything!
if(extension && extension.length > 0){
console.log(`Skipping ${request.uri} because it has a file extension.`);
return callback(null, request);
}
//there is no extension, so that means we want to add a trailing slash to the url.
//let's check if the last character is a slash.
const last_character = request.uri.slice(-1);
//if there is already a trailing slash, return.
if(last_character === "/"){
console.log(`Skipping ${request.uri} because it already has a trailing slash.`);
return callback(null, request);
}
//add a trailing slash.
let trailing_slash_url = `${request.uri}/`;
if(request.querystring) {
trailing_slash_url += `?${request.querystring}`;
}
//debug.
console.log(`Rewriting ${request.uri} to ${trailing_slash_url}...`);
//create HTTP redirect...
return callback(null, redirect(trailing_slash_url));
};
@hacklschorsch
Copy link

hacklschorsch commented Dec 2, 2021

I believe that will give you an open redirect vulnerability if someone does, for example:

http://your-host//example.com/index

Changing

    const new_url = `${url}/`;

to

    const new_url = `//${request.host}/${request.uri}/`;

or so should mitigate that IIANM

@nabilfreeman
Copy link
Author

Thanks @hacklschorsch - great spot

@nabilfreeman
Copy link
Author

Actually, I couldn't get this working because CloudFront does not expose the original host in a consistent way - so if you are using a custom domain against your distribution, you will get undefined in your 301. So I've put back.

To future readers: If you are just using a x.cloudfront.net domain, check out the revisions and pick out a previous one with the above fix in it.

Also, I added some code to ensure that paths get lowercased.

@jeremielp
Copy link

Interested in building a script like yours.

What is the consistency issue you are seeing?
Have you tried with CloudFront functions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment