mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-02-26 10:53:23 +00:00
477 lines
14 KiB
Go
477 lines
14 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/aptly-dev/aptly/aptly"
|
|
ctx "github.com/aptly-dev/aptly/context"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/smira/flag"
|
|
|
|
. "gopkg.in/check.v1"
|
|
)
|
|
|
|
type FilesUploadDiskFullSuite struct {
|
|
aptlyContext *ctx.AptlyContext
|
|
flags *flag.FlagSet
|
|
configFile *os.File
|
|
router http.Handler
|
|
}
|
|
|
|
var _ = Suite(&FilesUploadDiskFullSuite{})
|
|
|
|
func (s *FilesUploadDiskFullSuite) SetUpTest(c *C) {
|
|
aptly.Version = "testVersion"
|
|
|
|
file, err := os.CreateTemp("", "aptly")
|
|
c.Assert(err, IsNil)
|
|
s.configFile = file
|
|
|
|
jsonString, err := json.Marshal(gin.H{
|
|
"architectures": []string{},
|
|
"rootDir": c.MkDir(),
|
|
})
|
|
c.Assert(err, IsNil)
|
|
_, err = file.Write(jsonString)
|
|
c.Assert(err, IsNil)
|
|
_ = file.Close()
|
|
|
|
flags := flag.NewFlagSet("fakeFlags", flag.ContinueOnError)
|
|
flags.Bool("no-lock", false, "dummy")
|
|
flags.Int("db-open-attempts", 3, "dummy")
|
|
flags.String("config", s.configFile.Name(), "dummy")
|
|
flags.String("architectures", "", "dummy")
|
|
s.flags = flags
|
|
|
|
aptlyContext, err := ctx.NewContext(s.flags)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.aptlyContext = aptlyContext
|
|
s.router = Router(aptlyContext)
|
|
context = aptlyContext
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TearDownTest(c *C) {
|
|
if s.configFile != nil {
|
|
_ = os.Remove(s.configFile.Name())
|
|
}
|
|
if s.aptlyContext != nil {
|
|
s.aptlyContext.Shutdown()
|
|
}
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadSuccessWithSync(c *C) {
|
|
testContent := []byte("test file content for upload")
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
part, err := writer.CreateFormFile("file", "testfile.txt")
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = part.Write(testContent)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = writer.Close()
|
|
c.Assert(err, IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/testdir", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 200)
|
|
|
|
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir", "testfile.txt")
|
|
content, err := os.ReadFile(uploadedFile)
|
|
c.Assert(err, IsNil)
|
|
c.Check(content, DeepEquals, testContent)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadVerifiesFileIntegrity(c *C) {
|
|
testContent := bytes.Repeat([]byte("A"), 10000)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
part, err := writer.CreateFormFile("file", "largefile.bin")
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = io.Copy(part, bytes.NewReader(testContent))
|
|
c.Assert(err, IsNil)
|
|
|
|
err = writer.Close()
|
|
c.Assert(err, IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/testdir2", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
|
|
uploadedFile := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "testdir2", "largefile.bin")
|
|
content, err := os.ReadFile(uploadedFile)
|
|
c.Assert(err, IsNil)
|
|
c.Check(len(content), Equals, len(testContent))
|
|
c.Check(content, DeepEquals, testContent)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadMultipleFilesWithBatchSync(c *C) {
|
|
testFiles := map[string][]byte{
|
|
"file1.txt": []byte("content of file 1"),
|
|
"file2.txt": bytes.Repeat([]byte("B"), 5000),
|
|
"file3.deb": []byte("debian package content"),
|
|
}
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
for filename, content := range testFiles {
|
|
part, err := writer.CreateFormFile("file", filename)
|
|
c.Assert(err, IsNil)
|
|
_, err = part.Write(content)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
err := writer.Close()
|
|
c.Assert(err, IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/multitest", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
|
|
uploadDir := filepath.Join(s.aptlyContext.Config().GetRootDir(), "upload", "multitest")
|
|
for filename, expectedContent := range testFiles {
|
|
uploadedFile := filepath.Join(uploadDir, filename)
|
|
content, err := os.ReadFile(uploadedFile)
|
|
c.Assert(err, IsNil, Commentf("Failed to read %s", filename))
|
|
c.Check(content, DeepEquals, expectedContent, Commentf("Content mismatch for %s", filename))
|
|
}
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadReturnsErrorOnSyncFailure(c *C) {
|
|
oldSyncFile := syncFile
|
|
syncFile = func(f *os.File) error {
|
|
if filepath.Base(f.Name()) == "syncfail.txt" {
|
|
return syscall.ENOSPC
|
|
}
|
|
return nil
|
|
}
|
|
defer func() { syncFile = oldSyncFile }()
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
part1, err := writer.CreateFormFile("file", "ok.txt")
|
|
c.Assert(err, IsNil)
|
|
_, err = part1.Write([]byte("ok"))
|
|
c.Assert(err, IsNil)
|
|
|
|
part2, err := writer.CreateFormFile("file", "syncfail.txt")
|
|
c.Assert(err, IsNil)
|
|
_, err = part2.Write([]byte("will fail on sync"))
|
|
c.Assert(err, IsNil)
|
|
|
|
err = writer.Close()
|
|
c.Assert(err, IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/syncfaildir", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 500)
|
|
c.Check(bytes.Contains(w.Body.Bytes(), []byte("error syncing file")), Equals, true)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestVerifyPath(c *C) {
|
|
c.Check(verifyPath("a/b/c"), Equals, true)
|
|
c.Check(verifyPath("../x"), Equals, false)
|
|
c.Check(verifyPath("./x"), Equals, true)
|
|
c.Check(verifyPath(".."), Equals, false)
|
|
c.Check(verifyPath("."), Equals, false)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyWhenUploadMissing(c *C) {
|
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
|
|
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListDirsReturnsDirectories(c *C) {
|
|
uploadRoot := s.aptlyContext.UploadPath()
|
|
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d1"), 0777), IsNil)
|
|
c.Assert(os.MkdirAll(filepath.Join(uploadRoot, "d2"), 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(uploadRoot, "rootfile"), []byte("x"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
body := w.Body.String()
|
|
c.Check(strings.Contains(body, "d1"), Equals, true)
|
|
c.Check(strings.Contains(body, "d2"), Equals, true)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListFilesNotFound(c *C) {
|
|
req, err := http.NewRequest("GET", "/api/files/does-not-exist", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 404)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListFilesReturnsFiles(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dir")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "b.txt"), []byte("b"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("GET", "/api/files/dir", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
body := w.Body.String()
|
|
c.Check(strings.Contains(body, "a.txt"), Equals, true)
|
|
c.Check(strings.Contains(body, "b.txt"), Equals, true)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestDeleteDirRemovesDirectory(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/todel", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 200)
|
|
|
|
_, statErr := os.Stat(base)
|
|
c.Check(os.IsNotExist(statErr), Equals, true)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileRemovesFile(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel2")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/todel2/a.txt", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 200)
|
|
|
|
_, statErr := os.Stat(filepath.Join(base, "a.txt"))
|
|
c.Check(os.IsNotExist(statErr), Equals, true)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileNotFoundStillOk(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "todel3")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/todel3/nope.txt", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 200)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidDir(c *C) {
|
|
req, err := http.NewRequest("DELETE", "/api/files/..", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 400)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestRejectsInvalidFileName(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dirx")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/dirx/..", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 400)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListDirsEmptyIfUploadPathIsNotDir(c *C) {
|
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
|
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("GET", "/api/files", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 200)
|
|
c.Check(strings.TrimSpace(w.Body.String()), Equals, "[]")
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestListFilesReturns500OnPermissionError(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "noperms")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
|
c.Assert(os.Chmod(base, 0), IsNil)
|
|
defer func() { _ = os.Chmod(base, 0777) }()
|
|
|
|
req, err := http.NewRequest("GET", "/api/files/noperms", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestDeleteFileReturns500OnNonNotExistError(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "dirisfile")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
subdir := filepath.Join(base, "subdir")
|
|
c.Assert(os.MkdirAll(subdir, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(subdir, "x"), []byte("x"), 0644), IsNil)
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/dirisfile/subdir", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadBadMultipartReturns400(c *C) {
|
|
req, err := http.NewRequest("POST", "/api/files/badmultipart", bytes.NewBufferString("not multipart"))
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", "multipart/form-data; boundary=missing")
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
c.Assert(w.Code, Equals, 400)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadRejectsInvalidDir(c *C) {
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("file", "a.txt")
|
|
c.Assert(err, IsNil)
|
|
_, err = part.Write([]byte("x"))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(writer.Close(), IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/..", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 400)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500IfUploadRootIsNotDir(c *C) {
|
|
_ = os.RemoveAll(s.aptlyContext.UploadPath())
|
|
c.Assert(os.WriteFile(s.aptlyContext.UploadPath(), []byte("not a dir"), 0644), IsNil)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("file", "a.txt")
|
|
c.Assert(err, IsNil)
|
|
_, err = part.Write([]byte("x"))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(writer.Close(), IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/testdir", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnFileOpenFailure(c *C) {
|
|
// Pre-populate MultipartForm to inject a FileHeader that fails on Open().
|
|
form := &multipart.Form{
|
|
File: map[string][]*multipart.FileHeader{
|
|
"file": {{Filename: "broken.bin"}},
|
|
},
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/openfaildir", nil)
|
|
c.Assert(err, IsNil)
|
|
req.MultipartForm = form
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestUploadReturns500OnCreateFailure(c *C) {
|
|
base := filepath.Join(s.aptlyContext.UploadPath(), "readonly")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.Chmod(base, 0555), IsNil)
|
|
defer func() { _ = os.Chmod(base, 0777) }()
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("file", "a.txt")
|
|
c.Assert(err, IsNil)
|
|
_, err = part.Write([]byte("x"))
|
|
c.Assert(err, IsNil)
|
|
c.Assert(writer.Close(), IsNil)
|
|
|
|
req, err := http.NewRequest("POST", "/api/files/readonly", body)
|
|
c.Assert(err, IsNil)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|
|
|
|
func (s *FilesUploadDiskFullSuite) TestDeleteDirReturns500OnRemoveFailure(c *C) {
|
|
parent := s.aptlyContext.UploadPath()
|
|
base := filepath.Join(parent, "cantremove")
|
|
c.Assert(os.MkdirAll(base, 0777), IsNil)
|
|
c.Assert(os.WriteFile(filepath.Join(base, "a.txt"), []byte("a"), 0644), IsNil)
|
|
|
|
c.Assert(os.Chmod(parent, 0555), IsNil)
|
|
defer func() { _ = os.Chmod(parent, 0777) }()
|
|
|
|
req, err := http.NewRequest("DELETE", "/api/files/cantremove", nil)
|
|
c.Assert(err, IsNil)
|
|
w := httptest.NewRecorder()
|
|
s.router.ServeHTTP(w, req)
|
|
c.Assert(w.Code, Equals, 500)
|
|
}
|