Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/mediafire/util.go
1987 views
1
package mediafire
2
3
/*
4
Package mediafire
5
Author: Da3zKi7<[email protected]>
6
Date: 2025-09-11
7
8
D@' 3z K!7 - The King Of Cracking
9
*/
10
11
import (
12
"bytes"
13
"context"
14
"crypto/sha256"
15
"encoding/hex"
16
"encoding/json"
17
"fmt"
18
"io"
19
"net/http"
20
"os"
21
"strconv"
22
"strings"
23
"time"
24
25
"github.com/alist-org/alist/v3/drivers/base"
26
"github.com/alist-org/alist/v3/internal/driver"
27
"github.com/alist-org/alist/v3/internal/model"
28
"github.com/alist-org/alist/v3/internal/op"
29
"github.com/alist-org/alist/v3/pkg/utils"
30
)
31
32
func (d *Mediafire) getSessionToken(ctx context.Context) (string, error) {
33
tokenURL := d.hostBase + "/application/get_session_token.php"
34
35
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, nil)
36
if err != nil {
37
return "", err
38
}
39
40
req.Header.Set("Accept", "*/*")
41
req.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
42
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
43
req.Header.Set("Content-Length", "0")
44
req.Header.Set("Cookie", d.Cookie)
45
req.Header.Set("DNT", "1")
46
req.Header.Set("Origin", d.hostBase)
47
req.Header.Set("Priority", "u=1, i")
48
req.Header.Set("Referer", (d.hostBase + "/"))
49
req.Header.Set("Sec-Ch-Ua", d.secChUa)
50
req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
51
req.Header.Set("Sec-Ch-Ua-Platform", d.secChUaPlatform)
52
req.Header.Set("Sec-Fetch-Dest", "empty")
53
req.Header.Set("Sec-Fetch-Mode", "cors")
54
req.Header.Set("Sec-Fetch-Site", "same-site")
55
req.Header.Set("User-Agent", d.userAgent)
56
//req.Header.Set("Connection", "keep-alive")
57
58
resp, err := base.HttpClient.Do(req)
59
if err != nil {
60
return "", err
61
}
62
defer resp.Body.Close()
63
64
body, err := io.ReadAll(resp.Body)
65
if err != nil {
66
return "", err
67
}
68
69
//fmt.Printf("getSessionToken :: Raw response: %s\n", string(body))
70
//fmt.Printf("getSessionToken :: Parsed response: %+v\n", resp)
71
72
var tokenResp struct {
73
Response struct {
74
SessionToken string `json:"session_token"`
75
} `json:"response"`
76
}
77
78
if resp.StatusCode == 200 {
79
if err := json.Unmarshal(body, &tokenResp); err != nil {
80
return "", err
81
}
82
83
if tokenResp.Response.SessionToken == "" {
84
return "", fmt.Errorf("empty session token received")
85
}
86
87
cookieMap := make(map[string]string)
88
for _, cookie := range resp.Cookies() {
89
cookieMap[cookie.Name] = cookie.Value
90
}
91
92
if len(cookieMap) > 0 {
93
94
var cookies []string
95
for name, value := range cookieMap {
96
cookies = append(cookies, fmt.Sprintf("%s=%s", name, value))
97
}
98
d.Cookie = strings.Join(cookies, "; ")
99
op.MustSaveDriverStorage(d)
100
101
//fmt.Printf("getSessionToken :: Captured cookies: %s\n", d.Cookie)
102
}
103
104
} else {
105
return "", fmt.Errorf("getSessionToken :: failed to get session token, status code: %d", resp.StatusCode)
106
}
107
108
d.SessionToken = tokenResp.Response.SessionToken
109
110
//fmt.Printf("Init :: Obtain Session Token %v", d.SessionToken)
111
112
op.MustSaveDriverStorage(d)
113
114
return d.SessionToken, nil
115
}
116
117
func (d *Mediafire) renewToken(_ context.Context) error {
118
query := map[string]string{
119
"session_token": d.SessionToken,
120
"response_format": "json",
121
}
122
123
var resp MediafireRenewTokenResponse
124
_, err := d.postForm("/user/renew_session_token.php", query, &resp)
125
if err != nil {
126
return fmt.Errorf("failed to renew token: %w", err)
127
}
128
129
//fmt.Printf("getInfo :: Raw response: %s\n", string(body))
130
//fmt.Printf("getInfo :: Parsed response: %+v\n", resp)
131
132
if resp.Response.Result != "Success" {
133
return fmt.Errorf("MediaFire token renewal failed: %s", resp.Response.Result)
134
}
135
136
d.SessionToken = resp.Response.SessionToken
137
138
//fmt.Printf("Init :: Renew Session Token: %s", resp.Response.Result)
139
140
op.MustSaveDriverStorage(d)
141
142
return nil
143
}
144
145
func (d *Mediafire) getFiles(ctx context.Context, folderKey string) ([]File, error) {
146
files := make([]File, 0)
147
hasMore := true
148
chunkNumber := 1
149
150
for hasMore {
151
resp, err := d.getFolderContent(ctx, folderKey, chunkNumber)
152
if err != nil {
153
return nil, err
154
}
155
156
for _, folder := range resp.Folders {
157
files = append(files, File{
158
ID: folder.FolderKey,
159
Name: folder.Name,
160
Size: 0,
161
CreatedUTC: folder.CreatedUTC,
162
IsFolder: true,
163
})
164
}
165
166
for _, file := range resp.Files {
167
size, _ := strconv.ParseInt(file.Size, 10, 64)
168
files = append(files, File{
169
ID: file.QuickKey,
170
Name: file.Filename,
171
Size: size,
172
CreatedUTC: file.CreatedUTC,
173
IsFolder: false,
174
})
175
}
176
177
hasMore = resp.MoreChunks
178
chunkNumber++
179
}
180
181
return files, nil
182
}
183
184
func (d *Mediafire) getFolderContent(ctx context.Context, folderKey string, chunkNumber int) (*FolderContentResponse, error) {
185
186
foldersResp, err := d.getFolderContentByType(ctx, folderKey, "folders", chunkNumber)
187
if err != nil {
188
return nil, err
189
}
190
191
filesResp, err := d.getFolderContentByType(ctx, folderKey, "files", chunkNumber)
192
if err != nil {
193
return nil, err
194
}
195
196
return &FolderContentResponse{
197
Folders: foldersResp.Response.FolderContent.Folders,
198
Files: filesResp.Response.FolderContent.Files,
199
MoreChunks: foldersResp.Response.FolderContent.MoreChunks == "yes" || filesResp.Response.FolderContent.MoreChunks == "yes",
200
}, nil
201
}
202
203
func (d *Mediafire) getFolderContentByType(_ context.Context, folderKey, contentType string, chunkNumber int) (*MediafireResponse, error) {
204
data := map[string]string{
205
"session_token": d.SessionToken,
206
"response_format": "json",
207
"folder_key": folderKey,
208
"content_type": contentType,
209
"chunk": strconv.Itoa(chunkNumber),
210
"chunk_size": strconv.FormatInt(d.ChunkSize, 10),
211
"details": "yes",
212
"order_direction": d.OrderDirection,
213
"order_by": d.OrderBy,
214
"filter": "",
215
}
216
217
var resp MediafireResponse
218
_, err := d.postForm("/folder/get_content.php", data, &resp)
219
if err != nil {
220
return nil, err
221
}
222
223
if resp.Response.Result != "Success" {
224
return nil, fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
225
}
226
227
return &resp, nil
228
}
229
230
func (d *Mediafire) fileToObj(f File) *model.ObjThumb {
231
created, _ := time.Parse("2006-01-02T15:04:05Z", f.CreatedUTC)
232
233
var thumbnailURL string
234
if !f.IsFolder && f.ID != "" {
235
thumbnailURL = d.hostBase + "/convkey/acaa/" + f.ID + "3g.jpg"
236
}
237
238
return &model.ObjThumb{
239
Object: model.Object{
240
ID: f.ID,
241
//Path: "",
242
Name: f.Name,
243
Size: f.Size,
244
Modified: created,
245
Ctime: created,
246
IsFolder: f.IsFolder,
247
},
248
Thumbnail: model.Thumbnail{
249
Thumbnail: thumbnailURL,
250
},
251
}
252
}
253
254
func (d *Mediafire) getForm(endpoint string, query map[string]string, resp interface{}) ([]byte, error) {
255
req := base.RestyClient.R()
256
257
req.SetQueryParams(query)
258
259
req.SetHeaders(map[string]string{
260
"Cookie": d.Cookie,
261
//"User-Agent": base.UserAgent,
262
"User-Agent": d.userAgent,
263
"Origin": d.appBase,
264
"Referer": d.appBase + "/",
265
})
266
267
// If response OK
268
if resp != nil {
269
req.SetResult(resp)
270
}
271
272
// Targets MediaFire API
273
res, err := req.Get(d.apiBase + endpoint)
274
if err != nil {
275
return nil, err
276
}
277
278
return res.Body(), nil
279
}
280
281
func (d *Mediafire) postForm(endpoint string, data map[string]string, resp interface{}) ([]byte, error) {
282
req := base.RestyClient.R()
283
284
req.SetFormData(data)
285
286
req.SetHeaders(map[string]string{
287
"Cookie": d.Cookie,
288
"Content-Type": "application/x-www-form-urlencoded",
289
//"User-Agent": base.UserAgent,
290
"User-Agent": d.userAgent,
291
"Origin": d.appBase,
292
"Referer": d.appBase + "/",
293
})
294
295
// If response OK
296
if resp != nil {
297
req.SetResult(resp)
298
}
299
300
// Targets MediaFire API
301
res, err := req.Post(d.apiBase + endpoint)
302
if err != nil {
303
return nil, err
304
}
305
306
return res.Body(), nil
307
}
308
309
func (d *Mediafire) getDirectDownloadLink(_ context.Context, fileID string) (string, error) {
310
data := map[string]string{
311
"session_token": d.SessionToken,
312
"quick_key": fileID,
313
"link_type": "direct_download",
314
"response_format": "json",
315
}
316
317
var resp MediafireDirectDownloadResponse
318
_, err := d.getForm("/file/get_links.php", data, &resp)
319
if err != nil {
320
return "", err
321
}
322
323
if resp.Response.Result != "Success" {
324
return "", fmt.Errorf("MediaFire API error: %s", resp.Response.Result)
325
}
326
327
if len(resp.Response.Links) == 0 {
328
return "", fmt.Errorf("no download links found")
329
}
330
331
return resp.Response.Links[0].DirectDownload, nil
332
}
333
334
func (d *Mediafire) calculateSHA256(file *os.File) (string, error) {
335
hasher := sha256.New()
336
if _, err := file.Seek(0, 0); err != nil {
337
return "", err
338
}
339
if _, err := io.Copy(hasher, file); err != nil {
340
return "", err
341
}
342
return hex.EncodeToString(hasher.Sum(nil)), nil
343
}
344
345
func (d *Mediafire) uploadCheck(ctx context.Context, filename string, filesize int64, filehash, folderKey string) (*MediafireCheckResponse, error) {
346
347
actionToken, err := d.getActionToken(ctx)
348
if err != nil {
349
return nil, fmt.Errorf("failed to get action token: %w", err)
350
}
351
352
query := map[string]string{
353
"session_token": actionToken, /* d.SessionToken */
354
"filename": filename,
355
"size": strconv.FormatInt(filesize, 10),
356
"hash": filehash,
357
"folder_key": folderKey,
358
"resumable": "yes",
359
"response_format": "json",
360
}
361
362
var resp MediafireCheckResponse
363
_, err = d.postForm("/upload/check.php", query, &resp)
364
if err != nil {
365
return nil, err
366
}
367
368
//fmt.Printf("uploadCheck :: Raw response: %s\n", string(body))
369
//fmt.Printf("uploadCheck :: Parsed response: %+v\n", resp)
370
371
//fmt.Printf("uploadCheck :: ResumableUpload section: %+v\n", resp.Response.ResumableUpload)
372
//fmt.Printf("uploadCheck :: Upload key specifically: '%s'\n", resp.Response.ResumableUpload.UploadKey)
373
374
if resp.Response.Result != "Success" {
375
return nil, fmt.Errorf("MediaFire upload check failed: %s", resp.Response.Result)
376
}
377
378
return &resp, nil
379
}
380
381
func (d *Mediafire) resumableUpload(ctx context.Context, folderKey, uploadKey string, unitData []byte, unitID int, fileHash, filename string, totalFileSize int64) (string, error) {
382
actionToken, err := d.getActionToken(ctx)
383
if err != nil {
384
return "", err
385
}
386
387
url := d.apiBase + "/upload/resumable.php"
388
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(unitData))
389
if err != nil {
390
return "", err
391
}
392
393
q := req.URL.Query()
394
q.Add("folder_key", folderKey)
395
q.Add("response_format", "json")
396
q.Add("session_token", actionToken)
397
q.Add("key", uploadKey)
398
req.URL.RawQuery = q.Encode()
399
400
req.Header.Set("x-filehash", fileHash)
401
req.Header.Set("x-filesize", strconv.FormatInt(totalFileSize, 10))
402
req.Header.Set("x-unit-id", strconv.Itoa(unitID))
403
req.Header.Set("x-unit-size", strconv.FormatInt(int64(len(unitData)), 10))
404
req.Header.Set("x-unit-hash", d.sha256Hex(bytes.NewReader(unitData)))
405
req.Header.Set("x-filename", filename)
406
req.Header.Set("Content-Type", "application/octet-stream")
407
req.ContentLength = int64(len(unitData))
408
409
/* fmt.Printf("Debug resumable upload request:\n")
410
fmt.Printf(" URL: %s\n", req.URL.String())
411
fmt.Printf(" Headers: %+v\n", req.Header)
412
fmt.Printf(" Unit ID: %d\n", unitID)
413
fmt.Printf(" Unit Size: %d\n", len(unitData))
414
fmt.Printf(" Upload Key: %s\n", uploadKey)
415
fmt.Printf(" Action Token: %s\n", actionToken) */
416
417
res, err := base.HttpClient.Do(req)
418
if err != nil {
419
return "", err
420
}
421
defer res.Body.Close()
422
423
body, err := io.ReadAll(res.Body)
424
if err != nil {
425
return "", fmt.Errorf("failed to read response body: %v", err)
426
}
427
428
//fmt.Printf("MediaFire resumable upload response (status %d): %s\n", res.StatusCode, string(body))
429
430
var uploadResp struct {
431
Response struct {
432
Doupload struct {
433
Key string `json:"key"`
434
} `json:"doupload"`
435
Result string `json:"result"`
436
} `json:"response"`
437
}
438
439
if err := json.Unmarshal(body, &uploadResp); err != nil {
440
return "", fmt.Errorf("failed to parse response: %v", err)
441
}
442
443
if res.StatusCode != 200 {
444
return "", fmt.Errorf("resumable upload failed with status %d", res.StatusCode)
445
}
446
447
return uploadResp.Response.Doupload.Key, nil
448
}
449
450
func (d *Mediafire) uploadUnits(ctx context.Context, file *os.File, checkResp *MediafireCheckResponse, filename, fileHash, folderKey string, up driver.UpdateProgress) (string, error) {
451
unitSize, _ := strconv.ParseInt(checkResp.Response.ResumableUpload.UnitSize, 10, 64)
452
numUnits, _ := strconv.Atoi(checkResp.Response.ResumableUpload.NumberOfUnits)
453
uploadKey := checkResp.Response.ResumableUpload.UploadKey
454
455
stringWords := checkResp.Response.ResumableUpload.Bitmap.Words
456
intWords := make([]int, len(stringWords))
457
for i, word := range stringWords {
458
intWords[i], _ = strconv.Atoi(word)
459
}
460
461
var finalUploadKey string
462
463
for unitID := 0; unitID < numUnits; unitID++ {
464
465
if utils.IsCanceled(ctx) {
466
return "", ctx.Err()
467
}
468
469
if d.isUnitUploaded(intWords, unitID) {
470
up(float64(unitID+1) * 100 / float64(numUnits))
471
continue
472
}
473
474
uploadKey, err := d.uploadSingleUnit(ctx, file, unitID, unitSize, fileHash, filename, uploadKey, folderKey)
475
if err != nil {
476
return "", err
477
}
478
479
finalUploadKey = uploadKey
480
481
up(float64(unitID+1) * 100 / float64(numUnits))
482
}
483
484
return finalUploadKey, nil
485
}
486
487
func (d *Mediafire) uploadSingleUnit(ctx context.Context, file *os.File, unitID int, unitSize int64, fileHash, filename, uploadKey, folderKey string) (string, error) {
488
start := int64(unitID) * unitSize
489
size := unitSize
490
491
stat, err := file.Stat()
492
if err != nil {
493
return "", err
494
}
495
fileSize := stat.Size()
496
497
if start+size > fileSize {
498
size = fileSize - start
499
}
500
501
unitData := make([]byte, size)
502
if _, err := file.ReadAt(unitData, start); err != nil {
503
return "", err
504
}
505
506
return d.resumableUpload(ctx, folderKey, uploadKey, unitData, unitID, fileHash, filename, fileSize)
507
}
508
509
func (d *Mediafire) getActionToken(_ context.Context) (string, error) {
510
511
if d.actionToken != "" {
512
return d.actionToken, nil
513
}
514
515
data := map[string]string{
516
"type": "upload",
517
"lifespan": "1440",
518
"response_format": "json",
519
"session_token": d.SessionToken,
520
}
521
522
var resp MediafireActionTokenResponse
523
_, err := d.postForm("/user/get_action_token.php", data, &resp)
524
if err != nil {
525
return "", err
526
}
527
528
if resp.Response.Result != "Success" {
529
return "", fmt.Errorf("MediaFire action token failed: %s", resp.Response.Result)
530
}
531
532
return resp.Response.ActionToken, nil
533
}
534
535
func (d *Mediafire) pollUpload(ctx context.Context, key string) (*MediafirePollResponse, error) {
536
537
actionToken, err := d.getActionToken(ctx)
538
if err != nil {
539
return nil, fmt.Errorf("failed to get action token: %w", err)
540
}
541
542
//fmt.Printf("Debug Key: %+v\n", key)
543
544
query := map[string]string{
545
"key": key,
546
"response_format": "json",
547
"session_token": actionToken, /* d.SessionToken */
548
}
549
550
var resp MediafirePollResponse
551
_, err = d.postForm("/upload/poll_upload.php", query, &resp)
552
if err != nil {
553
return nil, err
554
}
555
556
//fmt.Printf("pollUpload :: Raw response: %s\n", string(body))
557
//fmt.Printf("pollUpload :: Parsed response: %+v\n", resp)
558
559
//fmt.Printf("pollUpload :: Debug Result: %+v\n", resp.Response.Result)
560
561
if resp.Response.Result != "Success" {
562
return nil, fmt.Errorf("MediaFire poll upload failed: %s", resp.Response.Result)
563
}
564
565
return &resp, nil
566
}
567
568
func (d *Mediafire) sha256Hex(r io.Reader) string {
569
h := sha256.New()
570
io.Copy(h, r)
571
return hex.EncodeToString(h.Sum(nil))
572
}
573
574
func (d *Mediafire) isUnitUploaded(words []int, unitID int) bool {
575
wordIndex := unitID / 16
576
bitIndex := unitID % 16
577
if wordIndex >= len(words) {
578
return false
579
}
580
return (words[wordIndex]>>bitIndex)&1 == 1
581
}
582
583
func (d *Mediafire) getExistingFileInfo(ctx context.Context, fileHash, filename, folderKey string) (*model.ObjThumb, error) {
584
585
if fileInfo, err := d.getFileByHash(ctx, fileHash); err == nil && fileInfo != nil {
586
return fileInfo, nil
587
}
588
589
files, err := d.getFiles(ctx, folderKey)
590
if err != nil {
591
return nil, err
592
}
593
594
for _, file := range files {
595
if file.Name == filename && !file.IsFolder {
596
return d.fileToObj(file), nil
597
}
598
}
599
600
return nil, fmt.Errorf("existing file not found")
601
}
602
603
func (d *Mediafire) getFileByHash(_ context.Context, hash string) (*model.ObjThumb, error) {
604
query := map[string]string{
605
"session_token": d.SessionToken,
606
"response_format": "json",
607
"hash": hash,
608
}
609
610
var resp MediafireFileSearchResponse
611
_, err := d.postForm("/file/get_info.php", query, &resp)
612
if err != nil {
613
return nil, err
614
}
615
616
if resp.Response.Result != "Success" {
617
return nil, fmt.Errorf("MediaFire file search failed: %s", resp.Response.Result)
618
}
619
620
if len(resp.Response.FileInfo) == 0 {
621
return nil, fmt.Errorf("file not found by hash")
622
}
623
624
file := resp.Response.FileInfo[0]
625
return d.fileToObj(file), nil
626
}
627
628