os.OpenRoot în Go

Deschide un director ca rădăcină din care nu se poate ieși; OpenInRoot deschide un fișier strict în interiorul unui director.

Sintaxă
func OpenRoot(name string) (*Root, error)
func OpenInRoot(dir, name string) (*File, error)

Ce face os.OpenRoot

Când numele fișierelor vin din afară (un URL, o arhivă, un formular), un nume ca ../../etc/passwd sau o legătură simbolică pot face programul să citească sau să scrie în afara directorului dorit (atacul „path traversal”). os.OpenRoot deschide un director ca *os.Root; metodele lui (Open, Create, OpenFile, Mkdir, Remove, Stat, ReadFile, WriteFile și altele) refuză orice cale care iese din director, inclusiv prin legături simbolice.

os.OpenInRoot(dir, nume) este scurtătura pentru un singur fișier: echivalentă cu OpenRoot(dir) urmat de root.Open(nume). Spre deosebire de filepath.IsLocal (verificare doar pe text) și de os.DirFS, Root verifică și legăturile simbolice de pe disc.

Un *os.Root trebuie închis cu Close. root.FS() îl transformă într-un fs.FS.

Parametri

Parametru Descriere
name La OpenRoot: directorul care devine rădăcină. La OpenInRoot: fișierul, relativ la dir.
dir La OpenInRoot: directorul din care nu se poate ieși.

Ce returnează

OpenRoot: un *os.Root și nil, sau o eroare. OpenInRoot: un *os.File sau o eroare.

Exemple

Exemplul 1: Fișiere încărcate, fără ieșire din director

package main

import (
    "fmt"
    "os"
)

func main() {
    os.Mkdir("uploads", 0o755)
    root, err := os.OpenRoot("uploads")
    if err != nil {
        fmt.Println("eroare:", err)
        return
    }
    defer root.Close()
    root.Mkdir("poze", 0o755) // și directoarele se creează prin root

    for _, nume := range []string{"cv-ana.pdf", "poze/vara.jpg", "../secret.txt", "/etc/passwd"} {
        err := root.WriteFile(nume, []byte("..."), 0o644)
        fmt.Printf("%-16s %v\n", nume, err)
    }
}

Rezultat

cv-ana.pdf       <nil>
poze/vara.jpg    <nil>
../secret.txt    openat ../secret.txt: path escapes from parent
/etc/passwd      openat /etc/passwd: path escapes from parent

Exemplul 2: Legătura simbolică spre exterior este refuzată

filepath.IsLocal acceptă numele scurtatura, dar legătura duce în afara directorului. OpenInRoot o refuză.

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    os.Mkdir("public", 0o755)
    os.WriteFile("parole.txt", []byte("secret"), 0o600)
    os.WriteFile("public/index.html", []byte("<h1>Salut</h1>"), 0o644)
    os.Symlink("../parole.txt", "public/scurtatura")

    for _, nume := range []string{"index.html", "scurtatura"} {
        fmt.Println(nume, "IsLocal:", filepath.IsLocal(nume))
        f, err := os.OpenInRoot("public", nume)
        if err != nil {
            fmt.Println("  OpenInRoot:", err)
            continue
        }
        fmt.Println("  OpenInRoot: deschis")
        f.Close()
    }
}

Rezultat

index.html IsLocal: true
  OpenInRoot: deschis
scurtatura IsLocal: true
  OpenInRoot: openat scurtatura: path escapes from parent

Greșeli frecvente

  • Te bazezi doar pe filepath.Clean sau filepath.Join ca să ții utilizatorii în director. Ele nu știu de legăturile simbolice de pe disc; folosește os.Root.
  • Uiți root.Close(). Directorul rămâne deschis cât timp rulează programul.

Comenzi înrudite

  • filepath.IsLocal: Spune dacă o cale rămâne în interiorul directorului curent: nu e absolută, nu e goală și nu urcă cu „..”.
  • os.DirFS: Transformă un director de pe disc într-un fs.FS, sistemul de fișiere abstract din Go.
  • os.Open: Deschide un fișier existent doar pentru citire și returnează un *os.File.
  • filepath.Join: Unește mai multe bucăți într-o cale, cu separatorul corect al sistemului, și curăță rezultatul.