Skip to content

Commit

Permalink
tsp, multipart file field can have no filename; update filename encod…
Browse files Browse the repository at this point in the history
…ing (#2544)
  • Loading branch information
weidongxu-microsoft authored Feb 9, 2024
1 parent d938a13 commit 730e7d4
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 72 deletions.
33 changes: 12 additions & 21 deletions javagen/src/main/resources/MultipartFormDataHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
import com.azure.core.util.BinaryData;
import com.azure.core.util.CoreUtils;

import java.text.Normalizer;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

// DO NOT modify this helper class

Expand Down Expand Up @@ -85,8 +83,8 @@ public BinaryData getRequestBody() {
public MultipartFormDataHelper serializeTextField(String fieldName, String value) {
if (value != null) {
String serialized = partSeparator
+ CRLF + "Content-Disposition: form-data; name=\""
+ fieldName + "\"" + CRLF + CRLF
+ CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\""
+ CRLF + CRLF
+ value
+ CRLF;
byte[] data = serialized.getBytes(encoderCharset);
Expand All @@ -105,7 +103,8 @@ public MultipartFormDataHelper serializeTextField(String fieldName, String value
*/
public MultipartFormDataHelper serializeJsonField(String fieldName, Object jsonObject) {
if (jsonObject != null) {
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName + "\""
String serialized = partSeparator + CRLF
+ "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\""
+ CRLF + "Content-Type: application/json"
+ CRLF + CRLF + BinaryData.fromObject(jsonObject) + CRLF;
byte[] data = serialized.getBytes(encoderCharset);
Expand All @@ -132,11 +131,6 @@ public MultipartFormDataHelper serializeFileField(
if (CoreUtils.isNullOrEmpty(contentType)) {
contentType = APPLICATION_OCTET_STREAM;
}
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName;
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
return this;
Expand Down Expand Up @@ -164,11 +158,6 @@ public MultipartFormDataHelper serializeFileFields(
contentType = APPLICATION_OCTET_STREAM;
}
String filename = filenames.get(i);
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName + String.valueOf(i + 1);
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
}
Expand All @@ -194,10 +183,14 @@ public MultipartFormDataHelper end() {
}

private void writeFileField(String fieldName, BinaryData file, String contentType, String filename) {
String contentDispositionFilename = "";
if (!CoreUtils.isNullOrEmpty(filename)) {
contentDispositionFilename = "; filename=\"" + escapeName(filename) + "\"";
}

// Multipart preamble
String fileFieldPreamble = partSeparator
+ CRLF + "Content-Disposition: form-data; name=\"" + fieldName
+ "\"; filename=\"" + filename + "\""
+ CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\"" + contentDispositionFilename
+ CRLF + "Content-Type: " + contentType + CRLF + CRLF;
byte[] data = fileFieldPreamble.getBytes(encoderCharset);
appendBytes(data);
Expand All @@ -216,9 +209,7 @@ private void appendBytes(byte[] bytes) {
requestDataStream = new SequenceInputStream(requestDataStream, new ByteArrayInputStream(bytes));
}

private static final Pattern REDACT_FILENAME = Pattern.compile("[^\\x20-\\x7E]|\"");

private static String normalizeAscii(String text) {
return REDACT_FILENAME.matcher(Normalizer.normalize(text, Normalizer.Form.NFD)).replaceAll("");
private static String escapeName(String name) {
return name.replace("\n", "%0A").replace("\r", "%0D").replace("\"", "%22");
}
}
8 changes: 7 additions & 1 deletion typespec-extension/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Release History

## 0.13.3 (2024-02-07)
## 0.13.5 (2024-02-09)

Compatible with compiler 0.53.

- Behavior changed on "multipart/form-data" request. If `filename` is not provided, implementation will no longer provide a default filename to `Content-Disposition` line.

## 0.13.4 (2024-02-07)

Compatible with compiler 0.53.

Expand Down
4 changes: 2 additions & 2 deletions typespec-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion typespec-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-java",
"version": "0.13.4",
"version": "0.13.5",
"description": "TypeSpec library for emitting Java client from the TypeSpec REST protocol binding",
"keywords": [
"TypeSpec"
Expand Down
2 changes: 1 addition & 1 deletion typespec-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"dependencies": {
"@azure-tools/cadl-ranch-specs": "0.29.0",
"@azure-tools/typespec-java": "file:/../typespec-extension/azure-tools-typespec-java-0.13.4.tgz"
"@azure-tools/typespec-java": "file:/../typespec-extension/azure-tools-typespec-java-0.13.5.tgz"
},
"devDependencies": {
"@typespec/prettier-plugin-typespec": "~0.53.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import java.io.SequenceInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

// DO NOT modify this helper class

Expand Down Expand Up @@ -89,8 +87,8 @@ public BinaryData getRequestBody() {
*/
public MultipartFormDataHelper serializeTextField(String fieldName, String value) {
if (value != null) {
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName + "\""
+ CRLF + CRLF + value + CRLF;
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName)
+ "\"" + CRLF + CRLF + value + CRLF;
byte[] data = serialized.getBytes(encoderCharset);
appendBytes(data);
}
Expand All @@ -107,8 +105,9 @@ public MultipartFormDataHelper serializeTextField(String fieldName, String value
*/
public MultipartFormDataHelper serializeJsonField(String fieldName, Object jsonObject) {
if (jsonObject != null) {
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName + "\""
+ CRLF + "Content-Type: application/json" + CRLF + CRLF + BinaryData.fromObject(jsonObject) + CRLF;
String serialized
= partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\"" + CRLF
+ "Content-Type: application/json" + CRLF + CRLF + BinaryData.fromObject(jsonObject) + CRLF;
byte[] data = serialized.getBytes(encoderCharset);
appendBytes(data);
}
Expand All @@ -130,11 +129,6 @@ public MultipartFormDataHelper serializeFileField(String fieldName, BinaryData f
if (CoreUtils.isNullOrEmpty(contentType)) {
contentType = APPLICATION_OCTET_STREAM;
}
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName;
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
return this;
Expand All @@ -159,11 +153,6 @@ public MultipartFormDataHelper serializeFileFields(String fieldName, List<Binary
contentType = APPLICATION_OCTET_STREAM;
}
String filename = filenames.get(i);
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName + String.valueOf(i + 1);
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
}
Expand All @@ -188,9 +177,15 @@ public MultipartFormDataHelper end() {
}

private void writeFileField(String fieldName, BinaryData file, String contentType, String filename) {
String contentDispositionFilename = "";
if (!CoreUtils.isNullOrEmpty(filename)) {
contentDispositionFilename = "; filename=\"" + escapeName(filename) + "\"";
}

// Multipart preamble
String fileFieldPreamble = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName
+ "\"; filename=\"" + filename + "\"" + CRLF + "Content-Type: " + contentType + CRLF + CRLF;
String fileFieldPreamble
= partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\""
+ contentDispositionFilename + CRLF + "Content-Type: " + contentType + CRLF + CRLF;
byte[] data = fileFieldPreamble.getBytes(encoderCharset);
appendBytes(data);

Expand All @@ -208,9 +203,7 @@ private void appendBytes(byte[] bytes) {
requestDataStream = new SequenceInputStream(requestDataStream, new ByteArrayInputStream(bytes));
}

private static final Pattern REDACT_FILENAME = Pattern.compile("[^\\x20-\\x7E]|\"");

private static String normalizeAscii(String text) {
return REDACT_FILENAME.matcher(Normalizer.normalize(text, Normalizer.Form.NFD)).replaceAll("");
private static String escapeName(String name) {
return name.replace("\n", "%0A").replace("\r", "%0D").replace("\"", "%22");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import java.io.SequenceInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

// DO NOT modify this helper class

Expand Down Expand Up @@ -89,8 +87,8 @@ public BinaryData getRequestBody() {
*/
public MultipartFormDataHelper serializeTextField(String fieldName, String value) {
if (value != null) {
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName + "\""
+ CRLF + CRLF + value + CRLF;
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName)
+ "\"" + CRLF + CRLF + value + CRLF;
byte[] data = serialized.getBytes(encoderCharset);
appendBytes(data);
}
Expand All @@ -107,8 +105,9 @@ public MultipartFormDataHelper serializeTextField(String fieldName, String value
*/
public MultipartFormDataHelper serializeJsonField(String fieldName, Object jsonObject) {
if (jsonObject != null) {
String serialized = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName + "\""
+ CRLF + "Content-Type: application/json" + CRLF + CRLF + BinaryData.fromObject(jsonObject) + CRLF;
String serialized
= partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\"" + CRLF
+ "Content-Type: application/json" + CRLF + CRLF + BinaryData.fromObject(jsonObject) + CRLF;
byte[] data = serialized.getBytes(encoderCharset);
appendBytes(data);
}
Expand All @@ -130,11 +129,6 @@ public MultipartFormDataHelper serializeFileField(String fieldName, BinaryData f
if (CoreUtils.isNullOrEmpty(contentType)) {
contentType = APPLICATION_OCTET_STREAM;
}
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName;
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
return this;
Expand All @@ -159,11 +153,6 @@ public MultipartFormDataHelper serializeFileFields(String fieldName, List<Binary
contentType = APPLICATION_OCTET_STREAM;
}
String filename = filenames.get(i);
if (CoreUtils.isNullOrEmpty(filename)) {
filename = fieldName + String.valueOf(i + 1);
}
filename = normalizeAscii(filename);

writeFileField(fieldName, file, contentType, filename);
}
}
Expand All @@ -188,9 +177,15 @@ public MultipartFormDataHelper end() {
}

private void writeFileField(String fieldName, BinaryData file, String contentType, String filename) {
String contentDispositionFilename = "";
if (!CoreUtils.isNullOrEmpty(filename)) {
contentDispositionFilename = "; filename=\"" + escapeName(filename) + "\"";
}

// Multipart preamble
String fileFieldPreamble = partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + fieldName
+ "\"; filename=\"" + filename + "\"" + CRLF + "Content-Type: " + contentType + CRLF + CRLF;
String fileFieldPreamble
= partSeparator + CRLF + "Content-Disposition: form-data; name=\"" + escapeName(fieldName) + "\""
+ contentDispositionFilename + CRLF + "Content-Type: " + contentType + CRLF + CRLF;
byte[] data = fileFieldPreamble.getBytes(encoderCharset);
appendBytes(data);

Expand All @@ -208,9 +203,7 @@ private void appendBytes(byte[] bytes) {
requestDataStream = new SequenceInputStream(requestDataStream, new ByteArrayInputStream(bytes));
}

private static final Pattern REDACT_FILENAME = Pattern.compile("[^\\x20-\\x7E]|\"");

private static String normalizeAscii(String text) {
return REDACT_FILENAME.matcher(Normalizer.normalize(text, Normalizer.Form.NFD)).replaceAll("");
private static String escapeName(String name) {
return name.replace("\n", "%0A").replace("\r", "%0D").replace("\"", "%22");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,10 @@ public void testFileArray() {
"123",
Arrays.asList(
new PicturesFileDetails(BinaryData.fromFile(PNG_FILE)).setFilename("voilà.png"),
new PicturesFileDetails(BinaryData.fromFile(PNG_FILE)).setFilename("ima\"\nge2.png")
new PicturesFileDetails(BinaryData.fromFile(PNG_FILE)).setFilename("ima\"\n\rge2.png")
))).block();

validationPolicy.validateFilenames("voila.png", "image2.png");
validationPolicy.validateFilenames("voilà.png", "ima%22%0A%0Dge2.png");
}

@Test
Expand Down

0 comments on commit 730e7d4

Please sign in to comment.