diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | domain.go | 26 | ||||
-rw-r--r-- | domain_test.go | 117 | ||||
-rw-r--r-- | domains_test.go | 36 | ||||
-rw-r--r-- | main.go | 7 | ||||
-rw-r--r-- | shared/pages/README.md | 1 | ||||
-rw-r--r-- | shared/pages/group/group.test.io/public/index.html | 1 | ||||
-rw-r--r-- | shared/pages/group/group.test.io/public/project2/index.html | 1 | ||||
-rw-r--r-- | shared/pages/group/project/public/index.html | 1 | ||||
-rw-r--r-- | shared/pages/group/project/public/subdir/index.html | 1 | ||||
-rw-r--r-- | shared/pages/group/project2/public/index.html | 1 | ||||
-rw-r--r-- | shared/pages/group/project2/public/subdir/index.html | 1 |
12 files changed, 185 insertions, 9 deletions
@@ -1 +1,2 @@ # Created by .ignore support plugin (hsz.mobi) +shared/pages/.update @@ -10,10 +10,11 @@ import ( ) type domain struct { - Group string - Project string - Config *domainConfig - certificate *tls.Certificate + Group string + Project string + Config *domainConfig + certificate *tls.Certificate + certificateError error } func (d *domain) notFound(w http.ResponseWriter, r *http.Request) { @@ -23,8 +24,13 @@ func (d *domain) notFound(w http.ResponseWriter, r *http.Request) { func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, subPath string) bool { publicPath := filepath.Join(*pagesRoot, d.Group, projectName, "public") fullPath := filepath.Join(publicPath, subPath) - fullPath = filepath.Clean(fullPath) - if !strings.HasPrefix(fullPath, publicPath) { + fullPath, err := filepath.EvalSymlinks(fullPath) + if err != nil { + return false + } + + if !strings.HasPrefix(fullPath, publicPath+"/") && fullPath != publicPath { + println("The", fullPath, "should be in", publicPath) return false } @@ -59,6 +65,7 @@ func (d *domain) tryFile(w http.ResponseWriter, r *http.Request, projectName, su return false } + println("Serving", fullPath, "for", r.URL.Path) http.ServeContent(w, r, filepath.Base(file.Name()), fi.ModTime(), file) return true } @@ -77,7 +84,7 @@ func (d *domain) serveFromGroup(w http.ResponseWriter, r *http.Request) { } } - if d.tryFile(w, r, strings.ToLower(r.Host), r.URL.Path) { + if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), r.URL.Path) { return } @@ -97,12 +104,13 @@ func (d *domain) ensureCertificate() (*tls.Certificate, error) { return nil, errors.New("tls certificates can be loaded only for pages with configuration") } - if d.certificate != nil { - return d.certificate, nil + if d.certificate != nil || d.certificateError != nil { + return d.certificate, d.certificateError } tls, err := tls.X509KeyPair([]byte(d.Config.Certificate), []byte(d.Config.Key)) if err != nil { + d.certificateError = err return nil, err } diff --git a/domain_test.go b/domain_test.go new file mode 100644 index 00000000..dac9b2a1 --- /dev/null +++ b/domain_test.go @@ -0,0 +1,117 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGroupServeHTTP(t *testing.T) { + testGroup := &domain{ + Group: "group", + Project: "", + } + + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/", nil, "main-dir") + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/index.html", nil, "main-dir") + assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project", nil) + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/", nil, "project-subdir") + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/index.html", nil, "project-subdir") + assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir", nil) + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir/", nil, "project-subsubdir") + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/", nil, "project2-main") + assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/index.html", nil, "project2-main") + assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink", nil) + assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/index.html", nil) + assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/subdir/", nil) + assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/fifo", nil) + assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil) +} + +func TestDomainServeHTTP(t *testing.T) { + testDomain := &domain{ + Group: "group", + Project: "project2", + Config: &domainConfig{ + Domain: "test.domain.com", + }, + } + + assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/index.html", nil, "project2-main") + assert.HTTPRedirect(t, testDomain.ServeHTTP, "GET", "/subdir", nil) + assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir/", nil, "project2-subdir") + assert.HTTPError(t, testDomain.ServeHTTP, "GET", "/not-existing-file", nil) +} + +func TestGroupCertificate(t *testing.T) { + testGroup := &domain{ + Group: "group", + Project: "", + } + + tls, err := testGroup.ensureCertificate() + assert.Nil(t, tls) + assert.Error(t, err) +} + +func TestDomainNoCertificate(t *testing.T) { + testDomain := &domain{ + Group: "group", + Project: "project2", + Config: &domainConfig{ + Domain: "test.domain.com", + }, + } + + tls, err := testDomain.ensureCertificate() + assert.Nil(t, tls) + assert.Error(t, err) + + _, err2 := testDomain.ensureCertificate() + assert.Error(t, err) + assert.Equal(t, err, err2) +} + +func TestDomainCertificate(t *testing.T) { + testDomain := &domain{ + Group: "group", + Project: "project2", + Config: &domainConfig{ + Domain: "test.domain.com", + Certificate: `-----BEGIN CERTIFICATE----- +MIICWDCCAcGgAwIBAgIJAMyzCfoGEwVNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjExMTcxNzM2WhcNMjYwMjA4MTcxNzM2WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB +gQC2ZSzGIlv2zRsELkmEA1JcvIdsFv80b0NbBftewDAQRuyPlhGNifFx6v7+3O1F +5+f+So43N0QbdrHu11K+ZuXNc6hUy0ofG/eRqXniGZEn8paUdQ98sWsbWelBDNeg +WX4FQomynjyxbG+3IuJR5UHoLWhrJ9+pbPrT915eObbaTQIDAQABo1AwTjAdBgNV +HQ4EFgQUGAhDu+gfckg4IkHRCQWBn4ltKV4wHwYDVR0jBBgwFoAUGAhDu+gfckg4 +IkHRCQWBn4ltKV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAaGx5U +JRW5HC9dXADLf9OnmJRqWi3VNXEXWFk5XgHKc1z7KIBWMsdj+1gzm5ltRO7hkHw9 +bx6jQKZBRiUxyqTFw9Ywrk1fYFAxk8hxuqVYcGdonImJarHZTdVMBRWut9+EZBVm +77eYbz2zASNpy++QIg85YgQum9uqREClHRBsxQ== +-----END CERTIFICATE-----`, + Key: `-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALZlLMYiW/bNGwQu +SYQDUly8h2wW/zRvQ1sF+17AMBBG7I+WEY2J8XHq/v7c7UXn5/5Kjjc3RBt2se7X +Ur5m5c1zqFTLSh8b95GpeeIZkSfylpR1D3yxaxtZ6UEM16BZfgVCibKePLFsb7ci +4lHlQegtaGsn36ls+tP3Xl45ttpNAgMBAAECgYAAqZFmDs3isY/9jeV6c0CjUZP0 +UokOubC27eihyXTjOj61rsfVicC0tzPB3S+HZ3YyODcYAD1hFCdFRMbqJhmDiewK +5GfATdNQeNARCfJdjYn57NKaXm7rc4C3so1YfxTL6k9QGJgTcybXiClQPDrhkZt3 +YLIeeJbY3OppLqjzgQJBAN5AzwyUqX5eQIUncQKcFY0PIjfFTku62brT7hq+TlqY +1B6n3GUtIX+tyYg1qusy4KUUSzMslXJubHsxKanGqZ0CQQDSFwzK7KEYoZol5OMX +mRsavc3iXmmEkkNRdNb1R4UqrlasPeeIeO1CfoD2RPcQhZCwFtR8xS8u6X9ncfC4 +qyxxAkAhpQvy6ppR7/Cyd4sLCxfUF8NlT/APVMTbHHQCBmcUHeiWj3C0vEVC78r/ +XKh4HGaXdt//ajNhdEflykZ1VgadAkB6Zh934mEA3rXWOgHsb7EQ5WAb8HF9YVGD +FZVfFaoJ8cRhWTeZlQp14Qn1cLyYjZh8XvCxOJiCtlsZw5JBpMihAkBA6ltWb+aZ +EBjC8ZRwZE+cAzmxaYPSs2J7JhS7X7H7Ax7ShhvHI4br3nqf00H4LkvtcHkn5d9G +MwE1w2r4Deww +-----END PRIVATE KEY-----`, + }, + } + + tls, err := testDomain.ensureCertificate() + assert.NotNil(t, tls) + assert.NoError(t, err) +} diff --git a/domains_test.go b/domains_test.go index 5c16aa14..ae849f46 100644 --- a/domains_test.go +++ b/domains_test.go @@ -4,8 +4,14 @@ import ( "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" + "time" + "io/ioutil" + "crypto/rand" + "os" ) +const updateFile = "shared/pages/.update" + func TestReadProjects(t *testing.T) { *pagesDomain = "test.io" @@ -33,3 +39,33 @@ func TestReadProjects(t *testing.T) { assert.Contains(t, expectedDomains, actual) } } + +func writeRandomTimestamp() { + b := make([]byte, 10) + rand.Read(b) + ioutil.WriteFile(updateFile, b, 0600) +} + +func TestWatchDomains(t *testing.T) { + update := make(chan domains) + go watchDomains(func(domains domains) { + update <- domains + }, time.Microsecond) + + defer os.Remove(updateFile) + + domains := <-update + assert.NotNil(t, domains, "if the domains are fetched on start") + + writeRandomTimestamp() + domains = <-update + assert.NotNil(t, domains, "if the domains are updated after the creation") + + writeRandomTimestamp() + domains = <-update + assert.NotNil(t, domains, "if the domains are updated after the timestamp change") + + os.Remove(updateFile) + domains = <-update + assert.NotNil(t, domains, "if the domains are updated after the timestamp removal") +} @@ -9,6 +9,7 @@ import ( "strings" "sync" "time" + "path/filepath" ) // VERSION stores the information about the semantic version of application @@ -109,6 +110,12 @@ func main() { fmt.Printf("URL: https://gitlab.com/gitlab-org/gitlab-pages") flag.Parse() + fullPath, err := filepath.EvalSymlinks(*pagesRoot) + if err != nil { + log.Fatalln(err) + } + *pagesRoot = fullPath + // Listen for HTTP if *listenHTTP != "" { wg.Add(1) diff --git a/shared/pages/README.md b/shared/pages/README.md new file mode 100644 index 00000000..afd6db1c --- /dev/null +++ b/shared/pages/README.md @@ -0,0 +1 @@ +We use that folder to store example files for various pages. diff --git a/shared/pages/group/group.test.io/public/index.html b/shared/pages/group/group.test.io/public/index.html new file mode 100644 index 00000000..8da4d196 --- /dev/null +++ b/shared/pages/group/group.test.io/public/index.html @@ -0,0 +1 @@ +main-dir diff --git a/shared/pages/group/group.test.io/public/project2/index.html b/shared/pages/group/group.test.io/public/project2/index.html new file mode 100644 index 00000000..a47d5768 --- /dev/null +++ b/shared/pages/group/group.test.io/public/project2/index.html @@ -0,0 +1 @@ +project2-main diff --git a/shared/pages/group/project/public/index.html b/shared/pages/group/project/public/index.html new file mode 100644 index 00000000..4ea0a6ae --- /dev/null +++ b/shared/pages/group/project/public/index.html @@ -0,0 +1 @@ +project-subdir diff --git a/shared/pages/group/project/public/subdir/index.html b/shared/pages/group/project/public/subdir/index.html new file mode 100644 index 00000000..c4815ba3 --- /dev/null +++ b/shared/pages/group/project/public/subdir/index.html @@ -0,0 +1 @@ +project-subsubdir diff --git a/shared/pages/group/project2/public/index.html b/shared/pages/group/project2/public/index.html new file mode 100644 index 00000000..a47d5768 --- /dev/null +++ b/shared/pages/group/project2/public/index.html @@ -0,0 +1 @@ +project2-main diff --git a/shared/pages/group/project2/public/subdir/index.html b/shared/pages/group/project2/public/subdir/index.html new file mode 100644 index 00000000..ffcfa0b4 --- /dev/null +++ b/shared/pages/group/project2/public/subdir/index.html @@ -0,0 +1 @@ +project2-subdir |