Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"g.activething.com/go/DateKeys/capsule"
|
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
"g.activething.com/go/DateKeys/extension"
|
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"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)"
|
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// 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, "└")
|
|
|
|
|
}
|
|
|
|
|
}
|
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
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
|
|
|
|
|
}
|
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
|
|
|
|
|
// 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.")
|
|
|
|
|
}
|