Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 28m ago 32290114
history blame raw 2.6 KiB
1package web
2
3// Byte ranges for a download.
4//
5// A blob is streamed from git, which cannot seek, so a range is served by
6// reading and discarding the bytes before it and copying only the requested
7// part. The win is what does not cross the network: a player asking for the
8// first few KiB of a 100 MiB asset costs one copy of the skipped bytes on the
9// server, not 100 MiB of transfer to the client.
10
11import (
12 "strconv"
13 "strings"
14)
15
16// byteRange is one satisfiable range of a representation.
17type byteRange struct {
18 start int64
19 length int64
20}
21
22// parseByteRange reads a Range header against a representation of size bytes.
23//
24// It answers one of three ways: a range to serve (partial), a range that cannot
25// be satisfied (unsatisfiable), or neither — no header, an unparsable one, or
26// more than one range. The last group means "serve the whole file", which
27// RFC 9110 allows; answering multiple ranges with a single full response keeps
28// the reader honest rather than silently dropping the ones it did not implement.
29func parseByteRange(header string, size int64) (br byteRange, partial, unsatisfiable bool) {
30 header = strings.TrimSpace(header)
31 const prefix = "bytes="
32 if !strings.HasPrefix(header, prefix) {
33 return byteRange{}, false, false
34 }
35 spec := strings.TrimSpace(strings.TrimPrefix(header, prefix))
36 if spec == "" || strings.ContainsRune(spec, ',') {
37 return byteRange{}, false, false
38 }
39 first, last, ok := strings.Cut(spec, "-")
40 if !ok {
41 return byteRange{}, false, false
42 }
43 first, last = strings.TrimSpace(first), strings.TrimSpace(last)
44
45 if first == "" {
46 // A suffix range: the last n bytes.
47 n, ok := parseRangeInt(last)
48 if !ok {
49 return byteRange{}, false, false
50 }
51 if n == 0 || size == 0 {
52 return byteRange{}, false, true
53 }
54 if n > size {
55 n = size
56 }
57 return byteRange{start: size - n, length: n}, true, false
58 }
59 start, ok := parseRangeInt(first)
60 if !ok {
61 return byteRange{}, false, false
62 }
63 if start >= size {
64 return byteRange{}, false, true
65 }
66 if last == "" {
67 return byteRange{start: start, length: size - start}, true, false
68 }
69 end, ok := parseRangeInt(last)
70 if !ok || end < start {
71 return byteRange{}, false, false
72 }
73 if end >= size {
74 end = size - 1
75 }
76 return byteRange{start: start, length: end - start + 1}, true, false
77}
78
79// parseRangeInt reads the digits of one range bound. A sign, a space, or an
80// overflow is not a bound, so the header is ignored rather than guessed at.
81func parseRangeInt(s string) (int64, bool) {
82 if s == "" {
83 return 0, false
84 }
85 for _, r := range s {
86 if r < '0' || r > '9' {
87 return 0, false
88 }
89 }
90 n, err := strconv.ParseInt(s, 10, 64)
91 return n, err == nil
92}
93