@@ -5,7 +5,6 @@ const gcs = require('@google-cloud/storage')();
5
5
const path = require ( 'path' ) ;
6
6
7
7
const gcsBucketId = `${ process . env . GCLOUD_PROJECT } .appspot.com` ;
8
- const LOCAL_TMP_FOLDER = '/tmp/' ;
9
8
10
9
const BROWSER_CACHE_DURATION = 300 ;
11
10
const CDN_CACHE_DURATION = 600 ;
@@ -22,7 +21,6 @@ function sendStoredFile(request, response) {
22
21
const bucket = gcs . bucket ( gcsBucketId ) ;
23
22
24
23
let downloadSource ;
25
- let downloadDestination ;
26
24
let fileName ;
27
25
28
26
if ( isDocsPath && filePathSegments . length === 2 ) {
@@ -32,43 +30,142 @@ function sendStoredFile(request, response) {
32
30
fileName = lastSegment ;
33
31
}
34
32
33
+ if ( ! fileName ) {
34
+ //Root
35
+ return getDirectoryListing ( '/' ) . catch ( sendErrorResponse ) ;
36
+ }
37
+
35
38
downloadSource = path . join . apply ( null , filePathSegments ) ;
36
- downloadDestination = `${ LOCAL_TMP_FOLDER } ${ fileName } ` ;
37
39
38
- downloadAndSend ( downloadSource , downloadDestination ) . catch ( error => {
40
+ downloadAndSend ( downloadSource ) . catch ( error => {
39
41
if ( isDocsPath && error . code === 404 ) {
40
42
fileName = 'index.html' ;
41
43
filePathSegments = [ version , 'docs' , fileName ] ;
42
44
downloadSource = path . join . apply ( null , filePathSegments ) ;
43
- downloadDestination = `${ LOCAL_TMP_FOLDER } ${ fileName } ` ;
44
45
45
- return downloadAndSend ( downloadSource , downloadDestination ) ;
46
+ return downloadAndSend ( downloadSource ) ;
46
47
}
47
48
48
49
return Promise . reject ( error ) ;
49
50
} ) . 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
+
51
90
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 ;
57
93
}
58
94
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 += '/' ;
61
100
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 )
67
136
. set ( {
68
137
'Cache-Control' : `public, max-age=${ BROWSER_CACHE_DURATION } , s-maxage=${ CDN_CACHE_DURATION } `
69
138
} )
70
- . sendFile ( downloadDestination ) ;
139
+ . send ( directoryListing ) ;
71
140
} ) ;
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
+ }
72
169
}
73
170
}
74
171
0 commit comments