stemedb/cmd/pitch-voiceover/pkg/httpkit/download.go
jordan 157dbbb9eb feat: Complete Aphoria Phase 8-9 + UAT suite (90/90 tests passing)
## Phase 8: Enterprise Extractor Improvements 
- 14 security extractors (TLS, JWT, SQL injection, XSS, etc.)
- 10 framework-specific extractors (Spring, Django, Rails, etc.)
- Config file security detection (YAML, TOML)

## Phase 9: Autonomous Extractor Generation 
- Shadow mode executor with TP/FP tracking
- Graduation pipeline with confidence thresholds
- Auto-rollback on regression detection
- Cross-project pattern syncing

## UAT Suite Complete (14 scripts, 90 tests)
- test-core-detection.sh (6 tests)
- test-declarative-extractors.sh (5 tests)
- test-domain-frameworks.sh (5 tests)
- test-domain-unreal.sh (3 tests)
- test-llm-extraction.sh (6 tests)
- test-eval-harness.sh (5 tests)
- test-cross-language.sh (3 tests)
- test-precommit-performance.sh (4 tests)
- test-output-formats.sh (8 tests)
- test-drift-detection.sh (6 tests)
- test-exit-codes.sh (12 tests)
+ 3 more scripts

## Other Changes
- Updated roadmap to mark Phase 8-9 complete
- Added .gitignore entries for build artifacts
- Updated pre-commit: 800 line limit, exclude tests/data/cmd

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 22:50:55 -07:00

57 lines
1.5 KiB
Go

package httpkit
import (
"context"
"errors"
"fmt"
"io"
"net/http"
)
// Download fetches a URL and returns the response body as bytes.
// maxBytes limits the response size; 0 means no limit.
//
// Unlike Do, Download does not apply default headers (download URLs are
// typically pre-signed or public), does not marshal JSON, and does not
// retry. It does respect the client's timeout.
func (c *Client) Download(ctx context.Context, url string, maxBytes int64) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("create download request: %w", err)
}
resp, err := c.http.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || isTimeoutError(err) {
return nil, fmt.Errorf("download: %w", ErrTimeout)
}
return nil, fmt.Errorf("download: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, &APIError{
StatusCode: resp.StatusCode,
Body: string(body),
sentinel: ClassifyStatus(resp.StatusCode),
}
}
var reader io.Reader = resp.Body
if maxBytes > 0 {
reader = io.LimitReader(resp.Body, maxBytes+1)
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("read download body: %w", err)
}
if maxBytes > 0 && int64(len(data)) > maxBytes {
return nil, fmt.Errorf("download exceeds %d byte limit", maxBytes)
}
return data, nil
}