diff --git a/packages/envd/internal/api/download.go b/packages/envd/internal/api/download.go index aea410718a..7232ea06e8 100644 --- a/packages/envd/internal/api/download.go +++ b/packages/envd/internal/api/download.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "compress/gzip" "errors" "fmt" @@ -16,6 +17,13 @@ import ( "github.com/e2b-dev/infra/packages/envd/internal/permissions" ) +// maxFileSizeForIdentityFallback is the maximum file size to read into memory +// when stat.Size() returns 0 (virtual files like procfs/sysfs). Files larger +// than this are served via the normal http.ServeContent path, which may return +// an empty body for truly size=0 virtual files but avoids loading large files +// into memory unnecessarily. +const maxFileSizeForIdentityFallback = 64 * 1024 // 64 KiB + func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesParams) { defer r.Body.Close() @@ -169,5 +177,25 @@ func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesPa return } + // For identity encoding, handle the case where stat.Size() returns 0 but the file + // has content (virtual files like procfs/sysfs). http.ServeContent uses seek to + // determine the size, which returns 0 for these files, causing empty responses. + // We read the content into memory and use a bytes.Reader to preserve Range/ + // conditional request semantics while serving the actual content. + if stat.Size() == 0 { + content, readErr := io.ReadAll(io.LimitReader(file, maxFileSizeForIdentityFallback)) + if readErr != nil { + errMsg = fmt.Errorf("error reading file '%s': %w", resolvedPath, readErr) + errorCode = http.StatusInternalServerError + jsonError(w, errorCode, errMsg) + + return + } + + http.ServeContent(w, r, path, stat.ModTime(), bytes.NewReader(content)) + + return + } + http.ServeContent(w, r, path, stat.ModTime(), file) }