Skip to content

Commit e740f52

Browse files
authored
Merge pull request hub4j#480 from martinvanzijl/issue_381_branch_url_escaping
Escape special characters in branch URLs
2 parents 50fb9c1 + a731109 commit e740f52

10 files changed

Lines changed: 194 additions & 1 deletion

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import java.io.InputStreamReader;
3535
import java.io.InterruptedIOException;
3636
import java.io.Reader;
37+
import java.io.UnsupportedEncodingException;
3738
import java.net.URL;
39+
import java.net.URLEncoder;
3840
import java.util.AbstractSet;
3941
import java.util.ArrayList;
4042
import java.util.Arrays;
@@ -51,6 +53,8 @@
5153
import java.util.WeakHashMap;
5254

5355
import static java.util.Arrays.*;
56+
import java.util.logging.Level;
57+
import java.util.logging.Logger;
5458
import static org.kohsuke.github.Previews.*;
5559

5660
/**
@@ -1393,8 +1397,25 @@ public Map<String,GHBranch> getBranches() throws IOException {
13931397
return r;
13941398
}
13951399

1400+
/**
1401+
* Replace special characters (e.g. #) with standard values (e.g. %23) so
1402+
* GitHub understands what is being requested.
1403+
* @param The string to be encoded.
1404+
* @return The encoded string.
1405+
*/
1406+
private String UrlEncode(String value) {
1407+
try {
1408+
return URLEncoder.encode(value, org.apache.commons.codec.CharEncoding.UTF_8);
1409+
} catch (UnsupportedEncodingException ex) {
1410+
Logger.getLogger(GHRepository.class.getName()).log(Level.SEVERE, null, ex);
1411+
}
1412+
1413+
// Something went wrong - just return original value as is.
1414+
return value;
1415+
}
1416+
13961417
public GHBranch getBranch(String name) throws IOException {
1397-
return root.retrieve().to(getApiTailUrl("branches/"+name),GHBranch.class).wrap(this);
1418+
return root.retrieve().to(getApiTailUrl("branches/"+UrlEncode(name)),GHBranch.class).wrap(this);
13981419
}
13991420

14001421
/**

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ public void archive() throws Exception {
2929
assertThat(getRepository().isArchived(), is(true));
3030
}
3131

32+
@Test
33+
public void getBranch_URLEncoded() throws Exception {
34+
GHRepository repo = getRepository();
35+
GHBranch branch = repo.getBranch("test/#UrlEncode");
36+
assertThat(branch.getName(), is("test/#UrlEncode"));
37+
}
38+
39+
40+
3241
protected GHRepository getRepository() throws IOException {
3342
return getRepository(gitHub);
3443
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"login":"github-api-test-org","id":7544739,"node_id":"MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=","url":"https://api.github.com/orgs/github-api-test-org","repos_url":"https://api.github.com/orgs/github-api-test-org/repos","events_url":"https://api.github.com/orgs/github-api-test-org/events","hooks_url":"https://api.github.com/orgs/github-api-test-org/hooks","issues_url":"https://api.github.com/orgs/github-api-test-org/issues","members_url":"https://api.github.com/orgs/github-api-test-org/members{/member}","public_members_url":"https://api.github.com/orgs/github-api-test-org/public_members{/member}","avatar_url":"https://avatars3.githubusercontent.com/u/7544739?v=4","description":null,"is_verified":false,"has_organization_projects":true,"has_repository_projects":true,"public_repos":9,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/github-api-test-org","created_at":"2014-05-10T19:39:11Z","updated_at":"2015-04-20T00:42:30Z","type":"Organization","total_private_repos":0,"owned_private_repos":0,"private_gists":0,"disk_usage":132,"collaborators":0,"billing_email":"kk@kohsuke.org","default_repository_permission":"none","members_can_create_repositories":false,"two_factor_requirement_enabled":false,"members_allowed_repository_creation_type":"none","plan":{"name":"free","space":976562499,"private_repos":0,"filled_seats":3,"seats":0}}

src/test/resources/org/kohsuke/github/GHRepositoryTest/wiremock/getBranch_URLEncoded/__files/repos_github-api-test-org_github-api-30364e1b-193c-4929-9dd0-5aab7b47dbb9.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"test/#UrlEncode","commit":{"sha":"14fa3698221f91613b9e1d809434326e5ed546af","node_id":"MDY6Q29tbWl0MjA2ODg4MjAxOjE0ZmEzNjk4MjIxZjkxNjEzYjllMWQ4MDk0MzQzMjZlNWVkNTQ2YWY=","commit":{"author":{"name":"Liam Newman","email":"bitwiseman@gmail.com","date":"2019-09-06T23:38:04Z"},"committer":{"name":"Liam Newman","email":"bitwiseman@gmail.com","date":"2019-09-07T00:09:09Z"},"message":"Update README","tree":{"sha":"f791b2c2752a83ddd7604a800800b18e7c1d0196","url":"https://api.github.com/repos/github-api-test-org/github-api/git/trees/f791b2c2752a83ddd7604a800800b18e7c1d0196"},"url":"https://api.github.com/repos/github-api-test-org/github-api/git/commits/14fa3698221f91613b9e1d809434326e5ed546af","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/github-api-test-org/github-api/commits/14fa3698221f91613b9e1d809434326e5ed546af","html_url":"https://github.com/github-api-test-org/github-api/commit/14fa3698221f91613b9e1d809434326e5ed546af","comments_url":"https://api.github.com/repos/github-api-test-org/github-api/commits/14fa3698221f91613b9e1d809434326e5ed546af/comments","author":{"login":"bitwiseman","id":1958953,"node_id":"MDQ6VXNlcjE5NTg5NTM=","avatar_url":"https://avatars3.githubusercontent.com/u/1958953?v=4","gravatar_id":"","url":"https://api.github.com/users/bitwiseman","html_url":"https://github.com/bitwiseman","followers_url":"https://api.github.com/users/bitwiseman/followers","following_url":"https://api.github.com/users/bitwiseman/following{/other_user}","gists_url":"https://api.github.com/users/bitwiseman/gists{/gist_id}","starred_url":"https://api.github.com/users/bitwiseman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitwiseman/subscriptions","organizations_url":"https://api.github.com/users/bitwiseman/orgs","repos_url":"https://api.github.com/users/bitwiseman/repos","events_url":"https://api.github.com/users/bitwiseman/events{/privacy}","received_events_url":"https://api.github.com/users/bitwiseman/received_events","type":"User","site_admin":false},"committer":{"login":"bitwiseman","id":1958953,"node_id":"MDQ6VXNlcjE5NTg5NTM=","avatar_url":"https://avatars3.githubusercontent.com/u/1958953?v=4","gravatar_id":"","url":"https://api.github.com/users/bitwiseman","html_url":"https://github.com/bitwiseman","followers_url":"https://api.github.com/users/bitwiseman/followers","following_url":"https://api.github.com/users/bitwiseman/following{/other_user}","gists_url":"https://api.github.com/users/bitwiseman/gists{/gist_id}","starred_url":"https://api.github.com/users/bitwiseman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitwiseman/subscriptions","organizations_url":"https://api.github.com/users/bitwiseman/orgs","repos_url":"https://api.github.com/users/bitwiseman/repos","events_url":"https://api.github.com/users/bitwiseman/events{/privacy}","received_events_url":"https://api.github.com/users/bitwiseman/received_events","type":"User","site_admin":false},"parents":[{"sha":"3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353","url":"https://api.github.com/repos/github-api-test-org/github-api/commits/3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353","html_url":"https://github.com/github-api-test-org/github-api/commit/3a09d2de4a9a1322a0ba2c3e2f54a919ca8fe353"}]},"_links":{"self":"https://api.github.com/repos/github-api-test-org/github-api/branches/test/#UrlEncode","html":"https://github.com/github-api-test-org/github-api/tree/test/#UrlEncode"},"protected":false,"protection":{"enabled":false,"required_status_checks":{"enforcement_level":"off","contexts":[]}},"protection_url":"https://api.github.com/repos/github-api-test-org/github-api/branches/test/#UrlEncode/protection"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"login":"bitwiseman","id":1958953,"node_id":"MDQ6VXNlcjE5NTg5NTM=","avatar_url":"https://avatars3.githubusercontent.com/u/1958953?v=4","gravatar_id":"","url":"https://api.github.com/users/bitwiseman","html_url":"https://github.com/bitwiseman","followers_url":"https://api.github.com/users/bitwiseman/followers","following_url":"https://api.github.com/users/bitwiseman/following{/other_user}","gists_url":"https://api.github.com/users/bitwiseman/gists{/gist_id}","starred_url":"https://api.github.com/users/bitwiseman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitwiseman/subscriptions","organizations_url":"https://api.github.com/users/bitwiseman/orgs","repos_url":"https://api.github.com/users/bitwiseman/repos","events_url":"https://api.github.com/users/bitwiseman/events{/privacy}","received_events_url":"https://api.github.com/users/bitwiseman/received_events","type":"User","site_admin":false,"name":"Liam Newman","company":"Cloudbees, Inc.","blog":"","location":"Seattle, WA, USA","email":"bitwiseman@gmail.com","hireable":null,"bio":"https://twitter.com/bitwiseman","public_repos":166,"public_gists":4,"followers":135,"following":9,"created_at":"2012-07-11T20:38:33Z","updated_at":"2019-09-24T19:32:29Z"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"id" : "5e8ae8ed-0988-4df2-89d3-eda4c6bdc60d",
3+
"name" : "orgs_github-api-test-org",
4+
"request" : {
5+
"url" : "/orgs/github-api-test-org",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"bodyFileName" : "orgs_github-api-test-org-5e8ae8ed-0988-4df2-89d3-eda4c6bdc60d.json",
11+
"headers" : {
12+
"Date" : "Thu, 26 Sep 2019 00:11:04 GMT",
13+
"Content-Type" : "application/json; charset=utf-8",
14+
"Server" : "GitHub.com",
15+
"Status" : "200 OK",
16+
"X-RateLimit-Limit" : "5000",
17+
"X-RateLimit-Remaining" : "4992",
18+
"X-RateLimit-Reset" : "1569460192",
19+
"Cache-Control" : "private, max-age=60, s-maxage=60",
20+
"Vary" : [ "Accept, Authorization, Cookie, X-GitHub-OTP", "Accept-Encoding" ],
21+
"ETag" : "W/\"b7989d48e6539c9c76038995b902421b\"",
22+
"Last-Modified" : "Mon, 20 Apr 2015 00:42:30 GMT",
23+
"X-OAuth-Scopes" : "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
24+
"X-Accepted-OAuth-Scopes" : "admin:org, read:org, repo, user, write:org",
25+
"X-GitHub-Media-Type" : "unknown, github.v3",
26+
"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",
27+
"Access-Control-Allow-Origin" : "*",
28+
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
29+
"X-Frame-Options" : "deny",
30+
"X-Content-Type-Options" : "nosniff",
31+
"X-XSS-Protection" : "1; mode=block",
32+
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
33+
"Content-Security-Policy" : "default-src 'none'",
34+
"X-GitHub-Request-Id" : "FD24:3764:2322DB:2A314B:5D8C0218"
35+
}
36+
},
37+
"uuid" : "5e8ae8ed-0988-4df2-89d3-eda4c6bdc60d",
38+
"persistent" : true,
39+
"insertionIndex" : 2
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"id" : "30364e1b-193c-4929-9dd0-5aab7b47dbb9",
3+
"name" : "repos_github-api-test-org_github-api",
4+
"request" : {
5+
"url" : "/repos/github-api-test-org/github-api",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"bodyFileName" : "repos_github-api-test-org_github-api-30364e1b-193c-4929-9dd0-5aab7b47dbb9.json",
11+
"headers" : {
12+
"Date" : "Thu, 26 Sep 2019 00:11:05 GMT",
13+
"Content-Type" : "application/json; charset=utf-8",
14+
"Server" : "GitHub.com",
15+
"Status" : "200 OK",
16+
"X-RateLimit-Limit" : "5000",
17+
"X-RateLimit-Remaining" : "4991",
18+
"X-RateLimit-Reset" : "1569460192",
19+
"Cache-Control" : "private, max-age=60, s-maxage=60",
20+
"Vary" : [ "Accept, Authorization, Cookie, X-GitHub-OTP", "Accept-Encoding" ],
21+
"ETag" : "W/\"afa5bdcd11463905460dc32b4cbac3ba\"",
22+
"Last-Modified" : "Wed, 25 Sep 2019 23:37:07 GMT",
23+
"X-OAuth-Scopes" : "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
24+
"X-Accepted-OAuth-Scopes" : "repo",
25+
"X-GitHub-Media-Type" : "unknown, github.v3",
26+
"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",
27+
"Access-Control-Allow-Origin" : "*",
28+
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
29+
"X-Frame-Options" : "deny",
30+
"X-Content-Type-Options" : "nosniff",
31+
"X-XSS-Protection" : "1; mode=block",
32+
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
33+
"Content-Security-Policy" : "default-src 'none'",
34+
"X-GitHub-Request-Id" : "FD24:3764:2322DE:2A315A:5D8C0218"
35+
}
36+
},
37+
"uuid" : "30364e1b-193c-4929-9dd0-5aab7b47dbb9",
38+
"persistent" : true,
39+
"insertionIndex" : 3
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"id" : "ed8bf5ba-65e0-47d8-bb4d-614063828c87",
3+
"name" : "repos_github-api-test-org_github-api_branches_test_urlencode",
4+
"request" : {
5+
"url" : "/repos/github-api-test-org/github-api/branches/test%2F%23UrlEncode",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"bodyFileName" : "repos_github-api-test-org_github-api_branches_test_urlencode-ed8bf5ba-65e0-47d8-bb4d-614063828c87.json",
11+
"headers" : {
12+
"Date" : "Thu, 26 Sep 2019 00:11:05 GMT",
13+
"Content-Type" : "application/json; charset=utf-8",
14+
"Server" : "GitHub.com",
15+
"Status" : "200 OK",
16+
"X-RateLimit-Limit" : "5000",
17+
"X-RateLimit-Remaining" : "4990",
18+
"X-RateLimit-Reset" : "1569460192",
19+
"Cache-Control" : "private, max-age=60, s-maxage=60",
20+
"Vary" : [ "Accept, Authorization, Cookie, X-GitHub-OTP", "Accept-Encoding" ],
21+
"ETag" : "W/\"70021000b4de67768f66b57bbeead27c\"",
22+
"X-OAuth-Scopes" : "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
23+
"X-Accepted-OAuth-Scopes" : "",
24+
"X-GitHub-Media-Type" : "unknown, github.v3",
25+
"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",
26+
"Access-Control-Allow-Origin" : "*",
27+
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
28+
"X-Frame-Options" : "deny",
29+
"X-Content-Type-Options" : "nosniff",
30+
"X-XSS-Protection" : "1; mode=block",
31+
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
32+
"Content-Security-Policy" : "default-src 'none'",
33+
"X-GitHub-Request-Id" : "FD24:3764:2322E4:2A3160:5D8C0219"
34+
}
35+
},
36+
"uuid" : "ed8bf5ba-65e0-47d8-bb4d-614063828c87",
37+
"persistent" : true,
38+
"insertionIndex" : 4
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"id" : "c92cdb3f-7cae-43ba-bee2-e5aa6863dcbc",
3+
"name" : "user",
4+
"request" : {
5+
"url" : "/user",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"bodyFileName" : "user-c92cdb3f-7cae-43ba-bee2-e5aa6863dcbc.json",
11+
"headers" : {
12+
"Date" : "Thu, 26 Sep 2019 00:11:04 GMT",
13+
"Content-Type" : "application/json; charset=utf-8",
14+
"Server" : "GitHub.com",
15+
"Status" : "200 OK",
16+
"X-RateLimit-Limit" : "5000",
17+
"X-RateLimit-Remaining" : "4994",
18+
"X-RateLimit-Reset" : "1569460192",
19+
"Cache-Control" : "private, max-age=60, s-maxage=60",
20+
"Vary" : [ "Accept, Authorization, Cookie, X-GitHub-OTP", "Accept-Encoding" ],
21+
"ETag" : "W/\"14ffd29009ddc2209c450bb29a5a8330\"",
22+
"Last-Modified" : "Tue, 24 Sep 2019 19:32:29 GMT",
23+
"X-OAuth-Scopes" : "gist, notifications, read:org, read:public_key, read:repo_hook, repo",
24+
"X-Accepted-OAuth-Scopes" : "",
25+
"X-GitHub-Media-Type" : "unknown, github.v3",
26+
"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",
27+
"Access-Control-Allow-Origin" : "*",
28+
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
29+
"X-Frame-Options" : "deny",
30+
"X-Content-Type-Options" : "nosniff",
31+
"X-XSS-Protection" : "1; mode=block",
32+
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
33+
"Content-Security-Policy" : "default-src 'none'",
34+
"X-GitHub-Request-Id" : "FD24:3764:2322CA:2A3143:5D8C0218"
35+
}
36+
},
37+
"uuid" : "c92cdb3f-7cae-43ba-bee2-e5aa6863dcbc",
38+
"persistent" : true,
39+
"insertionIndex" : 1
40+
}

0 commit comments

Comments
 (0)