/src/magic.rs

https://github.com/lunaryorn/mdcat · Rust · 111 lines · 90 code · 13 blank · 8 comment · 7 complexity · 0d9b75bd4857f36e6eedf8f1360d9131 MD5 · raw file

  1. // Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>
  2. // This Source Code Form is subject to the terms of the Mozilla Public
  3. // License, v. 2.0. If a copy of the MPL was not distributed with this
  4. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. //! Magic util functions for detecting image types.
  6. use anyhow::{anyhow, Context, Result};
  7. use mime::Mime;
  8. use std::io::prelude::*;
  9. use std::io::ErrorKind;
  10. use std::process::*;
  11. /// Whether the given MIME type denotes an SVG image.
  12. pub fn is_svg(mime: &Mime) -> bool {
  13. mime.type_() == mime::IMAGE && mime.subtype().as_str() == "svg"
  14. }
  15. /// Whether the given MIME type denotes a PNG image.
  16. pub fn is_png(mime: &Mime) -> bool {
  17. *mime == mime::IMAGE_PNG
  18. }
  19. /// Detect mime type with `file`.
  20. pub fn detect_mime_type(buffer: &[u8]) -> Result<Mime> {
  21. let mut process = Command::new("file")
  22. .arg("--brief")
  23. .arg("--mime-type")
  24. .arg("-")
  25. .stdin(Stdio::piped())
  26. .stdout(Stdio::piped())
  27. .stderr(Stdio::piped())
  28. .spawn()
  29. .with_context(|| "Failed to spawn mime --brief --mime-type")?;
  30. process
  31. .stdin
  32. .as_mut()
  33. .expect("Forgot to pipe stdin?")
  34. .write_all(buffer)
  35. .or_else(|error| match error.kind() {
  36. ErrorKind::BrokenPipe => Ok(()),
  37. _ => Err(error),
  38. })?;
  39. let output = process
  40. .wait_with_output()
  41. .with_context(|| "Failed to read output from mime --brief --mime-type")?;
  42. if output.status.success() {
  43. let stdout = std::str::from_utf8(&output.stdout)
  44. .with_context(|| {
  45. format!(
  46. "mime --brief --mime-type returned non-utf8: {:?}",
  47. output.stdout
  48. )
  49. })?
  50. .trim();
  51. stdout
  52. .parse()
  53. .with_context(|| format!("Failed to parse mime type from output: {}", stdout))
  54. } else {
  55. Err(anyhow!(
  56. "file --brief --mime-type failed with status {}: {}",
  57. output.status,
  58. String::from_utf8_lossy(&output.stderr)
  59. ))
  60. }
  61. }
  62. #[cfg(test)]
  63. mod tests {
  64. use super::*;
  65. use pretty_assertions::assert_eq;
  66. #[test]
  67. fn detect_mimetype_of_png_image() {
  68. let data = include_bytes!("../sample/rust-logo-128x128.png");
  69. let result = detect_mime_type(data);
  70. assert!(result.is_ok(), "Unexpected error: {:?}", result);
  71. assert_eq!(result.unwrap(), mime::IMAGE_PNG);
  72. }
  73. #[test]
  74. fn detect_mimetype_of_svg_image() {
  75. let data = include_bytes!("../sample/rust-logo.svg");
  76. let result = detect_mime_type(data);
  77. assert!(result.is_ok(), "Unexpected error: {:?}", result);
  78. let mime = result.unwrap();
  79. assert_eq!(mime.type_(), mime::IMAGE);
  80. assert_eq!(mime.subtype().as_str(), "svg");
  81. }
  82. #[test]
  83. fn detect_mimetype_of_magic_param_bytes_max_length() {
  84. let data = std::iter::repeat(b'\0')
  85. .take(1_048_576)
  86. .collect::<Vec<u8>>();
  87. let result = detect_mime_type(&data);
  88. assert!(result.is_ok(), "Unexpected error: {:?}", result);
  89. }
  90. #[test]
  91. fn detect_mimetype_of_larger_than_magic_param_bytes_max_length() {
  92. let data = std::iter::repeat(b'\0')
  93. .take(1_048_576 * 2)
  94. .collect::<Vec<u8>>();
  95. let result = detect_mime_type(&data);
  96. assert!(result.is_ok(), "Unexpected error: {:?}", result);
  97. }
  98. }