gitea/workflows: upgrade actions (#173)
All checks were successful
checks / check and test (push) Successful in 14s

Attempt at upgrading actions/checkout, actions/setup-go.

Also upgrade the Go version to the moving target `stable`.

Co-authored-by: Olivier Mengué <dolmen@cpan.org>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: #173
Co-authored-by: dolmen <dolmen@noreply.gitea.com>
Co-committed-by: dolmen <dolmen@noreply.gitea.com>
This commit was merged in pull request #173.
This commit is contained in:
2025-08-24 04:42:09 +00:00
committed by Lunny Xiao
parent 58ae51ec01
commit eb62260d12
11 changed files with 216 additions and 149 deletions

View File

@@ -3,56 +3,34 @@ on:
- push
- pull_request
env:
GOPROXY: https://goproxy.io,direct
GOPATH: /go_path
GOCACHE: /go_cache
jobs:
lint:
name: check and test
runs-on: ubuntu-latest
steps:
# - name: cache go path
# id: cache-go-path
# uses: https://github.com/actions/cache@v3
# with:
# path: /go_path
# key: go_path-${{ github.repository }}-${{ github.ref_name }}
# restore-keys: |
# go_path-${{ github.repository }}-
# go_path-
# - name: cache go cache
# id: cache-go-cache
# uses: https://github.com/actions/cache@v3
# with:
# path: /go_cache
# key: go_cache-${{ github.repository }}-${{ github.ref_name }}
# restore-keys: |
# go_cache-${{ github.repository }}-
# go_cache-
- uses: actions/setup-go@v3
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.20
- uses: https://github.com/actions/checkout@v3
- name: lint
run: go install golang.org/x/lint/golint@latest && golint ./...
- name: vet
run: go vet ./...
go-version-file: 'go.mod'
- name: lint and build
run: |
go vet ./...
make lint
make fmt-check
make misspell-check
go build
- name: test
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
env:
MINIO_SERVER_ENDPOINT: minio:9000
MINIO_SERVER_ACCESS_KEY_ID: ${{ secrets.aws_access_key_id }}
MINIO_SERVER_SECRET_KEY: ${{ secrets.aws_secret_access_key }}
MINIO_SERVER_ACCESS_KEY_ID: 123456
MINIO_SERVER_SECRET_KEY: 12345678
MINIO_SERVER_BUCKET: test
services:
minio:
image: minio/minio:RELEASE.2022-07-24T01-54-52Z
image: bitnami/minio:2023.8.31
env:
MINIO_ACCESS_KEY: ${{ secrets.aws_access_key_id }}
MINIO_SECRET_KEY: ${{ secrets.aws_secret_access_key }}
cmd:
- 'minio'
- 'server'
- '/data'
MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678
ports:
- "9000:9000"

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ coverage.txt
exampleftpd/exampleftpd
.vscode
*~
ftp_server

24
.revive.toml Normal file
View File

@@ -0,0 +1,24 @@
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 1
warningCode = 1
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]

64
Makefile Normal file
View File

@@ -0,0 +1,64 @@
DIST := dist
GO ?= go
SHASUM ?= shasum -a 256
export PATH := $($(GO) env GOPATH)/bin:$(PATH)
GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
SERVER_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
SERVER_VERSION_TAG ?= $(shell sed 's/+/_/' <<< $(SERVER_VERSION))
TAGS ?=
LDFLAGS := -X "gitea.com/goftp/server.version=$(SERVER_VERSION)" -s -w
# override to allow passing additional goflags via make CLI
override GOFLAGS := $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)'
PACKAGES ?= $(shell $(GO) list ./...)
SOURCES ?= $(shell find . -name "*.go" -type f)
.PHONY: fmt
fmt:
go fmt ./...
.PHONY: lint
lint: install-lint-tools
$(GO) run github.com/mgechev/revive@v1.3.2 -config .revive.toml ./... || exit 1
.PHONY: misspell-check
misspell-check: install-lint-tools
$(GO) run github.com/client9/misspell/cmd/misspell@latest -error -i unknwon,destory $(GOFILES)
.PHONY: misspell
misspell: install-lint-tools
$(GO) run github.com/client9/misspell/cmd/misspell@latest -w -i unknwon $(GOFILES)
.PHONY: fmt-check
fmt-check:
# get all go files and run go fmt on them
@diff=$$(go fmt ./...); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
echo "$${diff}"; \
exit 1; \
fi;
.PHONY: test
test:
$(GO) test $(PACKAGES)
.PHONY: unit-test-coverage
unit-test-coverage:
$(GO) test -cover -coverprofile coverage.out $(PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1
.PHONY: tidy
tidy:
$(GO) mod tidy
install-lint-tools:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/mgechev/revive@v1.3.2; \
fi
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
fi

148
cmd.go
View File

@@ -24,58 +24,56 @@ type Command interface {
Execute(*Session, string)
}
var (
defaultCommands = map[string]Command{
"ADAT": commandAdat{},
"ALLO": commandAllo{},
"APPE": commandAppe{},
"AUTH": commandAuth{},
"CDUP": commandCdup{},
"CWD": commandCwd{},
"CCC": commandCcc{},
"CONF": commandConf{},
"CLNT": commandCLNT{},
"DELE": commandDele{},
"ENC": commandEnc{},
"EPRT": commandEprt{},
"EPSV": commandEpsv{},
"FEAT": commandFeat{},
"LIST": commandList{},
"LPRT": commandLprt{},
"NLST": commandNlst{},
"MDTM": commandMdtm{},
"MIC": commandMic{},
"MLSD": commandMLSD{},
"MKD": commandMkd{},
"MODE": commandMode{},
"NOOP": commandNoop{},
"OPTS": commandOpts{},
"PASS": commandPass{},
"PASV": commandPasv{},
"PBSZ": commandPbsz{},
"PORT": commandPort{},
"PROT": commandProt{},
"PWD": commandPwd{},
"QUIT": commandQuit{},
"RETR": commandRetr{},
"REST": commandRest{},
"RNFR": commandRnfr{},
"RNTO": commandRnto{},
"RMD": commandRmd{},
"SIZE": commandSize{},
"STAT": commandStat{},
"STOR": commandStor{},
"STRU": commandStru{},
"SYST": commandSyst{},
"TYPE": commandType{},
"USER": commandUser{},
"XCUP": commandCdup{},
"XCWD": commandCwd{},
"XMKD": commandMkd{},
"XPWD": commandPwd{},
"XRMD": commandXRmd{},
}
)
var defaultCommands = map[string]Command{
"ADAT": commandAdat{},
"ALLO": commandAllo{},
"APPE": commandAppe{},
"AUTH": commandAuth{},
"CDUP": commandCdup{},
"CWD": commandCwd{},
"CCC": commandCcc{},
"CONF": commandConf{},
"CLNT": commandCLNT{},
"DELE": commandDele{},
"ENC": commandEnc{},
"EPRT": commandEprt{},
"EPSV": commandEpsv{},
"FEAT": commandFeat{},
"LIST": commandList{},
"LPRT": commandLprt{},
"NLST": commandNlst{},
"MDTM": commandMdtm{},
"MIC": commandMic{},
"MLSD": commandMLSD{},
"MKD": commandMkd{},
"MODE": commandMode{},
"NOOP": commandNoop{},
"OPTS": commandOpts{},
"PASS": commandPass{},
"PASV": commandPasv{},
"PBSZ": commandPbsz{},
"PORT": commandPort{},
"PROT": commandProt{},
"PWD": commandPwd{},
"QUIT": commandQuit{},
"RETR": commandRetr{},
"REST": commandRest{},
"RNFR": commandRnfr{},
"RNTO": commandRnto{},
"RMD": commandRmd{},
"SIZE": commandSize{},
"STAT": commandStat{},
"STOR": commandStor{},
"STRU": commandStru{},
"SYST": commandSyst{},
"TYPE": commandType{},
"USER": commandUser{},
"XCUP": commandCdup{},
"XCWD": commandCwd{},
"XMKD": commandMkd{},
"XPWD": commandPwd{},
"XRMD": commandXRmd{},
}
// DefaultCommands returns the default commands
func DefaultCommands() map[string]Command {
@@ -131,7 +129,7 @@ func (cmd commandAppe) Execute(sess *Session, param string) {
sess.lastFilePos = -1
}()
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "APPE",
Param: param,
@@ -257,7 +255,7 @@ func (cmd commandCwd) RequireAuth() bool {
func (cmd commandCwd) Execute(sess *Session, param string) {
path := sess.buildPath(param)
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "CWD",
Param: param,
@@ -303,7 +301,7 @@ func (cmd commandDele) RequireAuth() bool {
func (cmd commandDele) Execute(sess *Session, param string) {
path := sess.buildPath(param)
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "DELE",
Param: param,
@@ -510,7 +508,7 @@ func convertFileInfo(sess *Session, f os.FileInfo, p string) (FileInfo, error) {
}
func list(sess *Session, cmd, p, param string) ([]FileInfo, error) {
var ctx = &Context{
ctx := &Context{
Sess: sess,
Cmd: cmd,
Param: param,
@@ -574,7 +572,7 @@ func parseListParam(param string) (path string) {
}
i = strings.LastIndex(param, " "+field) + len(field) + 1
}
path = strings.TrimLeft(param[i:], " ") //Get all the path even with space inside
path = strings.TrimLeft(param[i:], " ") // Get all the path even with space inside
}
return path
}
@@ -596,7 +594,7 @@ func (cmd commandNlst) RequireAuth() bool {
}
func (cmd commandNlst) Execute(sess *Session, param string) {
var ctx = &Context{
ctx := &Context{
Sess: sess,
Cmd: "NLST",
Param: param,
@@ -647,7 +645,7 @@ func (cmd commandNlst) Execute(sess *Session, param string) {
}
// commandMdtm responds to the MDTM FTP command. It allows the client to
// retreive the last modified time of a file.
// retrieve the last modified time of a file.
type commandMdtm struct{}
func (cmd commandMdtm) IsExtend() bool {
@@ -695,7 +693,7 @@ func (cmd commandMkd) RequireAuth() bool {
func (cmd commandMkd) Execute(sess *Session, param string) {
path := sess.buildPath(param)
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "MKD",
Param: param,
@@ -783,7 +781,7 @@ func (cmd commandPass) Execute(sess *Session, param string) {
if driverAuth, found := sess.server.Driver.(Auth); found {
auth = driverAuth
}
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "PASS",
Param: param,
@@ -946,14 +944,14 @@ func (cmd commandRetr) Execute(sess *Session, param string) {
defer func() {
sess.lastFilePos = -1
}()
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "RETR",
Param: param,
Data: make(map[string]interface{}),
}
sess.server.notifiers.BeforeDownloadFile(&ctx, path)
var readPos = sess.lastFilePos
readPos := sess.lastFilePos
if readPos < 0 {
readPos = 0
}
@@ -1106,7 +1104,7 @@ func (cmd commandXRmd) Execute(sess *Session, param string) {
func executeRmd(cmd string, sess *Session, param string) {
p := sess.buildPath(param)
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: cmd,
Param: param,
@@ -1117,7 +1115,7 @@ func executeRmd(cmd string, sess *Session, param string) {
return
}
var needChangeCurDir = strings.HasPrefix(param, sess.curDir)
needChangeCurDir := strings.HasPrefix(param, sess.curDir)
sess.server.notifiers.BeforeDeleteDir(&ctx, p)
err := sess.server.Driver.DeleteDir(&ctx, p)
@@ -1247,7 +1245,7 @@ func (cmd commandMLSD) RequireAuth() bool {
func toMLSDFormat(files []FileInfo) []byte {
var buf bytes.Buffer
for _, file := range files {
var fileType = "file"
fileType := "file"
if file.IsDir() {
fileType = "dir"
}
@@ -1409,7 +1407,7 @@ func (cmd commandStat) Execute(sess *Session, param string) {
return
}
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "STAT",
Param: param,
@@ -1479,7 +1477,7 @@ func (cmd commandStor) Execute(sess *Session, param string) {
sess.lastFilePos = -1
}()
var ctx = Context{
ctx := Context{
Sess: sess,
Cmd: "STOR",
Param: param,
@@ -1548,14 +1546,14 @@ func (cmd commandSyst) Execute(sess *Session, param string) {
// commandType responds to the TYPE FTP command.
//
// like the MODE and STRU commands, TYPE dates back to a time when the FTP
// protocol was more aware of the content of the files it was transferring, and
// would sometimes be expected to translate things like EOL markers on the fly.
// like the MODE and STRU commands, TYPE dates back to a time when the FTP
// protocol was more aware of the content of the files it was transferring, and
// would sometimes be expected to translate things like EOL markers on the fly.
//
// Valid options were A(SCII), I(mage), E(BCDIC) or LN (for local type). Since
// we plan to just accept bytes from the client unchanged, I think Image mode is
// adequate. The RFC requires we accept ASCII mode however, so accept it, but
// ignore it.
// Valid options were A(SCII), I(mage), E(BCDIC) or LN (for local type). Since
// we plan to just accept bytes from the client unchanged, I think Image mode is
// adequate. The RFC requires we accept ASCII mode however, so accept it, but
// ignore it.
type commandType struct{}
func (cmd commandType) IsExtend() bool {

View File

@@ -40,12 +40,12 @@ type DataSocket interface {
}
type activeSocket struct {
conn *net.TCPConn
conn *net.TCPConn
reader io.Reader
writer io.Writer
sess *Session
host string
port int
sess *Session
host string
port int
}
func newActiveSocket(sess *Session, remote string, port int) (DataSocket, error) {
@@ -105,8 +105,8 @@ func (socket *activeSocket) Close() error {
type passiveSocket struct {
sess *Session
conn net.Conn
reader io.Reader
writer io.Writer
reader io.Reader
writer io.Writer
port int
host string
ingress chan []byte

10
go.mod
View File

@@ -1,13 +1,21 @@
module goftp.io/server/v2
go 1.12
go 1.20
require (
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf
github.com/minio/minio-go/v6 v6.0.46
github.com/stretchr/testify v1.3.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/minio/sha256-simd v0.1.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
)

5
go.sum
View File

@@ -1,4 +1,3 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -29,23 +28,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View File

@@ -34,7 +34,7 @@ func (formatter listFormatter) Detailed() []byte {
fmt.Fprint(&buf, lpad(strconv.FormatInt(file.Size(), 10), 12))
if file.ModTime().Before(time.Now().AddDate(-1, 0, 0)) {
fmt.Fprint(&buf, file.ModTime().Format(" Jan _2 2006 "))
} else{
} else {
fmt.Fprint(&buf, file.ModTime().Format(" Jan _2 15:04 "))
}
fmt.Fprintf(&buf, "%s\r\n", file.Name())

View File

@@ -161,16 +161,15 @@ func optsWithDefaults(opts *Options) *Options {
// via an instance of Options. Calling this function in your code will
// probably look something like this:
//
// driver := &MyDriver{}
// opts := &server.Options{
// Driver: driver,
// Auth: auth,
// Port: 2000,
// Perm: perm,
// Hostname: "127.0.0.1",
// }
// server, err := server.NewServer(opts)
//
// driver := &MyDriver{}
// opts := &server.Options{
// Driver: driver,
// Auth: auth,
// Port: 2000,
// Perm: perm,
// Hostname: "127.0.0.1",
// }
// server, err := server.NewServer(opts)
func NewServer(opts *Options) (*Server, error) {
opts = optsWithDefaults(opts)
if opts.Perm == nil {
@@ -250,7 +249,6 @@ func simpleTLSConfig(certFile, keyFile string) (*tls.Config, error) {
// If the server fails to start for any reason, an error will be returned. Common
// errors are trying to bind to a privileged port or something else is already
// listening on the same port.
//
func (server *Server) ListenAndServe() error {
var listener net.Listener
var err error
@@ -280,7 +278,6 @@ func (server *Server) ListenAndServe() error {
// Serve accepts connections on a given net.Listener and handles each
// request in a new goroutine.
//
func (server *Server) Serve(l net.Listener) error {
server.listener = l
server.ctx, server.cancel = context.WithCancel(context.Background())

View File

@@ -228,6 +228,7 @@ func (sess *Session) parseLine(line string) (string, string) {
return params[0], params[1]
}
// WriteMessage sends a standard FTP response back to the client.
func (sess *Session) WriteMessage(code int, message string) {
sess.writeMessage(code, message)
}
@@ -248,6 +249,7 @@ func (sess *Session) writeMessageMultiline(code int, message string) {
sess.controlWriter.Flush()
}
// BuildPath generates a safe absolute path for the given filename.
func (sess *Session) BuildPath(filename string) string {
return sess.buildPath(filename)
}
@@ -255,16 +257,16 @@ func (sess *Session) BuildPath(filename string) string {
// buildPath takes a client supplied path or filename and generates a safe
// absolute path within their account sandbox.
//
// buildpath("/")
// => "/"
// buildpath("one.txt")
// => "/one.txt"
// buildpath("/files/two.txt")
// => "/files/two.txt"
// buildpath("files/two.txt")
// => "/files/two.txt"
// buildpath("/../../../../etc/passwd")
// => "/etc/passwd"
// buildpath("/")
// => "/"
// buildpath("one.txt")
// => "/one.txt"
// buildpath("/files/two.txt")
// => "/files/two.txt"
// buildpath("files/two.txt")
// => "/files/two.txt"
// buildpath("/../../../../etc/passwd")
// => "/etc/passwd"
//
// The driver implementation is responsible for deciding how to treat this path.
// Obviously they MUST NOT just read the path off disk. The probably want to