/* SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine Copyright (C) 2020-2021 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ // Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine package main import ( "bytes" "crypto/sha1" "encoding/hex" "flag" "fmt" "io/ioutil" "log" "mime" "net/mail" "os" "os/exec" "regexp" "strconv" "strings" "syscall" "time" "go.cypherpunks.ru/recfile" "go.stargrave.org/sgblog" ) var hashFinder = regexp.MustCompile("([0-9a-f]{40})") // Remove various whitespaces and excess lines, because git-notes-add // will remove and we have to know exact bytes count func cleanupBody(body string) []string { lines := strings.Split(string(body), "\n") for i, line := range lines { line = strings.ReplaceAll(line, " ", " ") line = strings.TrimRight(line, " \r") lines[i] = line } for lines[0] == "" { lines = lines[1:] } for lines[len(lines)-1] == "" { lines = lines[:len(lines)-1] } withoutDups := make([]string, 0, len(lines)) emptyMet := false for _, line := range lines { if line == "" { if emptyMet { continue } emptyMet = true } else { emptyMet = false } withoutDups = append(withoutDups, line) } return withoutDups } func main() { gitCmd := flag.String("git-cmd", "/usr/local/bin/git", "Path to git executable") gitDir := flag.String("git-dir", "", "Path to .git repository") notesRef := flag.String("ref", "comments", "notes reference name") umask := flag.String("umask", "027", "umask value") dryRun := flag.Bool("dryrun", false, "Show comment, do not add") committerEmail := flag.String( "committer-email", "comment@blog.example.com", "Git committer's email", ) flag.Parse() uid := syscall.Geteuid() if err := syscall.Setuid(uid); err != nil { log.Fatal(err) } umaskInt, err := strconv.ParseUint(*umask, 8, 16) if err != nil { panic(err) } syscall.Umask(int(umaskInt)) msg, err := mail.ReadMessage(os.Stdin) if err != nil { log.Fatal(err) } subj, r, err := parseEmail(msg) if err != nil { log.Fatal(err) } body, err := ioutil.ReadAll(r) if err != nil { log.Fatal(err) } from := msg.Header.Get("From") if from == "" { log.Fatal("From is missing") } if len(body) == 0 { log.Fatal("no body") } from, err = new(mime.WordDecoder).DecodeHeader(from) if err != nil { log.Fatal(err) } subj = hashFinder.FindString(subj) if subj == "" { log.Fatal("no commit hash found in subject") } if h, err := hex.DecodeString(subj); err != nil || len(h) != sha1.Size { os.Exit(0) } fromCols := strings.Fields(from) if len(fromCols) == 1 { if idx := strings.Index(from, "@"); idx != -1 { from = strings.Trim(from[:idx], "<>") } } else { from = strings.Join(fromCols[:len(fromCols)-1], " ") } cmd := exec.Command( *gitCmd, "--git-dir", *gitDir, "notes", "--ref", *notesRef, "show", subj, ) note, _ := cmd.Output() note = bytes.TrimRight(note, "\r\n") buf := bytes.NewBuffer(note) recfileW := recfile.NewWriter(buf) if _, err = recfileW.RecordStart(); err != nil { log.Fatal(err) } // We trimmed newline, so have to start record twice if _, err = recfileW.RecordStart(); err != nil { log.Fatal(err) } if _, err = recfileW.WriteFields( recfile.Field{Name: "From", Value: from}, recfile.Field{Name: "Date", Value: time.Now().UTC().Format(sgblog.WhenFmt)}, ); err != nil { log.Fatal(err) } if _, err = recfileW.WriteFieldMultiline( "Body", append([]string{""}, cleanupBody(string(body))...), ); err != nil { log.Fatal(err) } if *dryRun { fmt.Print(buf.String()) os.Exit(0) } cmd = exec.Command( *gitCmd, "--git-dir", *gitDir, "notes", "--ref", *notesRef, "add", "-F", "-", "-f", subj, ) cmd.Env = append( cmd.Env, "GIT_AUTHOR_NAME=SGBlog "+sgblog.Version, "GIT_AUTHOR_EMAIL="+*committerEmail, "GIT_COMMITTER_NAME=SGBlog "+sgblog.Version, "GIT_COMMITTER_EMAIL="+*committerEmail, ) cmd.Stdin = buf if err = cmd.Run(); err != nil { log.Fatal(err) } }