v3 candidate
This commit is contained in:
@@ -25,6 +25,10 @@ const (
|
||||
|
||||
// requestTimeout limits the HTTP request duration.
|
||||
requestTimeout = 10 * time.Second
|
||||
|
||||
// maxUpdateResponseBytes bounds release and manifest responses before
|
||||
// parsing them into memory.
|
||||
maxUpdateResponseBytes = 1 << 20
|
||||
)
|
||||
|
||||
// Release describes a GitHub release with the fields we care about.
|
||||
@@ -135,8 +139,13 @@ func (c *Checker) Check(ctx context.Context) (*UpdateInfo, error) {
|
||||
return nil, fmt.Errorf("updater: GitHub API returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := readLimitedUpdateBody(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updater: failed to read response: %w", err)
|
||||
}
|
||||
|
||||
var release Release
|
||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||
if err := json.Unmarshal(body, &release); err != nil {
|
||||
return nil, fmt.Errorf("updater: failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
@@ -186,7 +195,7 @@ func (c *URLChecker) Check(ctx context.Context) (*UpdateInfo, error) {
|
||||
return nil, fmt.Errorf("updater: update server returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := readLimitedUpdateBody(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updater: failed to read response: %w", err)
|
||||
}
|
||||
@@ -215,6 +224,17 @@ func normaliseVersion(v string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
func readLimitedUpdateBody(r io.Reader) ([]byte, error) {
|
||||
body, err := io.ReadAll(io.LimitReader(r, maxUpdateResponseBytes+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(body) > maxUpdateResponseBytes {
|
||||
return nil, fmt.Errorf("response exceeds %d bytes", maxUpdateResponseBytes)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func parseManifest(body []byte, current string) (*UpdateInfo, bool) {
|
||||
var manifest Manifest
|
||||
if err := json.Unmarshal(body, &manifest); err != nil {
|
||||
|
||||
@@ -37,9 +37,9 @@ func TestIsNewer(t *testing.T) {
|
||||
{"1.0.1", "1.0.0", true},
|
||||
{"1.0.0", "1.0.0", false},
|
||||
{"0.9.0", "1.0.0", false},
|
||||
{"1.0.0", "dev", false}, // dev builds ignored
|
||||
{"1.0.0", "", false}, // empty current ignored
|
||||
{"1.2", "1.1.0", true}, // fewer parts
|
||||
{"1.0.0", "dev", false}, // dev builds ignored
|
||||
{"1.0.0", "", false}, // empty current ignored
|
||||
{"1.2", "1.1.0", true}, // fewer parts
|
||||
{"1.0.0", "1.0.0.1", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -104,3 +104,20 @@ func TestURLCheckerPlainText(t *testing.T) {
|
||||
t.Fatalf("expected update to be available")
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLCheckerRejectsOversizedManifest(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, _ = w.Write([]byte(strings.Repeat("x", maxUpdateResponseBytes+1)))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
checker := NewURLChecker(server.URL, "1.9.0")
|
||||
_, err := checker.Check(context.Background())
|
||||
if err == nil {
|
||||
t.Fatal("expected oversized manifest to be rejected")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "response exceeds") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user