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