Skip to content

MIME Type Reference — C source

A searchable map of common file extensions to their MIME types - text, image, audio, video, application, font, and multipart. Look up by extension or by type, filter by category, and copy the exact Content-Type string for any format.

This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.

/* mime-types — C99 port of the curated extension ↔ MIME table, ported from
 * src/lib/mime-types.ts. Display source for CosmoDev's polyglot tool pages.
 *
 * The table is a static array of 71 entries grouped by the seven top-level
 * media types (RFC 2046): text, image, audio, video, application, font, and
 * multipart. Each entry's `type` always equals the top-level segment of its
 * own `mime`, so the category filter and the Content-Type never disagree.
 *
 * C has no lists-at-runtime without allocation, so the lookups write their
 * matches into a caller-provided buffer and return the match count — the
 * idiomatic C shape for "return a filtered list".
 */

#include <ctype.h>
#include <stdio.h>
#include <string.h>

typedef enum {
    MIME_TEXT = 0,
    MIME_IMAGE,
    MIME_AUDIO,
    MIME_VIDEO,
    MIME_APPLICATION,
    MIME_FONT,
    MIME_MULTIPART,
} MimeCategory;

typedef struct {
    const char *ext;   /* lowercase, no leading dot (e.g. "json") */
    const char *mime;  /* full Content-Type (e.g. "application/json") */
    MimeCategory type; /* always the top-level segment of `mime` */
    const char *name;  /* human-readable format name (e.g. "JSON") */
} MimeEntry;

/* Display order of the categories (used by the UI filter). */
static const MimeCategory MIME_CATEGORIES[] = {
    MIME_TEXT, MIME_IMAGE, MIME_AUDIO, MIME_VIDEO,
    MIME_APPLICATION, MIME_FONT, MIME_MULTIPART,
};

static const char *const CATEGORY_LABELS[] = {
    "text", "image", "audio", "video", "application", "font", "multipart",
};

/* The curated extension ↔ MIME table. One entry is one (extension, type)
 * pair, so an extension with several MIME types (e.g. .js) appears more than
 * once, and a MIME with several extensions (e.g. image/jpeg) appears once per
 * extension. Values follow IANA / MDN conventions. */
static const MimeEntry MIME[] = {
    /* ── text ─────────────────────────────────────────────────────────── */
    {"txt", "text/plain", MIME_TEXT, "Plain text"},
    {"html", "text/html", MIME_TEXT, "HTML document"},
    {"htm", "text/html", MIME_TEXT, "HTML document"},
    {"xhtml", "application/xhtml+xml", MIME_APPLICATION, "XHTML"},
    {"css", "text/css", MIME_TEXT, "CSS stylesheet"},
    {"csv", "text/csv", MIME_TEXT, "Comma-separated values"},
    {"xml", "application/xml", MIME_APPLICATION, "XML document"},
    {"js", "text/javascript", MIME_TEXT, "JavaScript"},
    {"js", "application/javascript", MIME_APPLICATION, "JavaScript (legacy)"},
    {"mjs", "text/javascript", MIME_TEXT, "JavaScript module"},
    {"json", "application/json", MIME_APPLICATION, "JSON"},
    {"md", "text/markdown", MIME_TEXT, "Markdown"},
    {"yaml", "text/yaml", MIME_TEXT, "YAML"},
    {"yml", "text/yaml", MIME_TEXT, "YAML"},
    {"ics", "text/calendar", MIME_TEXT, "iCalendar"},
    {"rtf", "application/rtf", MIME_APPLICATION, "Rich text"},
    {"tsv", "text/tab-separated-values", MIME_TEXT, "Tab-separated values"},

    /* ── image ────────────────────────────────────────────────────────── */
    {"jpg", "image/jpeg", MIME_IMAGE, "JPEG image"},
    {"jpeg", "image/jpeg", MIME_IMAGE, "JPEG image"},
    {"png", "image/png", MIME_IMAGE, "PNG image"},
    {"gif", "image/gif", MIME_IMAGE, "GIF image"},
    {"webp", "image/webp", MIME_IMAGE, "WebP image"},
    {"avif", "image/avif", MIME_IMAGE, "AVIF image"},
    {"svg", "image/svg+xml", MIME_IMAGE, "SVG image"},
    {"bmp", "image/bmp", MIME_IMAGE, "Bitmap image"},
    {"ico", "image/x-icon", MIME_IMAGE, "Icon"},
    {"tif", "image/tiff", MIME_IMAGE, "TIFF image"},
    {"tiff", "image/tiff", MIME_IMAGE, "TIFF image"},
    {"heic", "image/heic", MIME_IMAGE, "HEIC image"},

    /* ── audio ────────────────────────────────────────────────────────── */
    {"mp3", "audio/mpeg", MIME_AUDIO, "MP3 audio"},
    {"wav", "audio/wav", MIME_AUDIO, "WAV audio"},
    {"ogg", "audio/ogg", MIME_AUDIO, "Ogg audio"},
    {"oga", "audio/ogg", MIME_AUDIO, "Ogg audio"},
    {"flac", "audio/flac", MIME_AUDIO, "FLAC audio"},
    {"aac", "audio/aac", MIME_AUDIO, "AAC audio"},
    {"m4a", "audio/mp4", MIME_AUDIO, "M4A audio"},
    {"weba", "audio/webm", MIME_AUDIO, "WebM audio"},
    {"mid", "audio/midi", MIME_AUDIO, "MIDI"},
    {"midi", "audio/midi", MIME_AUDIO, "MIDI"},

    /* ── video ────────────────────────────────────────────────────────── */
    {"mp4", "video/mp4", MIME_VIDEO, "MP4 video"},
    {"webm", "video/webm", MIME_VIDEO, "WebM video"},
    {"avi", "video/x-msvideo", MIME_VIDEO, "AVI video"},
    {"mov", "video/quicktime", MIME_VIDEO, "QuickTime video"},
    {"mkv", "video/x-matroska", MIME_VIDEO, "Matroska video"},
    {"mpeg", "video/mpeg", MIME_VIDEO, "MPEG video"},
    {"mpg", "video/mpeg", MIME_VIDEO, "MPEG video"},
    {"ogv", "video/ogg", MIME_VIDEO, "Ogg video"},
    {"m4v", "video/mp4", MIME_VIDEO, "M4V video"},

    /* ── application ──────────────────────────────────────────────────── */
    {"pdf", "application/pdf", MIME_APPLICATION, "PDF document"},
    {"zip", "application/zip", MIME_APPLICATION, "ZIP archive"},
    {"gz", "application/gzip", MIME_APPLICATION, "Gzip archive"},
    {"tar", "application/x-tar", MIME_APPLICATION, "Tar archive"},
    {"rar", "application/vnd.rar", MIME_APPLICATION, "RAR archive"},
    {"7z", "application/x-7z-compressed", MIME_APPLICATION, "7-Zip archive"},
    {"bin", "application/octet-stream", MIME_APPLICATION, "Binary data"},
    {"exe", "application/x-msdownload", MIME_APPLICATION, "Windows executable"},
    {"jar", "application/java-archive", MIME_APPLICATION, "Java archive"},
    {"wasm", "application/wasm", MIME_APPLICATION, "WebAssembly"},
    {"doc", "application/msword", MIME_APPLICATION, "Word document"},
    {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", MIME_APPLICATION, "Word document (OOXML)"},
    {"xls", "application/vnd.ms-excel", MIME_APPLICATION, "Excel spreadsheet"},
    {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", MIME_APPLICATION, "Excel spreadsheet (OOXML)"},
    {"ppt", "application/vnd.ms-powerpoint", MIME_APPLICATION, "PowerPoint"},
    {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", MIME_APPLICATION, "PowerPoint (OOXML)"},

    /* ── font ─────────────────────────────────────────────────────────── */
    {"woff", "font/woff", MIME_FONT, "WOFF font"},
    {"woff2", "font/woff2", MIME_FONT, "WOFF2 font"},
    {"ttf", "font/ttf", MIME_FONT, "TrueType font"},
    {"otf", "font/otf", MIME_FONT, "OpenType font"},
    {"eot", "application/vnd.ms-fontobject", MIME_APPLICATION, "Embedded OpenType"},

    /* ── multipart ────────────────────────────────────────────────────── */
    {"mht", "multipart/related", MIME_MULTIPART, "MHTML web archive"},
    {"mhtml", "multipart/related", MIME_MULTIPART, "MHTML web archive"},
};

#define MIME_COUNT (sizeof MIME / sizeof MIME[0])

/* Trim surrounding whitespace and lowercase in place into `out` (at most
 * cap-1 chars plus NUL). Shared by normalize_ext and the filter query. */
static char *canon(const char *s, char *out, size_t cap)
{
    size_t n = 0;
    while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++;
    for (; *s && n + 1 < cap; s++)
        out[n++] = (char)tolower((unsigned char)*s);
    while (n > 0 && (out[n - 1] == ' ' || out[n - 1] == '\t' ||
                     out[n - 1] == '\r' || out[n - 1] == '\n'))
        n--;
    out[n] = '\0';
    return out;
}

/* Lowercase an extension and drop a leading dot so ".JSON" == "json". */
char *normalize_ext(const char *ext, char *out, size_t cap)
{
    char *e = canon(ext, out, cap);
    char *p = e;
    while (*p == '.') p++;
    if (p != e) memmove(e, p, strlen(p) + 1);
    return e;
}

/* Every entry whose extension matches. The dot is optional and matching is
 * case-insensitive, so ".js", "JS", and "js" are equivalent. Matches are
 * copied into `out` (at most `cap`); returns the match count. */
size_t lookup_by_ext(const char *ext, MimeEntry *out, size_t cap)
{
    char e[64];
    normalize_ext(ext, e, sizeof e);
    if (e[0] == '\0') return 0;
    size_t n = 0;
    for (size_t i = 0; i < MIME_COUNT; i++)
        if (strcmp(MIME[i].ext, e) == 0 && n < cap) out[n++] = MIME[i];
    return n;
}

/* Every entry whose MIME matches, case-insensitively, tolerating surrounding
 * whitespace. One MIME commonly maps to several extensions (image/jpeg →
 * jpg, jpeg), so all are returned. */
size_t lookup_by_mime(const char *mime, MimeEntry *out, size_t cap)
{
    char m[128];
    canon(mime, m, sizeof m);
    if (m[0] == '\0') return 0;
    size_t n = 0;
    for (size_t i = 0; i < MIME_COUNT; i++)
        if (strcasecmp(MIME[i].mime, m) == 0 && n < cap) out[n++] = MIME[i];
    return n;
}

/* Optional constraints: has_type == 0 means "any category" (mirrors
 * `undefined` in the TypeScript source); an empty query is ignored. */
typedef struct {
    int has_type;
    MimeCategory type;
    char query[64]; /* raw; lowercased + trimmed by mime_filter */
} MimeFilter;

/* Case-insensitive substring test against a pre-lowercased `needle`. */
static int ci_contains(const char *hay, const char *needle)
{
    if (needle[0] == '\0') return 1;
    size_t nl = strlen(needle);
    for (const char *p = hay; *p; p++) {
        size_t i = 0;
        while (i < nl && p[i] &&
               tolower((unsigned char)p[i]) == (unsigned char)needle[i])
            i++;
        if (i == nl) return 1;
    }
    return 0;
}

/* Filter the full table by category and/or a free-text query matched against
 * ext, mime, and name. An unknown category value yields nothing. */
size_t mime_filter(const MimeFilter *opts, MimeEntry *out, size_t cap)
{
    char q[64];
    canon(opts->query, q, sizeof q);
    size_t n = 0;
    for (size_t i = 0; i < MIME_COUNT; i++) {
        const MimeEntry *e = &MIME[i];
        if (opts->has_type && e->type != opts->type) continue;
        if (q[0] != '\0' &&
            !ci_contains(e->ext, q) && !ci_contains(e->mime, q) &&
            !ci_contains(e->name, q))
            continue;
        if (n < cap) out[n++] = MIME[i];
    }
    return n;
}

int main(void)
{
    MimeEntry hits[8];
    size_t n = lookup_by_ext(".JSON", hits, 8);
    if (n > 0) printf(".JSON -> %s (%s)\n", hits[0].mime, hits[0].name);

    n = lookup_by_mime("IMAGE/JPEG", hits, 8);
    for (size_t i = 0; i < n; i++)
        printf("image/jpeg -> .%s\n", hits[i].ext);

    MimeFilter f = {0};
    strcpy(f.query, "font");
    printf("query \"font\": %zu of %zu entries\n",
           mime_filter(&f, hits, 8), (size_t)MIME_COUNT);
    return 0;
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →