mirror of https://github.com/perkeep/perkeep.git
Start of blobserver support for share/via requests.
This commit is contained in:
parent
e4d009ee77
commit
6132d7f9e7
|
@ -32,7 +32,7 @@ func handleCamli(conn http.ResponseWriter, req *http.Request) {
|
||||||
case "/camli/enumerate-blobs":
|
case "/camli/enumerate-blobs":
|
||||||
handler = auth.RequireAuth(handleEnumerateBlobs)
|
handler = auth.RequireAuth(handleEnumerateBlobs)
|
||||||
default:
|
default:
|
||||||
handler = auth.RequireAuth(createGetHandler(blobFetcher))
|
handler = createGetHandler(blobFetcher)
|
||||||
}
|
}
|
||||||
case "POST":
|
case "POST":
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
|
|
|
@ -1,26 +1,106 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"camli/auth"
|
||||||
"camli/blobref"
|
"camli/blobref"
|
||||||
"camli/httputil"
|
"camli/httputil"
|
||||||
"fmt"
|
"fmt"
|
||||||
"http"
|
"http"
|
||||||
"os"
|
"os"
|
||||||
"io"
|
"io"
|
||||||
|
"json"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createGetHandler(fetcher blobref.Fetcher) func (http.ResponseWriter, *http.Request) {
|
func createGetHandler(fetcher blobref.Fetcher) func(http.ResponseWriter, *http.Request) {
|
||||||
return func (conn http.ResponseWriter, req *http.Request) {
|
return func(conn http.ResponseWriter, req *http.Request) {
|
||||||
handleGet(conn, req, fetcher);
|
handleGet(conn, req, fetcher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchFailureDelayNs = 200e6 // 200 ms
|
||||||
|
const maxJsonSize = 10 * 1024
|
||||||
|
|
||||||
func handleGet(conn http.ResponseWriter, req *http.Request, fetcher blobref.Fetcher) {
|
func handleGet(conn http.ResponseWriter, req *http.Request, fetcher blobref.Fetcher) {
|
||||||
|
isOwner := auth.IsAuthorized(req)
|
||||||
|
|
||||||
blobRef := BlobFromUrlPath(req.URL.Path)
|
blobRef := BlobFromUrlPath(req.URL.Path)
|
||||||
if blobRef == nil {
|
if blobRef == nil {
|
||||||
httputil.BadRequestError(conn, "Malformed GET URL.")
|
httputil.BadRequestError(conn, "Malformed GET URL.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var viaBlobs []*blobref.BlobRef
|
||||||
|
if !isOwner {
|
||||||
|
viaPathOkay := false
|
||||||
|
startTime := time.Nanoseconds()
|
||||||
|
defer func() {
|
||||||
|
if !viaPathOkay {
|
||||||
|
// Insert a delay, to hide timing attacks probing
|
||||||
|
// for the existence of blobs.
|
||||||
|
sleep := fetchFailureDelayNs - (time.Nanoseconds() - startTime)
|
||||||
|
if sleep > 0 {
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
viaBlobs = make([]*blobref.BlobRef, 0)
|
||||||
|
if via := req.FormValue("via"); via != "" {
|
||||||
|
for _, vs := range strings.Split("via", ",", -1) {
|
||||||
|
if br := blobref.Parse(vs); br == nil {
|
||||||
|
httputil.BadRequestError(conn, "Malformed blobref in via param")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
viaBlobs = append(viaBlobs, br)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchChain := make([]*blobref.BlobRef, 0)
|
||||||
|
fetchChain = append(fetchChain, viaBlobs...)
|
||||||
|
fetchChain = append(fetchChain, blobRef)
|
||||||
|
for i, br := range fetchChain {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
file, size, err := fetcher.Fetch(br)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Fetch chain 0 of %s failed: %v", br.String(), err)
|
||||||
|
conn.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
if size > maxJsonSize {
|
||||||
|
log.Printf("Fetch chain 0 of %s too large", br.String())
|
||||||
|
conn.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jd := json.NewDecoder(file)
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
if err := jd.Decode(&m); err != nil {
|
||||||
|
log.Printf("Fetch chain 0 of %s wasn't JSON: %v", br.String(), err)
|
||||||
|
conn.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if m["camliType"].(string) != "share" {
|
||||||
|
log.Printf("Fetch chain 0 of %s wasn't a share", br.String())
|
||||||
|
conn.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(fetchChain) > 1 && fetchChain[1].String() != m["target"].(string) {
|
||||||
|
log.Printf("Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q",
|
||||||
|
br.String(), fetchChain[1].String(), m["target"])
|
||||||
|
conn.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("TODO: FETCH %s", br.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viaPathOkay = true
|
||||||
|
}
|
||||||
|
|
||||||
file, size, err := fetcher.Fetch(blobRef)
|
file, size, err := fetcher.Fetch(blobRef)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
|
@ -60,8 +140,8 @@ func handleGet(conn http.ResponseWriter, req *http.Request, fetcher blobref.Fetc
|
||||||
if !reqRange.IsWholeFile() {
|
if !reqRange.IsWholeFile() {
|
||||||
conn.SetHeader("Content-Range",
|
conn.SetHeader("Content-Range",
|
||||||
fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes,
|
fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes,
|
||||||
reqRange.SkipBytes + remainBytes,
|
reqRange.SkipBytes+remainBytes,
|
||||||
size))
|
size))
|
||||||
conn.WriteHeader(http.StatusPartialContent)
|
conn.WriteHeader(http.StatusPartialContent)
|
||||||
}
|
}
|
||||||
bytesCopied, err := io.Copy(conn, input)
|
bytesCopied, err := io.Copy(conn, input)
|
||||||
|
|
Loading…
Reference in New Issue