项目文件夹

文件
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

101 行
3.2 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package apps
import (
"io/fs"
"path/filepath"
"strings"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/extension/fileio"
)
type htmlPublishCandidate struct {
RelPath string
AbsPath string
Size int64
}
// isUnsafeRelPath reports whether a forward-slash relative path contains
// anything that should never be written into a tar header or treated as
// inside-root: leading slash (absolute), .. as a path component (start /
// middle / end / whole), or an embedded null byte. Component-aware so it
// does not false-positive on legitimate filenames that contain ".." as a
// substring (e.g. "archive.tar..bak").
func isUnsafeRelPath(rel string) bool {
return strings.HasPrefix(rel, "/") ||
rel == ".." ||
strings.HasPrefix(rel, "../") ||
strings.Contains(rel, "/../") ||
strings.HasSuffix(rel, "/..") ||
strings.ContainsRune(rel, 0)
}
// walkHTMLPublishCandidates walks rootPath and returns each regular file as a
// candidate. Stat goes through fileio so SafeInputPath validation runs on the
// root; the directory walk itself uses filepath.WalkDir because runtime.FileIO
// has no WalkDir equivalent today.
func walkHTMLPublishCandidates(fio fileio.FileIO, rootPath string) ([]htmlPublishCandidate, error) {
stat, err := fio.Stat(rootPath)
if err != nil {
return nil, appsInputPathError(err)
}
if !stat.IsDir() {
return []htmlPublishCandidate{{
RelPath: filepath.Base(rootPath),
AbsPath: rootPath,
Size: stat.Size(),
}}, nil
}
var out []htmlPublishCandidate
//nolint:forbidigo // fileio has no WalkDir; rootPath is already validated above via fio.Stat -> SafeInputPath.
err = filepath.WalkDir(rootPath, func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return appsInputPathEntryError(path, walkErr)
}
// Skip a stray git repo: a directory named .git skips the whole subtree,
// and a .git file (the gitdir pointer used by submodules/worktrees) is
// skipped too.
if d.Name() == ".git" {
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
if d.IsDir() {
return nil
}
info, err := d.Info()
if err != nil {
return appsInputPathEntryError(path, err)
}
// 只接受 regular file —— symlink / device / pipe / socket 都跳过。
// symlink 不跟随是设计决策(避免 loop + out-of-root 引用),且 fio.Open 也会拒非 regular。
if !info.Mode().IsRegular() {
return nil
}
rel, err := filepath.Rel(rootPath, path)
if err != nil {
return appsFileIOError(err, "resolve relative path for %s: %v", path, err)
}
relSlash := filepath.ToSlash(rel)
// Defense in depth: WalkDir + Rel inside rootPath should never yield a
// path with .. components, but a future logic change or unusual
// filesystem layout shouldn't be able to inject one into RelPath.
// Mirrors the same guard at tar entry write time.
if isUnsafeRelPath(relSlash) {
return errs.NewInternalError(errs.SubtypeUnknown, "walker produced unsafe relative path %q for %s", relSlash, path)
}
out = append(out, htmlPublishCandidate{
RelPath: relSlash,
AbsPath: path,
Size: info.Size(),
})
return nil
})
return out, err
}