@@ -109,7 +109,7 @@ func New(opts *Options) *Handler {
109
109
}
110
110
111
111
mux := http .NewServeMux ()
112
- mux .Handle ("/bin/" , binHandler (opts .BinFS , newBinHashCache ( opts .BinHashes )))
112
+ mux .Handle ("/bin/" , binHandler (opts .BinFS , newBinMetadataCache ( opts . BinFS , opts .BinHashes )))
113
113
mux .Handle ("/" , http .FileServer (
114
114
http .FS (
115
115
// OnlyFiles is a wrapper around the file system that prevents directory
@@ -134,7 +134,7 @@ func New(opts *Options) *Handler {
134
134
return handler
135
135
}
136
136
137
- func binHandler (binFS http.FileSystem , binHashCache * binHashCache ) http.Handler {
137
+ func binHandler (binFS http.FileSystem , binMetadataCache * binMetadataCache ) http.Handler {
138
138
return http .StripPrefix ("/bin" , http .HandlerFunc (func (rw http.ResponseWriter , r * http.Request ) {
139
139
// Convert underscores in the filename to hyphens. We eventually want to
140
140
// change our hyphen-based filenames to underscores, but we need to
@@ -156,7 +156,7 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
156
156
return
157
157
}
158
158
159
- f , err := binFS . Open (name )
159
+ metadata , err := binMetadataCache . getMetadata (name )
160
160
if xerrors .Is (err , os .ErrNotExist ) {
161
161
http .NotFound (rw , r )
162
162
return
@@ -165,7 +165,6 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
165
165
http .Error (rw , err .Error (), http .StatusInternalServerError )
166
166
return
167
167
}
168
- defer f .Close ()
169
168
170
169
// http.FileServer will not set Content-Length when performing chunked
171
170
// transport encoding, which is used for large files like our binaries
@@ -175,23 +174,13 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
175
174
// value of this header with the amount of bytes written to disk after
176
175
// decompression to show progress. Without this, they cannot show
177
176
// progress without disabling compression.
178
- stat , err := f .Stat ()
179
- if err != nil {
180
- http .Error (rw , err .Error (), http .StatusInternalServerError )
181
- return
182
- }
177
+ //
183
178
// There isn't really a spec for a length header for the "inner" content
184
179
// size, but some nginx modules use this header.
185
- rw .Header ().Set ("X-Original-Content-Length" , fmt .Sprintf ("%d" , stat . Size () ))
180
+ rw .Header ().Set ("X-Original-Content-Length" , fmt .Sprintf ("%d" , metadata . sizeBytes ))
186
181
187
- // Get and set ETag header.
188
- hash , err := binHashCache .getHash (name , f )
189
- if err != nil {
190
- http .Error (rw , err .Error (), http .StatusInternalServerError )
191
- return
192
- }
193
- // ETag header needs to be quoted.
194
- rw .Header ().Set ("ETag" , fmt .Sprintf (`%q` , hash ))
182
+ // Get and set ETag header. Must be quoted.
183
+ rw .Header ().Set ("ETag" , fmt .Sprintf (`%q` , metadata .sha1Hash ))
195
184
196
185
// http.FileServer will see the ETag header and automatically handle
197
186
// If-Match and If-None-Match headers on the request properly.
@@ -979,59 +968,95 @@ func RenderStaticErrorPage(rw http.ResponseWriter, r *http.Request, data ErrorPa
979
968
}
980
969
}
981
970
982
- type binHashCache struct {
983
- hashes map [string ]string
984
- mut sync.RWMutex
985
- sf singleflight.Group
986
- sem chan struct {}
971
+ type binMetadata struct {
972
+ sizeBytes int64 // -1 if not known yet
973
+ // SHA1 was chosen because it's fast to compute and reasonable for
974
+ // determining if a file has changed. The ETag is not used a security
975
+ // measure.
976
+ sha1Hash string // always set if in the cache
977
+ }
978
+
979
+ type binMetadataCache struct {
980
+ binFS http.FileSystem
981
+ originalHashes map [string ]string
982
+
983
+ metadata map [string ]binMetadata
984
+ mut sync.RWMutex
985
+ sf singleflight.Group
986
+ sem chan struct {}
987
987
}
988
988
989
- func newBinHashCache (binHashes map [string ]string ) * binHashCache {
990
- b := & binHashCache {
991
- hashes : make (map [string ]string , len (binHashes )),
992
- mut : sync.RWMutex {},
993
- sf : singleflight.Group {},
994
- sem : make (chan struct {}, 4 ),
989
+ func newBinMetadataCache (binFS http.FileSystem , binSha1Hashes map [string ]string ) * binMetadataCache {
990
+ b := & binMetadataCache {
991
+ binFS : binFS ,
992
+ originalHashes : make (map [string ]string , len (binSha1Hashes )),
993
+
994
+ metadata : make (map [string ]binMetadata , len (binSha1Hashes )),
995
+ mut : sync.RWMutex {},
996
+ sf : singleflight.Group {},
997
+ sem : make (chan struct {}, 4 ),
995
998
}
996
- // Make a copy since we're gonna be mutating it.
997
- for k , v := range binHashes {
998
- b .hashes [k ] = v
999
+
1000
+ // Previously we copied binSha1Hashes to the cache immediately. Since we now
1001
+ // read other information like size from the file, we can't do that. Instead
1002
+ // we copy the hashes to a different map that will be used to populate the
1003
+ // cache on the first request.
1004
+ for k , v := range binSha1Hashes {
1005
+ b .originalHashes [k ] = v
999
1006
}
1000
1007
1001
1008
return b
1002
1009
}
1003
1010
1004
- func (b * binHashCache ) getHash (name string , f http. File ) (string , error ) {
1011
+ func (b * binMetadataCache ) getMetadata (name string ) (binMetadata , error ) {
1005
1012
b .mut .RLock ()
1006
- hash , ok := b .hashes [name ]
1013
+ metadata , ok := b .metadata [name ]
1007
1014
b .mut .RUnlock ()
1008
1015
if ok {
1009
- return hash , nil
1016
+ return metadata , nil
1010
1017
}
1011
1018
1012
1019
// Avoid DOS by using a pool, and only doing work once per file.
1013
- v , err , _ := b .sf .Do (name , func () (interface {} , error ) {
1020
+ v , err , _ := b .sf .Do (name , func () (any , error ) {
1014
1021
b .sem <- struct {}{}
1015
1022
defer func () { <- b .sem }()
1016
1023
1017
- h := sha1 .New () //#nosec // Not used for cryptography.
1018
- _ , err := io .Copy (h , f )
1024
+ f , err := b .binFS .Open (name )
1019
1025
if err != nil {
1020
- return "" , err
1026
+ return binMetadata {}, err
1027
+ }
1028
+ defer f .Close ()
1029
+
1030
+ var metadata binMetadata
1031
+
1032
+ stat , err := f .Stat ()
1033
+ if err != nil {
1034
+ return binMetadata {}, err
1035
+ }
1036
+ metadata .sizeBytes = stat .Size ()
1037
+
1038
+ if hash , ok := b .originalHashes [name ]; ok {
1039
+ metadata .sha1Hash = hash
1040
+ } else {
1041
+ h := sha1 .New () //#nosec // Not used for cryptography.
1042
+ _ , err := io .Copy (h , f )
1043
+ if err != nil {
1044
+ return binMetadata {}, err
1045
+ }
1046
+ metadata .sha1Hash = hex .EncodeToString (h .Sum (nil ))
1021
1047
}
1022
1048
1023
- hash := hex .EncodeToString (h .Sum (nil ))
1024
1049
b .mut .Lock ()
1025
- b .hashes [name ] = hash
1050
+ b .metadata [name ] = metadata
1026
1051
b .mut .Unlock ()
1027
- return hash , nil
1052
+ return metadata , nil
1028
1053
})
1029
1054
if err != nil {
1030
- return "" , err
1055
+ return binMetadata {} , err
1031
1056
}
1032
1057
1033
1058
//nolint:forcetypeassert
1034
- return strings . ToLower ( v .(string ) ), nil
1059
+ return v .(binMetadata ), nil
1035
1060
}
1036
1061
1037
1062
func applicationNameOrDefault (cfg codersdk.AppearanceConfig ) string {
0 commit comments