Compare commits

..

32 Commits

Author SHA1 Message Date
f1a9b68022 fix(index): update indexes in database 2022-12-05 20:23:37 +08:00
dda1da4576 fix(index): nil pointer call 2022-12-05 20:22:35 +08:00
5b7aa9c1cf feat: allow all cors headers (close #2571) 2022-12-05 20:05:20 +08:00
a28aaceaad chore(ci): only build on main branch 2022-12-05 19:52:02 +08:00
2bb200af87 fix(deps): update modules by renovate[bot]
fix(deps): update module github.com/sheltonzhu/115driver to v1.0.13 (#2413) [skip ci]

fix(deps): update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#2526) [skip ci]

fix(deps): update module golang.org/x/image to v0.1.0 (#2587) [skip ci]

chore: go mod tidy
Co-Authored-By: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-05 19:50:49 +08:00
97f1efbb72 feat!: disable --force-bin-dir if --data is abs
related issues: #2580 #2542

after this commit, the `--force-bin-dir` would take no effect if `--data` is absolute path
2022-12-05 18:32:48 +08:00
bf8b6f4c2c feat: customize ignore paths of indexes 2022-12-05 16:45:11 +08:00
bd33c200dc feat: optimize index build 2022-12-05 16:07:36 +08:00
bc6baf1be0 fix(ci): sort lang json file 2022-12-05 14:40:46 +08:00
dc8d5106f9 feat: auto fix address in alist & smb storages (#2582) 2022-12-05 13:31:34 +08:00
8c0dfe2f3d feat: Search enhancement (#2562)
* feat: ignore AList storage on indexing

* fix: remove unused err in `walkFn`

* chore(ci): fix auto_lang trigger and run it

* feat: batch index

* feat: quit index & init index

* feat: set DocType for bleve data

* fix: build index cleanup check origin err
2022-12-05 13:28:39 +08:00
4e1be9bee6 fix: async init aria2 to optimize start duration 2022-12-04 00:00:40 +08:00
4c5285e094 chore(ci): format lang file (#2558) 2022-12-03 12:19:10 +08:00
0838feeb82 fix:introduce buffered response writer for webdav, fix status/error return failed. (#2544)
* fix: introduce buffered response writer for webdav, fix webdav status/error return failed.

* fix: bypass buffered writer for GET/HEAD/POST requests
2022-12-02 17:59:59 +08:00
ae791c8634 fix: hide check in canAccess (#2556)
修复 meta.Password 和 meta.Hide 都为空的情况下,会导致无权限访问
2022-12-02 17:44:29 +08:00
09f480318c fix: unify settings string (#2555) 2022-12-02 17:42:42 +08:00
4c5be5f07f feat: only show CanAccess search results (#2548)
* feat: only show `CanAccess` search results

* have done in frontend

Co-authored-by: Noah Hsu <i@nn.ci>
2022-12-02 10:09:39 +08:00
9c1ffdbb82 fix(aliyundrive): return error if got wrong http code (#2543) 2022-12-01 21:48:19 +08:00
18a63e34dd fix(task): memory alignment for curID (close #2541) 2022-12-01 13:16:31 +08:00
ff0bcfef8a feat: optional sign all files 2022-11-30 22:10:07 +08:00
4980b71ba3 fix: add hide check to canAccess (close #2532) 2022-11-30 22:01:33 +08:00
b5bf5f4325 fix: check if the req path is relative path (close #2531) 2022-11-30 21:38:00 +08:00
f9788ea7cf feat(webdav): delete privacy header and optimize 302 (#2534)
* fix: delete set-cookie from sharepoint webdav response header

* fix: avoid two redirects when using webdav

* fix: return the correct Content-Type instead of just `application/octet-stream`

* feat: webdav backend localOnly -> proxyOnly
2022-11-30 20:52:33 +08:00
83644dab85 fix: mapping filename in GetName
some missed filename mapping
2022-11-30 20:46:54 +08:00
d94cf72da2 fix(local): webp image decode while generate thumbnail (close #2484 pr #2520)
* Fix that webp thumb  in local storage won't load

* Simplify code

Co-authored-by: Noah Hsu <i@nn.ci>
2022-11-29 09:47:40 +08:00
e98561ceb1 fix: filename char mapping while build index 2022-11-28 21:08:11 +08:00
76f37373e0 fix: settings map read and write concurrently 2022-11-28 16:54:03 +08:00
61a06992c3 fix(aria2): directory missing (close #1856 pr #2504) 2022-11-28 14:05:28 +08:00
ddcba93eea feat: multiple search indexes (#2514)
* refactor: abstract search interface

* wip: ~

* fix cycle import

* objs update hook

* wip: ~

* Delete search/none

* auto update index while cache changed

* db searcher

TODO: bleve init issue

cannot open index, metadata missing

* fix size type

why float64??

* fix typo

* fix nil pointer using

* api adapt ui

* bleve: fix clear & change struct
2022-11-28 13:45:25 +08:00
bb969d8dc6 fix(aliyundrive_share): get share link download url directly (close #2472) 2022-11-24 18:50:04 +08:00
2383e851e2 fix: reset index before build new one (#2471) 2022-11-24 14:47:49 +08:00
330a767fd7 feat: build index & search with bleve (close #1740 pr #2386)
* feat: build index & search with bleve (#1740)

* delete unused struct

Co-authored-by: Noah Hsu <i@nn.ci>
2022-11-24 11:46:47 +08:00
81 changed files with 1771 additions and 313 deletions

View File

@ -7,6 +7,8 @@ on:
paths:
- 'drivers/**'
- 'internal/bootstrap/data/setting.go'
- 'internal/conf/const.go'
- 'cmd/lang.go'
workflow_dispatch:
jobs:
@ -42,6 +44,7 @@ jobs:
cd alist
go run ./main.go lang
cd ..
- name: Copy lang file
run: |
cp -f ./alist/lang/*.json ./alist-web/src/lang/en/ 2>/dev/null || :
@ -61,4 +64,4 @@ jobs:
github_token: ${{ secrets.MY_TOKEN }}
branch: main
directory: alist-web
repository: alist-org/alist-web
repository: alist-org/alist-web

View File

@ -2,9 +2,9 @@ name: build
on:
push:
branches: [ '**' ]
branches: [ 'main' ]
pull_request:
branches: [ '**' ]
branches: [ 'main' ]
jobs:
build:

View File

@ -15,6 +15,7 @@ func Init() {
bootstrap.InitConfig()
bootstrap.Log()
bootstrap.InitDB()
bootstrap.InitIndex()
data.InitData()
}

View File

@ -71,7 +71,7 @@ func writeFile(name string, data interface{}) {
} else {
log.Infof("%s.json changed, update file", name)
//log.Infof("old: %+v\nnew:%+v", oldData, data)
utils.WriteJsonToFile(fmt.Sprintf("lang/%s.json", name), data)
utils.WriteJsonToFile(fmt.Sprintf("lang/%s.json", name), newData, true)
}
}

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
//type BaseResp struct {
@ -39,7 +40,7 @@ func (f File) GetSize() int64 {
}
func (f File) GetName() string {
return f.FileName
return utils.MappingName(f.FileName)
}
func (f File) ModTime() time.Time {

View File

@ -6,6 +6,8 @@ import (
"sort"
"strings"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
// 居然有四种返回方式
@ -134,7 +136,7 @@ type Cloud189File struct {
}
func (c *Cloud189File) GetSize() int64 { return c.Size }
func (c *Cloud189File) GetName() string { return c.Name }
func (c *Cloud189File) GetName() string { return utils.MappingName(c.Name) }
func (c *Cloud189File) ModTime() time.Time {
if c.parseTime == nil {
c.parseTime = MustParseTime(c.LastOpTime)
@ -166,7 +168,7 @@ type Cloud189Folder struct {
}
func (c *Cloud189Folder) GetSize() int64 { return 0 }
func (c *Cloud189Folder) GetName() string { return c.Name }
func (c *Cloud189Folder) GetName() string { return utils.MappingName(c.Name) }
func (c *Cloud189Folder) ModTime() time.Time {
if c.parseTime == nil {
c.parseTime = MustParseTime(c.LastOpTime)

View File

@ -30,6 +30,9 @@ func (d *AListV2) Init(ctx context.Context, storage model.Storage) error {
if err != nil {
return err
}
if len(d.Addition.Address) > 0 && string(d.Addition.Address[len(d.Addition.Address)-1]) == "/" {
d.Addition.Address = d.Addition.Address[0 : len(d.Addition.Address)-1]
}
// TODO login / refresh token
//op.MustSaveDriverStorage(d)
return err

View File

@ -30,6 +30,9 @@ func (d *AListV3) Init(ctx context.Context, storage model.Storage) error {
if err != nil {
return err
}
if len(d.Addition.Address) > 0 && string(d.Addition.Address[len(d.Addition.Address)-1]) == "/" {
d.Addition.Address = d.Addition.Address[0 : len(d.Addition.Address)-1]
}
// TODO login / refresh token
//op.MustSaveDriverStorage(d)
return err
@ -46,7 +49,7 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
SetResult(&resp).
SetHeader("Authorization", d.AccessToken).
SetBody(ListReq{
PageReq: common.PageReq{
PageReq: model.PageReq{
Page: 1,
PerPage: 0,
},

View File

@ -3,11 +3,11 @@ package alist_v3
import (
"time"
"github.com/alist-org/alist/v3/server/common"
"github.com/alist-org/alist/v3/internal/model"
)
type ListReq struct {
common.PageReq
model.PageReq
Path string `json:"path" form:"path"`
Password string `json:"password" form:"password"`
Refresh bool `json:"refresh"`

View File

@ -62,6 +62,8 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
return d.request(url, method, callback, resp)
}
return nil, errors.New(e.Message), e
} else if res.IsError() {
return nil, errors.New("bad status code " + res.Status()), e
}
return res.Body(), nil, e
}

View File

@ -80,62 +80,40 @@ func (d *AliyundriveShare) List(ctx context.Context, dir model.Obj, args model.L
func (d *AliyundriveShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
data := base.Json{
"drive_id": d.DriveId,
"file_id": file.GetID(),
"expire_sec": 14400,
"drive_id": d.DriveId,
"file_id": file.GetID(),
// // Only ten minutes lifetime
"expire_sec": 600,
"share_id": d.ShareId,
}
var resp ShareLinkResp
var e ErrorResp
res, err := base.RestyClient.R().
SetError(&e).SetBody(data).
_, err := base.RestyClient.R().
SetError(&e).SetBody(data).SetResult(&resp).
SetHeader("content-type", "application/json").
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
Post("https://api.aliyundrive.com/v2/file/get_download_url")
SetHeader("x-share-token", d.ShareToken).
Post("https://api.aliyundrive.com/v2/file/get_share_link_download_url")
if err != nil {
return nil, err
}
var u string
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = d.refreshToken()
if e.Code == "AccessTokenInvalid" || e.Code == "ShareLinkTokenInvalid" {
if e.Code == "AccessTokenInvalid" {
err = d.refreshToken()
} else {
err = d.getShareToken()
}
if err != nil {
return nil, err
}
return d.Link(ctx, file, args)
} else if e.Code == "ForbiddenNoPermission.File" {
data = utils.MergeMap(data, base.Json{
// Only ten minutes valid
"expire_sec": 600,
"share_id": d.ShareId,
})
var resp ShareLinkResp
var e2 ErrorResp
_, err = base.RestyClient.R().
SetError(&e2).SetBody(data).SetResult(&resp).
SetHeader("content-type", "application/json").
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
SetHeader("x-share-token", d.ShareToken).
Post("https://api.aliyundrive.com/v2/file/get_share_link_download_url")
if err != nil {
return nil, err
}
if e2.Code != "" {
if e2.Code == "AccessTokenInvalid" || e2.Code == "ShareLinkTokenInvalid" {
err = d.getShareToken()
if err != nil {
return nil, err
}
return d.Link(ctx, file, args)
} else {
return nil, errors.New(e2.Code + ":" + e2.Message)
}
} else {
u = resp.DownloadUrl
}
} else {
return nil, errors.New(e.Code + ":" + e.Message)
return nil, errors.New(e.Code + ": " + e.Message)
}
} else {
u = utils.Json.Get(res.Body(), "url").ToString()
u = resp.DownloadUrl
}
return &model.Link{
Header: http.Header{

View File

@ -3,6 +3,8 @@ package baiduphoto
import (
"fmt"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
type TokenErrResp struct {
@ -100,7 +102,7 @@ type (
)
func (a *Album) GetSize() int64 { return 0 }
func (a *Album) GetName() string { return fmt.Sprint(a.Title) }
func (a *Album) GetName() string { return utils.MappingName(a.Title) }
func (a *Album) ModTime() time.Time {
if a.parseTime == nil {
a.parseTime = toTime(a.Mtime)

View File

@ -21,6 +21,7 @@ import (
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/disintegration/imaging"
_ "golang.org/x/image/webp"
)
type Local struct {

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"path/filepath"
"strings"
"time"
"github.com/alist-org/alist/v3/internal/driver"
@ -34,6 +35,9 @@ func (d *SMB) Init(ctx context.Context, storage model.Storage) error {
if err != nil {
return err
}
if strings.Index(d.Addition.Address, ":") < 0{
d.Addition.Address = d.Addition.Address + ":445"
}
return d.initFS()
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"strconv"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
type ErrResp struct {
@ -147,7 +149,7 @@ type Files struct {
}
func (c *Files) GetSize() int64 { size, _ := strconv.ParseInt(c.Size, 10, 64); return size }
func (c *Files) GetName() string { return c.Name }
func (c *Files) GetName() string { return utils.MappingName(c.Name) }
func (c *Files) ModTime() time.Time { return c.ModifiedTime }
func (c *Files) IsDir() bool { return c.Kind == FOLDER }
func (c *Files) GetID() string { return c.ID }

View File

@ -73,24 +73,14 @@ func (d *WebDav) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
//}
func (d *WebDav) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
callback := func(r *http.Request) {
if args.Header.Get("Range") != "" {
r.Header.Set("Range", args.Header.Get("Range"))
}
if args.Header.Get("If-Range") != "" {
r.Header.Set("If-Range", args.Header.Get("If-Range"))
}
}
reader, header, err := d.client.ReadStream(file.GetPath(), callback)
url, header, err := d.client.Link(file.GetPath())
if err != nil {
return nil, err
}
link := &model.Link{Data: reader}
if header.Get("Content-Range") != "" {
link.Status = 206
link.Header = header
}
return link, nil
return &model.Link{
URL: url,
Header: header,
}, nil
}
func (d *WebDav) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {

View File

@ -16,7 +16,7 @@ type Addition struct {
var config = driver.Config{
Name: "WebDav",
LocalSort: true,
OnlyLocal: true,
OnlyProxy: true,
DefaultRoot: "/",
}

32
go.mod
View File

@ -3,15 +3,17 @@ module github.com/alist-org/alist/v3
go 1.19
require (
github.com/SheltonZhu/115driver v1.0.12
github.com/SheltonZhu/115driver v1.0.13
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
github.com/aws/aws-sdk-go v1.44.142
github.com/aws/aws-sdk-go v1.44.152
github.com/blevesearch/bleve/v2 v2.3.5
github.com/caarlos0/env/v6 v6.10.1
github.com/deckarep/golang-set/v2 v2.1.0
github.com/disintegration/imaging v1.6.2
github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.8.1
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/hirochachacha/go-smb2 v1.1.0
@ -27,6 +29,7 @@ require (
github.com/upyun/go-sdk/v3 v3.0.3
github.com/winfsp/cgofuse v1.5.0
golang.org/x/crypto v0.3.0
golang.org/x/image v0.1.0
golang.org/x/net v0.2.0
gorm.io/driver/mysql v1.4.4
gorm.io/driver/postgres v1.4.5
@ -35,9 +38,26 @@ require (
)
require (
github.com/RoaringBitmap/roaring v0.9.4 // indirect
github.com/aead/ecdh v0.2.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
github.com/andreburgaud/crypt2go v1.1.0 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/blevesearch/bleve_index_api v1.0.4 // indirect
github.com/blevesearch/geo v0.1.15 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 // indirect
github.com/blevesearch/segment v0.9.0 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
github.com/blevesearch/vellum v1.0.9 // indirect
github.com/blevesearch/zapx/v11 v11.3.6 // indirect
github.com/blevesearch/zapx/v12 v12.3.6 // indirect
github.com/blevesearch/zapx/v13 v13.3.6 // indirect
github.com/blevesearch/zapx/v14 v14.3.6 // indirect
github.com/blevesearch/zapx/v15 v15.3.6 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
@ -48,6 +68,9 @@ require (
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
@ -68,13 +91,14 @@ require (
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect

67
go.sum
View File

@ -2,8 +2,10 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/SheltonZhu/115driver v1.0.12 h1:+GlIM5h8tzuec6MzK0wFwb7bY77nav7JhY8lTljzls4=
github.com/SheltonZhu/115driver v1.0.12/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
github.com/SheltonZhu/115driver v1.0.13 h1:YEhQ3iMvd5TD6Xp1wKg+73KgdCMjc0pDoT1eCNXnA3M=
github.com/SheltonZhu/115driver v1.0.13/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
@ -12,8 +14,43 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE=
github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI=
github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/blevesearch/bleve/v2 v2.3.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8=
github.com/blevesearch/bleve/v2 v2.3.5/go.mod h1:FneKGHMRrCLrp4X9+iy3wlBqgM2ALucg7bp8jUuAi/s=
github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
github.com/blevesearch/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU=
github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4=
github.com/blevesearch/geo v0.1.15/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ=
github.com/blevesearch/scorch_segment_api/v2 v2.1.3/go.mod h1:eZrfp1y+lUh+DzFjUcTBUSnKGuunyFIpBIvqYVzJfvc=
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
github.com/blevesearch/zapx/v11 v11.3.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc=
github.com/blevesearch/zapx/v11 v11.3.6/go.mod h1:B0CzJRj/pS7hJIroflRtFsa9mRHpMSucSgre0FVINns=
github.com/blevesearch/zapx/v12 v12.3.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4=
github.com/blevesearch/zapx/v12 v12.3.6/go.mod h1:iYi7tIKpauwU5os5wTxJITixr5Km21Hl365otMwdaP0=
github.com/blevesearch/zapx/v13 v13.3.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig=
github.com/blevesearch/zapx/v13 v13.3.6/go.mod h1:X+FsTwCU8qOHtK0d/ArvbOH7qiIgViSQ1GQvcR6LSkI=
github.com/blevesearch/zapx/v14 v14.3.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE=
github.com/blevesearch/zapx/v14 v14.3.6/go.mod h1:9X8W3XoikagU0rwcTqwZho7p9cC7m7zhPZO94S4wUvM=
github.com/blevesearch/zapx/v15 v15.3.6 h1:VSswg/ysDxHgitcNkpUNtaTYS4j3uItpXWLAASphl6k=
github.com/blevesearch/zapx/v15 v15.3.6/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
@ -30,6 +67,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
@ -62,12 +101,18 @@ github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -140,6 +185,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -178,6 +224,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
@ -245,6 +293,8 @@ github.com/winfsp/cgofuse v1.5.0 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -275,8 +325,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -303,6 +353,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"sync"
"sync/atomic"
@ -171,7 +172,12 @@ func (m *Monitor) Complete() error {
ReadCloser: f,
Mimetype: mimetype,
}
return op.Put(tsk.Ctx, storage, dstDirActualPath, stream, tsk.SetProgress)
relDir, err := filepath.Rel(m.tempDir, filepath.Dir(file.Path))
if err != nil {
log.Errorf("find relation directory error: %v", err)
}
newDistDir := filepath.Join(dstDirActualPath, relDir)
return op.Put(tsk.Ctx, storage, newDistDir, stream, tsk.SetProgress)
},
}))
}

View File

@ -6,9 +6,11 @@ import (
)
func InitAria2() {
_, err := aria2.InitClient(2)
if err != nil {
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
utils.Log.Infof("Aria2 not ready.")
}
go func() {
_, err := aria2.InitClient(2)
if err != nil {
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
utils.Log.Infof("Aria2 not ready.")
}
}()
}

View File

@ -13,12 +13,14 @@ import (
func InitConfig() {
if flags.ForceBinDir {
ex, err := os.Executable()
if err != nil {
utils.Log.Fatal(err)
if !filepath.IsAbs(flags.DataDir) {
ex, err := os.Executable()
if err != nil {
utils.Log.Fatal(err)
}
exPath := filepath.Dir(ex)
flags.DataDir = filepath.Join(exPath, flags.DataDir)
}
exPath := filepath.Dir(ex)
flags.DataDir = filepath.Join(exPath, "data")
}
configPath := filepath.Join(flags.DataDir, "config.json")
log.Infof("reading config file: %s", configPath)

View File

@ -25,12 +25,13 @@ func initSettings() {
settings[i].Flag = model.DEPRECATED
}
}
if settings != nil && len(settings) > 0 {
err = db.SaveSettingItems(settings)
if err != nil {
log.Fatalf("failed save settings: %+v", err)
}
}
// what's going on here???
//if settings != nil && len(settings) > 0 {
// err = db.SaveSettingItems(settings)
// if err != nil {
// log.Fatalf("failed save settings: %+v", err)
// }
//}
// insert new items
for i := range initialSettingItems {
v := initialSettingItems[i]
@ -116,17 +117,23 @@ func InitialSettings() []model.SettingItem {
{Key: conf.CustomizeHead, Value: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
(?U)access_token=(.*)&`,
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
// aria2 settings
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
{Key: conf.Aria2Secret, Value: "", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
// single settings
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,bleve,none", Group: model.INDEX},
{Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`},
{Key: conf.IndexProgress, Value: "{}", Type: conf.TypeText, Group: model.SINGLE, Flag: model.PRIVATE},
}
if flags.Dev {
initialSettingItems = append(initialSettingItems, []model.SettingItem{

View File

@ -0,0 +1,18 @@
package bootstrap
import (
"github.com/alist-org/alist/v3/internal/search"
log "github.com/sirupsen/logrus"
)
func InitIndex() {
progress, err := search.Progress()
if err != nil {
log.Errorf("init index error: %+v", err)
return
}
if !progress.IsDone {
progress.IsDone = true
search.WriteProgress(progress)
}
}

View File

@ -45,11 +45,13 @@ type Config struct {
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
Log LogConfig `json:"log"`
}
func DefaultConfig() *Config {
tempDir := filepath.Join(flags.DataDir, "temp")
indexDir := filepath.Join(flags.DataDir, "bleve")
logPath := filepath.Join(flags.DataDir, "log/log.log")
dbPath := filepath.Join(flags.DataDir, "data.db")
return &Config{
@ -64,6 +66,7 @@ func DefaultConfig() *Config {
TablePrefix: "x_",
DBFile: dbPath,
},
BleveDir: indexDir,
Log: LogConfig{
Enable: true,
Name: logPath,

View File

@ -25,7 +25,7 @@ const (
AudioTypes = "audio_types"
VideoTypes = "video_types"
ImageTypes = "image_types"
//OfficeTypes = "office_types"
// OfficeTypes = "office_types"
ProxyTypes = "proxy_types"
OfficeViewers = "office_viewers"
PdfViewers = "pdf_viewers"
@ -37,16 +37,22 @@ const (
CustomizeHead = "customize_head"
CustomizeBody = "customize_body"
LinkExpiration = "link_expiration"
SignAll = "sign_all"
PrivacyRegs = "privacy_regs"
OcrApi = "ocr_api"
FilenameCharMapping = "filename_char_mapping"
// index
SearchIndex = "search_index"
IgnorePaths = "ignore_paths"
// aria2
Aria2Uri = "aria2_uri"
Aria2Secret = "aria2_secret"
// single
Token = "token"
Token = "token"
IndexProgress = "index_progress"
)
const (

View File

@ -12,13 +12,18 @@ var db *gorm.DB
func Init(d *gorm.DB) {
db = d
var err error
if conf.Conf.Database.Type == "mysql" {
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
} else {
err = db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
}
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode))
if err != nil {
log.Fatalf("failed migrate database: %s", err.Error())
}
}
func AutoMigrate(dst ...interface{}) error {
var err error
if conf.Conf.Database.Type == "mysql" {
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(dst...)
} else {
err = db.AutoMigrate(dst...)
}
return err
}

62
internal/db/searchnode.go Normal file
View File

@ -0,0 +1,62 @@
package db
import (
"fmt"
"path"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
)
func CreateSearchNode(node *model.SearchNode) error {
return db.Create(node).Error
}
func BatchCreateSearchNodes(nodes *[]model.SearchNode) error {
return db.CreateInBatches(nodes, 1000).Error
}
func DeleteSearchNodesByParent(prefix string) error {
err := db.Where(fmt.Sprintf("%s LIKE ?",
columnName("parent")), fmt.Sprintf("%s%%", prefix)).
Delete(&model.SearchNode{}).Error
if err != nil {
return err
}
dir, name := path.Split(prefix)
return db.Where(fmt.Sprintf("%s = ? AND %s = ?",
columnName("parent"), columnName("name")),
utils.StandardizePath(dir), name).Delete(&model.SearchNode{}).Error
}
func ClearSearchNodes() error {
return db.Where("1 = 1").Delete(&model.SearchNode{}).Error
}
func GetSearchNodesByParent(parent string) ([]model.SearchNode, error) {
var nodes []model.SearchNode
if err := db.Where(fmt.Sprintf("%s = ?",
columnName("parent")), parent).Find(&nodes).Error; err != nil {
return nil, err
}
return nodes, nil
}
func SearchNode(req model.SearchReq) ([]model.SearchNode, int64, error) {
searchDB := db.Model(&model.SearchNode{}).Where(
fmt.Sprintf("%s LIKE ? AND %s LIKE ?",
columnName("parent"),
columnName("name")),
fmt.Sprintf("%s%%", req.Parent),
fmt.Sprintf("%%%s%%", req.Keywords))
var count int64
if err := searchDB.Count(&count).Error; err != nil {
return nil, 0, errors.Wrapf(err, "failed get users count")
}
var files []model.SearchNode
if err := searchDB.Offset((req.Page - 1) * req.PerPage).Limit(req.PerPage).Find(&files).Error; err != nil {
return nil, 0, err
}
return files, count, nil
}

View File

@ -11,81 +11,64 @@ import (
log "github.com/sirupsen/logrus"
)
type SettingItemHook struct {
Hook func(item *model.SettingItem) error
}
type SettingItemHook func(item *model.SettingItem) error
var SettingItemHooks = map[string]SettingItemHook{
conf.VideoTypes: {
Hook: func(item *model.SettingItem) error {
conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
return nil
},
var settingItemHooks = map[string]SettingItemHook{
conf.VideoTypes: func(item *model.SettingItem) error {
conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
return nil
},
conf.AudioTypes: {
Hook: func(item *model.SettingItem) error {
conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
return nil
},
conf.AudioTypes: func(item *model.SettingItem) error {
conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ImageTypes: {
Hook: func(item *model.SettingItem) error {
conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ImageTypes: func(item *model.SettingItem) error {
conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
return nil
},
conf.TextTypes: {
Hook: func(item *model.SettingItem) error {
conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
return nil
},
conf.TextTypes: func(item *model.SettingItem) error {
conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
return nil
},
//conf.OfficeTypes: {
// Hook: func(item *model.SettingItem) error {
// conf.TypesMap[conf.OfficeTypes] = strings.Split(item.Value, ",")
// return nil
// },
//},
conf.ProxyTypes: {
func(item *model.SettingItem) error {
conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ProxyTypes: func(item *model.SettingItem) error {
conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
return nil
},
conf.PrivacyRegs: {
Hook: func(item *model.SettingItem) error {
regStrs := strings.Split(item.Value, "\n")
regs := make([]*regexp.Regexp, 0, len(regStrs))
for _, regStr := range regStrs {
reg, err := regexp.Compile(regStr)
if err != nil {
return errors.WithStack(err)
}
regs = append(regs, reg)
}
conf.PrivacyReg = regs
return nil
},
},
conf.FilenameCharMapping: {
Hook: func(item *model.SettingItem) error {
err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
conf.PrivacyRegs: func(item *model.SettingItem) error {
regStrs := strings.Split(item.Value, "\n")
regs := make([]*regexp.Regexp, 0, len(regStrs))
for _, regStr := range regStrs {
reg, err := regexp.Compile(regStr)
if err != nil {
return err
return errors.WithStack(err)
}
log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
return nil
},
regs = append(regs, reg)
}
conf.PrivacyReg = regs
return nil
},
conf.FilenameCharMapping: func(item *model.SettingItem) error {
err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
if err != nil {
return err
}
log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
return nil
},
}
func HandleSettingItem(item *model.SettingItem) (bool, error) {
if hook, ok := SettingItemHooks[item.Key]; ok {
return true, hook.Hook(item)
if hook, ok := settingItemHooks[item.Key]; ok {
return true, hook(item)
}
return false, nil
}
func RegisterSettingItemHook(key string, hook SettingItemHook) {
settingItemHooks[key] = hook
}
// func HandleSettingItems(items []model.SettingItem) error {
// for i := range items {
// if err := HandleSettingItem(&items[i]); err != nil {

View File

@ -4,44 +4,43 @@ import (
"fmt"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/generic_sync"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
var settingsMap map[string]string
var publicSettingsMap map[string]string
var settingsMap generic_sync.MapOf[string, string]
var publicSettingsMap generic_sync.MapOf[string, string]
func settingsUpdate() {
settingsMap = nil
publicSettingsMap = nil
settingsMap.Clear()
publicSettingsMap.Clear()
}
func GetPublicSettingsMap() map[string]string {
if publicSettingsMap == nil {
publicSettingsMap = make(map[string]string)
if publicSettingsMap.Empty() {
settingItems, err := GetPublicSettingItems()
if err != nil {
log.Errorf("failed to get settingItems: %+v", err)
}
for _, settingItem := range settingItems {
publicSettingsMap[settingItem.Key] = settingItem.Value
publicSettingsMap.Store(settingItem.Key, settingItem.Value)
}
}
return publicSettingsMap
return publicSettingsMap.ToMap()
}
func GetSettingsMap() map[string]string {
if settingsMap == nil {
settingsMap = make(map[string]string)
func GetSettingsMap() *generic_sync.MapOf[string, string] {
if settingsMap.Empty() {
settingItems, err := GetSettingItems()
if err != nil {
log.Errorf("failed to get settingItems: %+v", err)
}
for _, settingItem := range settingItems {
settingsMap[settingItem.Key] = settingItem.Value
settingsMap.Store(settingItem.Key, settingItem.Value)
}
}
return settingsMap
return &settingsMap
}
func GetSettingItems() ([]model.SettingItem, error) {
@ -108,11 +107,17 @@ func SaveSettingItems(items []model.SettingItem) error {
others = append(others, items[i])
}
}
err := db.Save(others).Error
if err == nil {
settingsUpdate()
if len(others) > 0 {
err := db.Save(others).Error
if err != nil {
if len(others) < len(items) {
settingsUpdate()
}
return err
}
}
return err
settingsUpdate()
return nil
}
func SaveSettingItem(item model.SettingItem) error {

7
internal/errs/search.go Normal file
View File

@ -0,0 +1,7 @@
package errs
import "fmt"
var (
SearchNotAvailable = fmt.Errorf("search not available")
)

View File

@ -84,7 +84,7 @@ func hide(objs []model.Obj, meta *model.Meta) []model.Obj {
deleted := make([]bool, len(objs))
rs := strings.Split(meta.Hide, "\n")
for _, r := range rs {
re, _ := regexp.Compile(r)
re := regexp.MustCompile(r)
for i, obj := range objs {
if deleted[i] {
continue

45
internal/fs/walk.go Normal file
View File

@ -0,0 +1,45 @@
package fs
import (
"context"
"path"
"path/filepath"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
)
// WalkFS traverses filesystem fs starting at name up to depth levels.
//
// WalkFS will stop when current depth > `depth`. For each visited node,
// WalkFS calls walkFn. If a visited file system node is a directory and
// walkFn returns path.SkipDir, walkFS will skip traversal of this node.
func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn func(reqPath string, info model.Obj) error) error {
// This implementation is based on Walk's code in the standard path/path package.
walkFnErr := walkFn(name, info)
if walkFnErr != nil {
if info.IsDir() && walkFnErr == filepath.SkipDir {
return nil
}
return walkFnErr
}
if !info.IsDir() || depth == 0 {
return nil
}
meta, _ := db.GetNearestMeta(name)
// Read directory names.
objs, err := List(context.WithValue(ctx, "meta", meta), name)
if err != nil {
return walkFnErr
}
for _, fileInfo := range objs {
filename := path.Join(name, fileInfo.GetName())
if err := WalkFS(ctx, depth-1, filename, fileInfo, walkFn); err != nil {
if err == filepath.SkipDir {
break
}
return err
}
}
return nil
}

View File

@ -1,6 +1,10 @@
package model
import "time"
import (
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
type Object struct {
ID string
@ -12,7 +16,7 @@ type Object struct {
}
func (o *Object) GetName() string {
return o.Name
return utils.MappingName(o.Name)
}
func (o *Object) GetSize() int64 {

View File

@ -1,4 +1,4 @@
package common
package model
type PageReq struct {
Page int `json:"page" form:"page"`

40
internal/model/search.go Normal file
View File

@ -0,0 +1,40 @@
package model
import (
"fmt"
"time"
)
type IndexProgress struct {
ObjCount uint64 `json:"obj_count"`
IsDone bool `json:"is_done"`
LastDoneTime *time.Time `json:"last_done_time"`
Error string `json:"error"`
}
type SearchReq struct {
Parent string `json:"parent"`
Keywords string `json:"keywords"`
PageReq
}
type SearchNode struct {
Parent string `json:"parent" gorm:"index"`
Name string `json:"name" gorm:"index"`
IsDir bool `json:"is_dir"`
Size int64 `json:"size"`
}
func (p *SearchReq) Validate() error {
if p.Page < 1 {
return fmt.Errorf("page can't < 1")
}
if p.PerPage < 1 {
return fmt.Errorf("per_page can't < 1")
}
return nil
}
func (s *SearchNode) Type() string {
return "SearchNode"
}

View File

@ -1,12 +1,13 @@
package model
const (
SITE = iota
SINGLE = iota
SITE
STYLE
PREVIEW
GLOBAL
SINGLE
ARIA2
INDEX
)
const (

View File

@ -2,6 +2,7 @@ package model
import (
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
)
@ -89,3 +90,7 @@ func (u User) CanWebdavRead() bool {
func (u User) CanWebdavManage() bool {
return u.IsAdmin() || (u.Permission>>9)&1 == 1
}
func (u User) JoinPath(reqPath string) (string, error) {
return utils.JoinBasePath(u.BasePath, reqPath)
}

View File

@ -8,7 +8,6 @@ import (
"time"
"github.com/Xhofe/go-cache"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
@ -59,6 +58,12 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
if err != nil {
return nil, errors.Wrapf(err, "failed to list objs")
}
// call hooks
go func(reqPath string, files []model.Obj) {
for _, hook := range objsUpdateHooks {
hook(args.ReqPath, files)
}
}(args.ReqPath, files)
if !storage.Config().NoCache {
if len(files) > 0 {
log.Debugf("set cache: %s => %+v", key, files)
@ -123,7 +128,7 @@ func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, er
}
for _, f := range files {
// TODO maybe copy obj here
if utils.MappingName(f.GetName(), conf.FilenameCharMap) == name {
if f.GetName() == name {
// use path as id, why don't set id in List function?
// because files maybe cache, set id here can reduce memory usage
if f.GetPath() == "" {

13
internal/op/hook.go Normal file
View File

@ -0,0 +1,13 @@
package op
import "github.com/alist-org/alist/v3/internal/model"
type ObjsUpdateHook = func(parent string, objs []model.Obj)
var (
objsUpdateHooks = make([]ObjsUpdateHook, 0)
)
func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
objsUpdateHooks = append(objsUpdateHooks, hook)
}

View File

@ -0,0 +1,47 @@
package bleve
import (
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/search/searcher"
"github.com/blevesearch/bleve/v2"
log "github.com/sirupsen/logrus"
)
var config = searcher.Config{
Name: "bleve",
}
func Init(indexPath *string) (bleve.Index, error) {
log.Debugf("bleve path: %s", *indexPath)
fileIndex, err := bleve.Open(*indexPath)
if err == bleve.ErrorIndexPathDoesNotExist {
log.Infof("Creating new index...")
indexMapping := bleve.NewIndexMapping()
searchNodeMapping := bleve.NewDocumentMapping()
searchNodeMapping.AddFieldMappingsAt("is_dir", bleve.NewBooleanFieldMapping())
// TODO: appoint analyzer
parentFieldMapping := bleve.NewTextFieldMapping()
searchNodeMapping.AddFieldMappingsAt("parent", parentFieldMapping)
// TODO: appoint analyzer
nameFieldMapping := bleve.NewKeywordFieldMapping()
searchNodeMapping.AddFieldMappingsAt("name", nameFieldMapping)
indexMapping.AddDocumentMapping("SearchNode", searchNodeMapping)
fileIndex, err = bleve.New(*indexPath, indexMapping)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return fileIndex, nil
}
func init() {
searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
b, err := Init(&conf.Conf.BleveDir)
if err != nil {
return nil, err
}
return &Bleve{BIndex: b}, nil
})
}

View File

@ -0,0 +1,93 @@
package bleve
import (
"context"
"os"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/search/searcher"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/blevesearch/bleve/v2"
search2 "github.com/blevesearch/bleve/v2/search"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
type Bleve struct {
BIndex bleve.Index
}
func (b *Bleve) Config() searcher.Config {
return config
}
func (b *Bleve) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
query := bleve.NewMatchQuery(req.Keywords)
query.SetField("name")
search := bleve.NewSearchRequest(query)
search.Size = req.PerPage
search.Fields = []string{"*"}
searchResults, err := b.BIndex.Search(search)
if err != nil {
log.Errorf("search error: %+v", err)
return nil, 0, err
}
res, err := utils.SliceConvert(searchResults.Hits, func(src *search2.DocumentMatch) (model.SearchNode, error) {
return model.SearchNode{
Parent: src.Fields["parent"].(string),
Name: src.Fields["name"].(string),
IsDir: src.Fields["is_dir"].(bool),
Size: int64(src.Fields["size"].(float64)),
}, nil
})
return res, int64(len(res)), nil
}
func (b *Bleve) Index(ctx context.Context, node model.SearchNode) error {
return b.BIndex.Index(uuid.NewString(), node)
}
func (b *Bleve) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
batch := b.BIndex.NewBatch()
for _, node := range nodes {
batch.Index(uuid.NewString(), node)
}
return b.BIndex.Batch(batch)
}
func (b *Bleve) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
return nil, errs.NotSupport
}
func (b *Bleve) Del(ctx context.Context, prefix string) error {
return errs.NotSupport
}
func (b *Bleve) Release(ctx context.Context) error {
if b.BIndex != nil {
return b.BIndex.Close()
}
return nil
}
func (b *Bleve) Clear(ctx context.Context) error {
err := b.Release(ctx)
if err != nil {
return err
}
log.Infof("Removing old index...")
err = os.RemoveAll(conf.Conf.BleveDir)
if err != nil {
log.Errorf("clear bleve error: %+v", err)
}
bIndex, err := Init(&conf.Conf.BleveDir)
if err != nil {
return err
}
b.BIndex = bIndex
return nil
}
var _ searcher.Searcher = (*Bleve)(nil)

220
internal/search/build.go Normal file
View File

@ -0,0 +1,220 @@
package search
import (
"context"
"path"
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/mq"
"github.com/alist-org/alist/v3/pkg/utils"
mapset "github.com/deckarep/golang-set/v2"
log "github.com/sirupsen/logrus"
)
var (
Running = atomic.Bool{}
Quit chan struct{}
)
func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth int, count bool) error {
var (
err error
objCount uint64 = 0
fi model.Obj
)
Running.Store(true)
Quit = make(chan struct{}, 1)
indexMQ := mq.NewInMemoryMQ[ObjWithParent]()
go func() {
ticker := time.NewTicker(5 * time.Second)
for {
select {
case <-ticker.C:
log.Infof("index obj count: %d", objCount)
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
if len(messages) != 0 {
log.Debugf("current index: %s", messages[len(messages)-1].Content.Parent)
}
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
func(src mq.Message[ObjWithParent]) ObjWithParent {
return src.Content
})); err != nil {
log.Errorf("build index in batch error: %+v", err)
} else {
objCount = objCount + uint64(len(messages))
}
if count {
WriteProgress(&model.IndexProgress{
ObjCount: objCount,
IsDone: false,
LastDoneTime: nil,
})
}
})
case <-Quit:
Running.Store(false)
ticker.Stop()
eMsg := ""
now := time.Now()
originErr := err
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
func(src mq.Message[ObjWithParent]) ObjWithParent {
return src.Content
})); err != nil {
log.Errorf("build index in batch error: %+v", err)
} else {
objCount = objCount + uint64(len(messages))
}
if originErr != nil {
log.Errorf("build index error: %+v", originErr)
eMsg = originErr.Error()
} else {
log.Infof("success build index, count: %d", objCount)
}
if count {
WriteProgress(&model.IndexProgress{
ObjCount: objCount,
IsDone: true,
LastDoneTime: &now,
Error: eMsg,
})
}
})
return
}
}
}()
defer func() {
if Running.Load() {
Quit <- struct{}{}
}
}()
admin, err := db.GetAdmin()
if err != nil {
return err
}
if count {
WriteProgress(&model.IndexProgress{
ObjCount: 0,
IsDone: false,
})
}
for _, indexPath := range indexPaths {
walkFn := func(indexPath string, info model.Obj) error {
if !Running.Load() {
return filepath.SkipDir
}
for _, avoidPath := range ignorePaths {
if strings.HasPrefix(indexPath, avoidPath) {
return filepath.SkipDir
}
}
// ignore root
if indexPath == "/" {
return nil
}
indexMQ.Publish(mq.Message[ObjWithParent]{
Content: ObjWithParent{
Obj: info,
Parent: path.Dir(indexPath),
},
})
return nil
}
fi, err = fs.Get(ctx, indexPath)
if err != nil {
return err
}
// TODO: run walkFS concurrently
err = fs.WalkFS(context.WithValue(ctx, "user", admin), maxDepth, indexPath, fi, walkFn)
if err != nil {
return err
}
}
return nil
}
func Clear(ctx context.Context) error {
return instance.Clear(ctx)
}
func Update(parent string, objs []model.Obj) {
if instance == nil || !instance.Config().AutoUpdate || Running.Load() {
return
}
ignorePaths, err := GetIgnorePaths()
if err != nil {
log.Errorf("update search index error while get ignore paths: %+v", err)
return
}
if isIgnorePath(parent, ignorePaths) {
return
}
ctx := context.Background()
// only update when index have built
progress, err := Progress()
if err != nil {
log.Errorf("update search index error while get progress: %+v", err)
return
}
if !progress.IsDone {
return
}
nodes, err := instance.Get(ctx, parent)
if err != nil {
log.Errorf("update search index error while get nodes: %+v", err)
return
}
now := mapset.NewSet[string]()
for i := range objs {
now.Add(objs[i].GetName())
}
old := mapset.NewSet[string]()
for i := range nodes {
old.Add(nodes[i].Name)
}
// delete data that no longer exists
toDelete := old.Difference(now)
toAdd := now.Difference(old)
for i := range nodes {
if toDelete.Contains(nodes[i].Name) {
log.Debugf("delete index: %s", path.Join(parent, nodes[i].Name))
err = instance.Del(ctx, path.Join(parent, nodes[i].Name))
if err != nil {
log.Errorf("update search index error while del old node: %+v", err)
return
}
}
}
for i := range objs {
if toAdd.Contains(objs[i].GetName()) {
log.Debugf("add index: %s", path.Join(parent, objs[i].GetName()))
err = Index(ctx, parent, objs[i])
if err != nil {
log.Errorf("update search index error while index new node: %+v", err)
return
}
// build index if it's a folder
if objs[i].IsDir() {
err = BuildIndex(ctx, []string{path.Join(parent, objs[i].GetName())}, ignorePaths, -1, false)
if err != nil {
log.Errorf("update search index error while build index: %+v", err)
return
}
}
}
}
}
func init() {
op.RegisterObjsUpdateHook(Update)
}

View File

@ -0,0 +1,16 @@
package db
import (
"github.com/alist-org/alist/v3/internal/search/searcher"
)
var config = searcher.Config{
Name: "database",
AutoUpdate: true,
}
func init() {
searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
return &DB{}, nil
})
}

View File

@ -0,0 +1,45 @@
package db
import (
"context"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/search/searcher"
)
type DB struct{}
func (D DB) Config() searcher.Config {
return config
}
func (D DB) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
return db.SearchNode(req)
}
func (D DB) Index(ctx context.Context, node model.SearchNode) error {
return db.CreateSearchNode(&node)
}
func (D DB) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
return db.BatchCreateSearchNodes(&nodes)
}
func (D DB) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
return db.GetSearchNodesByParent(parent)
}
func (D DB) Del(ctx context.Context, prefix string) error {
return db.DeleteSearchNodesByParent(prefix)
}
func (D DB) Release(ctx context.Context) error {
return nil
}
func (D DB) Clear(ctx context.Context) error {
return db.ClearSearchNodes()
}
var _ searcher.Searcher = (*DB)(nil)

View File

@ -0,0 +1,6 @@
package search
import (
_ "github.com/alist-org/alist/v3/internal/search/bleve"
_ "github.com/alist-org/alist/v3/internal/search/db"
)

95
internal/search/search.go Normal file
View File

@ -0,0 +1,95 @@
package search
import (
"context"
"fmt"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/search/searcher"
log "github.com/sirupsen/logrus"
)
var instance searcher.Searcher = nil
// Init or reset index
func Init(mode string) error {
if instance != nil {
// unchanged, do nothing
if instance.Config().Name == mode {
return nil
}
err := instance.Release(context.Background())
if err != nil {
log.Errorf("release instance err: %+v", err)
}
instance = nil
}
if Running.Load() {
return fmt.Errorf("index is running")
}
if mode == "none" {
log.Warnf("not enable search")
return nil
}
s, ok := searcher.NewMap[mode]
if !ok {
return fmt.Errorf("not support index: %s", mode)
}
i, err := s()
if err != nil {
log.Errorf("init searcher error: %+v", err)
} else {
instance = i
}
return err
}
func Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
return instance.Search(ctx, req)
}
func Index(ctx context.Context, parent string, obj model.Obj) error {
if instance == nil {
return errs.SearchNotAvailable
}
return instance.Index(ctx, model.SearchNode{
Parent: parent,
Name: obj.GetName(),
IsDir: obj.IsDir(),
Size: obj.GetSize(),
})
}
type ObjWithParent struct {
Parent string
model.Obj
}
func BatchIndex(ctx context.Context, objs []ObjWithParent) error {
if instance == nil {
return errs.SearchNotAvailable
}
if len(objs) == 0 {
return nil
}
var searchNodes []model.SearchNode
for i := range objs {
searchNodes = append(searchNodes, model.SearchNode{
Parent: objs[i].Parent,
Name: objs[i].GetName(),
IsDir: objs[i].IsDir(),
Size: objs[i].GetSize(),
})
}
return instance.BatchIndex(ctx, searchNodes)
}
func init() {
db.RegisterSettingItemHook(conf.SearchIndex, func(item *model.SettingItem) error {
log.Debugf("searcher init, mode: %s", item.Value)
return Init(item.Value)
})
}

View File

@ -0,0 +1,9 @@
package searcher
type New func() (Searcher, error)
var NewMap = map[string]New{}
func RegisterSearcher(config Config, searcher New) {
NewMap[config.Name] = searcher
}

View File

@ -0,0 +1,31 @@
package searcher
import (
"context"
"github.com/alist-org/alist/v3/internal/model"
)
type Config struct {
Name string
AutoUpdate bool
}
type Searcher interface {
// Config of the searcher
Config() Config
// Search specific keywords in specific path
Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error)
// Index obj with parent
Index(ctx context.Context, node model.SearchNode) error
// Index obj with parent in batches
BatchIndex(ctx context.Context, nodes []model.SearchNode) error
// Get by parent
Get(ctx context.Context, parent string) ([]model.SearchNode, error)
// Del with prefix
Del(ctx context.Context, prefix string) error
// Release resource
Release(ctx context.Context) error
// Clear all index
Clear(ctx context.Context) error
}

65
internal/search/util.go Normal file
View File

@ -0,0 +1,65 @@
package search
import (
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/pkg/utils"
log "github.com/sirupsen/logrus"
)
func Progress() (*model.IndexProgress, error) {
p := setting.GetStr(conf.IndexProgress)
var progress model.IndexProgress
err := utils.Json.UnmarshalFromString(p, &progress)
return &progress, err
}
func WriteProgress(progress *model.IndexProgress) {
p, err := utils.Json.MarshalToString(progress)
if err != nil {
log.Errorf("marshal progress error: %+v", err)
}
err = db.SaveSettingItem(model.SettingItem{
Key: conf.IndexProgress,
Value: p,
Type: conf.TypeText,
Group: model.SINGLE,
Flag: model.PRIVATE,
})
if err != nil {
log.Errorf("save progress error: %+v", err)
}
}
func GetIgnorePaths() ([]string, error) {
storages, err := db.GetEnabledStorages()
if err != nil {
return nil, err
}
ignorePaths := make([]string, 0)
var skipDrivers = []string{"AList V2", "AList V3"}
for _, storage := range storages {
if utils.SliceContains(skipDrivers, storage.Driver) {
// TODO: request for indexing permission
ignorePaths = append(ignorePaths, storage.MountPath)
}
}
customIgnorePaths := setting.GetStr(conf.IgnorePaths)
if customIgnorePaths != "" {
ignorePaths = append(ignorePaths, strings.Split(customIgnorePaths, "\n")...)
}
return ignorePaths, nil
}
func isIgnorePath(path string, ignorePaths []string) bool {
for _, ignorePath := range ignorePaths {
if strings.HasPrefix(path, ignorePath) {
return true
}
}
return false
}

View File

@ -7,7 +7,7 @@ import (
)
func GetStr(key string, defaultValue ...string) string {
val, ok := db.GetSettingsMap()[key]
val, ok := db.GetSettingsMap().Load(key)
if !ok {
if len(defaultValue) > 0 {
return defaultValue[0]

75
pkg/generic/queue.go Normal file
View File

@ -0,0 +1,75 @@
package generic
type Queue[T any] struct {
queue []T
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{queue: make([]T, 0)}
}
func (q *Queue[T]) Push(v T) {
q.queue = append(q.queue, v)
}
func (q *Queue[T]) Pop() T {
v := q.queue[0]
q.queue = q.queue[1:]
return v
}
func (q *Queue[T]) Len() int {
return len(q.queue)
}
func (q *Queue[T]) IsEmpty() bool {
return len(q.queue) == 0
}
func (q *Queue[T]) Clear() {
q.queue = nil
}
func (q *Queue[T]) Peek() T {
return q.queue[0]
}
func (q *Queue[T]) PeekN(n int) []T {
return q.queue[:n]
}
func (q *Queue[T]) PopN(n int) []T {
v := q.queue[:n]
q.queue = q.queue[n:]
return v
}
func (q *Queue[T]) PopAll() []T {
v := q.queue
q.queue = nil
return v
}
func (q *Queue[T]) PopWhile(f func(T) bool) []T {
var i int
for i = 0; i < len(q.queue); i++ {
if !f(q.queue[i]) {
break
}
}
v := q.queue[:i]
q.queue = q.queue[i:]
return v
}
func (q *Queue[T]) PopUntil(f func(T) bool) []T {
var i int
for i = 0; i < len(q.queue); i++ {
if f(q.queue[i]) {
break
}
}
v := q.queue[:i]
q.queue = q.queue[i:]
return v
}

View File

@ -347,6 +347,23 @@ func (m *MapOf[K, V]) Values() []V {
return values
}
func (m *MapOf[K, V]) Count() int {
return len(m.dirty)
}
func (m *MapOf[K, V]) Empty() bool {
return m.Count() == 0
}
func (m *MapOf[K, V]) ToMap() map[K]V {
ans := make(map[K]V)
m.Range(func(key K, value V) bool {
ans[key] = value
return true
})
return ans
}
func (m *MapOf[K, V]) Clear() {
m.Range(func(key K, value V) bool {
m.Delete(key)

View File

@ -342,6 +342,58 @@ func (c *Client) Read(path string) ([]byte, error) {
return buf.Bytes(), nil
}
func (c *Client) Link(path string) (string, http.Header, error) {
method := "HEAD"
url := PathEscape(Join(c.root, path))
r, err := http.NewRequest(method, url, nil)
if err != nil {
return "", nil, newPathErrorErr("Link", path, err)
}
for k, vals := range c.headers {
for _, v := range vals {
r.Header.Add(k, v)
}
}
c.authMutex.Lock()
auth := c.auth
c.authMutex.Unlock()
auth.Authorize(r, method, path)
if c.interceptor != nil {
c.interceptor(method, r)
}
rs, err := c.c.Do(r)
if err != nil {
return "", nil, newPathErrorErr("Link", path, err)
}
if rs.StatusCode == 401 {
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))
if strings.Contains(wwwAuthenticateHeader, "digest") {
c.authMutex.Lock()
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
c.auth.Authorize(r, method, path)
c.authMutex.Unlock()
} else if strings.Contains(wwwAuthenticateHeader, "basic") {
c.authMutex.Lock()
c.auth = &BasicAuth{auth.User(), auth.Pass()}
c.auth.Authorize(r, method, path)
c.authMutex.Unlock()
} else {
return "", nil, newPathError("Authorize", c.root, rs.StatusCode)
}
} else if rs.StatusCode > 400 {
return "", nil, newPathError("Authorize", path, rs.StatusCode)
}
return r.URL.String(), r.Header, nil
}
// ReadStream reads the stream for a given path
func (c *Client) ReadStream(path string, callback func(rq *http.Request)) (io.ReadCloser, http.Header, error) {
rs, err := c.req("GET", path, nil, callback)

56
pkg/mq/mq.go Normal file
View File

@ -0,0 +1,56 @@
package mq
import (
"sync"
"github.com/alist-org/alist/v3/pkg/generic"
)
type Message[T any] struct {
Content T
}
type BasicConsumer[T any] func(Message[T])
type AllConsumer[T any] func([]Message[T])
type MQ[T any] interface {
Publish(Message[T])
Consume(BasicConsumer[T])
ConsumeAll(AllConsumer[T])
Clear()
}
type inMemoryMQ[T any] struct {
queue generic.Queue[Message[T]]
sync.Mutex
}
func NewInMemoryMQ[T any]() MQ[T] {
return &inMemoryMQ[T]{queue: *generic.NewQueue[Message[T]]()}
}
func (mq *inMemoryMQ[T]) Publish(msg Message[T]) {
mq.Lock()
defer mq.Unlock()
mq.queue.Push(msg)
}
func (mq *inMemoryMQ[T]) Consume(consumer BasicConsumer[T]) {
mq.Lock()
defer mq.Unlock()
for !mq.queue.IsEmpty() {
consumer(mq.queue.Pop())
}
}
func (mq *inMemoryMQ[T]) ConsumeAll(consumer AllConsumer[T]) {
mq.Lock()
defer mq.Unlock()
consumer(mq.queue.PopAll())
}
func (mq *inMemoryMQ[T]) Clear() {
mq.Lock()
defer mq.Unlock()
mq.queue.Clear()
}

View File

@ -8,8 +8,8 @@ import (
)
type Manager[K comparable] struct {
workerC chan struct{}
curID K
workerC chan struct{}
updateID func(*K)
tasks generic_sync.MapOf[K, *Task[K]]
}
@ -94,7 +94,7 @@ func (tm *Manager[K]) RemoveByStates(states ...string) {
tasks := tm.GetAll()
for _, task := range tasks {
if utils.SliceContains(states, task.GetState()) {
tm.Remove(task.ID)
_ = tm.Remove(task.ID)
}
}
}

View File

@ -138,6 +138,13 @@ func GetFileType(filename string) int {
return conf.UNKNOWN
}
func GetObjType(filename string, isDir bool) int {
if isDir {
return conf.FOLDER
}
return GetFileType(filename)
}
func GetMimeType(name string) string {
ext := path.Ext(name)
m := mime.TypeByExtension(ext)

View File

@ -1,6 +1,7 @@
package utils
import (
stdjson "encoding/json"
"os"
json "github.com/json-iterator/go"
@ -10,8 +11,11 @@ import (
var Json = json.ConfigCompatibleWithStandardLibrary
// WriteJsonToFile write struct to json file
func WriteJsonToFile(dst string, data interface{}) bool {
func WriteJsonToFile(dst string, data interface{}, std ...bool) bool {
str, err := json.MarshalIndent(data, "", " ")
if len(std) > 0 && std[0] {
str, err = stdjson.MarshalIndent(data, "", " ")
}
if err != nil {
log.Errorf("failed convert Conf to []byte:%s", err.Error())
return false

View File

@ -6,6 +6,8 @@ import (
"path/filepath"
"runtime"
"strings"
"github.com/alist-org/alist/v3/internal/errs"
)
// StandardizePath convert path like '/' '/root' '/a/b'
@ -60,3 +62,10 @@ func EncodePath(path string, all ...bool) string {
}
return strings.Join(seg, "/")
}
func JoinBasePath(basePath, reqPath string) (string, error) {
if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
return "", errs.RelativePath
}
return stdpath.Join(basePath, reqPath), nil
}

View File

@ -35,3 +35,12 @@ func SliceConvert[S any, D any](srcS []S, convert func(src S) (D, error)) ([]D,
}
return res, nil
}
func MustSliceConvert[S any, D any](srcS []S, convert func(src S) D) []D {
var res []D
for i := range srcS {
dst := convert(srcS[i])
res = append(res, dst)
}
return res
}

View File

@ -1,9 +1,13 @@
package utils
import "strings"
import (
"strings"
func MappingName(name string, m map[string]string) string {
for k, v := range m {
"github.com/alist-org/alist/v3/internal/conf"
)
func MappingName(name string) string {
for k, v := range conf.FilenameCharMap {
name = strings.ReplaceAll(name, k, v)
}
return name

View File

@ -1,6 +1,9 @@
package common
import (
"regexp"
"strings"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
@ -12,8 +15,17 @@ func CanWrite(meta *model.Meta, path string) bool {
return meta.WSub || meta.Path == path
}
func CanAccess(user *model.User, meta *model.Meta, path string, password string) bool {
// if is not guest, can access
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
// if the reqPath is in hide (only can check the nearest meta) and user can't see hides, can't access
if meta != nil && !user.CanSeeHides() && meta.Hide != "" {
for _, hide := range strings.Split(meta.Hide, "\n") {
re := regexp.MustCompile(hide)
if re.MatchString(reqPath[len(meta.Path):]) {
return false
}
}
}
// if is not guest and can access without password
if user.CanAccessWithoutPassword() {
return true
}
@ -22,7 +34,7 @@ func CanAccess(user *model.User, meta *model.Meta, path string, password string)
return true
}
// if meta doesn't apply to sub_folder, can access
if !utils.PathEqual(meta.Path, path) && !meta.PSub {
if !utils.PathEqual(meta.Path, reqPath) && !meta.PSub {
return true
}
// validate password

View File

@ -28,6 +28,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
if link.Header != nil {
// TODO clean header with blacklist or whitelist
link.Header.Del("set-cookie")
for h, val := range link.Header {
w.Header()[h] = val
}
@ -81,6 +83,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
_ = res.Body.Close()
}()
log.Debugf("proxy status: %d", res.StatusCode)
// TODO clean header with blacklist or whitelist
res.Header.Del("set-cookie")
for h, v := range res.Header {
w.Header()[h] = v
}

View File

@ -1,13 +1,16 @@
package common
import (
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/sign"
stdpath "path"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/internal/sign"
)
func Sign(obj model.Obj, parent string, encrypt bool) string {
if obj.IsDir() || !encrypt {
if obj.IsDir() || (!encrypt && !setting.GetBool(conf.SignAll)) {
return ""
}
return sign.Sign(stdpath.Join(parent, obj.GetName()))

View File

@ -1,8 +1,6 @@
package handles
import (
stdpath "path"
"github.com/alist-org/alist/v3/internal/aria2"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
@ -58,9 +56,13 @@ func AddAria2(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
req.Path = stdpath.Join(user.BasePath, req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, url := range req.Urls {
err := aria2.AddURI(c, url, req.Path)
err := aria2.AddURI(c, url, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return

View File

@ -26,25 +26,29 @@ func FsMkdir(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
if !user.CanWrite() {
meta, err := db.GetNearestMeta(stdpath.Dir(req.Path))
meta, err := db.GetNearestMeta(stdpath.Dir(reqPath))
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
return
}
}
if !common.CanWrite(meta, req.Path) {
if !common.CanWrite(meta, reqPath) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
}
if err := fs.MakeDir(c, req.Path); err != nil {
if err := fs.MakeDir(c, reqPath); err != nil {
common.ErrorResp(c, err, 500)
return
}
fs.ClearCache(stdpath.Dir(req.Path))
fs.ClearCache(stdpath.Dir(reqPath))
common.SuccessResp(c)
}
@ -69,17 +73,25 @@ func FsMove(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
srcDir, err := user.JoinPath(req.SrcDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
dstDir, err := user.JoinPath(req.DstDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, name := range req.Names {
err := fs.Move(c, stdpath.Join(req.SrcDir, name), req.DstDir)
err := fs.Move(c, stdpath.Join(srcDir, name), dstDir)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
}
fs.ClearCache(req.SrcDir)
fs.ClearCache(req.DstDir)
fs.ClearCache(srcDir)
fs.ClearCache(dstDir)
common.SuccessResp(c)
}
@ -98,11 +110,19 @@ func FsCopy(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
srcDir, err := user.JoinPath(req.SrcDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
dstDir, err := user.JoinPath(req.DstDir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
var addedTask []string
for _, name := range req.Names {
ok, err := fs.Copy(c, stdpath.Join(req.SrcDir, name), req.DstDir)
ok, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir)
if ok {
addedTask = append(addedTask, name)
}
@ -112,7 +132,7 @@ func FsCopy(c *gin.Context) {
}
}
if len(req.Names) != len(addedTask) {
fs.ClearCache(req.DstDir)
fs.ClearCache(dstDir)
}
if len(addedTask) > 0 {
common.SuccessResp(c, fmt.Sprintf("Added %d tasks", len(addedTask)))
@ -137,12 +157,16 @@ func FsRename(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.Path = stdpath.Join(user.BasePath, req.Path)
if err := fs.Rename(c, req.Path, req.Name); err != nil {
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
if err := fs.Rename(c, reqPath, req.Name); err != nil {
common.ErrorResp(c, err, 500)
return
}
fs.ClearCache(stdpath.Dir(req.Path))
fs.ClearCache(stdpath.Dir(reqPath))
common.SuccessResp(c)
}
@ -166,9 +190,13 @@ func FsRemove(c *gin.Context) {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
req.Dir = stdpath.Join(user.BasePath, req.Dir)
reqDir, err := user.JoinPath(req.Dir)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
for _, name := range req.Names {
err := fs.Remove(c, stdpath.Join(req.Dir, name))
err := fs.Remove(c, stdpath.Join(reqDir, name))
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -185,8 +213,10 @@ func Link(c *gin.Context) {
common.ErrorResp(c, err, 400)
return
}
user := c.MustGet("user").(*model.User)
rawPath := stdpath.Join(user.BasePath, req.Path)
//user := c.MustGet("user").(*model.User)
//rawPath := stdpath.Join(user.BasePath, req.Path)
// why need not join base_path? because it's always the full path
rawPath := req.Path
storage, err := fs.GetStorage(rawPath)
if err != nil {
common.ErrorResp(c, err, 500)

View File

@ -6,7 +6,6 @@ import (
"strings"
"time"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/fs"
@ -19,7 +18,7 @@ import (
)
type ListReq struct {
common.PageReq
model.PageReq
Path string `json:"path" form:"path"`
Password string `json:"password" form:"password"`
Refresh bool `json:"refresh"`
@ -57,8 +56,12 @@ func FsList(c *gin.Context) {
}
req.Validate()
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
meta, err := db.GetNearestMeta(req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
@ -66,30 +69,30 @@ func FsList(c *gin.Context) {
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
return
}
if !user.CanWrite() && !common.CanWrite(meta, req.Path) && req.Refresh {
if !user.CanWrite() && !common.CanWrite(meta, reqPath) && req.Refresh {
common.ErrorStrResp(c, "Refresh without permission", 403)
return
}
objs, err := fs.List(c, req.Path, req.Refresh)
objs, err := fs.List(c, reqPath, req.Refresh)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
total, objs := pagination(objs, &req.PageReq)
provider := "unknown"
storage, err := fs.GetStorage(req.Path)
storage, err := fs.GetStorage(reqPath)
if err == nil {
provider = storage.GetStorage().Driver
}
common.SuccessResp(c, FsListResp{
Content: toObjResp(objs, req.Path, isEncrypt(meta, req.Path)),
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath)),
Total: int64(total),
Readme: getReadme(meta, req.Path),
Write: user.CanWrite() || common.CanWrite(meta, req.Path),
Readme: getReadme(meta, reqPath),
Write: user.CanWrite() || common.CanWrite(meta, reqPath),
Provider: provider,
})
}
@ -101,15 +104,21 @@ func FsDirs(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
reqPath := req.Path
if req.ForceRoot {
if !user.IsAdmin() {
common.ErrorStrResp(c, "Permission denied", 403)
return
}
} else {
req.Path = stdpath.Join(user.BasePath, req.Path)
tmp, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
reqPath = tmp
}
meta, err := db.GetNearestMeta(req.Path)
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500, true)
@ -117,11 +126,11 @@ func FsDirs(c *gin.Context) {
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
return
}
objs, err := fs.List(c, req.Path)
objs, err := fs.List(c, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -140,7 +149,7 @@ func filterDirs(objs []model.Obj) []DirResp {
for _, obj := range objs {
if obj.IsDir() {
dirs = append(dirs, DirResp{
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
Name: obj.GetName(),
Modified: obj.ModTime(),
})
}
@ -165,7 +174,7 @@ func isEncrypt(meta *model.Meta, path string) bool {
return true
}
func pagination(objs []model.Obj, req *common.PageReq) (int, []model.Obj) {
func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
pageIndex, pageSize := req.Page, req.PerPage
total := len(objs)
start := (pageIndex - 1) * pageSize
@ -179,25 +188,21 @@ func pagination(objs []model.Obj, req *common.PageReq) (int, []model.Obj) {
return total, objs[start:end]
}
func toObjResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
var resp []ObjResp
for _, obj := range objs {
thumb := ""
if t, ok := obj.(model.Thumb); ok {
thumb = t.Thumb()
}
tp := conf.FOLDER
if !obj.IsDir() {
tp = utils.GetFileType(obj.GetName())
}
resp = append(resp, ObjResp{
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
Name: obj.GetName(),
Size: obj.GetSize(),
IsDir: obj.IsDir(),
Modified: obj.ModTime(),
Sign: common.Sign(obj, parent, encrypt),
Thumb: thumb,
Type: tp,
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
})
}
return resp
@ -223,8 +228,12 @@ func FsGet(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
meta, err := db.GetNearestMeta(req.Path)
reqPath, err := user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(reqPath)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
common.ErrorResp(c, err, 500)
@ -232,18 +241,18 @@ func FsGet(c *gin.Context) {
}
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
if !common.CanAccess(user, meta, reqPath, req.Password) {
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
return
}
obj, err := fs.Get(c, req.Path)
obj, err := fs.Get(c, reqPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
var rawURL string
storage, err := fs.GetStorage(req.Path)
storage, err := fs.GetStorage(reqPath)
provider := "unknown"
if err == nil {
provider = storage.Config().Name
@ -257,13 +266,13 @@ func FsGet(c *gin.Context) {
if storage.GetStorage().DownProxyUrl != "" {
rawURL = fmt.Sprintf("%s%s?sign=%s",
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
utils.EncodePath(req.Path, true),
sign.Sign(req.Path))
utils.EncodePath(reqPath, true),
sign.Sign(reqPath))
} else {
rawURL = fmt.Sprintf("%s/p%s?sign=%s",
common.GetApiUrl(c.Request),
utils.EncodePath(req.Path, true),
sign.Sign(req.Path))
utils.EncodePath(reqPath, true),
sign.Sign(reqPath))
}
} else {
// file have raw url
@ -271,7 +280,7 @@ func FsGet(c *gin.Context) {
rawURL = u.URL()
} else {
// if storage is not proxy, use raw url by fs.Link
link, _, err := fs.Link(c, req.Path, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
link, _, err := fs.Link(c, reqPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
if err != nil {
common.ErrorResp(c, err, 500)
return
@ -281,7 +290,7 @@ func FsGet(c *gin.Context) {
}
}
var related []model.Obj
parentPath := stdpath.Dir(req.Path)
parentPath := stdpath.Dir(reqPath)
sameLevelFiles, err := fs.List(c, parentPath)
if err == nil {
related = filterRelated(sameLevelFiles, obj)
@ -289,17 +298,17 @@ func FsGet(c *gin.Context) {
parentMeta, _ := db.GetNearestMeta(parentPath)
common.SuccessResp(c, FsGetResp{
ObjResp: ObjResp{
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
Name: obj.GetName(),
Size: obj.GetSize(),
IsDir: obj.IsDir(),
Modified: obj.ModTime(),
Sign: common.Sign(obj, parentPath, isEncrypt(meta, req.Path)),
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
Type: utils.GetFileType(obj.GetName()),
},
RawURL: rawURL,
Readme: getReadme(meta, req.Path),
Readme: getReadme(meta, reqPath),
Provider: provider,
Related: toObjResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
})
}
@ -329,7 +338,12 @@ func FsOther(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
req.Path = stdpath.Join(user.BasePath, req.Path)
var err error
req.Path, err = user.JoinPath(req.Path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(req.Path)
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
@ -339,7 +353,7 @@ func FsOther(c *gin.Context) {
}
c.Set("meta", meta)
if !common.CanAccess(user, meta, req.Path, req.Password) {
common.ErrorStrResp(c, "password is incorrect", 403)
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
return
}
res, err := fs.Other(c, req.FsOtherArgs)

View File

@ -21,8 +21,11 @@ func FsStream(c *gin.Context) {
}
asTask := c.GetHeader("As-Task") == "true"
user := c.MustGet("user").(*model.User)
path = stdpath.Join(user.BasePath, path)
path, err = user.JoinPath(path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
dir, name := stdpath.Split(path)
sizeStr := c.GetHeader("Content-Length")
size, err := strconv.ParseInt(sizeStr, 10, 64)
@ -61,8 +64,11 @@ func FsForm(c *gin.Context) {
}
asTask := c.GetHeader("As-Task") == "true"
user := c.MustGet("user").(*model.User)
path = stdpath.Join(user.BasePath, path)
path, err = user.JoinPath(path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
storage, err := fs.GetStorage(path)
if err != nil {
common.ErrorResp(c, err, 400)

65
server/handles/index.go Normal file
View File

@ -0,0 +1,65 @@
package handles
import (
"context"
"github.com/alist-org/alist/v3/internal/search"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
type BuildIndexReq struct {
Paths []string `json:"paths"`
MaxDepth int `json:"max_depth"`
IgnorePaths []string `json:"ignore_paths"`
}
func BuildIndex(c *gin.Context) {
var req BuildIndexReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
if search.Running.Load() {
common.ErrorStrResp(c, "index is running", 400)
return
}
ignorePaths, err := search.GetIgnorePaths()
if err != nil {
common.ErrorResp(c, err, 500)
return
}
ignorePaths = append(ignorePaths, req.IgnorePaths...)
go func() {
ctx := context.Background()
err := search.Clear(ctx)
if err != nil {
log.Errorf("clear index error: %+v", err)
return
}
err = search.BuildIndex(context.Background(), req.Paths, ignorePaths, req.MaxDepth, true)
if err != nil {
log.Errorf("build index error: %+v", err)
}
}()
common.SuccessResp(c)
}
func StopIndex(c *gin.Context) {
if !search.Running.Load() {
common.ErrorStrResp(c, "index is not running", 400)
return
}
search.Quit <- struct{}{}
common.SuccessResp(c)
}
func GetProgress(c *gin.Context) {
progress, err := search.Progress()
if err != nil {
common.ErrorResp(c, err, 500)
return
}
common.SuccessResp(c, progress)
}

View File

@ -15,7 +15,7 @@ import (
)
func ListMetas(c *gin.Context) {
var req common.PageReq
var req model.PageReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return

64
server/handles/search.go Normal file
View File

@ -0,0 +1,64 @@
package handles
import (
"path"
"strings"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/search"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
type SearchResp struct {
model.SearchNode
Type int `json:"type"`
}
func Search(c *gin.Context) {
var req model.SearchReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
if err := req.Validate(); err != nil {
common.ErrorResp(c, err, 400)
return
}
nodes, total, err := search.Search(c, req)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
filteredNodes := []model.SearchNode{}
user := c.MustGet("user").(*model.User)
for _, node := range nodes {
if !strings.HasPrefix(node.Parent, user.BasePath) {
continue
}
meta, err := db.GetNearestMeta(node.Parent)
if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
continue
}
if !common.CanAccess(user, meta, path.Join(node.Parent, node.Name), "") {
continue
}
// node.Parent = "/" + strings.Replace(node.Parent, user.BasePath, "", 1)
filteredNodes = append(filteredNodes, node)
}
common.SuccessResp(c, common.PageResp{
Content: utils.MustSliceConvert(filteredNodes, nodeToSearchResp),
Total: total,
})
}
func nodeToSearchResp(node model.SearchNode) SearchResp {
return SearchResp{
SearchNode: node,
Type: utils.GetObjType(node.Name, node.IsDir),
}
}

View File

@ -12,7 +12,7 @@ import (
)
func ListStorages(c *gin.Context) {
var req common.PageReq
var req model.PageReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return

View File

@ -11,7 +11,7 @@ import (
)
func ListUsers(c *gin.Context) {
var req common.PageReq
var req model.PageReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return

View File

@ -3,9 +3,11 @@ package middlewares
import (
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/internal/sign"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/server/common"
@ -44,6 +46,9 @@ func parsePath(path string) string {
}
func needSign(meta *model.Meta, path string) bool {
if setting.GetBool(conf.SignAll) {
return true
}
if meta == nil || meta.Password == "" {
return false
}

View File

@ -22,7 +22,11 @@ func FsUp(c *gin.Context) {
return
}
user := c.MustGet("user").(*model.User)
path = stdpath.Join(user.BasePath, path)
path, err = user.JoinPath(path)
if err != nil {
common.ErrorResp(c, err, 403)
return
}
meta, err := db.GetNearestMeta(stdpath.Dir(path))
if err != nil {
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {

View File

@ -0,0 +1,19 @@
package middlewares
import (
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
)
func SearchIndex(c *gin.Context) {
mode := setting.GetStr(conf.SearchIndex)
if mode == "none" {
common.ErrorResp(c, errs.SearchNotAvailable, 500)
c.Abort()
} else {
c.Next()
}
}

View File

@ -107,10 +107,16 @@ func admin(g *gin.RouterGroup) {
ms := g.Group("/message")
ms.POST("/get", message.HttpInstance.GetHandle)
ms.POST("/send", message.HttpInstance.SendHandle)
index := g.Group("/index")
index.POST("/build", middlewares.SearchIndex, handles.BuildIndex)
index.POST("/stop", middlewares.SearchIndex, handles.StopIndex)
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
}
func _fs(g *gin.RouterGroup) {
g.Any("/list", handles.FsList)
g.Any("/search", middlewares.SearchIndex, handles.Search)
g.Any("/get", handles.FsGet)
g.Any("/other", handles.FsOther)
g.Any("/dirs", handles.FsDirs)
@ -128,6 +134,7 @@ func _fs(g *gin.RouterGroup) {
func Cors(r *gin.Engine) {
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
//config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
config.AllowHeaders = []string{"*"}
r.Use(cors.New(config))
}

View File

@ -0,0 +1,46 @@
package webdav
import (
"net/http"
)
type bufferedResponseWriter struct {
statusCode int
data []byte
header http.Header
}
func (w *bufferedResponseWriter) Header() http.Header {
if w.header == nil {
w.header = make(http.Header)
}
return w.header
}
func (w *bufferedResponseWriter) Write(bytes []byte) (int, error) {
w.data = append(w.data, bytes...)
return len(bytes), nil
}
func (w *bufferedResponseWriter) WriteHeader(statusCode int) {
if w.statusCode == 0 {
w.statusCode = statusCode
}
}
func (w *bufferedResponseWriter) WriteToResponse(rw http.ResponseWriter) (int, error) {
h := rw.Header()
for k, vs := range w.header {
for _, v := range vs {
h.Add(k, v)
}
}
rw.WriteHeader(w.statusCode)
return rw.Write(w.data)
}
func newBufferedResponseWriter() *bufferedResponseWriter {
return &bufferedResponseWriter{
statusCode: 0,
}
}

View File

@ -10,11 +10,9 @@ import (
"path"
"path/filepath"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/fs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
// slashClean is equivalent to but slightly more efficient than
@ -101,7 +99,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
}
for _, fileInfo := range objs {
filename := path.Join(name, utils.MappingName(fileInfo.GetName(), conf.FilenameCharMap))
filename := path.Join(name, fileInfo.GetName())
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err

View File

@ -15,9 +15,7 @@ import (
"path"
"strconv"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
)
// Proppatch describes a property update instruction as defined in RFC 4918.
@ -199,7 +197,7 @@ func props(ctx context.Context, ls LockSystem, fi model.Obj, pnames []xml.Name)
}
// Otherwise, it must either be a live property or we don't know it.
if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
innerXML, err := prop.findFn(ctx, ls, utils.MappingName(fi.GetName(), conf.FilenameCharMap), fi)
innerXML, err := prop.findFn(ctx, ls, fi.GetName(), fi)
if err != nil {
return nil, err
}
@ -375,7 +373,7 @@ func findDisplayName(ctx context.Context, ls LockSystem, name string, fi model.O
// Hide the real name of a possibly prefixed root directory.
return "", nil
}
return escapeXML(utils.MappingName(fi.GetName(), conf.FilenameCharMap)), nil
return escapeXML(fi.GetName()), nil
}
func findContentLength(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {

View File

@ -45,30 +45,33 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err := http.StatusBadRequest, errUnsupportedMethod
brw := newBufferedResponseWriter()
useBufferedWriter := true
if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem
} else {
switch r.Method {
case "OPTIONS":
status, err = h.handleOptions(w, r)
status, err = h.handleOptions(brw, r)
case "GET", "HEAD", "POST":
useBufferedWriter = false
status, err = h.handleGetHeadPost(w, r)
case "DELETE":
status, err = h.handleDelete(w, r)
status, err = h.handleDelete(brw, r)
case "PUT":
status, err = h.handlePut(w, r)
status, err = h.handlePut(brw, r)
case "MKCOL":
status, err = h.handleMkcol(w, r)
status, err = h.handleMkcol(brw, r)
case "COPY", "MOVE":
status, err = h.handleCopyMove(w, r)
status, err = h.handleCopyMove(brw, r)
case "LOCK":
status, err = h.handleLock(w, r)
status, err = h.handleLock(brw, r)
case "UNLOCK":
status, err = h.handleUnlock(w, r)
status, err = h.handleUnlock(brw, r)
case "PROPFIND":
status, err = h.handlePropfind(w, r)
status, err = h.handlePropfind(brw, r)
case "PROPPATCH":
status, err = h.handleProppatch(w, r)
status, err = h.handleProppatch(brw, r)
}
}
@ -77,6 +80,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if status != http.StatusNoContent {
w.Write([]byte(StatusText(status)))
}
} else if useBufferedWriter {
brw.WriteToResponse(w)
}
if h.Logger != nil && err != nil {
h.Logger(r, err)
@ -178,7 +183,10 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
}
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
allow := "OPTIONS, LOCK, PUT, MKCOL"
if fi, err := fs.Get(ctx, reqPath); err == nil {
if fi.IsDir() {
@ -203,7 +211,10 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
// TODO: check locks for read-only access??
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
fi, err := fs.Get(ctx, reqPath)
if err != nil {
return http.StatusNotFound, err
@ -218,7 +229,8 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
w.Header().Set("ETag", etag)
// Let ServeContent determine the Content-Type header.
storage, _ := fs.GetStorage(reqPath)
if storage.GetStorage().WebdavNative() {
downProxyUrl := storage.GetStorage().DownProxyUrl
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") {
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header})
if err != nil {
return http.StatusInternalServerError, err
@ -227,19 +239,19 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
if err != nil {
return http.StatusInternalServerError, err
}
} else if storage.Config().MustProxy() || storage.GetStorage().WebdavProxy() {
u := fmt.Sprintf("%s/p%s?sign=%s",
common.GetApiUrl(r),
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" {
u := fmt.Sprintf("%s%s?sign=%s",
strings.Split(downProxyUrl, "\n")[0],
utils.EncodePath(reqPath, true),
sign.Sign(reqPath))
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
http.Redirect(w, r, u, 302)
http.Redirect(w, r, u, http.StatusFound)
} else {
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{IP: utils.ClientIP(r)})
if err != nil {
return http.StatusInternalServerError, err
}
http.Redirect(w, r, link.URL, 302)
http.Redirect(w, r, link.URL, http.StatusFound)
}
return 0, nil
}
@ -257,7 +269,10 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
// TODO: return MultiStatus where appropriate.
// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
@ -290,7 +305,10 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
// comments in http.checkEtag.
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
obj := model.Object{
Name: path.Base(reqPath),
Size: r.ContentLength,
@ -336,7 +354,10 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
if r.ContentLength > 0 {
return http.StatusUnsupportedMediaType, nil
@ -383,8 +404,14 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
ctx := r.Context()
user := ctx.Value("user").(*model.User)
src = path.Join(user.BasePath, src)
dst = path.Join(user.BasePath, dst)
src, err = user.JoinPath(src)
if err != nil {
return 403, err
}
dst, err = user.JoinPath(dst)
if err != nil {
return 403, err
}
if r.Method == "COPY" {
// Section 7.5.1 says that a COPY only needs to lock the destination,
@ -475,7 +502,10 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
}
}
reqPath, status, err := h.stripPrefix(r.URL.Path)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
if err != nil {
return status, err
}
@ -556,7 +586,10 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
}
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
fi, err := fs.Get(ctx, reqPath)
if err != nil {
if errs.IsObjectNotFound(err) {
@ -632,8 +665,10 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
ctx := r.Context()
user := ctx.Value("user").(*model.User)
reqPath = path.Join(user.BasePath, reqPath)
reqPath, err = user.JoinPath(reqPath)
if err != nil {
return 403, err
}
if _, err := fs.Get(ctx, reqPath); err != nil {
if errs.IsObjectNotFound(err) {
return http.StatusNotFound, err