2 SGBlog -- Git-backed CGI/inetd blogging/phlogging engine
3 Copyright (C) 2020 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
18 // Git-backed CGI/inetd blogging/phlogging engine
39 "go.cypherpunks.ru/netstring/v2"
40 "go.stargrave.org/sgblog"
43 var hashFinder = regexp.MustCompile("([0-9a-f]{40})")
45 // Remove various whitespaces and excess lines, because git-notes-add
46 // will remove and we have to know exact bytes count
47 func cleanupBody(body string) string {
48 lines := strings.Split(string(body), "\n")
49 for i, line := range lines {
50 line = strings.ReplaceAll(line, " ", " ")
51 line = strings.TrimRight(line, " \r")
57 for lines[len(lines)-1] == "" {
58 lines = lines[:len(lines)-1]
60 withoutDups := make([]string, 0, len(lines))
62 for _, line := range lines {
71 withoutDups = append(withoutDups, line)
73 return strings.Join(withoutDups, "\n")
77 gitCmd := flag.String("git-cmd", "/usr/local/bin/git", "Path to git executable")
78 gitDir := flag.String("git-dir", "", "Path to .git repository")
79 notesRef := flag.String("ref", "comments", "notes reference name")
80 umask := flag.String("umask", "027", "umask value")
81 dryRun := flag.Bool("dryrun", false, "Show comment, do not add")
82 committerEmail := flag.String(
84 "comment@blog.example.com",
85 "Git committer's email",
88 uid := syscall.Geteuid()
89 if err := syscall.Setuid(uid); err != nil {
92 umaskInt, err := strconv.ParseUint(*umask, 8, 16)
96 syscall.Umask(int(umaskInt))
98 msg, err := mail.ReadMessage(os.Stdin)
102 subj, r, err := parseEmail(msg)
106 body, err := ioutil.ReadAll(r)
110 from := msg.Header.Get("From")
112 log.Fatal("From is missing")
117 from, err = new(mime.WordDecoder).DecodeHeader(from)
122 subj = hashFinder.FindString(subj)
124 log.Fatal("no commit hash found in subject")
126 if h, err := hex.DecodeString(subj); err != nil || len(h) != sha1.Size {
129 fromCols := strings.Fields(from)
130 if len(fromCols) == 1 {
131 if idx := strings.Index(from, "@"); idx != -1 {
132 from = strings.Trim(from[:idx], "<>")
135 from = strings.Join(fromCols[:len(fromCols)-1], " ")
139 *gitCmd, "--git-dir", *gitDir,
140 "notes", "--ref", *notesRef, "show", subj,
142 note, _ := cmd.Output()
143 note = bytes.TrimRight(note, "\r\n")
145 buf := bytes.NewBuffer(note)
146 w := netstring.NewWriter(buf)
147 w.WriteChunk([]byte(fmt.Sprintf(
148 "From: %s\nDate: %s\n\n%s",
150 time.Now().UTC().Format(sgblog.WhenFmt),
151 cleanupBody(string(body)),
155 fmt.Print(buf.String())
160 *gitCmd, "--git-dir", *gitDir,
161 "notes", "--ref", *notesRef, "add",
162 "-F", "-", "-f", subj,
166 "GIT_AUTHOR_NAME=SGBlog "+sgblog.Version,
167 "GIT_AUTHOR_EMAIL="+*committerEmail,
168 "GIT_COMMITTER_NAME=SGBlog "+sgblog.Version,
169 "GIT_COMMITTER_EMAIL="+*committerEmail,
172 if err = cmd.Run(); err != nil {