@@ -5,7 +5,6 @@ const gcs = require('@google-cloud/storage')();
55const path = require ( 'path' ) ;
66
77const gcsBucketId = `${ process . env . GCLOUD_PROJECT } .appspot.com` ;
8- const LOCAL_TMP_FOLDER = '/tmp/' ;
98
109const BROWSER_CACHE_DURATION = 300 ;
1110const CDN_CACHE_DURATION = 600 ;
@@ -22,7 +21,6 @@ function sendStoredFile(request, response) {
2221 const bucket = gcs . bucket ( gcsBucketId ) ;
2322
2423 let downloadSource ;
25- let downloadDestination ;
2624 let fileName ;
2725
2826 if ( isDocsPath && filePathSegments . length === 2 ) {
@@ -32,43 +30,142 @@ function sendStoredFile(request, response) {
3230 fileName = lastSegment ;
3331 }
3432
33+ if ( ! fileName ) {
34+ //Root
35+ return getDirectoryListing ( '/' ) . catch ( sendErrorResponse ) ;
36+ }
37+
3538 downloadSource = path . join . apply ( null , filePathSegments ) ;
36- downloadDestination = `${ LOCAL_TMP_FOLDER } ${ fileName } ` ;
3739
38- downloadAndSend ( downloadSource , downloadDestination ) . catch ( error => {
40+ downloadAndSend ( downloadSource ) . catch ( error => {
3941 if ( isDocsPath && error . code === 404 ) {
4042 fileName = 'index.html' ;
4143 filePathSegments = [ version , 'docs' , fileName ] ;
4244 downloadSource = path . join . apply ( null , filePathSegments ) ;
43- downloadDestination = `${ LOCAL_TMP_FOLDER } ${ fileName } ` ;
4445
45- return downloadAndSend ( downloadSource , downloadDestination ) ;
46+ return downloadAndSend ( downloadSource ) ;
4647 }
4748
4849 return Promise . reject ( error ) ;
4950 } ) . catch ( error => {
50- let message = 'General error' ;
51+
52+ // If file not found, try the path as a directory
53+ return error . code === 404 ? getDirectoryListing ( request . path . slice ( 1 ) ) : Promise . reject ( error ) ;
54+ } ) . catch ( sendErrorResponse ) ;
55+
56+ function downloadAndSend ( downloadSource ) {
57+
58+ const file = bucket . file ( downloadSource ) ;
59+
60+ return file . getMetadata ( ) . then ( data => {
61+ return new Promise ( ( resolve , reject ) => {
62+
63+ const readStream = file . createReadStream ( )
64+ . on ( 'error' , error => {
65+ reject ( error ) ;
66+ } )
67+ . on ( 'response' , ( ) => {
68+ resolve ( response ) ;
69+ } ) ;
70+
71+ response
72+ . status ( 200 )
73+ . set ( {
74+ 'Content-Type' : data [ 0 ] . contentType ,
75+ 'Cache-Control' : `public, max-age=${ BROWSER_CACHE_DURATION } , s-maxage=${ CDN_CACHE_DURATION } `
76+ } ) ;
77+
78+ readStream . pipe ( response ) ;
79+ } ) ;
80+
81+ } ) ;
82+ }
83+
84+ function sendErrorResponse ( error ) {
85+ let code = 500 ;
86+ let message = `General error. Please try again later.
87+ If the error persists, please create an issue in the
88+ <a href="https://github.com/angular/angular.js/issues">AngularJS Github repository</a>` ;
89+
5190 if ( error . code === 404 ) {
52- if ( fileName . split ( '.' ) . length === 1 ) {
53- message = 'Directory listing is not supported' ;
54- } else {
55- message = 'File not found' ;
56- }
91+ message = 'File or directory not found' ;
92+ code = 404 ;
5793 }
5894
59- return response . status ( error . code ) . send ( message ) ;
60- } ) ;
95+ return response . status ( code ) . send ( message ) ;
96+ }
97+
98+ function getDirectoryListing ( path ) {
99+ if ( ! path . endsWith ( '/' ) ) path += '/' ;
61100
62- function downloadAndSend ( downloadSource , downloadDestination ) {
63- return bucket . file ( downloadSource ) . download ( {
64- destination : downloadDestination
65- } ) . then ( ( ) => {
66- return response . status ( 200 )
101+ const getFilesOptions = {
102+ delimiter : '/' ,
103+ autoPaginate : false
104+ } ;
105+
106+ if ( path !== '/' ) getFilesOptions . prefix = path ;
107+
108+ let fileList = [ ] ;
109+ let directoryList = [ ] ;
110+
111+ return getContent ( getFilesOptions ) . then ( ( ) => {
112+ let contentList = '' ;
113+
114+ directoryList . forEach ( directoryPath => {
115+ const dirName = directoryPath . split ( '/' ) . reverse ( ) [ 1 ] ;
116+ contentList += `<a href="${ dirName } /">${ dirName } /</a><br>` ;
117+ } ) ;
118+
119+ fileList . forEach ( file => {
120+ const fileName = file . metadata . name . split ( '/' ) . pop ( ) ;
121+ contentList += `<a href="${ fileName } ">${ fileName } </a><br>` ;
122+ } ) ;
123+
124+ // A trailing slash in the base creates correct relative links when the url is accessed
125+ // without trailing slash
126+ const base = request . originalUrl . endsWith ( '/' ) ? request . originalUrl : request . originalUrl + '/' ;
127+
128+ let directoryListing = `
129+ <base href="${ base } ">
130+ <h1>Index of ${ path } </h1>
131+ <hr>
132+ <pre>${ contentList } </pre>` ;
133+
134+ return response
135+ . status ( 200 )
67136 . set ( {
68137 'Cache-Control' : `public, max-age=${ BROWSER_CACHE_DURATION } , s-maxage=${ CDN_CACHE_DURATION } `
69138 } )
70- . sendFile ( downloadDestination ) ;
139+ . send ( directoryListing ) ;
71140 } ) ;
141+
142+ function getContent ( options ) {
143+ return bucket . getFiles ( options ) . then ( data => {
144+ const files = data [ 0 ] ;
145+ const nextQuery = data [ 1 ] ;
146+ const apiResponse = data [ 2 ] ;
147+
148+ if ( ! files . length && ( ! apiResponse || ! apiResponse . prefixes ) ) {
149+ return Promise . reject ( {
150+ code : 404
151+ } ) ;
152+ }
153+
154+ fileList = fileList . concat ( files ) ;
155+
156+ if ( apiResponse && apiResponse . prefixes ) {
157+ directoryList = directoryList . concat ( apiResponse . prefixes ) ;
158+ }
159+
160+ if ( nextQuery ) {
161+ // If the results are paged, get the next page
162+ return getContent ( nextQuery ) ;
163+ }
164+
165+ return true ;
166+ } ) ;
167+
168+ }
72169 }
73170}
74171
0 commit comments