Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaime Martinez <jmartinez@gitlab.com>2020-08-05 10:04:07 +0300
committerJaime Martinez <jmartinez@gitlab.com>2020-08-05 10:04:07 +0300
commit96a1293252ccf454260d57ce8ffbb956fb606c53 (patch)
tree69d96ff1a2ec373b6e4939475d64531b370ac582
parent6e04f521a7a66cd9a171e968293759705fd40892 (diff)
PoC with zip support from file resolver421-extract-to-use-in-zip
-rw-r--r--internal/serving/fileresolver/fileresolver.go35
-rw-r--r--internal/serving/fileresolver/fileresolver_test.go26
-rw-r--r--internal/serving/fileresolver/fileresolver_zip_test.go187
-rw-r--r--shared/pages/group.no.projects/public.zipbin0 -> 164 bytes
-rw-r--r--shared/pages/group/group.test.io/public.zipbin0 -> 2650 bytes
-rw-r--r--shared/pages/group/symlink/public.zipbin0 -> 978 bytes
6 files changed, 213 insertions, 35 deletions
diff --git a/internal/serving/fileresolver/fileresolver.go b/internal/serving/fileresolver/fileresolver.go
index f306e4ba..85cc9693 100644
--- a/internal/serving/fileresolver/fileresolver.go
+++ b/internal/serving/fileresolver/fileresolver.go
@@ -2,6 +2,7 @@ package fileresolver
import (
"errors"
+ "io"
"path/filepath"
"strings"
)
@@ -16,24 +17,28 @@ var (
type evalSymlinkFunc func(string) (string, error)
-// ResolveFilePath takes a lookupPath and any subPath to determine the file location.
+type openFileFunc func(string) (io.ReadCloser, error)
+
+func OpenFile(lookupPath, subPath string, evalSymLink evalSymlinkFunc, openFile openFileFunc) (io.ReadCloser, error) {
+ filePath, err := resolveFilePath(lookupPath, subPath, evalSymLink)
+ if err != nil {
+ return nil, err
+ }
+
+ return openFile(filePath)
+}
+
+// resolveFilePath takes a archivePath and any subPath to determine the file location.
// Requires the original requestURLPath to try to resolve index.html
// Requires an evalSymlinkFunc to determine if the file exists or not. Useful for resolving files in disk
-func ResolveFilePath(lookupPath, subPath, requestURLPath string, evalSymLink evalSymlinkFunc) (string, error) {
+func resolveFilePath(lookupPath, subPath string, evalSymLink evalSymlinkFunc) (string, error) {
fullPath, err := resolvePath(evalSymLink, lookupPath, subPath)
if err != nil {
if err == errIsDirectory {
// try to resolve index.html from the path we're currently in
- if endsWithSlash(requestURLPath) {
- fullPath, err = resolvePath(evalSymLink, lookupPath, subPath, "index.html")
- if err != nil {
- return "", err
- }
-
- return fullPath, nil
- }
+ return resolvePath(evalSymLink, lookupPath, subPath, "index.html")
} else if err == errNoExtension {
- // assume .html extension
+ // assume .html extension and try to resolve
return resolvePath(evalSymLink, lookupPath, strings.TrimSuffix(subPath, "/")+".html")
}
@@ -46,8 +51,7 @@ func ResolveFilePath(lookupPath, subPath, requestURLPath string, evalSymLink eva
// Resolve the HTTP request to a path on disk, converting requests for
// directories to requests for index.html inside the directory if appropriate.
// Takes a `evalSymLinkFunc` to try to follow any symlinks. For disk use `filepath.EvalSymlinks`.
-// Returns the resolved fullPath or an error
-// TODO: handle zip archives
+// Returns the resolved fullPath, fileName (filepath.Base) and error
func resolvePath(evalSymLink evalSymlinkFunc, publicPath string, subPath ...string) (string, error) {
// Ensure that publicPath always ends with "/"
publicPath = strings.TrimSuffix(publicPath, "/") + "/"
@@ -62,6 +66,10 @@ func resolvePath(evalSymLink evalSymlinkFunc, publicPath string, subPath ...stri
return "", errNoExtension
}
+ if evalSymLink == nil {
+ return testPath, nil
+ }
+
fullPath, err := evalSymLink(testPath)
if err != nil {
return "", errFileNotFound
@@ -83,6 +91,7 @@ func endsWithoutHTMLExtension(path string) bool {
return !strings.HasSuffix(path, ".html")
}
+// cleanEmpty removes empty string elements in the slice
func cleanEmpty(in []string) []string {
var out []string
diff --git a/internal/serving/fileresolver/fileresolver_test.go b/internal/serving/fileresolver/fileresolver_test.go
index dc26c353..a864f445 100644
--- a/internal/serving/fileresolver/fileresolver_test.go
+++ b/internal/serving/fileresolver/fileresolver_test.go
@@ -1,7 +1,7 @@
package fileresolver
import (
- "archive/zip"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestResolveFilePathFromDisk(t *testing.T) {
+func TestOpenFileFromDisk(t *testing.T) {
cleanup := setUpTests(t)
defer cleanup()
@@ -18,7 +18,6 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name string
lookupPath string
subPath string
- urlPath string
expectedFullPath string
expectedContent string
expectedErr error
@@ -27,7 +26,6 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name: "file_exists_with_subpath_and_extension",
lookupPath: "group/group.test.io/public/",
subPath: "index.html",
- urlPath: "/index.html",
expectedFullPath: "group/group.test.io/public/index.html",
expectedContent: "main-dir\n",
},
@@ -35,7 +33,6 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name: "file_exists_without_extension",
lookupPath: "group/group.test.io/public/",
subPath: "index",
- urlPath: "/index",
expectedFullPath: "group/group.test.io/public/index.html",
expectedContent: "main-dir\n",
},
@@ -43,7 +40,6 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name: "file_exists_without_subpath",
lookupPath: "group/group.test.io/public/",
subPath: "",
- urlPath: "/",
expectedFullPath: "group/group.test.io/public/index.html",
expectedContent: "main-dir\n",
},
@@ -51,21 +47,18 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name: "file_does_not_exist_without_subpath",
lookupPath: "group.no.projects/",
subPath: "",
- urlPath: "/",
expectedErr: errFileNotFound,
},
{
name: "file_does_not_exist",
lookupPath: "group/group.test.io/public/",
subPath: "unknown_file.html",
- urlPath: "/group.test.io/unknown_file.html",
expectedErr: errFileNotFound,
},
{
name: "symlink_inside_public",
lookupPath: "group/symlink/public/",
subPath: "index.html",
- urlPath: "/symlink/index.html",
expectedFullPath: "group/symlink/public/content/index.html",
expectedContent: "group/symlink/public/content/index.html\n",
},
@@ -73,24 +66,18 @@ func TestResolveFilePathFromDisk(t *testing.T) {
name: "symlink_outside_of_public_dir",
lookupPath: "group/symlink/public/",
subPath: "outside.html",
- urlPath: "/symlink/outside.html",
expectedErr: errFileNotInPublicDir,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- fullPath, err := ResolveFilePath(tt.lookupPath, tt.subPath, tt.urlPath, filepath.EvalSymlinks)
+ file, err := OpenFile(tt.lookupPath, tt.subPath, filepath.EvalSymlinks, openFSFile)
if tt.expectedErr != nil {
require.Equal(t, tt.expectedErr, err)
return
}
-
- require.Equal(t, tt.expectedFullPath, fullPath)
-
- file, err := openFSFile(fullPath)
require.NoError(t, err)
- defer file.Close()
content, err := ioutil.ReadAll(file)
require.NoError(t, err)
@@ -120,12 +107,7 @@ func chdirInPath(t *testing.T, path string) func() {
}
}
-func openZipFile(t *testing.T, fullPath string, archive *zip.Reader) (*zip.File, error) {
- t.Helper()
-
- return nil, nil
-}
-func openFSFile(fullPath string) (*os.File, error) {
+func openFSFile(fullPath string) (io.ReadCloser, error) {
fi, err := os.Lstat(fullPath)
if err != nil {
return nil, errFileNotFound
diff --git a/internal/serving/fileresolver/fileresolver_zip_test.go b/internal/serving/fileresolver/fileresolver_zip_test.go
new file mode 100644
index 00000000..3a0898ff
--- /dev/null
+++ b/internal/serving/fileresolver/fileresolver_zip_test.go
@@ -0,0 +1,187 @@
+package fileresolver
+
+import (
+ "archive/zip"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestOpenFileFromZip(t *testing.T) {
+ cleanup := setUpTests(t)
+ defer cleanup()
+
+ tests := []struct {
+ name string
+ archivePath string
+ subPath string
+ expectedContent string
+ expectedErrMsg string
+ }{
+ {
+ name: "file_exists_with_subpath_and_extension",
+ archivePath: "group/group.test.io/public.zip",
+ subPath: "index.html",
+ expectedContent: "main-dir\n",
+ },
+ {
+ name: "file_exists_without_extension",
+ archivePath: "group/group.test.io/public.zip",
+ subPath: "index",
+ expectedContent: "main-dir\n",
+ },
+ {
+ name: "file_exists_without_subpath",
+ archivePath: "group/group.test.io/public.zip",
+ subPath: "",
+ expectedContent: "main-dir\n",
+ },
+ {
+ name: "file_does_not_exist_without_subpath",
+ archivePath: "group.no.projects/public.zip",
+ subPath: "",
+ expectedErrMsg: "not found",
+ },
+ {
+ name: "file_does_not_exist",
+ archivePath: "group/group.test.io/public.zip",
+ subPath: "unknown_file.html",
+ expectedErrMsg: "not found",
+ },
+ {
+ name: "symlink_inside_public",
+ archivePath: "group/symlink/public.zip",
+ subPath: "index.html",
+ expectedContent: "group/symlink/public/content/index.html\n",
+ },
+ }
+
+ z := z{
+ maxSymlinkSize: 4096,
+ maxSymlinkDepth: 3,
+ zipDeployPath: "public",
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ reader, err := zip.OpenReader(tt.archivePath)
+ require.NoError(t, err)
+ defer reader.Close()
+ z.archive = reader
+
+ file, err := OpenFile("", tt.subPath, nil, z.resolvePublic)
+ if tt.expectedErrMsg != "" {
+ require.NotNil(t, err)
+ require.Contains(t, err.Error(), tt.expectedErrMsg)
+ return
+ }
+ require.NoError(t, err)
+
+ content, err := ioutil.ReadAll(file)
+ require.NoError(t, err)
+ require.Contains(t, string(content), tt.expectedContent)
+ })
+ }
+}
+
+// const zipDeployPath = "public"
+// const maxSymlinkSize = 4096
+// const maxSymlinkDepth = 3
+
+type z struct {
+ archive *zip.ReadCloser
+
+ zipDeployPath string
+ maxSymlinkSize int64
+ maxSymlinkDepth int
+}
+
+func (z *z) readSymlink(file *zip.File) (string, error) {
+ fi := file.FileInfo()
+
+ if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+ return "", nil
+ }
+
+ if fi.Size() > z.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 *z) resolveUnchecked(path string) (*zip.File, error) {
+ // limit the resolve depth of symlink
+ for depth := 0; depth < z.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 *z) resolvePublic(path string) (io.ReadCloser, error) {
+ path = filepath.Join(z.zipDeployPath, path)
+ file, err := z.resolveUnchecked(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if !strings.HasPrefix(file.Name, z.zipDeployPath+"/") {
+ return nil, fmt.Errorf("%q: is not in %s/", file.Name, z.zipDeployPath)
+ }
+
+ return file.Open()
+}
+
+func (z *z) find(path string) *zip.File {
+ if z.archive == nil {
+ return nil
+ }
+
+ // This is O(n) search, very, very, very slow
+ for _, file := range z.archive.File {
+ if file.Name == path || file.Name == path+"/" {
+ return file
+ }
+ }
+
+ return nil
+}
diff --git a/shared/pages/group.no.projects/public.zip b/shared/pages/group.no.projects/public.zip
new file mode 100644
index 00000000..bc9d63b5
--- /dev/null
+++ b/shared/pages/group.no.projects/public.zip
Binary files differ
diff --git a/shared/pages/group/group.test.io/public.zip b/shared/pages/group/group.test.io/public.zip
new file mode 100644
index 00000000..6168d2c8
--- /dev/null
+++ b/shared/pages/group/group.test.io/public.zip
Binary files differ
diff --git a/shared/pages/group/symlink/public.zip b/shared/pages/group/symlink/public.zip
new file mode 100644
index 00000000..8a73cc7b
--- /dev/null
+++ b/shared/pages/group/symlink/public.zip
Binary files differ