File: datauri.go
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright © 2025 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the “Software”), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 To compile a smaller-sized command-line app, you can use the `go` command as
  27 follows:
  28 
  29 go build -ldflags "-s -w" -trimpath datauri.go
  30 */
  31 
  32 package main
  33 
  34 import (
  35     "bufio"
  36     "bytes"
  37     "encoding/base64"
  38     "errors"
  39     "io"
  40     "os"
  41     "strings"
  42 )
  43 
  44 const info = `
  45 datauri [options...] [filenames...]
  46 
  47 
  48 Encode bytes as data-URIs, auto-detecting the file/data type using the first
  49 few bytes from each data/file stream. When given multiple inputs, the output
  50 will be multiple lines, one for each file given.
  51 
  52 Empty files/inputs result in empty lines. A simple dash (-) stands for the
  53 standard-input, which is also used automatically when not given any files.
  54 
  55 Data-URIs are base64-encoded text representations of arbitrary data, which
  56 include their payload's MIME-type, and which are directly useable/shareable
  57 in web-browsers as links, despite not looking like normal links/URIs.
  58 
  59 Some web-browsers limit the size of handled data-URIs to tens of kilobytes.
  60 
  61 
  62 Options
  63 
  64     -h, -help, --h, --help              show this help message
  65 `
  66 
  67 // errNoMoreOutput is a dummy error whose message is ignored, and which
  68 // causes the app to quit immediately and successfully
  69 var errNoMoreOutput = errors.New(`no more output`)
  70 
  71 func main() {
  72     if len(os.Args) > 1 {
  73         switch os.Args[1] {
  74         case `-h`, `--h`, `-help`, `--help`:
  75             os.Stderr.WriteString(info[1:])
  76             return
  77         }
  78     }
  79 
  80     if err := run(os.Stdout, os.Args[1:]); isActualError(err) {
  81         os.Stderr.WriteString(err.Error())
  82         os.Stderr.WriteString("\n")
  83         os.Exit(1)
  84     }
  85 }
  86 
  87 func run(w io.Writer, args []string) error {
  88     bw := bufio.NewWriter(w)
  89     defer bw.Flush()
  90 
  91     if len(args) == 0 {
  92         return dataURI(bw, os.Stdin, `<stdin>`)
  93     }
  94 
  95     for _, name := range args {
  96         if err := handleFile(bw, name); err != nil {
  97             return err
  98         }
  99     }
 100     return nil
 101 }
 102 
 103 func handleFile(w *bufio.Writer, name string) error {
 104     if name == `` || name == `-` {
 105         return dataURI(w, os.Stdin, `<stdin>`)
 106     }
 107 
 108     f, err := os.Open(name)
 109     if err != nil {
 110         return errors.New(`can't read from file named "` + name + `"`)
 111     }
 112     defer f.Close()
 113 
 114     return dataURI(w, f, name)
 115 }
 116 
 117 // isActualError is to figure out whether not to ignore an error, and thus
 118 // show it as an error message
 119 func isActualError(err error) bool {
 120     return err != nil && err != io.EOF && err != errNoMoreOutput
 121 }
 122 
 123 func dataURI(w *bufio.Writer, r io.Reader, name string) error {
 124     var buf [64]byte
 125     n, err := r.Read(buf[:])
 126     if err != nil && err != io.EOF {
 127         return err
 128     }
 129     start := buf[:n]
 130 
 131     // handle regular data, trying to auto-detect its MIME type using
 132     // its first few bytes
 133     mime, ok := detectMIME(start)
 134     if !ok {
 135         return errors.New(name + `: unknown file type`)
 136     }
 137 
 138     w.WriteString(`data:`)
 139     w.WriteString(mime)
 140     w.WriteString(`;base64,`)
 141     r = io.MultiReader(bytes.NewReader(start), r)
 142     enc := base64.NewEncoder(base64.StdEncoding, w)
 143     if _, err := io.Copy(enc, r); err != nil {
 144         return err
 145     }
 146     enc.Close()
 147 
 148     w.WriteByte('\n')
 149     if err := w.Flush(); err != nil {
 150         return errNoMoreOutput
 151     }
 152     return nil
 153 }
 154 
 155 // makeDotless is similar to filepath.Ext, except its results never start
 156 // with a dot
 157 func makeDotless(s string) string {
 158     i := strings.LastIndexByte(s, '.')
 159     if i >= 0 {
 160         return s[(i + 1):]
 161     }
 162     return s
 163 }
 164 
 165 // hasPrefixByte is a simpler, single-byte version of bytes.HasPrefix
 166 func hasPrefixByte(b []byte, prefix byte) bool {
 167     return len(b) > 0 && b[0] == prefix
 168 }
 169 
 170 // hasPrefixFold is a case-insensitive bytes.HasPrefix
 171 func hasPrefixFold(s []byte, prefix []byte) bool {
 172     n := len(prefix)
 173     return len(s) >= n && bytes.EqualFold(s[:n], prefix)
 174 }
 175 
 176 // trimLeadingWhitespace ignores leading space-like symbols: this is useful
 177 // to handle text-based data formats more flexibly
 178 func trimLeadingWhitespace(b []byte) []byte {
 179     for len(b) > 0 {
 180         switch b[0] {
 181         case ' ', '\t', '\n', '\r':
 182             b = b[1:]
 183         default:
 184             return b
 185         }
 186     }
 187 
 188     // an empty slice is all that's left, at this point
 189     return nil
 190 }
 191 
 192 // nameToMIME tries to match a MIME type to a filename, dotted file extension,
 193 // or a dot-less filetype/extension given
 194 func nameToMIME(fname string) (mimeType string, ok bool) {
 195     // handle dotless file types and filenames alike
 196     kind, ok := type2mime[makeDotless(fname)]
 197     return kind, ok
 198 }
 199 
 200 // detectMIME guesses the first appropriate MIME type from the first few
 201 // data bytes given: 24 bytes are enough to detect all supported types
 202 func detectMIME(b []byte) (mimeType string, ok bool) {
 203     t, ok := detectType(b)
 204     if ok {
 205         return t, true
 206     }
 207     return ``, false
 208 }
 209 
 210 // detectType guesses the first appropriate file type for the data given:
 211 // here the type is a a filename extension without the leading dot
 212 func detectType(b []byte) (dotlessExt string, ok bool) {
 213     // empty data, so there's no way to detect anything
 214     if len(b) == 0 {
 215         return ``, false
 216     }
 217 
 218     // check for plain-text web-document formats case-insensitively
 219     kind, ok := checkDoc(b)
 220     if ok {
 221         return kind, true
 222     }
 223 
 224     // check data formats which allow any byte at the start
 225     kind, ok = checkSpecial(b)
 226     if ok {
 227         return kind, true
 228     }
 229 
 230     // check all other supported data formats
 231     headers := hdrDispatch[b[0]]
 232     for _, t := range headers {
 233         if hasPrefixPattern(b[1:], t.Header[1:], cba) {
 234             return t.Type, true
 235         }
 236     }
 237 
 238     // unrecognized data format
 239     return ``, false
 240 }
 241 
 242 // checkDoc tries to guess if the bytes given are the start of HTML, SVG,
 243 // XML, or JSON data
 244 func checkDoc(b []byte) (kind string, ok bool) {
 245     // ignore leading whitespaces
 246     b = trimLeadingWhitespace(b)
 247 
 248     // can't detect anything with empty data
 249     if len(b) == 0 {
 250         return ``, false
 251     }
 252 
 253     // handle XHTML documents which don't start with a doctype declaration
 254     if bytes.Contains(b, doctypeHTML) {
 255         return html, true
 256     }
 257 
 258     // handle HTML/SVG/XML documents
 259     if hasPrefixByte(b, '<') {
 260         if hasPrefixFold(b, []byte{'<', '?', 'x', 'm', 'l'}) {
 261             if bytes.Contains(b, []byte{'<', 's', 'v', 'g'}) {
 262                 return svg, true
 263             }
 264             return xml, true
 265         }
 266 
 267         headers := hdrDispatch['<']
 268         for _, v := range headers {
 269             if hasPrefixFold(b, v.Header) {
 270                 return v.Type, true
 271             }
 272         }
 273         return ``, false
 274     }
 275 
 276     // handle JSON with top-level arrays
 277     if hasPrefixByte(b, '[') {
 278         // match [", or [[, or [{, ignoring spaces between
 279         b = trimLeadingWhitespace(b[1:])
 280         if len(b) > 0 {
 281             switch b[0] {
 282             case '"', '[', '{':
 283                 return json, true
 284             }
 285         }
 286         return ``, false
 287     }
 288 
 289     // handle JSON with top-level objects
 290     if hasPrefixByte(b, '{') {
 291         // match {", ignoring spaces between: after {, the only valid syntax
 292         // which can follow is the opening quote for the expected object-key
 293         b = trimLeadingWhitespace(b[1:])
 294         if hasPrefixByte(b, '"') {
 295             return json, true
 296         }
 297         return ``, false
 298     }
 299 
 300     // checking for a quoted string, any of the JSON keywords, or even a
 301     // number seems too ambiguous to declare the data valid JSON
 302 
 303     // no web-document format detected
 304     return ``, false
 305 }
 306 
 307 // checkSpecial handles special file-format headers, which should be checked
 308 // before the normal file-type headers, since the first-byte dispatch algo
 309 // doesn't work for these
 310 func checkSpecial(b []byte) (kind string, ok bool) {
 311     if len(b) >= 8 && bytes.Index(b, []byte{'f', 't', 'y', 'p'}) == 4 {
 312         for _, t := range specialHeaders {
 313             if hasPrefixPattern(b[4:], t.Header[4:], cba) {
 314                 return t.Type, true
 315             }
 316         }
 317     }
 318     return ``, false
 319 }
 320 
 321 // hasPrefixPattern works like bytes.HasPrefix, except it allows for a special
 322 // value to signal any byte is allowed on specific spots
 323 func hasPrefixPattern(what []byte, pat []byte, wildcard byte) bool {
 324     // if the data are shorter than the pattern to match, there's no match
 325     if len(what) < len(pat) {
 326         return false
 327     }
 328 
 329     // use a slice which ensures the pattern length is never exceeded
 330     what = what[:len(pat)]
 331 
 332     for i, x := range what {
 333         y := pat[i]
 334         if x != y && y != wildcard {
 335             return false
 336         }
 337     }
 338     return true
 339 }
 340 
 341 // all the MIME types used/recognized in this package
 342 const (
 343     aiff    = `audio/aiff`
 344     au      = `audio/basic`
 345     avi     = `video/avi`
 346     avif    = `image/avif`
 347     bmp     = `image/x-bmp`
 348     caf     = `audio/x-caf`
 349     cur     = `image/vnd.microsoft.icon`
 350     css     = `text/css`
 351     csv     = `text/csv`
 352     djvu    = `image/x-djvu`
 353     elf     = `application/x-elf`
 354     exe     = `application/vnd.microsoft.portable-executable`
 355     flac    = `audio/x-flac`
 356     gif     = `image/gif`
 357     gz      = `application/gzip`
 358     heic    = `image/heic`
 359     htm     = `text/html`
 360     html    = `text/html`
 361     ico     = `image/x-icon`
 362     iso     = `application/octet-stream`
 363     jpg     = `image/jpeg`
 364     jpeg    = `image/jpeg`
 365     js      = `application/javascript`
 366     json    = `application/json`
 367     m4a     = `audio/aac`
 368     m4v     = `video/x-m4v`
 369     mid     = `audio/midi`
 370     mov     = `video/quicktime`
 371     mp4     = `video/mp4`
 372     mp3     = `audio/mpeg`
 373     mpg     = `video/mpeg`
 374     ogg     = `audio/ogg`
 375     opus    = `audio/opus`
 376     pdf     = `application/pdf`
 377     png     = `image/png`
 378     ps      = `application/postscript`
 379     psd     = `image/vnd.adobe.photoshop`
 380     rtf     = `application/rtf`
 381     sqlite3 = `application/x-sqlite3`
 382     svg     = `image/svg+xml`
 383     text    = `text/plain`
 384     tiff    = `image/tiff`
 385     tsv     = `text/tsv`
 386     wasm    = `application/wasm`
 387     wav     = `audio/x-wav`
 388     webp    = `image/webp`
 389     webm    = `video/webm`
 390     xml     = `application/xml`
 391     zip     = `application/zip`
 392     zst     = `application/zstd`
 393 )
 394 
 395 // type2mime turns dotless format-names into MIME types
 396 var type2mime = map[string]string{
 397     `aiff`:    aiff,
 398     `wav`:     wav,
 399     `avi`:     avi,
 400     `jpg`:     jpg,
 401     `jpeg`:    jpeg,
 402     `m4a`:     m4a,
 403     `mp4`:     mp4,
 404     `m4v`:     m4v,
 405     `mov`:     mov,
 406     `png`:     png,
 407     `avif`:    avif,
 408     `webp`:    webp,
 409     `gif`:     gif,
 410     `tiff`:    tiff,
 411     `psd`:     psd,
 412     `flac`:    flac,
 413     `webm`:    webm,
 414     `mpg`:     mpg,
 415     `zip`:     zip,
 416     `gz`:      gz,
 417     `zst`:     zst,
 418     `mp3`:     mp3,
 419     `opus`:    opus,
 420     `bmp`:     bmp,
 421     `mid`:     mid,
 422     `ogg`:     ogg,
 423     `html`:    html,
 424     `htm`:     htm,
 425     `svg`:     svg,
 426     `xml`:     xml,
 427     `rtf`:     rtf,
 428     `pdf`:     pdf,
 429     `ps`:      ps,
 430     `au`:      au,
 431     `ico`:     ico,
 432     `cur`:     cur,
 433     `caf`:     caf,
 434     `heic`:    heic,
 435     `sqlite3`: sqlite3,
 436     `elf`:     elf,
 437     `exe`:     exe,
 438     `wasm`:    wasm,
 439     `iso`:     iso,
 440     `txt`:     text,
 441     `css`:     css,
 442     `csv`:     csv,
 443     `tsv`:     tsv,
 444     `js`:      js,
 445     `json`:    json,
 446     `geojson`: json,
 447 }
 448 
 449 // formatDescriptor ties a file-header pattern to its data-format type
 450 type formatDescriptor struct {
 451     Header []byte
 452     Type   string
 453 }
 454 
 455 // can be anything: ensure this value differs from all other literal bytes
 456 // in the generic-headers table: failing that, its value could cause subtle
 457 // type-misdetection bugs
 458 const cba = 0xFD // 253, which is > 127, the highest-valued ascii symbol
 459 
 460 // dash-streamed m4a format
 461 var m4aDash = []byte{
 462     cba, cba, cba, cba, 'f', 't', 'y', 'p', 'd', 'a', 's', 'h',
 463     000, 000, 000, 000, 'i', 's', 'o', '6', 'm', 'p', '4', '1',
 464 }
 465 
 466 // format markers with leading wildcards, which should be checked before the
 467 // normal ones: this is to prevent mismatches with the latter types, even
 468 // though you can make probabilistic arguments which suggest these mismatches
 469 // should be very unlikely in practice
 470 var specialHeaders = []formatDescriptor{
 471     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', ' '}, m4a},
 472     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', 000}, m4a},
 473     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', 'S', 'N', 'V'}, mp4},
 474     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, mp4},
 475     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, m4v},
 476     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'q', 't', ' ', ' '}, mov},
 477     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'}, heic},
 478     {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}, avif},
 479     {m4aDash, m4a},
 480 }
 481 
 482 // sqlite3 database format
 483 var sqlite3db = []byte{
 484     'S', 'Q', 'L', 'i', 't', 'e', ' ',
 485     'f', 'o', 'r', 'm', 'a', 't', ' ', '3',
 486     000,
 487 }
 488 
 489 // windows-variant bitmap file-header, which is followed by a byte-counter for
 490 // the 40-byte infoheader which follows that
 491 var winbmp = []byte{
 492     'B', 'M', cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, 40,
 493 }
 494 
 495 // deja-vu document format
 496 var djv = []byte{
 497     'A', 'T', '&', 'T', 'F', 'O', 'R', 'M', cba, cba, cba, cba, 'D', 'J', 'V',
 498 }
 499 
 500 var doctypeHTML = []byte{
 501     '<', '!', 'D', 'O', 'C', 'T', 'Y', 'P', 'E', ' ', 'h', 't', 'm', 'l',
 502 }
 503 
 504 // hdrDispatch groups format-description-groups by their first byte, thus
 505 // shortening total lookups for some data header: notice how the `ftyp` data
 506 // formats aren't handled here, since these can start with any byte, instead
 507 // of the literal value of the any-byte markers they use
 508 var hdrDispatch = [256][]formatDescriptor{
 509     {
 510         {[]byte{000, 000, 001, 0xBA}, mpg},
 511         {[]byte{000, 000, 001, 0xB3}, mpg},
 512         {[]byte{000, 000, 001, 000}, ico},
 513         {[]byte{000, 000, 002, 000}, cur},
 514         {[]byte{000, 'a', 's', 'm'}, wasm},
 515     }, // 0
 516     nil, // 1
 517     nil, // 2
 518     nil, // 3
 519     nil, // 4
 520     nil, // 5
 521     nil, // 6
 522     nil, // 7
 523     nil, // 8
 524     nil, // 9
 525     nil, // 10
 526     nil, // 11
 527     nil, // 12
 528     nil, // 13
 529     nil, // 14
 530     nil, // 15
 531     nil, // 16
 532     nil, // 17
 533     nil, // 18
 534     nil, // 19
 535     nil, // 20
 536     nil, // 21
 537     nil, // 22
 538     nil, // 23
 539     nil, // 24
 540     nil, // 25
 541     {
 542         {[]byte{0x1A, 0x45, 0xDF, 0xA3}, webm},
 543     }, // 26
 544     nil, // 27
 545     nil, // 28
 546     nil, // 29
 547     nil, // 30
 548     {
 549         // {[]byte{0x1F, 0x8B, 0x08, 0x08}, gz},
 550         {[]byte{0x1F, 0x8B, 0x08}, gz},
 551     }, // 31
 552     nil, // 32
 553     nil, // 33 !
 554     nil, // 34 "
 555     {
 556         {[]byte{'#', '!', ' '}, text},
 557         {[]byte{'#', '!', '/'}, text},
 558     }, // 35 #
 559     nil, // 36 $
 560     {
 561         {[]byte{'%', 'P', 'D', 'F'}, pdf},
 562         {[]byte{'%', '!', 'P', 'S'}, ps},
 563     }, // 37 %
 564     nil, // 38 &
 565     nil, // 39 '
 566     {
 567         {[]byte{0x28, 0xB5, 0x2F, 0xFD}, zst},
 568     }, // 40 (
 569     nil, // 41 )
 570     nil, // 42 *
 571     nil, // 43 +
 572     nil, // 44 ,
 573     nil, // 45 -
 574     {
 575         {[]byte{'.', 's', 'n', 'd'}, au},
 576     }, // 46 .
 577     nil, // 47 /
 578     nil, // 48 0
 579     nil, // 49 1
 580     nil, // 50 2
 581     nil, // 51 3
 582     nil, // 52 4
 583     nil, // 53 5
 584     nil, // 54 6
 585     nil, // 55 7
 586     {
 587         {[]byte{'8', 'B', 'P', 'S'}, psd},
 588     }, // 56 8
 589     nil, // 57 9
 590     nil, // 58 :
 591     nil, // 59 ;
 592     {
 593         // func checkDoc is better for these, since it's case-insensitive
 594         {doctypeHTML, html},
 595         {[]byte{'<', 's', 'v', 'g'}, svg},
 596         {[]byte{'<', 'h', 't', 'm', 'l', '>'}, html},
 597         {[]byte{'<', 'h', 'e', 'a', 'd', '>'}, html},
 598         {[]byte{'<', 'b', 'o', 'd', 'y', '>'}, html},
 599         {[]byte{'<', '?', 'x', 'm', 'l'}, xml},
 600     }, // 60 <
 601     nil, // 61 =
 602     nil, // 62 >
 603     nil, // 63 ?
 604     nil, // 64 @
 605     {
 606         {djv, djvu},
 607     }, // 65 A
 608     {
 609         {winbmp, bmp},
 610     }, // 66 B
 611     nil, // 67 C
 612     nil, // 68 D
 613     nil, // 69 E
 614     {
 615         {[]byte{'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'F'}, aiff},
 616         {[]byte{'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'C'}, aiff},
 617     }, // 70 F
 618     {
 619         {[]byte{'G', 'I', 'F', '8', '7', 'a'}, gif},
 620         {[]byte{'G', 'I', 'F', '8', '9', 'a'}, gif},
 621     }, // 71 G
 622     nil, // 72 H
 623     {
 624         {[]byte{'I', 'D', '3', 2}, mp3}, // ID3-format metadata
 625         {[]byte{'I', 'D', '3', 3}, mp3}, // ID3-format metadata
 626         {[]byte{'I', 'D', '3', 4}, mp3}, // ID3-format metadata
 627         {[]byte{'I', 'I', '*', 000}, tiff},
 628     }, // 73 I
 629     nil, // 74 J
 630     nil, // 75 K
 631     nil, // 76 L
 632     {
 633         {[]byte{'M', 'M', 000, '*'}, tiff},
 634         {[]byte{'M', 'T', 'h', 'd'}, mid},
 635         {[]byte{'M', 'Z', cba, 000, cba, 000}, exe},
 636         // {[]byte{'M', 'Z', 0x90, 000, 003, 000}, exe},
 637         // {[]byte{'M', 'Z', 0x78, 000, 001, 000}, exe},
 638         // {[]byte{'M', 'Z', 'P', 000, 002, 000}, exe},
 639     }, // 77 M
 640     nil, // 78 N
 641     {
 642         {[]byte{'O', 'g', 'g', 'S'}, ogg},
 643     }, // 79 O
 644     {
 645         {[]byte{'P', 'K', 003, 004}, zip},
 646     }, // 80 P
 647     nil, // 81 Q
 648     {
 649         {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'E', 'B', 'P'}, webp},
 650         {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'A', 'V', 'E'}, wav},
 651         {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'A', 'V', 'I', ' '}, avi},
 652     }, // 82 R
 653     {
 654         {sqlite3db, sqlite3},
 655     }, // 83 S
 656     nil, // 84 T
 657     nil, // 85 U
 658     nil, // 86 V
 659     nil, // 87 W
 660     nil, // 88 X
 661     nil, // 89 Y
 662     nil, // 90 Z
 663     nil, // 91 [
 664     nil, // 92 \
 665     nil, // 93 ]
 666     nil, // 94 ^
 667     nil, // 95 _
 668     nil, // 96 `
 669     nil, // 97 a
 670     nil, // 98 b
 671     {
 672         {[]byte{'c', 'a', 'f', 'f', 000, 001, 000, 000}, caf},
 673     }, // 99 c
 674     nil, // 100 d
 675     nil, // 101 e
 676     {
 677         {[]byte{'f', 'L', 'a', 'C'}, flac},
 678     }, // 102 f
 679     nil, // 103 g
 680     nil, // 104 h
 681     nil, // 105 i
 682     nil, // 106 j
 683     nil, // 107 k
 684     nil, // 108 l
 685     nil, // 109 m
 686     nil, // 110 n
 687     nil, // 111 o
 688     nil, // 112 p
 689     nil, // 113 q
 690     nil, // 114 r
 691     nil, // 115 s
 692     nil, // 116 t
 693     nil, // 117 u
 694     nil, // 118 v
 695     nil, // 119 w
 696     nil, // 120 x
 697     nil, // 121 y
 698     nil, // 122 z
 699     {
 700         {[]byte{'{', '\\', 'r', 't', 'f'}, rtf},
 701     }, // 123 {
 702     nil, // 124 |
 703     nil, // 125 }
 704     nil, // 126
 705     {
 706         {[]byte{127, 'E', 'L', 'F'}, elf},
 707     }, // 127
 708     nil, // 128
 709     nil, // 129
 710     nil, // 130
 711     nil, // 131
 712     nil, // 132
 713     nil, // 133
 714     nil, // 134
 715     nil, // 135
 716     nil, // 136
 717     {
 718         {[]byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A}, png},
 719     }, // 137
 720     nil, // 138
 721     nil, // 139
 722     nil, // 140
 723     nil, // 141
 724     nil, // 142
 725     nil, // 143
 726     nil, // 144
 727     nil, // 145
 728     nil, // 146
 729     nil, // 147
 730     nil, // 148
 731     nil, // 149
 732     nil, // 150
 733     nil, // 151
 734     nil, // 152
 735     nil, // 153
 736     nil, // 154
 737     nil, // 155
 738     nil, // 156
 739     nil, // 157
 740     nil, // 158
 741     nil, // 159
 742     nil, // 160
 743     nil, // 161
 744     nil, // 162
 745     nil, // 163
 746     nil, // 164
 747     nil, // 165
 748     nil, // 166
 749     nil, // 167
 750     nil, // 168
 751     nil, // 169
 752     nil, // 170
 753     nil, // 171
 754     nil, // 172
 755     nil, // 173
 756     nil, // 174
 757     nil, // 175
 758     nil, // 176
 759     nil, // 177
 760     nil, // 178
 761     nil, // 179
 762     nil, // 180
 763     nil, // 181
 764     nil, // 182
 765     nil, // 183
 766     nil, // 184
 767     nil, // 185
 768     nil, // 186
 769     nil, // 187
 770     nil, // 188
 771     nil, // 189
 772     nil, // 190
 773     nil, // 191
 774     nil, // 192
 775     nil, // 193
 776     nil, // 194
 777     nil, // 195
 778     nil, // 196
 779     nil, // 197
 780     nil, // 198
 781     nil, // 199
 782     nil, // 200
 783     nil, // 201
 784     nil, // 202
 785     nil, // 203
 786     nil, // 204
 787     nil, // 205
 788     nil, // 206
 789     nil, // 207
 790     nil, // 208
 791     nil, // 209
 792     nil, // 210
 793     nil, // 211
 794     nil, // 212
 795     nil, // 213
 796     nil, // 214
 797     nil, // 215
 798     nil, // 216
 799     nil, // 217
 800     nil, // 218
 801     nil, // 219
 802     nil, // 220
 803     nil, // 221
 804     nil, // 222
 805     nil, // 223
 806     nil, // 224
 807     nil, // 225
 808     nil, // 226
 809     nil, // 227
 810     nil, // 228
 811     nil, // 229
 812     nil, // 230
 813     nil, // 231
 814     nil, // 232
 815     nil, // 233
 816     nil, // 234
 817     nil, // 235
 818     nil, // 236
 819     nil, // 237
 820     nil, // 238
 821     nil, // 239
 822     nil, // 240
 823     nil, // 241
 824     nil, // 242
 825     nil, // 243
 826     nil, // 244
 827     nil, // 245
 828     nil, // 246
 829     nil, // 247
 830     nil, // 248
 831     nil, // 249
 832     nil, // 250
 833     nil, // 251
 834     nil, // 252
 835     nil, // 253
 836     nil, // 254
 837     {
 838         {[]byte{0xFF, 0xD8, 0xFF}, jpg},
 839         {[]byte{0xFF, 0xF3, 0x48, 0xC4, 0x00}, mp3},
 840         {[]byte{0xFF, 0xFB}, mp3},
 841     }, // 255
 842 }