Compare commits

...

9 Commits

12 changed files with 163 additions and 338 deletions

View File

@ -13,6 +13,23 @@ jobs:
name: Release name: Release
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Prerelease - name: Prerelease
uses: irongut/EditRelease@v1.2.0 uses: irongut/EditRelease@v1.2.0
with: with:

View File

@ -56,11 +56,11 @@ func fileToObj(f File) *model.ObjThumb {
if f.ServerFilename == "" { if f.ServerFilename == "" {
f.ServerFilename = path.Base(f.Path) f.ServerFilename = path.Base(f.Path)
} }
if f.LocalCtime == 0 { if f.ServerCtime == 0 {
f.LocalCtime = f.Ctime f.ServerCtime = f.Ctime
} }
if f.LocalMtime == 0 { if f.ServerMtime == 0 {
f.LocalMtime = f.Mtime f.ServerMtime = f.Mtime
} }
return &model.ObjThumb{ return &model.ObjThumb{
Object: model.Object{ Object: model.Object{
@ -68,8 +68,8 @@ func fileToObj(f File) *model.ObjThumb {
Path: f.Path, Path: f.Path,
Name: f.ServerFilename, Name: f.ServerFilename,
Size: f.Size, Size: f.Size,
Modified: time.Unix(f.LocalMtime, 0), Modified: time.Unix(f.ServerMtime, 0),
Ctime: time.Unix(f.LocalCtime, 0), Ctime: time.Unix(f.ServerCtime, 0),
IsFolder: f.Isdir == 1, IsFolder: f.Isdir == 1,
// 直接获取的MD5是错误的 // 直接获取的MD5是错误的

View File

@ -280,7 +280,7 @@ func (d *Local) Copy(_ context.Context, srcObj, dstDir model.Obj) error {
return cp.Copy(srcPath, dstPath, cp.Options{ return cp.Copy(srcPath, dstPath, cp.Options{
Sync: true, // Sync file to disk after copy, may have performance penalty in filesystem such as ZFS Sync: true, // Sync file to disk after copy, may have performance penalty in filesystem such as ZFS
PreserveTimes: true, PreserveTimes: true,
NumOfWorkers: 0, // Serialized copy without using goroutine PreserveOwner: true,
}) })
} }

View File

@ -14,7 +14,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"net/http" "net/http"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
@ -49,7 +48,6 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
d.Common.CaptchaToken = token d.Common.CaptchaToken = token
op.MustSaveDriverStorage(d) op.MustSaveDriverStorage(d)
}, },
LowLatencyAddr: "",
} }
} }
@ -138,14 +136,6 @@ func (d *PikPak) Init(ctx context.Context) (err error) {
d.Addition.RefreshToken = d.RefreshToken d.Addition.RefreshToken = d.RefreshToken
op.MustSaveDriverStorage(d) op.MustSaveDriverStorage(d)
if d.UseLowLatencyAddress && d.Addition.CustomLowLatencyAddress != "" {
d.Common.LowLatencyAddr = d.Addition.CustomLowLatencyAddress
} else if d.UseLowLatencyAddress {
d.Common.LowLatencyAddr = findLowestLatencyAddress(DlAddr)
d.Addition.CustomLowLatencyAddress = d.Common.LowLatencyAddr
op.MustSaveDriverStorage(d)
}
return nil return nil
} }
@ -188,12 +178,6 @@ func (d *PikPak) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
url = resp.Medias[0].Link.Url url = resp.Medias[0].Link.Url
} }
if d.UseLowLatencyAddress && d.Common.LowLatencyAddr != "" {
// 替换为加速链接
re := regexp.MustCompile(`https://[^/]+/download/`)
url = re.ReplaceAllString(url, "https://"+d.Common.LowLatencyAddr+"/download/")
}
return &model.Link{ return &model.Link{
URL: url, URL: url,
}, nil }, nil

View File

@ -7,16 +7,14 @@ import (
type Addition struct { type Addition struct {
driver.RootID driver.RootID
Username string `json:"username" required:"true"` Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"` Password string `json:"password" required:"true"`
Platform string `json:"platform" required:"true" type:"select" options:"android,web,pc"` Platform string `json:"platform" required:"true" default:"web" type:"select" options:"android,web,pc"`
RefreshToken string `json:"refresh_token" required:"true" default:""` RefreshToken string `json:"refresh_token" required:"true" default:""`
RefreshTokenMethod string `json:"refresh_token_method" required:"true" type:"select" options:"oauth2,http"` RefreshTokenMethod string `json:"refresh_token_method" required:"true" type:"select" options:"oauth2,http"`
CaptchaToken string `json:"captcha_token" default:""` CaptchaToken string `json:"captcha_token" default:""`
DeviceID string `json:"device_id" required:"false" default:""` DeviceID string `json:"device_id" required:"false" default:""`
DisableMediaLink bool `json:"disable_media_link" default:"true"` DisableMediaLink bool `json:"disable_media_link" default:"true"`
UseLowLatencyAddress bool `json:"use_low_latency_address" default:"false"`
CustomLowLatencyAddress string `json:"custom_low_latency_address" default:""`
} }
var config = driver.Config{ var config = driver.Config{

View File

@ -30,32 +30,34 @@ import (
// do others that not defined in Driver interface // do others that not defined in Driver interface
var AndroidAlgorithms = []string{ var AndroidAlgorithms = []string{
"aDhgaSE3MsjROCmpmsWqP1sJdFJ", "7xOq4Z8s",
"+oaVkqdd8MJuKT+uMr2AYKcd9tdWge3XPEPR2hcePUknd", "QE9/9+IQco",
"u/sd2GgT2fTytRcKzGicHodhvIltMntA3xKw2SRv7S48OdnaQIS5mn", "WdX5J9CPLZp",
"2WZiae2QuqTOxBKaaqCNHCW3olu2UImelkDzBn", "NmQ5qFAXqH3w984cYhMeC5TJR8j",
"/vJ3upic39lgmrkX855Qx", "cc44M+l7GDhav",
"yNc9ruCVMV7pGV7XvFeuLMOcy1", "KxGjo/wHB+Yx8Lf7kMP+/m9I+",
"4FPq8mT3JQ1jzcVxMVfwFftLQm33M7i", "wla81BUVSmDkctHDpUT",
"xozoy5e3Ea", "c6wMr1sm1WxiR3i8LDAm3W",
"hRLrEQCFNYi0PFPV",
"o1J41zIraDtJPNuhBu7Ifb/q3",
"U",
"RrbZvV0CTu3gaZJ56PVKki4IeP",
"NNuRbLckJqUp1Do0YlrKCUP",
"UUwnBbipMTvInA0U0E9",
"VzGc",
} }
var WebAlgorithms = []string{ var WebAlgorithms = []string{
"C9qPpZLN8ucRTaTiUMWYS9cQvWOE", "fyZ4+p77W1U4zcWBUwefAIFhFxvADWtT1wzolCxhg9q7etmGUjXr",
"+r6CQVxjzJV6LCV", "uSUX02HYJ1IkyLdhINEFcCf7l2",
"F", "iWt97bqD/qvjIaPXB2Ja5rsBWtQtBZZmaHH2rMR41",
"pFJRC", "3binT1s/5a1pu3fGsN",
"9WXYIDGrwTCz2OiVlgZa90qpECPD6olt", "8YCCU+AIr7pg+yd7CkQEY16lDMwi8Rh4WNp5",
"/750aCr4lm/Sly/c", "DYS3StqnAEKdGddRP8CJrxUSFh",
"RB+DT/gZCrbV", "crquW+4",
"", "ryKqvW9B9hly+JAymXCIfag5Z",
"CyLsf7hdkIRxRm215hl", "Hr08T/NDTX1oSJfHk90c",
"7xHvLi2tOYP0Y92b", "i",
"ZGTXXxu8E/MIWaEDB+Sm/",
"1UI3",
"E7fP5Pfijd+7K+t6Tg/NhuLq0eEUVChpJSkrKxpO",
"ihtqpG6FMt65+Xk+tWUH2",
"NhXXU9rg4XXdzo7u5o",
} }
var PCAlgorithms = []string{ var PCAlgorithms = []string{
@ -80,59 +82,21 @@ const (
const ( const (
AndroidClientID = "YNxT9w7GMdWvEOKa" AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg" AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.48.3" AndroidClientVersion = "1.49.3"
AndroidPackageName = "com.pikcloud.pikpak" AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204101" AndroidSdkVersion = "2.0.4.204101"
WebClientID = "YUMx5nI8ZU8Ap8pm" WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg" WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0" WebClientVersion = "undefined"
WebPackageName = "mypikpak.net" WebPackageName = "drive.mypikpak.com"
WebSdkVersion = "8.0.3" WebSdkVersion = "8.0.3"
PCClientID = "YvtoWO6GNHiuCl7x" PCClientID = "YvtoWO6GNHiuCl7x"
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA" PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
PCClientVersion = "undefined" // 2.5.6.4831 PCClientVersion = "undefined" // 2.5.6.4831
PCPackageName = "mypikpak.net" PCPackageName = "mypikpak.com"
PCSdkVersion = "8.0.3" PCSdkVersion = "8.0.3"
) )
var DlAddr = []string{
"dl-a10b-0621.mypikpak.net",
"dl-a10b-0622.mypikpak.net",
"dl-a10b-0623.mypikpak.net",
"dl-a10b-0624.mypikpak.net",
"dl-a10b-0625.mypikpak.net",
"dl-a10b-0858.mypikpak.net",
"dl-a10b-0859.mypikpak.net",
"dl-a10b-0860.mypikpak.net",
"dl-a10b-0861.mypikpak.net",
"dl-a10b-0862.mypikpak.net",
"dl-a10b-0863.mypikpak.net",
"dl-a10b-0864.mypikpak.net",
"dl-a10b-0865.mypikpak.net",
"dl-a10b-0866.mypikpak.net",
"dl-a10b-0867.mypikpak.net",
"dl-a10b-0868.mypikpak.net",
"dl-a10b-0869.mypikpak.net",
"dl-a10b-0870.mypikpak.net",
"dl-a10b-0871.mypikpak.net",
"dl-a10b-0872.mypikpak.net",
"dl-a10b-0873.mypikpak.net",
"dl-a10b-0874.mypikpak.net",
"dl-a10b-0875.mypikpak.net",
"dl-a10b-0876.mypikpak.net",
"dl-a10b-0877.mypikpak.net",
"dl-a10b-0878.mypikpak.net",
"dl-a10b-0879.mypikpak.net",
"dl-a10b-0880.mypikpak.net",
"dl-a10b-0881.mypikpak.net",
"dl-a10b-0882.mypikpak.net",
"dl-a10b-0883.mypikpak.net",
"dl-a10b-0884.mypikpak.net",
"dl-a10b-0885.mypikpak.net",
"dl-a10b-0886.mypikpak.net",
"dl-a10b-0887.mypikpak.net",
}
func (d *PikPak) login() error { func (d *PikPak) login() error {
// 检查用户名和密码是否为空 // 检查用户名和密码是否为空
if d.Addition.Username == "" || d.Addition.Password == "" { if d.Addition.Username == "" || d.Addition.Password == "" {
@ -338,7 +302,6 @@ type Common struct {
UserAgent string UserAgent string
// 验证码token刷新成功回调 // 验证码token刷新成功回调
RefreshCTokenCk func(token string) RefreshCTokenCk func(token string)
LowLatencyAddr string
} }
func generateDeviceSign(deviceID, packageName string) string { func generateDeviceSign(deviceID, packageName string) string {
@ -729,46 +692,3 @@ func OssOption(params *S3Params) []oss.Option {
} }
return options return options
} }
type AddressLatency struct {
Address string
Latency time.Duration
}
func checkLatency(address string, wg *sync.WaitGroup, ch chan<- AddressLatency) {
defer wg.Done()
start := time.Now()
resp, err := http.Get("https://" + address + "/generate_204")
if err != nil {
ch <- AddressLatency{Address: address, Latency: time.Hour} // Set high latency on error
return
}
defer resp.Body.Close()
latency := time.Since(start)
ch <- AddressLatency{Address: address, Latency: latency}
}
func findLowestLatencyAddress(addresses []string) string {
var wg sync.WaitGroup
ch := make(chan AddressLatency, len(addresses))
for _, address := range addresses {
wg.Add(1)
go checkLatency(address, &wg, ch)
}
wg.Wait()
close(ch)
var lowestLatencyAddress string
lowestLatency := time.Hour
for result := range ch {
if result.Latency < lowestLatency {
lowestLatency = result.Latency
lowestLatencyAddress = result.Address
}
}
return lowestLatencyAddress
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/internal/op"
"net/http" "net/http"
"regexp"
"time" "time"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
@ -37,7 +36,6 @@ func (d *PikPakShare) Init(ctx context.Context) error {
d.Common.CaptchaToken = token d.Common.CaptchaToken = token
op.MustSaveDriverStorage(d) op.MustSaveDriverStorage(d)
}, },
LowLatencyAddr: "",
} }
} }
@ -71,14 +69,6 @@ func (d *PikPakShare) Init(ctx context.Context) error {
d.UserAgent = "MainWindow Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) PikPak/2.5.6.4831 Chrome/100.0.4896.160 Electron/18.3.15 Safari/537.36" d.UserAgent = "MainWindow Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) PikPak/2.5.6.4831 Chrome/100.0.4896.160 Electron/18.3.15 Safari/537.36"
} }
if d.UseLowLatencyAddress && d.Addition.CustomLowLatencyAddress != "" {
d.Common.LowLatencyAddr = d.Addition.CustomLowLatencyAddress
} else if d.UseLowLatencyAddress {
d.Common.LowLatencyAddr = findLowestLatencyAddress(DlAddr)
d.Addition.CustomLowLatencyAddress = d.Common.LowLatencyAddr
op.MustSaveDriverStorage(d)
}
// 获取CaptchaToken // 获取CaptchaToken
err := d.RefreshCaptchaToken(GetAction(http.MethodGet, "https://api-drive.mypikpak.net/drive/v1/share:batch_file_info"), "") err := d.RefreshCaptchaToken(GetAction(http.MethodGet, "https://api-drive.mypikpak.net/drive/v1/share:batch_file_info"), "")
if err != nil { if err != nil {
@ -131,12 +121,6 @@ func (d *PikPakShare) Link(ctx context.Context, file model.Obj, args model.LinkA
} }
if d.UseLowLatencyAddress && d.Common.LowLatencyAddr != "" {
// 替换为加速链接
re := regexp.MustCompile(`https://[^/]+/download/`)
downloadUrl = re.ReplaceAllString(downloadUrl, "https://"+d.Common.LowLatencyAddr+"/download/")
}
return &model.Link{ return &model.Link{
URL: downloadUrl, URL: downloadUrl,
}, nil }, nil

View File

@ -7,13 +7,11 @@ import (
type Addition struct { type Addition struct {
driver.RootID driver.RootID
ShareId string `json:"share_id" required:"true"` ShareId string `json:"share_id" required:"true"`
SharePwd string `json:"share_pwd"` SharePwd string `json:"share_pwd"`
Platform string `json:"platform" required:"true" type:"select" options:"android,web,pc"` Platform string `json:"platform" default:"web" required:"true" type:"select" options:"android,web,pc"`
DeviceID string `json:"device_id" required:"false" default:""` DeviceID string `json:"device_id" required:"false" default:""`
UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"` UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"`
UseLowLatencyAddress bool `json:"use_low_latency_address" default:"false"`
CustomLowLatencyAddress string `json:"custom_low_latency_address" default:""`
} }
var config = driver.Config{ var config = driver.Config{

View File

@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"sync"
"time" "time"
"github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/drivers/base"
@ -18,32 +17,34 @@ import (
) )
var AndroidAlgorithms = []string{ var AndroidAlgorithms = []string{
"aDhgaSE3MsjROCmpmsWqP1sJdFJ", "7xOq4Z8s",
"+oaVkqdd8MJuKT+uMr2AYKcd9tdWge3XPEPR2hcePUknd", "QE9/9+IQco",
"u/sd2GgT2fTytRcKzGicHodhvIltMntA3xKw2SRv7S48OdnaQIS5mn", "WdX5J9CPLZp",
"2WZiae2QuqTOxBKaaqCNHCW3olu2UImelkDzBn", "NmQ5qFAXqH3w984cYhMeC5TJR8j",
"/vJ3upic39lgmrkX855Qx", "cc44M+l7GDhav",
"yNc9ruCVMV7pGV7XvFeuLMOcy1", "KxGjo/wHB+Yx8Lf7kMP+/m9I+",
"4FPq8mT3JQ1jzcVxMVfwFftLQm33M7i", "wla81BUVSmDkctHDpUT",
"xozoy5e3Ea", "c6wMr1sm1WxiR3i8LDAm3W",
"hRLrEQCFNYi0PFPV",
"o1J41zIraDtJPNuhBu7Ifb/q3",
"U",
"RrbZvV0CTu3gaZJ56PVKki4IeP",
"NNuRbLckJqUp1Do0YlrKCUP",
"UUwnBbipMTvInA0U0E9",
"VzGc",
} }
var WebAlgorithms = []string{ var WebAlgorithms = []string{
"C9qPpZLN8ucRTaTiUMWYS9cQvWOE", "fyZ4+p77W1U4zcWBUwefAIFhFxvADWtT1wzolCxhg9q7etmGUjXr",
"+r6CQVxjzJV6LCV", "uSUX02HYJ1IkyLdhINEFcCf7l2",
"F", "iWt97bqD/qvjIaPXB2Ja5rsBWtQtBZZmaHH2rMR41",
"pFJRC", "3binT1s/5a1pu3fGsN",
"9WXYIDGrwTCz2OiVlgZa90qpECPD6olt", "8YCCU+AIr7pg+yd7CkQEY16lDMwi8Rh4WNp5",
"/750aCr4lm/Sly/c", "DYS3StqnAEKdGddRP8CJrxUSFh",
"RB+DT/gZCrbV", "crquW+4",
"", "ryKqvW9B9hly+JAymXCIfag5Z",
"CyLsf7hdkIRxRm215hl", "Hr08T/NDTX1oSJfHk90c",
"7xHvLi2tOYP0Y92b", "i",
"ZGTXXxu8E/MIWaEDB+Sm/",
"1UI3",
"E7fP5Pfijd+7K+t6Tg/NhuLq0eEUVChpJSkrKxpO",
"ihtqpG6FMt65+Xk+tWUH2",
"NhXXU9rg4XXdzo7u5o",
} }
var PCAlgorithms = []string{ var PCAlgorithms = []string{
@ -62,59 +63,21 @@ var PCAlgorithms = []string{
const ( const (
AndroidClientID = "YNxT9w7GMdWvEOKa" AndroidClientID = "YNxT9w7GMdWvEOKa"
AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg" AndroidClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
AndroidClientVersion = "1.48.3" AndroidClientVersion = "1.49.3"
AndroidPackageName = "com.pikcloud.pikpak" AndroidPackageName = "com.pikcloud.pikpak"
AndroidSdkVersion = "2.0.4.204101" AndroidSdkVersion = "2.0.4.204101"
WebClientID = "YUMx5nI8ZU8Ap8pm" WebClientID = "YUMx5nI8ZU8Ap8pm"
WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg" WebClientSecret = "dbw2OtmVEeuUvIptb1Coyg"
WebClientVersion = "2.0.0" WebClientVersion = "undefined"
WebPackageName = "mypikpak.net" WebPackageName = "drive.mypikpak.com"
WebSdkVersion = "8.0.3" WebSdkVersion = "8.0.3"
PCClientID = "YvtoWO6GNHiuCl7x" PCClientID = "YvtoWO6GNHiuCl7x"
PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA" PCClientSecret = "1NIH5R1IEe2pAxZE3hv3uA"
PCClientVersion = "undefined" // 2.5.6.4831 PCClientVersion = "undefined" // 2.5.6.4831
PCPackageName = "mypikpak.net" PCPackageName = "mypikpak.com"
PCSdkVersion = "8.0.3" PCSdkVersion = "8.0.3"
) )
var DlAddr = []string{
"dl-a10b-0621.mypikpak.net",
"dl-a10b-0622.mypikpak.net",
"dl-a10b-0623.mypikpak.net",
"dl-a10b-0624.mypikpak.net",
"dl-a10b-0625.mypikpak.net",
"dl-a10b-0858.mypikpak.net",
"dl-a10b-0859.mypikpak.net",
"dl-a10b-0860.mypikpak.net",
"dl-a10b-0861.mypikpak.net",
"dl-a10b-0862.mypikpak.net",
"dl-a10b-0863.mypikpak.net",
"dl-a10b-0864.mypikpak.net",
"dl-a10b-0865.mypikpak.net",
"dl-a10b-0866.mypikpak.net",
"dl-a10b-0867.mypikpak.net",
"dl-a10b-0868.mypikpak.net",
"dl-a10b-0869.mypikpak.net",
"dl-a10b-0870.mypikpak.net",
"dl-a10b-0871.mypikpak.net",
"dl-a10b-0872.mypikpak.net",
"dl-a10b-0873.mypikpak.net",
"dl-a10b-0874.mypikpak.net",
"dl-a10b-0875.mypikpak.net",
"dl-a10b-0876.mypikpak.net",
"dl-a10b-0877.mypikpak.net",
"dl-a10b-0878.mypikpak.net",
"dl-a10b-0879.mypikpak.net",
"dl-a10b-0880.mypikpak.net",
"dl-a10b-0881.mypikpak.net",
"dl-a10b-0882.mypikpak.net",
"dl-a10b-0883.mypikpak.net",
"dl-a10b-0884.mypikpak.net",
"dl-a10b-0885.mypikpak.net",
"dl-a10b-0886.mypikpak.net",
"dl-a10b-0887.mypikpak.net",
}
func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { func (d *PikPakShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R() req := base.RestyClient.R()
req.SetHeaders(map[string]string{ req.SetHeaders(map[string]string{
@ -227,7 +190,6 @@ type Common struct {
UserAgent string UserAgent string
// 验证码token刷新成功回调 // 验证码token刷新成功回调
RefreshCTokenCk func(token string) RefreshCTokenCk func(token string)
LowLatencyAddr string
} }
func (c *Common) SetUserAgent(userAgent string) { func (c *Common) SetUserAgent(userAgent string) {
@ -367,46 +329,3 @@ func (d *PikPakShare) refreshCaptchaToken(action string, metas map[string]string
d.Common.SetCaptchaToken(resp.CaptchaToken) d.Common.SetCaptchaToken(resp.CaptchaToken)
return nil return nil
} }
type AddressLatency struct {
Address string
Latency time.Duration
}
func checkLatency(address string, wg *sync.WaitGroup, ch chan<- AddressLatency) {
defer wg.Done()
start := time.Now()
resp, err := http.Get("https://" + address + "/generate_204")
if err != nil {
ch <- AddressLatency{Address: address, Latency: time.Hour} // Set high latency on error
return
}
defer resp.Body.Close()
latency := time.Since(start)
ch <- AddressLatency{Address: address, Latency: latency}
}
func findLowestLatencyAddress(addresses []string) string {
var wg sync.WaitGroup
ch := make(chan AddressLatency, len(addresses))
for _, address := range addresses {
wg.Add(1)
go checkLatency(address, &wg, ch)
}
wg.Wait()
close(ch)
var lowestLatencyAddress string
lowestLatency := time.Hour
for result := range ch {
if result.Latency < lowestLatency {
lowestLatency = result.Latency
lowestLatencyAddress = result.Address
}
}
return lowestLatencyAddress
}

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"github.com/alist-org/alist/v3/pkg/utils"
"io" "io"
"math" "math"
"net/http" "net/http"
@ -13,6 +12,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/http_range" "github.com/alist-org/alist/v3/pkg/http_range"
"github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/aws/awsutil"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -168,6 +169,9 @@ func (d *downloader) sendChunkTask() *chunk {
// when the final reader Close, we interrupt // when the final reader Close, we interrupt
func (d *downloader) interrupt() error { func (d *downloader) interrupt() error {
if d.chunkChannel == nil {
return nil
}
d.cancel() d.cancel()
if d.written != d.params.Range.Length { if d.written != d.params.Range.Length {
log.Debugf("Downloader interrupt before finish") log.Debugf("Downloader interrupt before finish")
@ -177,6 +181,7 @@ func (d *downloader) interrupt() error {
} }
defer func() { defer func() {
close(d.chunkChannel) close(d.chunkChannel)
d.chunkChannel = nil
for _, buf := range d.bufs { for _, buf := range d.bufs {
buf.Close() buf.Close()
} }

View File

@ -1,20 +1,27 @@
package random package random
import ( import (
"math/rand" "crypto/rand"
"math/big"
mathRand "math/rand"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
) )
var Rand *rand.Rand var Rand *mathRand.Rand
const letterBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" const letterBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
func String(n int) string { func String(n int) string {
b := make([]byte, n) b := make([]byte, n)
letterLen := big.NewInt(int64(len(letterBytes)))
for i := range b { for i := range b {
b[i] = letterBytes[Rand.Intn(len(letterBytes))] idx, err := rand.Int(rand.Reader, letterLen)
if err != nil {
panic(err)
}
b[i] = letterBytes[idx.Int64()]
} }
return string(b) return string(b)
} }
@ -24,10 +31,10 @@ func Token() string {
} }
func RangeInt64(left, right int64) int64 { func RangeInt64(left, right int64) int64 {
return rand.Int63n(left+right) - left return mathRand.Int63n(left+right) - left
} }
func init() { func init() {
s := rand.NewSource(time.Now().UnixNano()) s := mathRand.NewSource(time.Now().UnixNano())
Rand = rand.New(s) Rand = mathRand.New(s)
} }

View File

@ -1,10 +1,10 @@
package handles package handles
import ( import (
"encoding/base32"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"github.com/Xhofe/go-cache"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
@ -21,29 +21,45 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"gorm.io/gorm" "gorm.io/gorm"
) )
var opts = totp.ValidateOpts{ const stateLength = 16
// state verify won't expire in 30 secs, which is quite enough for the callback const stateExpire = time.Minute * 5
Period: 30,
Skew: 1, var stateCache = cache.NewMemCache[string](cache.WithShards[string](stateLength))
// in some OIDC providers(such as Authelia), state parameter must be at least 8 characters
Digits: otp.DigitsEight, func _keyState(clientID, state string) string {
Algorithm: otp.AlgorithmSHA1, return fmt.Sprintf("%s_%s", clientID, state)
}
func generateState(clientID, ip string) string {
state := random.String(stateLength)
stateCache.Set(_keyState(clientID, state), ip, cache.WithEx[string](stateExpire))
return state
}
func verifyState(clientID, ip, state string) bool {
value, ok := stateCache.Get(_keyState(clientID, state))
return ok && value == ip
}
func ssoRedirectUri(c *gin.Context, useCompatibility bool, method string) string {
if useCompatibility {
return common.GetApiUrl(c.Request) + "/api/auth/" + method
} else {
return common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + method
}
} }
func SSOLoginRedirect(c *gin.Context) { func SSOLoginRedirect(c *gin.Context) {
method := c.Query("method") method := c.Query("method")
usecompatibility := setting.GetBool(conf.SSOCompatibilityMode) useCompatibility := setting.GetBool(conf.SSOCompatibilityMode)
enabled := setting.GetBool(conf.SSOLoginEnabled) enabled := setting.GetBool(conf.SSOLoginEnabled)
clientId := setting.GetStr(conf.SSOClientId) clientId := setting.GetStr(conf.SSOClientId)
platform := setting.GetStr(conf.SSOLoginPlatform) platform := setting.GetStr(conf.SSOLoginPlatform)
var r_url string var rUrl string
var redirect_uri string
if !enabled { if !enabled {
common.ErrorStrResp(c, "Single sign-on is not enabled", 403) common.ErrorStrResp(c, "Single sign-on is not enabled", 403)
return return
@ -53,69 +69,52 @@ func SSOLoginRedirect(c *gin.Context) {
common.ErrorStrResp(c, "no method provided", 400) common.ErrorStrResp(c, "no method provided", 400)
return return
} }
if usecompatibility { redirectUri := ssoRedirectUri(c, useCompatibility, method)
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/" + method
} else {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + method
}
urlValues.Add("response_type", "code") urlValues.Add("response_type", "code")
urlValues.Add("redirect_uri", redirect_uri) urlValues.Add("redirect_uri", redirectUri)
urlValues.Add("client_id", clientId) urlValues.Add("client_id", clientId)
switch platform { switch platform {
case "Github": case "Github":
r_url = "https://github.com/login/oauth/authorize?" rUrl = "https://github.com/login/oauth/authorize?"
urlValues.Add("scope", "read:user") urlValues.Add("scope", "read:user")
case "Microsoft": case "Microsoft":
r_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?" rUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?"
urlValues.Add("scope", "user.read") urlValues.Add("scope", "user.read")
urlValues.Add("response_mode", "query") urlValues.Add("response_mode", "query")
case "Google": case "Google":
r_url = "https://accounts.google.com/o/oauth2/v2/auth?" rUrl = "https://accounts.google.com/o/oauth2/v2/auth?"
urlValues.Add("scope", "https://www.googleapis.com/auth/userinfo.profile") urlValues.Add("scope", "https://www.googleapis.com/auth/userinfo.profile")
case "Dingtalk": case "Dingtalk":
r_url = "https://login.dingtalk.com/oauth2/auth?" rUrl = "https://login.dingtalk.com/oauth2/auth?"
urlValues.Add("scope", "openid") urlValues.Add("scope", "openid")
urlValues.Add("prompt", "consent") urlValues.Add("prompt", "consent")
urlValues.Add("response_type", "code") urlValues.Add("response_type", "code")
case "Casdoor": case "Casdoor":
endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/") endpoint := strings.TrimSuffix(setting.GetStr(conf.SSOEndpointName), "/")
r_url = endpoint + "/login/oauth/authorize?" rUrl = endpoint + "/login/oauth/authorize?"
urlValues.Add("scope", "profile") urlValues.Add("scope", "profile")
urlValues.Add("state", endpoint) urlValues.Add("state", endpoint)
case "OIDC": case "OIDC":
oauth2Config, err := GetOIDCClient(c) oauth2Config, err := GetOIDCClient(c, useCompatibility, redirectUri, method)
if err != nil {
common.ErrorStrResp(c, err.Error(), 400)
return
}
// generate state parameter
state, err := totp.GenerateCodeCustom(base32.StdEncoding.EncodeToString([]byte(oauth2Config.ClientSecret)), time.Now(), opts)
if err != nil { if err != nil {
common.ErrorStrResp(c, err.Error(), 400) common.ErrorStrResp(c, err.Error(), 400)
return return
} }
state := generateState(clientId, c.ClientIP())
c.Redirect(http.StatusFound, oauth2Config.AuthCodeURL(state)) c.Redirect(http.StatusFound, oauth2Config.AuthCodeURL(state))
return return
default: default:
common.ErrorStrResp(c, "invalid platform", 400) common.ErrorStrResp(c, "invalid platform", 400)
return return
} }
c.Redirect(302, r_url+urlValues.Encode()) c.Redirect(302, rUrl+urlValues.Encode())
} }
var ssoClient = resty.New().SetRetryCount(3) var ssoClient = resty.New().SetRetryCount(3)
func GetOIDCClient(c *gin.Context) (*oauth2.Config, error) { func GetOIDCClient(c *gin.Context, useCompatibility bool, redirectUri, method string) (*oauth2.Config, error) {
var redirect_uri string if redirectUri == "" {
usecompatibility := setting.GetBool(conf.SSOCompatibilityMode) redirectUri = ssoRedirectUri(c, useCompatibility, method)
argument := c.Query("method")
if usecompatibility {
argument = path.Base(c.Request.URL.Path)
}
if usecompatibility {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/" + argument
} else {
redirect_uri = common.GetApiUrl(c.Request) + "/api/auth/sso_callback" + "?method=" + argument
} }
endpoint := setting.GetStr(conf.SSOEndpointName) endpoint := setting.GetStr(conf.SSOEndpointName)
provider, err := oidc.NewProvider(c, endpoint) provider, err := oidc.NewProvider(c, endpoint)
@ -127,7 +126,7 @@ func GetOIDCClient(c *gin.Context) (*oauth2.Config, error) {
return &oauth2.Config{ return &oauth2.Config{
ClientID: clientId, ClientID: clientId,
ClientSecret: clientSecret, ClientSecret: clientSecret,
RedirectURL: redirect_uri, RedirectURL: redirectUri,
// Discovery returns the OAuth2 endpoints. // Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(), Endpoint: provider.Endpoint(),
@ -181,9 +180,9 @@ func parseJWT(p string) ([]byte, error) {
func OIDCLoginCallback(c *gin.Context) { func OIDCLoginCallback(c *gin.Context) {
useCompatibility := setting.GetBool(conf.SSOCompatibilityMode) useCompatibility := setting.GetBool(conf.SSOCompatibilityMode)
argument := c.Query("method") method := c.Query("method")
if useCompatibility { if useCompatibility {
argument = path.Base(c.Request.URL.Path) method = path.Base(c.Request.URL.Path)
} }
clientId := setting.GetStr(conf.SSOClientId) clientId := setting.GetStr(conf.SSOClientId)
endpoint := setting.GetStr(conf.SSOEndpointName) endpoint := setting.GetStr(conf.SSOEndpointName)
@ -192,18 +191,12 @@ func OIDCLoginCallback(c *gin.Context) {
common.ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
oauth2Config, err := GetOIDCClient(c) oauth2Config, err := GetOIDCClient(c, useCompatibility, "", method)
if err != nil { if err != nil {
common.ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return
} }
// add state verify process if !verifyState(clientId, c.ClientIP(), c.Query("state")) {
stateVerification, err := totp.ValidateCustom(c.Query("state"), base32.StdEncoding.EncodeToString([]byte(oauth2Config.ClientSecret)), time.Now(), opts)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
if !stateVerification {
common.ErrorStrResp(c, "incorrect or expired state parameter", 400) common.ErrorStrResp(c, "incorrect or expired state parameter", 400)
return return
} }
@ -236,7 +229,7 @@ func OIDCLoginCallback(c *gin.Context) {
common.ErrorStrResp(c, "cannot get username from OIDC provider", 400) common.ErrorStrResp(c, "cannot get username from OIDC provider", 400)
return return
} }
if argument == "get_sso_id" { if method == "get_sso_id" {
if useCompatibility { if useCompatibility {
c.Redirect(302, common.GetApiUrl(c.Request)+"/@manage?sso_id="+userID) c.Redirect(302, common.GetApiUrl(c.Request)+"/@manage?sso_id="+userID)
return return
@ -252,7 +245,7 @@ func OIDCLoginCallback(c *gin.Context) {
c.Data(200, "text/html; charset=utf-8", []byte(html)) c.Data(200, "text/html; charset=utf-8", []byte(html))
return return
} }
if argument == "sso_get_token" { if method == "sso_get_token" {
user, err := db.GetUserBySSOID(userID) user, err := db.GetUserBySSOID(userID)
if err != nil { if err != nil {
user, err = autoRegister(userID, userID, err) user, err = autoRegister(userID, userID, err)