Browse Source

Incomplete HTTP Response Error Handling

pull/7481/head
chrislu 3 weeks ago
parent
commit
bbd2123835
  1. 27
      weed/s3api/s3_sse_ctr_test.go
  2. 64
      weed/s3api/s3api_object_handlers.go

27
weed/s3api/s3_sse_ctr_test.go

@ -15,10 +15,10 @@ func TestCalculateIVWithOffset(t *testing.T) {
rand.Read(baseIV)
tests := []struct {
name string
offset int64
expectedSkip int
expectedBlock int64
name string
offset int64
expectedSkip int
expectedBlock int64
}{
{"BlockAligned_0", 0, 0, 0},
{"BlockAligned_16", 16, 0, 1},
@ -108,7 +108,7 @@ func TestCTRDecryptionWithNonBlockAlignedOffset(t *testing.T) {
}
decryptStream := cipher.NewCTR(decryptBlock, adjustedIV)
// Create a reader for the ciphertext starting at block-aligned offset
ciphertextFromBlockStart := ciphertext[blockAlignedOffset:]
decryptedFromBlockStart := make([]byte, len(ciphertextFromBlockStart))
@ -139,7 +139,7 @@ func TestCTRDecryptionWithNonBlockAlignedOffset(t *testing.T) {
previewLen2 = len(decryptedFromOffset)
}
t.Errorf(" Got first 32 bytes: %x", decryptedFromOffset[:previewLen2])
// Find first mismatch
for i := 0; i < len(expectedPlaintext) && i < len(decryptedFromOffset); i++ {
if expectedPlaintext[i] != decryptedFromOffset[i] {
@ -184,9 +184,9 @@ func TestCTRRangeRequestSimulation(t *testing.T) {
}{
{"First byte", 0, 0},
{"First 100 bytes", 0, 99},
{"Mid-block range", 5, 100}, // Critical: starts at non-aligned offset
{"Single mid-block byte", 17, 17}, // Critical: single byte at offset 17
{"Cross-block range", 10, 50}, // Spans multiple blocks
{"Mid-block range", 5, 100}, // Critical: starts at non-aligned offset
{"Single mid-block byte", 17, 17}, // Critical: single byte at offset 17
{"Cross-block range", 10, 50}, // Spans multiple blocks
{"Large range", 1000, 10000},
{"Tail range", int64(objectSize - 1000), int64(objectSize - 1)},
}
@ -194,7 +194,7 @@ func TestCTRRangeRequestSimulation(t *testing.T) {
for _, rt := range rangeTests {
t.Run(rt.name, func(t *testing.T) {
rangeSize := rt.end - rt.start + 1
// Calculate adjusted IV and skip for the range start
adjustedIV, skip := calculateIVWithOffset(iv, rt.start)
@ -208,7 +208,7 @@ func TestCTRRangeRequestSimulation(t *testing.T) {
}
decryptStream := cipher.NewCTR(decryptBlock, adjustedIV)
// Decrypt from block-aligned start through the end of range
ciphertextFromBlock := ciphertext[blockAlignedStart : rt.end+1]
decryptedFromBlock := make([]byte, len(ciphertextFromBlock))
@ -224,7 +224,7 @@ func TestCTRRangeRequestSimulation(t *testing.T) {
// Verify decrypted range matches original plaintext
expectedPlaintext := plaintext[rt.start : rt.end+1]
if !bytes.Equal(decryptedRange, expectedPlaintext) {
t.Errorf("Range decryption mismatch for %s (offset=%d, size=%d, skip=%d)",
t.Errorf("Range decryption mismatch for %s (offset=%d, size=%d, skip=%d)",
rt.name, rt.start, rangeSize, skip)
previewLen := 64
if len(expectedPlaintext) < previewLen {
@ -244,7 +244,7 @@ func TestCTRRangeRequestSimulation(t *testing.T) {
// TestCTRDecryptionWithIOReader tests the integration with io.Reader
func TestCTRDecryptionWithIOReader(t *testing.T) {
plaintext := []byte("Hello, World! This is a test of CTR mode decryption with non-aligned offsets.")
key := make([]byte, 32)
iv := make([]byte, 16)
rand.Read(key)
@ -305,4 +305,3 @@ func TestCTRDecryptionWithIOReader(t *testing.T) {
})
}
}

64
weed/s3api/s3api_object_handlers.go

@ -578,7 +578,10 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
streamTime = time.Since(tStream)
if err != nil {
glog.Errorf("GetObjectHandler: failed to stream from volume servers: %v", err)
// Don't write error response - headers already sent
// Try to write error response. The HTTP library will gracefully handle cases
// where headers are already sent (e.g., during streaming errors).
// For early validation errors, this ensures client gets proper error response.
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
}
@ -602,6 +605,9 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
}()
if entry == nil {
// Early validation error: write proper HTTP response
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error: entry is nil")
return fmt.Errorf("entry is nil")
}
@ -689,43 +695,59 @@ func (s3a *S3ApiServer) streamFromVolumeServers(w http.ResponseWriter, r *http.R
}
rangeParseTime = time.Since(tRangeParse)
// Set standard HTTP headers from entry metadata
// IMPORTANT: Set ALL headers BEFORE calling WriteHeader (headers are ignored after WriteHeader)
tHeaderSet := time.Now()
s3a.setResponseHeaders(w, entry, totalSize)
// Override/add range-specific headers if this is a range request
if isRangeRequest {
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, offset+size-1, totalSize))
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
}
headerSetTime = time.Since(tHeaderSet)
// Now write status code (headers are all set)
if isRangeRequest {
w.WriteHeader(http.StatusPartialContent)
}
// For small files stored inline in entry.Content
// For small files stored inline in entry.Content - validate BEFORE setting headers
if len(entry.Content) > 0 && totalSize == int64(len(entry.Content)) {
if isRangeRequest {
// Safely convert int64 to int for slice indexing
// Safely convert int64 to int for slice indexing - validate BEFORE WriteHeader
if offset < 0 || offset > int64(math.MaxInt) || size < 0 || size > int64(math.MaxInt) {
// Early validation error: write proper HTTP response BEFORE headers
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", totalSize))
w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
fmt.Fprintf(w, "Range too large for platform: offset=%d, size=%d", offset, size)
return fmt.Errorf("range too large for platform: offset=%d, size=%d", offset, size)
}
start := int(offset)
end := start + int(size)
// Bounds check (should already be validated, but double-check)
// Bounds check (should already be validated, but double-check) - BEFORE WriteHeader
if start < 0 || start > len(entry.Content) || end > len(entry.Content) || end < start {
// Early validation error: write proper HTTP response BEFORE headers
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", totalSize))
w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
fmt.Fprintf(w, "Invalid range for inline content: start=%d, end=%d, len=%d)", start, end, len(entry.Content))
return fmt.Errorf("invalid range for inline content: start=%d, end=%d, len=%d", start, end, len(entry.Content))
}
// Validation passed - now set headers and write
s3a.setResponseHeaders(w, entry, totalSize)
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, offset+size-1, totalSize))
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
w.WriteHeader(http.StatusPartialContent)
_, err := w.Write(entry.Content[start:end])
return err
}
// Non-range request for inline content
s3a.setResponseHeaders(w, entry, totalSize)
w.WriteHeader(http.StatusOK)
_, err := w.Write(entry.Content)
return err
}
// Set standard HTTP headers from entry metadata
// IMPORTANT: Set ALL headers BEFORE calling WriteHeader (headers are ignored after WriteHeader)
tHeaderSet := time.Now()
s3a.setResponseHeaders(w, entry, totalSize)
// Override/add range-specific headers if this is a range request
if isRangeRequest {
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", offset, offset+size-1, totalSize))
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
}
headerSetTime = time.Since(tHeaderSet)
// Now write status code (headers are all set)
if isRangeRequest {
w.WriteHeader(http.StatusPartialContent)
}
// Get chunks
chunks := entry.GetChunks()
glog.Infof("streamFromVolumeServers: entry has %d chunks, totalSize=%d, isRange=%v, offset=%d, size=%d",

Loading…
Cancel
Save