Skip to content

Instantly share code, notes, and snippets.

@lyoshenka
Last active October 2, 2019 13:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lyoshenka/39561b69ebfc95c0364974b5e462da8c to your computer and use it in GitHub Desktop.
Save lyoshenka/39561b69ebfc95c0364974b5e462da8c to your computer and use it in GitHub Desktop.
Upload this file to an s3 bucket with static website hosting enabled to get a directory listing of the bucket contents. Example: http://build.lbry.io
<!DOCTYPE html>
<html>
<head>
<title>Files</title>
</head>
<body>
<div id="navigation"></div>
<div id="listing"></div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript">
/**
* Modified from https://github.com/rufuspollock/s3-bucket-listing
*/
var BUCKET_URL = 'https://lbrycrd-snapshots.s3.amazonaws.com';
var BUCKET_WEBSITE_URL = 'https://lbrycrd-snapshots.s3-website.us-east-2.amazonaws.com';
var S3B_SORT = 'LBRY';
var EXCLUDE_FILE = 's3dir.html';
</script>
<script type="text/javascript">
if (typeof AUTO_TITLE != 'undefined' && AUTO_TITLE == true) {
document.title = location.hostname;
}
if (typeof S3_REGION != 'undefined') {
var BUCKET_URL = 'http://' + location.hostname + '.' + S3_REGION + '.amazonaws.com'; // e.g. just 's3' for us-east-1 region
var BUCKET_WEBSITE_URL = location.protocol + '//' + location.hostname;
}
if (typeof S3BL_IGNORE_PATH == 'undefined' || S3BL_IGNORE_PATH != true) {
var S3BL_IGNORE_PATH = false;
}
if (typeof BUCKET_URL == 'undefined') {
var BUCKET_URL = location.protocol + '//' + location.hostname;
}
if (typeof BUCKET_NAME != 'undefined') {
// if bucket_url does not start with bucket_name,
// assume path-style url
if (!~BUCKET_URL.indexOf(location.protocol + '//' + BUCKET_NAME)) {
BUCKET_URL += '/' + BUCKET_NAME;
}
}
if (typeof BUCKET_WEBSITE_URL == 'undefined') {
var BUCKET_WEBSITE_URL = BUCKET_URL;
}
if (typeof S3B_ROOT_DIR == 'undefined') {
var S3B_ROOT_DIR = '';
}
if (typeof S3B_SORT == 'undefined') {
var S3B_SORT = 'DEFAULT';
}
if (BUCKET_WEBSITE_URL.startsWith('https') && window.location.protocol !== "https:") {
window.location.replace('https:' + window.location.href.substring(window.location.protocol.length));
}
jQuery(function($) { getS3Data(); });
function cmp(a, b, reverse) {
return (a > b ? 1 : (a < b ? -1 : 0)) * (reverse ? -1 : 1);
}
function sortFunction(a, b) {
switch (S3B_SORT) {
case "OLD2NEW":
return a.LastModified > b.LastModified ? 1 : -1;
case "NEW2OLD":
return a.LastModified < b.LastModified ? 1 : -1;
case "A2Z":
return a.Key < b.Key ? 1 : -1;
case "Z2A":
return a.Key > b.Key ? 1 : -1;
case "BIG2SMALL":
return a.Size < b.Size ? 1 : -1;
case "SMALL2BIG":
return a.Size > b.Size ? 1 : -1;
case "LBRY":
return cmp(a.LastModified,b.LastModified,true) || cmp(a.Key,b.Key,true);
}
}
function getS3Data(marker, html) {
var s3_rest_url = createS3QueryUrl(marker);
// set loading notice
$('#listing')
.html('loading...');
$.get(s3_rest_url)
.done(function(data) {
// clear loading notice
$('#listing').html('');
var xml = $(data);
var info = getInfoFromS3Data(xml);
// Slight modification by FuzzBall03
// This will sort your file listing based on var S3B_SORT
// See url for example:
// http://esp-link.s3-website-us-east-1.amazonaws.com/
if (S3B_SORT != 'DEFAULT') {
var sortedFiles = info.files;
sortedFiles.sort(sortFunction);
info.files = sortedFiles;
var sortedDirs = info.directories;
sortedDirs.sort(sortFunction);
info.directories = sortedDirs;
}
buildNavigation(info);
html = typeof html !== 'undefined' ? prepareTable(info) + html :
prepareTable(info);
if (info.nextMarker != "null") {
getS3Data(info.nextMarker, html);
} else {
document.getElementById('listing').innerHTML =
'<pre>' + html + '</pre>';
}
})
.fail(function(error) {
console.error(error);
$('#listing').html('<strong>Error: ' + error + '</strong>');
});
}
function buildNavigation(info) {
var root = S3BL_IGNORE_PATH == false ?
'<a href="/">' + BUCKET_WEBSITE_URL + '</a> / ' :
'<a href="?prefix=">' + BUCKET_WEBSITE_URL + '</a> / ';
if (info.prefix) {
var processedPathSegments = '';
var content = $.map(info.prefix.split('/'), function(pathSegment) {
processedPathSegments = processedPathSegments + encodeURIComponent(pathSegment) + '/';
return S3BL_IGNORE_PATH == false ?
'<a href="/' + processedPathSegments + '">' + pathSegment + '</a>' :
'<a href="?prefix=' + processedPathSegments + '">' + pathSegment + '</a>';
});
$('#navigation').html(root + content.join(' / '));
} else {
$('#navigation').html(root);
}
}
function createS3QueryUrl(marker) {
var s3_rest_url = BUCKET_URL;
s3_rest_url += '?delimiter=/';
//
// Handling paths and prefixes:
//
// 1. S3BL_IGNORE_PATH = false
// Uses the pathname
// {bucket}/{path} => prefix = {path}
//
// 2. S3BL_IGNORE_PATH = true
// Uses ?prefix={prefix}
//
// Why both? Because we want classic directory style listing in normal
// buckets but also allow deploying to non-buckets
//
var rx = '.*[?&]prefix=' + S3B_ROOT_DIR + '([^&]+)(&.*)?$';
var prefix = '';
if (S3BL_IGNORE_PATH == false) {
var prefix = location.pathname.replace(/^\//, S3B_ROOT_DIR);
}
var match = location.search.match(rx);
if (match) {
prefix = S3B_ROOT_DIR + match[1];
} else {
if (S3BL_IGNORE_PATH) {
var prefix = S3B_ROOT_DIR;
}
}
if (prefix) {
// make sure we end in /
var prefix = prefix.replace(/\/$/, '') + '/';
s3_rest_url += '&prefix=' + prefix;
}
if (marker) {
s3_rest_url += '&marker=' + marker;
}
return s3_rest_url;
}
function getInfoFromS3Data(xml) {
var files = $.map(xml.find('Contents'), function(item) {
item = $(item);
// clang-format off
return {
Key: item.find('Key').text(),
LastModified: item.find('LastModified').text(),
Size: bytesToHumanReadable(item.find('Size').text()),
Type: 'file'
}
// clang-format on
});
var directories = $.map(xml.find('CommonPrefixes'), function(item) {
item = $(item);
// clang-format off
return {
Key: item.find('Prefix').text(),
LastModified: '',
Size: '0',
Type: 'directory'
}
// clang-format on
});
if ($(xml.find('IsTruncated')[0]).text() == 'true') {
var nextMarker = $(xml.find('NextMarker')[0]).text();
} else {
var nextMarker = null;
}
// clang-format off
return {
files: files,
directories: directories,
prefix: $(xml.find('Prefix')[0]).text(),
nextMarker: encodeURIComponent(nextMarker)
}
// clang-format on
}
// info is object like:
// {
// files: ..
// directories: ..
// prefix: ...
// }
function prepareTable(info) {
var files = info.files.concat(info.directories), prefix = info.prefix;
var cols = [45, 30, 15];
var content = [];
content.push(padRight('Last Modified', cols[1]) + ' ' +
padRight('Size', cols[2]) + 'Key \n');
content.push(new Array(cols[0] + cols[1] + cols[2] + 4).join('-') + '\n');
// add ../ at the start of the dir listing, unless we are already at root dir
if (prefix && prefix !== S3B_ROOT_DIR) {
var up = prefix.replace(/\/$/, '').split('/').slice(0, -1).concat('').join(
'/'), // one directory up
item =
{
Key: up,
LastModified: '',
Size: '',
keyText: '../',
href: S3BL_IGNORE_PATH ? '?prefix=' + up : '../'
},
row = renderRow(item, cols);
content.push(row + '\n');
}
jQuery.each(files, function(idx, item) {
// strip off the prefix
item.keyText = item.Key.substring(prefix.length);
if (item.Type === 'directory') {
if (S3BL_IGNORE_PATH) {
item.href = location.protocol + '//' + location.hostname +
location.pathname + '?prefix=' + item.Key;
} else {
item.href = item.keyText;
}
} else {
item.href = BUCKET_WEBSITE_URL + '/' + encodeURIComponent(item.Key);
item.href = item.href.replace(/%2F/g, '/');
}
var row = renderRow(item, cols);
if (typeof EXCLUDE_FILE == 'undefined' || EXCLUDE_FILE != item.Key)
content.push(row + '\n');
});
return content.join('');
}
function renderRow(item, cols) {
var row = '';
row += padRight(item.LastModified, cols[1]) + ' ';
row += padRight(item.Size, cols[2]);
row += '<a href="' + item.href + '">' + item.keyText + '</a>';
return row;
}
function padRight(padString, length) {
var str = padString.slice(0, length - 3);
if (padString.length > str.length) {
str += '...';
}
while (str.length < length) {
str = str + ' ';
}
return str;
}
function bytesToHumanReadable(sizeInBytes) {
var i = -1;
var units = [' kB', ' MB', ' GB'];
do {
sizeInBytes = sizeInBytes / 1024;
i++;
} while (sizeInBytes > 1024);
return Math.max(sizeInBytes, 0.1).toFixed(1) + units[i];
}
</script>
</body>
</html>

Setup

  • create s3 bucket
  • make it publicly-readable
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": [
                "arn:aws:s3:::BUCKETNAME"
            ]
        },        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": [
                "arn:aws:s3:::BUCKETNAME",
                "arn:aws:s3:::BUCKETNAME/*"
            ]
        }
    ]
}
  • set CORS configuration to enable CORS
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
  • enable static website hosting
    • set index document and error document to s3dir.html
  • edit s3dir.html to set config variables at the top of the file
    • BUCKET_URL is the REST endpoint (e.g. https://BUCKETNAME.s3.amazonaws.com)
    • BUCKET_WEBSITE_URL is the web url (eg https://build.lbry.io or https://BUCKETNAME.s3-website.us-east-2.amazonaws.com).
    • S3B_SORT is how to order the files in the dir listing
    • EXCLUDE_FILE is the name of the dir-listing file
    • set the <title> if you wish
  • upload s3dir.html to the bucket
  • go to the site to see a listing of the files in the bucket

note: if you use a custom domain and you're getting an XML response when you go to the homepage, you may have pointed your DNS record at the REST endpoint instead of the web endpoint.

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