Skip to content

Commit e4f9c88

Browse files
authored
Add source field to user-agent header (#2230)
1 parent 6362df4 commit e4f9c88

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

src/main/java/com/stripe/net/HttpClient.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.io.InputStream;
88
import java.net.ConnectException;
99
import java.net.SocketTimeoutException;
10+
import java.security.MessageDigest;
1011
import java.time.Duration;
1112
import java.util.HashMap;
1213
import java.util.Map;
@@ -26,6 +27,53 @@ public abstract class HttpClient {
2627
/** A value indicating whether the client should sleep between automatic request retries. */
2728
boolean networkRetriesSleep = true;
2829

30+
static String UNAME_HASH = computeUnameHash();
31+
32+
private static String computeUnameHash() {
33+
String uname = "";
34+
try {
35+
uname =
36+
(System.getProperty("os.name", "")
37+
+ " "
38+
+ System.getProperty("os.version", "")
39+
+ " "
40+
+ System.getProperty("os.arch", "")
41+
+ " "
42+
+ System.getProperty("java.version", "")
43+
+ " "
44+
+ System.getProperty("java.vendor", "")
45+
+ " "
46+
+ System.getProperty("java.vm.name", "")
47+
+ " "
48+
+ getHostname())
49+
.trim();
50+
} catch (Exception e) {
51+
// fall through with empty string
52+
}
53+
if (uname.isEmpty()) {
54+
return "";
55+
}
56+
try {
57+
MessageDigest md = MessageDigest.getInstance("MD5");
58+
byte[] hashBytes = md.digest(uname.getBytes(java.nio.charset.StandardCharsets.UTF_8));
59+
StringBuilder sb = new StringBuilder();
60+
for (byte b : hashBytes) {
61+
sb.append(String.format("%02x", b));
62+
}
63+
return sb.toString();
64+
} catch (Exception e) {
65+
return "";
66+
}
67+
}
68+
69+
private static String getHostname() {
70+
try {
71+
return java.net.InetAddress.getLocalHost().getHostName();
72+
} catch (Exception e) {
73+
return "";
74+
}
75+
}
76+
2977
/** Initializes a new instance of the {@link HttpClient} class. */
3078
protected HttpClient() {}
3179

@@ -228,6 +276,10 @@ static String buildXStripeClientUserAgentString(String aiAgent) {
228276
propertyMap.put("ai_agent", aiAgent);
229277
}
230278

279+
if (!UNAME_HASH.isEmpty()) {
280+
propertyMap.put("source", UNAME_HASH);
281+
}
282+
231283
return ApiResource.INTERNAL_GSON.toJson(propertyMap);
232284
}
233285

src/test/java/com/stripe/net/HttpClientTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.stripe.net;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
45
import static org.junit.jupiter.api.Assertions.assertNotNull;
56
import static org.junit.jupiter.api.Assertions.assertThrows;
67
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -295,4 +296,31 @@ public void testBuildXStripeClientUserAgentStringNoPlatformWithoutTelemetry() {
295296
Stripe.enableTelemetry = originalTelemetry;
296297
}
297298
}
299+
300+
@Test
301+
public void testBuildXStripeClientUserAgentStringIncludesSource() {
302+
String json = HttpClient.buildXStripeClientUserAgentString("");
303+
com.google.gson.JsonObject parsed =
304+
com.google.gson.JsonParser.parseString(json).getAsJsonObject();
305+
// "source" should be present and be a 32-character lowercase MD5 hex digest
306+
assertTrue(parsed.has("source"), "Expected 'source' field in X-Stripe-Client-User-Agent");
307+
String source = parsed.get("source").getAsString();
308+
assertTrue(
309+
source.matches("[0-9a-f]{32}"),
310+
"Expected 'source' to be a 32-character lowercase hex string, got: " + source);
311+
}
312+
313+
@Test
314+
public void testBuildXStripeClientUserAgentStringOmitsSourceWhenEmpty() throws Exception {
315+
String savedHash = HttpClient.UNAME_HASH;
316+
try {
317+
HttpClient.UNAME_HASH = "";
318+
String userAgentString = HttpClient.buildXStripeClientUserAgentString("");
319+
com.google.gson.JsonObject userAgent =
320+
com.google.gson.JsonParser.parseString(userAgentString).getAsJsonObject();
321+
assertFalse(userAgent.has("source"));
322+
} finally {
323+
HttpClient.UNAME_HASH = savedHash;
324+
}
325+
}
298326
}

0 commit comments

Comments
 (0)