Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions drivers/url_tree/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package url_tree
import (
"context"
"errors"
"fmt"
"net/url"
stdpath "path"
"strings"
"sync"
"text/template"

"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
Expand All @@ -18,8 +21,9 @@ import (
type Urls struct {
model.Storage
Addition
root *Node
mutex sync.RWMutex
root *Node
mutex sync.RWMutex
downloadParamsTmpl *template.Template
}

func (d *Urls) Config() driver.Config {
Expand All @@ -37,6 +41,14 @@ func (d *Urls) Init(ctx context.Context) error {
}
node.calSize()
d.root = node
d.downloadParamsTmpl = nil
if params := strings.TrimSpace(d.DownloadParams); params != "" {
tmpl, err := template.New("downloadParams").Parse(params)
if err != nil {
return fmt.Errorf("failed to parse download_params: %w", err)
}
d.downloadParamsTmpl = tmpl
}
return nil
}

Expand Down Expand Up @@ -76,13 +88,49 @@ func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
return nil, errs.ObjectNotFound
}
if node.isFile() {
downURL := node.Url
// Append custom download parameters on download, but keep preview links clean.
if args.Type != "preview" && d.downloadParamsTmpl != nil {
var err error
downURL, err = d.buildDownloadURL(node, file.GetPath())
if err != nil {
return nil, err
}
}
return &model.Link{
URL: node.Url,
URL: downURL,
}, nil
}
return nil, errs.NotFile
}

// buildDownloadURL renders the configured download_params template with the
// node's magic variables and appends the result as query parameters to the URL.
func (d *Urls) buildDownloadURL(node *Node, path string) (string, error) {
var sb strings.Builder
err := d.downloadParamsTmpl.Execute(&sb, map[string]interface{}{
"Name": node.Name,
"Path": path,
"Size": node.Size,
"Modified": node.Modified,
})
if err != nil {
return "", fmt.Errorf("failed to render download_params: %w", err)
}
rendered := strings.TrimSpace(sb.String())
rendered = strings.TrimPrefix(rendered, "?")
if rendered == "" {
return node.Url, nil
}
// Parse the rendered string so that magic-variable values (e.g. file names
// with non-ASCII characters) are properly URL-encoded by Encode().
query, err := url.ParseQuery(rendered)
if err != nil {
return "", fmt.Errorf("invalid download_params %q: %w", rendered, err)
}
return utils.InjectQuery(node.Url, query)
}

func (d *Urls) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
if !d.Writable {
return nil, errs.PermissionDenied
Expand Down
7 changes: 4 additions & 3 deletions drivers/url_tree/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ type Addition struct {
// driver.RootPath
// driver.RootID
// define other
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
Writable bool `json:"writable" type:"bool" default:"false"`
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
Writable bool `json:"writable" type:"bool" default:"false"`
DownloadParams string `json:"download_params" type:"text" help:"Extra query parameters appended to the URL on download (not on preview). Magic variables: {{.Name}} {{.Path}} {{.Size}} {{.Modified}}; values are URL-encoded automatically. e.g. attname={{.Name}}"`
}

var config = driver.Config{
Expand Down
37 changes: 37 additions & 0 deletions drivers/url_tree/urls_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package url_tree_test

import (
"context"
"testing"

"github.com/alist-org/alist/v3/drivers/url_tree"
"github.com/alist-org/alist/v3/internal/model"
)

func testTree() (*url_tree.Node, error) {
Expand Down Expand Up @@ -45,3 +47,38 @@ func TestGetNode(t *testing.T) {
t.Errorf("got wrong node: %+v", url3)
}
}

func TestDownloadParams(t *testing.T) {
d := &url_tree.Urls{
Addition: url_tree.Addition{
UrlStructure: "测试文件.mp4:https://cdn.example.com/random123",
DownloadParams: "attname={{.Name}}",
},
}
if err := d.Init(context.Background()); err != nil {
t.Fatalf("failed to init: %+v", err)
}
file, err := d.Get(context.Background(), "/测试文件.mp4")
if err != nil {
t.Fatalf("failed to get file: %+v", err)
}

// preview must keep the original (clean) URL
previewLink, err := d.Link(context.Background(), file, model.LinkArgs{Type: "preview"})
if err != nil {
t.Fatalf("failed to get preview link: %+v", err)
}
if previewLink.URL != "https://cdn.example.com/random123" {
t.Errorf("preview link should be clean, got: %s", previewLink.URL)
}

// download must append the encoded custom parameters
downLink, err := d.Link(context.Background(), file, model.LinkArgs{})
if err != nil {
t.Fatalf("failed to get download link: %+v", err)
}
want := "https://cdn.example.com/random123?attname=%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.mp4"
if downLink.URL != want {
t.Errorf("download link mismatch:\n got: %s\nwant: %s", downLink.URL, want)
}
}
Loading