-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathFlashTransferContext.cs
More file actions
137 lines (120 loc) · 5.04 KB
/
FlashTransferContext.cs
File metadata and controls
137 lines (120 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System.Security.Cryptography;
using Lagrange.Core.Internal.Packets.Service;
using Lagrange.Core.Utility;
using Lagrange.Core.Utility.Cryptography;
using Lagrange.Core.Utility.Extension;
namespace Lagrange.Core.Internal.Context;
public class FlashTransferContext
{
private const string Tag = nameof(FlashTransferContext);
private readonly BotContext _botContext;
private readonly HttpClient _client;
private readonly string? _url;
private const uint ChunkSize = 1024 * 1024;
internal FlashTransferContext(BotContext botContext)
{
_botContext = botContext;
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
_url = "https://multimedia.qfile.qq.com/sliceupload";
}
public async Task<bool> UploadFile(string uKey, uint appId, Stream bodyStream)
{
return await UploadFile(uKey, appId, 2, bodyStream, null, _url);
}
public async Task<bool> UploadFile(string uploadToken, string uploadHost, uint appId, uint uploadIndex, string fileSetId, string fileId, uint bindingStage, uint fileType, uint bindingField5, uint bindingField6, Stream bodyStream)
{
var binding = new FlashTransferUploadFileBinding
{
FileSetId = fileSetId,
FileSetIdDup = fileSetId,
FileId = fileId,
Stage = bindingStage,
Field5 = bindingField5,
Field6 = bindingField6,
FileType = fileType,
FileIdDup = fileId,
};
return await UploadFile(uploadToken, appId, uploadIndex, bodyStream, binding, $"https://{uploadHost}/sliceupload");
}
private async Task<bool> UploadFile(string uKey, uint appId, uint uploadIndex, Stream bodyStream, FlashTransferUploadFileBinding? binding, string? url)
{
var sha1StateVs = new FlashTransferSha1StateV { State = [] };
var chunkCount = (uint)((bodyStream.Length + ChunkSize - 1) / ChunkSize);
var sha1Stream = new Sha1Stream();
for (uint i = 0; i < chunkCount; i++)
{
if (i != chunkCount - 1)
{
var accLength = (int)((i + 1) * ChunkSize);
var accBuffer = new byte[accLength];
bodyStream.Position = 0;
await bodyStream.ReadExactlyAsync(accBuffer, 0, accLength);
var accSpan = accBuffer.AsSpan();
var digest = new byte[20];
sha1Stream.Update(accSpan);
sha1Stream.Hash(digest, false);
sha1Stream.Reset();
sha1StateVs.State.Add(digest.ToArray());
}
else
{
bodyStream.Position = 0;
sha1StateVs.State.Add(bodyStream.Sha1());
}
}
for (uint i = 0; i < chunkCount; i++)
{
var chunkStart = (long)(i * ChunkSize);
var chunkLength = (int)Math.Min(ChunkSize, bodyStream.Length - chunkStart);
bodyStream.Position = chunkStart;
var uploadBuffer = new byte[chunkLength];
await bodyStream.ReadExactlyAsync(uploadBuffer, 0, chunkLength);
var success = await UploadChunk(uKey, appId, uploadIndex, (uint)chunkStart, sha1StateVs, uploadBuffer, binding, url);
if (!success) return false;
}
return true;
}
private async Task<bool> UploadChunk(string uKey, uint appId, uint uploadIndex, uint start, FlashTransferSha1StateV chunkSha1S, byte[] body, FlashTransferUploadFileBinding? binding, string? url)
{
byte[] chunkSha1 = SHA1.HashData(body);
var req = new FlashTransferUploadReq
{
FileId = 0,
AppId = appId,
UploadIndex = uploadIndex,
Body = new FlashTransferUploadBody
{
FileId = [],
UKey = uKey,
Start = start,
End = (uint)(start + body.Length - 1),
Sha1 = chunkSha1,
Sha1StateV = binding == null ? chunkSha1S : new FlashTransferSha1StateV { State = [chunkSha1] },
Body = body,
FileBinding = binding
}
};
var payload = ProtoHelper.Serialize(req).ToArray();
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Headers =
{
{ "Accept", "*/*" },
{ "Expect", "100-continue" },
{ "Connection", "Keep-Alive" }
},
Content = new ByteArrayContent(payload)
};
var response = await _client.SendAsync(request);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
var resp = ProtoHelper.Deserialize<FlashTransferUploadResp>(responseBytes);
if (resp.Status != "success")
{
_botContext.LogError(Tag,
$"FlashTransfer Upload chunk {start} failed: {resp.Status} appId: {appId}, uploadIndex: {uploadIndex}, keyLength: {uKey?.Length ?? 0}");
return false;
}
return true;
}
}