-/*
-SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
-Copyright (C) 2020-2022 Sergey Matveev <stargrave@stargrave.org>
-
-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 <http://www.gnu.org/licenses/>.
-*/
+// SGBlog -- Git-backed CGI/UCSPI blogging/phlogging/gemlogging engine
+// Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
- "compress/gzip"
"crypto/sha1"
_ "embed"
- "encoding/hex"
+ "encoding/base64"
"encoding/xml"
"errors"
"fmt"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
+ "github.com/klauspost/compress/zstd"
+ "github.com/vorlif/spreak"
"go.stargrave.org/sgblog"
"go.stargrave.org/sgblog/cmd/sgblog/atom"
- "golang.org/x/crypto/blake2b"
+ "lukechampine.com/blake3"
)
const (
Title string
LinesNum int
CommentsNum int
+ ImagesNum int
DomainURLs []string
Topics []string
}
}
func etagString(etag hash.Hash) string {
- return `"` + hex.EncodeToString(etag.Sum(nil)) + `"`
+ return `"` + base64.RawURLEncoding.EncodeToString(etag.Sum(nil)) + `"`
}
func urlParse(what string) *url.URL {
return lineURLize(urlPrefix.(string), line.(string))
}
-func startHeader(etag hash.Hash, gziped bool) string {
+func startHeader(etag hash.Hash, zstded bool) string {
lines := []string{
"Content-Type: text/html; charset=utf-8",
"ETag: " + etagString(etag),
}
- if gziped {
- lines = append(lines, "Content-Encoding: gzip")
+ if zstded {
+ lines = append(lines, "Content-Encoding: zstd")
}
lines = append(lines, "")
lines = append(lines, "")
if err != nil {
log.Fatalln(err)
}
+ initLocalizer(cfg.Lang)
pathInfo := os.Getenv("PATH_INFO")
if len(pathInfo) == 0 {
makeErr(err, http.StatusBadRequest)
}
- etagHash, err := blake2b.New256(nil)
- if err != nil {
- panic(err)
- }
+ etagHash := blake3.New(32, nil)
for _, s := range []string{
"SGBLOG",
sgblog.Version,
- cfg.GitPath,
cfg.Branch,
cfg.Title,
cfg.URLPrefix,
cfg.CommentsNotesRef,
cfg.CommentsEmail,
}
- for _, gitURL := range cfg.GitURLs {
- etagHashForWeb = append(etagHashForWeb, gitURL)
- }
+ etagHashForWeb = append(etagHashForWeb, cfg.GitURLs...)
headHash, err := initRepo(cfg)
if err != nil {
var outBuf bytes.Buffer
var out io.Writer
out = &outBuf
- var gzipWriter *gzip.Writer
+ var zstdWriter *zstd.Encoder
acceptEncoding := os.Getenv("HTTP_ACCEPT_ENCODING")
for _, encoding := range strings.Split(acceptEncoding, ", ") {
- if encoding == "gzip" {
- gzipWriter = gzip.NewWriter(&outBuf)
- out = gzipWriter
+ if encoding == "zstd" {
+ zstdWriter, err = zstd.NewWriter(&outBuf, zstd.WithEncoderLevel(zstd.SpeedDefault))
+ if err != nil {
+ panic(err)
+ }
+ out = zstdWriter
}
}
entry.DomainURLs = append(entry.DomainURLs, makeA(line, u.Host))
}
entry.CommentsNum = len(sgblog.ParseComments(entry.CommentsRaw))
+ entry.ImagesNum = len(listImgs(cfg, entry.Commit.Hash))
entry.Topics = sgblog.ParseTopics(entry.TopicsRaw)
entries[i] = entry
}
if offsetPrev < 0 {
offsetPrev = 0
}
- os.Stdout.Write([]byte(startHeader(etagHash, gzipWriter != nil)))
+ os.Stdout.Write([]byte(startHeader(etagHash, zstdWriter != nil)))
err = TmplHTMLIndex.Execute(out, struct {
+ T *spreak.Localizer
Version string
Cfg *Cfg
Topic string
LogEnded bool
Entries []TableEntry
}{
+ T: localizer,
Version: sgblog.Version,
Cfg: cfg,
Topic: topic,
}
os.Stdout.WriteString("Content-Type: text/plain; charset=utf-8\n")
os.Stdout.WriteString("ETag: " + etagString(etagHash) + "\n")
- if gzipWriter != nil {
- os.Stdout.WriteString("Content-Encoding: gzip\n")
- gzipWriter.Close()
+ if zstdWriter != nil {
+ os.Stdout.WriteString("Content-Encoding: zstd\n")
+ zstdWriter.Close()
}
os.Stdout.WriteString("\n")
os.Stdout.Write(outBuf.Bytes())
} else {
title = fmt.Sprintf("%s (topic: %s)", cfg.Title, topic)
}
- idHasher, err := blake2b.New256(nil)
- if err != nil {
- panic(err)
- }
+ idHasher := blake3.New(32, nil)
idHasher.Write([]byte("ATOM POSTS"))
idHasher.Write([]byte(cfg.AtomId))
idHasher.Write([]byte(topic))
htmlized = append(htmlized, lineURLize(cfg.AtomBaseURL+cfg.URLPrefix, l))
}
htmlized = append(htmlized, "</pre>")
+ links := []atom.Link{{
+ Rel: "alternate",
+ Href: cfg.AtomBaseURL + cfg.URLPrefix + "/" + commit.Hash.String(),
+ }}
+ for _, img := range listImgs(cfg, commit.Hash) {
+ links = append(links, atom.Link{
+ Rel: "enclosure",
+ Href: "http://" + cfg.ImgDomain + "/" + img.Path,
+ Type: img.Typ,
+ Length: uint(img.Size),
+ })
+ }
feed.Entry = append(feed.Entry, &atom.Entry{
- Title: lines[0],
- ID: "urn:uuid:" + bytes2uuid(commit.Hash[:]),
- Link: []atom.Link{{
- Rel: "alternate",
- Href: cfg.AtomBaseURL + cfg.URLPrefix + "/" + commit.Hash.String(),
- }},
+ Title: lines[0],
+ ID: "urn:uuid:" + bytes2uuid(commit.Hash[:]),
+ Link: links,
Published: atom.Time(commit.Author.When),
Updated: atom.Time(commit.Author.When),
Summary: &atom.Text{Type: "text", Body: lines[0]},
etagHash.Write([]byte("ATOM COMMENTS"))
etagHash.Write(commit.Hash[:])
checkETag(etagHash)
- idHasher, err := blake2b.New256(nil)
- if err != nil {
- panic(err)
- }
+ idHasher := blake3.New(32, nil)
idHasher.Write([]byte("ATOM COMMENTS"))
idHasher.Write([]byte(cfg.AtomId))
feed := atom.Feed{
body: lines[3:],
})
}
- idHasher, err := blake2b.New256(nil)
- if err != nil {
- panic(err)
- }
+ idHasher := blake3.New(32, nil)
idHasher.Write([]byte("ATOM COMMENTS"))
idHasher.Write(commit.Hash[:])
feed := atom.Feed{
notesLines = strings.Split(string(notesRaw), "\n")
}
- os.Stdout.Write([]byte(startHeader(etagHash, gzipWriter != nil)))
+ os.Stdout.Write([]byte(startHeader(etagHash, zstdWriter != nil)))
err = TmplHTMLEntry.Execute(out, struct {
+ T *spreak.Localizer
Version string
Cfg *Cfg
Title string
NoteLines []string
Comments []CommentEntry
Topics []string
+ Imgs []Img
}{
+ T: localizer,
Version: sgblog.Version,
Cfg: cfg,
Title: title,
NoteLines: notesLines,
Comments: comments,
Topics: sgblog.ParseTopics(topicsRaw),
+ Imgs: listImgs(cfg, commit.Hash),
})
if err != nil {
makeErr(err, http.StatusInternalServerError)
makeErr(errors.New("unknown URL action"), http.StatusNotFound)
}
out.Write([]byte("</body></html>\n"))
- if gzipWriter != nil {
- gzipWriter.Close()
+ if zstdWriter != nil {
+ zstdWriter.Close()
}
os.Stdout.Write(outBuf.Bytes())
return
AtomFinish:
os.Stdout.WriteString("Content-Type: application/atom+xml; charset=utf-8\n")
os.Stdout.WriteString("ETag: " + etagString(etagHash) + "\n")
- if gzipWriter != nil {
- os.Stdout.WriteString("Content-Encoding: gzip\n")
- gzipWriter.Close()
+ if zstdWriter != nil {
+ os.Stdout.WriteString("Content-Encoding: zstd\n")
+ zstdWriter.Close()
}
os.Stdout.WriteString("\n")
os.Stdout.Write(outBuf.Bytes())