GET /range
Returns HTTP Range responses that misbehave. Tests clients that rely on partial-content semantics: resumable downloads, video streaming, CDN slicing. Default sends a 206 with the correct Content-Range header but the wrong body bytes.
mode
Which Range violation to send. One of: accepts-range-lies (default; 206 with correct Content-Range header but body bytes offset by 500), ignores-range (Range header present but server returns 200 + full body), bad-multipart (multipart/byteranges with non-monotonic parts — 200-299 before 0-99), stale-if-range (If-Range ETag mismatch, server returns 206 instead of 200 + full body), suffix-confusion (bytes=-N returns first N bytes instead of last N).
build a request:
expect: A partial-content response with one of five Range-related violations. The body is a 1000-byte document of ten 100-byte blocks (AAAA...BBBB...etc.) so wrong-slice lies are immediately visible.
curl -si -H 'Range: bytes=0-99' 'https://chaos.catastrophic.io/range?mode=accepts-range-lies' | grep -E '^(HTTP|content-type|content-range|content-length|x-chaos-range)'
# Default mode (accepts-range-lies) ACKs the requested range in
# Content-Range but ships bytes from a different offset. Clients
# that trust Content-Range to track stream position silently
# corrupt the reassembled file — the lie only surfaces when a
# final checksum fails.
import urllib.request
req = urllib.request.Request("https://chaos.catastrophic.io/range?mode=accepts-range-lies", headers={"Range": "bytes=0-99"})
resp = urllib.request.urlopen(req)
print("Status:", resp.status)
print("Content-Range:", resp.headers.get("Content-Range"))
print("X-Chaos-Range-Actual:", resp.headers.get("X-Chaos-Range-Actual"))
print("X-Chaos-Range-Mode:", resp.headers.get("X-Chaos-Range-Mode"))
body = resp.read()
print("body bytes:", len(body))
print("first 20 bytes:", body[:20])
const res = await fetch("https://chaos.catastrophic.io/range?mode=accepts-range-lies", { headers: { Range: "bytes=0-99" } });
console.log("Status:", res.status);
console.log("Content-Range:", res.headers.get("content-range"));
console.log("X-Chaos-Range-Actual:", res.headers.get("x-chaos-range-actual"));
console.log("X-Chaos-Range-Mode:", res.headers.get("x-chaos-range-mode"));
const buf = await res.arrayBuffer();
console.log("body bytes:", buf.byteLength);
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://chaos.catastrophic.io/range?mode=accepts-range-lies", nil)
req.Header.Set("Range", "bytes=0-99")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Content-Range:", resp.Header.Get("Content-Range"))
fmt.Println("X-Chaos-Range-Actual:", resp.Header.Get("X-Chaos-Range-Actual"))
fmt.Println("X-Chaos-Range-Mode:", resp.Header.Get("X-Chaos-Range-Mode"))
body, _ := io.ReadAll(resp.Body)
fmt.Println("body bytes:", len(body))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
fn main() -> Result<(), Box> {
let client = reqwest::blocking::Client::new();
let resp = client.get("https://chaos.catastrophic.io/range?mode=accepts-range-lies").header("Range", "bytes=0-99").send()?;
println!("Status: {}", resp.status());
println!("Content-Range: {:?}", resp.headers().get("content-range"));
println!("X-Chaos-Range-Actual: {:?}", resp.headers().get("x-chaos-range-actual"));
let body = resp.bytes()?;
println!("body bytes: {}", body.len());
Ok(())
}
import java.net.URI;
import java.net.http.*;
public class RangeChaos {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/range?mode=accepts-range-lies"))
.header("Range", "bytes=0-99")
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
System.out.println("Status: " + resp.statusCode());
System.out.println("Content-Range: " + resp.headers().firstValue("Content-Range").orElse(""));
System.out.println("X-Chaos-Range-Mode: " + resp.headers().firstValue("X-Chaos-Range-Mode").orElse(""));
System.out.println("body bytes: " + resp.body().length);
}
}
using var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, "https://chaos.catastrophic.io/range?mode=accepts-range-lies");
req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(0, 99);
var resp = await client.SendAsync(req);
Console.WriteLine($"Status: {(int)resp.StatusCode}");
Console.WriteLine($"Content-Range: {resp.Content.Headers.ContentRange}");
Console.WriteLine($"X-Chaos-Range-Mode: {resp.Headers.GetValues("X-Chaos-Range-Mode").FirstOrDefault()}");
var body = await resp.Content.ReadAsByteArrayAsync();
Console.WriteLine($"body bytes: {body.Length}");
require "net/http"
uri = URI("https://chaos.catastrophic.io/range?mode=accepts-range-lies")
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
req = Net::HTTP::Get.new(uri.request_uri, "Range" => "bytes=0-99")
res = http.request(req)
puts "Status: #{res.code}"
puts "Content-Range: #{res['Content-Range']}"
puts "X-Chaos-Range-Actual: #{res['X-Chaos-Range-Actual']}"
puts "X-Chaos-Range-Mode: #{res['X-Chaos-Range-Mode']}"
puts "body bytes: #{res.body.bytesize}"
end
$r = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/range?mode=accepts-range-lies' -Headers @{ 'Range' = 'bytes=0-99' }
"Status: $($r.StatusCode)"
"Content-Range: $($r.Headers['Content-Range'])"
"X-Chaos-Range-Actual: $($r.Headers['X-Chaos-Range-Actual'])"
"X-Chaos-Range-Mode: $($r.Headers['X-Chaos-Range-Mode'])"
"body bytes: $($r.RawContentLength)"
headers
body