init
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"browser.local/run/protocol"
|
||||
)
|
||||
|
||||
const maxExecutionContentBytes = 64 * 1024
|
||||
|
||||
type WorkspaceResolver struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func NewWorkspaceResolver(root string) WorkspaceResolver {
|
||||
if strings.TrimSpace(root) == "" {
|
||||
root = filepath.Join(".", ".run-workspace")
|
||||
}
|
||||
return WorkspaceResolver{root: root}
|
||||
}
|
||||
|
||||
func (resolver WorkspaceResolver) Scope(serverInstanceID string, profileKey string) (string, error) {
|
||||
if err := validateWorkspaceComponent(serverInstanceID, "serverInstanceId"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := validateWorkspaceComponent(profileKey, "profileKey"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
root, err := filepath.Abs(resolver.root)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve workspace root: %w", err)
|
||||
}
|
||||
if err := ensureDirectory(root); err != nil {
|
||||
return "", fmt.Errorf("secure workspace root: %w", err)
|
||||
}
|
||||
instances := filepath.Join(root, "instances")
|
||||
if err := ensureDirectory(instances); err != nil {
|
||||
return "", fmt.Errorf("secure workspace instances: %w", err)
|
||||
}
|
||||
serverDir := filepath.Join(instances, serverInstanceID)
|
||||
if err := ensureDirectory(serverDir); err != nil {
|
||||
return "", fmt.Errorf("secure server workspace: %w", err)
|
||||
}
|
||||
scope := filepath.Join(serverDir, profileKey)
|
||||
if err := ensureDirectory(scope); err != nil {
|
||||
return "", fmt.Errorf("secure profile workspace: %w", err)
|
||||
}
|
||||
return scope, nil
|
||||
}
|
||||
|
||||
func (resolver WorkspaceResolver) ExistingTarget(scope string, key string) (string, error) {
|
||||
path, err := resolver.target(scope, key, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
||||
return "", fmt.Errorf("target must be a regular file")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (resolver WorkspaceResolver) ExistingDirectory(scope string, key string) (string, error) {
|
||||
if strings.TrimSpace(scope) == "" || !protocol.ValidLogicalFileKey(key) || filepath.IsAbs(key) || strings.Contains(key, string(rune(92))) {
|
||||
return "", fmt.Errorf("logical directory is unsafe")
|
||||
}
|
||||
cleanScope, err := filepath.Abs(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
root, err := filepath.Abs(resolver.root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rel, err := filepath.Rel(root, cleanScope)
|
||||
if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
|
||||
return "", fmt.Errorf("workspace scope escapes root")
|
||||
}
|
||||
current := cleanScope
|
||||
for _, part := range strings.Split(filepath.ToSlash(key), "/") {
|
||||
if part == "" || part == "." || part == ".." {
|
||||
return "", fmt.Errorf("logical directory contains unsafe component")
|
||||
}
|
||||
current = filepath.Join(current, part)
|
||||
info, statErr := os.Lstat(current)
|
||||
if statErr != nil {
|
||||
return "", statErr
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return "", fmt.Errorf("logical directory is not a real directory")
|
||||
}
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (resolver WorkspaceResolver) WritableTarget(scope string, key string) (string, string, error) {
|
||||
if strings.HasPrefix(key, "actions/") || strings.HasPrefix(key, "state/") || key == "actions" || key == "state" {
|
||||
return "", "", fmt.Errorf("target is reserved")
|
||||
}
|
||||
parts := strings.Split(filepath.ToSlash(key), "/")
|
||||
parent := scope
|
||||
for _, part := range parts[:len(parts)-1] {
|
||||
parent = filepath.Join(parent, part)
|
||||
if err := ensureDirectory(parent); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
path, err := resolver.target(scope, key, true)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return path, filepath.Dir(path), nil
|
||||
}
|
||||
|
||||
func (resolver WorkspaceResolver) target(scope string, key string, allowMissingFinal bool) (string, error) {
|
||||
if strings.TrimSpace(scope) == "" {
|
||||
return "", fmt.Errorf("workspace scope is invalid")
|
||||
}
|
||||
if !protocol.ValidLogicalFileKey(key) || filepath.IsAbs(key) || strings.Contains(key, `\`) {
|
||||
return "", fmt.Errorf("logical key is unsafe")
|
||||
}
|
||||
cleanScope, err := filepath.Abs(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
root, err := filepath.Abs(resolver.root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rel, err := filepath.Rel(root, cleanScope)
|
||||
if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
|
||||
return "", fmt.Errorf("workspace scope escapes root")
|
||||
}
|
||||
parts := strings.Split(filepath.ToSlash(key), "/")
|
||||
current := cleanScope
|
||||
for index, part := range parts {
|
||||
if part == "" || part == "." || part == ".." {
|
||||
return "", fmt.Errorf("logical key contains unsafe component")
|
||||
}
|
||||
current = filepath.Join(current, part)
|
||||
info, statErr := os.Lstat(current)
|
||||
if statErr != nil {
|
||||
if allowMissingFinal && index == len(parts)-1 && os.IsNotExist(statErr) {
|
||||
return current, nil
|
||||
}
|
||||
return "", statErr
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
return "", fmt.Errorf("logical key contains a symlink")
|
||||
}
|
||||
if index < len(parts)-1 && !info.IsDir() {
|
||||
return "", fmt.Errorf("logical key parent is not a directory")
|
||||
}
|
||||
if index == len(parts)-1 && info.Mode()&os.ModeType != 0 {
|
||||
return "", fmt.Errorf("target is a special file")
|
||||
}
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func ensureDirectory(path string) error {
|
||||
if info, err := os.Lstat(path); err == nil {
|
||||
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return fmt.Errorf("path is not a real directory")
|
||||
}
|
||||
return os.Chmod(path, 0o700)
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
parent := filepath.Dir(path)
|
||||
if parent != path {
|
||||
if err := ensureDirectory(parent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.Mkdir(path, 0o700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||
return fmt.Errorf("created path is not a real directory")
|
||||
}
|
||||
return os.Chmod(path, 0o700)
|
||||
}
|
||||
|
||||
func validateWorkspaceComponent(value string, field string) error {
|
||||
if strings.TrimSpace(value) == "" || value != strings.TrimSpace(value) || value == "." || value == ".." || strings.ContainsAny(value, `/\`) || !protocol.ValidLogicalFileKey(value) {
|
||||
return fmt.Errorf("%s is unsafe", field)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user