├── tools ├── files │ └── test.jpg └── net40 │ ├── Newtonsoft.Json.dll │ └── nunit.framework.dll ├── src ├── QiniuTests │ ├── packages.config │ ├── Util │ │ ├── StringHelper.cs │ │ ├── Auth.cs │ │ └── Signature.cs │ ├── TestEnv.cs │ ├── Storage │ │ ├── ConfigTests.cs │ │ ├── DownloadManagerTests.cs │ │ ├── UploadUtilTests.cs │ │ ├── ZoneHelperTests.cs │ │ └── OperationManagerTests.cs │ ├── Http │ │ ├── HttpResult.cs │ │ ├── HttpManager.cs │ │ ├── Middleware.cs │ │ └── HttpRequestOptions.cs │ └── QiniuTests.csproj ├── Qiniu │ ├── packages.config │ ├── Storage │ │ ├── UploadProgressHandler.cs │ │ ├── QiniuException.cs │ │ ├── UploadController.cs │ │ ├── FetchInfo.cs │ │ ├── UploadUtil.cs │ │ ├── BucketInfo.cs │ │ ├── ListInfo.cs │ │ ├── PutExtra.cs │ │ ├── ResumeContext.cs │ │ ├── ResumeBlocker.cs │ │ ├── ZoneInfo.cs │ │ ├── ResumeInfo.cs │ │ ├── PrefopResult.cs │ │ ├── FetchResult.cs │ │ ├── ListItem.cs │ │ ├── StatResult.cs │ │ ├── BucketsResult.cs │ │ ├── DomainsResult.cs │ │ ├── PfopResult.cs │ │ ├── BucketResult.cs │ │ ├── ResumeHelper.cs │ │ ├── ChunkUnit.cs │ │ ├── ListResult.cs │ │ ├── BatchResult.cs │ │ ├── UploadManager.cs │ │ ├── PfopInfo.cs │ │ ├── FileInfo.cs │ │ ├── Zone.cs │ │ ├── DownloadManager.cs │ │ ├── BatchInfo.cs │ │ ├── ZoneHelper.cs │ │ └── PutPolicy.cs │ ├── Properties │ │ └── PublishProfiles │ │ │ └── FolderProfile.pubxml │ ├── Util │ │ ├── Mac.cs │ │ ├── UpToken.cs │ │ ├── Base64.cs │ │ ├── Hashing.cs │ │ ├── UnixTimestamp.cs │ │ ├── QETag.cs │ │ ├── ETag.cs │ │ ├── UserEnv.cs │ │ ├── UrlHelper.cs │ │ ├── CRC32.cs │ │ └── StringHelper.cs │ ├── Http │ │ ├── ContentType.cs │ │ ├── Middleware.cs │ │ ├── UrlHelper.cs │ │ ├── HttpHelper.cs │ │ ├── HttpCode.cs │ │ └── HttpResult.cs │ ├── QiniuCSharpSDK.cs │ ├── Qiniu.csproj │ └── CDN │ │ ├── PrefetchInfo.cs │ │ ├── FluxRequest.cs │ │ ├── BandwidthRequest.cs │ │ ├── PrefetchRequest.cs │ │ ├── FluxInfo.cs │ │ ├── BandwidthInfo.cs │ │ ├── RefreshInfo.cs │ │ ├── LogListInfo.cs │ │ ├── LogListRequest.cs │ │ ├── PrefetchResult.cs │ │ ├── FluxResult.cs │ │ ├── BandwidthResult.cs │ │ ├── RefreshRequest.cs │ │ ├── LogListResult.cs │ │ └── RefreshResult.cs └── Qiniu.sln ├── Makefile ├── .gitignore ├── README.md ├── .github └── workflows │ ├── version-check.yml │ └── test-ci.yml └── LICENSE /tools/files/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/files/test.jpg -------------------------------------------------------------------------------- /tools/net40/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/net40/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /tools/net40/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiniu/csharp-sdk/HEAD/tools/net40/nunit.framework.dll -------------------------------------------------------------------------------- /src/QiniuTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Qiniu/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build-and-test build test publish 2 | 3 | build-and-test: build test 4 | build: 5 | dotnet build src/Qiniu/Qiniu.csproj 6 | test: 7 | dotnet test src/QiniuTests/QiniuTests.csproj 8 | publish: 9 | dotnet publish src/Qiniu/Qiniu.csproj -c Release -f netstandard2.0 10 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/UploadProgressHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Storage 2 | { 3 | /// 4 | /// 分片上传进度处理 5 | /// 6 | /// 已上传的字节数 7 | /// 文件总字节数 8 | public delegate void UploadProgressHandler(long uploadedBytes, long totalBytes); 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | #[Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | /src/packages 31 | /packages 32 | /bin 33 | .vs 34 | /src/QiniuTests/TestEnv.cs 35 | /src/QiniuTests/coverage.json 36 | -------------------------------------------------------------------------------- /src/QiniuTests/Util/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using NUnit.Framework; 3 | 4 | using Qiniu.Util; 5 | using Qiniu.Tests; 6 | 7 | namespace QiniuTests.Util 8 | { 9 | [TestFixture] 10 | public class StringHelperTests : TestEnv 11 | { 12 | [TestCaseSource(typeof(CanonicalMimeHeaderKeyDataClass), nameof(CanonicalMimeHeaderKeyDataClass.TestCases))] 13 | public string CanonicalMimeHeaderKeyTest(string fieldName) 14 | { 15 | return StringHelper.CanonicalMimeHeaderKey(fieldName); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qiniu (Cloud) C# SDK 2 | 3 | ## 使用 4 | 5 | * 参考文档:[七牛云存储 C# SDK 使用指南](https://developer.qiniu.com/kodo/sdk/1237/csharp) 6 | * 可以参考我们为大家精心准备的使用 [实例](https://github.com/qiniu/csharp-sdk/tree/master/src/QiniuTests) 7 | 8 | 9 | ## 贡献代码 10 | 11 | 1. Fork 12 | 13 | 2. 创建您的特性分支 git checkout -b my-new-feature 14 | 15 | 3. 提交您的改动 git commit -am 'Added some feature' 16 | 17 | 4. 将您的修改记录提交到远程 git 仓库 git push origin my-new-feature 18 | 19 | 5. 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request 20 | 21 | 22 | ## 许可证 23 | 24 | Copyright (c) 2017 [qiniu.com](www.qiniu.com) 25 | -------------------------------------------------------------------------------- /src/Qiniu/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | FileSystem 8 | Release 9 | Any CPU 10 | netstandard2.0 11 | C:\Users\zhour\source\repos\csharp-sdk\bin\netstandard2.0 12 | 13 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/QiniuException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Qiniu.Http; 3 | 4 | namespace Qiniu.Storage 5 | { 6 | class QiniuException :Exception 7 | { 8 | public string message; 9 | public HttpResult HttpResult; 10 | public QiniuException(HttpResult httpResult, string message) 11 | { 12 | this.HttpResult = httpResult == null ? new HttpResult() : httpResult; 13 | this.message = message; 14 | } 15 | 16 | public override string Message 17 | { 18 | get 19 | { 20 | return this.message; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/QiniuTests/TestEnv.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Tests 2 | { 3 | public class TestEnv 4 | { 5 | public string AccessKey; 6 | public string SecretKey; 7 | public string Bucket; 8 | public string Domain; 9 | 10 | public TestEnv() 11 | { 12 | this.AccessKey = System.Environment.GetEnvironmentVariable("QINIU_ACCESS_KEY"); 13 | this.SecretKey = System.Environment.GetEnvironmentVariable("QINIU_SECRET_KEY"); 14 | this.Bucket = System.Environment.GetEnvironmentVariable("QINIU_TEST_BUCKET"); 15 | this.Domain = System.Environment.GetEnvironmentVariable("QINIU_TEST_DOMAIN"); } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/UploadController.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Storage 2 | { 3 | /// 4 | /// 上传任务的状态 5 | /// 6 | public enum UploadControllerAction 7 | { 8 | /// 9 | /// 任务状态:激活 10 | /// 11 | Activated, 12 | 13 | /// 14 | /// 任务状态:暂停 15 | /// 16 | Suspended, 17 | 18 | /// 19 | /// 任务状态:退出 20 | /// 21 | Aborted 22 | }; 23 | 24 | /// 25 | /// 上传任务的控制函数 26 | /// 27 | /// 28 | public delegate UploadControllerAction UploadController(); 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/version-check.yml: -------------------------------------------------------------------------------- 1 | name: C# SDK Version Check 2 | on: 3 | push: 4 | tags: 5 | - 'v[0-9]+.[0-9]+.[0-9]+' 6 | jobs: 7 | linux: 8 | name: Version Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Set env 14 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV 15 | - name: Check 16 | run: | 17 | set -e 18 | grep -qF "${RELEASE_VERSION}" src/Qiniu/Qiniu.csproj 19 | grep -qF "v${RELEASE_VERSION}" CHANGELOG.md 20 | grep -qF "public const string VERSION = \"${RELEASE_VERSION}\";" src/Qiniu/QiniuCSharpSDK.cs 21 | -------------------------------------------------------------------------------- /src/Qiniu/Util/Mac.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Util 2 | { 3 | /// 4 | /// 账户访问控制(密钥) 5 | /// 6 | public class Mac 7 | { 8 | /// 9 | /// 密钥-AccessKey 10 | /// 11 | public string AccessKey { set; get; } 12 | 13 | /// 14 | /// 密钥-SecretKey 15 | /// 16 | public string SecretKey { set; get; } 17 | 18 | /// 19 | /// 初始化密钥AK/SK 20 | /// 21 | /// AccessKey 22 | /// SecretKey 23 | public Mac(string accessKey, string secretKey) 24 | { 25 | this.AccessKey = accessKey; 26 | this.SecretKey = secretKey; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/FetchInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | namespace Qiniu.Storage 3 | { 4 | /// 5 | /// 资源抓取返回的内容 6 | /// 7 | public class FetchInfo 8 | { 9 | /// 10 | /// 文件名 11 | /// 12 | [JsonProperty("key")] 13 | public string Key { set; get; } 14 | 15 | /// 16 | /// 文件大小(字节) 17 | /// 18 | [JsonProperty("fsize")] 19 | public long Fsize { set; get; } 20 | 21 | /// 22 | /// 文件hash(ETAG) 23 | /// 24 | [JsonProperty("hash")] 25 | public string Hash { set; get; } 26 | 27 | /// 28 | /// 文件MIME类型 29 | /// 30 | [JsonProperty("mimeType")] 31 | public string MimeType { set; get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/test-ci.yml: -------------------------------------------------------------------------------- 1 | name: CSHARP CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | max-parallel: 1 13 | 14 | runs-on: windows-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: 2.1.502 23 | 24 | - name: Build 25 | run: dotnet build src/Qiniu/Qiniu.csproj 26 | 27 | - name: Test 28 | run: dotnet test -v n src/QiniuTests/QiniuTests.csproj 29 | 30 | env: 31 | QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }} 32 | QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }} 33 | QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }} 34 | QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }} 35 | -------------------------------------------------------------------------------- /src/QiniuTests/Util/Auth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using NUnit.Framework; 4 | using Qiniu.Util; 5 | 6 | namespace QiniuTests.Util 7 | { 8 | [TestFixture] 9 | public class AuthTests 10 | { 11 | private static Mac mac = new Mac("ak", "sk"); 12 | private static Auth auth = new Auth(mac); 13 | 14 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))] 15 | public string CreateManageTokenV2Test(string method, string url, StringDictionary headers, 16 | string body) 17 | { 18 | return auth.CreateManageTokenV2(method, url, headers, body); 19 | } 20 | 21 | [Test] 22 | public void CreateManageTokenV2Test() 23 | { 24 | string actual = auth.CreateManageTokenV2("GET", "http://rs.qbox.me"); 25 | Assert.AreEqual("Qiniu ak:bgfeAqx6xXMIXA232e8ocxfhINc=", actual); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/QiniuTests/Storage/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Qiniu.Tests; 3 | 4 | namespace Qiniu.Storage.Tests 5 | { 6 | [TestFixture] 7 | public class ConfigTests : TestEnv 8 | { 9 | [Test] 10 | public void UcHostTest() 11 | { 12 | Config config = new Config(); 13 | string ucHost = config.UcHost(); 14 | Assert.AreEqual("http://uc.qiniuapi.com", ucHost); 15 | config.SetUcHost("uc.example.com"); 16 | ucHost = config.UcHost(); 17 | Assert.AreEqual("http://uc.example.com", ucHost); 18 | 19 | config = new Config(); 20 | config.UseHttps = true; 21 | ucHost = config.UcHost(); 22 | Assert.AreEqual("https://uc.qiniuapi.com", ucHost); 23 | config.SetUcHost("uc.example.com"); 24 | ucHost = config.UcHost(); 25 | Assert.AreEqual("https://uc.example.com", ucHost); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Qiniu/Http/ContentType.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Http 2 | { 3 | /// 4 | /// HTTP 内容类型(Content-Type) 5 | /// 6 | public class ContentType 7 | { 8 | /// 9 | /// 资源类型:普通文本 10 | /// 11 | public static string TEXT_PLAIN = "text/plain"; 12 | 13 | /// 14 | /// 资源类型:JSON字符串 15 | /// 16 | public static string APPLICATION_JSON = "application/json"; 17 | 18 | /// 19 | /// 资源类型:未知类型(数据流) 20 | /// 21 | public static string APPLICATION_OCTET_STREAM = "application/octet-stream"; 22 | 23 | /// 24 | /// 资源类型:表单数据(键值对) 25 | /// 26 | public static string WWW_FORM_URLENC = "application/x-www-form-urlencoded"; 27 | 28 | /// 29 | /// 资源类型:多分部数据 30 | /// 31 | public static string MULTIPART_FORM_DATA = "multipart/form-data"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/QiniuTests/Storage/DownloadManagerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Qiniu.Storage; 3 | using System; 4 | using Qiniu.Util; 5 | using Qiniu.Tests; 6 | 7 | namespace Qiniu.Storage.Tests 8 | { 9 | [TestFixture] 10 | public class DownloadManagerTests : TestEnv 11 | { 12 | [Test] 13 | public void CreatePrivateUrlTest() 14 | { 15 | Mac mac = new Mac(AccessKey, SecretKey); 16 | string domain = "http://if-pri.qiniudn.com"; 17 | string key = "hello/world/七牛/test.png"; 18 | string privateUrl = DownloadManager.CreatePrivateUrl(mac, domain, key, 3600); 19 | Console.WriteLine(privateUrl); 20 | } 21 | 22 | [Test] 23 | public void CreatePublishUrlTest() 24 | { 25 | string domain = "http://if-pbl.qiniudn.com"; 26 | string key = "hello/world/七牛/test.png"; 27 | string publicUrl = DownloadManager.CreatePublishUrl(domain, key); 28 | Console.WriteLine(publicUrl); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 qiniu.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Qiniu/QiniuCSharpSDK.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Qiniu (Cloud) C# SDK for .NET Framework 2.0+/Core/UWP 3 | /// Modules in this SDK: 4 | /// "Storage" 存储相关功能,上传,下载,数据处理,资源管理 5 | /// "CDN", Fusion CDN, 融合CDN加速; 6 | /// "Util", Utilities such as MD5 hashing, 实用工具(如MD5哈希计算等); 7 | /// "Http", HTTP Request Manager, HTTP请求管理器 8 | /// 9 | public class QiniuCSharpSDK 10 | { 11 | /// 12 | /// SDK名称 13 | /// 14 | public const string ALIAS = "QiniuCSharpSDK"; 15 | 16 | /// 17 | /// 目标框架 18 | /// 19 | #if Net20 20 | public const string RTFX = "NET20"; 21 | #elif Net35 22 | public const string RTFX = "NET35"; 23 | #elif Net40 24 | public const string RTFX = "NET40"; 25 | #elif Net45 26 | public const string RTFX = "NET45"; 27 | #elif Net46 28 | public const string RTFX = "NET46"; 29 | #elif NetCore 30 | public const string RTFX = "NETCore"; 31 | #elif WINDOWS_UWP 32 | public const string RTFX = "UWP"; 33 | #else 34 | public const string RTFX = "UNKNOWN"; 35 | #endif 36 | 37 | /// 38 | /// SDK版本号 39 | /// 40 | public const string VERSION = "8.7.0"; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/UploadUtil.cs: -------------------------------------------------------------------------------- 1 | using Qiniu.Http; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | public static class UploadUtil 6 | { 7 | public static bool ShouldRetry(int code, int refCode) 8 | { 9 | if (code == (int) HttpCode.OK) 10 | { 11 | return false; 12 | } 13 | 14 | // allow list 15 | if ( 16 | refCode == (int)HttpCode.USER_UNDEF || 17 | refCode == (int)HttpCode.USER_NEED_RETRY 18 | ) 19 | { 20 | return true; 21 | } 22 | 23 | // block list 24 | int codeSeries = code / 100; 25 | 26 | if (codeSeries == 4 && code != (int)HttpCode.CRC32_CHECK_FAILEd) 27 | { 28 | return false; 29 | } 30 | 31 | if ( 32 | code == (int)HttpCode.FILE_NOT_EXIST || 33 | code == (int)HttpCode.FILE_EXISTS || 34 | code == (int)HttpCode.CALLBACK_FAILED 35 | ) 36 | { 37 | return false; 38 | } 39 | 40 | // others need retry 41 | return true; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/BucketInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Storage 2 | { 3 | /// 4 | /// bucket info 5 | /// 6 | public class BucketInfo 7 | { 8 | /// 9 | /// bucket name 10 | /// 11 | public string tbl { get; set; } 12 | 13 | /// 14 | /// itbl 15 | /// 16 | public long itbl { get; set; } 17 | 18 | /// 19 | /// deprecated 20 | /// 21 | public string phy {get;set;} 22 | 23 | /// 24 | /// id 25 | /// 26 | public long uid { get; set; } 27 | 28 | /// 29 | /// zone 30 | /// 31 | public string zone { get; set; } 32 | 33 | /// 34 | /// region 35 | /// 36 | public string region { get; set; } 37 | 38 | /// 39 | /// isGlobal 40 | /// 41 | public bool global { get; set; } 42 | 43 | /// 44 | /// isLineStorage 45 | /// 46 | public bool line { get; set; } 47 | 48 | /// 49 | /// creationTime 50 | /// 51 | public long ctime { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ListInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 获取空间文件(list操作) 7 | /// 8 | /// 返回JSON字符串 9 | /// 10 | /// { 11 | /// "marker":"MARKER", 12 | /// "items": 13 | /// [ 14 | /// { 15 | /// "key":"KEY", 16 | /// "hash":"HASH", 17 | /// "fsize":FSIZE, 18 | /// "mimeType":"MIME_TYPE", 19 | /// "putTime":PUT_TIME, 20 | /// "type":FILE_TYPE 21 | /// }, 22 | /// { 23 | /// ... 24 | /// } 25 | /// ], 26 | /// "CmmonPrefixes":"COMMON_PREFIXES" 27 | /// } 28 | /// 29 | /// 30 | public class ListInfo 31 | { 32 | /// 33 | /// marker标记 34 | /// 35 | [JsonProperty("marker", NullValueHandling = NullValueHandling.Ignore)] 36 | public string Marker { get; set; } 37 | 38 | /// 39 | /// 文件列表 40 | /// 41 | [JsonProperty("items", NullValueHandling = NullValueHandling.Ignore)] 42 | public List Items { get; set; } 43 | 44 | /// 45 | /// 公共前缀 46 | /// 47 | [JsonProperty("commonPrefixes", NullValueHandling = NullValueHandling.Ignore)] 48 | public List CommonPrefixes { get; set; } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/QiniuTests/Util/Signature.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | using System.Text; 3 | using NUnit.Framework; 4 | using Qiniu.Tests; 5 | using Qiniu.Util; 6 | 7 | namespace QiniuTests.Util 8 | { 9 | [TestFixture] 10 | public class SignatureTest : TestEnv 11 | { 12 | static Mac mac = new Mac("ak", "sk"); 13 | static Signature sign = new Signature(mac); 14 | 15 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))] 16 | public string SignatureV2Test(string method, string url, StringDictionary headers, string body) 17 | { 18 | return string.Format("Qiniu {0}", sign.SignRequestV2(method, url, headers, body)); 19 | } 20 | 21 | [TestCaseSource(typeof(SignatureV2DataClass), nameof(SignatureV2DataClass.TestCases))] 22 | public string SignatureV2ByBytesTest(string method, string url, StringDictionary headers, string body) 23 | { 24 | return string.Format("Qiniu {0}", sign.SignRequestV2(method, url, headers, Encoding.UTF8.GetBytes(body))); 25 | } 26 | 27 | [TestCaseSource(typeof(VerifyRequestDataClass), nameof(VerifyRequestDataClass.TestCases))] 28 | public bool VerifyRequestTest(string method, string url, StringDictionary headers, string body) 29 | { 30 | Mac mac = new Mac("abcdefghklmnopq", "1234567890"); 31 | Signature mockSign = new Signature(mac); 32 | return mockSign.VerifyRequest(method, url, headers, body); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/PutExtra.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 文件上传的额外可选设置 7 | /// 8 | public class PutExtra 9 | { 10 | /// 11 | /// 设置文件断点续传进度记录文件 12 | /// 13 | public string ResumeRecordFile { set; get; } 14 | /// 15 | /// 上传可选参数字典,参数名次以 x: 开头 16 | /// 17 | public Dictionary Params; 18 | /// 19 | /// 指定文件的MimeType 20 | /// 21 | public string MimeType { set; get; } 22 | /// 23 | /// 设置文件上传进度处理器 24 | /// 25 | public UploadProgressHandler ProgressHandler { set; get; } 26 | /// 27 | /// 设置文件上传的状态控制器 28 | /// 29 | public UploadController UploadController { set; get; } 30 | 31 | /// 32 | /// 最大重试次数 33 | /// 34 | /// 默认值应与 一致 35 | public int MaxRetryTimes { set; get; } = 3; 36 | 37 | /// 38 | /// 块并发上传的线程数量 39 | /// 40 | public int BlockUploadThreads { set; get; } 41 | 42 | /// 43 | /// 分片上传版本 目前支持v1/v2版本 默认v1 44 | /// 45 | public string Version = "v1"; 46 | 47 | /// 48 | /// 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB 49 | /// 50 | public int PartSize = 4 * 1024 * 1024; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/ResumeContext.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | 4 | namespace Qiniu.Storage 5 | { 6 | /// 7 | /// 分片上传的上下文信息 8 | /// 9 | public class ResumeContext 10 | { 11 | /// 12 | /// 上下文信息 13 | /// 14 | [JsonProperty("ctx")] 15 | public string Ctx { get; set; } 16 | 17 | /// 18 | /// 校验和 19 | /// 20 | [JsonProperty("checksum")] 21 | public string Checksum { get; set; } 22 | 23 | /// 24 | /// crc32校验值 25 | /// 26 | [JsonProperty("crc32")] 27 | public uint Crc32 { get; set; } 28 | 29 | /// 30 | /// 文件偏移位置 31 | /// 32 | [JsonProperty("offset")] 33 | public long Offset { get; set; } 34 | 35 | /// 36 | /// 上传目的host 37 | /// 38 | [JsonProperty("host")] 39 | public string Host { get; set; } 40 | 41 | /// 42 | /// ctx失效时刻 43 | /// 44 | [JsonProperty("expired_at")] 45 | public long ExpiredAt { get; set; } 46 | 47 | /// 48 | /// 新版分片上传上下文etag 49 | /// 50 | [JsonProperty("etag")] 51 | public Dictionary Etag { get; set; } 52 | 53 | /// 54 | /// 新版分片上传md5校验值 55 | /// 56 | [JsonProperty("md5")] 57 | public string Md5 { get; set; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ResumeBlocker.cs: -------------------------------------------------------------------------------- 1 | using Qiniu.Http; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace Qiniu.Storage 6 | { 7 | class ResumeBlocker 8 | { 9 | public ManualResetEvent DoneEvent { set; get; } 10 | public byte[] BlockBuffer { set; get; } 11 | public long BlockIndex { set; get; } 12 | public string UploadToken { set; get; } 13 | public PutExtra PutExtra { set; get; } 14 | public ResumeInfo ResumeInfo { set; get; } 15 | public Dictionary BlockMakeResults; 16 | public object ProgressLock { set; get; } 17 | public Dictionary UploadedBytesDict { set; get; } 18 | public long FileSize { set; get; } 19 | public string encodedObjectName { set; get; } // 仅用于分片上传 V2 20 | 21 | public ResumeBlocker(ManualResetEvent doneEvent, byte[] blockBuffer, long blockIndex, string uploadToken, 22 | PutExtra putExtra, ResumeInfo resumeInfo, Dictionary blockMakeResults, 23 | object progressLock, Dictionary uploadedBytesDict, long fileSize, string encodedObjectName) 24 | { 25 | this.DoneEvent = doneEvent; 26 | this.BlockBuffer = blockBuffer; 27 | this.BlockIndex = blockIndex; 28 | this.UploadToken = uploadToken; 29 | this.PutExtra = putExtra; 30 | this.ResumeInfo = resumeInfo; 31 | this.BlockMakeResults = blockMakeResults; 32 | this.ProgressLock = progressLock; 33 | this.UploadedBytesDict = uploadedBytesDict; 34 | this.FileSize = fileSize; 35 | this.encodedObjectName = encodedObjectName; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Qiniu/Qiniu.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Release 5 | AnyCPU 6 | {2F5B0328-DE8B-4B53-A500-3077E340A51B} 7 | Library 8 | Properties 9 | Qiniu 10 | Qiniu 11 | netstandard2.0 12 | 512 13 | 14 | 15 | 16 | false 17 | 18 | 19 | QiniuCSharpSDK.snk 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Qiniu 31 | 8.7.0 32 | Rong Zhou, Qiniu SDK 33 | Shanghai Qiniu Information Technology Co., Ltd. 34 | Qiniu Resource (Cloud) Storage SDK for C# 35 | 36 | 43 | 44 | -------------------------------------------------------------------------------- /src/QiniuTests/Storage/UploadUtilTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections; 3 | using Qiniu.Tests; 4 | 5 | namespace Qiniu.Storage.Tests 6 | { 7 | public class UploadUtilTestCases 8 | { 9 | public static IEnumerable ShouldRetryTestCases 10 | { 11 | get 12 | { 13 | // 200 always false 14 | yield return new TestCaseData(200, 200).Returns(false); 15 | 16 | // some refCode should retry 17 | yield return new TestCaseData(0, 3).Returns(true); 18 | yield return new TestCaseData(0, 0).Returns(true); 19 | 20 | // 4xx shouldn't retry but 406 21 | yield return new TestCaseData(406, 406).Returns(true); 22 | yield return new TestCaseData(400, 400).Returns(false); 23 | 24 | // some code shouldn't retry 25 | yield return new TestCaseData(612, 612).Returns(false); 26 | yield return new TestCaseData(614, 614).Returns(false); 27 | yield return new TestCaseData(579, 579).Returns(false); 28 | 29 | // any others should retry 30 | yield return new TestCaseData(500, 500).Returns(true); 31 | yield return new TestCaseData(502, 502).Returns(true); 32 | } 33 | } 34 | } 35 | 36 | [TestFixture] 37 | public class UploadUtilTests : TestEnv 38 | { 39 | [TestCaseSource(typeof(UploadUtilTestCases), nameof(UploadUtilTestCases.ShouldRetryTestCases))] 40 | public bool ShouldRetryTest(int code, int refCode) 41 | { 42 | return UploadUtil.ShouldRetry(code, refCode); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Qiniu/CDN/PrefetchInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Qiniu.CDN 4 | { 5 | /// 6 | /// 文件预取-消息内容结构 7 | /// 8 | /// 在请求成功时 code 为 200,requestId、quotaDay、surplusDay 才会有有效值,否则为空。 9 | /// 在请求失败时 code 为非 200,error 中包含描述信息。 10 | /// 11 | /// 以下是一个返回结果示例 12 | /// 13 | /// { 14 | /// "code":200, 15 | /// "error":"success", 16 | /// "requestId":"577471ace3ab3a030c058972", 17 | /// "invalidUrls":null, 18 | /// "quotaDay":100, 19 | /// "surplusDay":99 20 | /// } 21 | /// 22 | /// 23 | public class PrefetchInfo 24 | { 25 | /// 26 | /// 代码 含义 说明 27 | /// /// 200 success 成功(OK) 28 | /// 400031 invalid url 请求中存在无效的 url,请确保提交的 url 格式正确 29 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确 30 | /// 400033 prefetch url limit error 请求次数超出当日预取限额 31 | /// 400036 invalid request id 无效的请求 id 32 | /// 400037 url has existed url 正在预取中 33 | /// 500000 internal error 服务端内部错误,请联系技术支持 34 | /// 35 | public int Code { get; set; } 36 | 37 | /// 38 | /// 错误消息(状态码非OK时) 39 | /// 40 | public string Error { get; set; } 41 | 42 | /// 43 | /// 请求ID(可用于反馈排查) 44 | /// 45 | public string RequestId { get; set; } 46 | 47 | /// 48 | /// 非法URL 49 | /// 50 | public List InvalidUrls { get; set; } 51 | 52 | /// 53 | /// 当日限额 54 | /// 55 | public int QuotaDay { get; set; } 56 | 57 | /// 58 | /// 当日剩余额度 59 | /// 60 | public int SurplusDay { get; set; } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ZoneInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 从uc.qbox.me返回的消息 7 | /// 8 | internal class ZoneInfo 9 | { 10 | [JsonProperty("hosts")] 11 | public ZoneHost[] Hosts { get; set; } 12 | } 13 | 14 | internal class ZoneHost 15 | { 16 | /// 17 | /// 过期时间,单位:秒 18 | /// 19 | [JsonProperty("ttl")] 20 | public int Ttl { get; set; } 21 | 22 | [JsonProperty("region")] 23 | public string Region { get; set; } 24 | 25 | [JsonProperty("io")] 26 | public ServiceDomains Io { get; set; } 27 | 28 | [JsonProperty("io_src")] 29 | public ServiceDomains IoSrc { get; set; } 30 | 31 | [JsonProperty("up")] 32 | public ServiceDomains Up { get; set; } 33 | 34 | [JsonProperty("api")] 35 | public ServiceDomains Api { get; set; } 36 | 37 | [JsonProperty("rs")] 38 | public ServiceDomains Rs { get; set; } 39 | 40 | [JsonProperty("rsf")] 41 | public ServiceDomains Rsf { get; set; } 42 | 43 | [JsonProperty("s3")] 44 | public ServiceDomains S3 { get; set; } 45 | 46 | [JsonProperty("uc")] 47 | public ServiceDomains Uc { get; set; } 48 | 49 | internal class ServiceDomains 50 | { 51 | [JsonProperty("domains")] 52 | public string[] Domains { get; set; } 53 | 54 | [JsonProperty("old", NullValueHandling = NullValueHandling.Ignore)] 55 | public string[] Old { get; set; } 56 | 57 | [JsonProperty("region_alias", NullValueHandling = NullValueHandling.Ignore)] 58 | public string RegionAlias { get; set; } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ResumeInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Collections.Generic; 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 分片上传的记录信息 7 | /// 8 | public class ResumeInfo 9 | { 10 | /// 11 | /// 文件大小 12 | /// 13 | [JsonProperty("fileSize")] 14 | public long FileSize { get; set; } 15 | 16 | /// 17 | /// 文件块总数 18 | /// 19 | [JsonProperty("blockCount")] 20 | public long BlockCount { get; set; } 21 | 22 | /// 23 | /// 上下文信息列表 24 | /// 25 | [JsonProperty("contexts")] 26 | public string[] Contexts { get; set; } 27 | 28 | /// 29 | /// 上下文信息过期列表,与 context 配合使用 30 | /// 31 | [JsonProperty("contextsExpiredAt")] 32 | public long[] ContextsExpiredAt { get; set; } 33 | 34 | /// 35 | /// Ctx过期时间戳(单位秒) 36 | /// 37 | [JsonProperty("expiredAt")] 38 | public long ExpiredAt { get; set; } 39 | 40 | /// 41 | /// 上传进度信息序列化 42 | /// 43 | /// 44 | 45 | /// 46 | /// 新版分片上下文信息列表 47 | /// 48 | [JsonProperty("etags")] 49 | public Dictionary[] Etags { get; set; } 50 | 51 | /// 52 | /// 新版分片上传id 53 | /// 54 | [JsonProperty("uploadId")] 55 | public string UploadId { get; set; } 56 | 57 | /// 58 | /// 完成上传的字节数 59 | /// 60 | [JsonProperty("uploaded")] 61 | public long Uploaded { get; set; } 62 | 63 | public string ToJsonStr() 64 | { 65 | return JsonConvert.SerializeObject(this); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Qiniu/Util/UpToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using Qiniu.Storage; 5 | namespace Qiniu.Util 6 | { 7 | /// 8 | /// 上传凭证工具类 9 | /// 10 | public class UpToken 11 | { 12 | /// 13 | /// 从上传凭证获取AccessKey 14 | /// 15 | /// 上传凭证 16 | /// AccessKey 17 | public static string GetAccessKeyFromUpToken(string upToken) 18 | { 19 | string accessKey = null; 20 | string[] items = upToken.Split(':'); 21 | if (items.Length == 3) 22 | { 23 | accessKey = items[0]; 24 | } 25 | return accessKey; 26 | } 27 | 28 | /// 29 | /// 从上传凭证获取Bucket 30 | /// 31 | /// 上传凭证 32 | /// Bucket 33 | public static string GetBucketFromUpToken(string upToken) 34 | { 35 | string bucket = null; 36 | string[] items = upToken.Split(':'); 37 | if (items.Length == 3) 38 | { 39 | string encodedPolicy = items[2]; 40 | try 41 | { 42 | string policyStr = Encoding.UTF8.GetString(Base64.UrlsafeBase64Decode(encodedPolicy)); 43 | PutPolicy putPolicy = JsonConvert.DeserializeObject(policyStr); 44 | string scope = putPolicy.Scope; 45 | string[] scopeItems = scope.Split(':'); 46 | if (scopeItems.Length >= 1) 47 | { 48 | bucket = scopeItems[0]; 49 | } 50 | }catch(Exception) 51 | { 52 | 53 | } 54 | } 55 | return bucket; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/FluxRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | namespace Qiniu.CDN 3 | { 4 | /// 5 | /// 查询流量-请求 6 | /// 7 | public class FluxRequest 8 | { 9 | /// 10 | /// 起始日期,例如2016-09-01 11 | /// 12 | [JsonProperty("startDate")] 13 | public string StartDate { get; set; } 14 | 15 | /// 16 | /// 结束日期,例如2016-09-10 17 | /// 18 | [JsonProperty("endDate")] 19 | public string EndDate { get; set; } 20 | 21 | /// 22 | /// 时间粒度((取值:5min / hour /day)) 23 | /// 24 | [JsonProperty("granularity")] 25 | public string Granularity { get; set; } 26 | 27 | /// 28 | /// 域名列表,以西文半角分号分割 29 | /// 30 | [JsonProperty("domains")] 31 | public string Domains { get; set; } 32 | 33 | /// 34 | /// 初始化(所有成员为空,需要后续赋值) 35 | /// 36 | public FluxRequest() 37 | { 38 | StartDate = ""; 39 | EndDate = ""; 40 | Granularity = ""; 41 | Domains = ""; 42 | } 43 | 44 | /// 45 | /// 初始化所有成员 46 | /// 47 | /// 起始日期 48 | /// 结束日期 49 | /// 时间粒度 50 | /// 域名列表 51 | public FluxRequest(string startDate, string endDate, string granularity, string domains) 52 | { 53 | StartDate = startDate; 54 | EndDate = endDate; 55 | Granularity = granularity; 56 | Domains = domains; 57 | } 58 | 59 | /// 60 | /// 转换到JSON字符串 61 | /// 62 | /// 请求内容的JSON字符串 63 | public string ToJsonStr() 64 | { 65 | return JsonConvert.SerializeObject(this); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/BandwidthRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | namespace Qiniu.CDN 3 | { 4 | /// 5 | /// 查询带宽-请求 6 | /// 7 | public class BandwidthRequest 8 | { 9 | /// 10 | /// 起始日期,例如2016-09-01 11 | /// 12 | [JsonProperty("startDate")] 13 | public string StartDate { get; set; } 14 | 15 | /// 16 | /// 结束日期,例如2016-09-10 17 | /// 18 | [JsonProperty("endDate")] 19 | public string EndDate { get; set; } 20 | 21 | /// 22 | /// 时间粒度((取值:5min / hour /day)) 23 | /// 24 | [JsonProperty("granularity")] 25 | public string Granularity { get; set; } 26 | 27 | /// 28 | /// 域名列表,以西文半角分号分割 29 | /// 30 | [JsonProperty("domains")] 31 | public string Domains { get; set; } 32 | 33 | /// 34 | /// 初始化(所有成员为空,需要后续赋值) 35 | /// 36 | public BandwidthRequest() 37 | { 38 | StartDate = ""; 39 | EndDate = ""; 40 | Granularity = ""; 41 | Domains = ""; 42 | } 43 | 44 | /// 45 | /// 初始化所有成员 46 | /// 47 | /// 起始日期 48 | /// 结束日期 49 | /// 时间粒度 50 | /// 域名列表 51 | public BandwidthRequest(string startDate, string endDate, string granularity, string domains) 52 | { 53 | StartDate = startDate; 54 | EndDate = endDate; 55 | Granularity = granularity; 56 | Domains = domains; 57 | } 58 | 59 | /// 60 | /// 转换到JSON字符串 61 | /// 62 | /// 请求内容的JSON字符串 63 | public string ToJsonStr() 64 | { 65 | return JsonConvert.SerializeObject(this); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/PrefetchRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 文件预取-请求 9 | /// 10 | public class PrefetchRequest 11 | { 12 | /// 13 | /// 要预取的单个url列表,总数不超过100条 14 | /// 单个url,即一个具体的url,例如:http://bar.foo.com/test.zip 15 | /// 注意: 16 | /// 请输入资源 url 完整的绝对路径,由 http:// 或 https:// 开始 17 | /// 资源 url 不支持通配符,例如:不支持 http://www.test.com/abc/*.* 18 | /// 19 | [JsonProperty("urls",NullValueHandling=NullValueHandling.Ignore)] 20 | public List Urls { get; set; } 21 | 22 | /// 23 | /// 初始化(URL列表为空,需要后续赋值) 24 | /// 25 | public PrefetchRequest() 26 | { 27 | Urls = new List(); 28 | } 29 | 30 | /// 31 | /// 初始化(URL列表) 32 | /// 33 | /// URL列表 34 | public PrefetchRequest(IList urls) 35 | { 36 | if (urls != null) 37 | { 38 | Urls = new List(urls); 39 | } 40 | else 41 | { 42 | Urls = new List(); 43 | } 44 | } 45 | 46 | /// 47 | /// 添加要查询的URL 48 | /// 49 | /// URL列表 50 | public void AddUrls(IList urls) 51 | { 52 | if (urls != null) 53 | { 54 | foreach (string u in urls) 55 | { 56 | if(!Urls.Contains(u)) 57 | { 58 | Urls.Add(u); 59 | } 60 | } 61 | } 62 | } 63 | 64 | /// 65 | /// 转换到JSON字符串 66 | /// 67 | /// 请求内容的JSON字符串 68 | public string ToJsonStr() 69 | { 70 | return JsonConvert.SerializeObject(this); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/PrefopResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Qiniu.Http; 3 | using System.Text; 4 | namespace Qiniu.Storage 5 | { 6 | /// 7 | /// 查询数据处理状态的返回值 8 | /// 9 | public class PrefopResult:HttpResult 10 | { 11 | /// 12 | /// 持久化任务的状态 13 | /// 14 | public PfopInfo Result 15 | { 16 | get 17 | { 18 | PfopInfo info = null; 19 | 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info= JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code: {0}\n", Code); 37 | 38 | if (this.Result!=null) 39 | { 40 | sb.AppendFormat("result: {0}\n", JsonConvert.SerializeObject(this.Result)); 41 | } 42 | else 43 | { 44 | if (!string.IsNullOrEmpty(Text)) 45 | { 46 | sb.AppendLine("text:"); 47 | sb.AppendLine(Text); 48 | } 49 | } 50 | sb.AppendLine(); 51 | 52 | sb.AppendFormat("ref-code:{0}\n", RefCode); 53 | 54 | if (!string.IsNullOrEmpty(RefText)) 55 | { 56 | sb.AppendLine("ref-text:"); 57 | sb.AppendLine(RefText); 58 | } 59 | 60 | if (RefInfo != null) 61 | { 62 | sb.AppendFormat("ref-info:\n"); 63 | foreach (var d in RefInfo) 64 | { 65 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 66 | } 67 | } 68 | 69 | return sb.ToString(); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/FetchResult.cs: -------------------------------------------------------------------------------- 1 | using Qiniu.Http; 2 | using Newtonsoft.Json; 3 | using System.Text; 4 | namespace Qiniu.Storage 5 | { 6 | /// 7 | /// 文件抓取返回的消息 8 | /// 9 | public class FetchResult : HttpResult 10 | { 11 | /// 12 | /// Fetch信息列表 13 | /// 14 | public FetchInfo Result 15 | { 16 | get 17 | { 18 | FetchInfo info = null; 19 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 20 | { 21 | info = JsonConvert.DeserializeObject(Text); 22 | } 23 | return info; 24 | } 25 | } 26 | 27 | /// 28 | /// 转换为易读字符串格式 29 | /// 30 | /// 便于打印和阅读的字符串 31 | public override string ToString() 32 | { 33 | StringBuilder sb = new StringBuilder(); 34 | 35 | sb.AppendFormat("code: {0}\n", Code); 36 | 37 | if (Result != null) 38 | { 39 | sb.AppendFormat("Key={0}, Size={1}, Type={2}, Hash={3}\n", 40 | Result.Key, Result.Fsize, Result.MimeType, Result.Hash); 41 | } 42 | else 43 | { 44 | if (!string.IsNullOrEmpty(Text)) 45 | { 46 | sb.AppendLine("text:"); 47 | sb.AppendLine(Text); 48 | } 49 | } 50 | sb.AppendLine(); 51 | 52 | sb.AppendFormat("ref-code: {0}\n", RefCode); 53 | 54 | if (!string.IsNullOrEmpty(RefText)) 55 | { 56 | sb.AppendLine("ref-text:"); 57 | sb.AppendLine(RefText); 58 | } 59 | 60 | if (RefInfo != null) 61 | { 62 | sb.AppendFormat("ref-info:\n"); 63 | foreach (var d in RefInfo) 64 | { 65 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 66 | } 67 | } 68 | 69 | return sb.ToString(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ListItem.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | namespace Qiniu.Storage 3 | { 4 | /// 5 | /// 文件描述 6 | /// 7 | public class ListItem 8 | { 9 | /// 10 | /// 文件名 11 | /// 12 | [JsonProperty("key")] 13 | public string Key { get; set; } 14 | 15 | /// 16 | /// 文件hash(ETAG) 17 | /// 18 | [JsonProperty("hash")] 19 | public string Hash { get; set; } 20 | 21 | /// 22 | /// 文件大小(字节) 23 | /// 24 | [JsonProperty("fsize")] 25 | public long Fsize { get; set; } 26 | 27 | /// 28 | /// 文件MIME类型 29 | /// 30 | [JsonProperty("mimeType")] 31 | public string MimeType { get; set; } 32 | 33 | /// 34 | /// 上传时间 35 | /// 36 | [JsonProperty("putTime")] 37 | public long PutTime { get; set; } 38 | 39 | /// 40 | /// 文件存储类型 41 | /// 0 标准存储 42 | /// 1 低频存储 43 | /// 2 归档存储 44 | /// 3 深度归档存储 45 | /// 4 归档直读存储 46 | /// 47 | [JsonProperty("type")] 48 | public int FileType { get; set; } 49 | 50 | /// 51 | /// 资源内容的唯一属主标识 52 | /// 详见上传策略:https://developer.qiniu.com/kodo/1206/put-policy 53 | /// 54 | [JsonProperty("endUser", NullValueHandling = NullValueHandling.Ignore)] 55 | public string EndUser { get; set; } 56 | 57 | /// 58 | /// 文件的存储状态 59 | /// 0 启用 60 | /// 1 禁用 61 | /// 62 | [JsonProperty("status")] 63 | public int Status { get; set; } 64 | 65 | /// 66 | /// 文件的 md5 值 67 | /// 服务端不确保一定返回此字段,详见: 68 | /// https://developer.qiniu.com/kodo/1284/list#:~:text=%E3%80%82%0A%0A%E7%B1%BB%E5%9E%8B%EF%BC%9A%E6%95%B0%E5%AD%97-,md5,-%E5%90%A6 69 | /// 70 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)] 71 | public string Md5 { get; set; } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Qiniu/Util/Base64.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace Qiniu.Util 5 | { 6 | /// 7 | /// Base64 编码/解码 8 | /// 9 | public class Base64 10 | { 11 | /// 12 | /// 获取字符串Url安全Base64编码值 13 | /// 14 | /// 源字符串 15 | /// 已编码字符串 16 | public static string UrlSafeBase64Encode(string text) 17 | { 18 | return UrlSafeBase64Encode(Encoding.UTF8.GetBytes(text)); 19 | } 20 | 21 | /// 22 | /// URL安全的base64编码 23 | /// 24 | /// 需要编码的字节数据 25 | /// 26 | public static string UrlSafeBase64Encode(byte[] data) 27 | { 28 | return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_'); 29 | } 30 | 31 | /// 32 | /// bucket:key 编码 33 | /// 34 | /// 空间名称 35 | /// 文件key 36 | /// 编码 37 | public static string UrlSafeBase64Encode(string bucket, string key) 38 | { 39 | return UrlSafeBase64Encode(bucket + ":" + key); 40 | } 41 | 42 | /// 43 | /// Base64解码 44 | /// 45 | /// 待解码的字符串 46 | /// 已解码字符串 47 | public static byte[] UrlsafeBase64Decode(string text) 48 | { 49 | return Convert.FromBase64String(text.Replace('-', '+').Replace('_', '/')); 50 | } 51 | 52 | /// 53 | /// 获取EncodedObjectName,建议仅用于分片上传 V2 54 | /// 55 | /// 待加密的字符串 56 | /// 已加密的字符串 57 | public static string GetEncodedObjectName(string key) 58 | { 59 | string encodedObjectName = "~"; 60 | if (key != null) 61 | { 62 | encodedObjectName = UrlSafeBase64Encode(key); 63 | } 64 | return encodedObjectName; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/StatResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Qiniu.Http; 3 | using Newtonsoft.Json; 4 | 5 | namespace Qiniu.Storage 6 | { 7 | /// 8 | /// 获取空间文件信息(stat操作)的返回消息 9 | /// 10 | public class StatResult : HttpResult 11 | { 12 | /// 13 | /// stat信息列表 14 | /// 15 | public FileInfo Result 16 | { 17 | get 18 | { 19 | FileInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info = JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code: {0}\n", Code); 37 | 38 | if (Result != null) 39 | { 40 | sb.AppendFormat("Size={0}, Type={1}, Hash={2}, Time={3}\n", 41 | Result.Fsize, Result.MimeType, Result.Hash, Result.PutTime); 42 | 43 | } 44 | else 45 | { 46 | if (!string.IsNullOrEmpty(Text)) 47 | { 48 | sb.AppendLine("text:"); 49 | sb.AppendLine(Text); 50 | } 51 | } 52 | sb.AppendLine(); 53 | 54 | sb.AppendFormat("ref-code: {0}\n", RefCode); 55 | 56 | if (!string.IsNullOrEmpty(RefText)) 57 | { 58 | sb.AppendLine("ref-text:"); 59 | sb.AppendLine(RefText); 60 | } 61 | 62 | if (RefInfo != null) 63 | { 64 | sb.AppendFormat("ref-info:\n"); 65 | foreach (var d in RefInfo) 66 | { 67 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 68 | } 69 | } 70 | 71 | return sb.ToString(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/BucketsResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Qiniu.Http; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 获取空间列表-结果 10 | /// 11 | public class BucketsResult:HttpResult 12 | { 13 | /// 14 | /// 空间列表 15 | /// 16 | public List Result 17 | { 18 | get 19 | { 20 | List buckets = null; 21 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 22 | { 23 | buckets = JsonConvert.DeserializeObject>(Text); 24 | } 25 | return buckets; 26 | } 27 | } 28 | 29 | /// 30 | /// 转换为易读字符串格式 31 | /// 32 | /// 便于打印和阅读的字符串 33 | public override string ToString() 34 | { 35 | StringBuilder sb = new StringBuilder(); 36 | 37 | sb.AppendFormat("code: {0}\n", Code); 38 | 39 | if (Result != null) 40 | { 41 | sb.AppendLine("bucket(s):"); 42 | foreach(var b in Result) 43 | { 44 | sb.AppendLine(b); 45 | } 46 | } 47 | else 48 | { 49 | if (!string.IsNullOrEmpty(Text)) 50 | { 51 | sb.AppendLine("text:"); 52 | sb.AppendLine(Text); 53 | } 54 | } 55 | sb.AppendLine(); 56 | 57 | sb.AppendFormat("ref-code: {0}\n", RefCode); 58 | 59 | if (!string.IsNullOrEmpty(RefText)) 60 | { 61 | sb.AppendLine("ref-text:"); 62 | sb.AppendLine(RefText); 63 | } 64 | 65 | if (RefInfo != null) 66 | { 67 | sb.AppendFormat("ref-info:\n"); 68 | foreach (var d in RefInfo) 69 | { 70 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 71 | } 72 | } 73 | 74 | return sb.ToString(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Qiniu/Http/Middleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Qiniu.Http 5 | { 6 | public delegate HttpResult DNextSend(HttpRequestOptions reqOpts); 7 | 8 | public interface IMiddleware 9 | { 10 | HttpResult Send(HttpRequestOptions req, DNextSend next); 11 | } 12 | 13 | public delegate bool DRetryCondition(HttpResult respResult, HttpRequestOptions reqOpts); 14 | 15 | public class RetryDomainsMiddleware : IMiddleware 16 | { 17 | private List _backupDomains; 18 | 19 | private int _maxRetryTimes; 20 | 21 | private DRetryCondition _retryCondition; 22 | 23 | public RetryDomainsMiddleware( 24 | List backupDomains, 25 | int maxRetryTimes = 2, 26 | DRetryCondition retryCondition = null 27 | ) 28 | { 29 | _backupDomains = backupDomains; 30 | _maxRetryTimes = maxRetryTimes; 31 | _retryCondition = retryCondition; 32 | } 33 | 34 | public HttpResult Send(HttpRequestOptions reqOpts, DNextSend next) 35 | { 36 | HttpResult result = null; 37 | 38 | UriBuilder uriBuilder = new UriBuilder(reqOpts.Url); 39 | List domains = new List(_backupDomains); 40 | domains.Insert(0, uriBuilder.Host); 41 | 42 | foreach (string domain in domains) 43 | { 44 | uriBuilder.Host = domain; 45 | reqOpts.Url = uriBuilder.ToString(); 46 | 47 | for (int retriedTimes = 0; retriedTimes < _maxRetryTimes; retriedTimes++) 48 | { 49 | result = next(reqOpts); 50 | if (!ShouldRetry(result, reqOpts)) 51 | { 52 | return result; 53 | } 54 | } 55 | } 56 | 57 | return result; 58 | } 59 | 60 | private bool ShouldRetry(HttpResult respResult, HttpRequestOptions reqOpts) 61 | { 62 | if (_retryCondition != null) 63 | { 64 | return _retryCondition(respResult, reqOpts); 65 | } 66 | 67 | return respResult != null && respResult.NeedRetry(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/DomainsResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Qiniu.Http; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 获取空间域名(domains操作)的返回消息 10 | /// 11 | public class DomainsResult:HttpResult 12 | { 13 | /// 14 | /// 域名(列表) 15 | /// 16 | public List Result 17 | { 18 | get 19 | { 20 | List domains = null; 21 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 22 | { 23 | domains=JsonConvert.DeserializeObject>(Text); 24 | } 25 | return domains; 26 | } 27 | } 28 | 29 | /// 30 | /// 转换为易读字符串格式 31 | /// 32 | /// 便于打印和阅读的字符串 33 | public override string ToString() 34 | { 35 | StringBuilder sb = new StringBuilder(); 36 | 37 | sb.AppendFormat("code: {0}\n", Code); 38 | 39 | sb.AppendLine(); 40 | 41 | if (Result != null) 42 | { 43 | sb.AppendLine("domain(s):"); 44 | foreach (var d in Result) 45 | { 46 | sb.AppendLine(d); 47 | } 48 | } 49 | else 50 | { 51 | if (!string.IsNullOrEmpty(Text)) 52 | { 53 | sb.AppendLine("text:"); 54 | sb.AppendLine(Text); 55 | } 56 | } 57 | sb.AppendLine(); 58 | 59 | sb.AppendFormat("ref-code: {0}\n", RefCode); 60 | 61 | if (!string.IsNullOrEmpty(RefText)) 62 | { 63 | sb.AppendLine("ref-text:"); 64 | sb.AppendLine(RefText); 65 | } 66 | 67 | if (RefInfo != null) 68 | { 69 | sb.AppendFormat("ref-info:\n"); 70 | foreach (var d in RefInfo) 71 | { 72 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 73 | } 74 | } 75 | 76 | return sb.ToString(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/FluxInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System; 3 | namespace Qiniu.CDN 4 | { 5 | /// 6 | /// 流量-消息内容结构 7 | /// 说明: 8 | /// 1.返回的数据包含开始日期和结束日期 9 | /// 2.带宽的单位为 byte 10 | /// 3.数据(data)只包含有流量的域名 11 | /// 12 | /// 以下是一个返回结果示例 13 | /// 14 | /// { 15 | /// "code": 200, 16 | /// "error": "", 17 | /// "time": ["2016-07-01 00:00:00","2016-07-01 00:05:00", ...], 18 | /// "data": { 19 | /// "a.com": { 20 | /// "china": [8888, 9999, 10000, ...], 21 | /// "oversea": [3333, 4444, 5000, ...], 22 | /// }, 23 | /// "b.com": { 24 | /// "china": [8888, 9999, 10000, ...], 25 | /// "oversea": [3333, 4444, 5000, ...], 26 | /// } 27 | /// } 28 | /// } 29 | /// 30 | /// 另请参阅 http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html#batch-flux 31 | /// 32 | public class FluxInfo 33 | { 34 | /// 35 | /// 代码 含义 说明 36 | /// 200 success 成功(OK) 37 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确 38 | /// 400080 invalid start time 开始时间格式错误 39 | /// 400081 invalid end time 截止时间格式错误 40 | /// 400082 invalid time range 时间范围错误,请确保开始时间早于结束时间,且时间范围不超过 30 天 41 | /// 500000 internal error 服务端内部错误,请联系技术支持 42 | /// 43 | public int Code { get; set; } 44 | 45 | /// 46 | /// 错误消息(状态码非OK时) 47 | /// 48 | public string Error { get; set; } 49 | 50 | /// 51 | /// 时间点列表 52 | /// 53 | public List Time { get; set; } 54 | 55 | /// 56 | /// 流量数居(与时间点列表对应) 57 | /// 58 | public Dictionary Data { get; set; } 59 | 60 | /// 61 | /// 流量-数据内容 62 | /// 63 | public class FluxData 64 | { 65 | /// 66 | /// 国内流量数据 67 | /// 68 | public List China { get; set; } 69 | 70 | /// 71 | /// 海外流量数据 72 | /// 73 | public List Oversea { get; set; } 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/BandwidthInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System; 3 | namespace Qiniu.CDN 4 | { 5 | /// 6 | /// 带宽-消息内容结构 7 | /// 说明: 8 | /// 1.返回的数据包含开始日期和结束日期 9 | /// 2.带宽的单位为 bps 10 | /// 3.数据(data)只包含有流量的域名 11 | /// 以下是一个返回结果示例 12 | /// 13 | /// 200 OK HTTP/1.1 14 | /// { 15 | /// "code": 200, 16 | /// "error": "", 17 | /// "time": ["2016-07-01 00:00:00","2016-07-01 00:05:00", ...], 18 | /// "data": { 19 | /// "a.com": { 20 | /// "china": [8888, 9999, 10000, ...], 21 | /// "oversea": [3333, 4444, 5000, ...], 22 | /// }, 23 | /// "b.com": { 24 | /// "china": [8888, 9999, 10000, ...], 25 | /// "oversea": [3333, 4444, 5000, ...], 26 | /// } 27 | /// } 28 | /// } 29 | /// 30 | /// 另请参阅 http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html#batch-bandwidth 31 | /// 32 | public class BandwidthInfo 33 | { 34 | /// 35 | /// 代码 含义 说明 36 | /// 200 success 成功(OK) 37 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确 38 | /// 400080 invalid start time 开始时间格式错误 39 | /// 400081 invalid end time 截止时间格式错误 40 | /// 400082 invalid time range 时间范围错误,请确保开始时间早于结束时间,且时间范围不超过 30 天 41 | /// 500000 internal error 服务端内部错误,请联系技术支持 42 | /// 43 | public int Code { get; set; } 44 | 45 | /// 46 | /// 错误消息(状态码非OK时) 47 | /// 48 | public string Error { get; set; } 49 | 50 | /// 51 | /// 时间点列表 52 | /// 53 | public List Time { get; set; } 54 | 55 | /// 56 | /// 带宽数居(与时间点列表对应) 57 | /// 数据内容请参见该类型说明 58 | /// 59 | public Dictionary Data { get; set; } 60 | } 61 | 62 | /// 63 | /// 带宽-数据内容 64 | /// 65 | public class BandWidthData 66 | { 67 | /// 68 | /// 国内带宽数据 69 | /// 70 | public List China { get; set; } 71 | 72 | /// 73 | /// 海外带宽数据 74 | /// 75 | public List Oversea { get; set; } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/PfopResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Qiniu.Http; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 持久化 10 | /// 11 | public class PfopResult : HttpResult 12 | { 13 | /// 14 | /// 此ID可用于查询持久化进度 15 | /// 16 | public string PersistentId 17 | { 18 | get 19 | { 20 | string pid = null; 21 | 22 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 23 | { 24 | Dictionary ret= JsonConvert.DeserializeObject>(Text); 25 | if (ret.ContainsKey("persistentId")) 26 | { 27 | pid = ret["persistentId"]; 28 | } 29 | } 30 | return pid; 31 | } 32 | } 33 | 34 | /// 35 | /// 转换为易读字符串格式 36 | /// 37 | /// 便于打印和阅读的字符串 38 | public override string ToString() 39 | { 40 | StringBuilder sb = new StringBuilder(); 41 | 42 | sb.AppendFormat("code: {0}\n", Code); 43 | 44 | if (!string.IsNullOrEmpty(PersistentId)) 45 | { 46 | sb.AppendFormat("PersistentId: {0}\n", PersistentId); 47 | } 48 | else 49 | { 50 | if (!string.IsNullOrEmpty(Text)) 51 | { 52 | sb.AppendLine("text:"); 53 | sb.AppendLine(Text); 54 | } 55 | } 56 | sb.AppendLine(); 57 | 58 | sb.AppendFormat("ref-code:{0}\n", RefCode); 59 | 60 | if (!string.IsNullOrEmpty(RefText)) 61 | { 62 | sb.AppendLine("ref-text:"); 63 | sb.AppendLine(RefText); 64 | } 65 | 66 | if (RefInfo != null) 67 | { 68 | sb.AppendFormat("ref-info:\n"); 69 | foreach (var d in RefInfo) 70 | { 71 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 72 | } 73 | } 74 | 75 | return sb.ToString(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/RefreshInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Qiniu.CDN 4 | { 5 | /// 6 | /// 缓存刷新-消息内容结构 7 | /// 8 | /// 在请求成功时 code 为 200,requestId、urlQuotaDay、urlSurplusDay、dirQuotaDay、dirSurplusDay才会有有效值,否则为空。 9 | /// 在请求失败时 code 为非 200,error 中包含描述信息。 10 | /// 11 | /// 以下是一个返回结果示例 12 | /// 13 | /// { 14 | /// "code":200, 15 | /// "error":"success", 16 | /// "requestId":"575d1930f9537d3f2600003d", 17 | /// "invalidUrls":null, 18 | /// "invalidDirs":null, 19 | /// "urlQuotaDay":100, 20 | /// "urlSurplusDay":99, 21 | /// "dirQuotaDay":10, 22 | /// "dirSurplusDay":10 23 | /// } 24 | /// 25 | /// 26 | public class RefreshInfo 27 | { 28 | /// 29 | /// 代码 含义 说明 30 | /// 200 success 成功(OK) 31 | /// 400031 invalid url 请求中存在无效的 url,请确保 url 格式正确 32 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确 33 | /// 400034 refresh url limit error 请求次数超出当日刷新限额 34 | /// 400036 invalid request id 无效的请求 id 35 | /// 400037 url has existed url 正在刷新中 36 | /// 500000 internal error 服务端内部错误,请联系技术支持 37 | /// 38 | public int Code { get; set; } 39 | 40 | /// 41 | /// 错误消息(状态码非OK时) 42 | /// 43 | public string Error { get; set; } 44 | 45 | /// 46 | /// 请求ID(可用于反馈排查) 47 | /// 48 | public string RequestId { get; set; } 49 | 50 | /// 51 | /// 非法URL 52 | /// 53 | public List InvalidUrls { get; set; } 54 | 55 | /// 56 | /// 非法URL目录 57 | /// 58 | public List InvalidDirs { get; set; } 59 | 60 | /// 61 | /// 当日URL刷新限额 62 | /// 63 | public int UrlQuotaDay { get; set; } 64 | 65 | /// 66 | /// 当日剩余URL刷新额度 67 | /// 68 | public int UrlSurplusDay { get; set; } 69 | 70 | /// 71 | /// 当日URL目录刷新限额 72 | /// 73 | public int DirQuotaDay { get; set; } 74 | 75 | /// 76 | /// 当日剩余URL目录刷新额度 77 | /// 78 | public int DirSurplusDay { get; set; } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/BucketResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.Storage 6 | { 7 | /// 8 | /// 获取bucket信息-结果 9 | /// 10 | public class BucketResult : HttpResult 11 | { 12 | /// 13 | /// bucket信息 14 | /// 15 | public BucketInfo Result 16 | { 17 | get 18 | { 19 | BucketInfo info = null; 20 | 21 | if (Code == (int)HttpCode.OK && !string.IsNullOrEmpty(Text)) 22 | { 23 | info= JsonConvert.DeserializeObject(Text); 24 | } 25 | 26 | return info; 27 | } 28 | } 29 | 30 | /// 31 | /// 转换为易读字符串格式 32 | /// 33 | /// 便于打印和阅读的字符串 34 | public override string ToString() 35 | { 36 | StringBuilder sb = new StringBuilder(); 37 | 38 | sb.AppendFormat("code: {0}\n", Code); 39 | 40 | if (Result != null) 41 | { 42 | sb.AppendLine("bucket-info:"); 43 | sb.AppendFormat("tbl={0}\n", Result.tbl); 44 | sb.AppendFormat("zone={0}\n", Result.zone); 45 | sb.AppendFormat("region={0}\n", Result.region); 46 | sb.AppendFormat("isGlobal={0}\n", Result.global); 47 | sb.AppendFormat("isLine={0}\n", Result.line); 48 | } 49 | else 50 | { 51 | if (!string.IsNullOrEmpty(Text)) 52 | { 53 | sb.AppendLine("text:"); 54 | sb.AppendLine(Text); 55 | } 56 | } 57 | sb.AppendLine(); 58 | 59 | sb.AppendFormat("ref-code: {0}\n", RefCode); 60 | 61 | if (!string.IsNullOrEmpty(RefText)) 62 | { 63 | sb.AppendLine("ref-text:"); 64 | sb.AppendLine(RefText); 65 | } 66 | 67 | if (RefInfo != null) 68 | { 69 | sb.AppendFormat("ref-info:\n"); 70 | foreach (var d in RefInfo) 71 | { 72 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 73 | } 74 | } 75 | 76 | return sb.ToString(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/LogListInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Qiniu.CDN 4 | { 5 | /// 6 | /// 日志-消息内容结构 7 | /// 8 | /// 以下是一个返回结果示例 9 | /// 10 | /// { 11 | /// "data": { 12 | /// "log-test1.SOME_TEST.com": [ 13 | /// { 14 | /// "name":"log-test1.SOME_TEST.com_2016-07-01-00_00.gz", 15 | /// "size": 88490306, 16 | /// "mtime": 1466274440, 17 | /// "url": "http://FUSION_LOG_DOWNLOAD_URL1" 18 | /// } 19 | /// ], 20 | /// "log-test2.SOME_TEST.com": [ 21 | /// { 22 | /// "name":"log-test2.SOME_TEST.com_2016-07-01-00_00.gz", 23 | /// "size": 73280873, 24 | /// "mtime": 1466273259, 25 | /// "url": "http://FUSION_LOG_DOWNLOAD_URL2" 26 | /// } 27 | /// ] 28 | /// } 29 | /// } 30 | /// 31 | /// /// 32 | public class LogListInfo 33 | { 34 | /// 35 | /// 代码 含义 说明 36 | /// 200 success 成功(OK) 37 | /// 400 invalid params 请求参数格式错误 38 | /// 401 bad token 认证授权失败(包括密钥信息不正确;数字签名错误;授权已超时) 39 | /// 400032 invalid host 请求中存在无效的域名,请确保域名格式正确 40 | /// 500000 internal error 服务器内部错误 41 | /// 42 | public int Code { get; set; } 43 | 44 | /// 45 | /// 错误消息(状态码非OK时) 46 | /// 47 | public string Error { get; set; } 48 | 49 | /// 50 | /// 日志信息(与域名列表对应) 51 | /// 52 | public Dictionary> Data { get; set; } 53 | } 54 | 55 | /// 56 | /// 日志信息内容 57 | /// 58 | public class LogData 59 | { 60 | /// 61 | /// 文件名 62 | /// 63 | public string Name { get; set; } 64 | 65 | /// 66 | /// 文件大小,单位为 Byte 67 | /// 68 | public long Size { get; set; } 69 | 70 | /// 71 | /// 文件修改时间,Unix 时间戳 72 | /// 73 | public long Mtime { get; set; } 74 | 75 | /// 76 | /// 日志下载链接 77 | /// 78 | public string Url { get; set; } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/QiniuTests/Storage/ZoneHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | using Qiniu.Tests; 4 | 5 | namespace Qiniu.Storage.Tests 6 | { 7 | [TestFixture] 8 | public class ZoneHelperTests : TestEnv 9 | { 10 | [Test] 11 | public void QueryZoneTest() 12 | { 13 | Zone zone = ZoneHelper.QueryZone(AccessKey, Bucket); 14 | 15 | Assert.NotNull(zone); 16 | } 17 | 18 | [Test] 19 | public void QueryZoneWithCustomQueryRegionHost() 20 | { 21 | Config config = new Config(); 22 | config.SetQueryRegionHost("uc.qbox.me"); 23 | config.UseHttps = true; 24 | 25 | Zone zone = ZoneHelper.QueryZone( 26 | AccessKey, 27 | Bucket, 28 | config.UcHost() 29 | ); 30 | Assert.NotNull(zone); 31 | } 32 | 33 | [Test] 34 | public void QueryZoneWithBackupHostsTest() 35 | { 36 | Config config = new Config(); 37 | config.SetQueryRegionHost("fake-uc.csharp.qiniu.com"); 38 | config.SetBackupQueryRegionHosts(new List 39 | { 40 | "unavailable-uc.csharp.qiniu.com", 41 | "uc.qbox.me" 42 | } 43 | ); 44 | config.UseHttps = true; 45 | 46 | Zone zone = ZoneHelper.QueryZone( 47 | AccessKey, 48 | Bucket, 49 | config.UcHost(), 50 | config.BackupQueryRegionHosts() 51 | ); 52 | Assert.NotNull(zone); 53 | } 54 | 55 | [Test] 56 | public void QueryZoneWithUcAndBackupHostsTest() 57 | { 58 | Config config = new Config(); 59 | config.SetUcHost("fake-uc.csharp.qiniu.com"); 60 | config.SetBackupQueryRegionHosts(new List 61 | { 62 | "unavailable-uc.csharp.qiniu.com", 63 | "uc.qbox.me" 64 | } 65 | ); 66 | config.UseHttps = true; 67 | 68 | Zone zone = ZoneHelper.QueryZone( 69 | AccessKey, 70 | Bucket, 71 | config.UcHost(), 72 | config.BackupQueryRegionHosts() 73 | ); 74 | Assert.NotNull(zone); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/QiniuTests/Http/HttpResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using NUnit.Framework; 5 | 6 | using Qiniu.Http; 7 | 8 | namespace QiniuTests.Http 9 | { 10 | public class NeedRetryDataClass 11 | { 12 | private static readonly IReadOnlyList NotRetryableHttpCodes = new List 13 | { 14 | (int)HttpCode.INVALID_ARGUMENT, 15 | (int)HttpCode.INVALID_FILE, 16 | (int)HttpCode.INVALID_TOKEN, 17 | (int)HttpCode.USER_CANCELED, 18 | (int)HttpCode.USER_PAUSED, 19 | // 服务端 20 | (int)HttpCode.NOT_IMPLEMENTED, 21 | (int)HttpCode.BANDWIDTH_LIMIT_EXCEEDED, 22 | (int)HttpCode.TOO_FREQUENT_ACCESS, 23 | (int)HttpCode.CALLBACK_FAILED, 24 | (int)HttpCode.CONTENT_MODIFIED, 25 | (int)HttpCode.FILE_NOT_EXIST, 26 | (int)HttpCode.FILE_EXISTS, 27 | (int)HttpCode.INVALID_SHARE_BUCKET, 28 | (int)HttpCode.BUCKET_IS_SHARING, 29 | (int)HttpCode.BUCKET_COUNT_LIMIT, 30 | (int)HttpCode.BUCKET_NOT_EXIST, 31 | (int)HttpCode.EXCEED_SHARED_BUCKETS_LIMIT, 32 | (int)HttpCode.INVALID_MARKER, 33 | (int)HttpCode.CONTEXT_EXPIRED 34 | }; 35 | public static IEnumerable TestCases 36 | { 37 | get 38 | { 39 | for (int i = -5; i < 800; i++) 40 | { 41 | if (i > 0 && i < 500) 42 | { 43 | yield return new TestCaseData(i).Returns(false); 44 | continue; 45 | } 46 | 47 | if (NotRetryableHttpCodes.Contains(i)) 48 | { 49 | yield return new TestCaseData(i).Returns(false); 50 | continue; 51 | } 52 | 53 | yield return new TestCaseData(i).Returns(true); 54 | } 55 | } 56 | } 57 | } 58 | 59 | [TestFixture] 60 | public class HttpResultTests 61 | { 62 | [TestCaseSource(typeof(NeedRetryDataClass), nameof(NeedRetryDataClass.TestCases))] 63 | public bool NeedRetryTest(int code) 64 | { 65 | HttpResult httpResult = new HttpResult 66 | { 67 | Code = code 68 | }; 69 | return httpResult.NeedRetry(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/ResumeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Qiniu.Util; 4 | using Newtonsoft.Json; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 断点续上传辅助函数Load/Save 10 | /// 11 | public class ResumeHelper 12 | { 13 | 14 | /// 15 | /// 生成默认的断点记录文件名称 16 | /// 17 | /// 待上传的本地文件 18 | /// 要保存的目标key 19 | /// 用于记录断点信息的文件名 20 | public static string GetDefaultRecordKey(string localFile, string key) 21 | { 22 | string tempDir = System.IO.Path.GetTempPath(); 23 | System.IO.FileInfo fileInfo = new System.IO.FileInfo(localFile); 24 | string uniqueKey = string.Format("{0}:{1}:{2}", localFile, key, fileInfo.LastWriteTime.ToFileTime()); 25 | return Path.Combine(tempDir, "QiniuResume_" + Hashing.CalcMD5X(uniqueKey)); 26 | } 27 | 28 | /// 29 | /// 尝试从从文件载入断点信息 30 | /// 31 | /// 断点记录文件 32 | /// 断点信息 33 | public static ResumeInfo Load(string recordFile) 34 | { 35 | ResumeInfo resumeInfo = null; 36 | 37 | try 38 | { 39 | using (FileStream fs = new FileStream(recordFile, FileMode.Open)) 40 | { 41 | using (StreamReader sr = new StreamReader(fs)) 42 | { 43 | string jsonStr = sr.ReadToEnd(); 44 | resumeInfo=JsonConvert.DeserializeObject(jsonStr); 45 | } 46 | } 47 | } 48 | catch (Exception) 49 | { 50 | resumeInfo = null; 51 | } 52 | 53 | return resumeInfo; 54 | } 55 | 56 | /// 57 | /// 保存断点信息到文件 58 | /// 59 | /// 断点信息 60 | /// 断点记录文件 61 | public static void Save(ResumeInfo resumeInfo, string recordFile) 62 | { 63 | string jsonStr = resumeInfo.ToJsonStr(); 64 | 65 | using (FileStream fs = new FileStream(recordFile, FileMode.Create)) 66 | { 67 | using (StreamWriter sw = new StreamWriter(fs)) 68 | { 69 | sw.Write(jsonStr); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ChunkUnit.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Storage 2 | { 3 | /// 4 | /// 分片大小 5 | /// 6 | public enum ChunkUnit 7 | { 8 | /// 9 | /// 128KB 10 | /// 11 | U128K = 1, 12 | 13 | /// 14 | /// 256KB 15 | /// 16 | U256K = 2, 17 | 18 | /// 19 | /// 512KB 20 | /// 21 | U512K = 4, 22 | 23 | /// 24 | /// 1MB 25 | /// 26 | U1024K = 8, 27 | 28 | /// 29 | /// 2MB 30 | /// 31 | U2048K = 16, 32 | 33 | /// 34 | /// 4MB 35 | /// 36 | U4096K = 32 37 | }; 38 | 39 | /// 40 | /// ChunkSize转换 41 | /// 42 | public class ResumeChunk 43 | { 44 | private static int N = 128 * 1024; 45 | 46 | /// 47 | /// 计算ChunkSize 48 | /// 49 | /// 50 | /// 51 | public static int GetChunkSize(ChunkUnit cu) 52 | { 53 | int c = (int)cu; 54 | return c * N; 55 | } 56 | 57 | /// 58 | /// 计算ChunkUnit 59 | /// 60 | /// 61 | /// 62 | public static ChunkUnit GetChunkUnit(int chunkSize) 63 | { 64 | if (chunkSize < 128 * 1024 || chunkSize > 4 * 1024 * 1024) 65 | { 66 | return ChunkUnit.U2048K; 67 | } 68 | else 69 | { 70 | int u = chunkSize / N; 71 | int cu; 72 | 73 | if (u == 1) 74 | { 75 | cu = 1; 76 | } 77 | else if (u < 4) 78 | { 79 | cu = 2; 80 | } 81 | else if (u < 8) 82 | { 83 | cu = 4; 84 | } 85 | else if (u < 16) 86 | { 87 | cu = 8; 88 | } 89 | else if (u < 32) 90 | { 91 | cu = 16; 92 | } 93 | else 94 | { 95 | cu = 32; 96 | } 97 | 98 | return (ChunkUnit)cu; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/LogListRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | namespace Qiniu.CDN 5 | { 6 | /// 7 | /// 查询日志-请求 8 | /// 9 | public class LogListRequest 10 | { 11 | /// 12 | /// 日期,例如 2016-09-01 13 | /// 14 | [JsonProperty("day")] 15 | public string Day { get; set; } 16 | 17 | /// 18 | /// 域名列表,以西文半角分号分割 19 | /// 20 | [JsonProperty("domains")] 21 | public string Domains { get; set; } 22 | 23 | /// 24 | /// 初始化(所有成员为空,需要后续赋值) 25 | /// 26 | public LogListRequest() 27 | { 28 | Day = ""; 29 | Domains = ""; 30 | } 31 | 32 | /// 33 | /// 初始化所有成员 34 | /// 35 | /// 日期 36 | /// 域名列表(多个域名以;分隔的字符串) 37 | public LogListRequest(string day, string domains) 38 | { 39 | Day = day; 40 | Domains = domains; 41 | } 42 | 43 | /// 44 | /// 初始化所有成员 45 | /// 46 | /// 日期 47 | /// 域名列表 48 | public LogListRequest(string day, IList domains) 49 | { 50 | if (string.IsNullOrEmpty(day)) 51 | { 52 | Day = ""; 53 | } 54 | else 55 | { 56 | Day = day; 57 | } 58 | 59 | if (domains == null) 60 | { 61 | Domains = ""; 62 | } 63 | else 64 | { 65 | List uniqueDomains = new List(); 66 | foreach (string d in domains) 67 | { 68 | if (!uniqueDomains.Contains(d)) 69 | { 70 | uniqueDomains.Add(d); 71 | } 72 | } 73 | 74 | if (uniqueDomains.Count > 0) 75 | { 76 | Domains = string.Join(";", uniqueDomains); 77 | } 78 | else 79 | { 80 | Domains = ""; 81 | } 82 | } 83 | } 84 | 85 | /// 86 | /// 转换到JSON字符串 87 | /// 88 | /// 请求内容的JSON字符串 89 | public string ToJsonStr() 90 | { 91 | return JsonConvert.SerializeObject(this); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Qiniu/Util/Hashing.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | #if WINDOWS_UWP 3 | using Windows.Security.Cryptography; 4 | using Windows.Security.Cryptography.Core; 5 | #else 6 | using System.Security.Cryptography; 7 | #endif 8 | 9 | namespace Qiniu.Util 10 | { 11 | /// 12 | /// 计算hash值 13 | /// 特别注意,不同平台使用的Cryptography可能略有不同,使用中如有遇到问题,请反馈 14 | /// 提交您的issue到 https://github.com/qiniu/csharp-sdk 15 | /// 16 | public class Hashing 17 | { 18 | /// 19 | /// 计算SHA1 20 | /// 21 | /// 字节数据 22 | /// SHA1 23 | public static byte[] CalcSHA1(byte[] data) 24 | { 25 | #if WINDOWS_UWP 26 | var sha = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha1); 27 | var buf = CryptographicBuffer.CreateFromByteArray(data); 28 | var digest = sha.HashData(buf); 29 | var hashBytes = new byte[digest.Length]; 30 | CryptographicBuffer.CopyToByteArray(digest, out hashBytes); 31 | return hashBytes; 32 | #else 33 | SHA1 sha1 = SHA1.Create(); 34 | return sha1.ComputeHash(data); 35 | #endif 36 | } 37 | 38 | /// 39 | /// 计算MD5哈希(可能需要关闭FIPS) 40 | /// 41 | /// 待计算的字符串 42 | /// MD5结果 43 | public static string CalcMD5(string str) 44 | { 45 | #if WINDOWS_UWP 46 | var md5 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5); 47 | var buf = CryptographicBuffer.ConvertStringToBinary(str, BinaryStringEncoding.Utf8); 48 | var digest = md5.HashData(buf); 49 | return CryptographicBuffer.EncodeToHexString(digest); 50 | #else 51 | MD5 md5 = MD5.Create(); 52 | byte[] data = Encoding.UTF8.GetBytes(str); 53 | byte[] hashData = md5.ComputeHash(data); 54 | StringBuilder sb = new StringBuilder(hashData.Length * 2); 55 | foreach (byte b in hashData) 56 | { 57 | sb.AppendFormat("{0:x2}", b); 58 | } 59 | return sb.ToString(); 60 | #endif 61 | } 62 | 63 | /// 64 | /// 计算MD5哈希(第三方实现) 65 | /// 66 | /// 待计算的字符串,避免FIPS-Exception 67 | /// MD5结果 68 | public static string CalcMD5X(string str) 69 | { 70 | byte[] data = Encoding.UTF8.GetBytes(str); 71 | LabMD5 md5 = new LabMD5(); 72 | return md5.ComputeHash(data); 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/QiniuTests/Http/HttpManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.Reflection; 4 | using NUnit.Framework; 5 | using Qiniu.Http; 6 | using Qiniu.Util; 7 | 8 | namespace QiniuTests.Http 9 | { 10 | [TestFixture] 11 | public class HttpManagerTests 12 | { 13 | private static HttpManager httpManager = new HttpManager(); 14 | private static MethodInfo dynMethod = httpManager.GetType().GetMethod("addAuthHeaders", 15 | BindingFlags.NonPublic | BindingFlags.Instance); 16 | private static Mac mac = new Mac("ak", "sk"); 17 | 18 | [TearDown] 19 | public void EachTeardown() 20 | { 21 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", null); 22 | } 23 | 24 | [Test] 25 | public void DisableQiniuTimestampSignatureDefaultTest() 26 | { 27 | StringDictionary headers = new StringDictionary(); 28 | Auth auth = new Auth(mac); 29 | dynMethod.Invoke(httpManager, new object[] { headers, auth }); 30 | 31 | Assert.True(headers.ContainsKey("X-Qiniu-Date")); 32 | } 33 | 34 | [Test] 35 | public void DisableQiniuTimestampSignatureTest() 36 | { 37 | StringDictionary headers = new StringDictionary(); 38 | Auth auth = new Auth(mac, new AuthOptions 39 | { 40 | DisableQiniuTimestampSignature = true 41 | }); 42 | dynMethod.Invoke(httpManager, new object[] { headers, auth }); 43 | 44 | Assert.False(headers.ContainsKey("X-Qiniu-Date")); 45 | } 46 | 47 | [Test] 48 | public void DisableQiniuTimestampSignatureEnvTest() 49 | { 50 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", "true"); 51 | 52 | StringDictionary headers = new StringDictionary(); 53 | Auth auth = new Auth(mac); 54 | dynMethod.Invoke(httpManager, new object[] { headers, auth }); 55 | 56 | Assert.False(headers.ContainsKey("X-Qiniu-Date")); 57 | } 58 | 59 | [Test] 60 | public void DisableQiniuTimestampSignatureEnvBeIgnoredTest() 61 | { 62 | Environment.SetEnvironmentVariable("DISABLE_QINIU_TIMESTAMP_SIGNATURE", "true"); 63 | 64 | StringDictionary headers = new StringDictionary(); 65 | Auth auth = new Auth(mac, new AuthOptions 66 | { 67 | DisableQiniuTimestampSignature = false 68 | }); 69 | dynMethod.Invoke(httpManager, new object[] { headers, auth }); 70 | 71 | Assert.True(headers.ContainsKey("X-Qiniu-Date")); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/Qiniu/Util/UnixTimestamp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Qiniu.Util 4 | { 5 | /// 6 | /// 时间戳与日期时间转换 7 | /// 8 | public class UnixTimestamp 9 | { 10 | /// 11 | /// 基准时间 12 | /// 13 | private static DateTime dtBase = new DateTime(1970, 1, 1).ToLocalTime(); 14 | 15 | /// 16 | /// 时间戳末尾7位(补0或截断) 17 | /// 18 | private const long TICK_BASE = 10000000; 19 | 20 | /// 21 | /// 从现在(调用此函数时刻)起若干秒以后那个时间点的时间戳 22 | /// 23 | /// 从现在起多少秒以后 24 | /// Unix时间戳 25 | public static long GetUnixTimestamp(long secondsAfterNow) 26 | { 27 | DateTime dt = DateTime.Now.AddSeconds(secondsAfterNow).ToLocalTime(); 28 | TimeSpan tsx = dt.Subtract(dtBase); 29 | return tsx.Ticks / TICK_BASE; 30 | } 31 | 32 | /// 33 | /// 日期时间转换为时间戳 34 | /// 35 | /// 日期时间 36 | /// 时间戳 37 | public static long ConvertToTimestamp(DateTime dt) 38 | { 39 | TimeSpan tsx = dt.Subtract(dtBase); 40 | return tsx.Ticks / TICK_BASE; 41 | } 42 | 43 | /// 44 | /// 从UNIX时间戳转换为DateTime 45 | /// 46 | /// 时间戳字符串 47 | /// 日期时间 48 | public static DateTime ConvertToDateTime(string timestamp) 49 | { 50 | long ticks = long.Parse(timestamp) * TICK_BASE; 51 | return dtBase.AddTicks(ticks); 52 | } 53 | 54 | /// 55 | /// 从UNIX时间戳转换为DateTime 56 | /// 57 | /// 时间戳 58 | /// 日期时间 59 | public static DateTime ConvertToDateTime(long timestamp) 60 | { 61 | long ticks = timestamp * TICK_BASE; 62 | return dtBase.AddTicks(ticks); 63 | } 64 | 65 | /// 66 | /// 检查Ctx是否过期,我们给当前时间加上一天来看看是否超过了过期时间 67 | /// 而不是直接比较是否超过了过期时间,是给这个文件最大1天的上传持续时间 68 | /// 69 | /// 70 | /// 71 | public static bool IsContextExpired(long expiredAt) 72 | { 73 | if (expiredAt == 0) 74 | { 75 | return false; 76 | } 77 | bool expired = false; 78 | DateTime now = DateTime.Now.AddDays(1); 79 | long nowTs = ConvertToTimestamp(now); 80 | if (nowTs > expiredAt) 81 | { 82 | expired = true; 83 | } 84 | return expired; 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Qiniu/Util/QETag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Qiniu.Util 5 | { 6 | /// 7 | /// QINIU ETAG(文件hash) 8 | /// 9 | public class QETag 10 | { 11 | // 块大小(固定为4MB) 12 | private const int BLOCK_SIZE = 4 * 1024 * 1024; 13 | 14 | // 计算时以20B为单位 15 | private static int BLOCK_SHA1_SIZE = 20; 16 | 17 | /// 18 | /// 计算文件hash(ETAG) 19 | /// 20 | /// 21 | /// 文件hash 22 | public static string calcHash(string filePath) 23 | { 24 | string qetag = ""; 25 | 26 | try 27 | { 28 | using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 29 | { 30 | long fileLength = stream.Length; 31 | byte[] buffer = new byte[BLOCK_SIZE]; 32 | byte[] finalBuffer = new byte[BLOCK_SHA1_SIZE + 1]; 33 | if (fileLength <= BLOCK_SIZE) 34 | { 35 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE); 36 | byte[] readBuffer = new byte[readByteCount]; 37 | Array.Copy(buffer, readBuffer, readByteCount); 38 | 39 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer); 40 | 41 | finalBuffer[0] = 0x16; 42 | Array.Copy(sha1Buffer, 0, finalBuffer, 1, sha1Buffer.Length); 43 | } 44 | else 45 | { 46 | long blockCount = (fileLength % BLOCK_SIZE == 0) ? (fileLength / BLOCK_SIZE) : (fileLength / BLOCK_SIZE + 1); 47 | byte[] sha1AllBuffer = new byte[BLOCK_SHA1_SIZE * blockCount]; 48 | 49 | for (int i = 0; i < blockCount; i++) 50 | { 51 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE); 52 | byte[] readBuffer = new byte[readByteCount]; 53 | Array.Copy(buffer, readBuffer, readByteCount); 54 | 55 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer); 56 | Array.Copy(sha1Buffer, 0, sha1AllBuffer, i * BLOCK_SHA1_SIZE, sha1Buffer.Length); 57 | } 58 | 59 | byte[] sha1AllBufferSha1 = Hashing.CalcSHA1(sha1AllBuffer); 60 | 61 | finalBuffer[0] = 0x96; 62 | Array.Copy(sha1AllBufferSha1, 0, finalBuffer, 1, sha1AllBufferSha1.Length); 63 | 64 | } 65 | qetag = Base64.UrlSafeBase64Encode(finalBuffer); 66 | } 67 | } 68 | catch (Exception) { } 69 | 70 | return qetag; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Qiniu/Util/ETag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Qiniu.Util 5 | { 6 | /// 7 | /// QINIU ETAG(文件hash) 8 | /// 9 | public class ETag 10 | { 11 | // 块大小(固定为4MB) 12 | private const int BLOCK_SIZE = 4 * 1024 * 1024; 13 | 14 | // 计算时以20B为单位 15 | private static int BLOCK_SHA1_SIZE = 20; 16 | 17 | /// 18 | /// 计算文件hash(ETAG) 19 | /// 20 | /// 21 | /// 文件hash 22 | public static string CalcHash(string filePath) 23 | { 24 | string qetag = ""; 25 | 26 | try 27 | { 28 | using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 29 | { 30 | long fileLength = stream.Length; 31 | byte[] buffer = new byte[BLOCK_SIZE]; 32 | byte[] finalBuffer = new byte[BLOCK_SHA1_SIZE + 1]; 33 | if (fileLength <= BLOCK_SIZE) 34 | { 35 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE); 36 | byte[] readBuffer = new byte[readByteCount]; 37 | Array.Copy(buffer, readBuffer, readByteCount); 38 | 39 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer); 40 | 41 | finalBuffer[0] = 0x16; 42 | Array.Copy(sha1Buffer, 0, finalBuffer, 1, sha1Buffer.Length); 43 | } 44 | else 45 | { 46 | long blockCount = (fileLength % BLOCK_SIZE == 0) ? (fileLength / BLOCK_SIZE) : (fileLength / BLOCK_SIZE + 1); 47 | byte[] sha1AllBuffer = new byte[BLOCK_SHA1_SIZE * blockCount]; 48 | 49 | for (int i = 0; i < blockCount; i++) 50 | { 51 | int readByteCount = stream.Read(buffer, 0, BLOCK_SIZE); 52 | byte[] readBuffer = new byte[readByteCount]; 53 | Array.Copy(buffer, readBuffer, readByteCount); 54 | 55 | byte[] sha1Buffer = Hashing.CalcSHA1(readBuffer); 56 | Array.Copy(sha1Buffer, 0, sha1AllBuffer, i * BLOCK_SHA1_SIZE, sha1Buffer.Length); 57 | } 58 | 59 | byte[] sha1AllBufferSha1 = Hashing.CalcSHA1(sha1AllBuffer); 60 | 61 | finalBuffer[0] = 0x96; 62 | Array.Copy(sha1AllBufferSha1, 0, finalBuffer, 1, sha1AllBufferSha1.Length); 63 | 64 | } 65 | qetag = Base64.UrlSafeBase64Encode(finalBuffer); 66 | } 67 | } 68 | catch (Exception) { } 69 | 70 | return qetag; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Qiniu/Http/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Qiniu.Http 4 | { 5 | /// 6 | /// URL辅助工具(RegExp) 7 | /// 8 | public class UrlHelper 9 | { 10 | private static Regex regx = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?"); 11 | 12 | private static Regex regu = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?"); 13 | 14 | private static Regex regd = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?/"); 15 | 16 | /// 17 | /// 是否合法URL 18 | /// 19 | /// 待判断的url 20 | /// 21 | public static bool isValidUrl(string _url) 22 | { 23 | return regx.IsMatch(_url); 24 | } 25 | 26 | /// 27 | /// 是否一般URL(不包含?等后缀参数) 28 | /// 29 | /// 待判断的url 30 | /// 31 | public static bool isNormalUrl(string _url) 32 | { 33 | return regu.IsMatch(_url); 34 | } 35 | 36 | /// 37 | /// 是否合法URL目录 38 | /// 39 | /// 待判断的url目录 40 | /// 41 | public static bool isValidDir(string _dir) 42 | { 43 | return regd.IsMatch(_dir); 44 | } 45 | 46 | /// 47 | /// 从原始URL转换为一般URL(根据需要截断) 48 | /// 49 | /// 待转换的url 50 | /// 51 | public static string getNormalUrl(string _url) 52 | { 53 | var m = regu.Match(_url); 54 | return m.Value; 55 | } 56 | 57 | /// 58 | /// URL分析,拆分出Host,Path,File,Query各个部分 59 | /// 60 | /// 原始URL 61 | /// host部分 62 | /// path部分 63 | /// 文件名 64 | /// 参数 65 | public static void urlSplit(string url, out string host, out string path, out string file, out string query) 66 | { 67 | int start = 0; 68 | 69 | Regex regHost = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+"); 70 | host = regHost.Match(url, start).Value; 71 | start += host.Length; 72 | 73 | Regex regPath = new Regex(@"(/(\w|\-)*)+/"); 74 | path = regPath.Match(url, start).Value; 75 | if (string.IsNullOrEmpty(path)) 76 | { 77 | path = "/"; 78 | } 79 | start += path.Length; 80 | 81 | int index = url.IndexOf('?', start); 82 | file = url.Substring(start, index - start); 83 | 84 | query = url.Substring(index); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/PrefetchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 文件预取-结果 9 | /// 10 | public class PrefetchResult : HttpResult 11 | { 12 | /// 13 | /// 获取文件预取信息 14 | /// 15 | public PrefetchInfo Result 16 | { 17 | get 18 | { 19 | PrefetchInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info=JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code:{0}\n", Code); 37 | sb.AppendLine(); 38 | 39 | if (Result != null) 40 | { 41 | sb.AppendLine("result:"); 42 | sb.AppendFormat("code:{0}\n", Result.Code); 43 | if (!string.IsNullOrEmpty(Result.Error)) 44 | { 45 | sb.AppendFormat("error:{0}\n", Result.Error); 46 | } 47 | if (!string.IsNullOrEmpty(Result.RequestId)) 48 | { 49 | sb.AppendFormat("requestId:{0}\n", Result.RequestId); 50 | } 51 | if (Result.InvalidUrls != null && Result.InvalidUrls.Count > 0) 52 | { 53 | sb.Append("invalidUrls:"); 54 | foreach (var s in Result.InvalidUrls) 55 | { 56 | sb.Append(s + " "); 57 | } 58 | } 59 | sb.AppendLine(); 60 | sb.AppendFormat("quotaDay:{0}\n", Result.QuotaDay); 61 | sb.AppendFormat("surplusaDay:{0}\n", Result.SurplusDay); 62 | } 63 | else 64 | { 65 | if (!string.IsNullOrEmpty(Text)) 66 | { 67 | sb.AppendLine("text:"); 68 | sb.AppendLine(Text); 69 | } 70 | } 71 | sb.AppendLine(); 72 | 73 | sb.AppendFormat("ref-code:{0}\n", RefCode); 74 | 75 | if (!string.IsNullOrEmpty(RefText)) 76 | { 77 | sb.AppendLine("ref-text:"); 78 | sb.AppendLine(RefText); 79 | } 80 | 81 | if (RefInfo != null) 82 | { 83 | sb.AppendFormat("ref-info:\n"); 84 | foreach (var d in RefInfo) 85 | { 86 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 87 | } 88 | } 89 | 90 | return sb.ToString(); 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/Qiniu.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qiniu", "Qiniu\Qiniu.csproj", "{2F5B0328-DE8B-4B53-A500-3077E340A51B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiniuTests", "QiniuTests\QiniuTests.csproj", "{E8CB1665-53F7-46A5-9AFD-B85AD08262D0}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x64.Build.0 = Debug|Any CPU 24 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.ActiveCfg = Debug|Any CPU 25 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Debug|x86.Build.0 = Debug|Any CPU 26 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.ActiveCfg = Release|Any CPU 29 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x64.Build.0 = Release|Any CPU 30 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.ActiveCfg = Release|Any CPU 31 | {2F5B0328-DE8B-4B53-A500-3077E340A51B}.Release|x86.Build.0 = Release|Any CPU 32 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x64.Build.0 = Debug|Any CPU 36 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Debug|x86.Build.0 = Debug|Any CPU 38 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.ActiveCfg = Release|Any CPU 41 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x64.Build.0 = Release|Any CPU 42 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.ActiveCfg = Release|Any CPU 43 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {0358A72B-AB04-47A5-8AC2-BCDE2447D2A4} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/FluxResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 查询流量-结果 9 | /// 10 | public class FluxResult : HttpResult 11 | { 12 | /// 13 | /// 获取流量信息 14 | /// 15 | public FluxInfo Result 16 | { 17 | get 18 | { 19 | FluxInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info=JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code:{0}\n", Code); 37 | sb.AppendLine(); 38 | 39 | if (Result != null) 40 | { 41 | sb.AppendLine("result:"); 42 | sb.AppendFormat("code:{0}\n", Result.Code); 43 | if (!string.IsNullOrEmpty(Result.Error)) 44 | { 45 | sb.AppendFormat("error:{0}\n", Result.Error); 46 | } 47 | if (Result.Time != null) 48 | { 49 | sb.Append("time:"); 50 | foreach (var t in Result.Time) 51 | { 52 | sb.Append(t + " "); 53 | } 54 | sb.AppendLine(); 55 | } 56 | 57 | if (Result.Data != null && Result.Data.Count > 0) 58 | { 59 | sb.Append("flux:"); 60 | foreach (var kvp in Result.Data) 61 | { 62 | sb.AppendFormat("{0}:\nChina: {1}, Oversea={2}\n", kvp.Key, kvp.Value.China, kvp.Value.Oversea); 63 | } 64 | sb.AppendLine(); 65 | } 66 | } 67 | else 68 | { 69 | if (!string.IsNullOrEmpty(Text)) 70 | { 71 | sb.AppendLine("text:"); 72 | sb.AppendLine(Text); 73 | } 74 | } 75 | sb.AppendLine(); 76 | 77 | sb.AppendFormat("ref-code:{0}\n", RefCode); 78 | 79 | if (!string.IsNullOrEmpty(RefText)) 80 | { 81 | sb.AppendLine("ref-text:"); 82 | sb.AppendLine(RefText); 83 | } 84 | 85 | if (RefInfo != null) 86 | { 87 | sb.AppendFormat("ref-info:\n"); 88 | foreach (var d in RefInfo) 89 | { 90 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 91 | } 92 | } 93 | 94 | return sb.ToString(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/BandwidthResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 查询带宽-结果 9 | /// 10 | public class BandwidthResult : HttpResult 11 | { 12 | /// 13 | /// 获取带宽信息 14 | /// 15 | public BandwidthInfo Result 16 | { 17 | get 18 | { 19 | BandwidthInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info = JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code:{0}\n", Code); 37 | 38 | sb.AppendLine(); 39 | 40 | if (Result != null) 41 | { 42 | sb.AppendLine("result:"); 43 | sb.AppendFormat("code:{0}\n", Result.Code); 44 | if (!string.IsNullOrEmpty(Result.Error)) 45 | { 46 | sb.AppendFormat("error:{0}\n", Result.Error); 47 | } 48 | if (Result.Time != null) 49 | { 50 | sb.Append("time:"); 51 | foreach (var t in Result.Time) 52 | { 53 | sb.Append(t + " "); 54 | } 55 | sb.AppendLine(); 56 | } 57 | 58 | if (Result.Data != null && Result.Data.Count > 0) 59 | { 60 | sb.Append("bandwidth:"); 61 | foreach (var kvp in Result.Data) 62 | { 63 | sb.AppendFormat("{0}:\nChina: {1}, Oversea={2}\n", kvp.Key, kvp.Value.China, kvp.Value.Oversea); 64 | } 65 | sb.AppendLine(); 66 | } 67 | } 68 | else 69 | { 70 | if (!string.IsNullOrEmpty(Text)) 71 | { 72 | sb.AppendLine("text:"); 73 | sb.AppendLine(Text); 74 | } 75 | } 76 | sb.AppendLine(); 77 | 78 | sb.AppendFormat("ref-code:{0}\n", RefCode); 79 | 80 | if (!string.IsNullOrEmpty(RefText)) 81 | { 82 | sb.AppendLine("ref-text:"); 83 | sb.AppendLine(RefText); 84 | } 85 | 86 | if (RefInfo != null) 87 | { 88 | sb.AppendFormat("ref-info:\n"); 89 | foreach (var d in RefInfo) 90 | { 91 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 92 | } 93 | } 94 | 95 | return sb.ToString(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ListResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.Storage 6 | { 7 | /// 8 | /// 获取空间文件列表(list操作)的返回消息 9 | /// 10 | public class ListResult:HttpResult 11 | { 12 | /// 13 | /// 文件列表信息 14 | /// 15 | public ListInfo Result 16 | { 17 | get 18 | { 19 | ListInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info= JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串> 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code: {0}\n", Code); 37 | 38 | if (Result != null) 39 | { 40 | if (Result.CommonPrefixes != null) 41 | { 42 | sb.Append("commonPrefixes:"); 43 | foreach(var p in Result.CommonPrefixes) 44 | { 45 | sb.AppendFormat("{0} ", p); 46 | } 47 | sb.AppendLine(); 48 | } 49 | 50 | if (!string.IsNullOrEmpty(Result.Marker)) 51 | { 52 | sb.AppendFormat("marker: {0}\n", Result.Marker); 53 | } 54 | 55 | if (Result.Items != null) 56 | { 57 | sb.AppendLine("items:"); 58 | int i = 0, n = Result.Items.Count; 59 | foreach (var item in Result.Items) 60 | { 61 | sb.AppendFormat("#{0}/{1}:Key={2}, Size={3}, Mime={4}, Hash={5}, Time={6}, Type={7}\n", 62 | ++i, n, item.Key, item.Fsize, item.MimeType, item.Hash, item.PutTime, item.FileType); 63 | } 64 | } 65 | } 66 | else 67 | { 68 | if (!string.IsNullOrEmpty(Text)) 69 | { 70 | sb.AppendLine("text:"); 71 | sb.AppendLine(Text); 72 | } 73 | } 74 | sb.AppendLine(); 75 | 76 | sb.AppendFormat("ref-code: {0}\n", RefCode); 77 | 78 | if (!string.IsNullOrEmpty(RefText)) 79 | { 80 | sb.AppendLine("ref-text:"); 81 | sb.AppendLine(RefText); 82 | } 83 | 84 | if (RefInfo != null) 85 | { 86 | sb.AppendFormat("ref-info:\n"); 87 | foreach (var d in RefInfo) 88 | { 89 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 90 | } 91 | } 92 | 93 | return sb.ToString(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Qiniu/Http/HttpHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Qiniu.Util; 3 | 4 | namespace Qiniu.Http 5 | { 6 | /// 7 | /// HTTP辅助工具:帮助生成UA,boundary等 8 | /// 9 | public class HttpHelper 10 | { 11 | /// 12 | /// 资源类型:普通文本 13 | /// 14 | public static string CONTENT_TYPE_TEXT_PLAIN = "text/plain"; 15 | 16 | /// 17 | /// 资源类型:JSON字符串 18 | /// 19 | public static string CONTENT_TYPE_APP_JSON = "application/json"; 20 | 21 | /// 22 | /// 资源类型:未知类型(数据流) 23 | /// 24 | public static string CONTENT_TYPE_APP_OCTET = "application/octet-stream"; 25 | 26 | /// 27 | /// 资源类型:表单数据(键值对) 28 | /// 29 | public static string CONTENT_TYPE_WWW_FORM = "application/x-www-form-urlencoded"; 30 | 31 | /// 32 | /// 资源类型:多分部数据 33 | /// 34 | public static string CONTENT_TYPE_MULTIPART = "multipart/form-data"; 35 | 36 | /// 37 | /// HTTP状态码200 (OK) 38 | /// 39 | public static int STATUS_CODE_OK = 200; 40 | 41 | /// 42 | /// HTTP状态码298 (部分OK) 43 | /// 44 | public static int STATUS_CODE_PARTLY_OK = 298; 45 | 46 | /// 47 | /// 自定义HTTP状态码 (默认值) 48 | /// 49 | public static int STATUS_CODE_UNDEF = -256; 50 | 51 | /// 52 | /// 自定义HTTP状态码 (用户取消) 53 | /// 54 | public static int STATUS_CODE_USER_CANCELED = -255; 55 | 56 | /// 57 | /// 自定义HTTP状态码 (用户暂停) 58 | /// 59 | public static int STATUS_CODE_USER_PAUSED = -254; 60 | 61 | /// 62 | /// 自定义HTTP状态码 (用户继续) 63 | /// 64 | public static int STATUS_CODE_USER_RESUMED = -253; 65 | 66 | /// 67 | /// 自定义HTTP状态码 (需要重试) 68 | /// 69 | public static int STATUS_CODE_NEED_RETRY= -252; 70 | 71 | /// 72 | /// 自定义HTTP状态码 (异常或错误) 73 | /// 74 | public static int STATUS_CODE_EXCEPTION = -252; 75 | 76 | /// 77 | /// 客户端标识 78 | /// 79 | /// 客户端标识UA 80 | public static string getUserAgent() 81 | { 82 | #if NetStandard 83 | string sfx = Environment.MachineName; 84 | #else 85 | var osInfo = Environment.OSVersion; 86 | string sfx = Environment.MachineName + "; " + osInfo.Platform + "; " + osInfo.Version; 87 | #endif 88 | return string.Format("{0}/{1} ({2})", QiniuCSharpSDK.ALIAS, QiniuCSharpSDK.VERSION, sfx); 89 | } 90 | 91 | /// 92 | /// 多部分表单数据(multi-part form-data)的分界(boundary)标识 93 | /// 94 | /// 多部分表单数据的boundary 95 | public static string createFormDataBoundary() 96 | { 97 | string now = DateTime.UtcNow.Ticks.ToString(); 98 | return string.Format("-------{0}Boundary{1}", QiniuCSharpSDK.ALIAS, Hashing.CalcMD5(now)); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/RefreshRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | namespace Qiniu.CDN 5 | { 6 | /// 7 | /// 缓存刷新-请求 8 | /// 9 | public class RefreshRequest 10 | { 11 | /// 12 | /// 要预取的单个url列表,总数不超过100条 13 | /// 单个url,即一个具体的url,例如:http://bar.foo.com/test.zip 14 | /// 注意: 15 | /// 请输入资源 url 完整的绝对路径,由 http:// 或 https:// 开始 16 | /// 资源 url 不支持通配符,例如:不支持 http://www.test.com/abc/*.* 17 | /// 带参数的 url 刷新,根据其域名缓存配置是否忽略参数缓存决定刷新结果。 18 | /// 如果配置了时间戳防盗链的资源 url 提交时刷新需要去掉 e 和 token 参数 19 | /// 20 | [JsonProperty("urls", NullValueHandling = NullValueHandling.Ignore)] 21 | public List Urls { get; set; } 22 | 23 | /// 24 | /// 要刷新的目录url列表,总数不超过10条;目录dir,即表示一个目录级的url,需要以 / 结尾 25 | /// 例如:http://bar.foo.com/dir/, 26 | /// 也支持在尾部使用通配符,例如:http://bar.foo.com/dir/* 27 | /// 28 | [JsonProperty("dirs", NullValueHandling = NullValueHandling.Ignore)] 29 | public List Dirs { get; set; } 30 | 31 | /// 32 | /// 初始化(所有成员为空,需要后续赋值) 33 | /// 34 | public RefreshRequest() 35 | { 36 | this.Urls = new List(); 37 | this.Dirs = new List(); 38 | } 39 | 40 | /// 41 | /// 初始化URL列表 42 | /// 43 | /// URL列表 44 | /// URL目录列表 45 | public RefreshRequest(IList urls, IList dirs) 46 | { 47 | this.Urls = new List(); 48 | this.Dirs = new List(); 49 | 50 | if (urls != null) 51 | { 52 | AddUrls(urls); 53 | } 54 | 55 | if (dirs != null) 56 | { 57 | AddDirs(dirs); 58 | } 59 | } 60 | 61 | /// 62 | /// 添加URL列表 63 | /// 64 | /// URL列表 65 | public void AddUrls(IList urls) 66 | { 67 | if (urls != null) 68 | { 69 | foreach (var u in urls) 70 | { 71 | if (!this.Urls.Contains(u)) 72 | { 73 | this.Urls.Add(u); 74 | } 75 | } 76 | } 77 | } 78 | 79 | /// 80 | /// 添加URL目录列表 81 | /// 82 | /// URL目录列表 83 | public void AddDirs(IList dirs) 84 | { 85 | if (dirs != null) 86 | { 87 | foreach (var d in dirs) 88 | { 89 | if (!this.Dirs.Contains(d)) 90 | { 91 | this.Dirs.Add(d); 92 | } 93 | } 94 | } 95 | } 96 | 97 | /// 98 | /// 转换到JSON字符串 99 | /// 100 | /// 请求内容的JSON字符串 101 | public string ToJsonStr() 102 | { 103 | return JsonConvert.SerializeObject(this); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/BatchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Qiniu.Http; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 批量处理结果 10 | /// 11 | public class BatchResult : HttpResult 12 | { 13 | /// 14 | /// 错误消息 15 | /// 16 | public string Error 17 | { 18 | get 19 | { 20 | string ex = null; 21 | if (Code != (int)HttpCode.OK && Code != (int)HttpCode.PARTLY_OK) 22 | { 23 | 24 | Dictionary ret = JsonConvert.DeserializeObject>(Text); 25 | if (ret.ContainsKey("error")) 26 | { 27 | ex = ret["error"]; 28 | } 29 | } 30 | return ex; 31 | } 32 | } 33 | 34 | /// 35 | /// 获取批量处理结果 36 | /// 37 | public List Result 38 | { 39 | get 40 | { 41 | List info = null; 42 | if ((Code == (int)HttpCode.OK || Code == (int)HttpCode.PARTLY_OK) && 43 | (!string.IsNullOrEmpty(Text))) 44 | { 45 | info = JsonConvert.DeserializeObject>(Text); 46 | } 47 | return info; 48 | } 49 | } 50 | 51 | /// 52 | /// 转换为易读字符串格式 53 | /// 54 | /// 便于打印和阅读的字符串> 55 | public override string ToString() 56 | { 57 | StringBuilder sb = new StringBuilder(); 58 | 59 | sb.AppendFormat("code: {0}\n", Code); 60 | 61 | if (Result != null) 62 | { 63 | sb.AppendLine("result:"); 64 | int i = 0, n = Result.Count; 65 | foreach (var v in Result) 66 | { 67 | sb.AppendFormat("#{0}/{1}\n", ++i, n); 68 | sb.AppendFormat("code: {0}\n", v.Code); 69 | sb.AppendFormat("data:\n{0}\n\n", v.Data); 70 | } 71 | } 72 | else 73 | { 74 | if (!string.IsNullOrEmpty(Error)) 75 | { 76 | sb.AppendFormat("Error: {0}\n", Error); 77 | } 78 | else if (!string.IsNullOrEmpty(Text)) 79 | { 80 | sb.AppendLine("text:"); 81 | sb.AppendLine(Text); 82 | } 83 | } 84 | sb.AppendLine(); 85 | 86 | sb.AppendFormat("ref-code: {0}\n", RefCode); 87 | 88 | if (!string.IsNullOrEmpty(RefText)) 89 | { 90 | sb.AppendLine("ref-text:"); 91 | sb.AppendLine(RefText); 92 | } 93 | 94 | if (RefInfo != null) 95 | { 96 | sb.AppendFormat("ref-info:\n"); 97 | foreach (var d in RefInfo) 98 | { 99 | sb.AppendLine(string.Format("{0}: {1}", d.Key, d.Value)); 100 | } 101 | } 102 | 103 | return sb.ToString(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Qiniu/Util/UserEnv.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #if WINDOWS_UWP 4 | using System.Threading.Tasks; 5 | using Windows.Storage; 6 | #else 7 | using System.IO; 8 | #endif 9 | 10 | namespace Qiniu.Util 11 | { 12 | /// 13 | /// 环境变量-用户路径 14 | /// 15 | public class UserEnv 16 | { 17 | 18 | #if Net20 || Net35 || Net40 || Net45 || Net46 19 | 20 | /// 21 | /// 找到QHome目录(在用户目录下建立的"QHome"文件夹) 22 | /// 23 | /// QHOME路径 24 | public static string GetHomeFolder() 25 | { 26 | // Windows下Home目录 = %HOMEDRIVE% + %HOMEPATH% 27 | string homeDir = Environment.GetEnvironmentVariable("HOMEDRIVE") + Environment.GetEnvironmentVariable("HOMEPATH"); 28 | 29 | if (string.IsNullOrEmpty(homeDir)) 30 | { 31 | // 如果获取失败,就设置为当前路径 32 | homeDir = Path.GetFullPath("."); 33 | } 34 | 35 | string homeFolder = Path.Combine(homeDir, "QHome"); 36 | 37 | if(!Directory.Exists(homeFolder)) 38 | { 39 | Directory.CreateDirectory(homeFolder); 40 | } 41 | 42 | return homeFolder; 43 | } 44 | 45 | #elif NetCore 46 | 47 | /// 48 | /// 找到QHome目录(在用户目录下建立的"QHome"文件夹) 49 | /// 50 | /// QHOME路径 51 | public static string GetHomeFolder() 52 | { 53 | // Windows下Home目录 = %HOMEDRIVE% + %HOMEPATH% 54 | string homeDir = Environment.GetEnvironmentVariable("HOMEDRIVE") + Environment.GetEnvironmentVariable("HOMEPATH"); 55 | 56 | // OSX/Ubuntu下Home目录 = $HOME 57 | if (string.IsNullOrEmpty(homeDir)) 58 | { 59 | homeDir = Environment.GetEnvironmentVariable("HOME"); 60 | } 61 | 62 | if (string.IsNullOrEmpty(homeDir)) 63 | { 64 | // 如果获取失败,就设置为当前路径 65 | homeDir = Path.GetFullPath("."); 66 | } 67 | 68 | string homeFolder = Path.Combine(homeDir, "QHome"); 69 | 70 | if(!Directory.Exists(homeFolder)) 71 | { 72 | Directory.CreateDirectory(homeFolder); 73 | } 74 | 75 | return homeFolder; 76 | } 77 | 78 | #elif WINDOWS_UWP 79 | 80 | /// 81 | /// 找到QHome目录(在"应用缓存"目录下建立的"QHome"文件夹) 82 | /// 83 | /// QHOME路径 84 | public static async Task GetHomeFolderAsync() 85 | { 86 | return await ApplicationData.Current.LocalCacheFolder.CreateFolderAsync("QHome", CreationCollisionOption.OpenIfExists); 87 | //return await KnownFolders.DocumentsLibrary.CreateFolderAsync("QHome", CreationCollisionOption.OpenIfExists); 88 | } 89 | 90 | #else 91 | 92 | /// 93 | /// 找到QHome目录(在当前目录下建立的"QHome"文件夹) 94 | /// 95 | /// QHOME路径 96 | public static string GetHomeFolder() 97 | { 98 | //当前路径 99 | string homeFolder = Path.GetFullPath("./QHome"); 100 | 101 | if(!Directory.Exists(homeFolder)) 102 | { 103 | Directory.CreateDirectory(homeFolder); 104 | } 105 | return homeFolder; 106 | } 107 | 108 | #endif 109 | 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/LogListResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 查询日志-结果 9 | /// 10 | public class LogListResult : HttpResult 11 | { 12 | /// 13 | /// 获取日志列表信息 14 | /// 15 | public LogListInfo Result 16 | { 17 | get 18 | { 19 | LogListInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info=JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code:{0}\n", Code); 37 | sb.AppendLine(); 38 | 39 | if (Result != null) 40 | { 41 | sb.AppendLine("result:"); 42 | sb.AppendFormat("code:{0}\n", Result.Code); 43 | if (!string.IsNullOrEmpty(Result.Error)) 44 | { 45 | sb.AppendFormat("error:{0}\n", Result.Error); 46 | } 47 | if (Result.Data != null && Result.Data.Count > 0) 48 | { 49 | sb.AppendLine("log:"); 50 | foreach (var key in Result.Data.Keys) 51 | { 52 | sb.AppendFormat("{0}:\n", key); 53 | foreach (var d in Result.Data) 54 | { 55 | if (d.Value != null) 56 | { 57 | sb.AppendFormat("Domain:{0}\n", d.Key); 58 | foreach (var s in d.Value) 59 | { 60 | if (s != null) 61 | { 62 | sb.AppendFormat("Name:{0}\nSize:{1}\nMtime:{2}\nUrl:{3}\n\n", s.Name, s.Size, s.Mtime, s.Url); 63 | } 64 | } 65 | } 66 | } 67 | sb.AppendLine(); 68 | } 69 | } 70 | } 71 | else 72 | { 73 | if (!string.IsNullOrEmpty(Text)) 74 | { 75 | sb.AppendLine("text:"); 76 | sb.AppendLine(Text); 77 | } 78 | } 79 | sb.AppendLine(); 80 | 81 | sb.AppendFormat("ref-code:{0}\n", RefCode); 82 | 83 | if (!string.IsNullOrEmpty(RefText)) 84 | { 85 | sb.AppendLine("ref-text:"); 86 | sb.AppendLine(RefText); 87 | } 88 | 89 | if (RefInfo != null) 90 | { 91 | sb.AppendFormat("ref-info:\n"); 92 | foreach (var d in RefInfo) 93 | { 94 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 95 | } 96 | } 97 | 98 | return sb.ToString(); 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/UploadManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Qiniu.Util; 3 | using Qiniu.Http; 4 | using System.Collections.Generic; 5 | 6 | namespace Qiniu.Storage 7 | { 8 | /// 9 | /// 上传管理器,根据文件/数据(流)大小以及阈值设置自动选择合适的上传方式 10 | /// 11 | public class UploadManager 12 | { 13 | private Config config; 14 | 15 | /// 16 | /// 初始化 17 | /// 18 | /// 文件上传的配置信息 19 | public UploadManager(Config config) 20 | { 21 | this.config = config; 22 | } 23 | 24 | /// 25 | /// 上传数据 26 | /// 27 | /// 待上传的数据 28 | /// 要保存的文件名称 29 | /// 上传凭证 30 | /// 上传可选设置 31 | /// 上传文件后的返回结果 32 | public HttpResult UploadData(byte[] data, string key, string token, PutExtra extra) 33 | { 34 | FormUploader formUploader = new FormUploader(this.config); 35 | return formUploader.UploadData(data, key, token, extra); 36 | } 37 | 38 | /// 39 | /// 上传文件,根据文件大小以及设置的阈值(用户初始化UploadManager时可指定该值)自动选择: 40 | /// 若文件大小超过设定阈值,使用ResumableUploader,否则使用FormUploader 41 | /// 42 | /// 本地待上传的文件名 43 | /// 要保存的文件名称 44 | /// 上传凭证 45 | /// 上传可选设置 46 | /// 上传文件后的返回结果 47 | public HttpResult UploadFile(string localFile, string key, string token, PutExtra extra) 48 | { 49 | HttpResult result = new HttpResult(); 50 | 51 | System.IO.FileInfo fi = new System.IO.FileInfo(localFile); 52 | if (fi.Length > this.config.PutThreshold) 53 | { 54 | ResumableUploader resumeUploader = new ResumableUploader(config); 55 | result = resumeUploader.UploadFile(localFile, key, token, extra); 56 | } 57 | else 58 | { 59 | FormUploader formUploader = new FormUploader(config); 60 | result = formUploader.UploadFile(localFile, key, token, extra); 61 | } 62 | 63 | return result; 64 | } 65 | 66 | 67 | /// 68 | /// 上传文件数据流,根据文件大小以及设置的阈值(用户初始化UploadManager时可指定该值)自动选择: 69 | /// 若文件大小超过设定阈值,使用ResumableUploader,否则使用FormUploader 70 | /// 71 | /// 待上传的数据流 72 | /// 要保存的文件名称 73 | /// 上传凭证 74 | /// 上传可选设置 75 | /// 上传文件后的返回结果 76 | public HttpResult UploadStream(Stream stream, string key, string token, PutExtra extra) 77 | { 78 | HttpResult result = new HttpResult(); 79 | 80 | if (stream.Length > this.config.PutThreshold) 81 | { 82 | ResumableUploader resumeUploader = new ResumableUploader(this.config); 83 | result = resumeUploader.UploadStream(stream, key, token, extra); 84 | } 85 | else 86 | { 87 | FormUploader formUploader = new FormUploader(this.config); 88 | result = formUploader.UploadStream(stream, key, token, extra); 89 | } 90 | 91 | return result; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/PfopInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 持久化请求的回复 7 | /// 8 | public class PfopInfo 9 | { 10 | /// 11 | /// 任务ID 12 | /// 13 | [JsonProperty("id")] 14 | public string Id; 15 | /// 16 | /// 任务类型,为 1 代表为闲时任务 17 | /// 18 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] 19 | public int? Type; 20 | /// 21 | /// 任务创建时间 22 | /// 23 | [JsonProperty("creationDate", NullValueHandling = NullValueHandling.Ignore)] 24 | public string CreationDate; 25 | /// 26 | /// 任务结果状态码 27 | /// 28 | [JsonProperty("code")] 29 | public int Code; 30 | /// 31 | /// 任务结果状态描述 32 | /// 33 | [JsonProperty("desc")] 34 | public string Desc; 35 | /// 36 | /// 待处理的数据文件 37 | /// 38 | [JsonProperty("inputKey")] 39 | public string InputKey; 40 | /// 41 | /// 待处理文件所在空间 42 | /// 43 | [JsonProperty("inputBucket")] 44 | public string InputBucket; 45 | /// 46 | /// 数据处理队列 47 | /// 48 | [JsonProperty("pipeline")] 49 | public string Pipeline; 50 | /// 51 | /// 任务的Reqid 52 | /// 53 | [JsonProperty("reqid")] 54 | public string Reqid; 55 | /// 56 | /// 任务来源 57 | /// 58 | [JsonProperty("taskFrom")] 59 | public string TaskFrom; 60 | /// 61 | /// 数据处理的命令集合 62 | /// 63 | [JsonProperty("items")] 64 | public PfopItems[] Items; 65 | } 66 | 67 | /// 68 | /// 持久化处理命令 69 | /// 70 | public class PfopItems 71 | { 72 | /// 73 | /// 命令 74 | /// 75 | [JsonProperty("cmd")] 76 | public string Cmd; 77 | /// 78 | /// 命令执行结果状态码 79 | /// 80 | [JsonProperty("code")] 81 | public string Code; 82 | /// 83 | /// 命令执行结果描述 84 | /// 85 | [JsonProperty("desc")] 86 | public string Desc; 87 | /// 88 | /// 命令执行错误 89 | /// 90 | [JsonProperty("Error", NullValueHandling = NullValueHandling.Ignore)] 91 | public string Error; 92 | /// 93 | /// VSample命令的生成文件名列表 94 | /// 95 | [JsonProperty("keys", NullValueHandling = NullValueHandling.Ignore)] 96 | public string[] Keys; 97 | /// 98 | /// 命令生成的文件名 99 | /// 100 | [JsonProperty("key", NullValueHandling = NullValueHandling.Ignore)] 101 | public string Key; 102 | /// 103 | /// 命令生成的文件内容hash 104 | /// 105 | [JsonProperty("hash", NullValueHandling = NullValueHandling.Ignore)] 106 | public string Hash; 107 | /// 108 | /// 该命令是否返回了上一次相同命令生成的结果 109 | /// 110 | [JsonProperty("returnOld", NullValueHandling = NullValueHandling.Ignore)] 111 | public int? ReturnOld; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Qiniu/CDN/RefreshResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Newtonsoft.Json; 3 | using Qiniu.Http; 4 | 5 | namespace Qiniu.CDN 6 | { 7 | /// 8 | /// 缓存刷新-结果 9 | /// 10 | public class RefreshResult : HttpResult 11 | { 12 | /// 13 | /// 获取缓存刷新信息 14 | /// 15 | public RefreshInfo Result 16 | { 17 | get 18 | { 19 | RefreshInfo info = null; 20 | if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) 21 | { 22 | info=JsonConvert.DeserializeObject(Text); 23 | } 24 | return info; 25 | } 26 | } 27 | 28 | /// 29 | /// 转换为易读字符串格式 30 | /// 31 | /// 便于打印和阅读的字符串 32 | public override string ToString() 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | sb.AppendFormat("code:{0}\n", Code); 37 | sb.AppendLine(); 38 | 39 | if (Result != null) 40 | { 41 | sb.AppendLine("result:"); 42 | sb.AppendFormat("code:{0}\n", Result.Code); 43 | if (!string.IsNullOrEmpty(Result.Error)) 44 | { 45 | sb.AppendFormat("error:{0}\n", Result.Error); 46 | } 47 | if (!string.IsNullOrEmpty(Result.RequestId)) 48 | { 49 | sb.AppendFormat("requestId:{0}\n", Result.RequestId); 50 | } 51 | if (Result.InvalidDirs != null && Result.InvalidDirs.Count > 0) 52 | { 53 | sb.Append("invalidDirs:"); 54 | foreach (var s in Result.InvalidDirs) 55 | { 56 | sb.Append(s + " "); 57 | } 58 | sb.AppendLine(); 59 | } 60 | if (Result.InvalidUrls != null && Result.InvalidUrls.Count > 0) 61 | { 62 | sb.Append("invalidUrls:"); 63 | foreach (var s in Result.InvalidUrls) 64 | { 65 | sb.Append(s + " "); 66 | } 67 | sb.AppendLine(); 68 | } 69 | sb.AppendFormat("dirQuotaDay:{0}\n", Result.DirQuotaDay); 70 | sb.AppendFormat("dirSurplusDay:{0}\n", Result.DirSurplusDay); 71 | sb.AppendFormat("urlQuotaDay:{0}\n", Result.UrlQuotaDay); 72 | sb.AppendFormat("urlSurplusDay:{0}\n", Result.UrlSurplusDay); 73 | } 74 | else 75 | { 76 | if (!string.IsNullOrEmpty(Text)) 77 | { 78 | sb.AppendLine("text:"); 79 | sb.AppendLine(Text); 80 | } 81 | } 82 | sb.AppendLine(); 83 | 84 | sb.AppendFormat("ref-code:{0}\n", RefCode); 85 | 86 | if (!string.IsNullOrEmpty(RefText)) 87 | { 88 | sb.AppendLine("ref-text:"); 89 | sb.AppendLine(RefText); 90 | } 91 | 92 | if (RefInfo != null) 93 | { 94 | sb.AppendFormat("ref-info:\n"); 95 | foreach (var d in RefInfo) 96 | { 97 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 98 | } 99 | } 100 | 101 | return sb.ToString(); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Qiniu/Util/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Qiniu.Util 4 | { 5 | /// 6 | /// URL辅助工具(RegExp) 7 | /// 8 | public class UrlHelper 9 | { 10 | private static Regex regx = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?"); 11 | 12 | private static Regex regu = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?"); 13 | 14 | private static Regex regd = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,/~\+#]*)?/"); 15 | 16 | /// 17 | /// 是否合法URL 18 | /// 19 | /// 待判断的url 20 | /// 21 | public static bool IsValidUrl(string _url) 22 | { 23 | return regx.IsMatch(_url); 24 | } 25 | 26 | /// 27 | /// 是否一般URL(不包含?等后缀参数) 28 | /// 29 | /// 待判断的url 30 | /// 31 | public static bool IsNormalUrl(string _url) 32 | { 33 | return regu.IsMatch(_url); 34 | } 35 | 36 | /// 37 | /// 是否合法URL目录 38 | /// 39 | /// 待判断的url目录 40 | /// 41 | public static bool IsValidDir(string _dir) 42 | { 43 | return regd.IsMatch(_dir); 44 | } 45 | 46 | /// 47 | /// 从原始URL转换为一般URL(根据需要截断) 48 | /// 49 | /// 待转换的url 50 | /// 51 | public static string GetNormalUrl(string _url) 52 | { 53 | var m = regu.Match(_url); 54 | return m.Value; 55 | } 56 | 57 | /// 58 | /// URL分析,拆分出Host,Path,File,Query各个部分 59 | /// 60 | /// 原始URL 61 | /// host部分 62 | /// path部分 63 | /// 文件名 64 | /// 参数 65 | public static void UrlSplit(string url, out string host, out string path, out string file, out string query) 66 | { 67 | host = ""; 68 | path = ""; 69 | file = ""; 70 | query = ""; 71 | 72 | if(string.IsNullOrEmpty(url)) 73 | { 74 | return; 75 | } 76 | 77 | int start = 0; 78 | 79 | try 80 | { 81 | Regex regHost = new Regex(@"(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+"); 82 | host = regHost.Match(url, start).Value; 83 | start += host.Length; 84 | 85 | Regex regPath = new Regex(@"(/(\w|\-)*)+/"); 86 | path = regPath.Match(url, start).Value; 87 | if (!string.IsNullOrEmpty(path)) 88 | { 89 | start += path.Length; 90 | } 91 | 92 | int index = url.IndexOf('?', start); 93 | if (index > 0) 94 | { 95 | file = url.Substring(start, index - start); 96 | query = url.Substring(index); 97 | } 98 | else 99 | { 100 | file = url.Substring(start); 101 | query = ""; 102 | } 103 | } 104 | catch(System.Exception) 105 | { 106 | // 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/QiniuTests/Http/Middleware.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NUnit.Framework; 3 | using Qiniu.Http; 4 | 5 | namespace QiniuTests.Http 6 | { 7 | class RecorderMiddleware : IMiddleware 8 | { 9 | private readonly List _orderRecorder; 10 | 11 | private readonly string _label; 12 | 13 | public RecorderMiddleware(List orderRecorder, string label) 14 | { 15 | _orderRecorder = orderRecorder; 16 | _label = label; 17 | } 18 | 19 | public HttpResult Send(HttpRequestOptions req, DNextSend next) 20 | { 21 | _orderRecorder.Add("bef_" + _label + _orderRecorder.Count); 22 | HttpResult result = next(req); 23 | _orderRecorder.Add("aft_" + _label + _orderRecorder.Count); 24 | return result; 25 | } 26 | } 27 | 28 | [TestFixture] 29 | public class MiddlewareTests 30 | { 31 | [Test] 32 | public void SendWithMiddlewareTest() 33 | { 34 | HttpManager httpManager = new HttpManager(true); 35 | 36 | List orderRecorder = new List(); 37 | 38 | List middlewares = new List 39 | { 40 | new RecorderMiddleware(orderRecorder, "A"), 41 | new RecorderMiddleware(orderRecorder, "B") 42 | }; 43 | 44 | HttpResult resp = httpManager.Get("https://example.com/index.html", null, null, middlewares); 45 | 46 | Assert.AreEqual((int)HttpCode.OK, resp.Code, resp.ToString()); 47 | CollectionAssert.AreEqual( 48 | new List 49 | { 50 | "bef_A0", 51 | "bef_B1", 52 | "aft_B2", 53 | "aft_A3" 54 | }, 55 | orderRecorder 56 | ); 57 | } 58 | 59 | [Test] 60 | public void RetryDomainsMiddlewareTest() 61 | { 62 | 63 | HttpManager httpManager = new HttpManager(true); 64 | 65 | List orderRecorder = new List(); 66 | 67 | List middlewares = new List 68 | { 69 | new RetryDomainsMiddleware( 70 | new List 71 | { 72 | "unavailable.csharpsdk.qiniu.com", 73 | "example.com" 74 | }, 75 | 3 76 | ), 77 | new RecorderMiddleware(orderRecorder, "A") 78 | }; 79 | 80 | HttpResult resp = httpManager.Get("https://fake.csharpsdk.qiniu.com/index.html", null, null, middlewares); 81 | 82 | Assert.AreEqual((int)HttpCode.OK, resp.Code, resp.ToString()); 83 | 84 | CollectionAssert.AreEqual( 85 | new List 86 | { 87 | // fake.csharpsdk.qiniu.com 88 | "bef_A0", 89 | "aft_A1", 90 | "bef_A2", 91 | "aft_A3", 92 | "bef_A4", 93 | "aft_A5", 94 | // unavailable.csharpsdk.qiniu.com 95 | "bef_A6", 96 | "aft_A7", 97 | "bef_A8", 98 | "aft_A9", 99 | "bef_A10", 100 | "aft_A11", 101 | // qiniu.com 102 | "bef_A12", 103 | "aft_A13" 104 | }, 105 | orderRecorder 106 | ); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/FileInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 获取空间文件信息(stat操作)的有效内容 7 | /// 8 | public class FileInfo 9 | { 10 | /// 11 | /// 文件大小(字节) 12 | /// 13 | [JsonProperty("fsize")] 14 | public long Fsize { set; get; } 15 | 16 | /// 17 | /// 文件hash(ETAG) 18 | /// 19 | [JsonProperty("hash")] 20 | public string Hash { set; get; } 21 | 22 | /// 23 | /// 文件MIME类型 24 | /// 25 | [JsonProperty("mimeType")] 26 | public string MimeType { set; get; } 27 | 28 | /// 29 | /// 文件上传时间 30 | /// 31 | [JsonProperty("putTime")] 32 | public long PutTime { set; get; } 33 | 34 | /// 35 | /// 文件存储类型。 36 | /// 0 标准存储 37 | /// 1 低频存储 38 | /// 2 归档存储 39 | /// 3 深度归档存储 40 | /// 4 归档直读存储 41 | /// 42 | [JsonProperty("type")] 43 | public int FileType { get; set; } 44 | 45 | /// 46 | /// 文件解冻状态。 47 | /// 1 解冻中 48 | /// 2 已解冻 49 | /// 0 如果是归档/深度归档,但处于冻结,后端不返回此字段,因此默认值为 0。请勿依赖 0 判断冻结状态 50 | /// 51 | [JsonProperty("restoreStatus", NullValueHandling = NullValueHandling.Ignore)] 52 | public int RestoreStatus { get; set; } 53 | 54 | /// 55 | /// 文件的存储状态。 56 | /// 0 启用,非禁用后端不返回此字段,因此默认值为 0。 57 | /// 1 禁用 58 | /// 59 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] 60 | public int Status { get; set; } 61 | 62 | /// 63 | /// 文件的 md5 值。 64 | /// 服务端不确保一定返回此字段,详见: 65 | /// https://developer.qiniu.com/kodo/1308/stat#:~:text=%E8%AF%A5%E5%AD%97%E6%AE%B5%E3%80%82-,md5,-%E5%90%A6 66 | /// 67 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)] 68 | public string Md5 { get; set; } 69 | 70 | /// 71 | /// 文件过期删除日期,Unix 时间戳格式。 72 | /// 文件在设置过期时间后才会返回该字段。 73 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 74 | /// 75 | [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)] 76 | public int Expiration { get; set; } 77 | 78 | /// 79 | /// 文件生命周期中转为低频存储的日期,Unix 时间戳格式。 80 | /// 文件在设置过期时间后才会返回该字段。 81 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 82 | /// 83 | [JsonProperty("transitionToIA", NullValueHandling = NullValueHandling.Ignore)] 84 | public int TransitionToIa { get; set; } 85 | 86 | /// 87 | /// 文件生命周期中转为归档直读存储的日期,Unix 时间戳格式。 88 | /// 文件在设置过期时间后才会返回该字段。 89 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 90 | /// 91 | [JsonProperty("transitionToArchiveIR", NullValueHandling = NullValueHandling.Ignore)] 92 | public int TransitionToArchiveIr { get; set; } 93 | 94 | /// 95 | /// 文件生命周期中转为归档存储的日期,Unix 时间戳格式。 96 | /// 文件在设置过期时间后才会返回该字段。 97 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 98 | /// 99 | [JsonProperty("transitionToARCHIVE", NullValueHandling = NullValueHandling.Ignore)] 100 | public int TransitionToArchive { get; set; } 101 | 102 | /// 103 | /// 文件生命周期中转为深度归档存储的日期,Unix 时间戳格式。 104 | /// 文件在设置过期时间后才会返回该字段。 105 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 106 | /// 107 | [JsonProperty("transitionToDeepArchive", NullValueHandling = NullValueHandling.Ignore)] 108 | public int TransitionToDeepArchive { get; set; } 109 | } 110 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/Zone.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Storage 2 | { 3 | /// 4 | /// 目前已支持的区域:华东/华东2/华北/华南/北美/新加坡/首尔 5 | /// 6 | public class Zone 7 | { 8 | /// 9 | /// 资源管理 10 | /// 11 | public string RsHost { set; get; } 12 | 13 | /// 14 | /// 源列表 15 | /// 16 | public string RsfHost { set; get; } 17 | 18 | /// 19 | /// 数据处理 20 | /// 21 | public string ApiHost { set; get; } 22 | 23 | /// 24 | /// 镜像刷新、资源抓取 25 | /// 26 | public string IovipHost { set; get; } 27 | 28 | /// 29 | /// 资源上传 30 | /// 31 | public string[] SrcUpHosts { set; get; } 32 | 33 | /// 34 | /// CDN加速 35 | /// 36 | public string[] CdnUpHosts { set; get; } 37 | 38 | /// 39 | /// 华东 40 | /// 41 | public static Zone ZONE_CN_East = new Zone() 42 | { 43 | RsHost = "rs.qbox.me", 44 | RsfHost = "rsf.qbox.me", 45 | ApiHost = "api.qiniuapi.com", 46 | IovipHost = "iovip.qbox.me", 47 | SrcUpHosts = new string[] { "up.qiniup.com" }, 48 | CdnUpHosts = new string[] { "upload.qiniup.com" } 49 | }; 50 | 51 | /// 52 | /// 华东-浙江2 53 | /// 54 | public static Zone ZONE_CN_East_2 = new Zone() 55 | { 56 | RsHost = "rs-cn-east-2.qiniuapi.com", 57 | RsfHost = "rsf-cn-east-2.qiniuapi.com", 58 | ApiHost = "api-cn-east-2.qiniuapi.com", 59 | IovipHost = "iovip-cn-east-2.qiniuio.com", 60 | SrcUpHosts = new string[] { "up-cn-east-2.qiniup.com" }, 61 | CdnUpHosts = new string[] { "upload-cn-east-2.qiniup.com" } 62 | }; 63 | 64 | /// 65 | /// 华北 66 | /// 67 | public static Zone ZONE_CN_North = new Zone() 68 | { 69 | RsHost = "rs-z1.qbox.me", 70 | RsfHost = "rsf-z1.qbox.me", 71 | ApiHost = "api-z1.qiniuapi.com", 72 | IovipHost = "iovip-z1.qbox.me", 73 | SrcUpHosts = new string[] { "up-z1.qiniup.com" }, 74 | CdnUpHosts = new string[] { "upload-z1.qiniup.com" } 75 | }; 76 | 77 | /// 78 | /// 华南 79 | /// 80 | public static Zone ZONE_CN_South = new Zone() 81 | { 82 | RsHost = "rs-z2.qbox.me", 83 | RsfHost = "rsf-z2.qbox.me", 84 | ApiHost = "api-z2.qiniuapi.com", 85 | IovipHost = "iovip-z2.qbox.me", 86 | SrcUpHosts = new string[] { "up-z2.qiniup.com" }, 87 | CdnUpHosts = new string[] { "upload-z2.qiniup.com" } 88 | }; 89 | 90 | /// 91 | /// 北美 92 | /// 93 | public static Zone ZONE_US_North = new Zone() 94 | { 95 | RsHost = "rs-na0.qbox.me", 96 | RsfHost = "rsf-na0.qbox.me", 97 | ApiHost = "api-na0.qiniuapi.com", 98 | IovipHost = "iovip-na0.qbox.me", 99 | SrcUpHosts = new string[] { "up-na0.qiniup.com" }, 100 | CdnUpHosts = new string[] { "upload-na0.qiniup.com" } 101 | }; 102 | 103 | /// 104 | /// 新加坡 105 | /// 106 | public static Zone ZONE_AS_Singapore = new Zone() 107 | { 108 | RsHost = "rs-as0.qbox.me", 109 | RsfHost = "rsf-as0.qbox.me", 110 | ApiHost = "api-as0.qiniuapi.com", 111 | IovipHost = "iovip-as0.qbox.me", 112 | SrcUpHosts = new string[] { "up-as0.qiniup.com" }, 113 | CdnUpHosts = new string[] { "upload-as0.qiniup.com" } 114 | }; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/QiniuTests/QiniuTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {E8CB1665-53F7-46A5-9AFD-B85AD08262D0} 7 | Library 8 | Properties 9 | QiniuTests 10 | QiniuTests 11 | netcoreapp2.0 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {2F5B0328-DE8B-4B53-A500-3077E340A51B} 35 | Qiniu 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | runtime; build; native; contentfiles; analyzers; buildtransitive 44 | all 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | False 55 | 56 | 57 | False 58 | 59 | 60 | False 61 | 62 | 63 | False 64 | 65 | 66 | 67 | 68 | 75 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/DownloadManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Qiniu.Util; 5 | using Qiniu.Http; 6 | 7 | namespace Qiniu.Storage 8 | { 9 | /// 10 | /// 空间文件下载,只提供简单下载逻辑 11 | /// 对于大文件下载、断点续下载等需求,可以根据实际情况自行实现 12 | /// 13 | public class DownloadManager 14 | { 15 | /// 16 | /// 生成授权的下载链接(访问私有空间中的文件时需要使用这种链接) 17 | /// 18 | /// 账号(密钥) 19 | /// (私有)空间文件的下载域名 20 | /// (私有)空间文件名 21 | /// 从生成此链接的时刻算起,该链接有效时间(单位:秒) 22 | /// 已授权的下载链接 23 | public static string CreatePrivateUrl(Mac mac, string domain, string fileName, int expireInSeconds = 3600) 24 | { 25 | long deadline = UnixTimestamp.GetUnixTimestamp(expireInSeconds); 26 | string publicUrl = CreatePublishUrl(domain, fileName); 27 | StringBuilder sb = new StringBuilder(publicUrl); 28 | if (publicUrl.Contains("?")) 29 | { 30 | sb.AppendFormat("&e={0}", deadline); 31 | } 32 | else 33 | { 34 | sb.AppendFormat("?e={0}", deadline); 35 | } 36 | 37 | string token = Auth.CreateDownloadToken(mac, sb.ToString()); 38 | sb.AppendFormat("&token={0}", token); 39 | 40 | return sb.ToString(); 41 | } 42 | 43 | /// 44 | /// 生成公开空间的下载链接 45 | /// 46 | /// 公开空间的文件下载域名 47 | /// 公开空间文件名 48 | /// 公开空间文件下载链接 49 | public static string CreatePublishUrl(string domain, string fileName) 50 | { 51 | return string.Format("{0}/{1}", domain, Uri.EscapeUriString(fileName)); 52 | } 53 | 54 | /// 55 | /// 下载文件到本地 56 | /// 57 | /// (可访问的或者已授权的)链接 58 | /// (另存为)本地文件名 59 | /// 下载资源的结果 60 | public static HttpResult Download(string url, string saveasFile) 61 | { 62 | HttpResult result = new HttpResult(); 63 | 64 | try 65 | { 66 | HttpManager httpManager = new HttpManager(); 67 | 68 | result = httpManager.Get(url, null, true); 69 | if (result.Code == (int)HttpCode.OK) 70 | { 71 | using (FileStream fs = File.Create(saveasFile, result.Data.Length)) 72 | { 73 | fs.Write(result.Data, 0, result.Data.Length); 74 | fs.Flush(); 75 | } 76 | result.RefText += string.Format("[{0}] [Download] Success: (Remote file) ==> \"{1}\"\n", 77 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), saveasFile); 78 | } 79 | else 80 | { 81 | result.RefText += string.Format("[{0}] [Download] Error: code = {1}\n", 82 | DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff"), result.Code); 83 | } 84 | } 85 | catch (Exception ex) 86 | { 87 | StringBuilder sb = new StringBuilder(); 88 | sb.AppendFormat("[{0}] [Download] Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); 89 | Exception e = ex; 90 | while (e != null) 91 | { 92 | sb.Append(e.Message + " "); 93 | e = e.InnerException; 94 | } 95 | sb.AppendLine(); 96 | 97 | result.RefCode = (int)HttpCode.USER_UNDEF; 98 | result.RefText += sb.ToString(); 99 | } 100 | 101 | return result; 102 | } 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Qiniu/Util/CRC32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Qiniu.Util 5 | { 6 | /// 7 | /// CRC32计算器 8 | /// 9 | public class CRC32 10 | { 11 | /// 12 | /// magic 13 | /// 14 | public const uint IEEE = 0xedb88320; 15 | private uint[] Table; 16 | private uint Value; 17 | 18 | /// 19 | /// 初始化 20 | /// 21 | public CRC32() 22 | { 23 | Value = 0; 24 | Table = makeTable(IEEE); 25 | } 26 | 27 | /// 28 | /// 写入 29 | /// 30 | /// 字节数据 31 | /// 偏移位置 32 | /// 字节数 33 | public void Write(byte[] p, int offset, int count) 34 | { 35 | this.Value = Update(this.Value, this.Table, p, offset, count); 36 | } 37 | 38 | /// 39 | /// 校验和 40 | /// 41 | /// 校验和 42 | public uint Sum() 43 | { 44 | return this.Value; 45 | } 46 | 47 | private static uint[] makeTable(uint poly) 48 | { 49 | uint[] table = new uint[256]; 50 | for (int i = 0; i < 256; i++) 51 | { 52 | uint crc = (uint)i; 53 | for (int j = 0; j < 8; j++) 54 | { 55 | if ((crc & 1) == 1) 56 | crc = (crc >> 1) ^ poly; 57 | else 58 | crc >>= 1; 59 | } 60 | table[i] = crc; 61 | } 62 | return table; 63 | } 64 | 65 | /// 66 | /// 更新 67 | /// 68 | /// crc32 69 | /// 表 70 | /// 字节数据 71 | /// 偏移位置 72 | /// 字节数 73 | /// 74 | public static uint Update(UInt32 crc, UInt32[] table, byte[] p, int offset, int count) 75 | { 76 | crc = ~crc; 77 | for (int i = 0; i < count; i++) 78 | { 79 | crc = table[((byte)crc) ^ p[offset + i]] ^ (crc >> 8); 80 | } 81 | return ~crc; 82 | } 83 | 84 | /// 85 | /// 计算字节数据的crc32值 86 | /// 87 | /// 二进制数据 88 | /// crc32值 89 | public static uint CheckSumBytes(byte[] data) 90 | { 91 | CRC32 crc = new CRC32(); 92 | crc.Write(data, 0, data.Length); 93 | return crc.Sum(); 94 | } 95 | 96 | /// 97 | /// 检验 98 | /// 99 | /// 字节数据 100 | /// 偏移位置 101 | /// 字节数 102 | /// 103 | public static uint CheckSumSlice(byte[] data, int offset, int count) 104 | { 105 | CRC32 crc = new CRC32(); 106 | crc.Write(data, offset, count); 107 | return crc.Sum(); 108 | } 109 | 110 | /// 111 | /// 计算沙盒文件的crc32值 112 | /// 113 | /// 沙盒文件全路径 114 | /// crc32值 115 | public static uint checkSumFile(string filePath) 116 | { 117 | CRC32 crc = new CRC32(); 118 | int bufferLen = 32 * 1024; 119 | using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) 120 | { 121 | byte[] buffer = new byte[bufferLen]; 122 | while (true) 123 | { 124 | int n = fs.Read(buffer, 0, bufferLen); 125 | if (n == 0) 126 | break; 127 | crc.Write(buffer, 0, n); 128 | } 129 | } 130 | return crc.Sum(); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/Qiniu/Http/HttpCode.cs: -------------------------------------------------------------------------------- 1 | namespace Qiniu.Http 2 | { 3 | /// 4 | /// HTTP 状态码 5 | /// 6 | public enum HttpCode 7 | { 8 | #region _PRE_ 9 | 10 | /// 11 | /// 成功 12 | /// 13 | OK = 200, 14 | 15 | /// 16 | /// 部分OK 17 | /// 18 | PARTLY_OK = 298, 19 | 20 | /// 21 | /// 请求错误 22 | /// 23 | BAD_REQUEST = 400, 24 | 25 | /// 26 | /// 认证授权失败 27 | /// 28 | AUTHENTICATION_FAILED = 401, 29 | 30 | /// 31 | /// 拒绝访问 32 | /// 33 | ACCESS_DENIED = 403, 34 | 35 | /// 36 | /// 资源不存在 37 | /// 38 | OBJECT_NOT_FOUND = 404, 39 | 40 | /// 41 | /// CRC32校验失败 42 | /// 43 | CRC32_CHECK_FAILEd = 406, 44 | 45 | /// 46 | /// 上传文件大小超限 47 | /// 48 | FILE_SIZE_EXCEED = 413, 49 | 50 | /// 51 | /// 镜像回源失败 52 | /// 53 | PREFETCH_FAILED = 478, 54 | 55 | /// 56 | /// 接口未实现 57 | /// 58 | NOT_IMPLEMENTED = 501, 59 | 60 | /// 61 | /// 错误网关 62 | /// 63 | BAD_GATEWAY = 502, 64 | 65 | /// 66 | /// 服务端不可用 67 | /// 68 | SERVER_UNAVAILABLE = 503, 69 | 70 | /// 71 | /// 服务端操作超时 72 | /// 73 | SERVER_TIME_EXCEED = 504, 74 | 75 | /// 76 | /// 超出带宽限制 77 | /// 78 | BANDWIDTH_LIMIT_EXCEEDED = 509, 79 | 80 | /// 81 | /// 单个资源访问频率过高 82 | /// 83 | TOO_FREQUENT_ACCESS = 573, 84 | 85 | /// 86 | /// 回调失败 87 | /// 88 | CALLBACK_FAILED = 579, 89 | 90 | /// 91 | /// 服务端操作失败 92 | /// 93 | SERVER_OPERATION_FAILED = 599, 94 | 95 | /// 96 | /// 资源内容被修改 97 | /// 98 | CONTENT_MODIFIED = 608, 99 | 100 | /// 101 | /// 文件不存在/指定资源不存在或已被删除 102 | /// 103 | FILE_NOT_EXIST = 612, 104 | 105 | /// 106 | /// 文件已存在/目标资源已存在 107 | /// 108 | FILE_EXISTS = 614, 109 | 110 | /// 111 | /// 当前操作无法在共享空间执行(被共享的用户进行操作时) 112 | /// 113 | INVALID_SHARE_BUCKET = 616, 114 | 115 | /// 116 | /// 当前操作无法在共享空间执行(所有者进行操作时) 117 | /// 118 | BUCKET_IS_SHARING = 618, 119 | 120 | /// 121 | /// 空间数量已达上限 122 | /// 123 | BUCKET_COUNT_LIMIT = 630, 124 | 125 | /// 126 | /// 空间或者文件不存在 127 | /// 128 | BUCKET_NOT_EXIST = 631, 129 | 130 | /// 131 | /// 共享空间达到上限 132 | /// 133 | EXCEED_SHARED_BUCKETS_LIMIT = 632, 134 | 135 | /// 136 | /// 列举资源(list)使用了非法的marker 137 | /// 138 | INVALID_MARKER = 640, 139 | 140 | /// 141 | /// 在断点续上传过程中,后续上传接收地址不正确或ctx信息已过期。 142 | /// 143 | CONTEXT_EXPIRED = 701, 144 | 145 | #endregion _PRE_ 146 | 147 | #region _USR_ 148 | 149 | /// 150 | /// 自定义HTTP状态码 (默认值) 151 | /// 152 | USER_UNDEF = 0, 153 | 154 | /// 155 | /// 自定义HTTP状态码 (用户取消) 156 | /// 157 | USER_CANCELED = -2, 158 | 159 | /// 160 | /// 自定义HTTP状态码 (用户暂停) 161 | /// 162 | USER_PAUSED = 1, 163 | 164 | /// 165 | /// 自定义HTTP状态码 (用户继续) 166 | /// 167 | USER_RESUMED = 2, 168 | 169 | /// 170 | /// 自定义HTTP状态码 (需要重试) 171 | /// 172 | USER_NEED_RETRY = 3, 173 | 174 | /// 175 | /// 自定义HTTP状态码 (异常或错误) 176 | /// 177 | INVALID_ARGUMENT = -4, 178 | 179 | /// 180 | /// 自定义HTTP状态码(文件不合法) 181 | /// 182 | INVALID_FILE = -3, 183 | 184 | /// 185 | /// 自定义HTTP状态码(凭证不合法) 186 | /// 187 | INVALID_TOKEN = -5, 188 | 189 | #endregion _USR_ 190 | 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/BatchInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | namespace Qiniu.Storage 3 | { 4 | /// 5 | /// 批量处理返回的信息 6 | /// 7 | public class BatchInfo 8 | { 9 | /// 10 | /// 状态码 11 | /// 12 | [JsonProperty("code",NullValueHandling=NullValueHandling.Ignore)] 13 | public int Code { get; set; } 14 | 15 | /// 16 | /// 消息 17 | /// 18 | [JsonProperty("data",NullValueHandling=NullValueHandling.Ignore)] 19 | public BatchData Data { get; set; } 20 | } 21 | 22 | /// 23 | /// 批量处理的结果内容 24 | /// 25 | public class BatchData 26 | { 27 | /// 28 | /// 处理遇到的错误信息 29 | /// 30 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)] 31 | public string Error { get; set; } 32 | 33 | /// 34 | /// 文件hash(ETAG) 35 | /// 36 | [JsonProperty("hash", NullValueHandling = NullValueHandling.Ignore)] 37 | public string Hash { get; set; } 38 | 39 | /// 40 | /// 文件大小(字节) 41 | /// 42 | [JsonProperty("fsize", NullValueHandling = NullValueHandling.Ignore)] 43 | public long Fsize { get; set; } 44 | 45 | /// 46 | /// 文件MIME类型 47 | /// 48 | [JsonProperty("mimeType", NullValueHandling = NullValueHandling.Ignore)] 49 | public string MimeType { get; set; } 50 | 51 | /// 52 | /// 上传时间 53 | /// 54 | [JsonProperty("putTime", NullValueHandling = NullValueHandling.Ignore)] 55 | public long PutTime { get; set; } 56 | 57 | /// 58 | /// 文件存储类型。 59 | /// 0 标准存储 60 | /// 1 低频存储 61 | /// 2 归档存储 62 | /// 3 深度归档存储 63 | /// 4 归档直读存储 64 | /// 65 | [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] 66 | public int FileType { get; set; } 67 | 68 | /// 69 | /// 文件解冻状态。 70 | /// 1 解冻中 71 | /// 2 已解冻 72 | /// 0 如果是归档/深度归档,但处于冻结,后端不返回此字段,因此默认值为 0。请勿依赖 0 判断冻结状态 73 | /// 74 | [JsonProperty("restoreStatus", NullValueHandling = NullValueHandling.Ignore)] 75 | public int RestoreStatus { get; set; } 76 | 77 | /// 78 | /// 文件的存储状态。 79 | /// 0 启用,非禁用后端不返回此字段,因此默认值为 0。 80 | /// 1 禁用 81 | /// 82 | [JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)] 83 | public int Status { get; set; } 84 | 85 | /// 86 | /// 文件的 md5 值。 87 | /// 服务端不确保一定返回此字段,详见: 88 | /// https://developer.qiniu.com/kodo/1308/stat#:~:text=%E8%AF%A5%E5%AD%97%E6%AE%B5%E3%80%82-,md5,-%E5%90%A6 89 | /// 90 | [JsonProperty("md5", NullValueHandling = NullValueHandling.Ignore)] 91 | public string Md5 { get; set; } 92 | 93 | /// 94 | /// 文件过期删除日期,Unix 时间戳格式。 95 | /// 文件在设置过期时间后才会返回该字段。 96 | /// 历史文件过期仍会自动删除,但不会返回该字段,重新设置文件过期时间可使历史文件返回该字段。 97 | /// 98 | [JsonProperty("expiration", NullValueHandling = NullValueHandling.Ignore)] 99 | public int Expiration { get; set; } 100 | 101 | /// 102 | /// 文件生命周期中转为低频存储的日期,Unix 时间戳格式。 103 | /// 文件在设置低频存储转换时间后才会返回该字段。 104 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。 105 | /// 106 | [JsonProperty("TransitionToIA", NullValueHandling = NullValueHandling.Ignore)] 107 | public int TransitionToIa { get; set; } 108 | 109 | /// 110 | /// 文件生命周期中转为归档直读存储的日期,Unix 时间戳格式。 111 | /// 文件在设置归档直读存储转换时间后才会返回该字段。 112 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。 113 | /// 114 | [JsonProperty("transitionToArchiveIR", NullValueHandling = NullValueHandling.Ignore)] 115 | public int TransitionToArchiveIr { get; set; } 116 | 117 | /// 118 | /// 文件生命周期中转为归档存储的日期,Unix 时间戳格式。 119 | /// 文件在设置归档存储转换时间后才会返回该字段。 120 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。 121 | /// 122 | [JsonProperty("transitionToARCHIVE", NullValueHandling = NullValueHandling.Ignore)] 123 | public int TransitionToArchive { get; set; } 124 | 125 | /// 126 | /// 文件生命周期中转为深度归档存储的日期,Unix 时间戳格式。 127 | /// 文件在设置深度归档存储转换时间后才会返回该字段。 128 | /// 历史文件到期仍会自动转换,但不会返回该字段,重新设置文件转换时间可使历史文件返回该字段。 129 | /// 130 | [JsonProperty("transitionToDeepArchive", NullValueHandling = NullValueHandling.Ignore)] 131 | public int TransitionToDeepArchive { get; set; } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/QiniuTests/Storage/OperationManagerTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections; 4 | using System.Text; 5 | using Qiniu.Util; 6 | using Qiniu.Http; 7 | using Qiniu.Tests; 8 | 9 | namespace Qiniu.Storage.Tests 10 | { 11 | [TestFixture] 12 | public class OperationManagerTests : TestEnv 13 | { 14 | private OperationManager getOperationManager() 15 | { 16 | Mac mac = new Mac(AccessKey, SecretKey); 17 | Config config = new Config(); 18 | config.UseHttps = true; 19 | 20 | OperationManager manager = new OperationManager(mac, config); 21 | return manager; 22 | } 23 | 24 | [Test] 25 | public void PfopAndPrefopTest() 26 | { 27 | string key = "qiniu.mp4"; 28 | bool force = true; 29 | string pipeline = "sdktest"; 30 | string notifyUrl = "http://api.example.com/qiniu/pfop/notify"; 31 | string saveMp4Entry = Base64.UrlSafeBase64Encode(Bucket + ":avthumb_test_target.mp4"); 32 | string saveJpgEntry = Base64.UrlSafeBase64Encode(Bucket + ":vframe_test_target.jpg"); 33 | string avthumbMp4Fop = "avthumb/mp4|saveas/" + saveMp4Entry; 34 | string vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry; 35 | string fops = string.Join(";", new string[] { avthumbMp4Fop, vframeJpgFop }); 36 | 37 | OperationManager manager = getOperationManager(); 38 | PfopResult pfopRet = manager.Pfop(Bucket, key, fops, pipeline, notifyUrl, force); 39 | if (pfopRet.Code != (int)HttpCode.OK) 40 | { 41 | Assert.Fail("pfop error: " + pfopRet.ToString()); 42 | } 43 | Console.WriteLine(pfopRet.PersistentId); 44 | 45 | PrefopResult ret = manager.Prefop(pfopRet.PersistentId); 46 | if (ret.Code != (int)HttpCode.OK) 47 | { 48 | Assert.Fail("prefop error: " + ret.ToString()); 49 | } 50 | Console.WriteLine(ret.ToString()); 51 | } 52 | 53 | public static IEnumerable PfopOptionsTestCases 54 | { 55 | get 56 | { 57 | yield return new TestCaseData( 58 | 0, // type 59 | null // workflow template id 60 | ); 61 | yield return new TestCaseData( 62 | 1, 63 | null 64 | ); 65 | yield return new TestCaseData( 66 | 0, 67 | "test-workflow" 68 | ); 69 | } 70 | } 71 | 72 | [TestCaseSource(typeof(OperationManagerTests), nameof(PfopOptionsTestCases))] 73 | public void PfopWithOptionsTest(int type, string workflowId) 74 | { 75 | string bucketName = Bucket; 76 | string key = "qiniu.mp4"; 77 | 78 | StringBuilder persistentKeyBuilder = new StringBuilder("test-pfop/test-pfop-by-api"); 79 | if (type > 0) 80 | { 81 | persistentKeyBuilder.Append("type_" + type); 82 | } 83 | 84 | string fops; 85 | if (!string.IsNullOrEmpty(workflowId)) 86 | { 87 | fops = null; 88 | } 89 | else 90 | { 91 | string saveEntry = Base64.UrlSafeBase64Encode(String.Join( 92 | ":", 93 | bucketName, 94 | persistentKeyBuilder.ToString() 95 | )); 96 | fops = "avinfo|saveas/" + saveEntry; 97 | } 98 | 99 | OperationManager manager = getOperationManager(); 100 | PfopResult pfopRet = manager.Pfop( 101 | Bucket, 102 | key, 103 | fops, 104 | null, 105 | null, 106 | true, 107 | type, 108 | workflowId 109 | ); 110 | if (pfopRet.Code != (int)HttpCode.OK) 111 | { 112 | Assert.Fail("pfop error: " + pfopRet); 113 | } 114 | 115 | PrefopResult prefopRet = manager.Prefop(pfopRet.PersistentId); 116 | if (prefopRet.Code != (int)HttpCode.OK) 117 | { 118 | Assert.Fail("prefop error: " + prefopRet); 119 | } 120 | 121 | Assert.IsNotNull(prefopRet.Result.CreationDate); 122 | Assert.IsNotEmpty(prefopRet.Result.CreationDate); 123 | 124 | if (type == 1) 125 | { 126 | Assert.AreEqual(1, prefopRet.Result.Type); 127 | } 128 | 129 | if (!string.IsNullOrEmpty(workflowId)) 130 | { 131 | Assert.IsNotNull(prefopRet.Result.TaskFrom); 132 | Assert.IsNotEmpty(prefopRet.Result.TaskFrom); 133 | Assert.IsTrue(prefopRet.Result.TaskFrom.Contains(workflowId)); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Qiniu/Util/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Qiniu.Util 6 | { 7 | /// 8 | /// 字符串处理工具 9 | /// 10 | public class StringHelper 11 | { 12 | /// 13 | /// URL编码 14 | /// 15 | /// 源字符串 16 | /// URL编码字符串 17 | public static string UrlEncode(string text) 18 | { 19 | return Uri.EscapeDataString(text); 20 | } 21 | 22 | /// 23 | /// URL键值对编码 24 | /// 25 | /// 键值对 26 | /// URL编码的键值对数据 27 | public static string UrlFormEncode(Dictionary values) 28 | { 29 | StringBuilder urlValuesBuilder = new StringBuilder(); 30 | 31 | foreach (KeyValuePair kvp in values) 32 | { 33 | urlValuesBuilder.AppendFormat("{0}={1}&", Uri.EscapeDataString(kvp.Key), 34 | Uri.EscapeDataString(kvp.Value)); 35 | } 36 | 37 | string encodedStr = urlValuesBuilder.ToString(); 38 | return encodedStr.Substring(0, encodedStr.Length - 1); 39 | } 40 | 41 | /// 42 | /// 合法 Header 字段名的字符 43 | /// 44 | private static readonly Dictionary ValidMimeHeaderNameTokens = new Dictionary 45 | { 46 | {'!', true}, 47 | {'#', true}, 48 | {'$', true}, 49 | {'%', true}, 50 | {'&', true}, 51 | {'\\', true}, 52 | {'*', true}, 53 | {'+', true}, 54 | {'-', true}, 55 | {'.', true}, 56 | 57 | {'0', true}, 58 | {'1', true}, 59 | {'2', true}, 60 | {'3', true}, 61 | {'4', true}, 62 | {'5', true}, 63 | {'6', true}, 64 | {'7', true}, 65 | {'8', true}, 66 | {'9', true}, 67 | 68 | {'A', true}, 69 | {'B', true}, 70 | {'C', true}, 71 | {'D', true}, 72 | {'E', true}, 73 | {'F', true}, 74 | {'G', true}, 75 | {'H', true}, 76 | {'I', true}, 77 | {'J', true}, 78 | {'K', true}, 79 | {'L', true}, 80 | {'M', true}, 81 | {'N', true}, 82 | {'O', true}, 83 | {'P', true}, 84 | {'Q', true}, 85 | {'R', true}, 86 | {'S', true}, 87 | {'T', true}, 88 | {'U', true}, 89 | {'W', true}, 90 | {'V', true}, 91 | {'X', true}, 92 | {'Y', true}, 93 | {'Z', true}, 94 | 95 | {'^', true}, 96 | {'_', true}, 97 | {'`', true}, 98 | 99 | {'a', true}, 100 | {'b', true}, 101 | {'c', true}, 102 | {'d', true}, 103 | {'e', true}, 104 | {'f', true}, 105 | {'g', true}, 106 | {'h', true}, 107 | {'i', true}, 108 | {'j', true}, 109 | {'k', true}, 110 | {'l', true}, 111 | {'m', true}, 112 | {'n', true}, 113 | {'o', true}, 114 | {'p', true}, 115 | {'q', true}, 116 | {'r', true}, 117 | {'s', true}, 118 | {'t', true}, 119 | {'u', true}, 120 | {'v', true}, 121 | {'w', true}, 122 | {'x', true}, 123 | {'y', true}, 124 | {'z', true}, 125 | {'|', true}, 126 | {'~', true} 127 | }; 128 | 129 | /// 130 | /// 是否合法的 Header 字段名 131 | /// 132 | /// Header 字段名 133 | /// 是否合法 134 | private static bool IsValidMimeHeaderName(string fieldName) 135 | { 136 | foreach (var ch in fieldName) 137 | { 138 | if (!ValidMimeHeaderNameTokens.ContainsKey(ch)) 139 | { 140 | return false; 141 | } 142 | } 143 | 144 | return true; 145 | } 146 | 147 | /// 148 | /// 规范化 Header 字段名 149 | /// 将合法的字段名规范化为 Abc-Xyz 这种首字母大写的形式 150 | /// 151 | /// Header 字段名 152 | /// 规范化后的 Header 字段名 153 | public static string CanonicalMimeHeaderKey(string fieldName) 154 | { 155 | if (!IsValidMimeHeaderName(fieldName)) 156 | { 157 | return fieldName; 158 | } 159 | 160 | string result = ""; 161 | bool upper = true; 162 | foreach (var ch in fieldName) 163 | { 164 | switch (upper) 165 | { 166 | case true when 'a' <= ch && ch <= 'z': 167 | result += char.ToUpper(ch); 168 | break; 169 | case false when 'A' <= ch && ch <= 'Z': 170 | result += char.ToLower(ch); 171 | break; 172 | default: 173 | result += ch; 174 | break; 175 | } 176 | 177 | upper = ch == '-'; 178 | } 179 | return result; 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /src/Qiniu/Http/HttpResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace Qiniu.Http 6 | { 7 | /// 8 | /// HTTP请求(GET,POST等)的返回消息 9 | /// 10 | public class HttpResult 11 | { 12 | private static readonly IReadOnlyList NotRetryableHttpCodes = new List 13 | { 14 | (int)HttpCode.INVALID_ARGUMENT, 15 | (int)HttpCode.INVALID_FILE, 16 | (int)HttpCode.INVALID_TOKEN, 17 | (int)HttpCode.USER_CANCELED, 18 | (int)HttpCode.USER_PAUSED, 19 | // 服务端 20 | (int)HttpCode.NOT_IMPLEMENTED, 21 | (int)HttpCode.BANDWIDTH_LIMIT_EXCEEDED, 22 | (int)HttpCode.TOO_FREQUENT_ACCESS, 23 | (int)HttpCode.CALLBACK_FAILED, 24 | (int)HttpCode.CONTENT_MODIFIED, 25 | (int)HttpCode.FILE_NOT_EXIST, 26 | (int)HttpCode.FILE_EXISTS, 27 | (int)HttpCode.INVALID_SHARE_BUCKET, 28 | (int)HttpCode.BUCKET_IS_SHARING, 29 | (int)HttpCode.BUCKET_COUNT_LIMIT, 30 | (int)HttpCode.BUCKET_NOT_EXIST, 31 | (int)HttpCode.EXCEED_SHARED_BUCKETS_LIMIT, 32 | (int)HttpCode.INVALID_MARKER, 33 | (int)HttpCode.CONTEXT_EXPIRED 34 | }; 35 | 36 | /// 37 | /// 状态码 (200表示OK) 38 | /// 39 | public int Code { get; set; } 40 | 41 | /// 42 | /// 消息或错误文本 43 | /// 44 | public string Text { get; set; } 45 | 46 | /// 47 | /// 消息或错误(二进制格式) 48 | /// 49 | public byte[] Data { get; set; } 50 | 51 | /// 52 | /// 参考代码(用户自定义) 53 | /// 54 | public int RefCode { get; set; } 55 | 56 | /// 57 | /// 附加信息(用户自定义,如Exception内容) 58 | /// 59 | public string RefText { get; set; } 60 | 61 | /// 62 | /// 参考信息(从返回消息WebResponse的头部获取) 63 | /// 64 | public Dictionary RefInfo { get; set; } 65 | 66 | /// 67 | /// 初始化(所有成员默认值,需要后续赋值) 68 | /// 69 | public HttpResult() 70 | { 71 | Code = (int)HttpCode.USER_UNDEF; 72 | Text = null; 73 | Data = null; 74 | 75 | RefCode = (int)HttpCode.USER_UNDEF; 76 | RefInfo = null; 77 | } 78 | 79 | /// 80 | /// 对象复制 81 | /// 82 | /// 要复制其内容的来源 83 | public void Shadow(HttpResult hr) 84 | { 85 | this.Code = hr.Code; 86 | this.Text = hr.Text; 87 | this.Data = hr.Data; 88 | this.RefCode = hr.RefCode; 89 | this.RefText += hr.RefText; 90 | this.RefInfo = hr.RefInfo; 91 | } 92 | 93 | /// 94 | /// 转换为易读或便于打印的字符串格式 95 | /// 96 | /// 便于打印和阅读的字符串 97 | public override string ToString() 98 | { 99 | StringBuilder sb = new StringBuilder(); 100 | 101 | sb.AppendFormat("code:{0}", Code); 102 | sb.AppendLine(); 103 | 104 | if (!string.IsNullOrEmpty(Text)) 105 | { 106 | sb.AppendLine("text:"); 107 | sb.AppendLine(Text); 108 | } 109 | 110 | if (Data != null) 111 | { 112 | sb.AppendLine("data:"); 113 | int n = 1024; 114 | if (Data.Length <= n) 115 | { 116 | sb.AppendLine(Encoding.UTF8.GetString(Data)); 117 | } 118 | else 119 | { 120 | 121 | sb.AppendLine(Encoding.UTF8.GetString(Data, 0, n)); 122 | sb.AppendFormat("<--- TOO-LARGE-TO-DISPLAY --- TOTAL {0} BYTES --->", Data.Length); 123 | sb.AppendLine(); 124 | } 125 | } 126 | 127 | sb.AppendLine(); 128 | 129 | sb.AppendFormat("ref-code:{0}", RefCode); 130 | sb.AppendLine(); 131 | 132 | if(!string.IsNullOrEmpty(RefText)) 133 | { 134 | sb.AppendLine("ref-text:"); 135 | sb.AppendLine(RefText); 136 | } 137 | 138 | if (RefInfo != null) 139 | { 140 | sb.AppendLine("ref-info:"); 141 | foreach (var d in RefInfo) 142 | { 143 | sb.AppendLine(string.Format("{0}:{1}", d.Key, d.Value)); 144 | } 145 | } 146 | 147 | sb.AppendLine(); 148 | 149 | return sb.ToString(); 150 | } 151 | 152 | /// 153 | /// 非法上传凭证错误 154 | /// 155 | public static HttpResult InvalidToken = new HttpResult { 156 | Code=(int)HttpCode.INVALID_TOKEN, 157 | Text="invalid uptoken" 158 | }; 159 | 160 | /// 161 | /// 非法文件错误 162 | /// 163 | public static HttpResult InvalidFile = new HttpResult 164 | { 165 | Code = (int)HttpCode.INVALID_FILE, 166 | Text = "invalid file" 167 | }; 168 | 169 | public bool NeedRetry() 170 | { 171 | if (Code > 0 && Code < 500) 172 | { 173 | return false; 174 | } 175 | if (NotRetryableHttpCodes.Contains(Code)) 176 | { 177 | return false; 178 | } 179 | return true; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Qiniu/Storage/ZoneHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using Qiniu.Http; 5 | using Newtonsoft.Json; 6 | 7 | namespace Qiniu.Storage 8 | { 9 | internal class ZoneCacheValue 10 | { 11 | public DateTime Deadline { get; set; } 12 | public Zone Zone { get; set; } 13 | } 14 | 15 | /// 16 | /// Zone辅助类,查询及配置Zone 17 | /// 18 | public class ZoneHelper 19 | { 20 | private static Dictionary zoneCache = new Dictionary(); 21 | private static object rwLock = new object(); 22 | 23 | /// 24 | /// 从 UC 服务查询得到各个服务域名,生成 Zone 对象并返回 25 | /// 26 | /// AccessKey 27 | /// 空间名称 28 | /// 查询域名 29 | /// 备用查询域名 30 | public static Zone QueryZone( 31 | string accessKey, 32 | string bucket, 33 | string ucHost = null, 34 | List backupUcHosts = null 35 | ) 36 | { 37 | ZoneCacheValue zoneCacheValue = null; 38 | string cacheKey = string.Format("{0}:{1}", accessKey, bucket); 39 | 40 | //check from cache 41 | lock (rwLock) 42 | { 43 | if (zoneCache.ContainsKey(cacheKey)) 44 | { 45 | zoneCacheValue = zoneCache[cacheKey]; 46 | } 47 | } 48 | 49 | if ( 50 | zoneCacheValue != null && 51 | DateTime.Now < zoneCacheValue.Deadline && 52 | zoneCacheValue.Zone != null 53 | ) 54 | { 55 | return zoneCacheValue.Zone; 56 | } 57 | 58 | //query from uc api 59 | Zone zone; 60 | HttpResult hr = null; 61 | if (String.IsNullOrEmpty(ucHost)) 62 | { 63 | ucHost = "https://" + Config.DefaultQueryRegionHost; 64 | } 65 | 66 | if (backupUcHosts == null) 67 | { 68 | backupUcHosts = Config.DefaultBackupQueryRegionHosts; 69 | } 70 | 71 | try 72 | { 73 | string queryUrl = string.Format("{0}/v4/query?ak={1}&bucket={2}", 74 | ucHost, 75 | accessKey, 76 | bucket 77 | ); 78 | HttpManager httpManager = new HttpManager(); 79 | List middlewares = new List 80 | { 81 | new RetryDomainsMiddleware( 82 | backupUcHosts 83 | ) 84 | }; 85 | hr = httpManager.Get(queryUrl, null, null, middlewares); 86 | if (hr.Code != (int) HttpCode.OK || string.IsNullOrEmpty(hr.Text)) 87 | { 88 | throw new Exception("code: " + hr.Code + ", text: " + hr.Text + ", ref-text:" + hr.RefText); 89 | } 90 | 91 | ZoneInfo zInfo = JsonConvert.DeserializeObject(hr.Text); 92 | if (zInfo == null) 93 | { 94 | throw new Exception("JSON Deserialize failed: " + hr.Text); 95 | } 96 | 97 | if (zInfo.Hosts.Length == 0) 98 | { 99 | throw new Exception("There are no hosts available: " + hr.Text); 100 | } 101 | 102 | ZoneHost zHost = zInfo.Hosts[0]; 103 | 104 | zone = new Zone(); 105 | zone.SrcUpHosts = zHost.Up.Domains; 106 | zone.CdnUpHosts = zHost.Up.Domains; 107 | 108 | if (!string.IsNullOrEmpty(zHost.Io.Domains[0])) 109 | { 110 | zone.IovipHost = zHost.Io.Domains[0]; 111 | } 112 | else 113 | { 114 | zone.IovipHost = Config.DefaultIoHost; 115 | } 116 | 117 | if (!string.IsNullOrEmpty(zHost.Api.Domains[0])) 118 | { 119 | zone.ApiHost = zHost.Api.Domains[0]; 120 | } 121 | else 122 | { 123 | zone.ApiHost = Config.DefaultApiHost; 124 | } 125 | 126 | if (!string.IsNullOrEmpty(zHost.Rs.Domains[0])) 127 | { 128 | zone.RsHost = zHost.Rs.Domains[0]; 129 | } 130 | else 131 | { 132 | zone.RsHost = Config.DefaultRsHost; 133 | } 134 | 135 | if (!string.IsNullOrEmpty(zHost.Rsf.Domains[0])) 136 | { 137 | zone.RsfHost = zHost.Rsf.Domains[0]; 138 | } 139 | else 140 | { 141 | zone.RsfHost = Config.DefaultRsfHost; 142 | } 143 | 144 | lock (rwLock) 145 | { 146 | zoneCacheValue = new ZoneCacheValue(); 147 | TimeSpan ttl = TimeSpan.FromSeconds(zHost.Ttl); 148 | zoneCacheValue.Deadline = DateTime.Now.Add(ttl); 149 | zoneCacheValue.Zone = zone; 150 | zoneCache[cacheKey] = zoneCacheValue; 151 | } 152 | } 153 | catch (Exception ex) 154 | { 155 | StringBuilder sb = new StringBuilder(); 156 | sb.AppendFormat("[{0}] QueryZone Error: ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")); 157 | Exception e = ex; 158 | while (e != null) 159 | { 160 | sb.Append(e.Message + " "); 161 | e = e.InnerException; 162 | } 163 | sb.AppendLine(); 164 | 165 | throw new QiniuException(hr, sb.ToString()); 166 | } 167 | 168 | return zone; 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/QiniuTests/Http/HttpRequestOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | using System.Net; 4 | using NUnit.Framework; 5 | using Qiniu.Http; 6 | 7 | namespace QiniuTests.Http 8 | { 9 | [TestFixture] 10 | public class HttpRequestOptionsTests 11 | { 12 | [Test] 13 | public void SetUrlTest() 14 | { 15 | HttpRequestOptions reqOpts = new HttpRequestOptions(); 16 | reqOpts.Url = "https://qiniu.com/index.html"; 17 | 18 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest(); 19 | Assert.AreEqual("https://qiniu.com/index.html", wReq.Address.ToString()); 20 | wReq.Abort(); 21 | 22 | reqOpts.Url = "https://www.qiniu.com/index.html"; 23 | wReq = reqOpts.CreateHttpWebRequest(); 24 | Assert.AreEqual("https://www.qiniu.com/index.html", wReq.Address.ToString()); 25 | wReq.Abort(); 26 | } 27 | 28 | [Test] 29 | public void SetPropertiesTest() 30 | { 31 | HttpRequestOptions reqOpts = new HttpRequestOptions(); 32 | reqOpts.Url = "https://qiniu.com/index.html"; 33 | 34 | // some items are hard to set and test, skipped. 35 | reqOpts.AllowAutoRedirect = false; // default true 36 | reqOpts.AllowReadStreamBuffering = true; // default false 37 | reqOpts.AllowWriteStreamBuffering = false; // default true 38 | // reqOpts.AutomaticDecompression = ; 39 | // reqOpts.CachePolicy =; 40 | // reqOpts.ClientCertificates = System.Security.Cryptography.X509Certificates.X509CertificateCollection; 41 | reqOpts.ConnectionGroupName = "qngroup"; // default "" 42 | // reqOpts.ContinueDelegate =; 43 | reqOpts.ContinueTimeout = 360; // default 350 44 | // reqOpts.CookieContainer =; 45 | // reqOpts.Credentials =; 46 | // reqOpts.ImpersonationLevel = Delegation; 47 | reqOpts.KeepAlive = false; // default true 48 | reqOpts.MaximumAutomaticRedirections = 10; // default 50 49 | reqOpts.MaximumResponseHeadersLength = 32; // default 64 50 | reqOpts.MediaType = "video/mp4"; // default "" 51 | reqOpts.Method = "POST"; // default "GET" 52 | reqOpts.Pipelined = false; // default true 53 | reqOpts.PreAuthenticate = true; // default false 54 | // reqOpts.Proxy = System.Net.SystemWebProxy; 55 | reqOpts.ReadWriteTimeout = 200000; // default 300000 56 | reqOpts.SendChunked = true; // default false 57 | // reqOpts.ServerCertificateValidationCallback =; 58 | reqOpts.Timeout = 50000; // default 100000 59 | reqOpts.UnsafeAuthenticatedConnectionSharing = true; // default false 60 | reqOpts.UseDefaultCredentials = true; // default false 61 | 62 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest(); 63 | Assert.AreEqual(false, wReq.AllowAutoRedirect); // default true 64 | Assert.AreEqual(true, wReq.AllowReadStreamBuffering); // default false 65 | Assert.AreEqual(false, wReq.AllowWriteStreamBuffering); // default true 66 | // Assert.AreEqual(, wReq.AutomaticDecompression); // default Null 67 | // Assert(, wReq.CachePolicy); // default Null 68 | // Assert(, wReq.ClientCertificates); // default System.Security.Cryptography.X509Certificates.X509CertificateCollection 69 | Assert.AreEqual("qngroup", wReq.ConnectionGroupName); // default "" 70 | // Assert(, wReq.ContinueDelegate); // default Null 71 | Assert.AreEqual(360, wReq.ContinueTimeout); // default 350 72 | // Assert(, wReq.CookieContainer); // default Null 73 | // Assert(, wReq.Credentials); // default Null 74 | // Assert(, wReq.ImpersonationLevel); // default Null 75 | Assert.AreEqual(false, wReq.KeepAlive); // default true 76 | Assert.AreEqual(10, wReq.MaximumAutomaticRedirections); // default 50 77 | Assert.AreEqual(32, wReq.MaximumResponseHeadersLength); // default 64 78 | Assert.AreEqual("video/mp4", wReq.MediaType); // default "" 79 | Assert.AreEqual("POST", wReq.Method); // default "GET" 80 | Assert.AreEqual(false, wReq.Pipelined); // default true 81 | Assert.AreEqual(true, wReq.PreAuthenticate); // default false 82 | // Assert(, wReq.Proxy); // default System.Net.SystemWebProxy; 83 | Assert.AreEqual(200000, wReq.ReadWriteTimeout); // default 300000 84 | Assert.AreEqual(true, wReq.SendChunked); // default false 85 | // Assert(, wReq.ServerCertificateValidationCallback); // default Null 86 | Assert.AreEqual(50000, wReq.Timeout); // default 100000 87 | Assert.AreEqual(true, wReq.UnsafeAuthenticatedConnectionSharing); // default false 88 | Assert.AreEqual(true, wReq.UseDefaultCredentials); // default false 89 | 90 | wReq.Abort(); 91 | } 92 | 93 | [Test] 94 | public void SetHeadersTest() 95 | { 96 | HttpRequestOptions reqOpts = new HttpRequestOptions(); 97 | reqOpts.Url = "https://qiniu.com/index.html"; 98 | 99 | reqOpts.Headers = new StringDictionary 100 | { 101 | { "Accept", "text/plain" }, 102 | { "content-Type", "text/plain" }, 103 | { "date", "Wed, 03 Aug 2011 04:00:00 GMT" }, 104 | { "expect", "200-ok" }, 105 | { "host", "qiniu.com" }, 106 | { "if-modified-since", "Wed, 03 Aug 2011 04:00:00 GMT" }, 107 | { "referer", "https://qiniu.com/" }, 108 | { "transfer-encoding", "gzip" }, 109 | { "user-agent", "qn-csharp-sdk" }, 110 | { "X-Qiniu-A", "qn" } 111 | }; 112 | 113 | // TransferEncoding requires the SendChunked property to be set to true 114 | reqOpts.SendChunked = true; 115 | 116 | HttpWebRequest wReq = reqOpts.CreateHttpWebRequest(); 117 | Assert.AreEqual("text/plain", wReq.Accept); 118 | Assert.AreEqual("text/plain", wReq.ContentType); 119 | Assert.AreEqual(DateTime.Parse("Wed, 03 Aug 2011 04:00:00 GMT"), wReq.Date); 120 | Assert.AreEqual("200-ok", wReq.Expect); 121 | Assert.AreEqual("qiniu.com", wReq.Host); 122 | Assert.AreEqual(DateTime.Parse("Wed, 03 Aug 2011 04:00:00 GMT"), wReq.IfModifiedSince); 123 | Assert.AreEqual("https://qiniu.com/", wReq.Referer); 124 | Assert.AreEqual("gzip", wReq.TransferEncoding); 125 | Assert.AreEqual("qn-csharp-sdk", wReq.UserAgent); 126 | Assert.AreEqual("qn", wReq.Headers["X-Qiniu-A"]); 127 | 128 | wReq.Abort(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/Qiniu/Storage/PutPolicy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Qiniu.Storage 4 | { 5 | /// 6 | /// 上传策略 7 | /// 参考文档:https://developer.qiniu.com/kodo/manual/1206/put-policy 8 | /// 9 | public class PutPolicy 10 | { 11 | /// 12 | /// [必需]bucket或者bucket:key 13 | /// 14 | [JsonProperty("scope")] 15 | public string Scope { get; set; } 16 | 17 | /// 18 | /// [可选]若为 1,表示允许用户上传以 scope 的 keyPrefix 为前缀的文件。 19 | /// 20 | [JsonProperty("isPrefixalScope", NullValueHandling = NullValueHandling.Ignore)] 21 | public int? isPrefixalScope { get; set; } 22 | 23 | /// 24 | /// [必需]上传策略失效时刻,请使用SetExpire来设置它 25 | /// 26 | [JsonProperty("deadline")] 27 | public long Deadline { get; private set; } 28 | 29 | /// 30 | /// [可选]"仅新增"模式 31 | /// 32 | [JsonProperty("insertOnly", NullValueHandling = NullValueHandling.Ignore)] 33 | public int? InsertOnly { get; set; } 34 | 35 | /// 36 | /// [可选]saveKey 的优先级设置。为 true 时,saveKey不能为空,会忽略客户端指定的key,强制使用saveKey进行文件命名。 37 | /// 默认为 false 38 | /// 39 | [JsonProperty("forceSaveKey", NullValueHandling = NullValueHandling.Ignore)] 40 | public bool? ForceSaveKey { get; set; } 41 | 42 | /// 43 | /// [可选]保存文件的key 44 | /// 45 | [JsonProperty("saveKey", NullValueHandling = NullValueHandling.Ignore)] 46 | public string SaveKey { get; set; } 47 | 48 | /// 49 | /// [可选]终端用户 50 | /// 51 | [JsonProperty("endUser", NullValueHandling = NullValueHandling.Ignore)] 52 | public string EndUser { get; set; } 53 | 54 | /// 55 | /// [可选]返回URL 56 | /// 57 | [JsonProperty("returnUrl", NullValueHandling = NullValueHandling.Ignore)] 58 | public string ReturnUrl { get; set; } 59 | 60 | /// 61 | /// [可选]返回内容 62 | /// 63 | [JsonProperty("returnBody", NullValueHandling = NullValueHandling.Ignore)] 64 | public string ReturnBody { get; set; } 65 | 66 | /// 67 | /// [可选]回调URL 68 | /// 69 | [JsonProperty("callbackUrl", NullValueHandling = NullValueHandling.Ignore)] 70 | public string CallbackUrl { get; set; } 71 | 72 | /// 73 | /// [可选]回调内容 74 | /// 75 | [JsonProperty("callbackBody", NullValueHandling = NullValueHandling.Ignore)] 76 | public string CallbackBody { get; set; } 77 | 78 | /// 79 | /// [可选]回调内容类型 80 | /// 81 | [JsonProperty("callbackBodyType", NullValueHandling = NullValueHandling.Ignore)] 82 | public string CallbackBodyType { get; set; } 83 | 84 | /// 85 | /// [可选]回调host 86 | /// 87 | [JsonProperty("callbackHost", NullValueHandling = NullValueHandling.Ignore)] 88 | public string CallbackHost { get; set; } 89 | 90 | /// 91 | /// [可选]回调fetchkey 92 | /// 93 | [JsonProperty("callbackFetchKey", NullValueHandling = NullValueHandling.Ignore)] 94 | public int? CallbackFetchKey { get; set; } 95 | 96 | /// 97 | /// [可选]上传预转持久化,与 PersistentWorkflowTemplateId 二选一 98 | /// 99 | [JsonProperty("persistentOps", NullValueHandling = NullValueHandling.Ignore)] 100 | public string PersistentOps { get; set; } 101 | 102 | /// 103 | /// [可选]持久化结果通知 104 | /// 105 | [JsonProperty("persistentNotifyUrl", NullValueHandling = NullValueHandling.Ignore)] 106 | public string PersistentNotifyUrl { get; set; } 107 | 108 | /// 109 | /// [可选]私有队列 110 | /// 111 | [JsonProperty("persistentPipeline", NullValueHandling = NullValueHandling.Ignore)] 112 | public string PersistentPipeline { get; set; } 113 | 114 | /// 115 | /// [可选]持久化任务类型,为 1 时开启闲时任务 116 | /// 117 | [JsonProperty("persistentType", NullValueHandling = NullValueHandling.Ignore)] 118 | public int? PersistentType { get; set; } 119 | 120 | /// 121 | /// [可选]任务模版,与 PersistentOps 二选一 122 | /// 123 | [JsonProperty("persistentWorkflowTemplateID", NullValueHandling = NullValueHandling.Ignore)] 124 | public string PersistentWorkflowTemplateId { get; set; } 125 | 126 | 127 | /// 128 | /// [可选]上传文件大小限制:最小值,单位Byte 129 | /// 130 | [JsonProperty("fsizeMin", NullValueHandling = NullValueHandling.Ignore)] 131 | public long? FsizeMin { get; set; } 132 | 133 | /// 134 | /// [可选]上传文件大小限制:最大值,单位Byte 135 | /// 136 | [JsonProperty("fsizeLimit", NullValueHandling = NullValueHandling.Ignore)] 137 | public long? FsizeLimit { get; set; } 138 | 139 | /// 140 | /// [可选]上传时是否自动检测MIME 141 | /// 142 | [JsonProperty("detectMime", NullValueHandling = NullValueHandling.Ignore)] 143 | public int? DetectMime { get; set; } 144 | 145 | /// 146 | /// [可选]上传文件MIME限制 147 | /// 148 | [JsonProperty("mimeLimit", NullValueHandling = NullValueHandling.Ignore)] 149 | public string MimeLimit { get; set; } 150 | 151 | /// 152 | /// [可选]文件上传后多少天后自动删除 153 | /// 154 | [JsonProperty("deleteAfterDays", NullValueHandling = NullValueHandling.Ignore)] 155 | public int? DeleteAfterDays { get; set; } 156 | 157 | /// 158 | /// [可选]文件的存储类型,默认为普通存储,设置为:0 标准存储(默认),1 低频存储,2 归档存储,3 深度归档存储 159 | /// 160 | [JsonProperty("fileType", NullValueHandling = NullValueHandling.Ignore)] 161 | public int? FileType { get; set; } 162 | 163 | /// 164 | /// 设置上传凭证有效期(配置Deadline属性) 165 | /// 166 | /// 167 | public void SetExpires(int expireInSeconds) 168 | { 169 | this.Deadline = Util.UnixTimestamp.GetUnixTimestamp(expireInSeconds); 170 | } 171 | 172 | public void SetExpires(long expireInSeconds) 173 | { 174 | this.Deadline = Util.UnixTimestamp.GetUnixTimestamp(expireInSeconds); 175 | } 176 | 177 | /// 178 | /// 转换为JSON字符串 179 | /// 180 | /// JSON字符串 181 | public string ToJsonString() 182 | { 183 | if (this.Deadline == 0) 184 | { 185 | //默认一个小时有效期 186 | this.SetExpires(3600); 187 | } 188 | return JsonConvert.SerializeObject(this); 189 | } 190 | 191 | } 192 | } 193 | --------------------------------------------------------------------------------