online / endpoints 59 / categories 14 / rate 60/min/ip /

GET /multipart

GET /multipart

Returns a multipart/form-data response with RFC 7578 parser-adversity. Default declares one boundary in the Content-Type and uses a different one in the body. Use ?mode= to isolate other violations: missing terminating CRLF, ambiguous Content-Disposition `name=` parameters, or nested multipart bodies.

mode boundary-mismatch (default; Content-Type declares one boundary but the body uses another; strict parsers find no opening delimiter and return zero parts, lenient parsers may scan and recover), trailing-crlf-confusion (closing boundary `--boundary-xyz--` has no trailing CRLF; strict parsers treat the stream as unterminated, lenient parsers accept), disposition-name-injection (Content-Disposition has two `name=` parameters; RFC 7578 does not define precedence and parsers disagree on which wins), nested-multipart (inner part uses Content-Type: multipart/mixed with its own boundary; most form-data parsers do not recurse into nested multipart and return the raw bytes or error).

control Compare against the well-formed counterpart: not.catastrophic.io/multipart side-by-side

build a request:

expect: A multipart/form-data response with one RFC 7578 violation. Strict parsers raise; lenient parsers recover differently — silent shape divergence is the typical outcome.

bash
curl -si 'https://chaos.catastrophic.io/multipart?mode=boundary-mismatch'
import urllib.request, email
resp = urllib.request.urlopen("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch")
ct = resp.headers.get("Content-Type")
print("Content-Type:", ct)
print("X-Chaos-Multipart-Mode:", resp.headers.get("X-Chaos-Multipart-Mode"))
print("X-Chaos-Multipart-Note:", resp.headers.get("X-Chaos-Multipart-Note"))
raw = resp.read()
# Reconstruct as a MIME message so email.parser can split parts.
msg = email.message_from_bytes(b"Content-Type: " + ct.encode() + b"\r\n\r\n" + raw)
parts = msg.get_payload() if msg.is_multipart() else None
print("parts parsed:", len(parts) if parts else "n/a")
// Node 18+: Response from undici can parse multipart via .formData()
const res = await fetch("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch");
console.log("Content-Type:", res.headers.get("content-type"));
console.log("X-Chaos-Multipart-Mode:", res.headers.get("x-chaos-multipart-mode"));
console.log("X-Chaos-Multipart-Note:", res.headers.get("x-chaos-multipart-note"));
try {
  const fd = await res.formData();
  for (const [k, v] of fd) console.log(`  part: ${k} =`, typeof v === "string" ? v : ``);
} catch (e) {
  console.log("formData parse failed:", e.message);
  console.log("raw body:", await res.text());
}
package main

import (
    "fmt"
    "io"
    "mime"
    "mime/multipart"
    "net/http"
    "strings"
)

func main() {
    resp, _ := http.Get("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch")
    defer resp.Body.Close()
    ct := resp.Header.Get("Content-Type")
    fmt.Println("Content-Type:", ct)
    fmt.Println("X-Chaos-Multipart-Mode:", resp.Header.Get("X-Chaos-Multipart-Mode"))
    _, params, _ := mime.ParseMediaType(ct)
    mr := multipart.NewReader(resp.Body, params["boundary"])
    for {
        p, err := mr.NextPart()
        if err == io.EOF { break }
        if err != nil { fmt.Println("part error:", err); break }
        body, _ := io.ReadAll(p)
        fmt.Printf("  part: name=%q len=%d\n", p.FormName(), len(body))
        _ = strings.TrimSpace
    }
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
// Multipart parsing on the response side typically needs a separate crate
// (e.g. multer); this example just dumps the raw body.
fn main() -> Result<(), Box> {
    let resp = reqwest::blocking::get("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch")?;
    println!("Content-Type: {:?}", resp.headers().get("content-type"));
    println!("X-Chaos-Multipart-Mode: {:?}", resp.headers().get("x-chaos-multipart-mode"));
    println!("--- raw body ---\n{}", resp.text()?);
    Ok(())
}
import java.net.URI;
import java.net.http.*;

public class MultipartChaos {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch")).build();
        var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println("Content-Type: " +
            resp.headers().firstValue("Content-Type").orElse(""));
        System.out.println("X-Chaos-Multipart-Mode: " +
            resp.headers().firstValue("X-Chaos-Multipart-Mode").orElse(""));
        System.out.println("Body: " + resp.body());
    }
}
using var client = new HttpClient();
var resp = await client.GetAsync("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch");
Console.WriteLine($"Content-Type: {resp.Content.Headers.ContentType}");
Console.WriteLine($"X-Chaos-Multipart-Mode: " +
    $"{resp.Headers.GetValues("X-Chaos-Multipart-Mode").FirstOrDefault()}");
var body = await resp.Content.ReadAsStringAsync();
Console.WriteLine($"Body: {body}");
require "net/http"
uri = URI("https://chaos.catastrophic.io/multipart?mode=boundary-mismatch")
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    res = http.get(uri.request_uri)
    puts "Content-Type: #{res['Content-Type']}"
    puts "X-Chaos-Multipart-Mode: #{res['X-Chaos-Multipart-Mode']}"
    puts "X-Chaos-Multipart-Note: #{res['X-Chaos-Multipart-Note']}"
    puts res.body
end
$r = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/multipart?mode=boundary-mismatch'
"Content-Type: $($r.Headers['Content-Type'])"
"X-Chaos-Multipart-Mode: $($r.Headers['X-Chaos-Multipart-Mode'])"
"X-Chaos-Multipart-Note: $($r.Headers['X-Chaos-Multipart-Note'])"
$r.Content