```1<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2<html>
4    <meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
5    <script language="javascript">
6      // Split a string in 2 parts. The first is the leading number, if any,
7      // the second is the string following the numbers.
8      function splitNum(s) {
9        var results = new Array();
10        results[0] = 'None';
11        for (var i = 0; i < s.length; i++) {
12          var substr = s.substr(0, i+1)
13          if (isNaN(substr)) {
14            // Not a number anymore.
15            results[1] = s.substr(i)
16            break;
17          } else {
18            // This is a number. update the results.
19            results[0] = parseFloat(substr);
20          }
21        }
22        return results;
23      }
24
25      // Compare 2 strings using a custom alphanumerical algorithm.
26      // This is similar to a normal string sort, except that we sort
27      // first by leading digits, if any.
28      // For example:
29      //  100hello > 2goodbye
30      // Numbers anywhere else in the string are compared using the normal
31      // sort algorithm.
32      function alphanumCompare(a, b) {
33        var parsedA = splitNum(a);
34        var parsedB = splitNum(b);
35        var numA = parsedA[0];
36        var numB = parsedB[0];
37        var strA = parsedA[1];
38        var strB = parsedB[1];
39
40        if (isNaN(numA) == false && isNaN(numB) == false) {
42          if (numA < numB) return -1;
43          if (numA > numB) return 1;
44          // Identical. Fallback to string.
45          return (strA < strB) ? -1 : (strA > strB ? 1 : 0)
46        }
47
48        // If only one starts with a number, we start with that one as
49        // the lowest.
50        if (isNaN(numA) == false) return -1
51        if (isNaN(numB) == false) return 1
52
53        // They are both strings.
54        return (a < b) ? -1 : (a > b ? 1 : 0)
55      }
56    </script>
58  <body>
59    <script type="application/javascript">
60      String.prototype.startsWith = function(str) {
61        return (this.match('^' + str) == str)
62      }
63
64      // Helper function to retrieve the value of a GET query parameter.
65      // Greatly inspired from http://alturl.com/8rj7a
66      function getParameter(parameterName) {
67        // Add '=' to the parameter name (i.e. parameterName=value)
68        var parameterName = parameterName + '=';
69        var queryString = window.location.search.substring(1);
70        if (queryString.length <= 0) {
71          return '';
72        }
73
74        // Find the beginning of the string
75        begin = queryString.indexOf(parameterName);
76
77        // If the parameter name is not found, skip it, otherwise return the
78        // value.
79        if (begin == -1) {
80          return '';
81        }
82
83        // Add the length (integer) to the beginning.
84        begin += parameterName.length;
85
86        // Multiple parameters are separated by the '&' sign.
87        end = queryString.indexOf ('&', begin);
88
89        if (end == -1) {
90          end = queryString.length;
91        }
92
93        // Return the string.
94        return unescape(queryString.substring(begin, end));
95      }
96
97      // Given a tag and a node, returns the value for this tag on this node.
98      function getNodeValue(node, tag) {
99        return node.getElementsByTagName(tag)[0].firstChild.nodeValue;
100      }
101
102      // Displays the directory listing given the XML and path.
103      function displayList(xmlstring, root, path, pathRoot) {
105        document.write('<h1>Index of /' + path + '</h1>');
106
107        // Start the table for the results.
108        document.write('<table style="border-spacing:15px 0px;">');
109
110        var sortOrder = getParameter('sort');
111        var sortLink = location.pathname + '?path=' + path;
112        if (sortOrder != 'desc') {
114        }
115
116        // Display the table header.
117        document.write('<tr><th><img src="' + root + pathRoot +
118                       'icons/blank.gif" alt="[ICO]"></th>');
119        document.write('<th><a href="' + sortLink + '">Name</a></th>');
121        document.write('<th>Size</th>');
122        document.write('<th>Storage Class</th>');
123        document.write('<th>ETag</th></tr>');
124        document.write('<tr><th colspan="6"><hr></th></tr>');
125
126        // Display the 'go back' button.
127        if (path != '') {
128          var backpath = location.pathname;
129
130          // If there is more than one section delimited by '/' in the current
131          // path we truncate the last section and append the rest to backpath.
132          var delimiter = path.lastIndexOf('/');
133          if (delimiter >= 0) {
134            delimiter = path.substr(0, delimiter).lastIndexOf('/');
135            if (delimiter >= 0) {
136              backpath += '?path=';
137              backpath += path.substr(0, delimiter+1);
138            }
139          }
140
141          document.write('<tr><td valign="top"><img src="' + root + pathRoot +
142                         'icons/back.gif" alt="[DIR]"></td>');
143          document.write('<td><a href="');
144          document.write(backpath);
145          document.write('">Parent Directory</a></td>');
146          document.write('<td>&nbsp;</td>');
147          document.write('<td align="right">  - </td></tr>');
148        }
149
150        // Set up the variables.
151        var directories = new Array();
152        var files = new Array();
153
154        for (var iter = 0; iter < xmlstrings.length; iter++) {
155          var xmlstring = xmlstrings[iter];
156          // Parse the XML output.
157          var parser = new DOMParser();
158          var xmlDoc = parser.parseFromString(xmlstring, 'text/xml');
159
160          // Get the main element.
161          var results = xmlDoc.getElementsByTagName('ListBucketResult');
162
163          // Get all the directories.
164          var prefixes = results[0].getElementsByTagName('CommonPrefixes');
165          for (var i = 0; i < prefixes.length; i++) {
166            var prefix = getNodeValue(prefixes[i], 'Prefix');
167            directories.push(prefix.substr(path.length));
168        }
169
170          // Get all the files.
171          var contents = results[0].getElementsByTagName('Contents');
172          for (var i = 0; i < contents.length; i++) {
173            var obj = new Object();
174            obj.keyName = getNodeValue(contents[i], 'Key');
175            obj.lastModified = getNodeValue(contents[i], 'LastModified');
176            obj.eTag = getNodeValue(contents[i], 'ETag');
177            obj.size = getNodeValue(contents[i], 'Size');
178            files.push(obj);
179          }
180        }
181
182        files.sort(alphanumCompare);
183        directories.sort(alphanumCompare);
184
185        // Reverse the list for a descending sort.
186        if (sortOrder == 'desc') {
187          files.reverse();
188          directories.reverse();
189        }
190
191        // Display the directories.
192        for (var i = 0; i < directories.length; i++) {
193          var lnk = location.pathname.substr(0, location.pathname.indexOf('?'));
194          lnk += '?path=' + path + directories[i];
195
196          document.write('<tr>');
197          document.write('<td valign="top"><img src="' + root + pathRoot +
198                         'icons/folder.gif" alt="[DIR]"></td>');
199          document.write('<td><a href="' + lnk + '">' +
200                         directories[i].split('/')[0] + '</a></td>');
201          document.write('<td align="right">-</td>');
202          document.write('<td align="right">-</td>');
203          document.write('<td align="right">-</td>');
204          document.write('<td align="right">-</td>');
205          document.write('</tr>');
206        }
207
208        // Display the files.
209        for (var i = 0; i < files.length; i++) {
210          var link = root + files[i].keyName;
211          var filename = files[i].keyName.substr(path.length);
212          var size = files[i].size / 1024 / 1024;
213          var lastModified = files[i].lastModified.replace('T', ' ');
214          lastModified = lastModified.substr(0, lastModified.indexOf('.'));
215
216          // Remove the entries we don't want to show.
217          if (filename == '') {
218            continue;
219          }
220
221          if (filename.indexOf('\$folder\$') >= 0) {
222            continue;
223          }
224
225          // Display the row.
226          document.write('<tr>');
227          document.write('<td valign="top"><img src="' + root + pathRoot +
228                         'icons/binary.gif" alt="[DIR]"></td>');
229          document.write('<td><a href="' + link + '">' + filename +
230                         '</a></td>');
231          document.write('<td align="right">' + lastModified + '</td>');
232          document.write('<td align="right">' + size.toFixed(2) + 'MB</td>');
233          document.write('<td align="right"><pre>' +
234                         files[i].eTag.split('"')[1] + '</pre></td>');
235          document.write('</tr>');
236        }
237
238        // Close the table.
239        document.write('<tr><th colspan="6"><hr></th></tr>');
240        document.write('</table>');
241      }
242
243      var xmlstrings = new Array();
244
245      function fetchAndDisplay(marker) {
246        var path = getParameter('path');
247        var lastSlash = location.pathname.lastIndexOf("/");
248        var filename = location.pathname.substring(lastSlash + 1);
249        var firstSlash = location.pathname.indexOf("/", 1);
250        var root = location.pathname.substring(0, firstSlash + 1);
251        var pathRoot = location.pathname.substring(firstSlash + 1,
252                                                   lastSlash + 1);
253        if (!path) {
254          path = location.pathname.substring(firstSlash + 1, lastSlash + 1);
255        }
256
257        var markerParam = '';
258        if (marker != '') {
259          markerParam = '&marker=' + marker;
260        }
261
262        var http = new XMLHttpRequest();
263        http.open('GET',
264                  root + '?delimiter=/&prefix=' + path + markerParam,
265                  true);
267        http.send(null);
268        function useHttpResponse() {
269          if (http.readyState == 4) {
270            var xmlstring = http.responseText;
271            xmlstrings.push(xmlstring);
272
273            // Check if the data is truncated. if so, we need to request the
274            // rest.
275            var parser = new DOMParser();
276            var xmlDoc = parser.parseFromString(xmlstring, 'text/xml');
277
278            // Get the main element.
279            var results = xmlDoc.getElementsByTagName('ListBucketResult');
280
281            // Get IsTruncated.
282            var truncated = getNodeValue(results[0], 'IsTruncated');
283            var nextMarker = '';
284            if (truncated == 'true') {
285              nextMarker = getNodeValue(results[0], 'NextMarker');
286              fetchAndDisplay(nextMarker);
287            } else {
288              displayList(xmlstrings, root, path, pathRoot);
289            }
290          }
291        }
292      }
293
294      fetchAndDisplay('');
295    </script>
296  </body>
297</html>
298```