From 5820375a5f3c75e07c6f156ec3ef93420d4f8a22 Mon Sep 17 00:00:00 2001 From: Tiago Cardoso Date: Mon, 17 Jun 2024 17:40:06 +0100 Subject: [PATCH] probe content type of files using secure mechanism current implementations are relying on file name/extension, which is prone to errors (.mp4, is it a video or a picture?) and spoofing. This change allows customers to use or opt into mechanisms which inspect the first bytes of a file's payload --- .../libraries/okhttp-gson/ApiClient.mustache | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../org/openapitools/client/ApiClient.java | 16 ++++++----- .../openapitools/client/ApiClientTest.java | 27 +++++++++++++++++++ 12 files changed, 137 insertions(+), 66 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache index d0ee6b629ccb..c820a3599fd8 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/ApiClient.mustache @@ -1643,21 +1643,25 @@ public class ApiClient { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/echo_api/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/echo_api/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java index 8f710611efb4..e6f7b3502d26 100644 --- a/samples/client/echo_api/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/echo_api/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java @@ -1383,21 +1383,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/others/java/okhttp-gson-streaming/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/others/java/okhttp-gson-streaming/src/main/java/org/openapitools/client/ApiClient.java index b382d2da2c9d..4ba4c2b0d70c 100644 --- a/samples/client/others/java/okhttp-gson-streaming/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/others/java/okhttp-gson-streaming/src/main/java/org/openapitools/client/ApiClient.java @@ -1406,21 +1406,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-awsv4signature/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-awsv4signature/src/main/java/org/openapitools/client/ApiClient.java index 21ac39664037..581713df6753 100644 --- a/samples/client/petstore/java/okhttp-gson-awsv4signature/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-awsv4signature/src/main/java/org/openapitools/client/ApiClient.java @@ -1491,21 +1491,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-dynamicOperations/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-dynamicOperations/src/main/java/org/openapitools/client/ApiClient.java index 3b744150e977..f927c98b3f72 100644 --- a/samples/client/petstore/java/okhttp-gson-dynamicOperations/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-dynamicOperations/src/main/java/org/openapitools/client/ApiClient.java @@ -1484,21 +1484,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-group-parameter/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-group-parameter/src/main/java/org/openapitools/client/ApiClient.java index 6d1cc62db7e7..19f05465c0a1 100644 --- a/samples/client/petstore/java/okhttp-gson-group-parameter/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-group-parameter/src/main/java/org/openapitools/client/ApiClient.java @@ -1479,21 +1479,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-nullable-required/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-nullable-required/src/main/java/org/openapitools/client/ApiClient.java index 3f95f4861622..1ea25c45245f 100644 --- a/samples/client/petstore/java/okhttp-gson-nullable-required/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-nullable-required/src/main/java/org/openapitools/client/ApiClient.java @@ -1482,21 +1482,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-parcelableModel/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-parcelableModel/src/main/java/org/openapitools/client/ApiClient.java index ccc405823612..fad8e0e02888 100644 --- a/samples/client/petstore/java/okhttp-gson-parcelableModel/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-parcelableModel/src/main/java/org/openapitools/client/ApiClient.java @@ -1485,21 +1485,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-swagger1/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-swagger1/src/main/java/org/openapitools/client/ApiClient.java index 6d1cc62db7e7..19f05465c0a1 100644 --- a/samples/client/petstore/java/okhttp-gson-swagger1/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-swagger1/src/main/java/org/openapitools/client/ApiClient.java @@ -1479,21 +1479,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson-swagger2/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson-swagger2/src/main/java/org/openapitools/client/ApiClient.java index 6d1cc62db7e7..19f05465c0a1 100644 --- a/samples/client/petstore/java/okhttp-gson-swagger2/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson-swagger2/src/main/java/org/openapitools/client/ApiClient.java @@ -1479,21 +1479,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java index 8593e66829db..a3577b0ead38 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/ApiClient.java @@ -1544,21 +1544,25 @@ public RequestBody buildRequestBodyMultipart(Map formParams) { * @return The guessed Content-Type */ public String guessContentTypeFromFile(File file) { - String contentType = URLConnection.guessContentTypeFromName(file.getName()); - if (contentType == null) { + try { + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } catch(IOException error) { return "application/octet-stream"; - } else { - return contentType; } } /** * Add a Content-Disposition Header for the given key and file to the MultipartBody Builder. * - * @param mpBuilder MultipartBody.Builder + * @param mpBuilder MultipartBody.Builder * @param key The key of the Header element * @param file The file to add to the Header - */ + */ private void addPartToMultiPartBuilder(MultipartBody.Builder mpBuilder, String key, File file) { Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""); MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); diff --git a/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/ApiClientTest.java b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/ApiClientTest.java index 05c47e82206e..71789f2d384b 100644 --- a/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/ApiClientTest.java +++ b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/ApiClientTest.java @@ -3,6 +3,9 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.*; +import java.io.IOException; +import java.io.File; +import java.nio.file.Files; import okhttp3.OkHttpClient; import org.junit.jupiter.api.*; @@ -81,6 +84,30 @@ public void testSelectHeaderContentType() { assertNull(apiClient.selectHeaderContentType(contentTypes)); } + @Test + public void testguessContentTypeFromFile() throws IOException { + byte[] b = { + //ccurve.png + -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, + 0, 0, 0, 15, 0, 0, 0, 15, 8, 6, 0, 0, 0, 59, -42, -107, + 74, 0, 0, 0, 64, 73, 68, 65, 84, 120, -38, 99, 96, -64, 14, -2, + 99, -63, 68, 1, 100, -59, -1, -79, -120, 17, -44, -8, 31, -121, 28, 81, + 26, -1, -29, 113, 13, 78, -51, 100, -125, -1, -108, 24, 64, 86, -24, -30, + 11, 101, -6, -37, 76, -106, -97, 25, 104, 17, 96, -76, 77, 97, 20, -89, + 109, -110, 114, 21, 0, -82, -127, 56, -56, 56, 76, -17, -42, 0, 0, 0, + 0, 73, 69, 78, 68, -82, 66, 96, -126 + }; + + File jpegFile = File.createTempFile("image", ".png"); + jpegFile.deleteOnExit(); + Files.write(jpegFile.toPath(), b); + assertEquals("image/png", apiClient.guessContentTypeFromFile(jpegFile)); + // File otherFile = File.createTempFile("image", ".txt"); + // otherFile.deleteOnExit(); + // Files.write(otherFile.toPath(), b); + // assertEquals("image/png", apiClient.guessContentTypeFromFile(otherFile)); + } + @Test public void testGetAuthentications() { Map auths = apiClient.getAuthentications();