Skip to content

Commit

Permalink
Merge pull request #240 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth authored Mar 28, 2024
2 parents d422b58 + 969616b commit 2e792b0
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## [Unreleased]
### Added
- Some more binary file types detection, by @HardNorth
### Changed
- `jackson-databind` dependency reverted to `api` type, by @HardNorth

## [5.2.7]
### Changed
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencies {
api ("com.squareup.retrofit2:retrofit:${project.retrofit_version}") {
exclude module: 'okhttp'
}
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.7.1'
api 'com.fasterxml.jackson.core:jackson-databind:2.12.7.1' // Access is needed by HTTP loggers to format JSON
implementation "com.squareup.retrofit2:converter-scalars:${project.retrofit_version}"
implementation ("com.squareup.retrofit2:converter-jackson:${project.retrofit_version}") {
exclude module: 'jackson-databind'
Expand Down
33 changes: 26 additions & 7 deletions src/main/java/com/epam/reportportal/utils/MimeTypeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
public class MimeTypeDetector {
private static final String UNKNOWN_TYPE = "application/octet-stream";
private static final String EXTENSION_DELIMITER = ".";
private static final int BYTES_TO_READ_FOR_DETECTION = 20;
private static final int BYTES_TO_READ_FOR_DETECTION = 128;

private static final Map<String, String> ADDITIONAL_EXTENSION_MAPPING = Collections.unmodifiableMap(new HashMap<String, String>() {{
put(".properties", "text/plain");
Expand All @@ -48,7 +48,7 @@ private MimeTypeDetector() {
throw new IllegalStateException("Static only class. No instances should exist for the class!");
}

private static int[] readDetectionBytes(@Nonnull InputStream is) throws IOException {
static int[] readDetectionBytes(@Nonnull InputStream is) throws IOException {
if (!is.markSupported()) {
// Trigger UnsupportedOperationException before reading the stream, no users should get there unless they hack with reflections
is.reset();
Expand All @@ -66,24 +66,38 @@ private static int[] readDetectionBytes(@Nonnull InputStream is) throws IOExcept
return bytes;
}

static boolean isBinary(@Nonnull InputStream is) throws IOException {
int[] bytes = readDetectionBytes(is);
for (int b : bytes) {
if (b == 0) {
return true;
}
}
return false;
}

@Nullable
static String guessContentTypeFromStream(@Nonnull InputStream is) throws IOException {
int[] bytes = readDetectionBytes(is);
if (bytes.length >= 8) {
if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47 // 4 bytes break
&& bytes[4] == 0x0d && bytes[5] == 0x0a && bytes[6] == 0x1a && bytes[7] == 0x0a) {
if (bytes[0] == 0x89 && bytes[1] == 'P' && bytes[2] == 'N' && bytes[3] == 'G' // 4 bytes break
&& bytes[4] == '\r' && bytes[5] == '\n' && bytes[6] == 0x1a && bytes[7] == '\n') {
return "image/png";
}
}
if (bytes.length >= 4) {
if (bytes[0] == 0x50 && bytes[1] == 0x4b && bytes[2] == 0x03 && bytes[3] == 0x04) {
if (bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 0x03 && bytes[3] == 0x04) {
// ZIPs
if (bytes.length >= 7 && bytes[4] == 0x14 && bytes[5] == 0x00 && bytes[6] == 0x08) {
return "application/java-archive";
}
return "application/zip";
}
if (bytes[0] == 0x25 && bytes[1] == 0x50 && bytes[2] == 0x44 && bytes[3] == 0x46) {
if (bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 0x05 && bytes[3] == 0x06) {
// Zero-length ZIP
return "application/zip";
}
if (bytes[0] == '%' && bytes[1] == 'P' && bytes[2] == 'D' && bytes[3] == 'F' && isBinary(is)) {
return "application/pdf";
}
if (bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF) {
Expand All @@ -94,6 +108,11 @@ static String guessContentTypeFromStream(@Nonnull InputStream is) throws IOExcep
}
}
}
if (bytes.length >= 2) {
if (bytes[0] == 'B' && bytes[1] == 'M' && isBinary(is)) {
return "image/bmp";
}
}
return null;
}

Expand Down Expand Up @@ -139,6 +158,6 @@ public static String detect(@Nonnull final ByteSource source, @Nullable final St
type = detectByExtensionInternal(resourceName);
}
}
return type == null ? UNKNOWN_TYPE : type;
return type == null ? isBinary(source.openStream()) ? UNKNOWN_TYPE : "text/plain" : type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

public class MimeTypeDetectorTest {

@SuppressWarnings("unused")
public static Iterable<Object[]> files() {
return Arrays.asList(
new Object[] { Paths.get("src/test/resources/pug/lucky.jpg").toFile(), "image/jpeg" },
Expand All @@ -52,8 +51,7 @@ public void test_mime_types_byte_source(File file, String expected) throws IOExc
Assertions.assertEquals(expected, MimeTypeDetector.detect(Utils.getFileAsByteSource(file), file.getName()));
}

@SuppressWarnings("unused")
public static Iterable<Object[]> binaryFiles() {
public static Iterable<Object[]> binaryFileTypes() {
return Arrays.asList(
new Object[] { Paths.get("src/test/resources/pug/lucky.jpg").toFile(), "image/jpeg" },
new Object[] { Paths.get("src/test/resources/pug/unlucky.jpg").toFile(), "image/jpeg" },
Expand All @@ -65,7 +63,7 @@ public static Iterable<Object[]> binaryFiles() {
}

@ParameterizedTest
@MethodSource("binaryFiles")
@MethodSource("binaryFileTypes")
public void test_mime_types_files_by_content_only(File file, String expected) throws IOException {
File testFile = Files.createTempFile("test_tmp_", null).toFile();
try (InputStream is = new FileInputStream(file)) {
Expand All @@ -76,7 +74,6 @@ public void test_mime_types_files_by_content_only(File file, String expected) th
Assertions.assertEquals(expected, MimeTypeDetector.detect(testFile));
}

@SuppressWarnings("unused")
public static Iterable<Object[]> binaryFilesFallback() {
return Arrays.asList(
new Object[] { Paths.get("src/test/resources/pug/lucky.jpg").toFile(), "image/jpeg" },
Expand All @@ -96,4 +93,23 @@ public void test_mime_types_files_by_content_only_fallback(File file, String exp
}
Assertions.assertEquals(expected, MimeTypeDetector.guessContentTypeFromStream(Utils.getFileAsByteSource(testFile).openStream()));
}

public static Iterable<Object[]> binaryFiles() {
return Arrays.asList(
new Object[] { Paths.get("src/test/resources/pug/lucky.jpg").toFile(), true },
new Object[] { Paths.get("src/test/resources/pug/unlucky.jpg").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/image.png").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/demo.zip").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/test.jar").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/test.pdf").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/test.bin").toFile(), true },
new Object[] { Paths.get("src/test/resources/files/proxy_auth_response.txt").toFile(), false }
);
}

@ParameterizedTest
@MethodSource("binaryFiles")
public void test_is_binary(File file, boolean expected) throws IOException {
Assertions.assertEquals(MimeTypeDetector.isBinary(Utils.getFileAsByteSource(file).openStream()), expected);
}
}

0 comments on commit 2e792b0

Please sign in to comment.