check.go (2905B)
1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 10 "github.com/jackmordaunt/icns/v4" 11 "github.com/jackmordaunt/icns/v4/exe" 12 "github.com/jackmordaunt/icns/v4/ico" 13 ) 14 15 // check reports what the platform that owns the format will make of an icon 16 // file. Findings are printed in order of severity, and the error reports the 17 // ones that change what is drawn. 18 func check(path string, r io.Reader) error { 19 data, err := io.ReadAll(r) 20 if err != nil { 21 return fmt.Errorf("reading %s: %w", name(path), err) 22 } 23 var lines []string 24 serious := 0 25 switch container(data, extension(filepath.Ext(path))) { 26 case ".icns": 27 problems, err := icns.Validate(bytes.NewReader(data)) 28 if err != nil { 29 return fmt.Errorf("reading %s: %w", name(path), err) 30 } 31 for _, p := range problems { 32 lines = append(lines, p.String()) 33 if p.Severity != icns.Advice { 34 serious++ 35 } 36 } 37 case ".ico": 38 problems, err := ico.Validate(bytes.NewReader(data)) 39 if err != nil { 40 return fmt.Errorf("reading %s: %w", name(path), err) 41 } 42 for _, p := range problems { 43 lines = append(lines, p.String()) 44 if p.Severity != ico.Advice { 45 serious++ 46 } 47 } 48 case ".exe", ".dll": 49 // The icons Explorer draws for a shipped binary are the ones worth 50 // checking, and they are only reachable through its resources. 51 groups, err := exe.Icons(bytes.NewReader(data)) 52 if err != nil { 53 return fmt.Errorf("reading %s: %w", name(path), err) 54 } 55 for _, group := range groups { 56 problems, err := ico.Validate(bytes.NewReader(group.ICO())) 57 if err != nil { 58 return fmt.Errorf("reading %s: %s: %w", name(path), group, err) 59 } 60 for _, p := range problems { 61 lines = append(lines, fmt.Sprintf("%s: %s", group, p)) 62 if p.Severity != ico.Advice { 63 serious++ 64 } 65 } 66 } 67 default: 68 return fmt.Errorf("%s is not an icns, ico or Windows binary", name(path)) 69 } 70 for _, line := range lines { 71 fmt.Fprintf(os.Stdout, "%s: %s\n", name(path), line) 72 } 73 if serious > 0 { 74 return fmt.Errorf("%s: %d of %d findings change what is drawn", name(path), serious, len(lines)) 75 } 76 return nil 77 } 78 79 // container names what the data holds, by the bytes it begins with and 80 // failing that by the extension it was given. A Windows binary is named by 81 // its extension, since the icons are inside it rather than at its start. 82 func container(data []byte, ext string) string { 83 switch { 84 case len(data) >= 4 && string(data[:4]) == "icns": 85 return ".icns" 86 case len(data) >= 4 && string(data[:4]) == "\x00\x00\x01\x00": 87 return ".ico" 88 case len(data) >= 2 && string(data[:2]) == "MZ": 89 if binaries[ext] { 90 return ext 91 } 92 return ".exe" 93 } 94 if containers[ext] { 95 return ext 96 } 97 return "" 98 } 99 100 // name labels the file in a report, calling the one arriving on a pipe what 101 // it is rather than leaving it blank. 102 func name(path string) string { 103 if path == "" { 104 return "stdin" 105 } 106 return filepath.Base(path) 107 }