Skip to content

Instantly share code, notes, and snippets.

@erizzo
Created February 3, 2023 23:13
Show Gist options
  • Save erizzo/deb52e8f74f937d2ea2aa74f494dcb9f to your computer and use it in GitHub Desktop.
Save erizzo/deb52e8f74f937d2ea2aa74f494dcb9f to your computer and use it in GitHub Desktop.
import static org.springframework.http.MediaType.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import static java.lang.System.arraycopy;
@Component
public class MediaTypeDetector {
private static final int SIGNATURE_MAX_LENGTH = 11;
private static final int END_OF_SIGNATURE = -2;
private static final String APP = "application";
private static final String IMAGE = "image";
private static final String AUDIO = "audio";
public static final MediaType APP_EPS = new MediaType(APP, "eps");
public static final MediaType APP_JAVA = new MediaType(APP, "java");
public static final MediaType APP_JAVA_OBJECT = new MediaType(APP, "x-java-serialized-object");
public static final MediaType APP_PS = new MediaType(APP, "postscript");
public static final MediaType APP_ZIP = new MediaType(APP, "zip");
public static final MediaType AUDIO_BASIC = new MediaType(AUDIO, "basic");
public static final MediaType AUDIO_MP3 = new MediaType(AUDIO, "mp3");
public static final MediaType AUDIO_WAV = new MediaType(AUDIO, "x-wav");
public static final MediaType IMAGE_BMP = new MediaType(IMAGE, "bmp");
public static final MediaType IMAGE_SVG = new MediaType(IMAGE, "svg+xml");
public static final MediaType IMAGE_TIFF = new MediaType(IMAGE, "tiff");
private static final List<Signature> Signatures = new LinkedList<>();
static {
Signatures.add(Signature.of(IMAGE_SVG, 0x3C, 0x73, 0x76, 0x67, 0x20)); // "<svg "
Signatures.add(Signature.of(IMAGE_PNG, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A));
Signatures.add(Signature.of(IMAGE_JPEG, 0xFF, 0xD8, 0xFF, 0xE0));
Signatures.add(Signature.of(IMAGE_JPEG, 0xFF, 0xD8, 0xFF, 0xEE));
Signatures.add(Signature.of(IMAGE_JPEG, 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00));
Signatures.add(Signature.of(IMAGE_TIFF, 0x49, 0x49, 0x2A, 0x00));
Signatures.add(Signature.of(IMAGE_TIFF, 0x4D, 0x4D, 0x00, 0x2A));
Signatures.add(Signature.of(IMAGE_GIF, 0x47, 0x49, 0x46, 0x38));
Signatures.add(Signature.of(APPLICATION_PDF, 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E));
Signatures.add(Signature.of(APP_EPS, 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D));
Signatures.add(Signature.of(APP_PS, 0x25, 0x21, 0x50, 0x53));
Signatures.add(Signature.of(AUDIO_MP3, 0xFF, 0xFB, 0x30));
Signatures.add(Signature.of(AUDIO_MP3, 0x49, 0x44, 0x33));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x21));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x68, 0x74, 0x6D, 0x6C));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x68, 0x65, 0x61, 0x64));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x62, 0x6F, 0x64, 0x79));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x48, 0x54, 0x4D, 0x4C));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x48, 0x45, 0x41, 0x44));
Signatures.add(Signature.of(TEXT_HTML, 0x3C, 0x42, 0x4F, 0x44, 0x59));
Signatures.add(Signature.of(TEXT_XML, 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20));
Signatures.add(Signature.of(TEXT_XML, 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78));
Signatures.add(Signature.of(TEXT_XML, 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00));
Signatures.add(Signature.of(IMAGE_BMP, 0x42, 0x4D));
Signatures.add(Signature.of(AUDIO_BASIC, 0x2E, 0x73, 0x6E, 0x64));
Signatures.add(Signature.of(AUDIO_BASIC, 0x64, 0x6E, 0x73, 0x2E));
Signatures.add(Signature.of(AUDIO_WAV, 0x52, 0x49, 0x46, 0x46));
Signatures.add(Signature.of(APP_ZIP, 0x50, 0x4B));
Signatures.add(Signature.of(APP_JAVA, 0xCA, 0xFE, 0xBA, 0xBE));
Signatures.add(Signature.of(APP_JAVA_OBJECT, 0xAC, 0xED));
}
private static class Signature {
int[] magicBytes;
MediaType mediaType;
static Signature of(MediaType type, int... data) {
Signature result = new Signature();
result.mediaType = type;
if (data.length > SIGNATURE_MAX_LENGTH) {
throw new IllegalArgumentException();
}
result.magicBytes = new int[SIGNATURE_MAX_LENGTH];
int i = -1;
while (++i < data.length) {
result.magicBytes[i] = data[i];
}
while (i < SIGNATURE_MAX_LENGTH) {
result.magicBytes[i++] = END_OF_SIGNATURE;
}
return result;
}
boolean matches(int[] source) {
int i = -1;
boolean matches = true;
while (++i < SIGNATURE_MAX_LENGTH && magicBytes[i] != END_OF_SIGNATURE && matches) {
matches = magicBytes[i] == source[i] || magicBytes[i] == -1;
}
return matches;
}
}
public MediaType getMediaType(final Path path) throws IOException {
return getMediaType(path.toFile());
}
public MediaType getMediaType(final java.io.File file) throws IOException {
try (final InputStream fis = new FileInputStream(file)) {
return getMediaType(fis);
}
}
public MediaType getMediaType(final InputStream is) throws IOException {
final byte[] input = new byte[SIGNATURE_MAX_LENGTH];
final int count = is.read(input, 0, SIGNATURE_MAX_LENGTH);
if (count > 1) {
final byte[] available = new byte[count];
arraycopy(input, 0, available, 0, count);
return getMediaType(available);
}
return null;
}
public MediaType getMediaType(final byte[] data) {
if (data == null) {
throw new IllegalArgumentException("data can not be null");
}
final int[] source = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
for (int i = 0; i < source.length; i++) {
source[i] = data[i] & 0xFF;
}
for (Signature signature : Signatures) {
if (signature.matches(source)) {
return signature.mediaType;
}
}
return null; // No matching signature
}
}
@erizzo
Copy link
Author

erizzo commented Feb 3, 2023

Copy link

ghost commented Feb 4, 2023

@erizzo the source citation needs to be added to the source code, not as a comment in GitHub, so that if people copy the code, the citation will be carried forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment