diff --git a/mgrctl/cmd/api/get.go b/mgrctl/cmd/api/get.go index c43ef740253..e7c80781f28 100644 --- a/mgrctl/cmd/api/get.go +++ b/mgrctl/cmd/api/get.go @@ -7,6 +7,7 @@ package api import ( "encoding/json" "fmt" + "mime" "strings" "github.com/rs/zerolog/log" @@ -18,7 +19,7 @@ import ( "github.com/uyuni-project/uyuni-tools/shared/utils" ) -func runGet(_ *types.GlobalFlags, flags *apiFlags, _ *cobra.Command, args []string) error { +func runGet(_ *types.GlobalFlags, flags *apiFlags, cmd *cobra.Command, args []string) error { log.Debug().Msgf("Running GET command %s", args[0]) client, err := api.Init(&flags.ConnectionDetails) if err == nil && (client.Details.User != "" || client.Details.InSession) { @@ -35,13 +36,90 @@ func runGet(_ *types.GlobalFlags, flags *apiFlags, _ *cobra.Command, args []stri return utils.Errorf(err, L("error in query '%s'"), path) } - // TODO do this only when result is JSON or TEXT. Watchout for binary data - // Decode JSON to the string and pretty print it + // Check if the result is binary data by examining content type or trying JSON marshaling + if err := outputResult(cmd, res); err != nil { + return err + } + + return nil +} + +// outputResult handles outputting the API response appropriately based on content type. +// For JSON/text data, it pretty-prints the JSON. +// For binary data, it writes raw bytes to stdout. +func outputResult(cmd *cobra.Command, res *api.APIResponse[interface{}]) error { + // Try to marshal as JSON first out, err := json.MarshalIndent(res.Result, "", " ") if err != nil { + // If JSON marshaling fails, treat as binary data + log.Debug().Msg(L("Result is not JSON-serializable, treating as binary data")) + + // Convert result to bytes and write directly + if res.Result != nil { + if data, ok := res.Result.([]byte); ok { + _, err := cmd.OutOrStdout().Write(data) + return err + } + // Fallback: try to write as string + _, err := fmt.Fprint(cmd.OutOrStdout(), res.Result) + return err + } + return nil + } + + // Check if output looks like binary (contains null bytes or non-printable chars) + if containsBinaryData(out) { + log.Debug().Msg(L("Detected binary data in response")) + _, err := cmd.OutOrStdout().Write(out) return err } - fmt.Print(string(out)) - return nil + // Output JSON with newline + _, err = fmt.Fprintln(cmd.OutOrStdout(), string(out)) + return err +} + +// containsBinaryData checks if the data contains binary content. +func containsBinaryData(data []byte) bool { + // Check for null bytes which indicate binary data + for _, b := range data { + if b == 0 { + return true + } + // Check for high frequency of non-printable characters + if b < 32 && b != '\n' && b != '\r' && b != '\t' { + return true + } + } + return false +} + +// isTextContentType checks if the content type indicates text-based data. +func isTextContentType(contentType string) bool { + if contentType == "" { + return false + } + + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return false + } + + // Text-based content types + textTypes := []string{ + "application/json", + "text/plain", + "text/html", + "text/xml", + "application/xml", + "application/javascript", + } + + for _, t := range textTypes { + if mediaType == t { + return true + } + } + + return false }