fix: address verified review findings (api-transport-download-1)

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 17:38:51 +02:00
1 parent 45c564811e
commit 6b847058eb
11 files changed
+192 -11

No files matched your search

+4 -1
View File
@@ -303,6 +303,9 @@ func (o apiResponse) write(w io.Writer, r *http.Response) error {
return err
}
if o.query != "" && succeeded {
if len(data) == 0 {
return nil
}
if !json.Valid(data) {
return fmt.Errorf("--jq needs a JSON response, but the response is %s", cmp.Or(mediaType, "not JSON"))
}
@@ -363,7 +366,7 @@ func writeRequestHead(w io.Writer, r *http.Request) error {
func writeHead(w io.Writer, prefix, first string, headers http.Header) error {
var b strings.Builder
b.WriteString(prefix + first + "\n")
b.WriteString(prefix + terminal.SafeLine(first) + "\n")
for _, key := range slices.Sorted(maps.Keys(headers)) {
for _, value := range headers.Values(key) {
b.WriteString(prefix + terminal.SafeLine(key+": "+value) + "\n")
+20
View File
@@ -219,6 +219,8 @@ func TestAPIFiltersAndFormatsJSONResponses(t *testing.T) {
case "/api/v1/repos/alice/nope":
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"not found"}`))
case "/api/v1/repos/alice/gone":
w.WriteHeader(http.StatusNoContent)
case "/api/v1/repos":
if r.URL.Query().Get("page") != "2" {
w.Header().Set("Link", `<?page=2>; rel="next"`)
@@ -247,6 +249,7 @@ func TestAPIFiltersAndFormatsJSONResponses(t *testing.T) {
{name: "error body ends with a newline", args: []string{"repos/alice/nope"}, want: "{\"message\":\"not found\"}\n", err: "HTTP 404"},
{name: "error body is not filtered", args: []string{"repos/alice/nope", "--jq", ".message"}, want: "{\"message\":\"not found\"}\n", err: "HTTP 404"},
{name: "jq needs JSON", args: []string{"text", "--jq", "."}, err: "--jq needs a JSON response, but the response is text/plain"},
{name: "jq on 204 No Content", args: []string{"repos/alice/gone", "-X", "DELETE", "--jq", "."}},
{name: "silent", args: []string{"version", "--silent"}},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -288,3 +291,20 @@ func TestAPIHelpShowsJQ(t *testing.T) {
require.Contains(t, out.String(), "\n -q, --jq expression ")
require.NotContains(t, out.String(), "--json fields")
}
func TestAPIIncludeSanitizesTheStatusLine(t *testing.T) {
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
conn, rw, err := w.(http.Hijacker).Hijack()
if err != nil {
return
}
defer conn.Close()
_, _ = rw.WriteString("HTTP/1.1 200 OK\x1b]0;PWNED\x07\r\nContent-Length: 2\r\nConnection: close\r\n\r\n{}")
_ = rw.Flush()
}))
defer server.Close()
deps, _, diagnostics := serverDeps(t, server.URL)
require.NoError(t, Execute(context.Background(), deps, []string{"api", "version", "--include"}))
require.NotContains(t, diagnostics.String(), "\x1b")
require.NotContains(t, diagnostics.String(), "\x07")
}
+9 -2
View File
@@ -14,6 +14,7 @@ import (
"path/filepath"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"gitea.dev/tea/internal/clierr"
@@ -53,10 +54,13 @@ func (f downloadFlags) destination(name string) (path string, skip bool, err err
}
path = f.output
if path == "" || isDirectory(path) {
if name == "" || name == "." || name == ".." || strings.ContainsAny(name, `/\`) {
if name == "" || name == "." || name == ".." || strings.ContainsAny(name, `/\`) || strings.ContainsFunc(name, func(r rune) bool { return unicode.IsControl(r) || unicode.Is(unicode.Cf, r) }) {
return "", false, clierr.Wrap(clierr.Usage, fmt.Errorf("cannot use %q as a file name; pass --output", name))
}
path = filepath.Join(cmp.Or(path, f.dir, "."), name)
if path == "-" {
path = "." + string(filepath.Separator) + path // a file named "-", not standard output
}
}
if _, err := os.Lstat(path); err == nil {
if f.skipExisting {
@@ -115,8 +119,11 @@ func writeTerminalSafe(w io.Writer, tty bool, body io.Reader) error {
// rune cut off at the end.
func binary(data []byte) bool {
for len(data) > 0 {
if !utf8.FullRune(data) {
return false
}
r, size := utf8.DecodeRune(data)
if r == 0 || (r == utf8.RuneError && size == 1 && (len(data) >= utf8.UTFMax || utf8.FullRune(data))) {
if r == 0 || (r == utf8.RuneError && size == 1) {
return true
}
data = data[size:]
+5
View File
@@ -98,6 +98,9 @@ func TestDownloadDestinations(t *testing.T) {
{name: "--skip-existing", flags: downloadFlags{skipExisting: true}, file: "existing.txt", want: "existing.txt", skip: true},
{name: "parent directory name", file: "../escape.txt", err: `cannot use "../escape.txt" as a file name; pass --output`},
{name: "dot dot", file: "..", err: "cannot use"},
{name: "escape sequence in the name", file: "\x1b]0;PWNED\x07.txt", err: "cannot use"},
{name: "bidi override in the name", file: "a\u202etxt.exe", err: "cannot use"},
{name: "a file named dash stays a file", file: "-", want: "." + string(filepath.Separator) + "-"},
{name: "--output and --dir", flags: downloadFlags{output: "x", dir: "dl"}, file: "notes.txt", err: "--output and --dir cannot be combined"},
{name: "--clobber and --skip-existing", flags: downloadFlags{clobber: true, skipExisting: true}, file: "notes.txt", err: "--clobber and --skip-existing cannot be combined"},
} {
@@ -147,6 +150,8 @@ func TestDownloadsToATerminalRefuseBinaryData(t *testing.T) {
{name: "terminal text loses escapes", body: "title\x1b]0;PWNED\x07\n", want: "title]0;PWNED\n", tty: true},
{name: "terminal binary", body: "PK\x03\x04\x00\x01", tty: true, err: "refusing to write binary data to the terminal"},
{name: "terminal invalid UTF-8", body: "abc\xff\xfedef", tty: true, err: "refusing to write binary data"},
{name: "terminal 3-byte rune cut after 2 bytes", body: strings.Repeat("a", binarySniffLength-2) + "\u2026", want: strings.Repeat("a", binarySniffLength-2) + "\u2026", tty: true},
{name: "terminal 4-byte rune cut after 2 bytes", body: strings.Repeat("a", binarySniffLength-2) + "\U0001F600", want: strings.Repeat("a", binarySniffLength-2) + "\U0001F600", tty: true},
{name: "terminal UTF-8 cut at the sniff boundary", body: strings.Repeat("a", binarySniffLength-1) + "é", want: strings.Repeat("a", binarySniffLength-1) + "é", tty: true},
} {
t.Run(tc.name, func(t *testing.T) {