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
15 changes: 10 additions & 5 deletions pkg/cli/download_workflow.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"encoding/base64"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -172,13 +173,17 @@ func downloadWorkflowContent(repo, path, ref string, verbose bool) ([]byte, erro
}

// The content is base64 encoded, decode it
contentBase64 := strings.TrimSpace(string(output))
base64Cmd := exec.Command("base64", "-d")
base64Cmd.Stdin = strings.NewReader(contentBase64)
content, err := base64Cmd.Output()
return decodeBase64FileContent(string(output))
}

// decodeBase64FileContent decodes base64-encoded file content returned by the GitHub API.
// The GitHub API wraps lines at 60 characters and may include surrounding whitespace,
// so both are stripped before decoding.
func decodeBase64FileContent(raw string) ([]byte, error) {
cleaned := strings.ReplaceAll(strings.TrimSpace(raw), "\n", "")
content, err := base64.StdEncoding.DecodeString(cleaned)
if err != nil {
return nil, fmt.Errorf("failed to decode file content: %w", err)
}

return content, nil
}
77 changes: 77 additions & 0 deletions pkg/cli/download_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//go:build !integration

package cli

import (
"encoding/base64"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDecodeBase64FileContent(t *testing.T) {
tests := []struct {
name string
input func() string // build the raw API-style input
expected string
wantErr bool
}{
{
name: "plain base64 without newlines",
input: func() string {
return base64.StdEncoding.EncodeToString([]byte("hello world"))
},
expected: "hello world",
},
{
name: "GitHub API style with embedded newlines every 60 chars",
input: func() string {
encoded := base64.StdEncoding.EncodeToString([]byte("hello world"))
// Simulate GitHub API line-wrapping at 60 characters
var sb strings.Builder
for i, c := range encoded {
if i > 0 && i%60 == 0 {
sb.WriteByte('\n')
}
sb.WriteRune(c)
}
return sb.String()
},
expected: "hello world",
},
{
name: "leading and trailing whitespace stripped",
input: func() string {
return " " + base64.StdEncoding.EncodeToString([]byte("trim me")) + "\n"
},
expected: "trim me",
},
{
name: "binary content round-trips correctly",
input: func() string {
data := []byte{0x00, 0x01, 0x02, 0xFF, 0xFE}
return base64.StdEncoding.EncodeToString(data)
},
expected: string([]byte{0x00, 0x01, 0x02, 0xFF, 0xFE}),
},
{
name: "invalid base64 returns error",
input: func() string { return "!!!not-valid-base64!!!" },
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decodeBase64FileContent(tt.input())
if tt.wantErr {
assert.Error(t, err, "expected an error for invalid base64 input")
return
}
require.NoError(t, err, "unexpected error decoding base64 content")
assert.Equal(t, tt.expected, string(got), "decoded content should match expected")
})
}
}
Loading