package diff import ( "bytes" "encoding/json" "fmt" "html/template" "io" "os" "path/filepath" "strconv" "github.com/apex/log" "github.com/blacktop/ipsw/internal/utils" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/ast" "github.com/gomarkdown/markdown/html" ) // {{ range $index, $element := .Ents }} // - [{{ $element }}](#{{ $element | urlize }}) // {{ end -}} const diffMarkdownTemplate = ` - [{{ .Title }}](#{{ .Title | slug }}) - [IPSWs](#ipsws) - [Kernel](#kernel) - [Version](#version) {{- if .Kexts }} - [Kexts](#kexts) {{- if .Kexts.New }} - [🆕 NEW ({{ len .Kexts.New }})](#-new) {{- end }} {{- if .Kexts.Removed }} - [❌ Removed ({{ len .Kexts.Removed }})](#-removed) {{- end }} {{- if .Kexts.Updated }} - [⬆️ Updated ({{ len .Kexts.Updated }})](#️-updated) {{- end }} {{- end }} - [Machos](#machos) {{- if .Machos }} {{- if .Machos.New }} - [🆕 NEW ({{ len .Machos.New }})](#-new-1) {{- end }} {{- if .Machos.Removed }} - [❌ Removed ({{ len .Machos.Removed }})](#-removed-1) {{- end }} {{- if .Machos.Updated }} - [⬆️ Updated ({{ len .Machos.Updated }})](#️-updated-1) {{- end }} {{- end }} {{- if .Ents }} - [🔑 Entitlements](#entitlements) {{- end }} - [DSC](#dsc) - [WebKit](#webkit) - [Dylibs](#dylibs) {{- if .Dylibs }} {{- if .Dylibs.New }} - [🆕 NEW ({{ len .Dylibs.New }})](#-new-2) {{- end }} {{- if .Dylibs.Removed }} - [❌ Removed ({{ len .Dylibs.Removed }})](#️-removed-2) {{- end }} {{- if .Dylibs.Updated }} - [⬆️ Updated ({{ len .Dylibs.Updated }})](#️-updated-2) {{- end }} {{- end }} # {{ .Title }} ## IPSWs - {{ .Old.IPSWPath | base }} - {{ .New.IPSWPath | base }} ## Kernel {{ if .Old.Kernel.Version }} ### Version | iOS | Version | Build | Date | | :-------------------------------------- | :-------------------------------------- | :----------------------------------- | :------------------------------------ | | {{ .Old.Version }} *({{ .Old.Build }})* | {{ .Old.Kernel.Version.KernelVersion.Darwin }} | {{ .Old.Kernel.Version.KernelVersion.XNU }} | {{ .Old.Kernel.Version.KernelVersion.Date.Format "Mon, 02Jan2006 15:04:05 MST" }} | | {{ .New.Version }} *({{ .New.Build }})* | {{ .New.Kernel.Version.KernelVersion.Darwin }} | {{ .New.Kernel.Version.KernelVersion.XNU }} | {{ .New.Kernel.Version.KernelVersion.Date.Format "Mon, 02Jan2006 15:04:05 MST" }} | {{ end -}} {{ if .Kexts }} ### Kexts {{ if .Kexts.New }} ### 🆕 NEW {{ range .Kexts.New }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Kexts.Removed }} ### ❌ Removed {{ range .Kexts.Removed }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Kexts.Updated }} ### ⬆️ Updated
View Updated {{ range $key, $value := .Kexts.Updated }} #### {{ $key | base }} > {{ $key | code }} {{ $value | noescape }} {{ end }}
{{ end -}} {{ end -}} {{ if .KDKs }} ## KDKs - {{ .Old.KDK | code}} - {{ .New.KDK | code}} {{ .KDKs | noescape }} {{ end -}} {{- if .Machos }} ## MachOs {{ if .Machos.New }} ### 🆕 NEW {{ range .Machos.New }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Machos.Removed }} ### ❌ Removed {{ range .Machos.Removed }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Machos.Updated }} ### ⬆️ Updated
View Updated {{ range $key, $value := .Machos.Updated }} #### {{ $key | base }} > {{ $key | code }} {{ $value | noescape }} {{ end }}
{{ end -}} {{ end -}} {{ if .Ents }} ### 🔑 Entitlements
View Entitlements {{ .Ents | noescape }}
{{ end -}} {{- if .Firmwares }} ## Firmwares {{ if .Firmwares.New }} ### 🆕 NEW {{ range .Firmwares.New }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Firmwares.Removed }} ### ❌ Removed {{ range .Firmwares.Removed }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Firmwares.Updated }} ### ⬆️ Updated
View Updated {{ range $key, $value := .Firmwares.Updated }} #### {{ $key | base }} > {{ $key | code }} {{ $value | noescape }} {{ end }}
{{ end -}} {{ end -}} {{ if .Launchd }} ## launchd Config {{ .Launchd | noescape }} {{ end }} ## DSC {{ if .Old.Webkit }} ### WebKit | iOS | Version | | :-------------------------------------- | :---------------- | | {{ .Old.Version }} *({{ .Old.Build }})* | {{ .Old.Webkit }} | | {{ .New.Version }} *({{ .New.Build }})* | {{ .New.Webkit }} | {{ end -}} {{- if .Dylibs }} ### Dylibs {{ if .Dylibs.New }} #### 🆕 NEW {{ range .Dylibs.New }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Dylibs.Removed }} #### ❌ Removed {{ range .Dylibs.Removed }} - {{ . | code }} {{ end }} {{ end -}} {{- if .Dylibs.Updated }} #### ⬆️ Updated
View Updated {{ range $key, $value := .Dylibs.Updated }} ##### {{ $key | base }} > {{ $key | code }} {{ $value | noescape }} {{ end }}
{{ end -}} {{ end -}} ` func (d *Diff) String() string { var tmptout bytes.Buffer tmpl := template.Must(template.New("diff"). Funcs(template.FuncMap{ "noescape": func(value any) template.HTML { return template.HTML(fmt.Sprint(value)) }, }). Funcs(template.FuncMap{ "base": func(value string) template.HTML { return template.HTML(fmt.Sprintf("`%s`", filepath.Base(value))) }, }). Funcs(template.FuncMap{ "code": func(value string) template.HTML { return template.HTML(fmt.Sprintf("`%s`", value)) }, }). Funcs(template.FuncMap{ "slug": func(value string) string { return utils.Slugify(value) }, }). Parse(diffMarkdownTemplate)) if err := tmpl.Execute(&tmptout, d); err != nil { return fmt.Errorf("failed to execute diff template: %s", err).Error() } return tmptout.String() } // ToJSON saves the diff as a JSON file func (d *Diff) ToJSON() error { dat, err := json.MarshalIndent(d, "", " ") if err != nil { return err } if len(d.conf.Output) > 0 { if err := os.MkdirAll(d.conf.Output, 0755); err != nil { return err } fname := filepath.Join(d.conf.Output, fmt.Sprintf("%s.json", d.Title)) log.Infof("Creating JSON diff file: %s", fname) return os.WriteFile(fname, dat, 0644) } fmt.Println(string(dat)) return nil } func (d *Diff) ToHTML() error { htmlHeader := `
` htmlFooter := `

` var htmlBuf bytes.Buffer renderer := html.NewRenderer(html.RendererOptions{ Title: d.Title, Flags: html.TOC | html.CommonFlags, RenderNodeHook: renderHook, }) output := string(markdown.ToHTML([]byte(d.String()), nil, renderer)) tmpl := template.Must(template.New("Render").Parse(htmlHeader + "{{.}}" + htmlFooter)) if err := tmpl.Execute(&htmlBuf, template.HTML(output)); err != nil { return err } if err := os.MkdirAll(d.conf.Output, 0755); err != nil { return err } fname := filepath.Join(d.conf.Output, fmt.Sprintf("%s.html", d.Title)) log.Infof("Creating HTML diff file: %s", fname) return os.WriteFile(fname, htmlBuf.Bytes(), 0644) } func renderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) { if _, ok := node.(*ast.Heading); ok { level := strconv.Itoa(node.(*ast.Heading).Level) if entering && level == "1" { w.Write([]byte(`

`)) } else if entering { w.Write([]byte("")) } else { w.Write([]byte("")) } return ast.GoToNext, true } else if _, ok := node.(*ast.Image); ok { src := string(node.(*ast.Image).Destination) c := node.(*ast.Image).GetChildren()[0] alt := string(c.AsLeaf().Literal) if entering && alt != "" { w.Write([]byte(`
` + alt + ``)) } else if entering { w.Write([]byte(`
`)) } else { w.Write([]byte(`
`)) } return ast.SkipChildren, true } else { return ast.GoToNext, false } } // func (c *Context) MarshalJSON() ([]byte, error) { // return json.Marshal(&struct { // ID int `json:"id,omitempty"` // Name string `json:"name,omitempty"` // Info info `json:"info,omitempty"` // Encoding intEncoding `json:"encoding,omitempty"` // }{ // ID: i.id, // Name: i.name, // Info: i.info, // Encoding: i.encoding, // }) // }