]> Sergey Matveev's repositories - sgblog.git/blob - cmd/sgblog/main.go
05654c0760942625cf2c8fcd04be124c60379bba
[sgblog.git] / cmd / sgblog / main.go
1 /*
2 SGBlog -- Git-backed CGI/inetd blogging/phlogging 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-backed CGI/inetd blogging/phlogging engine
19 package main
20
21 import (
22         "bytes"
23         "crypto/sha1"
24         "fmt"
25         "io/ioutil"
26         "os"
27         "regexp"
28         "strings"
29
30         "go.cypherpunks.ru/netstring/v2"
31         "gopkg.in/src-d/go-git.v4"
32         "gopkg.in/src-d/go-git.v4/plumbing"
33         "gopkg.in/src-d/go-git.v4/plumbing/object"
34 )
35
36 const (
37         PageEntries = 50
38 )
39
40 var (
41         sha1DigestRe = regexp.MustCompilePOSIX(fmt.Sprintf("([0-9a-f]{%d,%d})", sha1.Size*2, sha1.Size*2))
42         repo         *git.Repository
43         notesTree    *object.Tree
44         commentsRef  *plumbing.Reference
45         commentsTree *object.Tree
46 )
47
48 type Cfg struct {
49         GitPath string
50         Branch  string
51         Title   string
52
53         URLPrefix string
54
55         AtomBaseURL string
56         AtomId      string
57         AtomAuthor  string
58
59         CSS       string
60         Webmaster string
61         AboutURL  string
62         GitURLs   []string
63
64         CommentsNotesRef string
65         CommentsEmail    string
66
67         GopherDomain string
68 }
69
70 func msgSplit(msg string) []string {
71         lines := strings.Split(msg, "\n")
72         lines = lines[:len(lines)-1]
73         if len(lines) < 3 {
74                 lines = []string{lines[0], "", ""}
75         }
76         return lines
77 }
78
79 func getNote(tree *object.Tree, what plumbing.Hash) []byte {
80         if tree == nil {
81                 return nil
82         }
83         var entry *object.TreeEntry
84         var err error
85         paths := make([]string, 3)
86         paths[0] = what.String()
87         paths[1] = paths[0][:2] + "/" + paths[0][2:]
88         paths[2] = paths[1][:4+1] + "/" + paths[1][4+1:]
89         for _, p := range paths {
90                 entry, err = tree.FindEntry(p)
91                 if err == nil {
92                         break
93                 }
94         }
95         if entry == nil {
96                 return nil
97         }
98         blob, err := repo.BlobObject(entry.Hash)
99         if err != nil {
100                 return nil
101         }
102         r, err := blob.Reader()
103         if err != nil {
104                 return nil
105         }
106         data, err := ioutil.ReadAll(r)
107         if err != nil {
108                 return nil
109         }
110         return bytes.TrimSuffix(data, []byte{'\n'})
111 }
112
113 func parseComments(data []byte) []string {
114         comments := []string{}
115         nsr := netstring.NewReader(bytes.NewReader(data))
116         for {
117                 if _, err := nsr.Next(); err != nil {
118                         break
119                 }
120                 if comment, err := ioutil.ReadAll(nsr); err == nil {
121                         comments = append(comments, string(comment))
122                 }
123         }
124         return comments
125 }
126
127 func initRepo(cfg *Cfg) (*plumbing.Hash, error) {
128         var err error
129         repo, err = git.PlainOpen(cfg.GitPath)
130         if err != nil {
131                 return nil, err
132         }
133         head, err := repo.Reference(plumbing.ReferenceName(cfg.Branch), false)
134         if err != nil {
135                 return nil, err
136         }
137         headHash := head.Hash()
138         if notes, err := repo.Notes(); err == nil {
139                 var notesRef *plumbing.Reference
140                 notes.ForEach(func(ref *plumbing.Reference) error {
141                         switch string(ref.Name()) {
142                         case "refs/notes/commits":
143                                 notesRef = ref
144                         case cfg.CommentsNotesRef:
145                                 commentsRef = ref
146                         }
147                         return nil
148                 })
149                 if notesRef != nil {
150                         if commentsCommit, err := repo.CommitObject(notesRef.Hash()); err == nil {
151                                 notesTree, _ = commentsCommit.Tree()
152                         }
153                 }
154                 if commentsRef != nil {
155                         if commentsCommit, err := repo.CommitObject(commentsRef.Hash()); err == nil {
156                                 commentsTree, _ = commentsCommit.Tree()
157                         }
158                 }
159         }
160         return &headHash, nil
161 }
162
163 func main() {
164         if len(os.Args) == 3 && os.Args[1] == "-gopher" {
165                 serveGopher()
166         } else {
167                 serveHTTP()
168         }
169 }