diff options
author | Kamil Trzciński <ayufan@ayufan.eu> | 2019-03-01 13:47:12 +0300 |
---|---|---|
committer | Kamil Trzciński <ayufan@ayufan.eu> | 2019-03-01 13:47:12 +0300 |
commit | 89e88540533def25a6e4ec6e7c1dc1b5b74d3db8 (patch) | |
tree | f0315a2b0931ad121bd1e79903dab8f2538999b2 /internal/storage | |
parent | da03df956f659db8121991b330af26929a69cd2e (diff) |
Support Zip archive
Diffstat (limited to 'internal/storage')
-rw-r--r-- | internal/storage/storage.go | 2 | ||||
-rw-r--r-- | internal/storage/zip_storage.go | 110 |
2 files changed, 108 insertions, 4 deletions
diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 58e1cd49..5f767479 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -12,7 +12,7 @@ import ( // to interact with the file, to read, stat, and seek type File interface { io.Reader - io.Seeker + //io.Seeker io.Closer } diff --git a/internal/storage/zip_storage.go b/internal/storage/zip_storage.go index d658d8b1..8f964e94 100644 --- a/internal/storage/zip_storage.go +++ b/internal/storage/zip_storage.go @@ -3,27 +3,131 @@ package storage import ( "archive/zip" "errors" + "fmt" + "io/ioutil" "os" + "path/filepath" + "strings" "gitlab.com/gitlab-org/gitlab-pages/internal/client" ) +const zipDeployPath = "public" +const maxSymlinkSize = 4096 +const maxSymlinkDepth = 3 + type zipStorage struct { *client.LookupPath archive *zip.ReadCloser } +func (z *zipStorage) find(path string) *zip.File { + // This is O(n) search, very, very, very slow + for _, file := range z.archive.File { + if file.Name == path { + return file + } + } + + return nil +} + +func (z *zipStorage) readSymlink(file *zip.File) (string, error) { + fi := file.FileInfo() + + if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { + return "", nil + } + + if fi.Size() > maxSymlinkSize { + return "", errors.New("symlink size too long") + } + + rc, err := file.Open() + if err != nil { + return "", err + } + defer rc.Close() + + data, err := ioutil.ReadAll(rc) + if err != nil { + return "", err + } + + // resolve symlink location relative to current file + targetPath, err := filepath.Rel(filepath.Dir(file.Name), string(data)) + if err != nil { + return "", err + } + + return targetPath, nil +} + +func (z *zipStorage) resolveUnchecked(path string) (*zip.File, error) { + // limit the resolve depth of symlink + for depth := 0; depth < maxSymlinkDepth; depth++ { + file := z.find(path) + if file == nil { + break + } + + targetPath, err := z.readSymlink(file) + if err != nil { + return nil, err + } + + // not a symlink + if targetPath == "" { + return file, nil + } + + path = targetPath + } + + return nil, fmt.Errorf("%q: not found", path) +} + +func (z *zipStorage) resolvePublic(path string) (string, *zip.File, error) { + path = filepath.Join(zipDeployPath, path) + file, err := z.resolveUnchecked(path) + if err != nil { + return "", nil, err + } + + if !strings.HasPrefix(file.Name, zipDeployPath+"/") { + return "", nil, fmt.Errorf("%q: is not in %s/", file.Name, zipDeployPath) + } + + return file.Name[len(zipDeployPath)+1:], file, nil +} + func (z *zipStorage) Resolve(path string) (string, error) { - return "", errors.New("not supported") + targetPath, _, err := z.resolvePublic(path) + return targetPath, err } func (z *zipStorage) Stat(path string) (os.FileInfo, error) { - return nil, errors.New("not supported") + _, file, err := z.resolvePublic(path) + if err != nil { + return nil, err + } + + return file.FileInfo(), nil } func (z *zipStorage) Open(path string) (File, os.FileInfo, error) { - return nil, nil, errors.New("not supported") + _, file, err := z.resolvePublic(path) + if err != nil { + return nil, nil, err + } + + rc, err := file.Open() + if err != nil { + return nil, nil, err + } + + return rc, file.FileInfo(), nil } func (z *zipStorage) Close() { |