+22
-2
appview/repo/repo.go
+22
-2
appview/repo/repo.go
···
612
if !rp.config.Core.Dev {
613
protocol = "https"
614
}
615
blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath)
616
-
resp, err := http.Get(blobURL)
617
if err != nil {
618
-
log.Println("failed to reach knotserver:", err)
619
rp.pages.Error503(w)
620
return
621
}
622
defer resp.Body.Close()
623
624
if resp.StatusCode != http.StatusOK {
625
log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
···
612
if !rp.config.Core.Dev {
613
protocol = "https"
614
}
615
+
616
blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath)
617
+
618
+
req, err := http.NewRequest("GET", blobURL, nil)
619
if err != nil {
620
+
log.Println("failed to create request", err)
621
+
return
622
+
}
623
+
624
+
// forward the If-None-Match header
625
+
if clientETag := r.Header.Get("If-None-Match"); clientETag != "" {
626
+
req.Header.Set("If-None-Match", clientETag)
627
+
}
628
+
629
+
client := &http.Client{}
630
+
resp, err := client.Do(req)
631
+
if err != nil {
632
+
log.Println("failed to reach knotserver", err)
633
rp.pages.Error503(w)
634
return
635
}
636
defer resp.Body.Close()
637
+
638
+
// forward 304 not modified
639
+
if resp.StatusCode == http.StatusNotModified {
640
+
w.WriteHeader(http.StatusNotModified)
641
+
return
642
+
}
643
644
if resp.StatusCode != http.StatusOK {
645
log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
+12
-7
knotserver/routes.go
+12
-7
knotserver/routes.go
···
286
mimeType = "image/svg+xml"
287
}
288
289
// allow image, video, and text/plain files to be served directly
290
switch {
291
-
case strings.HasPrefix(mimeType, "image/"):
292
-
// allowed
293
-
case strings.HasPrefix(mimeType, "video/"):
294
-
// allowed
295
case strings.HasPrefix(mimeType, "text/plain"):
296
-
// allowed
297
default:
298
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
299
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
300
return
301
}
302
303
-
w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours
304
-
w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents)))
305
w.Header().Set("Content-Type", mimeType)
306
w.Write(contents)
307
}
···
286
mimeType = "image/svg+xml"
287
}
288
289
+
contentHash := sha256.Sum256(contents)
290
+
eTag := fmt.Sprintf("\"%x\"", contentHash)
291
+
292
// allow image, video, and text/plain files to be served directly
293
switch {
294
+
case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"):
295
+
if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag {
296
+
w.WriteHeader(http.StatusNotModified)
297
+
return
298
+
}
299
+
w.Header().Set("ETag", eTag)
300
+
301
case strings.HasPrefix(mimeType, "text/plain"):
302
+
w.Header().Set("Cache-Control", "public, no-cache")
303
+
304
default:
305
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
306
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
307
return
308
}
309
310
w.Header().Set("Content-Type", mimeType)
311
w.Write(contents)
312
}