File: si.c 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 You can build this command-line app by running 27 28 cc -Wall -s -O3 -march=native -mtune=native -flto -o ./si ./si.c 29 */ 30 31 #include <arpa/inet.h> 32 #include <fcntl.h> 33 #include <netinet/tcp.h> 34 #include <stdbool.h> 35 #include <stdint.h> 36 #include <stdio.h> 37 #include <string.h> 38 #include <sys/socket.h> 39 #include <sys/wait.h> 40 #include <unistd.h> 41 42 #ifdef _WIN32 43 #include <windows.h> 44 #endif 45 46 #ifdef _WIN32 47 #define WSL 48 #endif 49 50 #ifdef RED_ERRORS 51 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 52 #ifdef __APPLE__ 53 #define ERROR_STYLE "\x1b[31m" 54 #endif 55 #define RESET_STYLE "\x1b[0m" 56 #else 57 #define ERROR_STYLE 58 #define RESET_STYLE 59 #endif 60 61 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 62 63 #ifndef OPEN_COMMAND 64 #define OPEN_COMMAND "xdg-open" 65 #endif 66 67 #ifndef IBUF_SIZE 68 #define IBUF_SIZE (32 * 1024) 69 #endif 70 71 // EMIT_CONST emits string constants without their final null byte 72 #define EMIT_CONST(w, x) write(w, x, sizeof(x) - 1) 73 74 const char* info = "" 75 "si [filenames/URIs...]\n" 76 "\n" 77 "\n" 78 "This app (Show It) shows data using your default web browser by auto-opening\n" 79 "tabs. When reading from stdin, the content-type is auto-detected: data are\n" 80 "then sent right away to the browser via localhost, using a random port among\n" 81 "the available ones.\n" 82 "\n" 83 "The localhost connection is available only until all data are transferred:\n" 84 "this means refreshing your browser tab will lose your content, replacing it\n" 85 "with a server-not-found message page.\n" 86 "\n" 87 "When given filenames and/or URIs, the browser tabs will point to their paths,\n" 88 "so accidentally reloading them doesn't make their content disappear, unless\n" 89 "those files are actually deleted between reloads.\n" 90 "\n" 91 "Dozens of common data-formats are recognized when piped from stdin, such as\n" 92 " - HTML (web pages)\n" 93 " - PDF\n" 94 " - pictures (PNG, JPEG, SVG, WEBP, GIF)\n" 95 " - audio (AAC, MP3, FLAC, WAV, AU, MIDI)\n" 96 " - video (MP4, MOV, WEBM, MKV, AVI)\n" 97 " - JSON\n" 98 " - generic UTF-8 plain-text\n" 99 "\n" 100 "Base64-encoded data URIs are auto-detected and decoded appropriately.\n" 101 ""; 102 103 #define default_mime_fallback "application/octet-stream" 104 105 // can be anything: ensure this value differs from all other literal bytes 106 // in the generic-headers table: failing that, its value could cause subtle 107 // type-misdetection bugs; the value is chosen to be `obviously` findable 108 // in the source, which also implies a constant beyond the ascii range, as 109 // ascii char-constants are also used in the tables 110 const unsigned char cba = 0xfd; // 253 111 112 #define aiff "audio/aiff" 113 #define au "audio/basic" 114 #define avi "video/avi" 115 #define avif "image/avif" 116 #define bmp "image/x-bmp" 117 #define caf "audio/x-caf" 118 #define cur "image/vnd.microsoft.icon" 119 #define css "text/css" 120 #define csv "text/csv" 121 #define djvu "image/x-djvu" 122 #define elf "application/x-elf" 123 #define exe "application/vnd.microsoft.portable-executable" 124 #define flac "audio/x-flac" 125 #define gif "image/gif" 126 #define gz "application/gzip" 127 #define heic "image/heic" 128 #define htm "text/html" 129 #define html "text/html" 130 #define ico "image/x-icon" 131 #define iso "application/octet-stream" 132 #define jpeg "image/jpeg" 133 #define js "application/javascript" 134 #define json "application/json" 135 #define m4a "audio/aac" 136 #define m4v "video/x-m4v" 137 #define midi "audio/midi" 138 #define mov "video/quicktime" 139 #define mp4 "video/mp4" 140 #define mp3 "audio/mpeg" 141 #define mpg "video/mpeg" 142 #define octet "application/octet-stream" 143 #define ogg "audio/ogg" 144 #define opus "audio/opus" 145 #define pdf "application/pdf" 146 #define png "image/png" 147 #define ps "application/postscript" 148 #define psd "image/vnd.adobe.photoshop" 149 #define rtf "application/rtf" 150 #define sqlite3 "application/x-sqlite3" 151 #define svg "image/svg+xml" 152 #define text "text/plain" 153 #define tiff "image/tiff" 154 #define tsv "text/tsv" 155 #define utf8 "text/plain; charset=UTF-8" 156 #define wasm "application/wasm" 157 #define wav "audio/x-wav" 158 #define webp "image/webp" 159 #define webm "video/webm" 160 #define xml "application/xml" 161 #define zip "application/zip" 162 #define zst "application/zstd" 163 164 // format_descriptor ties a file-header pattern to its data-format type; 165 // the 15-byte header-limit nicely aligns with the 1-byte length before it 166 typedef struct format_descriptor { 167 unsigned char header_length; 168 unsigned char header_bytes[15]; 169 const char* mime; 170 } format_descriptor; 171 172 // starts_as tries to match header data to the pattern given: this includes 173 // allowing `any byte` when the pattern indicates so, using a value reserved 174 // for that purpose 175 bool starts_as(const uint8_t* x, size_t xlen, const uint8_t* y, size_t ylen) { 176 // when header data aren't enough for a pattern, there's no match 177 if (xlen < ylen) { 178 return false; 179 } 180 181 for (size_t i = 0; i < ylen; i++) { 182 if (y[i] == cba) { 183 // `can be anything` value always matches 184 continue; 185 } 186 187 if (x[i] != y[i]) { 188 return false; 189 } 190 } 191 192 return true; 193 } 194 195 // not confident enough to actually use this, and replace all table entries 196 #define start_format_descriptor(...) \ 197 sizeof((unsigned char[]){ __VA_ARGS__ }) / sizeof(unsigned char), \ 198 { __VA_ARGS__ } 199 200 // format markers with leading wildcards, which should be checked before the 201 // normal ones: this is to prevent mismatches with the latter types, even 202 // though you can make probabilistic arguments which suggest these mismatches 203 // should be very unlikely in practice 204 format_descriptor special_headers[] = { 205 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', ' '}, m4a}, 206 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', 000}, m4a}, 207 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', 'S', 'N', 'V'}, mp4}, 208 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, mp4}, 209 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, m4v}, 210 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'q', 't', ' ', ' '}, mov}, 211 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'}, heic}, 212 {12, {cba, cba, cba, cba, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}, avif}, 213 // { 214 // 24, 215 // { 216 // cba, cba, cba, cba, 'f', 't', 'y', 'p', 'd', 'a', 's', 'h', 217 // 000, 000, 000, 000, 'i', 's', 'o', '6', 'm', 'p', '4', '1', 218 // }, 219 // m4a, 220 // }, 221 {0}, 222 }; 223 224 format_descriptor hdr_dispatch_0[] = { 225 {4, {000, 000, 001, 0xBA}, mpg}, 226 {4, {000, 000, 001, 0xB3}, mpg}, 227 {4, {000, 000, 001, 000}, ico}, 228 {4, {000, 000, 002, 000}, cur}, 229 {4, {000, 'a', 's', 'm'}, wasm}, 230 {0}, 231 }; 232 233 format_descriptor hdr_dispatch_26[] = { 234 {4, {0x1A, 0x45, 0xDF, 0xA3}, webm}, 235 {0}, 236 }; 237 238 format_descriptor hdr_dispatch_31[] = { 239 // {4, {0x1F, 0x8B, 0x08, 0x08}, gz}, 240 {3, {0x1F, 0x8B, 0x08}, gz}, 241 {0}, 242 }; 243 244 format_descriptor hdr_dispatch_35[] = { 245 {3, "#! ", text}, 246 {3, "#!/", text}, 247 {0}, 248 }; 249 250 format_descriptor hdr_dispatch_37[] = { 251 {4, "%PDF", pdf}, 252 {4, "%!PS", ps}, 253 {0}, 254 }; 255 256 format_descriptor hdr_dispatch_40[] = { 257 {4, {0x28, 0xB5, 0x2F, 0xFD}, zst}, 258 {0}, 259 }; 260 261 format_descriptor hdr_dispatch_46[] = { 262 {4, ".snd", au}, 263 {0}, 264 }; 265 266 format_descriptor hdr_dispatch_56[] = { 267 {4, "8BPS", psd}, 268 {0}, 269 }; 270 271 format_descriptor hdr_dispatch_60[] = { 272 {15, "<!DOCTYPE html>", html}, 273 {15, "<!DOCTYPE html ", html}, 274 {5, "<svg>", svg}, 275 {5, "<svg ", svg}, 276 {6, "<html>", html}, 277 {6, "<html ", html}, 278 {6, "<head>", html}, 279 {6, "<head ", html}, 280 {6, "<body>", html}, 281 {6, "<body ", html}, 282 {6, "<?xml>", xml}, 283 {6, "<?xml ", xml}, 284 {0}, 285 }; 286 287 format_descriptor hdr_dispatch_65[] = { 288 { 289 15, 290 { 291 'A', 'T', '&', 'T', 'F', 'O', 'R', 'M', 292 cba, cba, cba, cba, 'D', 'J', 'V', 293 }, 294 djvu, 295 }, 296 {0}, 297 }; 298 299 format_descriptor hdr_dispatch_66[] = { 300 { 301 15, 302 { 303 'B', 'M', cba, cba, cba, cba, cba, cba, 304 cba, cba, cba, cba, cba, cba, 0x28, 305 }, 306 bmp, 307 }, 308 {0}, 309 }; 310 311 format_descriptor hdr_dispatch_70[] = { 312 {12, {'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'F'}, aiff}, 313 {12, {'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'C'}, aiff}, 314 {0}, 315 }; 316 317 format_descriptor hdr_dispatch_71[] = { 318 {6, "GIF87a", gif}, 319 {6, "GIF89a", gif}, 320 {0}, 321 }; 322 323 format_descriptor hdr_dispatch_73[] = { 324 {4, {'I', 'D', '3', 2}, mp3}, // ID3-format metadata 325 {4, {'I', 'D', '3', 3}, mp3}, // ID3-format metadata 326 {4, {'I', 'D', '3', 4}, mp3}, // ID3-format metadata 327 {4, {'I', 'I', '*', 000}, tiff}, 328 {0}, 329 }; 330 331 format_descriptor hdr_dispatch_77[] = { 332 {4, {'M', 'M', 000, '*'}, tiff}, 333 {4, "MThd", midi}, 334 {6, {'M', 'Z', cba, 000, cba, 000}, exe}, 335 // {6, {'M', 'Z', 0x90, 000, 003, 000}, exe}, 336 // {6, {'M', 'Z', 0x78, 000, 001, 000}, exe}, 337 // {6, {'M', 'Z', 'P', 000, 002, 000}, exe}, 338 {0}, 339 }; 340 341 format_descriptor hdr_dispatch_79[] = { 342 {4, "OggS", ogg}, 343 {0}, 344 }; 345 346 format_descriptor hdr_dispatch_80[] = { 347 {4, {'P', 'K', 003, 004}, zip}, 348 {0}, 349 }; 350 351 format_descriptor hdr_dispatch_82[] = { 352 {12, {'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'E', 'B', 'P'}, webp}, 353 {12, {'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'A', 'V', 'E'}, wav}, 354 {12, {'R', 'I', 'F', 'F', cba, cba, cba, cba, 'A', 'V', 'I', ' '}, avi}, 355 {0}, 356 }; 357 358 // format_descriptor hdr_dispatch_83[] = { 359 // // {16, "SQLite format 3\x00", sqlite3}, 360 // {0}, 361 // }; 362 363 format_descriptor hdr_dispatch_99[] = { 364 {8, {'c', 'a', 'f', 'f', 000, 001, 000, 000}, caf}, 365 {0}, 366 }; 367 368 format_descriptor hdr_dispatch_102[] = { 369 {4, "fLaC", flac}, 370 {0}, 371 }; 372 373 format_descriptor hdr_dispatch_123[] = { 374 {4, "{\\rtf", rtf}, 375 {0}, 376 }; 377 378 format_descriptor hdr_dispatch_127[] = { 379 {4, {127, 'E', 'L', 'F'}, elf}, 380 {0}, 381 }; 382 383 format_descriptor hdr_dispatch_137[] = { 384 {8, {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A}, png}, 385 {0}, 386 }; 387 388 format_descriptor hdr_dispatch_255[] = { 389 {3, {0xFF, 0xD8, 0xFF}, jpeg}, 390 {5, {0xFF, 0xF3, 0x48, 0xC4, 0x00}, mp3}, 391 {2, {0xFF, 0xFB}, mp3}, 392 {0}, 393 }; 394 395 // hdr_dispatch groups format-description-groups by their first byte, thus 396 // shortening total lookups for some data header 397 // 398 // notice how the `ftyp` data formats aren't handled here, since these can 399 // start with any byte, instead of the literal value of the any-byte markers 400 // they use 401 // 402 // all entries are arrays which must always end with a special entry whose 403 // pattern-length is declared to be 0, since there's no explicit way to know 404 // the length of these arrays when looping on them 405 // 406 // all non-null entries are setup explicitly, later in the code 407 format_descriptor* hdr_dispatch[256] = { 408 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 409 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 410 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 411 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 412 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 413 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 414 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 415 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 416 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 417 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 418 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 419 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 420 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 421 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 422 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 423 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 424 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 425 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 426 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 427 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 428 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 429 NULL, NULL, NULL, NULL, 430 }; 431 432 typedef struct guess_mime_args { 433 const unsigned char* buf; 434 size_t len; 435 const char* fallback_mime_type; 436 } guess_mime_args; 437 438 // guess_mime tries to auto-detect a MIME-type from the header bytes given, 439 // using the lookup-tables 440 const char* guess_mime(guess_mime_args args) { 441 const unsigned char* buf = args.buf; 442 const size_t len = args.len; 443 const char* fallback_mime_type = args.fallback_mime_type; 444 445 if (len == 0) { 446 return NULL; 447 } 448 449 // just in case, start with the patterns which allow any first byte 450 for (size_t i = 0; special_headers[i].header_length > 0; i++) { 451 const unsigned char* hb = special_headers[i].header_bytes; 452 const size_t hl = special_headers[i].header_length; 453 if (starts_as(buf, len, hb, hl)) { 454 return special_headers[i].mime; 455 } 456 } 457 458 // the m4a-dash header exceeds the 15-byte limit of the lookup tables 459 const uint8_t header1[24] = { 460 cba, cba, cba, cba, 'f', 't', 'y', 'p', 'd', 'a', 's', 'h', 461 000, 000, 000, 000, 'i', 's', 'o', '6', 'm', 'p', '4', '1', 462 }; 463 if (starts_as(buf, len, header1, sizeof(header1))) { 464 return m4a; 465 } 466 467 // the sqlite3 header exceeds the 15-byte limit of the lookup tables 468 const uint8_t header2[16] = "SQLite format 3\x00"; 469 if (starts_as(buf, len, header2, sizeof(header2))) { 470 return sqlite3; 471 } 472 473 format_descriptor* guesses = hdr_dispatch[buf[0]]; 474 if (guesses == NULL) { 475 return fallback_mime_type; 476 } 477 478 for (size_t i = 0; guesses[i].header_length > 0; i++) { 479 const unsigned char* hb = guesses[i].header_bytes; 480 const size_t hl = guesses[i].header_length; 481 if (starts_as(buf, len, hb, hl)) { 482 return guesses[i].mime; 483 } 484 } 485 486 return fallback_mime_type; 487 } 488 489 #ifdef WSL 490 bool pop_up(const char* s) { 491 return execlp("cmd.exe", "/b", "/c", "start", "", s, NULL) >= 0; 492 // return execlp("rundll32", "url.dll,FileProtocolHandler", s, NULL) >= 0; 493 } 494 #else 495 bool pop_up(const char* what) { 496 return execlp(OPEN_COMMAND, what, NULL) >= 0; 497 } 498 #endif 499 500 bool pop_up_wait(const char* what) { 501 int code = 0; 502 pid_t pid = fork(); 503 if (pid < 0) { 504 return false; 505 } 506 if (pid == 0) { 507 code = pop_up(what) ? 0 : 1; 508 } else { 509 code = 1; 510 waitpid(pid, &code, 0); 511 } 512 return code == 0; 513 } 514 515 void ignore_request(const int conn) { 516 char buf[4 * 1024]; 517 518 while (true) { 519 const size_t n = recv(conn, buf, sizeof(buf), 0); 520 if (n < 4) { 521 return; 522 } 523 524 if ( 525 buf[n - 4] == '\r' && buf[n - 3] == '\n' && 526 buf[n - 2] == '\r' && buf[n - 1] == '\n' 527 ) { 528 return; 529 } 530 } 531 } 532 533 bool sync_send(int conn, const unsigned char* buf, size_t n) { 534 struct tcp_info tcp_info; 535 socklen_t size = sizeof(tcp_info); 536 send(conn, buf, n, 0); 537 538 for (size_t i = 0; i < 10; i++) { 539 getsockopt(conn, SOL_TCP, TCP_INFO, &tcp_info, &size); 540 if (tcp_info.tcpi_unacked <= 0) { 541 return true; 542 } 543 usleep(1000); 544 } 545 546 return false; 547 } 548 549 size_t send_all(int conn, const unsigned char* buf, size_t n) { 550 size_t sent = 0; 551 const size_t min_delay = 50 * 1000; 552 // const size_t max_delay = 50 * 1000; 553 size_t delay = min_delay; 554 555 for (size_t i = 0; i < 100 && n > 0; i++) { 556 const size_t delta = send(conn, buf, n, 0); 557 if (i > 0) { 558 usleep(delay); 559 } 560 561 n -= delta; 562 buf += delta; 563 sent += delta; 564 565 if (n > 0) { 566 fprintf(stderr, "send_all: %zu bytes still pending\n", n); 567 } 568 } 569 570 return sent; 571 } 572 573 bool handle_reader(FILE* src, const char* fallback_mime_type) { 574 unsigned char buf[IBUF_SIZE]; 575 576 size_t n = fread(&buf, sizeof(buf[0]), sizeof(buf), src); 577 guess_mime_args args; 578 args.buf = buf; 579 args.len = n; 580 args.fallback_mime_type = fallback_mime_type; 581 const char* mime = guess_mime(args); 582 if (mime == NULL || mime[0] == 0) { 583 return false; 584 } 585 586 const int fd = socket(AF_INET, SOCK_STREAM, 0); 587 if (fd < 0) { 588 fprintf(stderr, ERROR_LINE("can't open socket connection")); 589 return false; 590 } 591 if (fcntl(fd, F_GETFD) == -1) { 592 close(fd); 593 fprintf(stderr, ERROR_LINE("can't open socket connection")); 594 return false; 595 } 596 597 struct sockaddr_in srv; 598 memset(&srv, 0, sizeof(srv)); 599 srv.sin_family = AF_INET; 600 srv.sin_port = htons(0); 601 srv.sin_addr.s_addr = htonl(INADDR_ANY); 602 // srv.sin_addr.s_addr = inet_addr("0.0.0.0"); 603 604 if (bind(fd, (struct sockaddr*)&srv, sizeof(srv)) != 0) { 605 close(fd); 606 fprintf(stderr, ERROR_LINE("can't open socket connection")); 607 return false; 608 } 609 610 if (listen(fd, 1) != 0) { 611 close(fd); 612 fprintf(stderr, ERROR_LINE("can't listen on open connection")); 613 return false; 614 } 615 616 // get the randomly-picked port number 617 socklen_t size = sizeof(srv); 618 if (getsockname(fd, (struct sockaddr*)&srv, &size) < 0) { 619 fprintf(stderr, ERROR_LINE("can't get socket name")); 620 return false; 621 } 622 623 char uri[256]; 624 const int port = ntohs(srv.sin_port); 625 sprintf(uri, "http://localhost:%d", port); 626 // fprintf(stderr, "opening %s\n", uri); 627 628 pop_up_wait(uri); 629 630 const int conn = accept(fd, NULL, NULL); 631 if (conn < 0) { 632 close(fd); 633 fprintf(stderr, ERROR_LINE("failed to accept connection")); 634 return false; 635 } 636 637 ignore_request(conn); 638 639 EMIT_CONST(conn, "HTTP/1.1 200 OK\r\n"); 640 EMIT_CONST(conn, "Content-Type: "); 641 write(conn, mime, strlen(mime)); 642 EMIT_CONST(conn, "\r\n"); 643 EMIT_CONST(conn, "Content-Disposition: inline\r\n"); 644 EMIT_CONST(conn, "Connection: close\r\n"); 645 EMIT_CONST(conn, "\r\n"); 646 647 size_t got = n; 648 // size_t sent = send_all(conn, buf, n); 649 size_t sent = send(conn, buf, n, 0); 650 const size_t delay = 100 * 1000; 651 652 while (true) { 653 // while (!feof(src)) { 654 // n = fread(&buf, sizeof(buf[0]), sizeof(buf), src); 655 // if (n < 1) { 656 // usleep(delay); 657 // continue; 658 // } 659 n = fread(&buf, sizeof(buf[0]), sizeof(buf) - 1, src); 660 int last = fgetc(src); 661 if (last != EOF) { 662 buf[n] = (unsigned char)last; 663 n++; 664 } 665 if (n < 1 && last != EOF) { 666 usleep(delay); 667 continue; 668 } 669 670 got += n; 671 // const size_t delta = send_all(conn, buf, n); 672 const size_t delta = send(conn, buf, n, 0); 673 sent += delta; 674 fprintf(stderr, "stdin eof: %d\n", feof(src)); 675 fprintf(stderr, "so far, got %zu bytes, sent %zu bytes\n", got, sent); 676 if (delta == 0) { 677 break; 678 } 679 if (last == EOF) { 680 break; 681 } 682 } 683 684 shutdown(conn, SHUT_WR); 685 close(conn); 686 close(fd); 687 return true; 688 } 689 690 // is_help_option simplifies control-flow for func run 691 bool is_help_option(const char* s) { 692 return s[0] == '-' && ( 693 strcmp(s, "-h") == 0 || 694 strcmp(s, "-help") == 0 || 695 strcmp(s, "--h") == 0 || 696 strcmp(s, "--help") == 0 697 ); 698 } 699 700 // is_fallback_option simplifies control-flow for func run 701 bool is_fallback_option(const char* s) { 702 return s[0] == '-' && ( 703 strcmp(s, "-f") == 0 || 704 strcmp(s, "-fallback") == 0 || 705 strcmp(s, "--f") == 0 || 706 strcmp(s, "--fallback") == 0 || 707 strcmp(s, "-m") == 0 || 708 strcmp(s, "-mime") == 0 || 709 strcmp(s, "--m") == 0 || 710 strcmp(s, "--mime") == 0 || 711 strcmp(s, "-t") == 0 || 712 strcmp(s, "-type") == 0 || 713 strcmp(s, "--t") == 0 || 714 strcmp(s, "--type") == 0 715 ); 716 } 717 718 const char* fallback_aliases[196] = { 719 // tiny shortcuts 720 "b", octet, 721 "j", json, 722 "t", text, 723 "u", utf8, 724 725 // failure fallbacks 726 "e", "", 727 "err", "", 728 "error", "", 729 "f", "", 730 "fail", "", 731 732 // common mistakes 733 "text/json", json, 734 735 // other 736 "", default_mime_fallback, 737 "default", default_mime_fallback, 738 739 "aif", aiff, 740 "aiff", aiff, 741 "au", au, 742 "avi", avi, 743 "avif", avif, 744 "bmp", bmp, 745 "caf", caf, 746 "cur", cur, 747 "css", css, 748 "csv", csv, 749 "djvu", djvu, 750 "elf", elf, 751 "exe", exe, 752 "flac", flac, 753 "gif", gif, 754 "gz", gz, 755 "heic", heic, 756 "html", html, 757 "ico", ico, 758 "iso", iso, 759 "jpg", jpeg, 760 "jpeg", jpeg, 761 "js", js, 762 "json", json, 763 "m4a", m4a, 764 "m4v", m4v, 765 "midi", midi, 766 "mov", mov, 767 "mp4", mp4, 768 "mp3", mp3, 769 "mpeg", mpg, 770 "ogg", ogg, 771 "opus", opus, 772 "pdf", pdf, 773 "png", png, 774 "ps", ps, 775 "psd", psd, 776 "rtf", rtf, 777 "sqlite3", sqlite3, 778 "svg", svg, 779 "text", text, 780 "tiff", tiff, 781 "tsv", tsv, 782 "wasm", wasm, 783 "wav", wav, 784 "webp", webp, 785 "webm", webm, 786 "xml", xml, 787 "zip", zip, 788 "zst", zst, 789 790 // longer shortcuts 791 "aac", m4a, 792 "aif", aiff, 793 "bin", octet, 794 "binary", octet, 795 "bits", octet, 796 "gzip", gz, 797 "htm", htm, 798 "mid", midi, 799 "mpg", mpg, 800 "octet", octet, 801 "octets", octet, 802 "octetstream", octet, 803 "octet-stream", octet, 804 "plain", text, 805 "sqlite", sqlite3, 806 "svg+xml", svg, 807 "tif", tiff, 808 "utf8", utf8, 809 "utf-8", utf8, 810 "xbmp", bmp, 811 "xcaf", caf, 812 "xflac", flac, 813 "xicon", ico, 814 "xm4v", m4v, 815 "xsqlite3", sqlite3, 816 "xwav", wav, 817 "xwave", wav, 818 "x-bmp", bmp, 819 "x-caf", caf, 820 "x-flac", flac, 821 "x-icon", ico, 822 "x-m4v", m4v, 823 "x-sqlite3", sqlite3, 824 "x-wav", wav, 825 "wave", wav, 826 "zstd", zst, 827 }; 828 829 const char* resolve_alias(const char* name) { 830 const size_t n = sizeof(fallback_aliases) / sizeof(fallback_aliases[0]); 831 for (size_t i = 0; i < n; i += 2) { 832 if (strcmp(name, fallback_aliases[i]) == 0) { 833 return fallback_aliases[i + 1]; 834 } 835 } 836 return name; 837 } 838 839 // run returns the number of errors 840 int run(int argc, char** argv) { 841 const char* fallback_mime_type = default_mime_fallback; 842 843 size_t files = 0; 844 size_t dashes = 0; 845 size_t errors = 0; 846 bool change_fallback = false; 847 bool options = true; 848 849 // handle all filenames given 850 for (size_t i = 1; i < argc; i++) { 851 if (strcmp(argv[i], "-") == 0) { 852 dashes++; 853 if (dashes < 2) { 854 if (!handle_reader(stdin, fallback_mime_type)) { 855 errors++; 856 } 857 files++; 858 } 859 continue; 860 } 861 862 if (strcmp(argv[i], "--") == 0) { 863 options = false; 864 continue; 865 } 866 867 if (change_fallback) { 868 fallback_mime_type = resolve_alias(argv[i]); 869 change_fallback = false; 870 continue; 871 } 872 873 if (options && is_fallback_option(argv[i])) { 874 change_fallback = true; 875 continue; 876 } 877 878 if (!pop_up_wait(argv[i])) { 879 errors++; 880 } 881 files++; 882 } 883 884 if (change_fallback) { 885 fprintf(stderr, ERROR_LINE("forgot new fallback MIME-type")); 886 errors++; 887 return errors; 888 } 889 890 // no filenames means use stdin as the only input 891 if (files == 0) { 892 if (!handle_reader(stdin, fallback_mime_type)) { 893 errors++; 894 } 895 } 896 897 return errors; 898 } 899 900 int main(int argc, char** argv) { 901 #ifdef _WIN32 902 setmode(fileno(stdin), O_BINARY); 903 // ensure output lines end in LF instead of CRLF on windows 904 setmode(fileno(stdout), O_BINARY); 905 setmode(fileno(stderr), O_BINARY); 906 #endif 907 908 if (argc > 1 && is_help_option(argv[1])) { 909 printf("%s", info); 910 return 0; 911 } 912 913 // fill entries in the type-detection dispatch table 914 memset(hdr_dispatch, 0, sizeof(hdr_dispatch)); 915 hdr_dispatch[0] = hdr_dispatch_0; // 0 916 hdr_dispatch[26] = hdr_dispatch_26; // 26 917 hdr_dispatch[31] = hdr_dispatch_31; // 31 918 hdr_dispatch[35] = hdr_dispatch_35; // 35 # 919 hdr_dispatch[37] = hdr_dispatch_37; // 37 % 920 hdr_dispatch[40] = hdr_dispatch_40; // 40 ( 921 hdr_dispatch[46] = hdr_dispatch_46; // 46 . 922 hdr_dispatch[56] = hdr_dispatch_56; // 56 8 923 hdr_dispatch[60] = hdr_dispatch_60; // 60 < 924 hdr_dispatch[65] = hdr_dispatch_65; // 65 A 925 hdr_dispatch[66] = hdr_dispatch_66; // 66 B 926 hdr_dispatch[70] = hdr_dispatch_70; // 70 F 927 hdr_dispatch[71] = hdr_dispatch_71; // 71 G 928 hdr_dispatch[73] = hdr_dispatch_73; // 73 I 929 hdr_dispatch[77] = hdr_dispatch_77; // 77 M 930 hdr_dispatch[79] = hdr_dispatch_79; // 79 O 931 hdr_dispatch[80] = hdr_dispatch_80; // 80 P 932 hdr_dispatch[82] = hdr_dispatch_82; // 82 R 933 // hdr_dispatch[83] = hdr_dispatch_83; // 83 S 934 hdr_dispatch[99] = hdr_dispatch_99; // 99 c 935 hdr_dispatch[102] = hdr_dispatch_102; // 102 f 936 hdr_dispatch[123] = hdr_dispatch_123; // 123 { 937 hdr_dispatch[127] = hdr_dispatch_127; // 127 938 hdr_dispatch[137] = hdr_dispatch_137; // 137 939 hdr_dispatch[255] = hdr_dispatch_255; // 255 940 941 // setvbuf(stdin, NULL, _IOFBF, 0); 942 // setvbuf(stdin, NULL, _IONBF, 0); 943 return run(argc, argv) == 0 ? 0 : 1; 944 }