file.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*
  2. * Copyright (c) 2021, Valtteri Koskivuori <vkoskiv@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Vector.h>
  7. #include <LibCompress/Gzip.h>
  8. #include <LibCore/ArgsParser.h>
  9. #include <LibCore/File.h>
  10. #include <LibCore/MappedFile.h>
  11. #include <LibCore/MimeData.h>
  12. #include <LibCore/System.h>
  13. #include <LibELF/Image.h>
  14. #include <LibELF/Validation.h>
  15. #include <LibGfx/ImageDecoder.h>
  16. #include <LibMain/Main.h>
  17. #include <stdio.h>
  18. #include <sys/stat.h>
  19. #include <unistd.h>
  20. static Optional<DeprecatedString> description_only(DeprecatedString description, [[maybe_unused]] DeprecatedString const& path)
  21. {
  22. return description;
  23. }
  24. // FIXME: Ideally Gfx::ImageDecoder could tell us the image type directly.
  25. static Optional<DeprecatedString> image_details(DeprecatedString const& description, DeprecatedString const& path)
  26. {
  27. auto file_or_error = Core::MappedFile::map(path);
  28. if (file_or_error.is_error())
  29. return {};
  30. auto& mapped_file = *file_or_error.value();
  31. auto mime_type = Core::guess_mime_type_based_on_filename(path);
  32. auto image_decoder = Gfx::ImageDecoder::try_create_for_raw_bytes(mapped_file.bytes(), mime_type);
  33. if (!image_decoder)
  34. return {};
  35. StringBuilder builder;
  36. builder.appendff("{}, {} x {}", description, image_decoder->width(), image_decoder->height());
  37. if (image_decoder->is_animated()) {
  38. builder.appendff(", animated with {} frames that loop", image_decoder->frame_count());
  39. int loop_count = image_decoder->loop_count();
  40. if (loop_count == 0)
  41. builder.appendff(" indefinitely");
  42. else
  43. builder.appendff(" {} {}", loop_count, loop_count == 1 ? "time" : "times");
  44. }
  45. return builder.to_deprecated_string();
  46. }
  47. static Optional<DeprecatedString> gzip_details(DeprecatedString description, DeprecatedString const& path)
  48. {
  49. auto file_or_error = Core::MappedFile::map(path);
  50. if (file_or_error.is_error())
  51. return {};
  52. auto& mapped_file = *file_or_error.value();
  53. if (!Compress::GzipDecompressor::is_likely_compressed(mapped_file.bytes()))
  54. return {};
  55. auto gzip_details = Compress::GzipDecompressor::describe_header(mapped_file.bytes());
  56. if (!gzip_details.has_value())
  57. return {};
  58. return DeprecatedString::formatted("{}, {}", description, gzip_details.value());
  59. }
  60. static Optional<DeprecatedString> elf_details(DeprecatedString description, DeprecatedString const& path)
  61. {
  62. auto file_or_error = Core::MappedFile::map(path);
  63. if (file_or_error.is_error())
  64. return {};
  65. auto& mapped_file = *file_or_error.value();
  66. auto elf_data = mapped_file.bytes();
  67. ELF::Image elf_image(elf_data);
  68. if (!elf_image.is_valid())
  69. return {};
  70. StringBuilder interpreter_path_builder;
  71. auto result_or_error = ELF::validate_program_headers(*(const ElfW(Ehdr)*)elf_data.data(), elf_data.size(), elf_data, &interpreter_path_builder);
  72. if (result_or_error.is_error() || !result_or_error.value())
  73. return {};
  74. auto interpreter_path = interpreter_path_builder.string_view();
  75. auto& header = *reinterpret_cast<const ElfW(Ehdr)*>(elf_data.data());
  76. auto bitness = header.e_ident[EI_CLASS] == ELFCLASS64 ? "64" : "32";
  77. auto byteorder = header.e_ident[EI_DATA] == ELFDATA2LSB ? "LSB" : "MSB";
  78. bool is_dynamically_linked = !interpreter_path.is_empty();
  79. DeprecatedString dynamic_section = DeprecatedString::formatted(", dynamically linked, interpreter {}", interpreter_path);
  80. return DeprecatedString::formatted("{} {}-bit {} {}, {}, version {} ({}){}",
  81. description,
  82. bitness,
  83. byteorder,
  84. ELF::Image::object_file_type_to_string(header.e_type).value_or("(?)"sv),
  85. ELF::Image::object_machine_type_to_string(header.e_machine).value_or("(?)"sv),
  86. header.e_ident[EI_ABIVERSION],
  87. ELF::Image::object_abi_type_to_string(header.e_ident[EI_OSABI]).value_or("(?)"sv),
  88. is_dynamically_linked ? dynamic_section : "");
  89. }
  90. #define ENUMERATE_MIME_TYPE_DESCRIPTIONS \
  91. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/gzip", "gzip compressed data", gzip_details) \
  92. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/javascript", "JavaScript source", description_only) \
  93. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/json", "JSON data", description_only) \
  94. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/pdf", "PDF document", description_only) \
  95. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/rtf", "Rich text file", description_only) \
  96. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/tar", "tape archive", description_only) \
  97. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/wasm", "WebAssembly bytecode", description_only) \
  98. __ENUMERATE_MIME_TYPE_DESCRIPTION("application/x-7z-compressed", "7-Zip archive", description_only) \
  99. __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/flac", "FLAC audio", description_only) \
  100. __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/midi", "MIDI notes", description_only) \
  101. __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/mpeg", "MP3 audio", description_only) \
  102. __ENUMERATE_MIME_TYPE_DESCRIPTION("audio/wave", "WAVE audio", description_only) \
  103. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/blender", "Blender project file", description_only) \
  104. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/elf", "ELF", elf_details) \
  105. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/ext", "ext filesystem", description_only) \
  106. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/iso-9660", "ISO 9660 CD/DVD image", description_only) \
  107. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/isz", "Compressed ISO image", description_only) \
  108. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/lua-bytecode", "Lua bytecode", description_only) \
  109. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/matroska", "Matroska container", description_only) \
  110. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/nes-rom", "Nintendo Entertainment System ROM", description_only) \
  111. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/qcow", "qcow file", description_only) \
  112. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/raw-zlib", "raw zlib stream", description_only) \
  113. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/sqlite", "sqlite database", description_only) \
  114. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-31x-compressed", "Windows 3.1X compressed file", description_only) \
  115. __ENUMERATE_MIME_TYPE_DESCRIPTION("extra/win-95-compressed", "Windows 95 compressed file", description_only) \
  116. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/bmp", "BMP image data", image_details) \
  117. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/gif", "GIF image data", image_details) \
  118. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/jpeg", "JPEG image data", image_details) \
  119. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/png", "PNG image data", image_details) \
  120. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/webp", "WebP image data", image_details) \
  121. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-bitmap", "PBM image data", image_details) \
  122. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-graymap", "PGM image data", image_details) \
  123. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-portable-pixmap", "PPM image data", image_details) \
  124. __ENUMERATE_MIME_TYPE_DESCRIPTION("image/x-qoi", "QOI image data", image_details) \
  125. __ENUMERATE_MIME_TYPE_DESCRIPTION("text/markdown", "Markdown document", description_only) \
  126. __ENUMERATE_MIME_TYPE_DESCRIPTION("text/x-shellscript", "POSIX shell script text executable", description_only)
  127. static Optional<DeprecatedString> get_description_from_mime_type(DeprecatedString const& mime, DeprecatedString const& path)
  128. {
  129. #define __ENUMERATE_MIME_TYPE_DESCRIPTION(mime_type, description, details) \
  130. if (DeprecatedString(mime_type) == mime) \
  131. return details(DeprecatedString(description), path);
  132. ENUMERATE_MIME_TYPE_DESCRIPTIONS;
  133. #undef __ENUMERATE_MIME_TYPE_DESCRIPTION
  134. return {};
  135. }
  136. ErrorOr<int> serenity_main(Main::Arguments arguments)
  137. {
  138. TRY(Core::System::pledge("stdio rpath"));
  139. Vector<DeprecatedString> paths;
  140. bool flag_mime_only = false;
  141. Core::ArgsParser args_parser;
  142. args_parser.set_general_help("Determine type of files");
  143. args_parser.add_option(flag_mime_only, "Only print mime type", "mime-type", 'I');
  144. args_parser.add_positional_argument(paths, "Files to identify", "files", Core::ArgsParser::Required::Yes);
  145. args_parser.parse(arguments);
  146. bool all_ok = true;
  147. // Read accounts for longest possible offset + signature we currently match against.
  148. auto buffer = TRY(ByteBuffer::create_uninitialized(0x9006));
  149. for (auto const& path : paths) {
  150. auto file_or_error = Core::File::open(path, Core::File::OpenMode::Read);
  151. if (file_or_error.is_error()) {
  152. perror(path.characters());
  153. all_ok = false;
  154. continue;
  155. }
  156. auto file = file_or_error.release_value();
  157. struct stat file_stat = TRY(Core::System::lstat(path));
  158. auto file_size_in_bytes = file_stat.st_size;
  159. if (S_ISDIR(file_stat.st_mode)) {
  160. outln("{}: directory", path);
  161. } else if (!file_size_in_bytes) {
  162. outln("{}: empty", path);
  163. } else {
  164. auto bytes = TRY(file->read(buffer));
  165. auto file_name_guess = Core::guess_mime_type_based_on_filename(path);
  166. auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(bytes).value_or(file_name_guess);
  167. auto human_readable_description = get_description_from_mime_type(mime_type, DeprecatedString(path)).value_or(mime_type);
  168. outln("{}: {}", path, flag_mime_only ? mime_type : human_readable_description);
  169. }
  170. }
  171. return all_ok ? 0 : 1;
  172. }