diff --git a/Octokit.Reactive/Clients/IObservableOauthClient.cs b/Octokit.Reactive/Clients/IObservableOauthClient.cs index 5313caa819..895c9006de 100644 --- a/Octokit.Reactive/Clients/IObservableOauthClient.cs +++ b/Octokit.Reactive/Clients/IObservableOauthClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace Octokit.Reactive { @@ -22,8 +23,9 @@ public interface IObservableOauthClient /// an access token using this method. /// /// + /// /// - IObservable CreateAccessToken(OauthTokenRequest request); + IObservable CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default); /// /// Makes a request to initiate the device flow authentication. @@ -33,25 +35,28 @@ public interface IObservableOauthClient /// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication. /// /// + /// /// - IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request); + IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the response from . + /// Makes a request to get an access token using the response from . /// /// /// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code. /// /// The client Id you received from GitHub when you registered the application. - /// The response you received from + /// The response you received from + /// /// - IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse); + IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the refresh token returned in . + /// Makes a request to get an access token using the refresh token returned in . /// /// Token renewal request. + /// /// with the new token set. - IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request); + IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default); } } diff --git a/Octokit.Reactive/Clients/ObservableOauthClient.cs b/Octokit.Reactive/Clients/ObservableOauthClient.cs index 1e4f42ef66..4d58da79fe 100644 --- a/Octokit.Reactive/Clients/ObservableOauthClient.cs +++ b/Octokit.Reactive/Clients/ObservableOauthClient.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Threading.Tasks; +using System.Threading; namespace Octokit.Reactive { @@ -23,24 +24,24 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request) return _client.Oauth.GetGitHubLoginUrl(request); } - public IObservable CreateAccessToken(OauthTokenRequest request) + public IObservable CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessToken(request).ToObservable(); + return _client.Oauth.CreateAccessToken(request, cancellationToken).ToObservable(); } - public IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request) + public IObservable InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.InitiateDeviceFlow(request).ToObservable(); + return _client.Oauth.InitiateDeviceFlow(request, cancellationToken).ToObservable(); } - public IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse) + public IObservable CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse).ToObservable(); + return _client.Oauth.CreateAccessTokenForDeviceFlow(clientId, deviceFlowResponse, cancellationToken).ToObservable(); } - public IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request) + public IObservable CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default) { - return _client.Oauth.CreateAccessTokenFromRenewalToken(request) + return _client.Oauth.CreateAccessTokenFromRenewalToken(request, cancellationToken) .ToObservable(); } } diff --git a/Octokit.Tests/SimpleJsonTests.cs b/Octokit.Tests/SimpleJsonTests.cs new file mode 100644 index 0000000000..ffc211cee4 --- /dev/null +++ b/Octokit.Tests/SimpleJsonTests.cs @@ -0,0 +1,36 @@ +using Octokit; +using System.Threading.Tasks; +using Xunit; + +public class SimpleJsonTests +{ + [Theory] + [InlineData("\"abc\"", "abc")] + [InlineData(" \"abc\" ", "abc")] + [InlineData("\" abc \" ", " abc ")] + [InlineData("\"abc\\\"def\"", "abc\"def")] + [InlineData("\"abc\\r\\ndef\"", "abc\r\ndef")] + public async Task ParseStringSuccess(string input, string expected) + { + int index = 0; + bool success = true; + + string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success); + + Assert.True(success); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("\"abc")] + public async Task ParseStringIncomplete(string input) + { + int index = 0; + bool success = true; + + string actual = SimpleJson.ParseString(input.ToCharArray(), ref index, ref success); + + Assert.False(success); + Assert.Null(actual); + } +} diff --git a/Octokit/Clients/IOAuthClient.cs b/Octokit/Clients/IOAuthClient.cs index c1add05062..0ce00e4648 100644 --- a/Octokit/Clients/IOAuthClient.cs +++ b/Octokit/Clients/IOAuthClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Octokit @@ -26,8 +27,9 @@ public interface IOauthClient /// an access token using this method. /// /// + /// /// - Task CreateAccessToken(OauthTokenRequest request); + Task CreateAccessToken(OauthTokenRequest request, CancellationToken concellationToken = default); /// /// Makes a request to initiate the device flow authentication. @@ -37,25 +39,28 @@ public interface IOauthClient /// This request also returns a device verification code that you must use to receive an access token to check the status of user authentication. /// /// + /// /// - Task InitiateDeviceFlow(OauthDeviceFlowRequest request); + Task InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default); /// - /// Makes a request to get an access token using the response from . + /// Makes a request to get an access token using the response from . /// /// /// Will poll the access token endpoint, until the device and user codes expire or the user has successfully authorized the app with a valid user code. /// /// The client Id you received from GitHub when you registered the application. - /// The response you received from + /// The response you received from + /// /// - Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse); + Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken concellationToken = default); /// - /// Makes a request to get an access token using the refresh token returned in . + /// Makes a request to get an access token using the refresh token returned in . /// /// Token renewal request. + /// /// with the new token set. - Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request); + Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken concellationToken = default); } } diff --git a/Octokit/Clients/OAuthClient.cs b/Octokit/Clients/OAuthClient.cs index 9d051d1716..c8616cdd2b 100644 --- a/Octokit/Clients/OAuthClient.cs +++ b/Octokit/Clients/OAuthClient.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; namespace Octokit @@ -48,7 +49,7 @@ public Uri GetGitHubLoginUrl(OauthLoginRequest request) } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessToken(OauthTokenRequest request) + public async Task CreateAccessToken(OauthTokenRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); @@ -56,12 +57,12 @@ public async Task CreateAccessToken(OauthTokenRequest request) var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } [ManualRoute("POST", "/login/device/code")] - public async Task InitiateDeviceFlow(OauthDeviceFlowRequest request) + public async Task InitiateDeviceFlow(OauthDeviceFlowRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); @@ -69,12 +70,12 @@ public async Task InitiateDeviceFlow(OauthDeviceFlowReq var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse) + public async Task CreateAccessTokenForDeviceFlow(string clientId, OauthDeviceFlowResponse deviceFlowResponse, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId)); Ensure.ArgumentNotNull(deviceFlowResponse, nameof(deviceFlowResponse)); @@ -85,9 +86,11 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa while (true) { + cancellationToken.ThrowIfCancellationRequested(); + var request = new OauthTokenRequestForDeviceFlow(clientId, deviceFlowResponse.DeviceCode); var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); if (response.Body.Error != null) { @@ -103,7 +106,7 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa throw new ApiException(string.Format(CultureInfo.InvariantCulture, "{0}: {1}\n{2}", response.Body.Error, response.Body.ErrorDescription, response.Body.ErrorUri), null); } - await Task.Delay(TimeSpan.FromSeconds(pollingDelay)); + await Task.Delay(TimeSpan.FromSeconds(pollingDelay), cancellationToken); } else { @@ -113,14 +116,14 @@ public async Task CreateAccessTokenForDeviceFlow(string clientId, Oa } [ManualRoute("POST", "/login/oauth/access_token")] - public async Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request) + public async Task CreateAccessTokenFromRenewalToken(OauthTokenRenewalRequest request, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(request, nameof(request)); var endPoint = ApiUrls.OauthAccessToken(); var body = new FormUrlEncodedContent(request.ToParametersDictionary()); - var response = await connection.Post(endPoint, body, "application/json", null, hostAddress).ConfigureAwait(false); + var response = await connection.Post(endPoint, body, "application/json", null, hostAddress, cancellationToken).ConfigureAwait(false); return response.Body; } } diff --git a/Octokit/SimpleJson.cs b/Octokit/SimpleJson.cs index 7b9a855481..bafb26f921 100644 --- a/Octokit/SimpleJson.cs +++ b/Octokit/SimpleJson.cs @@ -792,15 +792,18 @@ static object ParseValue(char[] json, ref int index, ref bool success) return null; } - static string ParseString(char[] json, ref int index, ref bool success) + internal static string ParseString(char[] json, ref int index, ref bool success) { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + // Avoid allocating this StringBuilder unless a backslash is encountered in the json + StringBuilder s = null; char c; EatWhitespace(json, ref index); // " c = json[index++]; + + int startIndex = index; bool complete = false; while (!complete) { @@ -815,6 +818,13 @@ static string ParseString(char[] json, ref int index, ref bool success) } else if (c == '\\') { + if (s == null) + { + s = new StringBuilder(BUILDER_CAPACITY); + for (int i = startIndex; i < index - 1; i++) + s.Append(json[i]); + } + if (index == json.Length) break; c = json[index++]; @@ -875,14 +885,21 @@ static string ParseString(char[] json, ref int index, ref bool success) } } else - s.Append(c); + { + if (s != null) + s.Append(c); + } } if (!complete) { success = false; return null; } - return s.ToString(); + + if (s != null) + return s.ToString(); + + return new string(json, startIndex, index - startIndex - 1); } private static string ConvertFromUtf32(int utf32)