bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
// appsService 是 CLI 命令的 service 前缀(lark-cli apps ...)。
|
|
const appsService = "apps"
|
|
|
|
// apiBasePath is the registered OAPI prefix for the apps domain.
|
|
const apiBasePath = "/open-apis/spark/v1"
|
|
|
|
// appIDListHint is the shared recovery hint for commands whose most likely
|
|
// failure cause is a wrong/inaccessible --app-id. It points at +list to find
|
|
// the correct app id. The app_/cli_ format rule is taught in
|
|
// lark-apps SKILL.md ("app_id 获取"); the hint stays lean and does not repeat it.
|
|
const appIDListHint = "verify --app-id is correct and you have access to the app; list your apps with `lark-cli apps +list`"
|
|
|
|
// withAppsHint attaches an actionable next-step hint to a typed failure,
|
|
// preserving its original classification (subtype/code/log_id). A hint already
|
|
// present on the error is kept (the upstream wording wins); only an empty hint
|
|
// is filled in. Mirrors drive.appendDriveExportRecoveryHint. err==nil and
|
|
// untyped errors pass through unchanged.
|
|
func withAppsHint(err error, hint string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
// p points at the embedded Problem, so the mutation is reflected in err.
|
|
if p, ok := errs.ProblemOf(err); ok {
|
|
if strings.TrimSpace(p.Hint) == "" {
|
|
p.Hint = hint
|
|
}
|
|
return err
|
|
}
|
|
return err
|
|
}
|
|
|
|
// rejectOutputTraversal is a defense-in-depth pre-check on a user-supplied
|
|
// --output path. The authoritative guard is the local FileIO layer
|
|
// (validate.SafeOutputPath sandboxes every write to the cwd, resolving .. and
|
|
// symlinks), so traversal is already blocked at write time; this gives an
|
|
// earlier, clearer validation error and pins the contract in the command layer.
|
|
// Empty (use server-derived default) passes through. Absolute paths and any
|
|
// ".." path component are rejected.
|
|
func rejectOutputTraversal(output string) error {
|
|
o := strings.TrimSpace(output)
|
|
if o == "" {
|
|
return nil
|
|
}
|
|
if filepath.IsAbs(o) {
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument,
|
|
"--output must be a relative path within the current directory, got %q", o).WithParam("--output")
|
|
}
|
|
for _, seg := range strings.Split(filepath.Clean(o), string(filepath.Separator)) {
|
|
if seg == ".." {
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument,
|
|
"--output must not contain .. path traversal, got %q", o).WithParam("--output")
|
|
}
|
|
}
|
|
return nil
|
|
}
|