2022-10-27 10:54:49 +08:00
|
|
|
package alist_v3
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2023-04-27 16:33:01 +08:00
|
|
|
"fmt"
|
2023-08-27 21:14:23 +08:00
|
|
|
"io"
|
2023-04-23 17:48:26 +08:00
|
|
|
"net/http"
|
2025-03-27 23:18:34 +08:00
|
|
|
"net/url"
|
2022-12-07 19:02:28 +08:00
|
|
|
"path"
|
2022-12-10 19:03:09 +08:00
|
|
|
"strings"
|
2022-10-27 10:54:49 +08:00
|
|
|
|
2023-04-27 16:33:01 +08:00
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
|
|
|
"github.com/alist-org/alist/v3/internal/conf"
|
2022-10-27 10:54:49 +08:00
|
|
|
"github.com/alist-org/alist/v3/internal/driver"
|
2025-03-27 23:18:34 +08:00
|
|
|
"github.com/alist-org/alist/v3/internal/errs"
|
2022-10-27 10:54:49 +08:00
|
|
|
"github.com/alist-org/alist/v3/internal/model"
|
2023-04-27 16:33:01 +08:00
|
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
2022-10-27 10:54:49 +08:00
|
|
|
"github.com/alist-org/alist/v3/server/common"
|
2023-04-23 17:48:26 +08:00
|
|
|
"github.com/go-resty/resty/v2"
|
2024-06-16 16:55:49 +08:00
|
|
|
log "github.com/sirupsen/logrus"
|
2022-10-27 10:54:49 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type AListV3 struct {
|
|
|
|
|
model.Storage
|
|
|
|
|
Addition
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Config() driver.Config {
|
|
|
|
|
return config
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) GetAddition() driver.Additional {
|
2022-12-13 18:03:30 +08:00
|
|
|
return &d.Addition
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
2022-12-13 18:03:30 +08:00
|
|
|
func (d *AListV3) Init(ctx context.Context) error {
|
2022-12-10 19:03:09 +08:00
|
|
|
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
|
2023-04-23 17:48:26 +08:00
|
|
|
var resp common.Resp[MeResp]
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/me", http.MethodGet, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetResult(&resp)
|
|
|
|
|
})
|
2023-04-27 16:33:01 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// if the username is not empty and the username is not the same as the current username, then login again
|
2024-06-16 16:55:49 +08:00
|
|
|
if d.Username != resp.Data.Username {
|
2023-04-27 16:33:01 +08:00
|
|
|
err = d.login()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// re-get the user info
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err = d.request("/me", http.MethodGet, func(req *resty.Request) {
|
2023-04-27 16:33:01 +08:00
|
|
|
req.SetResult(&resp)
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
feat: enhance permission control and label management (#9215)
* 标签管理
* pr检查优化
* feat(role): Implement role management functionality
- Add role management routes in `server/router.go` for listing, getting, creating, updating, and deleting roles
- Introduce `initRoles()` in `internal/bootstrap/data/data.go` for initializing roles during bootstrap
- Create `internal/op/role.go` to handle role operations including caching and singleflight
- Implement role handler functions in `server/handles/role.go` for API responses
- Define database operations for roles in `internal/db/role.go`
- Extend `internal/db/db.go` for role model auto-migration
- Design `internal/model/role.go` to represent role structure with ID, name, description, base path, and permissions
- Initialize default roles (`admin` and `guest`) in `internal/bootstrap/data/role.go` during startup
* refactor(user roles): Support multiple roles for users
- Change the `Role` field type from `int` to `[]int` in `drivers/alist_v3/types.go` and `drivers/quqi/types.go`.
- Update the `Role` field in `internal/model/user.go` to use a new `Roles` type with JSON and database support.
- Modify `IsGuest` and `IsAdmin` methods to check for roles using `Contains` method.
- Update `GetUserByRole` method in `internal/db/user.go` to handle multiple roles.
- Add `roles.go` to define a new `Roles` type with JSON marshalling and scanning capabilities.
- Adjust code in `server/handles/user.go` to compare roles with `utils.SliceEqual`.
- Change role initialization for users in `internal/bootstrap/data/dev.go` and `internal/bootstrap/data/user.go`.
- Update `Role` handling in `server/handles/task.go`, `server/handles/ssologin.go`, and `server/handles/ldap_login.go`.
* feat(user/role): Add path limit check for user and role permissions
- Add new permission bit for checking path limits in `user.go`
- Implement `CheckPathLimit` method in `User` struct to validate path access
- Modify `JoinPath` method in `User` to enforce path limit checks
- Update `role.go` to include path limit logic in `Role` struct
- Document new permission bit in `Role` and `User` comments for clarity
* feat(permission): Add role-based permission handling
- Introduce `role_perm.go` for managing user permissions based on roles.
- Implement `HasPermission` and `MergeRolePermissions` functions.
- Update `webdav.go` to utilize role-based permissions instead of direct user checks.
- Modify `fsup.go` to integrate `CanAccessWithRoles` function.
- Refactor `fsread.go` to use `common.HasPermission` for permission validation.
- Adjust `fsmanage.go` for role-based access control checks.
- Enhance `ftp.go` and `sftp.go` to manage FTP access via roles.
- Update `fsbatch.go` to employ `MergeRolePermissions` for batch operations.
- Replace direct user permission checks with role-based permission handling across various modules.
* refactor(user): Replace integer role values with role IDs
- Change `GetAdmin()` and `GetGuest()` functions to retrieve role by name and use role ID.
- Add patch for version `v3.45.2` to convert legacy integer roles to role IDs.
- Update `dev.go` and `user.go` to use role IDs instead of integer values for roles.
- Remove redundant code in `role.go` related to guest role creation.
- Modify `ssologin.go` and `ldap_login.go` to set user roles to nil instead of using integer roles.
- Introduce `convert_roles.go` to handle conversion of legacy roles and ensure role existence in the database.
* feat(role_perm): implement support for multiple base paths for roles
- Modify role permission checks to support multiple base paths
- Update role creation and update functions to handle multiple base paths
- Add migration script to convert old base_path to base_paths
- Define new Paths type for handling multiple paths in the model
- Adjust role model to replace BasePath with BasePaths
- Update existing patches to handle roles with multiple base paths
- Update bootstrap data to reflect the new base_paths field
* feat(role): Restrict modifications to default roles (admin and guest)
- Add validation to prevent changes to "admin" and "guest" roles in `UpdateRole` and `DeleteRole` functions.
- Introduce `ErrChangeDefaultRole` error in `internal/errs/role.go` to standardize error messaging.
- Update role-related API handlers in `server/handles/role.go` to enforce the new restriction.
- Enhance comments in `internal/bootstrap/data/role.go` to clarify the significance of default roles.
- Ensure consistent error responses for unauthorized role modifications across the application.
* 🔄 **refactor(role): Enhance role permission handling**
- Replaced `BasePaths` with `PermissionPaths` in `Role` struct for better permission granularity.
- Introduced JSON serialization for `PermissionPaths` using `RawPermission` field in `Role` struct.
- Implemented `BeforeSave` and `AfterFind` GORM hooks for handling `PermissionPaths` serialization.
- Refactored permission calculation logic in `role_perm.go` to work with `PermissionPaths`.
- Updated role creation logic to initialize `PermissionPaths` for `admin` and `guest` roles.
- Removed deprecated `CheckPathLimit` method from `Role` struct.
* fix(model/user/role): update permission settings for admin and role
- Change `RawPermission` field in `role.go` to hide JSON representation
- Update `Permission` field in `user.go` to `0xFFFF` for full access
- Modify `PermissionScopes` in `role.go` to `0xFFFF` for enhanced permissions
* 🔒 feat(role-permissions): Enhance role-based access control
- Introduce `canReadPathByRole` function in `role_perm.go` to verify path access based on user roles
- Modify `CanAccessWithRoles` to include role-based path read check
- Add `RoleNames` and `Permissions` to `UserResp` struct in `auth.go` for enhanced user role and permission details
- Implement role details aggregation in `auth.go` to populate `RoleNames` and `Permissions`
- Update `User` struct in `user.go` to include `RolesDetail` for more detailed role information
- Enhance middleware in `auth.go` to load and verify detailed role information for users
- Move `guest` user initialization logic in `user.go` to improve code organization and avoid repetition
* 🔒 fix(permissions): Add permission checks for archive operations
- Add `MergeRolePermissions` and `HasPermission` checks to validate user access for reading archives
- Ensure users have `PermReadArchives` before proceeding with `GetNearestMeta` in specific archive paths
- Implement permission checks for decompress operations, requiring `PermDecompress` for source paths
- Return `PermissionDenied` errors with 403 status if user lacks necessary permissions
* 🔒 fix(server): Add permission check for offline download
- Add permission merging logic for user roles
- Check user has permission for offline download addition
- Return error response with "permission denied" if check fails
* ✨ feat(role-permission): Implement path-based role permission checks
- Add `CheckPathLimitWithRoles` function to validate access based on `PermPathLimit` permission.
- Integrate `CheckPathLimitWithRoles` in `offline_download` to enforce path-based access control.
- Apply `CheckPathLimitWithRoles` across file system management operations (e.g., creation, movement, deletion).
- Ensure `CheckPathLimitWithRoles` is invoked for batch operations and archive-related actions.
- Update error handling to return `PermissionDenied` if the path validation fails.
- Import `errs` package in `offline_download` for consistent error responses.
* ✨ feat(role-permission): Implement path-based role permission checks
- Add `CheckPathLimitWithRoles` function to validate access based on `PermPathLimit` permission.
- Integrate `CheckPathLimitWithRoles` in `offline_download` to enforce path-based access control.
- Apply `CheckPathLimitWithRoles` across file system management operations (e.g., creation, movement, deletion).
- Ensure `CheckPathLimitWithRoles` is invoked for batch operations and archive-related actions.
- Update error handling to return `PermissionDenied` if the path validation fails.
- Import `errs` package in `offline_download` for consistent error responses.
* ♻️ refactor(access-control): Update access control logic to use role-based checks
- Remove deprecated logic from `CanAccess` function in `check.go`, replacing it with `CanAccessWithRoles` for improved role-based access control.
- Modify calls in `search.go` to use `CanAccessWithRoles` for more precise handling of permissions.
- Update `fsread.go` to utilize `CanAccessWithRoles`, ensuring accurate access validation based on user roles.
- Simplify import statements in `check.go` by removing unused packages to clean up the codebase.
* ✨ feat(fs): Improve visibility logic for hidden files
- Import `server/common` package to handle permissions more robustly
- Update `whetherHide` function to use `MergeRolePermissions` for user-specific path permissions
- Replace direct user checks with `HasPermission` for `PermSeeHides`
- Enhance logic to ensure `nil` user cases are handled explicitly
* 标签管理
* feat(db/auth/user): Enhance role handling and clean permission paths
- Comment out role modification checks in `server/handles/user.go` to allow flexible role changes.
- Improve permission path handling in `server/handles/auth.go` by normalizing and deduplicating paths.
- Introduce `addedPaths` map in `CurrentUser` to prevent duplicate permissions.
* feat(storage/db): Implement role permissions path prefix update
- Add `UpdateRolePermissionsPathPrefix` function in `role.go` to update role permissions paths.
- Modify `storage.go` to call the new function when the mount path is renamed.
- Introduce path cleaning and prefix matching logic for accurate path updates.
- Ensure roles are updated only if their permission scopes are modified.
- Handle potential errors with informative messages during database operations.
* feat(role-migration): Implement role conversion and introduce NEWGENERAL role
- Add `NEWGENERAL` to the roles enumeration in `user.go`
- Create new file `convert_role.go` for migrating legacy roles to new model
- Implement `ConvertLegacyRoles` function to handle role conversion with permission scopes
- Add `convert_role.go` patch to `all.go` under version `v3.46.0`
* feat(role/auth): Add role retrieval by user ID and update path prefixes
- Add `GetRolesByUserID` function for efficient role retrieval by user ID
- Implement `UpdateUserBasePathPrefix` to update user base paths
- Modify `UpdateRolePermissionsPathPrefix` to return modified role IDs
- Update `auth.go` middleware to use the new role retrieval function
- Refresh role and user caches upon path prefix updates to maintain consistency
---------
Co-authored-by: Leslie-Xy <540049476@qq.com>
2025-07-26 09:51:59 +08:00
|
|
|
if utils.SliceContains(resp.Data.Role, model.GUEST) {
|
2025-03-27 23:18:34 +08:00
|
|
|
u := d.Address + "/api/public/settings"
|
|
|
|
|
res, err := base.RestyClient.R().Get(u)
|
2023-04-27 16:33:01 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
allowMounted := utils.Json.Get(res.Body(), "data", conf.AllowMounted).ToString() == "true"
|
|
|
|
|
if !allowMounted {
|
|
|
|
|
return fmt.Errorf("the site does not allow mounted")
|
|
|
|
|
}
|
2023-04-23 17:48:26 +08:00
|
|
|
}
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Drop(ctx context.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
2022-11-15 10:51:32 +08:00
|
|
|
var resp common.Resp[FsListResp]
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetResult(&resp).SetBody(ListReq{
|
2022-11-28 13:45:25 +08:00
|
|
|
PageReq: model.PageReq{
|
2022-10-27 10:54:49 +08:00
|
|
|
Page: 1,
|
|
|
|
|
PerPage: 0,
|
|
|
|
|
},
|
|
|
|
|
Path: dir.GetPath(),
|
2023-04-24 20:56:46 +08:00
|
|
|
Password: d.MetaPassword,
|
2022-10-27 10:54:49 +08:00
|
|
|
Refresh: false,
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
2022-10-27 10:54:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var files []model.Obj
|
|
|
|
|
for _, f := range resp.Data.Content {
|
|
|
|
|
file := model.ObjThumb{
|
|
|
|
|
Object: model.Object{
|
|
|
|
|
Name: f.Name,
|
|
|
|
|
Modified: f.Modified,
|
2023-08-29 13:31:24 +08:00
|
|
|
Ctime: f.Created,
|
2022-10-27 10:54:49 +08:00
|
|
|
Size: f.Size,
|
|
|
|
|
IsFolder: f.IsDir,
|
2023-08-29 13:31:24 +08:00
|
|
|
HashInfo: utils.FromString(f.HashInfo),
|
2022-10-27 10:54:49 +08:00
|
|
|
},
|
|
|
|
|
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
|
|
|
|
|
}
|
|
|
|
|
files = append(files, &file)
|
|
|
|
|
}
|
|
|
|
|
return files, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
2022-11-15 10:51:32 +08:00
|
|
|
var resp common.Resp[FsGetResp]
|
2024-05-12 17:34:36 +08:00
|
|
|
// if PassUAToUpsteam is true, then pass the user-agent to the upstream
|
|
|
|
|
userAgent := base.UserAgent
|
|
|
|
|
if d.PassUAToUpsteam {
|
|
|
|
|
userAgent = args.Header.Get("user-agent")
|
|
|
|
|
if userAgent == "" {
|
|
|
|
|
userAgent = base.UserAgent
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetResult(&resp).SetBody(FsGetReq{
|
2022-10-27 10:54:49 +08:00
|
|
|
Path: file.GetPath(),
|
2023-04-24 20:56:46 +08:00
|
|
|
Password: d.MetaPassword,
|
2024-05-12 17:34:36 +08:00
|
|
|
}).SetHeader("user-agent", userAgent)
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
2022-10-27 10:54:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &model.Link{
|
|
|
|
|
URL: resp.Data.RawURL,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetBody(MkdirOrLinkReq{
|
2022-12-07 19:02:28 +08:00
|
|
|
Path: path.Join(parentDir.GetPath(), dirName),
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetBody(MoveCopyReq{
|
2023-01-22 18:52:54 +08:00
|
|
|
SrcDir: path.Dir(srcObj.GetPath()),
|
2022-12-07 19:02:28 +08:00
|
|
|
DstDir: dstDir.GetPath(),
|
|
|
|
|
Names: []string{srcObj.GetName()},
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetBody(RenameReq{
|
2022-12-07 19:02:28 +08:00
|
|
|
Path: srcObj.GetPath(),
|
|
|
|
|
Name: newName,
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetBody(MoveCopyReq{
|
2023-01-22 18:52:54 +08:00
|
|
|
SrcDir: path.Dir(srcObj.GetPath()),
|
2022-12-07 19:02:28 +08:00
|
|
|
DstDir: dstDir.GetPath(),
|
|
|
|
|
Names: []string{srcObj.GetName()},
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
2025-03-27 23:18:34 +08:00
|
|
|
_, _, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) {
|
2023-04-23 17:48:26 +08:00
|
|
|
req.SetBody(RemoveReq{
|
2023-01-27 14:51:56 +08:00
|
|
|
Dir: path.Dir(obj.GetPath()),
|
2022-12-07 19:02:28 +08:00
|
|
|
Names: []string{obj.GetName()},
|
2023-04-23 17:48:26 +08:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-01 17:29:55 +08:00
|
|
|
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error {
|
2025-02-16 12:22:11 +08:00
|
|
|
reader := driver.NewLimitedUploadStream(ctx, &driver.ReaderUpdatingProgress{
|
2025-02-01 17:29:55 +08:00
|
|
|
Reader: s,
|
|
|
|
|
UpdateProgress: up,
|
|
|
|
|
})
|
2025-02-16 12:22:11 +08:00
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, d.Address+"/api/fs/put", reader)
|
2024-06-16 16:55:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("Authorization", d.Token)
|
2025-02-01 17:29:55 +08:00
|
|
|
req.Header.Set("File-Path", path.Join(dstDir.GetPath(), s.GetName()))
|
2024-06-16 16:55:49 +08:00
|
|
|
req.Header.Set("Password", d.MetaPassword)
|
2025-02-01 17:29:55 +08:00
|
|
|
if md5 := s.GetHash().GetHash(utils.MD5); len(md5) > 0 {
|
2025-01-27 20:20:09 +08:00
|
|
|
req.Header.Set("X-File-Md5", md5)
|
|
|
|
|
}
|
2025-02-01 17:29:55 +08:00
|
|
|
if sha1 := s.GetHash().GetHash(utils.SHA1); len(sha1) > 0 {
|
2025-01-27 20:20:09 +08:00
|
|
|
req.Header.Set("X-File-Sha1", sha1)
|
|
|
|
|
}
|
2025-02-01 17:29:55 +08:00
|
|
|
if sha256 := s.GetHash().GetHash(utils.SHA256); len(sha256) > 0 {
|
2025-01-27 20:20:09 +08:00
|
|
|
req.Header.Set("X-File-Sha256", sha256)
|
|
|
|
|
}
|
2024-06-16 16:55:49 +08:00
|
|
|
|
2025-02-01 17:29:55 +08:00
|
|
|
req.ContentLength = s.GetSize()
|
2024-06-16 16:55:49 +08:00
|
|
|
// client := base.NewHttpClient()
|
|
|
|
|
// client.Timeout = time.Hour * 6
|
|
|
|
|
res, err := base.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes, err := io.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
log.Debugf("[alist_v3] response body: %s", string(bytes))
|
|
|
|
|
if res.StatusCode >= 400 {
|
|
|
|
|
return fmt.Errorf("request failed, status: %s", res.Status)
|
|
|
|
|
}
|
|
|
|
|
code := utils.Json.Get(bytes, "code").ToInt()
|
|
|
|
|
if code != 200 {
|
|
|
|
|
if code == 401 || code == 403 {
|
|
|
|
|
err = d.login()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("request failed,code: %d, message: %s", code, utils.Json.Get(bytes, "message").ToString())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
2022-10-27 10:54:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-27 23:18:34 +08:00
|
|
|
func (d *AListV3) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
|
|
|
|
if !d.ForwardArchiveReq {
|
|
|
|
|
return nil, errs.NotImplement
|
|
|
|
|
}
|
|
|
|
|
var resp common.Resp[ArchiveMetaResp]
|
|
|
|
|
_, code, err := d.request("/fs/archive/meta", http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetResult(&resp).SetBody(ArchiveMetaReq{
|
|
|
|
|
ArchivePass: args.Password,
|
|
|
|
|
Password: d.MetaPassword,
|
|
|
|
|
Path: obj.GetPath(),
|
|
|
|
|
Refresh: false,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
if code == 202 {
|
|
|
|
|
return nil, errs.WrongArchivePassword
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var tree []model.ObjTree
|
|
|
|
|
if resp.Data.Content != nil {
|
|
|
|
|
tree = make([]model.ObjTree, 0, len(resp.Data.Content))
|
|
|
|
|
for _, content := range resp.Data.Content {
|
|
|
|
|
tree = append(tree, &content)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &model.ArchiveMetaInfo{
|
|
|
|
|
Comment: resp.Data.Comment,
|
|
|
|
|
Encrypted: resp.Data.Encrypted,
|
|
|
|
|
Tree: tree,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
|
|
|
|
if !d.ForwardArchiveReq {
|
|
|
|
|
return nil, errs.NotImplement
|
|
|
|
|
}
|
|
|
|
|
var resp common.Resp[ArchiveListResp]
|
|
|
|
|
_, code, err := d.request("/fs/archive/list", http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetResult(&resp).SetBody(ArchiveListReq{
|
|
|
|
|
ArchiveMetaReq: ArchiveMetaReq{
|
|
|
|
|
ArchivePass: args.Password,
|
|
|
|
|
Password: d.MetaPassword,
|
|
|
|
|
Path: obj.GetPath(),
|
|
|
|
|
Refresh: false,
|
|
|
|
|
},
|
|
|
|
|
PageReq: model.PageReq{
|
|
|
|
|
Page: 1,
|
|
|
|
|
PerPage: 0,
|
|
|
|
|
},
|
|
|
|
|
InnerPath: args.InnerPath,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
if code == 202 {
|
|
|
|
|
return nil, errs.WrongArchivePassword
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var files []model.Obj
|
|
|
|
|
for _, f := range resp.Data.Content {
|
|
|
|
|
file := model.ObjThumb{
|
|
|
|
|
Object: model.Object{
|
|
|
|
|
Name: f.Name,
|
|
|
|
|
Modified: f.Modified,
|
|
|
|
|
Ctime: f.Created,
|
|
|
|
|
Size: f.Size,
|
|
|
|
|
IsFolder: f.IsDir,
|
|
|
|
|
HashInfo: utils.FromString(f.HashInfo),
|
|
|
|
|
},
|
|
|
|
|
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
|
|
|
|
|
}
|
|
|
|
|
files = append(files, &file)
|
|
|
|
|
}
|
|
|
|
|
return files, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
|
|
|
|
if !d.ForwardArchiveReq {
|
|
|
|
|
return nil, errs.NotSupport
|
|
|
|
|
}
|
|
|
|
|
var resp common.Resp[ArchiveMetaResp]
|
|
|
|
|
_, _, err := d.request("/fs/archive/meta", http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetResult(&resp).SetBody(ArchiveMetaReq{
|
|
|
|
|
ArchivePass: args.Password,
|
|
|
|
|
Password: d.MetaPassword,
|
|
|
|
|
Path: obj.GetPath(),
|
|
|
|
|
Refresh: false,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &model.Link{
|
|
|
|
|
URL: fmt.Sprintf("%s?inner=%s&pass=%s&sign=%s",
|
|
|
|
|
resp.Data.RawURL,
|
|
|
|
|
utils.EncodePath(args.InnerPath, true),
|
|
|
|
|
url.QueryEscape(args.Password),
|
|
|
|
|
resp.Data.Sign),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *AListV3) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) error {
|
|
|
|
|
if !d.ForwardArchiveReq {
|
|
|
|
|
return errs.NotImplement
|
|
|
|
|
}
|
|
|
|
|
dir, name := path.Split(srcObj.GetPath())
|
|
|
|
|
_, _, err := d.request("/fs/archive/decompress", http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetBody(DecompressReq{
|
|
|
|
|
ArchivePass: args.Password,
|
|
|
|
|
CacheFull: args.CacheFull,
|
|
|
|
|
DstDir: dstDir.GetPath(),
|
|
|
|
|
InnerPath: args.InnerPath,
|
|
|
|
|
Name: []string{name},
|
|
|
|
|
PutIntoNewDir: args.PutIntoNewDir,
|
|
|
|
|
SrcDir: dir,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 10:54:49 +08:00
|
|
|
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
|
|
|
|
// return nil, errs.NotSupport
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
var _ driver.Driver = (*AListV3)(nil)
|