Skip to content

Commit

Permalink
Merge pull request #235 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth committed Feb 7, 2024
2 parents 736e35f + a02c08f commit 975c8c6
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/test/resources/files/test.bin binary
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up JDK 1.8
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '8'
Expand All @@ -47,6 +47,6 @@ jobs:
run: ./gradlew build

- name: Codecov upload
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up JDK 1.8
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '8'
Expand Down Expand Up @@ -107,7 +107,7 @@ jobs:

- name: Checkout develop branch
if: ${{ github.ref }} == 'master'
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: 'develop'
fetch-depth: 0
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Package Files #
*.jar
!src/test/resources/files/test.jar
!gradle/wrapper/**
*.war
*.ear
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## [Unreleased]
### Changed
- Improve MIME type detection in `MimeTypeDetector.detect`, methods, by @HardNorth

## [5.2.3]
### Changed
Expand Down
75 changes: 66 additions & 9 deletions src/main/java/com/epam/reportportal/utils/MimeTypeDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -35,16 +37,66 @@
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 Map<String, String> ADDITIONAL_EXTENSION_MAPPING =
Collections.unmodifiableMap(new HashMap<String, String>() {{
put(".properties", "text/plain");
}});
private static final Map<String, String> ADDITIONAL_EXTENSION_MAPPING = Collections.unmodifiableMap(new HashMap<String, String>() {{
put(".properties", "text/plain");
put(".json", "application/json");
}});

private MimeTypeDetector() {
throw new IllegalStateException("Static only class. No instances should exist for the class!");
}

private 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();
}
int[] bytes = new int[BYTES_TO_READ_FOR_DETECTION];
int readNum = 0;
int read;
while (((read = is.read()) != -1) && readNum < BYTES_TO_READ_FOR_DETECTION) {
bytes[readNum++] = read;
}
if (readNum < BYTES_TO_READ_FOR_DETECTION - 1) {
bytes = Arrays.copyOf(bytes, readNum);
}
is.reset();
return bytes;
}

@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) {
return "image/png";
}
}
if (bytes.length >= 4) {
if (bytes[0] == 0x50 && bytes[1] == 0x4b && 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) {
return "application/pdf";
}
if (bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF) {
// JPEGs
if (bytes[3] == 0xE0 || bytes[3] == 0xE1 || bytes[3] == 0xE8) {
// E0 - JPEG/JFIF; E1 - EXIF; E8 - SPIFF
return "image/jpeg";
}
}
}
return null;
}

private static String detectByExtensionInternal(String name) {
int extensionIndex = name.lastIndexOf(EXTENSION_DELIMITER);
if (extensionIndex >= 0) {
Expand All @@ -55,7 +107,11 @@ private static String detectByExtensionInternal(String name) {

@Nonnull
public static String detect(@Nonnull final File file) throws IOException {
String type = URLConnection.guessContentTypeFromStream(Utils.getFileAsByteSource(file).openStream());
ByteSource source = Utils.getFileAsByteSource(file);
String type = URLConnection.guessContentTypeFromStream(source.openStream());
if (type == null) {
type = guessContentTypeFromStream(source.openStream());
}
if (type == null) {
type = Files.probeContentType(file.toPath());
}
Expand All @@ -71,10 +127,11 @@ public static String detect(@Nonnull final File file) throws IOException {
@Nonnull
public static String detect(@Nonnull final ByteSource source, @Nullable final String resourceName) throws IOException {
String type = URLConnection.guessContentTypeFromStream(source.openStream());
if (resourceName != null) {
if (type == null) {
type = Files.probeContentType(Paths.get(resourceName));
}
if (type == null) {
type = guessContentTypeFromStream(source.openStream());
}
if (resourceName != null && type == null) {
type = Files.probeContentType(Paths.get(resourceName));
if (type == null) {
type = URLConnection.guessContentTypeFromName(resourceName);
}
Expand Down
36 changes: 34 additions & 2 deletions src/main/java/com/epam/reportportal/utils/files/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
public class Utils {

/**
* This is an util class and should not be instantiated.
* This is a util class and should not be instantiated.
*/
private Utils() {
}

private static final int KILOBYTE = 2 ^ 10;
private static final int KILOBYTE = (int) Math.pow(2, 10);

private static final int READ_BUFFER = 10 * KILOBYTE;

Expand Down Expand Up @@ -72,6 +72,38 @@ public static byte[] readInputStreamToBytes(@Nonnull InputStream is) throws IOEx
return readInputStreamToBytes(is, READ_BUFFER);
}

/**
* Copies an {@link InputStream} into on {@link OutputStream} byte-to-byte.
*
* @param is a stream to read from
* @param os a stream to write to
* @param bufferSize size of read buffer in bytes
* @return bytes copied
* @throws IOException in case of a read error
*/
public static int copyStreams(@Nonnull InputStream is, @Nonnull OutputStream os, int bufferSize) throws IOException {
byte[] buffer = new byte[bufferSize];
int read;
int total = 0;
while ((read = is.read(buffer)) > 0) {
total += read;
os.write(buffer, 0, read);
}
return total;
}

/**
* Copies an {@link InputStream} into on {@link OutputStream} byte-to-byte.
*
* @param is a stream to read from
* @param os a stream to write to
* @return bytes copied
* @throws IOException in case of a read error
*/
public static int copyStreams(@Nonnull InputStream is, @Nonnull OutputStream os) throws IOException {
return copyStreams(is, os, READ_BUFFER);
}

/**
* Reads an {@link InputStream} into an array of bytes.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,27 @@

package com.epam.reportportal.utils;

import com.epam.reportportal.utils.files.ByteSource;
import org.apache.commons.io.IOUtils;
import com.epam.reportportal.utils.files.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;

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" },
return Arrays.asList(
new Object[] { Paths.get("src/test/resources/pug/lucky.jpg").toFile(), "image/jpeg" },
new Object[] { new File("src/test/resources/files/simple_response.txt"), "text/plain" },
new Object[] { new File("src/test/resources/hello.json"), "application/json" },
new Object[] { new File("src/test/resources/logback-test.xml"), "application/xml" },
new Object[] { new File("src/test/resources/junit-platform.properties"), "text/plain" }
new Object[] { new File("src/test/resources/junit-platform.properties"), "text/plain" },
new Object[] { Paths.get("src/test/resources/files/test.bin").toFile(), "application/octet-stream" }
);
}

Expand All @@ -49,8 +49,51 @@ public void test_mime_types_files(File file, String expected) throws IOException
@ParameterizedTest
@MethodSource("files")
public void test_mime_types_byte_source(File file, String expected) throws IOException {
Assertions.assertEquals(expected,
MimeTypeDetector.detect(ByteSource.wrap(IOUtils.toByteArray(new FileInputStream(file))), file.getName())
Assertions.assertEquals(expected, MimeTypeDetector.detect(Utils.getFileAsByteSource(file), file.getName()));
}

@SuppressWarnings("unused")
public static Iterable<Object[]> binaryFiles() {
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" },
new Object[] { Paths.get("src/test/resources/files/image.png").toFile(), "image/png" },
new Object[] { Paths.get("src/test/resources/files/demo.zip").toFile(), "application/zip" },
new Object[] { Paths.get("src/test/resources/files/test.jar").toFile(), "application/java-archive" },
new Object[] { Paths.get("src/test/resources/files/test.pdf").toFile(), "application/pdf" }
);
}

@ParameterizedTest
@MethodSource("binaryFiles")
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)) {
try (OutputStream os = new FileOutputStream(testFile)) {
Utils.copyStreams(is, os);
}
}
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" },
new Object[] { Paths.get("src/test/resources/pug/unlucky.jpg").toFile(), "image/jpeg" },
new Object[] { Paths.get("src/test/resources/files/image.png").toFile(), "image/png" }
);
}

@ParameterizedTest
@MethodSource("binaryFilesFallback")
public void test_mime_types_files_by_content_only_fallback(File file, String expected) throws IOException {
File testFile = Files.createTempFile("test_tmp_", null).toFile();
try (InputStream is = new FileInputStream(file)) {
try (OutputStream os = new FileOutputStream(testFile)) {
Utils.copyStreams(is, os);
}
}
Assertions.assertEquals(expected, MimeTypeDetector.guessContentTypeFromStream(Utils.getFileAsByteSource(testFile).openStream()));
}
}
Binary file added src/test/resources/files/demo.zip
Binary file not shown.
Binary file added src/test/resources/files/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/test/resources/files/test.bin
Binary file not shown.
Binary file added src/test/resources/files/test.jar
Binary file not shown.
Binary file added src/test/resources/files/test.pdf
Binary file not shown.

0 comments on commit 975c8c6

Please sign in to comment.