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 }