@@ -217,6 +217,16 @@ func (s *LocalDirV1) handleV1Query(w http.ResponseWriter, r *http.Request) {
217217 }
218218 defer catalogFile .Close ()
219219
220+ w .Header ().Set ("Last-Modified" , catalogStat .ModTime ().UTC ().Format (TimeFormat ))
221+ switch checkIfModifiedSince (r , w , catalogStat .ModTime ()) {
222+ case condNone :
223+ return
224+ case condFalse :
225+ w .WriteHeader (http .StatusNotModified )
226+ return
227+ case condTrue :
228+ }
229+
220230 schema := r .URL .Query ().Get ("schema" )
221231 pkg := r .URL .Query ().Get ("package" )
222232 name := r .URL .Query ().Get ("name" )
@@ -298,3 +308,71 @@ func (s *LocalDirV1) getIndex(catalog string) (*index, error) {
298308 }
299309 return idx .(* index ), nil
300310}
311+
312+ type condResult int
313+
314+ const (
315+ condNone condResult = iota
316+ condTrue
317+ condFalse
318+ )
319+
320+ // Copyright 2009 The Go Authors. All rights reserved.
321+ // Use of this source code is governed by a BSD-style
322+ // license that can be found in the LICENSE file.
323+ //
324+ // Source: Originally from Go's net/http/fs.go
325+ // https://cs.opensource.google/go/go/+/master:src/net/http/fs.go
326+ func checkIfModifiedSince (r * http.Request , w http.ResponseWriter , modtime time.Time ) condResult {
327+ ims := r .Header .Get ("If-Modified-Since" )
328+ if ims == "" || isZeroTime (modtime ) {
329+ return condTrue
330+ }
331+ t , err := ParseTime (ims )
332+ if err != nil {
333+ httpError (w , err )
334+ return condNone
335+ }
336+ // The Last-Modified header truncates sub-second precision so
337+ // the modtime needs to be truncated too.
338+ modtime = modtime .Truncate (time .Second )
339+ if modtime .Compare (t ) <= 0 {
340+ return condFalse
341+ }
342+ return condTrue
343+ }
344+
345+ // TimeFormat is the time format to use when generating times in HTTP
346+ // headers. It is like [time.RFC1123] but hard-codes GMT as the time
347+ // zone. The time being formatted must be in UTC for Format to
348+ // generate the correct format.
349+ //
350+ // For parsing this time format, see [ParseTime].
351+ const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
352+
353+ var (
354+ unixEpochTime = time .Unix (0 , 0 )
355+ timeFormats = []string {
356+ TimeFormat ,
357+ time .RFC850 ,
358+ time .ANSIC ,
359+ }
360+ )
361+
362+ // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
363+ func isZeroTime (t time.Time ) bool {
364+ return t .IsZero () || t .Equal (unixEpochTime )
365+ }
366+
367+ // ParseTime parses a time header (such as the Date: header),
368+ // trying each of the three formats allowed by HTTP/1.1:
369+ // [TimeFormat], [time.RFC850], and [time.ANSIC].
370+ func ParseTime (text string ) (t time.Time , err error ) {
371+ for _ , layout := range timeFormats {
372+ t , err = time .Parse (layout , text )
373+ if err == nil {
374+ return
375+ }
376+ }
377+ return
378+ }
0 commit comments