GET /csv
Returns CSV with RFC 4180 corner-case violations: unquoted commas, embedded newlines, UTF-8 BOM at start, ragged column counts. Targets data pipelines, BI tools, and ML preprocessing — anywhere CSV is the lingua franca and parsers vary in strictness.
mode
unquoted-commas (default; row contains an unquoted comma in a field — appears to have 4 columns instead of 3), embedded-newlines (row contains a literal CRLF in an unquoted field — naive splitters break mid-row), bom-mismatch (UTF-8 BOM at start of stream — strict parsers include the BOM in cell[0] of the header row, breaking column lookups by name), ragged-columns (header declares 3 columns but rows have 2 and 4 — parsers either raise, pad with nulls, or silently misalign).
control Compare against the well-formed counterpart: not.catastrophic.io/csv side-by-side
build a request:
expect: 200 OK with Content-Type: text/csv; charset=utf-8. Cache-Control: no-transform prevents the CDN edge from rewriting the body. X-Chaos-Csv-Mode reflects the selected mode; X-Chaos-Csv-Note explains the flaw.
curl -i 'https://chaos.catastrophic.io/csv?mode=unquoted-commas'
# stdlib csv.reader is forgiving — it accepts unquoted commas (just
# gets the column count wrong) and embedded newlines (silently splits
# the row). pandas raises on ragged columns by default.
import urllib.request, io, csv
raw = urllib.request.urlopen("https://chaos.catastrophic.io/csv?mode=unquoted-commas").read().decode("utf-8")
reader = csv.reader(io.StringIO(raw))
for i, row in enumerate(reader):
print(f"row {i}: {len(row)} cols -> {row}")
// Papa Parse is the de-facto browser CSV library; it surfaces
// delimiter / column-count errors via the `errors` array.
// Without Papa, parsing CSV correctly in vanilla JS is a footgun
// because of the embedded-newlines mode below.
const res = await fetch("https://chaos.catastrophic.io/csv?mode=unquoted-commas");
const raw = await res.text();
console.log("Mode:", res.headers.get("X-Chaos-Csv-Mode"));
const result = Papa.parse(raw, { header: true });
console.log("Errors:", result.errors);
console.log("Rows:", result.data);
package main
import (
"encoding/csv"
"fmt"
"net/http"
"strings"
"io"
)
func main() {
resp, _ := http.Get("https://chaos.catastrophic.io/csv?mode=unquoted-commas")
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
fmt.Println("Mode:", resp.Header.Get("X-Chaos-Csv-Mode"))
// encoding/csv: FieldsPerRecord=0 enforces the header's count;
// FieldsPerRecord=-1 disables the check entirely.
r := csv.NewReader(strings.NewReader(string(raw)))
r.FieldsPerRecord = 0
for {
rec, err := r.Read()
if err == io.EOF { break }
if err != nil { fmt.Println("Parse error:", err); break }
fmt.Println(rec)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
// csv = "1"
fn main() -> Result<(), Box> {
let raw = reqwest::blocking::get("https://chaos.catastrophic.io/csv?mode=unquoted-commas")?.text()?;
let mut rdr = csv::ReaderBuilder::new()
.has_headers(true)
.flexible(false)
.from_reader(raw.as_bytes());
for result in rdr.records() {
match result {
Ok(rec) => println!("{:?}", rec),
Err(e) => { eprintln!("Parse error: {e}"); break; }
}
}
Ok(())
}
// Requires Apache Commons CSV: org.apache.commons:commons-csv
import org.apache.commons.csv.*;
import java.net.URI;
import java.net.http.*;
import java.io.StringReader;
public class CsvChaos {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/csv?mode=unquoted-commas")).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
var fmt = CSVFormat.RFC4180.builder().setHeader().build();
try (var p = CSVParser.parse(new StringReader(resp.body()), fmt)) {
for (var record : p) System.out.println(record.toMap());
} catch (Exception e) {
System.err.println("Parse error: " + e.getMessage());
}
}
}
// Requires CsvHelper.
using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using var client = new HttpClient();
var raw = await client.GetStringAsync("https://chaos.catastrophic.io/csv?mode=unquoted-commas");
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
HasHeaderRecord = true,
MissingFieldFound = null,
BadDataFound = ctx => Console.Error.WriteLine($"Bad data at row {ctx.RawRecord}"),
};
using var reader = new StringReader(raw);
using var csv = new CsvReader(reader, config);
var rows = csv.GetRecords().ToList();
Console.WriteLine($"Rows: {rows.Count}");
# stdlib CSV is RFC-mostly. liberal_parsing: true accepts the unquoted
# comma mode without raising; default settings raise on ragged columns.
require "net/http"
require "csv"
resp = Net::HTTP.get_response(URI("https://chaos.catastrophic.io/csv?mode=unquoted-commas"))
raw = resp.body
puts "Mode: #{resp['X-Chaos-Csv-Mode']}"
begin
CSV.parse(raw, headers: true).each_with_index do |row, i|
puts "row #{i}: #{row.length} cols -> #{row.to_h}"
end
rescue CSV::MalformedCSVError => e
puts "Refused: #{e.message}"
end
# Import-Csv is strict on column counts by default. Headers come from
# row 1; ragged rows produce $null for missing columns.
$resp = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/csv?mode=unquoted-commas' -SkipHttpErrorCheck
$resp.Headers['X-Chaos-Csv-Mode']
$resp.Content | ConvertFrom-Csv | Format-Table
headers
body