diff options
author | crupest <crupest@outlook.com> | 2019-04-25 18:53:05 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-04-25 18:53:05 +0800 |
commit | 40a1e1d96940ae005db6467cd4207b25d6261837 (patch) | |
tree | 27fb5e962476667a4443f0160a4c75382caa4fb5 | |
parent | c66b2755ec10b8b23e9c491752b03a68c36c3eb5 (diff) | |
download | timeline-40a1e1d96940ae005db6467cd4207b25d6261837.tar.gz timeline-40a1e1d96940ae005db6467cd4207b25d6261837.tar.bz2 timeline-40a1e1d96940ae005db6467cd4207b25d6261837.zip |
Implement generate object get url in cos.
-rw-r--r-- | Timeline.Tests/QCloudCosServiceUnitTest.cs | 45 | ||||
-rw-r--r-- | Timeline/Services/TencentCloudCosService.cs | 58 | ||||
-rw-r--r-- | Timeline/Services/UserService.cs | 6 |
3 files changed, 93 insertions, 16 deletions
diff --git a/Timeline.Tests/QCloudCosServiceUnitTest.cs b/Timeline.Tests/QCloudCosServiceUnitTest.cs index b99352b9..b0e6a868 100644 --- a/Timeline.Tests/QCloudCosServiceUnitTest.cs +++ b/Timeline.Tests/QCloudCosServiceUnitTest.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Net; using System.Threading.Tasks; using Timeline.Services; using Timeline.Tests.Helpers; @@ -17,6 +18,19 @@ namespace Timeline.Tests public QCloudCosServiceUnitTest(WebApplicationFactory<Startup> factory, ITestOutputHelper outputHelper) { _factory = factory.WithTestConfig(outputHelper); + _factory.CreateDefaultClient().Dispose(); // Ensure test server is created. + } + + [Fact] + public void ValidateBucketNameTest() + { + Assert.True(QCloudCosService.ValidateBucketName("hello")); + Assert.True(QCloudCosService.ValidateBucketName("hello0123")); + Assert.True(QCloudCosService.ValidateBucketName("hello0123-hello")); + Assert.False(QCloudCosService.ValidateBucketName("-hello")); + Assert.False(QCloudCosService.ValidateBucketName("hello-")); + Assert.False(QCloudCosService.ValidateBucketName("helloU")); + Assert.False(QCloudCosService.ValidateBucketName("hello!")); } [Fact] @@ -50,21 +64,40 @@ namespace Timeline.Tests Assert.Equal("q-sign-algorithm=sha1&q-ak=AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q&q-sign-time=1417773892;1417853898&q-key-time=1417773892;1417853898&q-header-list=host;x-cos-content-sha1;x-cos-storage-class&q-url-param-list=&q-signature=0ab12f43e74cbe148d705cd9fae8adc9a6d39cc1", QCloudCosService.GenerateSign(credential, request, signValidTime)); } - /* +/* +// Tests in this part need secret configs in cos. +#region SecretTests [Fact] public async Task ObjectExistsTest() { - _factory.CreateDefaultClient().Dispose(); + using (var serviceScope = _factory.Server.Host.Services.CreateScope()) + { + var services = serviceScope.ServiceProvider; + var service = services.GetRequiredService<IQCloudCosService>(); + Assert.True(await service.IsObjectExists("avatar", "__default")); + Assert.False(await service.IsObjectExists("avatar", "haha")); + Assert.False(await service.IsObjectExists("haha", "haha")); + } + } + // Although this test does not pass on my archlunux system. But the GenerateObjectGetUrl actually works well. + // And I don't know why. + [Fact] + public async Task GenerateObjectGetUrlTest() + { using (var serviceScope = _factory.Server.Host.Services.CreateScope()) { var services = serviceScope.ServiceProvider; var service = services.GetRequiredService<IQCloudCosService>(); - Assert.True(await service.ObjectExists("avatar", "__default")); - Assert.False(await service.ObjectExists("avatar", "haha")); - Assert.False(await service.ObjectExists("haha", "haha")); + var url = service.GenerateObjectGetUrl("avatar", "__default"); + using (var client = _factory.CreateClient()) + { + var res = await client.GetAsync(url); + Assert.Equal(HttpStatusCode.OK, res.StatusCode); + } } } - */ +#endregion +*/ } } diff --git a/Timeline/Services/TencentCloudCosService.cs b/Timeline/Services/TencentCloudCosService.cs index 8dbd3614..f4358714 100644 --- a/Timeline/Services/TencentCloudCosService.cs +++ b/Timeline/Services/TencentCloudCosService.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Timeline.Configs; @@ -15,8 +16,21 @@ namespace Timeline.Services { public interface IQCloudCosService { - Task<bool> ObjectExists(string bucket, string key); - string GetObjectUrl(string bucket, string key); + /// <summary> + /// Test if an object in the bucket exists. + /// </summary> + /// <param name="bucket">The bucket name.</param> + /// <param name="key">The object key.</param> + /// <returns>True if exists. False if not.</returns> + Task<bool> IsObjectExists(string bucket, string key); + + /// <summary> + /// Generate a presignated url to access the object. + /// </summary> + /// <param name="bucket">The bucket name.</param> + /// <param name="key">The object key.</param> + /// <returns>The presignated url.</returns> + string GenerateObjectGetUrl(string bucket, string key); } public class QCloudCosService : IQCloudCosService @@ -32,6 +46,13 @@ namespace Timeline.Services _httpClientFactory = httpClientFactory; } + private const string BucketNamePattern = @"^(([a-z0-9][a-z0-9-]*[a-z0-9])|[a-z0-9])$"; + + public static bool ValidateBucketName(string bucketName) + { + return Regex.IsMatch(bucketName, BucketNamePattern); + } + public class QCloudCredentials { public string SecretId { get; set; } @@ -161,26 +182,29 @@ namespace Timeline.Services return $"{bucket}-{config.AppId}.cos.{config.Region}.myqcloud.com"; } - public async Task<bool> ObjectExists(string bucket, string key) + public async Task<bool> IsObjectExists(string bucket, string key) { if (bucket == null) throw new ArgumentNullException(nameof(bucket)); if (key == null) throw new ArgumentNullException(nameof(key)); + if (!ValidateBucketName(bucket)) + throw new ArgumentException($"Bucket name is not valid. Param is {bucket} .", nameof(bucket)); var client = _httpClientFactory.CreateClient(); var host = GetHost(bucket); + var encodedKey = WebUtility.UrlEncode(key); var request = new HttpRequestMessage(); request.Method = HttpMethod.Head; - request.RequestUri = new Uri($"https://{host}/{key}"); + request.RequestUri = new Uri($"https://{host}/{encodedKey}"); request.Headers.Host = host; request.Headers.Date = DateTimeOffset.Now; request.Headers.TryAddWithoutValidation("Authorization", GenerateSign(GetCredentials(), new RequestInfo { Method = "head", - Uri = "/" + key, + Uri = "/" + encodedKey, Headers = new Dictionary<string, string> { ["Host"] = host @@ -205,9 +229,29 @@ namespace Timeline.Services } } - public string GetObjectUrl(string bucket, string key) + public string GenerateObjectGetUrl(string bucket, string key) { - throw new NotImplementedException(); + if (bucket == null) + throw new ArgumentNullException(nameof(bucket)); + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (!ValidateBucketName(bucket)) + throw new ArgumentException($"Bucket name is not valid. Param is {bucket} .", nameof(bucket)); + + var host = GetHost(bucket); + var encodedKey = WebUtility.UrlEncode(key); + + var signature = GenerateSign(GetCredentials(), new RequestInfo + { + Method = "get", + Uri = "/" + encodedKey, + Headers = new Dictionary<string, string> + { + ["Host"] = host + } + }, new TimeDuration(DateTimeOffset.Now, DateTimeOffset.Now.AddMinutes(6))); + + return $"https://{host}/{encodedKey}?{signature}"; } } } diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index d1555660..4a47ca0f 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -301,11 +301,11 @@ namespace Timeline.Services public async Task<string> GetAvatarUrl(string username) { - var exists = await _cosService.ObjectExists("avatar", username); + var exists = await _cosService.IsObjectExists("avatar", username); if (exists) - return _cosService.GetObjectUrl("avatar", username); + return _cosService.GenerateObjectGetUrl("avatar", username); else - return _cosService.GetObjectUrl("avatar", "__default"); + return _cosService.GenerateObjectGetUrl("avatar", "__default"); } } } |