]> Sergey Matveev's repositories - sgblog.git/blob - cmd/sgblog-comment-add/main.go
Configurable comments commits author
[sgblog.git] / cmd / sgblog-comment-add / main.go
1 /*
2 SGBlog -- Git-based CGI blogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Affero General Public License as
7 published by the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Git-based CGI blogging engine email to comments adder
19 package main
20
21 import (
22         "bytes"
23         "crypto/sha1"
24         "encoding/hex"
25         "flag"
26         "fmt"
27         "io/ioutil"
28         "log"
29         "net/mail"
30         "os"
31         "os/exec"
32         "strconv"
33         "strings"
34         "syscall"
35         "time"
36
37         "go.cypherpunks.ru/netstring/v2"
38         "go.stargrave.org/sgblog"
39 )
40
41 func main() {
42         gitCmd := flag.String("git-cmd", "/usr/local/bin/git", "Path to git executable")
43         gitDir := flag.String("git-dir", "", "Path to .git repository")
44         notesRef := flag.String("ref", "comments", "notes reference name")
45         umask := flag.String("umask", "027", "umask value")
46         dryRun := flag.Bool("dryrun", false, "Show comment, do not add")
47         committerEmail := flag.String(
48                 "committer-email",
49                 "comment@blog.example.com",
50                 "Git committer's email",
51         )
52         flag.Parse()
53         uid := syscall.Geteuid()
54         if err := syscall.Setuid(uid); err != nil {
55                 log.Fatal(err)
56         }
57         umaskInt, err := strconv.ParseUint(*umask, 8, 16)
58         if err != nil {
59                 panic(err)
60         }
61         syscall.Umask(int(umaskInt))
62
63         msg, err := mail.ReadMessage(os.Stdin)
64         if err != nil {
65                 log.Fatal(err)
66         }
67         subj, r, err := parseEmail(msg)
68         if err != nil {
69                 log.Fatal(err)
70         }
71         body, err := ioutil.ReadAll(r)
72         if err != nil {
73                 log.Fatal(err)
74         }
75         from := msg.Header.Get("From")
76         if from == "" {
77                 log.Fatal("From is missing")
78         }
79         if len(body) == 0 {
80                 log.Fatal("no body")
81         }
82
83         subj = strings.TrimPrefix(subj, "Re: ")
84         if h, err := hex.DecodeString(subj); err != nil || len(h) != sha1.Size {
85                 os.Exit(0)
86         }
87         fromCols := strings.Fields(from)
88         if len(fromCols) == 1 {
89                 if idx := strings.Index(from, "@"); idx != -1 {
90                         from = strings.Trim(from[:idx], "<>")
91                 }
92         } else {
93                 from = strings.Join(fromCols[:len(fromCols)-1], " ")
94         }
95
96         cmd := exec.Command(
97                 *gitCmd, "--git-dir", *gitDir,
98                 "notes", "--ref", *notesRef, "show", subj,
99         )
100         note, _ := cmd.Output()
101         note = bytes.TrimSuffix(note, []byte{'\n'})
102
103         // Remove trailing whitespaces, because git-notes-add will remove
104         // them anyway, and we have to know exact bytes count
105         lines := strings.Split(string(body), "\n")
106         for i, line := range lines {
107                 lines[i] = strings.TrimRight(line, " ")
108         }
109         for lines[len(lines)-1] == "" {
110                 lines = lines[:len(lines)-1]
111         }
112
113         buf := bytes.NewBuffer(note)
114         w := netstring.NewWriter(buf)
115         w.WriteChunk([]byte(fmt.Sprintf(
116                 "From: %s\nDate: %s\n\n%s",
117                 from,
118                 time.Now().UTC().Format(sgblog.WhenFmt),
119                 strings.Join(lines, "\n"),
120         )))
121
122         if *dryRun {
123                 fmt.Print(buf.String())
124                 os.Exit(0)
125         }
126
127         cmd = exec.Command(
128                 *gitCmd, "--git-dir", *gitDir,
129                 "notes", "--ref", *notesRef, "add",
130                 "-F", "-", "-f", subj,
131         )
132         cmd.Env = append(
133                 cmd.Env,
134                 "GIT_AUTHOR_NAME=SGBlog "+sgblog.Version,
135                 "GIT_AUTHOR_EMAIL="+*committerEmail,
136                 "GIT_COMMITTER_NAME=SGBlog "+sgblog.Version,
137                 "GIT_COMMITTER_EMAIL="+*committerEmail,
138         )
139         cmd.Stdin = buf
140         if err = cmd.Run(); err != nil {
141                 log.Fatal(err)
142         }
143 }