Skip to content

Commit d243722

Browse files
hive-botclaude
andcommitted
fix: send the User-Agent to translation memory storage URLs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6e9f02c commit d243722

4 files changed

Lines changed: 55 additions & 12 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
190190
`setExtraBodyParameters()`, added last to the `rephraseText()` request body.
191191

192192
### Changed
193+
- Requests to the pre-signed translation memory storage URLs now send the same
194+
`User-Agent` as DeepL API requests (the one set with `setHeaders()`, if any);
195+
previously they sent the JVM default. They still get no `Authorization` or
196+
other custom headers.
193197
- `translateText()` sends the language codes with the language part
194198
lower-cased and the rest as given, like document translation, for example
195199
`zh-Hans`; before, the part after the first `-` was upper-cased.

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1911,7 +1911,8 @@ The available options setters are:
19111911
- `setHeaders()`: additional HTTP headers to attach to all requests. A header
19121912
with the same name as one the library sets, matched in any casing, replaces
19131913
it, including `Authorization` and `User-Agent`; `Content-Type` keeps the
1914-
library's value.
1914+
library's value. Requests to the pre-signed translation memory storage URLs
1915+
(import upload and export download) get only the `User-Agent`.
19151916
- `setServerUrl()`: base URL for DeepL API, may be overridden for testing
19161917
purposes. By default, the correct DeepL API (Free or Pro) is automatically
19171918
selected.

‎deepl-java/src/main/java/com/deepl/api/HttpClientWrapper.java‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,25 +235,36 @@ public HttpResponse uploadWithBackoff(
235235

236236
/**
237237
* Uploads content to an absolute URL outside of the DeepL API, for example a pre-signed storage
238-
* URL returned by a translation memory import job. The DeepL headers, in particular the
239-
* Authorization header, are deliberately not sent to third-party storage.
238+
* URL returned by a translation memory import job. Only the User-Agent header is sent; the
239+
* Authorization header and other custom headers are deliberately not sent to third-party storage.
240240
*/
241241
public HttpResponse sendAssetPutRequestWithBackoff(
242242
String url, byte[] fileContent, String contentType)
243243
throws InterruptedException, DeepLException {
244244
HttpContent content = HttpContent.buildRawContent(contentType, fileContent);
245-
return sendRequestToUrlWithBackoff(PUT, url, content, Collections.emptyMap(), false, true)
245+
return sendRequestToUrlWithBackoff(PUT, url, content, storageHeaders(), false, true)
246246
.toStringResponse();
247247
}
248248

249249
/**
250250
* Downloads content from an absolute URL outside of the DeepL API, for example a pre-signed
251-
* storage URL returned by a completed translation memory export job. The DeepL headers, in
252-
* particular the Authorization header, are deliberately not sent to third-party storage.
251+
* storage URL returned by a completed translation memory export job. Only the User-Agent header
252+
* is sent; the Authorization header and other custom headers are deliberately not sent to
253+
* third-party storage.
253254
*/
254255
public void downloadAssetWithBackoff(String url, ResponseCheck check, OutputStream outputStream)
255256
throws InterruptedException, DeepLException, IOException {
256-
downloadToStreamWithBackoff(GET, url, null, Collections.emptyMap(), check, outputStream);
257+
downloadToStreamWithBackoff(GET, url, null, storageHeaders(), check, outputStream);
258+
}
259+
260+
/** The User-Agent entry of the client headers, in whatever casing it was set. */
261+
private Map<String, String> storageHeaders() {
262+
for (Map.Entry<String, String> header : headers.entrySet()) {
263+
if ("User-Agent".equalsIgnoreCase(header.getKey())) {
264+
return Collections.singletonMap(header.getKey(), header.getValue());
265+
}
266+
}
267+
return Collections.emptyMap();
257268
}
258269

259270
// Retrying after the first byte was written would duplicate it in the output stream, which

‎deepl-java/src/offlineTest/java/com/deepl/api/TranslationMemoriesTest.java‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
1111
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
1212
import static com.github.tomakehurst.wiremock.client.WireMock.get;
13-
import static com.github.tomakehurst.wiremock.client.WireMock.notMatching;
13+
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
1414
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
1515
import static com.github.tomakehurst.wiremock.client.WireMock.post;
1616
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
@@ -35,7 +35,9 @@
3535
import java.nio.file.Path;
3636
import java.time.Duration;
3737
import java.util.Collections;
38+
import java.util.HashMap;
3839
import java.util.List;
40+
import java.util.Map;
3941
import org.junit.jupiter.api.Test;
4042
import org.junit.jupiter.api.extension.RegisterExtension;
4143
import org.junit.jupiter.api.io.TempDir;
@@ -223,13 +225,13 @@ void throwsIllegalArgumentWhenTheImportFileIsInvalid() {
223225
}
224226

225227
@Test
226-
void uploadsTheFileBytesWithoutDeepLHeaders() throws Exception {
228+
void uploadsTheFileBytesWithTheUserAgentAndWithoutAuthorization() throws Exception {
227229
byte[] content = new byte[] {0x3c, (byte) 0xff, 0x00, (byte) 0x80, 0x3e};
228230
wm.stubFor(
229231
put("/storage")
230232
.withHeader("Content-Type", equalTo("application/xml"))
231233
.withHeader("Authorization", absent())
232-
.withHeader("User-Agent", notMatching("deepl-java.*"))
234+
.withHeader("User-Agent", matching("deepl-java/.*"))
233235
.withRequestBody(binaryEqualTo(content))
234236
.willReturn(aResponse().withStatus(200)));
235237
client.uploadTranslationMemoryFile(storageUrl(), content, "application/xml");
@@ -454,17 +456,42 @@ void sendsTheIdOfATranslationMemoryObjectWhenCreatingAnExport() throws Exception
454456
}
455457

456458
@Test
457-
void downloadsTheExportToAFileWithoutDeepLHeaders() throws Exception {
459+
void downloadsTheExportToAFileWithTheUserAgentAndWithoutAuthorization() throws Exception {
458460
wm.stubFor(
459461
get("/storage/export")
460462
.withHeader("Authorization", absent())
461-
.withHeader("User-Agent", notMatching("deepl-java.*"))
463+
.withHeader("User-Agent", matching("deepl-java/.*"))
462464
.willReturn(aResponse().withStatus(200).withBody("<tmx/>")));
463465
File output = tempDir.resolve("export.tmx").toFile();
464466
client.downloadTranslationMemoryExport(completedExport(), output);
465467
assertEquals("<tmx/>", Fixtures.readFile(output));
466468
}
467469

470+
@Test
471+
void sendsOnlyTheCallersUserAgentToStorage() throws Exception {
472+
Map<String, String> headers = new HashMap<>();
473+
headers.put("user-agent", "my-app/1.0");
474+
headers.put("X-Custom", "v");
475+
headers.put("Authorization", "Bearer t");
476+
DeepLClient customClient =
477+
OfflineClient.createClient(
478+
wm, (DeepLClientOptions) new DeepLClientOptions().setHeaders(headers));
479+
wm.stubFor(
480+
put("/storage")
481+
.withHeader("User-Agent", equalTo("my-app/1.0"))
482+
.withHeader("X-Custom", absent())
483+
.withHeader("Authorization", absent())
484+
.willReturn(aResponse().withStatus(200)));
485+
wm.stubFor(
486+
get("/storage/export")
487+
.withHeader("User-Agent", equalTo("my-app/1.0"))
488+
.withHeader("X-Custom", absent())
489+
.withHeader("Authorization", absent())
490+
.willReturn(aResponse().withStatus(200).withBody("<tmx/>")));
491+
customClient.uploadTranslationMemoryFile(storageUrl(), new byte[] {0x3c}, "application/xml");
492+
customClient.downloadTranslationMemoryExport(completedExport(), new ByteArrayOutputStream());
493+
}
494+
468495
@Test
469496
void downloadsTheExportToAStream() throws Exception {
470497
byte[] bytes = new byte[] {0x00, (byte) 0xff, (byte) 0x80};

0 commit comments

Comments
 (0)