Compare commits

...

77 Commits

Author SHA1 Message Date
cc62cc99d2 🐛 fix plist ipa name 2022-02-18 19:01:30 +08:00
270349f37c 🐛 fix write status sequence 2022-02-18 18:58:02 +08:00
977888070a 🐛 close #558 fix local file can't download 2022-02-18 18:50:01 +08:00
192d0f2bf3 🐛 fix update can't start 2022-02-18 18:49:40 +08:00
f2ec7884ec 🐛 fix only proxy webdav_direct 2022-02-17 17:49:03 +08:00
815975a4d2 🐛 fix 139 delete dir 2022-02-17 17:37:45 +08:00
f96a0238fc 👷 change issues-month-statistics 2022-02-17 17:29:45 +08:00
f695bd0959 🔧 change bundle-version 2022-02-17 16:54:34 +08:00
efe8f46e17 ✏️ fix typo 2022-02-17 11:56:00 +08:00
515daa22a9 🐛 fix #551 add S3ForcePathStyle config 2022-02-17 11:30:34 +08:00
f11e22deaf 🚧 change base64 characters 2022-02-17 09:14:21 +08:00
5be976169f 🚧 fix bundle-identifier 2022-02-17 00:44:30 +08:00
a6e08f3bf4 📝 update readme 2022-02-17 00:08:37 +08:00
944e68a979 🚧 plist generate 2022-02-17 00:06:10 +08:00
b3a6e33ce1 🎇 quark support 2022-02-16 20:20:39 +08:00
cb53ddc8e8 🐛 fix nil pointer 2022-02-16 16:10:39 +08:00
693417be4f 🔧 add hide files setting item 2022-02-15 14:51:50 +08:00
5c3f91bb55 support empty password 2022-02-15 14:42:24 +08:00
8a219d0732 👷 add issue bot 2022-02-14 20:03:42 +08:00
146a544af3 ✏️ fix typo 2022-02-14 19:43:43 +08:00
48dccc6c0b 📝 update readme 2022-02-14 15:41:20 +08:00
ce1740cec4 ✏️ fix typo 2022-02-14 15:36:21 +08:00
d40dbeae3e 💬 add issue template 2022-02-14 15:33:48 +08:00
5094b673c4 👷 add some issue bot 2022-02-14 15:32:25 +08:00
228e6d10e7 🔧 close #519 customize temp dir 2022-02-14 15:06:57 +08:00
e90b979d15 close #535 request set timeout 2022-02-14 14:59:00 +08:00
fb05a6ca48 🐛 fix #533 only encode fileName 2022-02-14 14:31:09 +08:00
e055ed3afa 🎇 lanzou proxy add user-agent 2022-02-13 17:46:07 +08:00
4371c470b3 webdav direct proxy 2022-02-13 15:57:42 +08:00
7bb237d0ef 🐛 fix #527 189 upload file name contains % 2022-02-13 13:03:34 +08:00
5c42354b01 🐛 fix #527 189 upload name contains + 2022-02-12 20:27:38 +08:00
387e8af422 🐛 fix 189 upload while filename contains & 2022-02-12 13:08:46 +08:00
5dca777caf 🔒 fix baidu direct link 2022-02-12 12:04:10 +08:00
0814778a14 🐛 fix no account error while only one 2022-02-11 17:11:02 +08:00
6827af3997 🎇 close #522 hide account for guest webdav 2022-02-11 16:19:55 +08:00
435bdea8f7 🔧 #523 add some default setting 2022-02-11 16:17:07 +08:00
4f81735af6 🎇 close #512 favicon redirect 2022-02-08 18:07:13 +08:00
bef3d2f88d 🐛 Fix the temp folder is not created at the first startup 2022-02-08 16:22:53 +08:00
ba99c7dc03 🐛 fix multiple accounts with the same prefix cannot be load balanced 2022-02-08 16:02:47 +08:00
f5c5162a9b load balance 2022-02-08 15:51:58 +08:00
a22903533e 🐛 set random seed 2022-02-04 16:08:15 +08:00
86cda58b22 🚧 echo password 2022-02-04 14:58:48 +08:00
7804cf9d5c 🔒 random webdav admin password 2022-02-04 14:39:11 +08:00
2bb7036110 🔧 change logo 2022-02-03 21:28:13 +08:00
ba545555cf 📝 update readme 2022-02-03 21:00:08 +08:00
be55ca690c 🔧 change default logo 2022-02-03 20:43:33 +08:00
9013add749 🔒 random initial password 2022-02-03 12:27:50 +08:00
3201b6da76 🐛 fix #376 windows webdav upload 2022-02-02 18:48:34 +08:00
feb42f1f4b 🔥 delete proxy interface 2022-02-02 18:03:54 +08:00
6f14d0eb5c 🎇 baidu disk support 2022-02-02 17:32:11 +08:00
7530d8f5b2 🐛 fix lanzou for download change 2022-02-01 22:33:19 +08:00
e25fe05a53 yandex disk support 2022-02-01 17:15:11 +08:00
8e0ab8f780 🔥 remove onedrive refresh token cron 2022-02-01 15:20:17 +08:00
cb2a3c2b42 🎨 change proxy interface 2022-02-01 14:28:21 +08:00
1b6ec94f33 🐛 fix s3 custom host 2022-02-01 11:34:32 +08:00
cb23edc1fe 🐛 fix #462 check connect while get ftp client 2022-01-31 11:13:29 +08:00
6fd05d7d72 🐛 fix connMap not init 2022-01-30 00:55:12 +08:00
f26ac57569 🐛 fix ftp conn not store 2022-01-30 00:04:31 +08:00
2434ac54d0 🐛 fix webdav 2022-01-29 18:36:22 +08:00
f25b557327 📝 update readme 2022-01-29 15:31:06 +08:00
81a0706d01 189 chunk upload 2022-01-29 11:16:40 +08:00
5f6b576cbf 🔧 switch cdn to jsdelivr 2022-01-28 23:29:38 +08:00
549877f71e 🔥 Delete Text 2022-01-28 23:21:11 +08:00
c6a5ba9b91 🚧 189 chunk upload 2022-01-28 19:51:54 +08:00
1a69d80489 🎨 Pull away Path 2022-01-28 11:04:56 +08:00
b797f4302c 🎇 use tempFile to cal md5 2022-01-27 23:48:29 +08:00
bf9aa5c3d3 🔒 not allowed use relative path of native 2022-01-27 15:10:33 +08:00
7390e19a7a 🔒 not allowed down with relative path 2022-01-27 15:05:17 +08:00
b31a12a0cc 🔒 not allowed access using relative path for native 2022-01-27 14:54:20 +08:00
26ce001782 🎇 add ocr for 189 2022-01-27 12:34:49 +08:00
a2c7ff3262 ✏️ Invalid Token 2022-01-26 16:27:38 +08:00
8fc7c716c0 copy api 2022-01-26 14:07:51 +08:00
c70fc3fc4b finish teambition chunk upload 2022-01-26 14:05:35 +08:00
df513b7dc0 teambition upload (<= 20 MB) 2022-01-23 14:03:04 +08:00
2a9598f4c6 🐛 fix #407 189cloud use local sort 2022-01-21 19:01:33 +08:00
224c20779c 🔧 add webp to image types 2022-01-20 23:01:59 +08:00
5d722298cb 📝 update readme 2022-01-20 22:38:53 +08:00
80 changed files with 3557 additions and 626 deletions

View File

@ -5,7 +5,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Thanks for taking the time to fill out this bug report! Thanks for taking the time to fill out this bug report, please confirm that your issue is not a duplicate issue
- type: input - type: input
id: version id: version
attributes: attributes:

View File

@ -1,5 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Questions & Discussions & Feature request - name: Questions & Discussions
url: https://github.com/Xhofe/alist/discussions url: https://github.com/Xhofe/alist/discussions
about: Use GitHub discussions for message-board style questions and discussions or feature request. about: Use GitHub discussions for message-board style questions and discussions.

View File

@ -0,0 +1,24 @@
name: "Feature request"
description: Feature request
labels: ["enhancement: pending triage"]
body:
- type: textarea
id: feature-description
attributes:
label: Description of the feature / 需求描述
validations:
required: true
- type: textarea
id: suggested-solution
attributes:
label: Suggested solution / 实现思路
description: |
Solutions to achieve this requirement.
实现此需求的解决思路。
- type: textarea
id: additional-context
attributes:
label: Additional context / 附件
description: |
Any other context or screenshots about the feature request here, or information you find helpful.
相关的任何其他上下文或截图,或者你觉得有帮助的信息

View File

@ -0,0 +1,20 @@
name: Check need info
on:
schedule:
- cron: "0 0 */7 * *"
jobs:
check-need-info:
runs-on: ubuntu-latest
steps:
- name: close-issues
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issues'
token: ${{ secrets.GITHUB_TOKEN }}
labels: 'question'
inactive-day: 7
body: |
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 7 days.
你好 @${{ github.event.issue.user.login }}此issue因超过7天未回复被关闭。

25
.github/workflows/issue_duplicate.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Issue Duplicate
on:
issues:
types: [labeled]
jobs:
create-comment:
runs-on: ubuntu-latest
if: github.event.label.name == 'duplicate'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, your issue is a duplicate and will be closed.
你好 @${{ github.event.issue.user.login }}你的issue是重复的将被关闭。
- name: Close issue
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issue'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,16 @@
name: Issue Month Statistics
on:
schedule:
- cron: "0 1 1 * *"
jobs:
month-statistics:
runs-on: ubuntu-latest
steps:
- name: month-statistics
uses: actions-cool/issues-month-statistics@v1
with:
count-lables: true
count-comments: true
emoji: 'eyes'

20
.github/workflows/issue_question.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Issue Question
on:
issues:
types: [labeled]
jobs:
create-comment:
runs-on: ubuntu-latest
if: github.event.label.name == 'question'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2.0.0
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
你好 @${{ github.event.issue.user.login }}请按照issue模板填写, 并详细说明问题/复现步骤/实现思路或提供更多信息等, 7天内未回复issue自动关闭。

25
.github/workflows/issue_wontfix.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Issue Wontfix
on:
issues:
types: [labeled]
jobs:
lock-issue:
runs-on: ubuntu-latest
if: github.event.label.name == 'wontfix'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v2
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
Hello @${{ github.event.issue.user.login }}, this issue will not be worked on and will be closed.
你好 @${{ github.event.issue.user.login }},这不会被处理,将被关闭。
- name: Close issue
uses: actions-cool/issues-helper@v2
with:
actions: 'close-issue'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3> <a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
<p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p> <p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a> <a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -12,6 +12,7 @@
</div> </div>
--- ---
English | [中文](./README_cn.md) English | [中文](./README_cn.md)
@ -35,6 +36,9 @@ English | [中文](./README_cn.md)
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ )) - [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/) - [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family) - [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [Baidu Disk](http://pan.baidu.com/)
- [x] [Quark](https://pan.quark.cn)
- [x] Easy to deploy and out-of-the-box - [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...) - [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode - [x] Image preview in gallery mode
@ -50,11 +54,11 @@ English | [中文](./README_cn.md)
- [x] Cloudflare workers proxy - [x] Cloudflare workers proxy
- [x] File/Folder package download - [x] File/Folder package download
- [x] Support video list playback and subtitles(ass,srt,vtt) - [x] Support video list playback and subtitles(ass,srt,vtt)
- [x] Web upload(Can allow visitors to upload) and delete - [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
## Discussion ## Discussion
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.** Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
## Demo ## Demo

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3> <a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/assets@main/logo.svg"/></a>
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p> <p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a> <a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a> <a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
@ -11,6 +11,7 @@
</a> </a>
</div> </div>
--- ---
[English](./README.md) | 中文 [English](./README.md) | 中文
@ -34,6 +35,9 @@
- [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ ) - [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ )
- [x] [分秒帧](https://www.mediatrack.cn/) - [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云) - [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度网盘](http://pan.baidu.com/)
- [x] [夸克网盘](https://pan.quark.cn)
- [x] 部署方便,开箱即用 - [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本…… - [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览 - [x] 画廊模式下的图像预览
@ -49,11 +53,11 @@
- [x] Cloudflare workers 中转 - [x] Cloudflare workers 中转
- [x] 文件/文件夹打包下载 - [x] 文件/文件夹打包下载
- [x] 支持视频列表播放和字幕(ass,srt,vtt) - [x] 支持视频列表播放和字幕(ass,srt,vtt)
- [x] 网页上传(可以允许访客上传),删除 - [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
## 讨论 ## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告。** 一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告和功能请求。**
## 演示 ## 演示

View File

@ -22,7 +22,7 @@ func Init() bool {
log.Errorf(err.Error()) log.Errorf(err.Error())
return false return false
} }
log.Infof("current password: %s", pass.Value) fmt.Printf("your password: %s\n", pass.Value)
return false return false
} }
server.InitIndex() server.InitIndex()

View File

@ -19,6 +19,7 @@ func InitAccounts() {
if !ok { if !ok {
log.Errorf("no [%s] driver", account.Type) log.Errorf("no [%s] driver", account.Type)
} else { } else {
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
err := driver.Save(&accounts[i], nil) err := driver.Save(&accounts[i], nil)
if err != nil { if err != nil {
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error()) log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
@ -27,4 +28,4 @@ func InitAccounts() {
} }
} }
} }
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io/ioutil" "io/ioutil"
"os"
) )
// InitConf init config // InitConf init config
@ -20,25 +21,29 @@ func InitConf() {
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) { if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
log.Fatalf("failed to create default config file") log.Fatalf("failed to create default config file")
} }
return } else {
config, err := ioutil.ReadFile(conf.ConfigFile)
if err != nil {
log.Fatalf("reading config file error:%s", err.Error())
}
conf.Conf = conf.DefaultConfig()
err = utils.Json.Unmarshal(config, conf.Conf)
if err != nil {
log.Fatalf("load config error: %s", err.Error())
}
log.Debugf("config:%+v", conf.Conf)
// update config.json struct
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
if err != nil {
log.Fatalf("marshal config error:%s", err.Error())
}
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
if err != nil {
log.Fatalf("update config struct error: %s", err.Error())
}
} }
config, err := ioutil.ReadFile(conf.ConfigFile) err := os.MkdirAll(conf.Conf.TempDir, 0700)
if err != nil { if err != nil {
log.Fatalf("reading config file error:%s", err.Error()) log.Fatalf("create temp dir error: %s", err.Error())
}
conf.Conf = new(conf.Config)
err = utils.Json.Unmarshal(config, conf.Conf)
if err != nil {
log.Fatalf("load config error: %s", err.Error())
}
log.Debugf("config:%+v", conf.Conf)
// update config.json struct
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
if err != nil {
log.Fatalf("marshal config error:%s", err.Error())
}
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
if err != nil {
log.Fatalf("update config struct error: %s", err.Error())
} }
} }

View File

@ -12,6 +12,9 @@ func InitLog() {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetReportCaller(true) log.SetReportCaller(true)
} }
if conf.Password {
log.SetLevel(log.WarnLevel)
}
log.SetFormatter(&log.TextFormatter{ log.SetFormatter(&log.TextFormatter{
//DisableColors: true, //DisableColors: true,
ForceColors: true, ForceColors: true,
@ -29,4 +32,4 @@ func init() {
flag.BoolVar(&conf.Password, "password", false, "print current password") flag.BoolVar(&conf.Password, "password", false, "print current password")
flag.Parse() flag.Parse()
InitLog() InitLog()
} }

View File

@ -3,6 +3,7 @@ package bootstrap
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
"strings" "strings"
@ -27,7 +28,7 @@ func InitSettings() {
}, },
{ {
Key: "password", Key: "password",
Value: "alist", Value: utils.RandomStr(8),
Description: "password", Description: "password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -35,7 +36,7 @@ func InitSettings() {
}, },
{ {
Key: "logo", Key: "logo",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
Description: "logo", Description: "logo",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -43,7 +44,7 @@ func InitSettings() {
}, },
{ {
Key: "favicon", Key: "favicon",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
Description: "favicon", Description: "favicon",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -64,16 +65,23 @@ func InitSettings() {
Description: "text type extensions", Description: "text type extensions",
Group: model.FRONT, Group: model.FRONT,
}, },
//{
// Key: "hide readme file",
// Value: "true",
// Type: "bool",
// Description: "hide readme file? ",
// Group: model.FRONT,
//},
{ {
Key: "hide readme file", Key: "hide files",
Value: "true", Value: "/\\/README.md/i",
Type: "bool", Type: "text",
Description: "hide readme file? ", Description: "hide files, support RegExp, one per line",
Group: model.FRONT, Group: model.FRONT,
}, },
{ {
Key: "music cover", Key: "music cover",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
Description: "music cover image", Description: "music cover image",
Type: "string", Type: "string",
Access: model.PUBLIC, Access: model.PUBLIC,
@ -157,7 +165,7 @@ func InitSettings() {
}, },
{ {
Key: "WebDAV username", Key: "WebDAV username",
Value: "alist_admin", Value: "admin",
Description: "WebDAV username", Description: "WebDAV username",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -165,7 +173,7 @@ func InitSettings() {
}, },
{ {
Key: "WebDAV password", Key: "WebDAV password",
Value: "alist_admin", Value: utils.RandomStr(8),
Description: "WebDAV password", Description: "WebDAV password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -189,7 +197,7 @@ func InitSettings() {
}, },
{ {
Key: "Visitor WebDAV username", Key: "Visitor WebDAV username",
Value: "alist_visitor", Value: "guest",
Description: "Visitor WebDAV username", Description: "Visitor WebDAV username",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -197,7 +205,7 @@ func InitSettings() {
}, },
{ {
Key: "Visitor WebDAV password", Key: "Visitor WebDAV password",
Value: "alist_visitor", Value: "guest",
Description: "Visitor WebDAV password", Description: "Visitor WebDAV password",
Type: "string", Type: "string",
Access: model.PRIVATE, Access: model.PRIVATE,
@ -219,6 +227,14 @@ func InitSettings() {
Access: model.PUBLIC, Access: model.PUBLIC,
Group: model.FRONT, Group: model.FRONT,
}, },
{
Key: "ocr api",
Value: "https://api.xhofe.top/ocr/file/json",
Description: "Used to identify verification codes",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
} }
for i, _ := range settings { for i, _ := range settings {
v := settings[i] v := settings[i]
@ -227,6 +243,9 @@ func InitSettings() {
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v) err = model.SaveSetting(v)
if v.Key == "password" {
log.Infof("Initial password: %s", v.Value)
}
if err != nil { if err != nil {
log.Fatalf("failed write setting: %s", err.Error()) log.Fatalf("failed write setting: %s", err.Error())
} }

View File

@ -30,13 +30,15 @@ type Config struct {
Database Database `json:"database"` Database Database `json:"database"`
Scheme Scheme `json:"scheme"` Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"` Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir"`
} }
func DefaultConfig() *Config { func DefaultConfig() *Config {
return &Config{ return &Config{
Address: "0.0.0.0", Address: "0.0.0.0",
Port: 5244, Port: 5244,
Assets: "zhimg", Assets: "jsdelivr",
TempDir: "data/temp",
Database: Database{ Database: Database{
Type: "sqlite3", Type: "sqlite3",
Port: 0, Port: 0,

View File

@ -36,10 +36,10 @@ var (
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"} OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"} VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"} AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf"} ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
) )
var settingsMap = make(map[string]string, 0) var settingsMap = make(map[string]string)
func Set(key string, value string) { func Set(key string, value string) {
settingsMap[key] = value settingsMap[key] = value
@ -78,6 +78,7 @@ var (
"check parent folder", "check down link", "WebDAV username", "WebDAV password", "check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password", "Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type", "default page size", "load type",
"ocr api", "favicon",
} }
) )

View File

@ -1,6 +1,8 @@
package _23 package _23
import ( import (
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
@ -10,9 +12,11 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
) )
@ -53,6 +57,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt", Values: "name,fileId,updateAt,createAt",
Required: true, Required: true,
Default: "name",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -60,6 +65,7 @@ func (driver Pan123) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "asc,desc", Values: "asc,desc",
Required: true, Required: true,
Default: "asc",
}, },
} }
} }
@ -192,9 +198,9 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Pan123) Proxy(c *gin.Context, account *model.Account) { //func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("origin") // r.Header.Del("origin")
} //}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) { func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -294,10 +300,37 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
return base.ErrNotFolder return base.ErrNotFolder
} }
parentFileId, _ := strconv.Atoi(parentFile.Id) parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}
etag := hex.EncodeToString(h.Sum(nil))
log.Debugln("md5:", etag)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
data := base.Json{ data := base.Json{
"driveId": 0, "driveId": 0,
"duplicate": true, "duplicate": true,
"etag": "836aae6cac845e17fce51919594737d0", //maybe file's md5 "etag": etag,
"fileName": file.GetFileName(), "fileName": file.GetFileName(),
"parentFileId": parentFileId, "parentFileId": parentFileId,
"size": file.GetSize(), "size": file.GetSize(),
@ -310,6 +343,9 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
if err != nil { if err != nil {
return err return err
} }
if resp.Data.Key == "" {
return nil
}
cfg := &aws.Config{ cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken), Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"), Region: aws.String("123pan"),
@ -324,7 +360,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
input := &s3manager.UploadInput{ input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket, Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key, Key: &resp.Data.Key,
Body: file, Body: tempFile,
} }
_, err = uploader.Upload(input) _, err = uploader.Upload(input)
if err != nil { if err != nil {

View File

@ -17,7 +17,7 @@ import (
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) { func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
url := "https://yun.139.com" + pathname url := "https://yun.139.com" + pathname
req := base.RestyClient.R() req := base.RestyClient.R()
randStr := randomStr(16) randStr := utils.RandomStr(16)
ts := time.Now().Format("2006-01-02 15:04:05") ts := time.Now().Format("2006-01-02 15:04:05")
log.Debugf("%+v", data) log.Debugf("%+v", data)
body, err := utils.Json.Marshal(data) body, err := utils.Json.Marshal(data)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"math" "math"
@ -163,9 +162,9 @@ func (driver Cloud139) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Cloud139) Proxy(c *gin.Context, account *model.Account) { //func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) { func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -335,7 +334,7 @@ func (driver Cloud139) Delete(path string, account *model.Account) error {
"taskInfo": base.Json{ "taskInfo": base.Json{
"newCatalogID": "", "newCatalogID": "",
"contentInfoList": contentInfoList, "contentInfoList": contentInfoList,
"catalogInfoList": contentInfoList, "catalogInfoList": catalogInfoList,
}, },
"commonAccountInfo": base.Json{ "commonAccountInfo": base.Json{
"account": account.Username, "account": account.Username,

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"math/rand"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
@ -13,16 +12,6 @@ import (
"time" "time"
) )
func randomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}
func encodeURIComponent(str string) string { func encodeURIComponent(str string) string {
r := url.QueryEscape(str) r := url.QueryEscape(str)
r = strings.Replace(r, "+", "%20", -1) r = strings.Replace(r, "+", "%20", -1)

View File

@ -2,16 +2,9 @@ package _89
import ( import (
"bytes" "bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
@ -19,14 +12,11 @@ import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"math" "math"
mathRand "math/rand"
"net/http" "net/http"
"net/url"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@ -35,6 +25,7 @@ import (
) )
var client189Map map[string]*resty.Client var client189Map map[string]*resty.Client
var infoMap = make(map[string]Rsa)
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) { func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
client, ok := client189Map[account.Name] client, ok := client189Map[account.Name]
@ -116,6 +107,7 @@ func (driver Cloud189) Login(account *model.Account) error {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) //cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New() client = resty.New()
//client.SetCookieJar(cookieJar) //client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3) client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/") client.SetHeader("Referer", "https://cloud.189.cn/")
} }
@ -153,9 +145,33 @@ func (driver Cloud189) Login(account *model.Account) error {
vCodeRS := "" vCodeRS := ""
if vCodeID != "" { if vCodeID != "" {
// need ValidateCode // need ValidateCode
log.Debugf("try to identify verification codes")
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
imgRes, err := client.R().SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}).Get(u)
if err != nil {
return err
}
vRes, err := client.R().SetMultipartField(
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return err
}
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
}
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", vCodeRS)
} }
userRsa := RsaEncode([]byte(account.Username), jRsakey) userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey) passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do" url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp var loginResp LoginResp
res, err := client.R(). res, err := client.R().
@ -199,39 +215,6 @@ func (driver Cloud189) Login(account *model.Account) error {
return nil return nil
} }
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
func (driver Cloud189) isFamily(account *model.Account) bool { func (driver Cloud189) isFamily(account *model.Account) bool {
return account.InternalType == "Family" return account.InternalType == "Family"
} }
@ -249,8 +232,8 @@ func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud1
"mediaType": "0", "mediaType": "0",
"folderId": fileId, "folderId": fileId,
"iconOption": "5", "iconOption": "5",
"orderBy": account.OrderBy, "orderBy": "lastOpTime", //account.OrderBy
"descending": account.OrderDirection, "descending": "true", //account.OrderDirection
}, nil, nil, account) }, nil, nil, account)
if err != nil { if err != nil {
return nil, err return nil, err
@ -321,7 +304,7 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) //log.Debug(res.String())
if e.ErrorCode != "" { if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" { if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account) err = driver.Login(account)
@ -338,61 +321,198 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
} }
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) { func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
//info, ok := infoMap[account.Name]
//if !ok {
// info = Info{}
// infoMap[account.Name] = info
//} else {
// log.Debugf("hit")
//}
//if info.SessionKey != "" {
// return info.SessionKey, nil
//}
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account) resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
if err != nil { if err != nil {
return "", err return "", err
} }
return jsoniter.Get(resp, "sessionKey").ToString(), nil sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
//info.SessionKey = sessionKey
return sessionKey, nil
} }
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) { func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
rsa, ok := infoMap[account.Name]
if !ok {
rsa = Rsa{}
infoMap[account.Name] = rsa
}
now := time.Now().UnixMilli()
if rsa.Expire > now {
return rsa.PubKey, rsa.PkId, nil
}
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account) resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString(), nil pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
rsa.PubKey, rsa.PkId = pubKey, pkId
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
return pubKey, pkId, nil
} }
func (driver Cloud189) UploadRequest(url string, form map[string]string, account *model.Account) ([]byte, error) { //func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
sessionKey, err := driver.GetSessionKey(account) // //sessionKey, err := driver.GetSessionKey(account)
if err != nil { // //if err != nil {
return nil, err // // return nil, err
} // //}
// sessionKey := account.DriveId
// pubKey, pkId, err := driver.GetResKey(account)
// log.Debugln(sessionKey, pubKey, pkId)
// if err != nil {
// return nil, err
// }
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
// date := strconv.FormatInt(time.Now().Unix(), 10)
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", date)
// a.Set("params", params)
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
// headers := map[string]string{
// "signature": signature,
// "sessionKey": sessionKey,
// "encryptionText": encryptionText,
// "pkId": pkId,
// "x-request-id": xRId,
// "x-request-date": date,
// }
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data := res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
//
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
// c := strconv.FormatInt(time.Now().UnixMilli(), 10)
// r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
// l = l[0 : 16+int(16*mathRand.Float32())]
//
// e := qs(form)
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
// h := hex.EncodeToString(data)
//
// sessionKey := account.DriveId
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", c)
// a.Set("params", h)
// g := SHA1(EncodeParam(a), l)
//
// pubKey, pkId, err := driver.GetResKey(account)
// if err != nil {
// return nil, err
// }
// b := RsaEncode([]byte(l), pubKey, false)
// client, err := driver.getClient(account)
// if err != nil {
// return nil, err
// }
// req := client.R()
// req.Header.Set("accept", "application/json;charset=UTF-8")
// req.Header.Set("SessionKey", sessionKey)
// req.Header.Set("Signature", hex.EncodeToString(g))
// req.Header.Set("X-Request-Date", c)
// req.Header.Set("X-Request-ID", r)
// req.Header.Set("EncryptionText", b)
// req.Header.Set("PkId", pkId)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data = res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
l = l[0 : 16+int(16*utils.Rand.Float32())]
e := qs(form)
data := AesEncrypt([]byte(e), []byte(l[0:16]))
h := hex.EncodeToString(data)
sessionKey := account.DriveId
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, uri, c, h), l)
pubKey, pkId, err := driver.GetResKey(account) pubKey, pkId, err := driver.GetResKey(account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
xRId := uuid.New().String() b := RsaEncode([]byte(l), pubKey, false)
pkey := strings.ReplaceAll(xRId, "-", "")[:mathRand.Intn(16)+16] client, err := driver.getClient(account)
params := aesEncrypt(qs(form), pkey[:16])
date := strconv.FormatInt(time.Now().Unix(), 10)
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, url, date, params), pkey)
encryptionText := RsaEncode([]byte(pkey), pubKey)
headers := map[string]string{
"signature": signature,
"sessionKey": sessionKey,
"encryptionText": encryptionText,
"pkId": pkId,
"x-request-id": xRId,
"x-request-date": date,
"origin": "https://cloud.189.cn",
"referer": "https://cloud.189.cn/",
}
log.Debugf("%+v\n%s", headers, params)
res, err := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params).Get("https://upload.cloud.189.cn" + url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) req := client.R()
data := res.Body() req.Header.Set("accept", "application/json;charset=UTF-8")
req.Header.Set("SessionKey", sessionKey)
req.Header.Set("Signature", signature)
req.Header.Set("X-Request-Date", c)
req.Header.Set("X-Request-ID", r)
req.Header.Set("EncryptionText", b)
req.Header.Set("PkId", pkId)
if resp != nil {
req.SetResult(resp)
}
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
if err != nil {
return nil, err
}
//log.Debug(res.String())
data = res.Body()
if jsoniter.Get(data, "code").ToString() != "SUCCESS" { if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
return nil, errors.New(jsoniter.Get(data, "msg").ToString()) return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
} }
return data, nil return data, nil
} }
// Upload Error: decrypt encryptionText failed // NewUpload Error: signature check false
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error { func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
account.DriveId = sessionKey
}
_ = model.SaveAccount(account)
const DEFAULT uint64 = 10485760 const DEFAULT uint64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0 var finish uint64 = 0
@ -405,15 +525,18 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
} }
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{ res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": parentFile.Id, "parentFolderId": parentFile.Id,
"fileName": file.Name, "fileName": encode(file.Name),
"fileSize": strconv.FormatInt(int64(file.Size), 10), "fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10), "sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1", "lazyCheck": "1",
}, account) }, account, nil)
if err != nil { if err != nil {
return err return err
} }
uploadFileId := jsoniter.Get(res, "data.uploadFileId").ToString() uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
// "uploadFileId": uploadFileId,
//}, account, nil)
var i int64 var i int64
var byteSize uint64 var byteSize uint64
md5s := make([]string, 0) md5s := make([]string, 0)
@ -423,180 +546,82 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
if DEFAULT < byteSize { if DEFAULT < byteSize {
byteSize = DEFAULT byteSize = DEFAULT
} }
log.Debugf("%d,%d", byteSize, finish) //log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize) byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData) n, err := io.ReadFull(file, byteData)
log.Debug(err, n) //log.Debug(err, n)
if err != nil { if err != nil {
return err return err
} }
finish += uint64(n) finish += uint64(n)
md5Bytes := getMd5(byteData) md5Bytes := getMd5(byteData)
md5Str := hex.EncodeToString(md5Bytes) md5Hex := hex.EncodeToString(md5Bytes)
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes) md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
md5s = append(md5s, md5Str) md5s = append(md5s, strings.ToUpper(md5Hex))
md5Sum.Write(byteData) md5Sum.Write(byteData)
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
var resp UploadUrlsResp
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{ res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64), "partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
"uploadFileId": uploadFileId, "uploadFileId": uploadFileId,
}, account) }, account, &resp)
if err != nil { if err != nil {
return err return err
} }
uploadData := jsoniter.Get(res, "uploadUrls.partNumber_"+strconv.FormatInt(i, 10)) uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
headers := strings.Split(uploadData.Get("requestHeader").ToString(), "&") log.Debugf("uploadData: %+v", uploadData)
req, err := http.NewRequest("PUT", uploadData.Get("requestURL").ToString(), bytes.NewBuffer(byteData)) requestURL := uploadData.RequestURL
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
for _, v := range uploadHeaders {
i := strings.Index(v, "=")
req.Header.Set(v[0:i], v[i+1:])
}
r, err := base.HttpClient.Do(req)
log.Debugf("%+v %+v", r, r.Request.Header)
if err != nil { if err != nil {
return err return err
} }
for _, header := range headers {
kv := strings.Split(header, "=")
req.Header.Set(kv[0], strings.Join(kv[1:], "="))
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
} }
id := md5Sum.Sum(nil) fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
sliceMd5 := fileMd5
if file.GetSize() > DEFAULT {
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
}
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{ res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
"uploadFileId": uploadFileId, "uploadFileId": uploadFileId,
"fileMd5": hex.EncodeToString(id), "fileMd5": fileMd5,
"sliceMd5": utils.GetMD5Encode(strings.Join(md5s, "\n")), "sliceMd5": sliceMd5,
"lazyCheck": "1", "lazyCheck": "1",
}, account) }, account, nil)
account.DriveId, _ = driver.GetSessionKey(account)
return err return err
} }
func random() string { func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000)) //return base.ErrNotImplement
} client, err := driver.getClient(account)
func RsaEncode(origData []byte, j_rsakey string) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil { if err != nil {
log.Errorf("err: %s", err.Error()) return err
} }
return b64tohex(base64.StdEncoding.EncodeToString(b)) parentFile, err := driver.File(file.ParentPath, account)
} if err != nil {
return err
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
} }
if e == 1 { // api refer to PanIndex
d += int2char(c << 2) res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
} }
return d if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
} return nil
func qs(form map[string]string) string {
strList := make([]string, 0)
for k, v := range form {
strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
} }
return strings.Join(strList, "&") log.Debugf(res.String())
} return errors.New(res.String())
func aesEncrypt(data, key string) string {
encrypted := AesEncryptECB([]byte(data), []byte(key))
//return string(encrypted)
return hex.EncodeToString(encrypted)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
}
return encrypted
}
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
//
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
}
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}
return genKey
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client, 0)
} }

View File

@ -1,14 +1,11 @@
package _89 package _89
import ( import (
"errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -17,7 +14,8 @@ type Cloud189 struct{}
func (driver Cloud189) Config() base.DriverConfig { func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{ return base.DriverConfig{
Name: "189Cloud", Name: "189Cloud",
LocalSort: true,
} }
} }
@ -55,20 +53,20 @@ func (driver Cloud189) Items() []base.Item {
// Label: "family id", // Label: "family id",
// Type: base.TypeString, // Type: base.TypeString,
//}, //},
{ //{
Name: "order_by", // Name: "order_by",
Label: "order_by", // Label: "order_by",
Type: base.TypeSelect, // Type: base.TypeSelect,
Values: "name,size,lastOpTime,createdDate", // Values: "name,size,lastOpTime,createdDate",
Required: true, // Required: true,
}, //},
{ //{
Name: "order_direction", // Name: "order_direction",
Label: "desc", // Label: "desc",
Type: base.TypeSelect, // Type: base.TypeSelect,
Values: "true,false", // Values: "true,false",
Required: true, // Required: true,
}, //},
} }
} }
@ -173,7 +171,12 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
if err != nil { if err != nil {
return nil, err return nil, err
} }
link := base.Link{} link := base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "Authorization", Value: ""},
},
}
if res.StatusCode() == 302 { if res.StatusCode() == 302 {
link.Url = res.Header().Get("location") link.Url = res.Header().Get("location")
} else { } else {
@ -199,9 +202,9 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) { //func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
ctx.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) { func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -349,29 +352,8 @@ func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) er
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
client, err := driver.getClient(account) return driver.NewUpload(file, account)
if err != nil { //return driver.OldUpload(file, account)
return err
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
// api refer to PanIndex
res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
}
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
return nil
}
log.Debugf(res.String())
return errors.New(res.String())
} }
var _ base.Driver = (*Cloud189)(nil) var _ base.Driver = (*Cloud189)(nil)

55
drivers/189/types.go Normal file
View File

@ -0,0 +1,55 @@
package _89
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}
//type Info struct {
// SessionKey string
// Rsa Rsa
//}
type Rsa struct {
Expire int64 `json:"expire"`
PkId string `json:"pkId"`
PubKey string `json:"pubKey"`
}

199
drivers/189/util.go Normal file
View File

@ -0,0 +1,199 @@
package _89
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/url"
"regexp"
"strconv"
"strings"
)
func random() string {
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
}
res := base64.StdEncoding.EncodeToString(b)
if hex {
return b64tohex(res)
}
return res
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
}
if e == 1 {
d += int2char(c << 2)
}
return d
}
func qs(form map[string]string) string {
f := make(url.Values)
for k, v := range form {
f.Set(k, v)
}
return EncodeParam(f)
//strList := make([]string, 0)
//for k, v := range form {
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
//}
//return strings.Join(strList, "&")
}
func EncodeParam(v url.Values) string {
if v == nil {
return ""
}
var buf strings.Builder
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
for _, k := range keys {
vs := v[k]
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
//if k == "fileName" {
// buf.WriteString(encode(v))
//} else {
buf.WriteString(v)
//}
}
}
return buf.String()
}
func encode(str string) string {
//str = strings.ReplaceAll(str, "%", "%25")
//str = strings.ReplaceAll(str, "&", "%26")
//str = strings.ReplaceAll(str, "+", "%2B")
//return str
return url.QueryEscape(str)
}
func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
if block == nil {
return []byte{}
}
data = PKCS7Padding(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client)
}
func decodeURIComponent(str string) string {
r, _ := url.PathUnescape(str)
//r = strings.ReplaceAll(r, " ", "+")
return r
}
func Random(v string) string {
reg := regexp.MustCompilePOSIX("[xy]")
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
var i int64
t := int64(16 * utils.Rand.Float32())
if msg[0] == 120 {
i = t
} else {
i = 3&t | 8
}
return []byte(strconv.FormatInt(i, 16))
})
return string(data)
}
//func SHA1(v, l string) []byte {
// key := []byte(l)
// mac := hmac.New(sha1.New, key)
// mac.Write([]byte(v))
// return mac.Sum(nil)
//}

View File

@ -236,6 +236,7 @@ func (driver AliDrive) batch(srcId, dstId string, account *model.Account) error
func init() { func init() {
base.RegisterDriver(&AliDrive{}) base.RegisterDriver(&AliDrive{})
aliClient. aliClient.
SetTimeout(base.DefaultTimeout).
SetRetryCount(3). SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"). SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json"). SetHeader("content-type", "application/json").

View File

@ -8,7 +8,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
@ -191,6 +190,12 @@ func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link,
return nil, fmt.Errorf("%s", e.Message) return nil, fmt.Errorf("%s", e.Message)
} }
return &base.Link{ return &base.Link{
Headers: []base.Header{
{
Name: "Referer",
Value: "https://www.aliyundrive.com/",
},
},
Url: resp["url"].(string), Url: resp["url"].(string),
}, nil }, nil
} }
@ -212,10 +217,10 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver AliDrive) Proxy(c *gin.Context, account *model.Account) { //func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
c.Request.Header.Set("Referer", "https://www.aliyundrive.com/") // r.Header.Set("Referer", "https://www.aliyundrive.com/")
} //}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account) file, err := driver.GetFile(path, account)

View File

@ -7,7 +7,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -148,7 +147,7 @@ func (driver Alist) Path(path string, account *model.Account) (*model.File, []mo
return nil, resp.Data.Files, nil return nil, resp.Data.Files, nil
} }
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {} //func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) { func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
var resp PathResp var resp PathResp

View File

@ -6,6 +6,7 @@ import (
_ "github.com/Xhofe/alist/drivers/189" _ "github.com/Xhofe/alist/drivers/189"
_ "github.com/Xhofe/alist/drivers/alidrive" _ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist" _ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/baidu"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
_ "github.com/Xhofe/alist/drivers/ftp" _ "github.com/Xhofe/alist/drivers/ftp"
_ "github.com/Xhofe/alist/drivers/google" _ "github.com/Xhofe/alist/drivers/google"
@ -14,10 +15,12 @@ import (
_ "github.com/Xhofe/alist/drivers/native" _ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive" _ "github.com/Xhofe/alist/drivers/onedrive"
_ "github.com/Xhofe/alist/drivers/pikpak" _ "github.com/Xhofe/alist/drivers/pikpak"
_ "github.com/Xhofe/alist/drivers/quark"
_ "github.com/Xhofe/alist/drivers/s3" _ "github.com/Xhofe/alist/drivers/s3"
_ "github.com/Xhofe/alist/drivers/shandian" _ "github.com/Xhofe/alist/drivers/shandian"
_ "github.com/Xhofe/alist/drivers/teambition" _ "github.com/Xhofe/alist/drivers/teambition"
_ "github.com/Xhofe/alist/drivers/webdav" _ "github.com/Xhofe/alist/drivers/webdav"
_ "github.com/Xhofe/alist/drivers/yandex"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"strings" "strings"
) )

186
drivers/baidu/baidu.go Normal file
View File

@ -0,0 +1,186 @@
package baidu
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
"path"
"strconv"
)
func (driver Baidu) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Baidu) refreshToken(account *model.Account) error {
u := "https://openapi.baidu.com/oauth/2.0/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Get(u)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Baidu) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://pan.baidu.com/rest/2.0" + pathname
req := base.RestyClient.R()
req.SetQueryParam("access_token", account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Patch:
res, err = req.Patch(u)
case base.Delete:
res, err = req.Delete(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
errno := jsoniter.Get(res.Body(), "errno").ToInt()
if errno != 0 {
if errno == -6 {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(pathname, method, headers, query, form, data, resp, account)
}
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
}
return res.Body(), nil
}
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, params, nil, nil, resp, account)
}
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, params, nil, data, resp, account)
}
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "filemanager",
"opera": opera,
}
marshal, err := utils.Json.Marshal(filelist)
if err != nil {
return nil, err
}
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
return driver.Post("/xpan/file", params, data, nil, account)
}
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
dir = utils.Join(account.RootFolder, dir)
start := 0
limit := 200
params := map[string]string{
"method": "list",
"dir": dir,
"web": "web",
}
if account.OrderBy != "" {
params["order"] = account.OrderBy
if account.OrderDirection == "desc" {
params["desc"] = "1"
}
}
res := make([]model.File, 0)
for {
params["start"] = strconv.Itoa(start)
params["limit"] = strconv.Itoa(limit)
start += limit
var resp ListResp
_, err := driver.Get("/xpan/file", params, &resp, account)
if err != nil {
return nil, err
}
if len(resp.List) == 0 {
break
}
for _, f := range resp.List {
file := model.File{
Id: strconv.FormatInt(f.FsId, 10),
Name: f.ServerFilename,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.ServerMtime),
Thumbnail: f.Thumbs.Url3,
}
if f.Isdir == 1 {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
}
res = append(res, file)
}
}
return res, nil
}
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "create",
}
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
if uploadid != "" {
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
}
return driver.Post("/xpan/file", params, data, nil, account)
}
func init() {
base.RegisterDriver(&Baidu{})
}

355
drivers/baidu/driver.go Normal file
View File

@ -0,0 +1,355 @@
package baidu
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"strconv"
"strings"
)
type Baidu struct{}
func (driver Baidu) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Baidu.Disk",
}
}
func (driver Baidu) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Default: "name",
Values: "name,time,size",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Default: "asc",
Required: false,
},
{
Name: "client_id",
Label: "client id",
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
return driver.RefreshToken(account)
}
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
files, err := driver.GetFiles(path, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if file.IsDir() {
return nil, base.ErrNotFile
}
var resp DownloadResp
params := map[string]string{
"method": "filemetas",
"fsids": fmt.Sprintf("[%s]", file.Id),
"dlink": "1",
}
_, err = driver.Get("/xpan/multimedia", params, &resp, account)
if err != nil {
return nil, err
}
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
if err != nil {
return nil, err
}
//if res.StatusCode() == 302 {
u = res.Header().Get("location")
//}
return &base.Link{
Url: u,
Headers: []base.Header{
{Name: "User-Agent", Value: "pan.baidu.com"},
}}, nil
}
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
// r.Header.Set("User-Agent", "pan.baidu.com")
//}
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Baidu) MakeDir(path string, account *model.Account) error {
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
return err
}
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("move", data, account)
return err
}
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
newname := utils.Base(dst)
data := []base.Json{
{
"path": path,
"newname": newname,
},
}
_, err := driver.manage("rename", data, account)
return err
}
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("copy", data, account)
return err
}
func (driver Baidu) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
data := []string{path}
_, err := driver.manage("delete", data, account)
return err
}
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
var Default uint64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
var SliceSize uint64 = 256 * 1024
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
content_md5 := ""
slice_md5 := ""
left := file.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
content_md5 = hex.EncodeToString(h1.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
if file.GetSize() <= SliceSize {
slice_md5 = content_md5
} else {
sliceData := make([]byte, SliceSize)
_, err = io.ReadFull(tempFile, sliceData)
if err != nil {
return err
}
h2.Write(sliceData)
slice_md5 = hex.EncodeToString(h2.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
}
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
path, file.GetSize(),
block_list_str,
content_md5, slice_md5)
params := map[string]string{
"method": "precreate",
}
var precreateResp PrecreateResp
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
if err != nil {
return err
}
log.Debugf("%+v", precreateResp)
if precreateResp.ReturnType == 2 {
return nil
}
params = map[string]string{
"method": "upload",
"access_token": account.AccessToken,
"type": "tmpfile",
"path": path,
"uploadid": precreateResp.Uploadid,
}
left = file.GetSize()
for _, partseq := range precreateResp.BlockList {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
if err != nil {
return err
}
log.Debugln(res.String())
}
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
return err
}
var _ base.Driver = (*Baidu)(nil)

84
drivers/baidu/types.go Normal file
View File

@ -0,0 +1,84 @@
package baidu
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
}
type File struct {
//TkbindId int `json:"tkbind_id"`
//OwnerType int `json:"owner_type"`
//Category int `json:"category"`
//RealCategory string `json:"real_category"`
FsId int64 `json:"fs_id"`
ServerMtime int64 `json:"server_mtime"`
//OperId int `json:"oper_id"`
//ServerCtime int `json:"server_ctime"`
Thumbs struct {
//Icon string `json:"icon"`
Url3 string `json:"url3"`
//Url2 string `json:"url2"`
//Url1 string `json:"url1"`
} `json:"thumbs"`
//Wpfile int `json:"wpfile"`
//LocalMtime int `json:"local_mtime"`
Size int64 `json:"size"`
//ExtentTinyint7 int `json:"extent_tinyint7"`
Path string `json:"path"`
//Share int `json:"share"`
//ServerAtime int `json:"server_atime"`
//Pl int `json:"pl"`
//LocalCtime int `json:"local_ctime"`
ServerFilename string `json:"server_filename"`
//Md5 string `json:"md5"`
//OwnerId int `json:"owner_id"`
//Unlist int `json:"unlist"`
Isdir int `json:"isdir"`
}
type ListResp struct {
Errno int `json:"errno"`
GuidInfo string `json:"guid_info"`
List []File `json:"list"`
RequestId int64 `json:"request_id"`
Guid int `json:"guid"`
}
type DownloadResp struct {
Errmsg string `json:"errmsg"`
Errno int `json:"errno"`
List []struct {
//Category int `json:"category"`
//DateTaken int `json:"date_taken,omitempty"`
Dlink string `json:"dlink"`
//Filename string `json:"filename"`
//FsId int64 `json:"fs_id"`
//Height int `json:"height,omitempty"`
//Isdir int `json:"isdir"`
//Md5 string `json:"md5"`
//OperId int `json:"oper_id"`
//Path string `json:"path"`
//ServerCtime int `json:"server_ctime"`
//ServerMtime int `json:"server_mtime"`
//Size int `json:"size"`
//Thumbs struct {
// Icon string `json:"icon,omitempty"`
// Url1 string `json:"url1,omitempty"`
// Url2 string `json:"url2,omitempty"`
// Url3 string `json:"url3,omitempty"`
//} `json:"thumbs"`
//Width int `json:"width,omitempty"`
} `json:"list"`
//Names struct {
//} `json:"names"`
RequestId string `json:"request_id"`
}
type PrecreateResp struct {
Path string `json:"path"`
Uploadid string `json:"uploadid"`
ReturnType int `json:"return_type"`
BlockList []int `json:"block_list"`
Errno int `json:"errno"`
RequestId int64 `json:"request_id"`
}

18
drivers/baidu/util.go Normal file
View File

@ -0,0 +1,18 @@
package baidu
import (
"net/url"
"strings"
"time"
)
func getTime(t int64) *time.Time {
tm := time.Unix(t, 0)
return &tm
}
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.ReplaceAll(r, "+", "%20")
return r
}

View File

@ -2,10 +2,10 @@ package base
import ( import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/http" "net/http"
"time"
) )
type DriverConfig struct { type DriverConfig struct {
@ -38,8 +38,8 @@ type Driver interface {
Link(args Args, account *model.Account) (*Link, error) Link(args Args, account *model.Account) (*Link, error)
// Path 取路径(文件或文件夹) // Path 取路径(文件或文件夹)
Path(path string, account *model.Account) (*model.File, []model.File, error) Path(path string, account *model.Account) (*model.File, []model.File, error)
// Proxy 代理处理 // Deprecated Proxy 代理处理
Proxy(c *gin.Context, account *model.Account) //Proxy(r *http.Request, account *model.Account)
// Preview 预览 // Preview 预览
Preview(path string, account *model.Account) (interface{}, error) Preview(path string, account *model.Account) (interface{}, error)
// MakeDir 创建文件夹 // MakeDir 创建文件夹
@ -62,6 +62,7 @@ type Item struct {
Name string `json:"name"` Name string `json:"name"`
Label string `json:"label"` Label string `json:"label"`
Type string `json:"type"` Type string `json:"type"`
Default string `json:"default"`
Values string `json:"values"` Values string `json:"values"`
Required bool `json:"required"` Required bool `json:"required"`
Description string `json:"description"` Description string `json:"description"`
@ -84,19 +85,21 @@ func GetDriversMap() map[string]Driver {
} }
func GetDrivers() map[string][]Item { func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0) res := make(map[string][]Item)
for k, v := range driversMap { for k, v := range driversMap {
webdavDirect := Item{
Name: "webdav_direct",
Label: "webdav direct",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the native",
}
if v.Config().OnlyProxy { if v.Config().OnlyProxy {
res[k] = v.Items() res[k] = append([]Item{
webdavDirect,
}, v.Items()...)
} else { } else {
res[k] = append([]Item{ res[k] = append([]Item{
//{
// Name: "allow_proxy",
// Label: "allow_proxy",
// Type: TypeBool,
// Required: true,
// Description: "allow proxy",
//},
{ {
Name: "proxy", Name: "proxy",
Label: "proxy", Label: "proxy",
@ -111,6 +114,7 @@ func GetDrivers() map[string][]Item {
Required: true, Required: true,
Description: "Transfer the WebDAV of this account through the server", Description: "Transfer the WebDAV of this account through the server",
}, },
webdavDirect,
}, v.Items()...) }, v.Items()...)
} }
res[k] = append([]Item{ res[k] = append([]Item{
@ -160,6 +164,8 @@ func GetDrivers() map[string][]Item {
var NoRedirectClient *resty.Client var NoRedirectClient *resty.Client
var RestyClient = resty.New() var RestyClient = resty.New()
var HttpClient = &http.Client{} var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 20
func init() { func init() {
NoRedirectClient = resty.New().SetRedirectPolicy( NoRedirectClient = resty.New().SetRedirectPolicy(
@ -167,8 +173,8 @@ func init() {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}), }),
) )
userAgent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" NoRedirectClient.SetHeader("user-agent", UserAgent)
NoRedirectClient.SetHeader("user-agent", userAgent) RestyClient.SetHeader("user-agent", UserAgent)
RestyClient.SetHeader("user-agent", userAgent)
RestyClient.SetRetryCount(3) RestyClient.SetRetryCount(3)
RestyClient.SetTimeout(DefaultTimeout)
} }

View File

@ -12,6 +12,8 @@ var (
ErrNotSupport = errors.New("not support") ErrNotSupport = errors.New("not support")
ErrNotFolder = errors.New("not a folder") ErrNotFolder = errors.New("not a folder")
ErrEmptyFile = errors.New("empty file") ErrEmptyFile = errors.New("empty file")
ErrRelativePath = errors.New("access using relative path is not allowed")
ErrEmptyToken = errors.New("empty token")
) )
const ( const (
@ -42,7 +44,8 @@ type Header struct {
} }
type Link struct { type Link struct {
Url string `json:"url"` Url string `json:"url"`
Headers []Header `json:"headers"` Headers []Header `json:"headers"`
Data io.ReadCloser Data io.ReadCloser
FilePath string `json:"path"` // for native
} }

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/jlaffaye/ftp" "github.com/jlaffaye/ftp"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
@ -186,9 +185,9 @@ func (driver FTP) Path(path string, account *model.Account) (*model.File, []mode
return nil, files, nil return nil, files, nil
} }
func (driver FTP) Proxy(c *gin.Context, account *model.Account) { //func (driver FTP) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) { func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -11,7 +11,12 @@ var connMap map[string]*ftp.ServerConn
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) { func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
conn, ok := connMap[account.Name] conn, ok := connMap[account.Name]
if ok { if ok {
return conn, nil _, err := conn.CurrentDir()
if err == nil {
return conn, nil
} else {
delete(connMap, account.Name)
}
} }
conn, err := ftp.Connect(account.SiteUrl) conn, err := ftp.Connect(account.SiteUrl)
if err != nil { if err != nil {
@ -21,9 +26,11 @@ func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
connMap[account.Name] = conn
return conn, nil return conn, nil
} }
func init() { func init() {
base.RegisterDriver(&FTP{}) base.RegisterDriver(&FTP{})
connMap = make(map[string]*ftp.ServerConn)
} }

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
@ -178,9 +177,9 @@ func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File
return nil, files, nil return nil, files, nil
} }
func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) { //func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Add("Authorization", "Bearer "+account.AccessToken) // r.Header.Add("Authorization", "Bearer "+account.AccessToken)
} //}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -138,6 +137,9 @@ func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, e
} }
link := base.Link{ link := base.Link{
Url: url, Url: url,
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
} }
return &link, nil return &link, nil
} }
@ -159,9 +161,9 @@ func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) { //func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) { func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -15,8 +14,6 @@ import (
"time" "time"
) )
var lanzouClient = resty.New()
type LanZouFile struct { type LanZouFile struct {
Name string `json:"name"` Name string `json:"name"`
NameAll string `json:"name_all"` NameAll string `json:"name_all"`
@ -58,7 +55,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
files := make([]LanZouFile, 0) files := make([]LanZouFile, 0)
var resp LanZouFilesResp var resp LanZouFilesResp
// folders // folders
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "47", "task": "47",
"folder_id": folderId, "folder_id": folderId,
@ -77,7 +74,7 @@ func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZo
// files // files
pg := 1 pg := 1
for { for {
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). _, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "5", "task": "5",
"folder_id": folderId, "folder_id": folderId,
@ -108,7 +105,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
res, err := lanzouClient.R().Get(shareUrl) res, err := base.RestyClient.R().Get(shareUrl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -129,7 +126,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
pg := 1 pg := 1
for { for {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{ res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
"lx": lx, "lx": lx,
"fid": fid, "fid": fid,
"uid": uid, "uid": uid,
@ -166,7 +163,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
// GetDownPageId 获取下载页面的ID // GetDownPageId 获取下载页面的ID
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) { func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
var resp LanZouFilesResp var resp LanZouFilesResp
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken). res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{ SetFormData(map[string]string{
"task": "22", "task": "22",
"file_id": fileId, "file_id": fileId,
@ -201,7 +198,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if err != nil { if err != nil {
return "", err return "", err
} }
res, err := lanzouClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId)) res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -209,19 +206,24 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
if len(iframe) == 0 { if len(iframe) == 0 {
return "", fmt.Errorf("get down empty page") return "", fmt.Errorf("get down empty page")
} }
iframeUrl := "https://wwa.lanzouo.com" + iframe[1] iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
res, err = lanzouClient.R().Get(iframeUrl) res, err = base.RestyClient.R().Get(iframeUrl)
if err != nil { if err != nil {
return "", err return "", err
} }
log.Debugln(res.String())
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String()) ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
if len(ajaxdata) == 0 { if len(ajaxdata) == 0 {
return "", fmt.Errorf("get iframe empty page") return "", fmt.Errorf("get iframe empty page")
} }
signs := ajaxdata[1] signs := ajaxdata[1]
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1] //sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1] sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1] //websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
//websign := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())[1]
websign := ""
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
var resp LanzouLinkResp var resp LanzouLinkResp
form := map[string]string{ form := map[string]string{
"action": "downprocess", "action": "downprocess",
@ -232,7 +234,7 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
"websignkey": websignkey, "websignkey": websignkey,
} }
log.Debugf("form: %+v", form) log.Debugf("form: %+v", form)
res, err = lanzouClient.R().SetResult(&resp). res, err = base.RestyClient.R().SetResult(&resp).
SetHeader("origin", "https://"+u.Host). SetHeader("origin", "https://"+u.Host).
SetHeader("referer", iframeUrl). SetHeader("referer", iframeUrl).
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host)) SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
@ -248,7 +250,4 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
func init() { func init() {
base.RegisterDriver(&Lanzou{}) base.RegisterDriver(&Lanzou{})
lanzouClient.
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
} }

View File

@ -1,7 +1,6 @@
package mediatrack package mediatrack
import ( import (
"bytes"
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -13,11 +12,12 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil"
"os"
"path/filepath" "path/filepath"
) )
@ -45,11 +45,12 @@ func (driver MediaTrack) Items() []base.Item {
Required: true, Required: true,
}, },
{ {
Name: "order_by", Name: "order_by",
Label: "order_by", Label: "order_by",
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "updated_at,title,size", Values: "updated_at,title,size",
Required: true, Required: true,
Description: "title",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -57,6 +58,7 @@ func (driver MediaTrack) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "true,false", Values: "true,false",
Required: true, Required: true,
Default: "false",
}, },
} }
} }
@ -147,9 +149,9 @@ func (driver MediaTrack) Path(path string, account *model.Account) (*model.File,
return nil, files, nil return nil, files, nil
} }
func (driver MediaTrack) Proxy(c *gin.Context, account *model.Account) { //func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) { func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotImplement return nil, base.ErrNotImplement
@ -265,21 +267,39 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
if err != nil { if err != nil {
return err return err
} }
var buf bytes.Buffer tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
read := io.TeeReader(file, &buf) if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s) uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{ input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket, Bucket: &resp.Data.Bucket,
Key: &resp.Data.Object, Key: &resp.Data.Object,
Body: read, Body: tempFile,
} }
_, err = uploader.Upload(input) _, err = uploader.Upload(input)
if err != nil { if err != nil {
return err return err
} }
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id) url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New() h := md5.New()
_, err = io.Copy(h, &buf) _, err = io.Copy(h, tempFile)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil" "io/ioutil"
@ -58,6 +57,9 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
} }
func (driver Native) File(path string, account *model.Account) (*model.File, error) { func (driver Native) File(path string, account *model.Account) (*model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound return nil, base.ErrPathNotFound
@ -82,6 +84,9 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
} }
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) { func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound return nil, base.ErrPathNotFound
@ -114,6 +119,10 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
} }
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) { func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
_, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
fullPath := filepath.Join(account.RootFolder, args.Path) fullPath := filepath.Join(account.RootFolder, args.Path)
s, err := os.Stat(fullPath) s, err := os.Stat(fullPath)
if err != nil { if err != nil {
@ -123,7 +132,7 @@ func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, e
return nil, base.ErrNotFile return nil, base.ErrNotFile
} }
link := base.Link{ link := base.Link{
Url: fullPath, FilePath: fullPath,
} }
return &link, nil return &link, nil
} }
@ -146,21 +155,27 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver Native) Proxy(c *gin.Context, account *model.Account) { //func (driver Native) Proxy(r *http.Request, account *model.Account) {
// unnecessary // // unnecessary
} //}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) { func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
} }
func (driver Native) MakeDir(path string, account *model.Account) error { func (driver Native) MakeDir(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
err := os.MkdirAll(fullPath, 0700) err := os.MkdirAll(fullPath, 0700)
return err return err
} }
func (driver Native) Move(src string, dst string, account *model.Account) error { func (driver Native) Move(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src) fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst) fullDst := filepath.Join(account.RootFolder, dst)
return os.Rename(fullSrc, fullDst) return os.Rename(fullSrc, fullDst)
@ -171,6 +186,9 @@ func (driver Native) Rename(src string, dst string, account *model.Account) erro
} }
func (driver Native) Copy(src string, dst string, account *model.Account) error { func (driver Native) Copy(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src) fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst) fullDst := filepath.Join(account.RootFolder, dst)
srcFile, err := driver.File(src, account) srcFile, err := driver.File(src, account)
@ -190,6 +208,9 @@ func (driver Native) Copy(src string, dst string, account *model.Account) error
} }
func (driver Native) Delete(path string, account *model.Account) error { func (driver Native) Delete(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
file, err := driver.File(path, account) file, err := driver.File(path, account)
if err != nil { if err != nil {
@ -205,6 +226,9 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name) fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account) _, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
if err == nil { if err == nil {
@ -224,6 +248,16 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
defer func() { defer func() {
_ = out.Close() _ = out.Close()
}() }()
//var buf bytes.Buffer
//reader := io.TeeReader(file, &buf)
//h := md5.New()
//_, err = io.Copy(h, reader)
//if err != nil {
// return err
//}
//hash := hex.EncodeToString(h.Sum(nil))
//log.Debugln("md5:", hash)
//_, err = io.Copy(out, &buf)
_, err = io.Copy(out, file) _, err = io.Copy(out, file)
return err return err
} }

View File

@ -6,8 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -92,9 +90,9 @@ func (driver Onedrive) Items() []base.Item {
} }
func (driver Onedrive) Save(account *model.Account, old *model.Account) error { func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
if old != nil { //if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId)) // conf.Cron.Remove(cron.EntryID(old.CronId))
} //}
if account == nil { if account == nil {
return nil return nil
} }
@ -104,28 +102,25 @@ func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
} }
account.RootFolder = utils.ParsePath(account.RootFolder) account.RootFolder = utils.ParsePath(account.RootFolder)
err := driver.RefreshToken(account) err := driver.RefreshToken(account)
_ = model.SaveAccount(account)
if err != nil { if err != nil {
return err return err
} }
cronId, err := conf.Cron.AddFunc("@every 1h", func() { //cronId, err := conf.Cron.AddFunc("@every 1h", func() {
name := account.Name // name := account.Name
log.Debugf("onedrive account name: %s", name) // log.Debugf("onedrive account name: %s", name)
newAccount, ok := model.GetAccount(name) // newAccount, ok := model.GetAccount(name)
log.Debugf("onedrive account: %+v", newAccount) // log.Debugf("onedrive account: %+v", newAccount)
if !ok { // if !ok {
return // return
} // }
err = driver.RefreshToken(&newAccount) // err = driver.RefreshToken(&newAccount)
_ = model.SaveAccount(&newAccount) // _ = model.SaveAccount(&newAccount)
}) //})
if err != nil { //if err != nil {
return err // return err
} //}
account.CronId = int(cronId) //account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil return nil
} }
@ -205,9 +200,9 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) { //func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
c.Request.Header.Del("Origin") // r.Header.Del("Origin")
} //}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) { func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -19,8 +19,6 @@ import (
"time" "time"
) )
var oneClient = resty.New()
type Host struct { type Host struct {
Oauth string Oauth string
Api string Api string
@ -47,7 +45,7 @@ var onedriveHostMap = map[string]Host{
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string { func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path) path = filepath.Join(account.RootFolder, path)
log.Debugf(path) //log.Debugf(path)
host, _ := onedriveHostMap[account.Zone] host, _ := onedriveHostMap[account.Zone]
if auth { if auth {
return host.Oauth return host.Oauth
@ -81,7 +79,7 @@ type OneTokenErr struct {
func (driver Onedrive) RefreshToken(account *model.Account) error { func (driver Onedrive) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account) err := driver.refreshToken(account)
if err != nil && err.Error() == "empty refresh_token" { if err != nil && err == base.ErrEmptyToken {
return driver.refreshToken(account) return driver.refreshToken(account)
} }
return err return err
@ -91,7 +89,7 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token" url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp var resp base.TokenResp
var e OneTokenErr var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token", "grant_type": "refresh_token",
"client_id": account.ClientId, "client_id": account.ClientId,
"client_secret": account.ClientSecret, "client_secret": account.ClientSecret,
@ -109,8 +107,8 @@ func (driver Onedrive) refreshToken(account *model.Account) error {
account.Status = "work" account.Status = "work"
} }
if resp.RefreshToken == "" { if resp.RefreshToken == "" {
account.Status = "empty refresh_token" account.Status = base.ErrEmptyToken.Error()
return errors.New("empty refresh_token") return base.ErrEmptyToken
} }
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil return nil
@ -178,16 +176,17 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
} }
for nextLink != "" { for nextLink != "" {
var files OneFiles var files OneFiles
var e OneRespErr _, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
_, err := oneClient.R().SetResult(&files).SetError(&e). //var e OneRespErr
SetHeader("Authorization", "Bearer "+account.AccessToken). //_, err := oneClient.R().SetResult(&files).SetError(&e).
Get(nextLink) // SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(nextLink)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Error.Code != "" { //if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message) // return nil, fmt.Errorf("%s", e.Error.Message)
} //}
res = append(res, files.Value...) res = append(res, files.Value...)
nextLink = files.NextLink nextLink = files.NextLink
} }
@ -196,16 +195,18 @@ func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile,
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) { func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
var file OneFile var file OneFile
var e OneRespErr //var e OneRespErr
_, err := oneClient.R().SetResult(&file).SetError(&e). u := driver.GetMetaUrl(account, false, path)
SetHeader("Authorization", "Bearer "+account.AccessToken). _, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
Get(driver.GetMetaUrl(account, false, path)) //_, err := oneClient.R().SetResult(&file).SetError(&e).
// SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(driver.GetMetaUrl(account, false, path))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if e.Error.Code != "" { //if e.Error.Code != "" {
return nil, fmt.Errorf("%s", e.Error.Message) // return nil, fmt.Errorf("%s", e.Error.Message)
} //}
return &file, nil return &file, nil
} }
@ -252,7 +253,7 @@ func (driver Onedrive) Request(url string, method int, headers, query, form map[
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) //log.Debug(res.String())
if e.Error.Code != "" { if e.Error.Code != "" {
if e.Error.Code == "InvalidAuthenticationToken" { if e.Error.Code == "InvalidAuthenticationToken" {
err = driver.RefreshToken(account) err = driver.RefreshToken(account)
@ -314,5 +315,4 @@ func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account)
func init() { func init() {
base.RegisterDriver(&Onedrive{}) base.RegisterDriver(&Onedrive{})
oneClient.SetRetryCount(3)
} }

View File

@ -5,8 +5,28 @@ import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"runtime/debug"
) )
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
return driver.Path(path, account)
}
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
_, files, err := Path(driver, account, path)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
return driver.File(path, account)
}
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error { func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
log.Debugf("mkdir: %s", path) log.Debugf("mkdir: %s", path)
err := driver.MakeDir(path, account) err := driver.MakeDir(path, account)
@ -78,5 +98,6 @@ func Upload(driver base.Driver, account *model.Account, file *model.FileStream,
if err != nil { if err != nil {
log.Errorf("upload error: %s", err.Error()) log.Errorf("upload error: %s", err.Error())
} }
debug.FreeOSMemory()
return err return err
} }

View File

@ -10,7 +10,6 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
@ -142,9 +141,9 @@ func (driver PikPak) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver PikPak) Proxy(c *gin.Context, account *model.Account) { //func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) { func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

320
drivers/quark/driver.go Normal file
View File

@ -0,0 +1,320 @@
package quark
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
)
type Quark struct{}
func (driver Quark) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Quark",
OnlyProxy: true,
}
}
func (driver Quark) Items() []base.Item {
return []base.Item{
{
Name: "access_token",
Label: "Cookie",
Type: base.TypeString,
Required: true,
Description: "Unknown expiration time",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
Default: "0",
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "file_type,file_name,updated_at",
Required: true,
Default: "file_name",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: true,
Default: "asc",
},
}
}
func (driver Quark) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
_, err := driver.Get("/config", nil, nil, account)
return err
}
func (driver Quark) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Quark) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Quark) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
data := base.Json{
"fids": []string{file.Id},
}
var resp DownResp
_, err = driver.Post("/file/download", data, &resp, account)
if err != nil {
return nil, err
}
return &base.Link{
Url: resp.Data[0].DownloadUrl,
Headers: []base.Header{
{Name: "Cookie", Value: account.AccessToken},
},
}, nil
}
func (driver Quark) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("quark path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Quark) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Quark) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"dir_init_lock": false,
"dir_path": "",
"file_name": utils.Base(path),
"pdir_fid": parentFile.Id,
}
_, err = driver.Post("/file", data, nil, account)
return err
}
func (driver Quark) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
"to_pdir_fid": dstParentFile.Id,
}
_, err = driver.Post("/file/move", data, nil, account)
return err
}
func (driver Quark) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
data := base.Json{
"fid": srcFile.Id,
"file_name": utils.Base(dst),
}
_, err = driver.Post("/file/rename", data, nil, account)
return err
}
func (driver Quark) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver Quark) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
}
_, err = driver.Post("/file/delete", data, nil, account)
return err
}
func (driver Quark) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
m := md5.New()
_, err = io.Copy(m, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
md5Str := hex.EncodeToString(m.Sum(nil))
s := sha1.New()
_, err = io.Copy(s, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
sha1Str := hex.EncodeToString(s.Sum(nil))
// pre
pre, err := driver.UpPre(file, parentFile.Id, account)
if err != nil {
return err
}
log.Debugln("hash: ", md5Str, sha1Str)
// hash
finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
if err != nil {
return err
}
if finish {
return nil
}
// part up
partSize := pre.Metadata.PartSize
var bytes []byte
md5s := make([]string, 0)
defaultBytes := make([]byte, partSize)
left := int64(file.GetSize())
partNumber := 1
for left > 0 {
if left > int64(partSize) {
bytes = defaultBytes
} else {
bytes = make([]byte, left)
}
_, err := io.ReadFull(tempFile, bytes)
if err != nil {
return err
}
left -= int64(partSize)
log.Debugf("left: %d", left)
m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account)
//m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account, md5Str, sha1Str)
if err != nil {
return err
}
if m == "finish" {
return nil
}
md5s = append(md5s, m)
partNumber++
}
err = driver.UpCommit(pre, md5s, account)
if err != nil {
return err
}
return driver.UpFinish(pre, account)
}
var _ base.Driver = (*Quark)(nil)

266
drivers/quark/quark.go Normal file
View File

@ -0,0 +1,266 @@
package quark
import (
"crypto/md5"
"encoding/base64"
"errors"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
"strings"
"time"
)
func (driver Quark) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://drive.quark.cn/1/clouddrive" + pathname
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Cookie": account.AccessToken,
"Accept": "application/json, text/plain, */*",
"Referer": "https://pan.quark.cn/",
})
req.SetQueryParam("pr", "ucpro")
req.SetQueryParam("fr", "pc")
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Delete:
res, err = req.Delete(u)
case base.Patch:
res, err = req.Patch(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debugf("%s response: %s", pathname, res.String())
if e.Status >= 400 || e.Code != 0 {
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver Quark) Get(pathname string, query map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, query, nil, nil, resp, account)
}
func (driver Quark) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
}
func (driver Quark) GetFiles(parent string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
page := 1
size := 100
query := map[string]string{
"pdir_fid": parent,
"_size": strconv.Itoa(size),
"_fetch_total": "1",
"_sort": "file_type:asc," + account.OrderBy + ":" + account.OrderDirection,
}
for {
query["_page"] = strconv.Itoa(page)
var resp SortResp
_, err := driver.Get("/file/sort", query, &resp, account)
if err != nil {
return nil, err
}
for _, f := range resp.Data.List {
files = append(files, *driver.formatFile(&f))
}
if page*size >= resp.Metadata.Count {
break
}
page++
}
return files, nil
}
func (driver Quark) UpPre(file *model.FileStream, parentId string, account *model.Account) (UpPreResp, error) {
now := time.Now()
data := base.Json{
"ccp_hash_update": true,
"dir_name": "",
"file_name": file.Name,
"format_type": file.MIMEType,
"l_created_at": now.UnixMilli(),
"l_updated_at": now.UnixMilli(),
"pdir_fid": parentId,
"size": file.Size,
}
log.Debugf("uppre data: %+v", data)
var resp UpPreResp
_, err := driver.Post("/file/upload/pre", data, &resp, account)
return resp, err
}
func (driver Quark) UpHash(md5, sha1, taskId string, account *model.Account) (bool, error) {
data := base.Json{
"md5": md5,
"sha1": sha1,
"task_id": taskId,
}
log.Debugf("hash: %+v", data)
var resp HashResp
_, err := driver.Post("/file/update/hash", data, &resp, account)
return resp.Data.Finish, err
}
func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account) (string, error) {
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
timeStr := time.Now().UTC().Format(http.TimeFormat)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`PUT
%s
%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?partNumber=%d&uploadId=%s`,
mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
var resp UpAuthResp
_, err := driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return "", err
}
//if partNumber == 1 {
// finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
// if err != nil {
// return "", err
// }
// if finish {
// return "finish", nil
// }
//}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-Type": mineType,
"Referer": "https://pan.quark.cn/",
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"partNumber": strconv.Itoa(partNumber),
"uploadId": pre.Data.UploadId,
}).SetBody(bytes).Put(u)
if res.StatusCode() != 200 {
return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return res.Header().Get("ETag"), nil
}
func (driver Quark) UpCommit(pre UpPreResp, md5s []string, account *model.Account) error {
timeStr := time.Now().UTC().Format(http.TimeFormat)
log.Debugf("md5s: %+v", md5s)
bodyBuilder := strings.Builder{}
bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUpload>
`)
for i, m := range md5s {
bodyBuilder.WriteString(fmt.Sprintf(`<Part>
<PartNumber>%d</PartNumber>
<ETag>%s</ETag>
</Part>
`, i+1, m))
}
bodyBuilder.WriteString("</CompleteMultipartUpload>")
body := bodyBuilder.String()
m := md5.New()
m.Write([]byte(body))
contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil))
callbackBytes, err := utils.Json.Marshal(pre.Data.Callback)
if err != nil {
return err
}
callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`POST
%s
application/xml
%s
x-oss-callback:%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?uploadId=%s`,
contentMd5, timeStr, callbackBase64, timeStr,
pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
log.Debugf("xml: %s", body)
log.Debugf("auth data: %+v", data)
var resp UpAuthResp
_, err = driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return err
}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-MD5": contentMd5,
"Content-Type": "application/xml",
"Referer": "https://pan.quark.cn/",
"x-oss-callback": callbackBase64,
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"uploadId": pre.Data.UploadId,
}).SetBody(body).Post(u)
if res.StatusCode() != 200 {
return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return nil
}
func (driver Quark) UpFinish(pre UpPreResp, account *model.Account) error {
data := base.Json{
"obj_key": pre.Data.ObjKey,
"task_id": pre.Data.TaskId,
}
_, err := driver.Post("/file/upload/finish", data, nil, account)
if err != nil {
return err
}
time.Sleep(time.Second)
return nil
}
func init() {
base.RegisterDriver(&Quark{})
}

134
drivers/quark/types.go Normal file
View File

@ -0,0 +1,134 @@
package quark
type Resp struct {
Status int `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
//ReqId string `json:"req_id"`
//Timestamp int `json:"timestamp"`
}
type File struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
//PdirFid string `json:"pdir_fid"`
//Category int `json:"category"`
//FileType int `json:"file_type"`
Size int64 `json:"size"`
//FormatType string `json:"format_type"`
//Status int `json:"status"`
//Tags string `json:"tags,omitempty"`
//LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
//NameSpace int `json:"name_space"`
//IncludeItems int `json:"include_items,omitempty"`
//RiskType int `json:"risk_type"`
//BackupSign int `json:"backup_sign"`
//Duration int `json:"duration"`
//FileSource string `json:"file_source"`
File bool `json:"file"`
//CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
//PrivateExtra struct {} `json:"_private_extra"`
//ObjCategory string `json:"obj_category,omitempty"`
//Thumbnail string `json:"thumbnail,omitempty"`
}
type SortResp struct {
Resp
Data struct {
List []File `json:"list"`
} `json:"data"`
Metadata struct {
Size int `json:"_size"`
Page int `json:"_page"`
Count int `json:"_count"`
Total int `json:"_total"`
Way string `json:"way"`
} `json:"metadata"`
}
type DownResp struct {
Resp
Data []struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
PdirFid string `json:"pdir_fid"`
Category int `json:"category"`
FileType int `json:"file_type"`
Size int `json:"size"`
FormatType string `json:"format_type"`
Status int `json:"status"`
Tags string `json:"tags"`
LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
NameSpace int `json:"name_space"`
Thumbnail string `json:"thumbnail"`
DownloadUrl string `json:"download_url"`
Md5 string `json:"md5"`
RiskType int `json:"risk_type"`
RangeSize int `json:"range_size"`
BackupSign int `json:"backup_sign"`
ObjCategory string `json:"obj_category"`
Duration int `json:"duration"`
FileSource string `json:"file_source"`
File bool `json:"file"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PrivateExtra struct {
} `json:"_private_extra"`
} `json:"data"`
Metadata struct {
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
} `json:"metadata"`
}
type UpPreResp struct {
Resp
Data struct {
TaskId string `json:"task_id"`
Finish bool `json:"finish"`
UploadId string `json:"upload_id"`
ObjKey string `json:"obj_key"`
UploadUrl string `json:"upload_url"`
Fid string `json:"fid"`
Bucket string `json:"bucket"`
Callback struct {
CallbackUrl string `json:"callbackUrl"`
CallbackBody string `json:"callbackBody"`
} `json:"callback"`
FormatType string `json:"format_type"`
Size int `json:"size"`
AuthInfo string `json:"auth_info"`
} `json:"data"`
Metadata struct {
PartThread int `json:"part_thread"`
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
PartSize int `json:"part_size"` // 分片大小
} `json:"metadata"`
}
type HashResp struct {
Resp
Data struct {
Finish bool `json:"finish"`
Fid string `json:"fid"`
Thumbnail string `json:"thumbnail"`
FormatType string `json:"format_type"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}
type UpAuthResp struct {
Resp
Data struct {
AuthKey string `json:"auth_key"`
Speed int `json:"speed"`
Headers []interface{} `json:"headers"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}

31
drivers/quark/util.go Normal file
View File

@ -0,0 +1,31 @@
package quark
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path"
"time"
)
func getTime(t int64) *time.Time {
tm := time.UnixMilli(t)
//log.Debugln(tm)
return &tm
}
func (driver Quark) formatFile(f *File) *model.File {
file := model.File{
Id: f.Fid,
Name: f.FileName,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.UpdatedAt),
}
if f.File {
file.Type = utils.GetFileType(path.Ext(f.FileName))
} else {
file.Type = conf.FOLDER
}
return &file
}

View File

@ -8,7 +8,6 @@ import (
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/url" "net/url"
"path/filepath" "path/filepath"
@ -77,9 +76,21 @@ func (driver S3) Items() []base.Item {
{ {
Name: "zone", Name: "zone",
Label: "placeholder filename", Label: "placeholder filename",
Type: base.TypeNumber, Type: base.TypeString,
Description: "default empty string", Description: "default empty string",
}, },
{
Name: "bool_1",
Label: "S3ForcePathStyle",
Type: base.TypeBool,
},
{
Name: "internal_type",
Label: "ListObject Version",
Type: base.TypeSelect,
Values: "v1,v2",
Default: "v1",
},
} }
} }
@ -133,7 +144,11 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
if err == nil { if err == nil {
files, _ = cache.([]model.File) files, _ = cache.([]model.File)
} else { } else {
files, err = driver.List(path, account) if account.InternalType == "v2" {
files, err = driver.ListV2(path, account)
} else {
files, err = driver.List(path, account)
}
if err == nil && len(files) > 0 { if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account) _ = base.SetCache(path, files, account)
} }
@ -142,7 +157,7 @@ func (driver S3) Files(path string, account *model.Account) ([]model.File, error
} }
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) { func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -189,9 +204,9 @@ func (driver S3) Path(path string, account *model.Account) (*model.File, []model
return nil, files, nil return nil, files, nil
} }
func (driver S3) Proxy(c *gin.Context, account *model.Account) { //func (driver S3) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) { func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -215,7 +230,7 @@ func (driver S3) Rename(src string, dst string, account *model.Account) error {
} }
func (driver S3) Copy(src string, dst string, account *model.Account) error { func (driver S3) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return err return err
} }
@ -235,7 +250,7 @@ func (driver S3) Copy(src string, dst string, account *model.Account) error {
} }
func (driver S3) Delete(path string, account *model.Account) error { func (driver S3) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,6 +1,7 @@
package s3 package s3
import ( import (
"errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
@ -22,20 +23,21 @@ var sessionsMap map[string]*session.Session
func (driver S3) NewSession(account *model.Account) (*session.Session, error) { func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
cfg := &aws.Config{ cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""), Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
Region: &account.Region, Region: &account.Region,
Endpoint: &account.Endpoint, Endpoint: &account.Endpoint,
S3ForcePathStyle: aws.Bool(account.Bool1),
} }
return session.NewSession(cfg) return session.NewSession(cfg)
} }
func (driver S3) GetClient(account *model.Account) (*s3.S3, error) { func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
s, ok := sessionsMap[account.Name] s, ok := sessionsMap[account.Name]
if !ok { if !ok {
return nil, fmt.Errorf("can't find [%s] session", account.Name) return nil, fmt.Errorf("can't find [%s] session", account.Name)
} }
client := s3.New(s) client := s3.New(s)
if account.CustomHost != "" { if link && account.CustomHost != "" {
cURL, err := url.Parse(account.CustomHost) cURL, err := url.Parse(account.CustomHost)
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,7 +56,7 @@ func (driver S3) GetClient(account *model.Account) (*s3.S3, error) {
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) { func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true) prefix = driver.GetKey(prefix, account, true)
log.Debugf("list: %s", prefix) log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account) client, err := driver.GetClient(account, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -99,6 +101,9 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
} }
files = append(files, file) files = append(files, file)
} }
if listObjectsResult.IsTruncated == nil {
return nil, errors.New("IsTruncated nil")
}
if *listObjectsResult.IsTruncated { if *listObjectsResult.IsTruncated {
marker = *listObjectsResult.NextMarker marker = *listObjectsResult.NextMarker
} else { } else {
@ -108,6 +113,73 @@ func (driver S3) List(prefix string, account *model.Account) ([]model.File, erro
return files, nil return files, nil
} }
func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
//if prefix == "" {
// prefix = "/"
//}
log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account, false)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
var continuationToken, startAfter *string
for {
input := &s3.ListObjectsV2Input{
Bucket: &account.Bucket,
ContinuationToken: continuationToken,
Prefix: &prefix,
Delimiter: aws.String("/"),
StartAfter: startAfter,
}
listObjectsResult, err := client.ListObjectsV2(input)
if err != nil {
return nil, err
}
log.Debugf("resp: %+v", listObjectsResult)
for _, object := range listObjectsResult.CommonPrefixes {
name := utils.Base(strings.Trim(*object.Prefix, "/"))
file := model.File{
//Id: *object.Key,
Name: name,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
TimeStr: "-",
Type: conf.FOLDER,
}
files = append(files, file)
}
for _, object := range listObjectsResult.Contents {
name := utils.Base(*object.Key)
if name == account.Zone {
continue
}
file := model.File{
//Id: *object.Key,
Name: name,
Size: *object.Size,
Driver: driver.Config().Name,
UpdatedAt: object.LastModified,
Type: utils.GetFileType(path.Ext(*object.Key)),
}
files = append(files, file)
}
if !aws.BoolValue(listObjectsResult.IsTruncated) {
break
}
if listObjectsResult.NextContinuationToken != nil {
continuationToken = listObjectsResult.NextContinuationToken
continue
}
if len(listObjectsResult.Contents) == 0 {
break
}
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key
}
return files, nil
}
func (driver S3) GetKey(path string, account *model.Account, dir bool) string { func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
path = utils.Join(account.RootFolder, path) path = utils.Join(account.RootFolder, path)
path = strings.TrimPrefix(path, "/") path = strings.TrimPrefix(path, "/")
@ -118,6 +190,6 @@ func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
} }
func init() { func init() {
sessionsMap = make(map[string]*session.Session, 0) sessionsMap = make(map[string]*session.Session)
base.RegisterDriver(&S3{}) base.RegisterDriver(&S3{})
} }

View File

@ -6,7 +6,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -154,9 +153,9 @@ func (driver Shandian) Path(path string, account *model.Account) (*model.File, [
return nil, files, nil return nil, files, nil
} }
func (driver Shandian) Proxy(c *gin.Context, account *model.Account) { //func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) { func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -52,6 +51,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "fileName,fileSize,updated,created", Values: "fileName,fileSize,updated,created",
Required: true, Required: true,
Default: "fileName",
}, },
{ {
Name: "order_direction", Name: "order_direction",
@ -59,6 +59,7 @@ func (driver Teambition) Items() []base.Item {
Type: base.TypeSelect, Type: base.TypeSelect,
Values: "Asc,Desc", Values: "Asc,Desc",
Required: true, Required: true,
Default: "Asc",
}, },
} }
} }
@ -149,9 +150,9 @@ func (driver Teambition) Path(path string, account *model.Account) (*model.File,
return nil, files, nil return nil, files, nil
} }
func (driver Teambition) Proxy(c *gin.Context, account *model.Account) { //func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) { func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport
@ -252,7 +253,34 @@ func (driver Teambition) Delete(path string, account *model.Account) error {
} }
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error { func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if err != nil {
return err
}
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return err
}
token := GetBetweenStr(string(res), "strikerAuth&quot;:&quot;", "&quot;,&quot;phoneForLogin")
var newFile *FileUpload
if file.Size <= 20971520 {
// post upload
newFile, err = driver.upload(file, token, account)
} else {
// chunk upload
//err = base.ErrNotImplement
newFile, err = driver.chunkUpload(file, token, account)
}
if err != nil {
return err
}
return driver.finishUpload(newFile, parentFile.Id, account)
} }
var _ base.Driver = (*Teambition)(nil) var _ base.Driver = (*Teambition)(nil)

View File

@ -2,11 +2,14 @@ package teambition
import ( import (
"errors" "errors"
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"io"
"path" "path"
"strconv" "strconv"
"time" "time"
@ -66,25 +69,6 @@ func (driver Teambition) Request(pathname string, method int, headers, query, fo
return res.Body(), nil return res.Body(), nil
} }
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) { func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0) files := make([]model.File, 0)
page := 1 page := 1
@ -151,6 +135,103 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
return files, nil return files, nil
} }
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newFile FileUpload
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
SetMultipartFormData(map[string]string{
"name": file.GetFileName(),
"type": file.GetMIMEType(),
"size": strconv.FormatUint(file.GetSize(), 10),
//"lastModifiedDate": "",
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
if err != nil {
return nil, err
}
return &newFile, nil
}
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
referer := "https://www.teambition.com/"
if account.InternalType == "International" {
prefix = "us-tcs"
referer = "https://us.teambition.com/"
}
var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
SetBody(base.Json{
"fileName": file.GetFileName(),
"fileSize": file.GetSize(),
"lastUpdated": time.Now(),
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
if err != nil {
return nil, err
}
for i := 0; i < newChunk.Chunks; i++ {
chunkSize := newChunk.ChunkSize
if i == newChunk.Chunks-1 {
chunkSize = int(file.GetSize()) - i*chunkSize
}
log.Debugf("%d : %d", i, chunkSize)
chunkData := make([]byte, chunkSize)
_, err = io.ReadFull(file, chunkData)
if err != nil {
return nil, err
}
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
log.Debugf("url: %s", u)
res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token,
"Content-Type": "application/octet-stream",
"Referer": referer,
}).SetBody(chunkData).Post(u)
if err != nil {
return nil, err
}
log.Debug(res.Status(), res.String())
//req, err := http.NewRequest("POST",
// u,
// bytes.NewBuffer(chunkData))
//if err != nil {
// return nil, err
//}
//req.Header.Set("Authorization", token)
//req.Header.Set("Content-Type", "application/octet-stream")
//req.Header.Set("Referer", "https://www.teambition.com/")
//resp, err := base.HttpClient.Do(req)
//res, _ := ioutil.ReadAll(resp.Body)
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
if err != nil {
return nil, err
}
}
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
prefix, newChunk.FileKey))
log.Debug(res.Status(), res.String())
if err != nil {
return nil, err
}
return &newChunk.FileUpload, nil
}
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
file.InvolveMembers = []interface{}{}
file.Visible = "members"
file.ParentId = parentId
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
"works": []FileUpload{*file},
"_parentId": parentId,
}, nil, account)
return err
}
func init() { func init() {
base.RegisterDriver(&Teambition{}) base.RegisterDriver(&Teambition{})
} }

View File

@ -0,0 +1,63 @@
package teambition
import "time"
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
type FileUpload struct {
FileKey string `json:"fileKey"`
FileName string `json:"fileName"`
FileType string `json:"fileType"`
FileSize int `json:"fileSize"`
FileCategory string `json:"fileCategory"`
ImageWidth int `json:"imageWidth"`
ImageHeight int `json:"imageHeight"`
InvolveMembers []interface{} `json:"involveMembers"`
Source string `json:"source"`
Visible string `json:"visible"`
ParentId string `json:"_parentId"`
}
type ChunkUpload struct {
FileUpload
Storage string `json:"storage"`
MimeType string `json:"mimeType"`
Chunks int `json:"chunks"`
ChunkSize int `json:"chunkSize"`
Created time.Time `json:"created"`
FileMD5 string `json:"fileMD5"`
LastUpdated time.Time `json:"lastUpdated"`
UploadedChunks []interface{} `json:"uploadedChunks"`
Token struct {
AppID string `json:"AppID"`
OrganizationID string `json:"OrganizationID"`
UserID string `json:"UserID"`
Exp time.Time `json:"Exp"`
Storage string `json:"Storage"`
Resource string `json:"Resource"`
Speed int `json:"Speed"`
} `json:"token"`
DownloadUrl string `json:"downloadUrl"`
ThumbnailUrl string `json:"thumbnailUrl"`
PreviewUrl string `json:"previewUrl"`
ImmPreviewUrl string `json:"immPreviewUrl"`
PreviewExt string `json:"previewExt"`
LastUploadTime interface{} `json:"lastUploadTime"`
}

View File

@ -0,0 +1,18 @@
package teambition
import "strings"
func GetBetweenStr(str, start, end string) string {
n := strings.Index(str, start)
if n == -1 {
return ""
}
n = n + len(start)
str = string([]byte(str)[n:])
m := strings.Index(str, end)
if m == -1 {
return ""
}
str = string([]byte(str)[:m])
return str
}

View File

@ -5,7 +5,6 @@ import (
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath" "path/filepath"
) )
@ -137,9 +136,9 @@ func (driver WebDav) Path(path string, account *model.Account) (*model.File, []m
return nil, files, nil return nil, files, nil
} }
func (driver WebDav) Proxy(c *gin.Context, account *model.Account) { //func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
//
} //}
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) { func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport return nil, base.ErrNotSupport

224
drivers/yandex/driver.go Normal file
View File

@ -0,0 +1,224 @@
package yandex
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net/http"
"path/filepath"
"strconv"
)
type Yandex struct{}
func (driver Yandex) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Yandex.Disk",
}
}
func (driver Yandex) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Default: "name",
Values: "name,path,created,modified,size",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Default: "asc",
Required: false,
},
{
Name: "client_id",
Label: "client id",
Default: "a78d5a69054042fa936f6c77f9a0ae8b",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "9c119bbb04b346d2a52aa64401936b2b",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Yandex) Save(account *model.Account, old *model.Account) error {
return driver.RefreshToken(account)
}
func (driver Yandex) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Yandex) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
files, err := driver.GetFiles(path, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Yandex) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := utils.Join(account.RootFolder, args.Path)
log.Debugln("down path:", path)
var resp DownResp
_, err := driver.Request("/download", base.Get, nil, map[string]string{
"path": path,
}, nil, nil, &resp, account)
if err != nil {
return nil, err
}
link := base.Link{
Url: resp.Href,
}
return &link, nil
}
func (driver Yandex) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Yandex) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Yandex) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Yandex) MakeDir(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
_, err := driver.Request("", base.Put, nil, map[string]string{
"path": path,
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Move(src string, dst string, account *model.Account) error {
from := utils.Join(account.RootFolder, src)
path := utils.Join(account.RootFolder, dst)
_, err := driver.Request("/move", base.Post, nil, map[string]string{
"from": from,
"path": path,
"overwrite": "true",
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver Yandex) Copy(src string, dst string, account *model.Account) error {
from := utils.Join(account.RootFolder, src)
path := utils.Join(account.RootFolder, dst)
_, err := driver.Request("/copy", base.Post, nil, map[string]string{
"from": from,
"path": path,
"overwrite": "true",
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
_, err := driver.Request("", base.Delete, nil, map[string]string{
"path": path,
}, nil, nil, nil, account)
return err
}
func (driver Yandex) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
path := utils.Join(account.RootFolder, file.ParentPath, file.Name)
var resp UploadResp
_, err := driver.Request("/upload", base.Get, nil, map[string]string{
"path": path,
"overwrite": "true",
}, nil, nil, &resp, account)
if err != nil {
return err
}
req, err := http.NewRequest(resp.Method, resp.Href, file)
if err != nil {
return err
}
req.Header.Set("Content-Length", strconv.FormatUint(file.Size, 10))
req.Header.Set("Content-Type", "application/octet-stream")
_, err = base.HttpClient.Do(req)
//res, err := base.RestyClient.R().
// SetHeader("Content-Length", strconv.FormatUint(file.Size, 10)).
// SetBody(file).Put(resp.Href)
//log.Debugln(res.Status(), res.String())
return err
}
var _ base.Driver = (*Yandex)(nil)

74
drivers/yandex/types.go Normal file
View File

@ -0,0 +1,74 @@
package yandex
import "time"
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
}
type ErrResp struct {
Message string `json:"message"`
Description string `json:"description"`
Error string `json:"error"`
}
type File struct {
//AntivirusStatus string `json:"antivirus_status"`
Size int64 `json:"size"`
//CommentIds struct {
// PrivateResource string `json:"private_resource"`
// PublicResource string `json:"public_resource"`
//} `json:"comment_ids"`
Name string `json:"name"`
//Exif struct {
// DateTime time.Time `json:"date_time"`
//} `json:"exif"`
//Created time.Time `json:"created"`
//ResourceId string `json:"resource_id"`
Modified time.Time `json:"modified"`
//MimeType string `json:"mime_type"`
File string `json:"file"`
//MediaType string `json:"media_type"`
Preview string `json:"preview"`
Path string `json:"path"`
//Sha256 string `json:"sha256"`
Type string `json:"type"`
//Md5 string `json:"md5"`
//Revision int64 `json:"revision"`
}
type FilesResp struct {
Embedded struct {
Sort string `json:"sort"`
Items []File `json:"items"`
Limit int `json:"limit"`
Offset int `json:"offset"`
Path string `json:"path"`
Total int `json:"total"`
} `json:"_embedded"`
Name string `json:"name"`
Exif struct {
} `json:"exif"`
ResourceId string `json:"resource_id"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
Path string `json:"path"`
CommentIds struct {
} `json:"comment_ids"`
Type string `json:"type"`
Revision int64 `json:"revision"`
}
type DownResp struct {
Href string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
}
type UploadResp struct {
OperationId string `json:"operation_id"`
Href string `json:"href"`
Method string `json:"method"`
Templated bool `json:"templated"`
}

1
drivers/yandex/util.go Normal file
View File

@ -0,0 +1 @@
package yandex

154
drivers/yandex/yandex.go Normal file
View File

@ -0,0 +1,154 @@
package yandex
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"path"
"strconv"
)
func (driver Yandex) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Yandex) refreshToken(account *model.Account) error {
u := "https://oauth.yandex.com/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Post(u)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Yandex) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://cloud-api.yandex.net/v1/disk/resources" + pathname
req := base.RestyClient.R()
req.SetHeader("Authorization", "OAuth "+account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
var e ErrResp
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Patch:
res, err = req.Patch(u)
case base.Delete:
res, err = req.Delete(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
if e.Error != "" {
if e.Error == "UnauthorizedError" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(pathname, method, headers, query, form, data, resp, account)
}
return nil, errors.New(e.Description)
}
return res.Body(), nil
}
func (driver Yandex) GetFiles(rawPath string, account *model.Account) ([]model.File, error) {
path_ := utils.Join(account.RootFolder, rawPath)
limit := 100
page := 1
res := make([]model.File, 0)
for {
offset := (page - 1) * limit
query := map[string]string{
"path": path_,
"limit": strconv.Itoa(limit),
"offset": strconv.Itoa(offset),
}
if account.OrderBy != "" {
if account.OrderDirection == "desc" {
query["sort"] = "-" + account.OrderBy
} else {
query["sort"] = account.OrderBy
}
}
var resp FilesResp
_, err := driver.Request("", base.Get, nil, query, nil, nil, &resp, account)
if err != nil {
return nil, err
}
for _, file := range resp.Embedded.Items {
f := model.File{
Name: file.Name,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: &file.Modified,
Thumbnail: file.Preview,
Url: file.File,
}
if file.Type == "dir" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(path.Ext(file.Name))
}
res = append(res, f)
}
if resp.Embedded.Total <= offset+limit {
break
}
}
return res, nil
}
func init() {
base.RegisterDriver(&Yandex{})
}

View File

@ -2,6 +2,9 @@ package model
import ( import (
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
"strings"
"sync"
"time" "time"
) )
@ -30,8 +33,9 @@ type Account struct {
SiteUrl string `json:"site_url"` SiteUrl string `json:"site_url"`
SiteId string `json:"site_id"` SiteId string `json:"site_id"`
InternalType string `json:"internal_type"` InternalType string `json:"internal_type"`
WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转 WebdavProxy bool `json:"webdav_proxy"` // 开启之后只会webdav走中转
Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转 Proxy bool `json:"proxy"` // 是否中转,开启之后web和webdav都会走中转
WebdavDirect bool `json:"webdav_direct"` // webdav 下载不跳转
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载 //AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302 DownProxyUrl string `json:"down_proxy_url"` // 用于中转下载服务的URL 两处 1. path请求中返回的链接 2. down下载时进行302
APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址 APIProxyUrl string `json:"api_proxy_url"` // 用于中转api的地址
@ -43,9 +47,12 @@ type Account struct {
AccessSecret string `json:"access_secret"` AccessSecret string `json:"access_secret"`
CustomHost string `json:"custom_host"` CustomHost string `json:"custom_host"`
ExtractFolder string `json:"extract_folder"` ExtractFolder string `json:"extract_folder"`
Bool1 bool `json:"bool_1"`
} }
var accountsMap = map[string]Account{} var accountsMap = make(map[string]Account)
var balance = ".balance"
// SaveAccount save account to database // SaveAccount save account to database
func SaveAccount(account *Account) error { func SaveAccount(account *Account) error {
@ -100,6 +107,46 @@ func GetAccount(name string) (Account, bool) {
return account, ok return account, ok
} }
func GetAccountsByName(name string) []Account {
accounts := make([]Account, 0)
if AccountsCount() == 1 {
account, _ := GetAccount("")
accounts = append(accounts, account)
return accounts
}
for _, v := range accountsMap {
if v.Name == name || v.Name == name+balance {
accounts = append(accounts, v)
}
}
return accounts
}
var balanceMap sync.Map
func GetBalancedAccount(name string) (Account, bool) {
accounts := GetAccountsByName(name)
accountNum := len(accounts)
switch accountNum {
case 0:
return Account{}, false
case 1:
return accounts[0], true
default:
cur, ok := balanceMap.Load(name)
if ok {
i := cur.(int)
i = (i + 1) % accountNum
balanceMap.Store(name, i)
log.Debugln("use: ", i)
return accounts[i], true
} else {
balanceMap.Store(name, 0)
return accounts[0], true
}
}
}
func GetAccountById(id uint) (*Account, error) { func GetAccountById(id uint) (*Account, error) {
var account Account var account Account
account.ID = id account.ID = id
@ -116,6 +163,9 @@ func GetAccountFiles() ([]File, error) {
return nil, err return nil, err
} }
for _, v := range accounts { for _, v := range accounts {
if strings.HasSuffix(v.Name, balance) {
continue
}
files = append(files, File{ files = append(files, File{
Name: v.Name, Name: v.Name,
Size: 0, Size: 0,

View File

@ -97,7 +97,7 @@ func LoadSettings() {
favicon, err := GetSettingByKey("favicon") favicon, err := GetSettingByKey("favicon")
if err == nil { if err == nil {
//conf.Favicon = favicon.Value //conf.Favicon = favicon.Value
conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png", favicon.Value, 1) conf.ManageHtml = strings.Replace(conf.RawIndexHtml, "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg", favicon.Value, 1)
} }
title, err := GetSettingByKey("title") title, err := GetSettingByKey("title")
if err == nil { if err == nil {
@ -114,7 +114,11 @@ func LoadSettings() {
// token // token
adminPassword, err := GetSettingByKey("password") adminPassword, err := GetSettingByKey("password")
if err == nil { if err == nil {
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value)) if adminPassword.Value != "" {
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
} else {
conf.Token = ""
}
} }
// load settings // load settings
for _, key := range conf.LoadSettings { for _, key := range conf.LoadSettings {

View File

@ -40,7 +40,7 @@ func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
path = "/" + strings.Join(paths[2:], "/") path = "/" + strings.Join(paths[2:], "/")
name = paths[1] name = paths[1]
} }
account, ok := model.GetAccount(name) account, ok := model.GetBalancedAccount(name)
if !ok { if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name) return nil, "", nil, fmt.Errorf("no [%s] account", name)
} }

89
server/common/proxy.go Normal file
View File

@ -0,0 +1,89 @@
package common
import (
"errors"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
)
var HttpClient = &http.Client{}
func Proxy(w http.ResponseWriter, r *http.Request, link *base.Link, file *model.File) error {
// 本机读取数据
var err error
if link.Data != nil {
//c.Data(http.StatusOK, "application/octet-stream", link.Data)
defer func() {
_ = link.Data.Close()
}()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
w.Header().Set("Content-Length", strconv.FormatInt(file.Size, 10))
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, link.Data)
if err != nil {
return err
}
return nil
}
// 本机文件直接返回文件
if link.FilePath != "" {
f, err := os.Open(link.FilePath)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
fileStat, err := os.Stat(link.FilePath)
if err != nil {
return err
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
http.ServeContent(w, r, file.Name, fileStat.ModTime(), f)
return nil
} else {
req, err := http.NewRequest(r.Method, link.Url, nil)
if err != nil {
return err
}
for h, val := range r.Header {
req.Header[h] = val
}
for _, header := range link.Headers {
req.Header.Set(header.Name, header.Value)
}
res, err := HttpClient.Do(req)
if err != nil {
return err
}
defer func() {
_ = res.Body.Close()
}()
log.Debugf("proxy status: %d", res.StatusCode)
for h, v := range res.Header {
w.Header()[h] = v
}
w.WriteHeader(res.StatusCode)
if res.StatusCode >= 400 {
all, _ := ioutil.ReadAll(res.Body)
msg := string(all)
log.Debugln(msg)
return errors.New(msg)
}
_, err = io.Copy(w, res.Body)
if err != nil {
return err
}
return nil
}
}

View File

@ -0,0 +1,61 @@
package file
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
func Copy(c *gin.Context) {
var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
if len(req.Names) == 0 {
common.ErrorStrResp(c, "Empty file names", 400)
return
}
if model.AccountsCount() > 1 && (req.SrcDir == "/" || req.DstDir == "/") {
common.ErrorStrResp(c, "Can't operate root folder", 400)
return
}
srcAccount, srcPath, srcDriver, err := common.ParsePath(utils.Join(req.SrcDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
dstAccount, dstPath, _, err := common.ParsePath(utils.Join(req.DstDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if srcAccount.Name != dstAccount.Name {
common.ErrorStrResp(c, "Can't copy files between two accounts", 400)
return
}
if srcPath == "/" || dstPath == "/" {
common.ErrorStrResp(c, "Can't copy root folder", 400)
return
}
srcDir, dstDir := utils.Dir(srcPath), utils.Dir(dstPath)
for i, name := range req.Names {
clearCache := false
if i == len(req.Names)-1 {
clearCache = true
}
err := operate.Copy(srcDriver, srcAccount, utils.Join(srcDir, name), utils.Join(dstDir, name), clearCache)
if err != nil {
if i == 0 {
_ = base.DeleteCache(srcDir, srcAccount)
_ = base.DeleteCache(dstDir, dstAccount)
}
common.ErrorResp(c, err, 500)
return
}
}
common.SuccessResp(c)
}

View File

@ -1,6 +1,7 @@
package file package file
import ( import (
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -30,7 +31,7 @@ func Folder(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
file, rawFiles, err := driver.Path(path, account) file, rawFiles, err := operate.Path(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -9,14 +9,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type MoveReq struct { type MoveCopyReq struct {
SrcDir string `json:"src_dir"` SrcDir string `json:"src_dir"`
DstDir string `json:"dst_dir"` DstDir string `json:"dst_dir"`
Names []string `json:"names"` Names []string `json:"names"`
} }
func Move(c *gin.Context) { func Move(c *gin.Context) {
var req MoveReq var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return

View File

@ -24,7 +24,7 @@ func Rename(c *gin.Context) {
return return
} }
if path_ == "/" { if path_ == "/" {
common.ErrorStrResp(c, "Can't account name here", 400) common.ErrorStrResp(c, "Can't edit account name here", 400)
return return
} }
err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true) err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true)

View File

@ -0,0 +1,65 @@
package controllers
import (
"encoding/base64"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"strings"
)
func Favicon(c *gin.Context) {
c.Redirect(302, conf.GetStr("favicon"))
}
func Plist(c *gin.Context) {
data := c.Param("data")
data = strings.ReplaceAll(data, "_", "/")
data = strings.ReplaceAll(data, "-", "=")
bytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
u := string(bytes)
name := utils.Base(u)
ipaIndex := strings.Index(name, ".ipa")
if ipaIndex != -1 {
name = name[:ipaIndex]
}
plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>%s</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>ci.nn.%s</string>
<key>bundle-version</key>
<string>4.4</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>%s</string>
</dict>
</dict>
</array>
</dict>
</plist>`, u, name, name)
c.Header("Content-Type", "application/xml;charset=utf-8")
c.Status(200)
_, _ = c.Writer.WriteString(plist)
}

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
@ -109,7 +110,7 @@ func Path(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
file, files, err := driver.Path(path, account) file, files, err := operate.Path(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -4,17 +4,13 @@ import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"net/http"
"net/url"
"os"
"path/filepath" "path/filepath"
"strconv"
) )
func Proxy(c *gin.Context) { func Proxy(c *gin.Context) {
@ -50,7 +46,7 @@ func Proxy(c *gin.Context) {
return return
} }
// 检查文件 // 检查文件
file, err := driver.File(path, account) file, err := operate.File(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
@ -61,92 +57,17 @@ func Proxy(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
// 本机读取数据 err = common.Proxy(c.Writer, c.Request, link, file)
if link.Data != nil { if err != nil {
//c.Data(http.StatusOK, "application/octet-stream", link.Data) common.ErrorResp(c, err, 500)
defer func() {
_ = link.Data.Close()
}()
c.Status(http.StatusOK)
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename=%s`, url.QueryEscape(file.Name)))
c.Header("Content-Length", strconv.FormatInt(file.Size, 10))
_, err = io.Copy(c.Writer, link.Data)
if err != nil {
_, _ = c.Writer.WriteString(err.Error())
}
return
}
// 本机文件直接返回文件
if account.Type == "Native" {
// 对于名称为index.html的文件需要特殊处理
if utils.Base(rawPath) == "index.html" {
file, err := os.Open(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
defer func() {
_ = file.Close()
}()
fileStat, err := os.Stat(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
http.ServeContent(c.Writer, c.Request, utils.Base(rawPath), fileStat.ModTime(), file)
return
}
c.File(link.Url)
return
} else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link)
return
}
driver.Proxy(c, account)
r := c.Request
w := c.Writer
//target, err := url.Parse(link.Url)
//if err != nil {
// common.ErrorResp(c, err, 500)
// return
//}
req, err := http.NewRequest("GET", link.Url, nil)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
for h, val := range r.Header {
req.Header[h] = val
}
res, err := HttpClient.Do(req)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
defer func() {
_ = res.Body.Close()
}()
log.Debugf("proxy status: %d", res.StatusCode)
w.WriteHeader(res.StatusCode)
for h, v := range res.Header {
w.Header()[h] = v
}
_, err = io.Copy(w, res.Body)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
} }
} }
var client *resty.Client var client *resty.Client
var HttpClient = &http.Client{}
func init() { func init() {
client = resty.New() client = resty.New()
client.SetRetryCount(3) client.SetRetryCount(3).SetTimeout(base.DefaultTimeout)
} }
func Text(c *gin.Context, link *base.Link) { func Text(c *gin.Context, link *base.Link) {

View File

@ -19,7 +19,7 @@ func Auth(c *gin.Context) {
//} //}
//if token != utils.GetMD5Encode(password.Value) { //if token != utils.GetMD5Encode(password.Value) {
if token != conf.Token { if token != conf.Token {
common.ErrorStrResp(c, "Wrong password", 401) common.ErrorStrResp(c, "Invalid token", 401)
return return
} }
c.Next() c.Next()

View File

@ -15,6 +15,8 @@ func InitApiRouter(r *gin.Engine) {
Cors(r) Cors(r)
r.GET("/d/*path", middlewares.DownCheck, controllers.Down) r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy) r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
r.GET("/favicon.ico", controllers.Favicon)
r.GET("/i/:data/ipa.plist", controllers.Plist)
api := r.Group("/api") api := r.Group("/api")
public := api.Group("/public") public := api.Group("/public")
@ -54,6 +56,7 @@ func InitApiRouter(r *gin.Engine) {
admin.POST("/mkdir", file.Mkdir) admin.POST("/mkdir", file.Mkdir)
admin.POST("/rename", file.Rename) admin.POST("/rename", file.Rename)
admin.POST("/move", file.Move) admin.POST("/move", file.Move)
admin.POST("/copy", file.Copy)
admin.POST("/folder", file.Folder) admin.POST("/folder", file.Folder)
} }
WebDav(r) WebDav(r)

View File

@ -48,8 +48,8 @@ func Static(r *gin.Engine) {
r.StaticFS("/assets/", http.FS(assets)) r.StaticFS("/assets/", http.FS(assets))
r.StaticFS("/public/", http.FS(pub)) r.StaticFS("/public/", http.FS(pub))
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.Status(200)
c.Header("Content-Type", "text/html") c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") { if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml) _, _ = c.Writer.WriteString(conf.ManageHtml)
} else { } else {

View File

@ -24,8 +24,13 @@ import (
type FileSystem struct{} type FileSystem struct{}
var upFileMap = make(map[string]*model.File)
func (fs *FileSystem) File(rawPath string) (*model.File, error) { func (fs *FileSystem) File(rawPath string) (*model.File, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
if f, ok := upFileMap[rawPath]; ok {
return f, nil
}
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
now := time.Now() now := time.Now()
return &model.File{ return &model.File{
@ -40,23 +45,22 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return driver.File(path_, account) return operate.File(driver, account, path_)
} }
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) { func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
var files []model.File
var err error
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
files, err := model.GetAccountFiles() files, err = model.GetAccountFiles()
} else {
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return files, nil files, err = operate.Files(driver, account, path_)
} }
account, path_, driver, err := common.ParsePath(rawPath)
if err != nil {
return nil, err
}
files, err := driver.Files(path_, account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,7 +95,7 @@ func ClientIP(r *http.Request) string {
return "" return ""
} }
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) { func (fs *FileSystem) Link(w http.ResponseWriter, r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath) rawPath = utils.ParsePath(rawPath)
log.Debugf("get link path: %s", rawPath) log.Debugf("get link path: %s", rawPath)
if model.AccountsCount() > 1 && rawPath == "/" { if model.AccountsCount() > 1 && rawPath == "/" {
@ -106,6 +110,19 @@ func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
if r.TLS != nil { if r.TLS != nil {
protocol = "https" protocol = "https"
} }
// 直接返回
if account.WebdavDirect {
file, err := fs.File(rawPath)
if err != nil {
return "", err
}
link_, err := driver.Link(base.Args{Path: path_}, account)
if err != nil {
return "", err
}
err = common.Proxy(w, r, link_, file)
return "", err
}
if driver.Config().OnlyProxy || account.WebdavProxy { if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath) link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
if conf.GetBool("check down link") { if conf.GetBool("check down link") {
@ -154,6 +171,17 @@ func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath strin
// return err // return err
//} //}
filePath, fileName := filepath.Split(path_) filePath, fileName := filepath.Split(path_)
now := time.Now()
if fileSize == 0 {
upFileMap[rawPath] = &model.File{
Name: fileName,
Size: 0,
UpdatedAt: &now,
}
return nil
} else {
delete(upFileMap, rawPath)
}
fileData := model.FileStream{ fileData := model.FileStream{
MIMEType: r.Header.Get("Content-Type"), MIMEType: r.Header.Get("Content-Type"),
File: r.Body, File: r.Body,

View File

@ -46,7 +46,7 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) { func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) {
file, err := fs.File(path) file, err := fs.File(path)
if err != nil { if err != nil {
log.Debug(path, err) log.Debugln(path, err)
return false, nil return false, nil
} }
return true, file return true, file
@ -237,11 +237,14 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
} }
w.Header().Set("ETag", etag) w.Header().Set("ETag", etag)
log.Debugf("url: %+v", r.URL) log.Debugf("url: %+v", r.URL)
link, err := fs.Link(r, reqPath) link, err := fs.Link(w, r, reqPath)
if err != nil { if err != nil {
log.Debugf("webdav link error: %s", err.Error())
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
http.Redirect(w, r, link, 302) if link != "" {
http.Redirect(w, r, link, 302)
}
return 0, nil return 0, nil
} }

View File

@ -123,3 +123,15 @@ func Join(elem ...string) string {
} }
return res return res
} }
func Split(p string) (string, string) {
return path.Split(p)
}
// FormatName TODO
func FormatName(name string) string {
name = strings.ReplaceAll(name, "/", " ")
name = strings.ReplaceAll(name, "#", " ")
name = strings.ReplaceAll(name, "?", " ")
return name
}

25
utils/random.go Normal file
View File

@ -0,0 +1,25 @@
package utils
import (
"math/rand"
"strings"
"time"
)
var Rand *rand.Rand
func init() {
s := rand.NewSource(time.Now().UnixNano())
Rand = rand.New(s)
}
func RandomStr(n int) string {
builder := strings.Builder{}
t := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for i := 0; i < n; i++ {
r := Rand.Intn(len(t))
builder.WriteString(t[r : r+1])
}
return builder.String()
}