Skip to content

Commit bfd9023

Browse files
authored
Merge pull request #762 from bitwiseman/task/insensitive
Http header field names must be case-insensitive
2 parents dd55e8a + c9cdf5d commit bfd9023

File tree

11 files changed

+384
-18
lines changed

11 files changed

+384
-18
lines changed

src/main/java/org/kohsuke/github/GitHubResponse.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
import java.net.URL;
1313
import java.nio.charset.StandardCharsets;
1414
import java.util.Collections;
15-
import java.util.HashMap;
15+
import java.util.Comparator;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.TreeMap;
1819

1920
import javax.annotation.CheckForNull;
2021
import javax.annotation.Nonnull;
@@ -206,6 +207,9 @@ interface BodyHandler<T> {
206207
*/
207208
static abstract class ResponseInfo {
208209

210+
private static final Comparator<String> nullableCaseInsensitiveComparator = Comparator
211+
.nullsFirst(String.CASE_INSENSITIVE_ORDER);
212+
209213
private final int statusCode;
210214
@Nonnull
211215
private final GitHubRequest request;
@@ -217,7 +221,12 @@ protected ResponseInfo(@Nonnull GitHubRequest request,
217221
@Nonnull Map<String, List<String>> headers) {
218222
this.request = request;
219223
this.statusCode = statusCode;
220-
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
224+
225+
// Response header field names must be case-insensitive.
226+
TreeMap<String, List<String>> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator);
227+
caseInsensitiveMap.putAll(headers);
228+
229+
this.headers = Collections.unmodifiableMap(caseInsensitiveMap);
221230
}
222231

223232
/**

src/test/java/org/kohsuke/github/GHRateLimitTest.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import java.time.Duration;
1111
import java.util.Date;
1212

13-
import static org.hamcrest.CoreMatchers.*;
13+
import static org.hamcrest.Matchers.*;
1414
import static org.hamcrest.core.IsInstanceOf.instanceOf;
1515

1616
/**
@@ -163,16 +163,20 @@ private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining) {
163163
}
164164

165165
private void verifyRateLimitValues(GHRateLimit previousLimit, int remaining, boolean changedResetDate) {
166-
// newer or unchange
167-
int resetComparisionValue = changedResetDate ? 1 : 0;
168-
169166
// Basic checks of values
170167
assertThat(rateLimit, notNullValue());
171168
assertThat(rateLimit.getLimit(), equalTo(previousLimit.getLimit()));
172169
assertThat(rateLimit.getRemaining(), equalTo(remaining));
173170

174171
// Check that the reset date of the current limit is not older than the previous one
175-
assertThat(rateLimit.getResetDate().compareTo(previousLimit.getResetDate()), equalTo(resetComparisionValue));
172+
long diffMillis = rateLimit.getResetDate().getTime() - previousLimit.getResetDate().getTime();
173+
174+
assertThat(diffMillis, greaterThanOrEqualTo(0L));
175+
if (changedResetDate) {
176+
assertThat(diffMillis, greaterThan(1000L));
177+
} else {
178+
assertThat(diffMillis, lessThanOrEqualTo(1000L));
179+
}
176180

177181
// Additional checks for record values
178182
assertThat(rateLimit.getCore().getLimit(), equalTo(rateLimit.getLimit()));

src/test/java/org/kohsuke/github/GitHubTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.util.*;
99

1010
import static org.hamcrest.CoreMatchers.*;
11+
import static org.hamcrest.Matchers.is;
12+
import static org.hamcrest.Matchers.notNullValue;
1113
import static org.kohsuke.github.GHMarketplaceAccountType.ORGANIZATION;
1214

1315
/**
@@ -162,4 +164,47 @@ public void getMyMarketplacePurchases() throws IOException {
162164
assertNull(account.getOrganizationBillingEmail());
163165
}
164166
}
167+
168+
@Test
169+
public void gzip() throws Exception {
170+
171+
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
172+
173+
// getResponseHeaderFields is deprecated but we'll use it for testing.
174+
assertThat(org.getResponseHeaderFields(), notNullValue());
175+
176+
// WireMock should automatically gzip all responses
177+
assertThat(org.getResponseHeaderFields().get("Content-Encoding").get(0), is("gzip"));
178+
assertThat(org.getResponseHeaderFields().get("Content-eNcoding").get(0), is("gzip"));
179+
}
180+
181+
@Test
182+
public void testHeaderFieldName() throws Exception {
183+
184+
GHOrganization org = gitHub.getOrganization(GITHUB_API_TEST_ORG);
185+
186+
// getResponseHeaderFields is deprecated but we'll use it for testing.
187+
assertThat(org.getResponseHeaderFields(), notNullValue());
188+
189+
// Header field names must be case-insensitive
190+
assertThat(org.getResponseHeaderFields().containsKey("CacHe-ContrOl"), is(true));
191+
192+
// The KeySet from header fields should also be case-insensitive
193+
assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true));
194+
assertThat(org.getResponseHeaderFields().keySet().contains("CacHe-ControL"), is(true));
195+
196+
assertThat(org.getResponseHeaderFields().get("cachE-cOntrol").get(0), is("private, max-age=60, s-maxage=60"));
197+
198+
// GitHub has started changing their headers to all lowercase.
199+
// For this test we want the field names to be with mixed-case (harder to do comparison).
200+
// Ensure that it remains that way, if test resources are ever refreshed.
201+
boolean found = false;
202+
for (String key : org.getResponseHeaderFields().keySet()) {
203+
if (Objects.equals("Cache-Control", key)) {
204+
found = true;
205+
break;
206+
}
207+
}
208+
assertThat("Must have the literal expected string 'Cache-Control' for header field name", found);
209+
}
165210
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"login": "github-api-test-org",
3+
"id": 7544739,
4+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
5+
"url": "https://api.github.com/orgs/github-api-test-org",
6+
"repos_url": "https://api.github.com/orgs/github-api-test-org/repos",
7+
"events_url": "https://api.github.com/orgs/github-api-test-org/events",
8+
"hooks_url": "https://api.github.com/orgs/github-api-test-org/hooks",
9+
"issues_url": "https://api.github.com/orgs/github-api-test-org/issues",
10+
"members_url": "https://api.github.com/orgs/github-api-test-org/members{/member}",
11+
"public_members_url": "https://api.github.com/orgs/github-api-test-org/public_members{/member}",
12+
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
13+
"description": null,
14+
"is_verified": false,
15+
"has_organization_projects": true,
16+
"has_repository_projects": true,
17+
"public_repos": 25,
18+
"public_gists": 0,
19+
"followers": 0,
20+
"following": 0,
21+
"html_url": "https://github.com/github-api-test-org",
22+
"created_at": "2014-05-10T19:39:11Z",
23+
"updated_at": "2015-04-20T00:42:30Z",
24+
"type": "Organization",
25+
"total_private_repos": 0,
26+
"owned_private_repos": 0,
27+
"private_gists": 0,
28+
"disk_usage": 147,
29+
"collaborators": 0,
30+
"billing_email": "[email protected]",
31+
"default_repository_permission": "none",
32+
"members_can_create_repositories": false,
33+
"two_factor_requirement_enabled": false,
34+
"plan": {
35+
"name": "free",
36+
"space": 976562499,
37+
"private_repos": 0,
38+
"filled_seats": 15,
39+
"seats": 0
40+
}
41+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"login": "bitwiseman",
3+
"id": 1958953,
4+
"node_id": "MDQ6VXNlcjE5NTg5NTM=",
5+
"avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4",
6+
"gravatar_id": "",
7+
"url": "https://api.github.com/users/bitwiseman",
8+
"html_url": "https://github.com/bitwiseman",
9+
"followers_url": "https://api.github.com/users/bitwiseman/followers",
10+
"following_url": "https://api.github.com/users/bitwiseman/following{/other_user}",
11+
"gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}",
12+
"starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}",
13+
"subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions",
14+
"organizations_url": "https://api.github.com/users/bitwiseman/orgs",
15+
"repos_url": "https://api.github.com/users/bitwiseman/repos",
16+
"events_url": "https://api.github.com/users/bitwiseman/events{/privacy}",
17+
"received_events_url": "https://api.github.com/users/bitwiseman/received_events",
18+
"type": "User",
19+
"site_admin": false,
20+
"name": "Liam Newman",
21+
"company": "Cloudbees, Inc.",
22+
"blog": "",
23+
"location": "Seattle, WA, USA",
24+
"email": "[email protected]",
25+
"hireable": null,
26+
"bio": "https://twitter.com/bitwiseman",
27+
"public_repos": 181,
28+
"public_gists": 7,
29+
"followers": 151,
30+
"following": 9,
31+
"created_at": "2012-07-11T20:38:33Z",
32+
"updated_at": "2020-03-27T19:14:56Z",
33+
"private_gists": 8,
34+
"total_private_repos": 10,
35+
"owned_private_repos": 0,
36+
"disk_usage": 33697,
37+
"collaborators": 0,
38+
"two_factor_authentication": true,
39+
"plan": {
40+
"name": "free",
41+
"space": 976562499,
42+
"collaborators": 0,
43+
"private_repos": 10000
44+
}
45+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"id": "6b1390ac-fba9-4e09-9b33-1915d6aad42d",
3+
"name": "orgs_github-api-test-org",
4+
"request": {
5+
"url": "/orgs/github-api-test-org",
6+
"method": "GET",
7+
"headers": {
8+
"Accept": {
9+
"equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"
10+
}
11+
}
12+
},
13+
"response": {
14+
"status": 200,
15+
"bodyFileName": "orgs_github-api-test-org-2.json",
16+
"headers": {
17+
"Date": "Tue, 31 Mar 2020 01:58:58 GMT",
18+
"Content-Type": "application/json; charset=utf-8",
19+
"Server": "GitHub.com",
20+
"Status": "200 OK",
21+
"X-RateLimit-Limit": "5000",
22+
"X-RateLimit-Remaining": "4947",
23+
"X-RateLimit-Reset": "1585620720",
24+
"Cache-Control": "private, max-age=60, s-maxage=60",
25+
"Vary": [
26+
"Accept, Authorization, Cookie, X-GitHub-OTP",
27+
"Accept-Encoding, Accept, X-Requested-With"
28+
],
29+
"ETag": "W/\"19a1dc3fabba80fa04e5602bb1e9457b\"",
30+
"Last-Modified": "Mon, 20 Apr 2015 00:42:30 GMT",
31+
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
32+
"X-Accepted-OAuth-Scopes": "admin:org, read:org, repo, user, write:org",
33+
"X-GitHub-Media-Type": "unknown, github.v3",
34+
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
35+
"X-Frame-Options": "deny",
36+
"X-Content-Type-Options": "nosniff",
37+
"X-XSS-Protection": "1; mode=block",
38+
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
39+
"Content-Security-Policy": "default-src 'none'",
40+
"X-GitHub-Request-Id": "DBDB:5BD4:C4C3:F7A4:5E82A3E2"
41+
}
42+
},
43+
"uuid": "6b1390ac-fba9-4e09-9b33-1915d6aad42d",
44+
"persistent": true,
45+
"insertionIndex": 2
46+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"id": "0cdbd9dc-1c6f-42f5-a4b5-9d5472b2a56b",
2+
"id": "1f83f6e6-3e0f-4529-a615-9084272bae98",
33
"name": "user",
44
"request": {
55
"url": "/user",
@@ -14,35 +14,33 @@
1414
"status": 200,
1515
"bodyFileName": "user-1.json",
1616
"headers": {
17-
"Date": "Sat, 26 Oct 2019 01:27:31 GMT",
17+
"Date": "Tue, 31 Mar 2020 01:58:58 GMT",
1818
"Content-Type": "application/json; charset=utf-8",
1919
"Server": "GitHub.com",
2020
"Status": "200 OK",
2121
"X-RateLimit-Limit": "5000",
22-
"X-RateLimit-Remaining": "4436",
23-
"X-RateLimit-Reset": "1572055286",
22+
"X-RateLimit-Remaining": "4949",
23+
"X-RateLimit-Reset": "1585620721",
2424
"Cache-Control": "private, max-age=60, s-maxage=60",
2525
"Vary": [
2626
"Accept, Authorization, Cookie, X-GitHub-OTP",
27-
"Accept-Encoding"
27+
"Accept-Encoding, Accept, X-Requested-With"
2828
],
29-
"ETag": "W/\"8c3d3dcf6fc5f9edaf26c902295396e5\"",
30-
"Last-Modified": "Tue, 24 Sep 2019 19:32:29 GMT",
29+
"ETag": "W/\"740bb7db37d5437d08ea1aa5e652cf37\"",
30+
"Last-Modified": "Fri, 27 Mar 2020 19:14:56 GMT",
3131
"X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion",
3232
"X-Accepted-OAuth-Scopes": "",
3333
"X-GitHub-Media-Type": "unknown, github.v3",
34-
"Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
35-
"Access-Control-Allow-Origin": "*",
3634
"Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload",
3735
"X-Frame-Options": "deny",
3836
"X-Content-Type-Options": "nosniff",
3937
"X-XSS-Protection": "1; mode=block",
4038
"Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin",
4139
"Content-Security-Policy": "default-src 'none'",
42-
"X-GitHub-Request-Id": "CA8F:3649:E32786:1091CE0:5DB3A103"
40+
"X-GitHub-Request-Id": "DBDB:5BD4:C4C2:F7A3:5E82A3E1"
4341
}
4442
},
45-
"uuid": "0cdbd9dc-1c6f-42f5-a4b5-9d5472b2a56b",
43+
"uuid": "1f83f6e6-3e0f-4529-a615-9084272bae98",
4644
"persistent": true,
4745
"insertionIndex": 1
4846
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"login": "github-api-test-org",
3+
"id": 7544739,
4+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
5+
"url": "https://api.github.com/orgs/github-api-test-org",
6+
"repos_url": "https://api.github.com/orgs/github-api-test-org/repos",
7+
"events_url": "https://api.github.com/orgs/github-api-test-org/events",
8+
"hooks_url": "https://api.github.com/orgs/github-api-test-org/hooks",
9+
"issues_url": "https://api.github.com/orgs/github-api-test-org/issues",
10+
"members_url": "https://api.github.com/orgs/github-api-test-org/members{/member}",
11+
"public_members_url": "https://api.github.com/orgs/github-api-test-org/public_members{/member}",
12+
"avatar_url": "https://avatars3.githubusercontent.com/u/7544739?v=4",
13+
"description": null,
14+
"is_verified": false,
15+
"has_organization_projects": true,
16+
"has_repository_projects": true,
17+
"public_repos": 25,
18+
"public_gists": 0,
19+
"followers": 0,
20+
"following": 0,
21+
"html_url": "https://github.com/github-api-test-org",
22+
"created_at": "2014-05-10T19:39:11Z",
23+
"updated_at": "2015-04-20T00:42:30Z",
24+
"type": "Organization",
25+
"total_private_repos": 0,
26+
"owned_private_repos": 0,
27+
"private_gists": 0,
28+
"disk_usage": 147,
29+
"collaborators": 0,
30+
"billing_email": "[email protected]",
31+
"default_repository_permission": "none",
32+
"members_can_create_repositories": false,
33+
"two_factor_requirement_enabled": false,
34+
"plan": {
35+
"name": "free",
36+
"space": 976562499,
37+
"private_repos": 0,
38+
"filled_seats": 15,
39+
"seats": 0
40+
}
41+
}

0 commit comments

Comments
 (0)