{"record":{"id":"0164254f27341b9e","repo":"Kareadita/Kavita","slug":"unsupported-cbl-file-extension-ext","errorCode":null,"errorMessage":"Unsupported CBL file extension: {ext}","messagePattern":"Unsupported CBL file extension: (.+?)","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/Helpers/CblParser.cs","lineNumber":34,"sourceCode":"/// </summary>\npublic static class CblParser\n{\n    private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions()\n    {\n        PropertyNameCaseInsensitive = true,\n    };\n\n    /// <summary>\n    /// Auto-detect format by file extension and parse accordingly.\n    /// </summary>\n    public static ParsedCblReadingList Parse(string filePath)\n    {\n        var ext = Path.GetExtension(filePath).ToLowerInvariant();\n        return ext switch\n        {\n            \".cbl\" or \".xml\" => ParseV1(filePath),\n            \".json\" => ParseV2(filePath),\n            _ => throw new ArgumentException($\"Unsupported CBL file extension: {ext}\")\n        };\n    }\n\n    /// <summary>\n    /// Parse a v1 XML CBL file into the unified model.\n    /// </summary>\n    public static ParsedCblReadingList ParseV1(string filePath)\n    {\n        var serializer = new XmlSerializer(typeof(CblReadingList));\n        using var stream = File.OpenRead(filePath);\n        var cbl = (CblReadingList)serializer.Deserialize(stream);\n\n        var result = new ParsedCblReadingList\n        {\n            SchemaVersion = 1,\n            Name = cbl.Name ?? string.Empty,\n            Summary = cbl.Summary ?? string.Empty,\n            StartYear = cbl.StartYear,","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/Helpers/CblParser.cs#L16-L52","documentation":"Thrown by CblParser.Parse when the imported reading-list file has an extension other than .cbl, .xml (both routed to the V1 XML parser) or .json (V2 JSON parser). The parser does no content sniffing; the switch expression rejects anything else with an ArgumentException. This is a hard precondition failure: the file path's extension alone selects the deserializer.","triggerScenarios":"Calling CblParser.Parse(filePath) where Path.GetExtension(filePath).ToLowerInvariant() returns something not in {\".cbl\", \".xml\", \".json\"}. Triggered via the CBL import endpoint when a user uploads a .cbz, .txt, .zip, a file with no extension, or a case the ToLowerInvariant did not normalize away from the allowed set.","commonSituations":"User uploads a ComicBookLover .cbz or a .cbl.txt that an OS/mail client renamed; file saved with a trailing dot or hidden double extension (\".json \" with whitespace, \".JSON\" is fine but \".Json.bak\" is not); a reading-list export from another tool uses a proprietary extension.","solutions":["Re-save/rename the file so its extension is exactly .cbl, .xml, or .json before uploading.","Confirm the V1 (XML) vs V2 (JSON) schema of the contents, then pick the matching extension — .cbl/.xml for the XML format, .json for the V2 format.","If you control the pipeline, strip leading/trailing whitespace and any accidental double dots from the filename upstream of Parse.","If a new format is legitimately needed, extend the switch in CblParser.Parse with a new arm and a ParseV* method."],"exampleFix":"// before\nvar result = CblParser.Parse(\"readinglist.cbz\");\n\n// after\nvar result = CblParser.Parse(Path.ChangeExtension(\"readinglist.cbz\", \".cbl\"));","handlingStrategy":"validation","validationCode":"private static readonly HashSet<string> AllowedCblExtensions = new(StringComparer.OrdinalIgnoreCase) { \".cbl\", \".xml\", \".json\" };\n\nbool IsValidCblFile(string path)\n{\n    var ext = Path.GetExtension(path);\n    return !string.IsNullOrEmpty(ext) && AllowedCblExtensions.Contains(ext);\n}\n\n// before upload\nif (!IsValidCblFile(upload.FileName)) return BadRequest(\"Only .cbl, .xml, .json are accepted.\");","typeGuard":"bool IsCblParseable(string path) =>\n    Path.GetExtension(path)?.ToLowerInvariant() is \".cbl\" or \".xml\" or \".json\";","tryCatchPattern":"try { var list = CblParser.Parse(path); }\ncatch (ArgumentException ex) when (ex.Message.StartsWith(\"Unsupported CBL file extension\"))\n{ /* surface 'unsupported file type' to user */ }","preventionTips":["Restrict the upload <input accept=\".cbl,.xml,.json\"> so the browser filters first.","Validate the extension server-side before calling Parse.","Do not rely on content sniffing — the parser dispatches purely on extension."],"tags":["cbl-import","file-extension","argument-validation","reading-list"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}