aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2019-04-25 18:53:05 +0800
committercrupest <crupest@outlook.com>2019-04-25 18:53:05 +0800
commit40a1e1d96940ae005db6467cd4207b25d6261837 (patch)
tree27fb5e962476667a4443f0160a4c75382caa4fb5
parentc66b2755ec10b8b23e9c491752b03a68c36c3eb5 (diff)
downloadtimeline-40a1e1d96940ae005db6467cd4207b25d6261837.tar.gz
timeline-40a1e1d96940ae005db6467cd4207b25d6261837.tar.bz2
timeline-40a1e1d96940ae005db6467cd4207b25d6261837.zip
Implement generate object get url in cos.
-rw-r--r--Timeline.Tests/QCloudCosServiceUnitTest.cs45
-rw-r--r--Timeline/Services/TencentCloudCosService.cs58
-rw-r--r--Timeline/Services/UserService.cs6
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");
}
}
}