Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pkg/picod/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ type FileInfo struct {

// UploadFileRequest defines JSON upload request body
type UploadFileRequest struct {
Path string `json:"path" binding:"required"`
Content string `json:"content" binding:"required"` // Base64 encoded content
Mode string `json:"mode"`
Path string `json:"path" binding:"required"`
Content *string `json:"content" binding:"required"` // Base64 encoded content
Mode string `json:"mode"`
}

// UploadFileHandler handles file upload requests
Expand Down Expand Up @@ -175,7 +175,7 @@ func (s *Server) handleJSONBase64Upload(c *gin.Context) {
}

// Decode Base64 content
decodedContent, err := base64.StdEncoding.DecodeString(req.Content)
decodedContent, err := base64.StdEncoding.DecodeString(*req.Content)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": fmt.Sprintf("Invalid base64 content: %v", err),
Expand Down
62 changes: 62 additions & 0 deletions pkg/picod/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ limitations under the License.
package picod

import (
"bytes"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -321,3 +325,61 @@ func TestMkdirSafe(t *testing.T) {
assert.True(t, os.IsNotExist(statErr), "directory should not exist outside workspace")
})
}

func TestHandleJSONBase64Upload_Content(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "picod-upload-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)

server := &Server{}
assert.NoError(t, server.setWorkspace(tmpDir))

tests := []struct {
name string
body string
file string
expectedCode int
expectedData string
}{
{
name: "empty content creates an empty file",
body: `{"path": "empty.txt", "content": ""}`,
file: "empty.txt",
expectedCode: http.StatusOK,
expectedData: "",
},
{
name: "missing content is rejected",
body: `{"path": "missing.txt"}`,
file: "missing.txt",
expectedCode: http.StatusBadRequest,
},
{
name: "non-empty content is written",
body: `{"path": "hello.txt", "content": "aGVsbG8="}`,
file: "hello.txt",
expectedCode: http.StatusOK,
expectedData: "hello",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/api/files", bytes.NewBufferString(tt.body))
c.Request.Header.Set("Content-Type", "application/json")

server.UploadFileHandler(c)

assert.Equal(t, tt.expectedCode, w.Code, w.Body.String())
data, readErr := os.ReadFile(filepath.Join(tmpDir, tt.file))
if tt.expectedCode != http.StatusOK {
assert.True(t, os.IsNotExist(readErr), "file should not be created")
return
}
assert.NoError(t, readErr)
assert.Equal(t, tt.expectedData, string(data))
})
}
}
4 changes: 2 additions & 2 deletions pkg/picod/picod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TestPicoD_EndToEnd(t *testing.T) {
contentB64 := base64.StdEncoding.EncodeToString([]byte(content))
uploadReq := UploadFileRequest{
Path: "test.txt",
Content: contentB64,
Content: &contentB64,
Mode: "0644",
}
body, _ := json.Marshal(uploadReq)
Expand Down Expand Up @@ -290,7 +290,7 @@ func TestPicoD_EndToEnd(t *testing.T) {
// 6. Jail Escape Attempt (Should Fail)
escapeReq := UploadFileRequest{
Path: "../outside.txt",
Content: contentB64,
Content: &contentB64,
}
escapeBody, _ := json.Marshal(escapeReq)
req, _ = http.NewRequest("POST", ts.URL+"/api/files", bytes.NewBuffer(escapeBody))
Expand Down