file.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /*
  2. * Copyright (c) 2021, Valtteri Koskivuori <vkoskiv@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/MappedFile.h>
  7. #include <AK/Vector.h>
  8. #include <LibCompress/Gzip.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibCore/FileStream.h>
  11. #include <LibCore/MimeData.h>
  12. #include <LibGfx/ImageDecoder.h>
  13. #include <stdio.h>
  14. #include <sys/stat.h>
  15. #include <unistd.h>
  16. static Optional<String> description_only(String description, [[maybe_unused]] const String& path)
  17. {
  18. return description;
  19. }
  20. // FIXME: Ideally Gfx::ImageDecoder could tell us the image type directly.
  21. static Optional<String> image_details(const String& description, const String& path)
  22. {
  23. auto file_or_error = MappedFile::map(path);
  24. if (file_or_error.is_error())
  25. return {};
  26. auto& mapped_file = *file_or_error.value();
  27. auto image_decoder = Gfx::ImageDecoder::create((const u8*)mapped_file.data(), mapped_file.size());
  28. if (!image_decoder->is_valid())
  29. return {};
  30. return String::formatted("{}, {} x {}", description, image_decoder->width(), image_decoder->height());
  31. }
  32. static Optional<String> gzip_details(String description, const String& path)
  33. {
  34. auto file_or_error = MappedFile::map(path);
  35. if (file_or_error.is_error())
  36. return {};
  37. auto& mapped_file = *file_or_error.value();
  38. if (!Compress::GzipDecompressor::is_likely_compressed(mapped_file.bytes()))
  39. return {};
  40. auto gzip_details = Compress::GzipDecompressor::describe_header(mapped_file.bytes());
  41. if (!gzip_details.has_value())
  42. return {};
  43. return String::formatted("{}, {}", description, gzip_details.value());
  44. }
  45. #define ENUMERATE_MIME_TYPE_DESCRIPTIONS \
  46. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/gzip", "gzip compressed data", gzip_details) \
  47. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/javascript", "JavaScript source", description_only) \
  48. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/json", "JSON data", description_only) \
  49. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/pdf", "PDF document", description_only) \
  50. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/rtf", "Rich text file", description_only) \
  51. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/tar", "tape archive", description_only) \
  52. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/wasm", "WebAssembly bytecode", description_only) \
  53. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/x-7z-compressed", "7-Zip archive", description_only) \
  54. __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/midi", "MIDI sound", description_only) \
  55. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/blender", "Blender project file", description_only) \
  56. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/ext", "ext filesystem", description_only) \
  57. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/flac", "FLAC audio", description_only) \
  58. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/iso-9660", "ISO 9660 CD/DVD image", description_only) \
  59. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/isz", "Compressed ISO image", description_only) \
  60. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/lua-bytecode", "Lua bytecode", description_only) \
  61. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/matroska", "Matroska container", description_only) \
  62. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/nes-rom", "Nintendo Entertainment System ROM", description_only) \
  63. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/qcow", "qcow file", description_only) \
  64. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/raw-zlib", "raw zlib stream", description_only) \
  65. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/sqlite", "sqlite database", description_only) \
  66. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-31x-compressed", "Windows 3.1X compressed file", description_only) \
  67. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-95-compressed", "Windows 95 compressed file", description_only) \
  68. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/bmp", "BMP image data", image_details) \
  69. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/gif", "GIF image data", image_details) \
  70. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/jpeg", "JPEG image data", image_details) \
  71. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/png", "PNG image data", image_details) \
  72. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-bitmap", "PBM image data", image_details) \
  73. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-graymap", "PGM image data", image_details) \
  74. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-pixmap", "PPM image data", image_details) \
  75. __ENUMERATE_MIME_TYPE_DESCRIPTION("text/markdown", "Markdown document", description_only) \
  76. __ENUMERATE_MIME_TYPE_DESCRIPTION("text/x-shellscript", "POSIX shell script text executable", description_only)
  77. static Optional<String> get_description_from_mime_type(const String& mime, const String& path)
  78. {
  79. #define __ENUMERATE_MIME_TYPE_DESCRIPTION(mime_type, description, details) \
  80. if (String(mime_type) == mime) \
  81. return details(String(description), path);
  82. ENUMERATE_MIME_TYPE_DESCRIPTIONS;
  83. #undef __ENUMERATE_MIME_TYPE_DESCRIPTION
  84. return {};
  85. }
  86. int main(int argc, char** argv)
  87. {
  88. if (pledge("stdio rpath", nullptr) < 0) {
  89. perror("pledge");
  90. return 1;
  91. }
  92. Vector<const char*> paths;
  93. bool flag_mime_only = false;
  94. Core::ArgsParser args_parser;
  95. args_parser.set_general_help("Determine type of files");
  96. args_parser.add_option(flag_mime_only, "Only print mime type", "mime-type", 'I');
  97. args_parser.add_positional_argument(paths, "Files to identify", "files", Core::ArgsParser::Required::Yes);
  98. args_parser.parse(argc, argv);
  99. bool all_ok = true;
  100. for (auto path : paths) {
  101. auto file = Core::File::construct(path);
  102. if (!file->open(Core::OpenMode::ReadOnly)) {
  103. perror(path);
  104. all_ok = false;
  105. continue;
  106. }
  107. struct stat file_stat;
  108. if (lstat(path, &file_stat) < 0) {
  109. perror("lstat");
  110. return 1;
  111. }
  112. auto file_size_in_bytes = file_stat.st_size;
  113. if (file->is_directory()) {
  114. outln("{}: directory", path);
  115. } else if (!file_size_in_bytes) {
  116. outln("{}: empty", path);
  117. } else {
  118. // Read accounts for longest possible offset + signature we currently match against.
  119. auto bytes = file->read(0x9006);
  120. auto file_name_guess = Core::guess_mime_type_based_on_filename(path);
  121. auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(bytes.bytes()).value_or(file_name_guess);
  122. auto human_readable_description = get_description_from_mime_type(mime_type, String(path)).value_or(mime_type);
  123. outln("{}: {}", path, flag_mime_only ? mime_type : human_readable_description);
  124. }
  125. }
  126. return all_ok ? 0 : 1;
  127. }