{"record":{"id":"d20ceb0f2e614ba0","repo":"iOfficeAI/OfficeCLI","slug":"cannot-determine-image-type-from-url-url-speci","errorCode":null,"errorMessage":"Cannot determine image type from URL: {url}. Specify format via file extension or content-type header.","messagePattern":"Cannot determine image type from URL: (.+?)\\. Specify format via file extension or content-type header\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ImageSource.cs","lineNumber":183,"sourceCode":"        var bytes = SsrfGuard.ReadBounded(response.Content.ReadAsStream(), SsrfGuard.MaxRemoteBytes, url, \"image\");\n        var stream = new MemoryStream(bytes);\n\n        // Try content-type header first\n        var serverMime = response.Content.Headers.ContentType?.MediaType;\n        if (!string.IsNullOrEmpty(serverMime) && TryMimeToContentType(serverMime, out var ct))\n            return (stream, ct);\n\n        // Fallback: extract extension from URL path (strip query string)\n        var uri = new Uri(url);\n        var ext = Path.GetExtension(uri.AbsolutePath);\n        if (!string.IsNullOrEmpty(ext))\n            return (stream, ExtensionToContentType(ext));\n\n        // Last resort: sniff magic bytes\n        if (TrySniffContentType(bytes, out var sniffed))\n            return (stream, sniffed);\n\n        throw new ArgumentException($\"Cannot determine image type from URL: {url}. Specify format via file extension or content-type header.\");\n    }\n\n    private static PartTypeInfo MimeToContentType(string mime)\n    {\n        if (TryMimeToContentType(mime, out var ct)) return ct;\n        throw new ArgumentException($\"Unsupported MIME type: {mime}. Supported: image/png, image/jpeg, image/gif, image/bmp, image/tiff, image/svg+xml\");\n    }\n\n    private static bool TryMimeToContentType(string mime, out PartTypeInfo contentType)\n    {\n        contentType = mime.ToLowerInvariant() switch\n        {\n            \"image/png\" => ImagePartType.Png,\n            \"image/jpeg\" or \"image/jpg\" => ImagePartType.Jpeg,\n            \"image/gif\" => ImagePartType.Gif,\n            \"image/bmp\" => ImagePartType.Bmp,\n            \"image/tiff\" or \"image/tif\" => ImagePartType.Tiff,\n            \"image/svg+xml\" => ImagePartType.Svg,","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ImageSource.cs#L165-L201","documentation":"Thrown by ImageSource.ResolveUrl as a last resort when the image type cannot be determined by any of the three fallback methods: (1) the server's Content-Type header didn't map to a known MIME type, (2) the URL path has no file extension or an unrecognized one, and (3) magic-byte sniffing on the downloaded bytes failed. The error includes the URL so the user can inspect it manually.","triggerScenarios":"Fetching a URL like 'https://example.com/generate-image?id=42' where: the server sends no Content-Type or sends 'application/octet-stream', the URL path has no extension (the query string is stripped), and the downloaded bytes don't match any known image magic sequence. This is the final fallback after TryMimeToContentType, ExtensionToContentType, and TrySniffContentType all fail.","commonSituations":"A dynamically-generated image endpoint with no file extension in the URL. A CDN or proxy that strips Content-Type headers. A server that serves the image with 'application/octet-stream' or 'application/binary'. A URL that redirects to a different resource than expected.","solutions":["Use a URL that ends in a recognized image extension (e.g. append '.png' if the server ignores it).","Ensure the server sends a correct Content-Type header (image/png, image/jpeg, etc.).","Download the file manually, determine its type, rename with the correct extension, and embed as a local file.","If the server only supports octet-stream, download the bytes and use a local file with the correct extension."],"exampleFix":"// before — no extension, no content-type\nadd image src='https://api.example.com/render?id=42' path='/body'\n\n// after — download locally, add correct extension\n// curl -o /tmp/img.png 'https://api.example.com/render?id=42'\n// file /tmp/img.png  → verify it's actually PNG\nadd image src='/tmp/img.png' path='/body'","handlingStrategy":"validation","validationCode":"// Pre-check: ensure URL has a recognizable extension or known content-type\nvar uri = new Uri(url);\nstring ext = Path.GetExtension(uri.AbsolutePath);\nvar knownExts = new HashSet<string> { \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".tif\", \".tiff\", \".emf\", \".wmf\", \".svg\" };\nif (string.IsNullOrEmpty(ext) || !knownExts.Contains(ext.ToLowerInvariant()))\n{\n    Console.Error.WriteLine($\"URL has no recognized image extension. Download locally or ensure server sends Content-Type.\");\n    // Download and embed as local file instead\n    return;\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    var (stream, contentType) = ImageSource.Resolve(url);\n}\ncatch (ArgumentException ex) when (ex.Message.Contains(\"Cannot determine image type\"))\n{\n    // Download the file, detect type, add extension, embed locally\n}","preventionTips":["Prefer URLs with standard image extensions so the fallback extension detection works.","Ensure the image server sends a correct Content-Type header.","For dynamic image endpoints without extensions, download to a local file with a known extension first."],"tags":["image","remote-fetch","content-type","type-detection","network"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}