You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/cmd/datekeys/present.go

214 lines
6.6 KiB

package main
import (
"fmt"
"io"
"os"
"strings"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/pathrule"
)
// The labels of spec §29.7, which the official SDK must use.
const (
authorLabel = "autor declarado (texto del creador, sin comprobar):"
commentTitle = "Comentario del creador (sin comprobar)"
noteTitle = "Nota pública del creador (sin comprobar)"
// prefix goes before every piece of text of the creator. It counts 3
// columns: U+2502 is of ambiguous width, 2 columns in a CJK terminal.
prefix = "│ "
prefixWidth = 3
// defaultWidth is W when the output is not a terminal, and minWidth the
// least W ever used.
defaultWidth = 80
minWidth = 20
)
// outputWidth is W for w (spec §29.7): the width of the terminal, or 80 when
// w is not one, and never less than 20.
func outputWidth(w io.Writer) int {
width := defaultWidth
if f, ok := w.(*os.File); ok {
if n, ok := termWidth(f); ok && n > 0 {
width = n
}
}
return max(width, minWidth)
}
// runeWidth counts the width of r by excess: 1 for printable ASCII, 2 for
// any other code point (spec §29.7).
func runeWidth(r rune) int {
if r >= 0x20 && r <= 0x7E {
return 1
}
return 2
}
// expandTabs turns each TAB of line into spaces up to the next column that
// is a multiple of 8, counting columns as runeWidth does.
func expandTabs(line string) string {
if !strings.Contains(line, "\t") {
return line
}
var b strings.Builder
col := 0
for _, r := range line {
if r == '\t' {
n := 8 - col%8
b.WriteString(strings.Repeat(" ", n))
col += n
continue
}
b.WriteRune(r)
col += runeWidth(r)
}
return b.String()
}
// pieces splits line into pieces of at most limit columns, each with at
// least one code point. An empty line is one empty piece.
func pieces(line string, limit int) []string {
var out []string
start, width := 0, 0
for i, r := range line {
w := runeWidth(r)
if width+w > limit && i > start {
out = append(out, line[start:i])
start, width = i, 0
}
width += w
}
return append(out, line[start:])
}
// writeCreator writes text of the creator, line by line, each line in
// pieces of at most W - 3 columns behind the prefix: no line of the creator
// is ever broken by the terminal or shown without the prefix, so none can
// pass for a line of the reader (spec §29.7).
func writeCreator(w io.Writer, text string, width int) {
for _, line := range strings.Split(text, "\n") {
for _, p := range pieces(expandTabs(line), width-prefixWidth) {
fmt.Fprintln(w, prefix+p)
}
}
}
// present shows what a format 3 capsule holds, after step 18, as spec §29.7
// says: the verdicts first, then the declared author and the comment as
// text of the creator that nobody has checked, then the paths of the files
// written to dir, with a warning after those that are risky to open, and the
// verdicts again at the end.
func present(w io.Writer, o *capsule.Opened, dir string, width int) {
verdicts := o.Verdicts.Lines()
for _, line := range verdicts {
fmt.Fprintln(w, line)
}
h := o.Head
if o.Inspection != nil && o.Inspection.Header != nil {
if note, ok := o.Inspection.Header.PublicNote(); ok {
fmt.Fprintln(w, "┌ "+noteTitle)
writeCreator(w, note, width)
fmt.Fprintln(w, "└")
}
}
if h.Author != "" {
fmt.Fprintln(w, authorLabel)
writeCreator(w, h.Author, width)
}
if h.Comment != "" {
fmt.Fprintln(w, "┌ "+commentTitle)
writeCreator(w, h.Comment, width)
fmt.Fprintln(w, "└")
}
if len(h.Files) > 0 {
fmt.Fprintf(w, "Ficheros escritos en %s (%d):\n", dir, len(h.Files))
for _, f := range h.Files {
writeCreator(w, f.Path, width)
for _, warning := range risks(f.Path) {
fmt.Fprintln(w, " aviso: "+warning)
}
}
}
for _, line := range verdicts {
fmt.Fprintln(w, line)
}
}
// The names that spec §29.7 asks a reader to warn of, compared by their key
// of R7, so that ".GIT" or "Informe.LNK" warn too.
var (
shortcutExts = keys(".lnk", ".url", ".library-ms", ".searchConnector-ms")
programExts = keys(".exe", ".com", ".bat", ".cmd", ".scr", ".pif", ".msi", ".msp", ".cpl", ".hta",
".jar", ".js", ".jse", ".vbs", ".vbe", ".wsf", ".wsh", ".ps1", ".psm1", ".reg", ".sh", ".command", ".app")
desktopINI = pathrule.Key("desktop.ini")
gitDir = pathrule.Key(".git")
)
func keys(names ...string) map[string]bool {
m := make(map[string]bool, len(names))
for _, n := range names {
m[pathrule.Key(n)] = true
}
return m
}
// risks returns the warnings for path: a Windows shortcut or folder
// setting, a .git folder, a program, or a segment that starts with '-'.
func risks(path string) []string {
var out []string
segs := strings.Split(path, "/")
for i, s := range segs {
k := pathrule.Key(s)
switch {
case k == gitDir && i < len(segs)-1:
out = append(out, "está dentro de una carpeta .git, cuyos ganchos pueden ejecutar órdenes")
case k == gitDir:
out = append(out, "se llama .git y puede apuntar a otro repositorio")
case k == desktopINI:
out = append(out, "es la configuración de una carpeta de Windows")
}
if strings.HasPrefix(s, "-") {
out = append(out, "un nombre que empieza por '-' puede tomarse por una opción en una orden")
}
}
last := segs[len(segs)-1]
if dot := strings.LastIndexByte(last, '.'); dot > 0 {
switch ext := pathrule.Key(last[dot:]); {
case shortcutExts[ext]:
out = append(out, "es un acceso directo de Windows: puede abrir otro programa o una dirección")
case programExts[ext]:
out = append(out, "es un programa o un script: no lo ejecutes sin saber qué hace")
}
}
return out
}
// showNote shows the public note of an inspected capsule as spec v0.11 §24.1
// and §29.7 ask: as text of the creator that nobody has checked, with the
// prefix and the wrapping of any text of the creator, and, before the date,
// the warning that nobody can check who made the capsule or whether it is
// signed. A note that breaks the rules of text is not shown, and the person
// is told. It shows the note even when the capsule fails a check: that is
// when a forged one is most likely.
func showNote(w io.Writer, in *capsule.Inspection) {
if in == nil || in.Header == nil {
return
}
note, ok := in.Header.PublicNote()
if !ok {
for _, e := range in.Header.Noncritical {
if e.ID == extension.NoteID && e.Version == 1 {
fmt.Fprintln(w, " La cápsula lleva una nota pública que no cumple las reglas de texto: no se muestra.")
}
}
return
}
fmt.Fprintln(w, "┌ "+noteTitle)
writeCreator(w, note, outputWidth(w))
fmt.Fprintln(w, "└")
fmt.Fprintln(w, " Nadie puede comprobar antes de la fecha quién creó la cápsula ni si va firmada.")
}

Powered by TurnKey Linux.