diff --git a/activityPubSending.go b/activityPubSending.go index 0011ee8..bddbb0e 100644 --- a/activityPubSending.go +++ b/activityPubSending.go @@ -6,7 +6,6 @@ import ( "encoding/gob" "encoding/json" "fmt" - "io" "log" "net/http" "net/url" @@ -120,8 +119,7 @@ func (a *goBlog) apSendSigned(blogIri, to string, activity []byte) error { } defer resp.Body.Close() if !apRequestIsSuccess(resp.StatusCode) { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("signed request failed with status %d: %s", resp.StatusCode, string(body)) + return fmt.Errorf("signed request failed with status %d", resp.StatusCode) } return nil } diff --git a/captcha.go b/captcha.go index d9a6598..7c94213 100644 --- a/captcha.go +++ b/captcha.go @@ -55,7 +55,7 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler { } // Encode original request h, _ := json.Marshal(r.Header) - b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB + b, _ := io.ReadAll(io.LimitReader(r.Body, 20000000)) // Only allow 20 MB _ = r.Body.Close() if len(b) == 0 { // Maybe it's a form diff --git a/editor.go b/editor.go index 2b850a2..cb48acd 100644 --- a/editor.go +++ b/editor.go @@ -36,7 +36,7 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) { defer cancel() for { // Retrieve content - mt, message, err := c.Read(ctx) + mt, message, err := c.Reader(ctx) if err != nil { break } @@ -48,21 +48,24 @@ func (a *goBlog) serveEditorPreview(w http.ResponseWriter, r *http.Request) { if err != nil { break } - a.createMarkdownPreview(w, blog, string(message)) - err = w.Close() - if err != nil { + a.createMarkdownPreview(w, blog, message) + if err = w.Close(); err != nil { break } } } -func (a *goBlog) createMarkdownPreview(w io.Writer, blog string, markdown string) { +func (a *goBlog) createMarkdownPreview(w io.Writer, blog string, markdown io.Reader) { + md, err := io.ReadAll(markdown) + if err != nil { + _, _ = io.WriteString(w, err.Error()) + return + } p := &post{ Blog: blog, - Content: markdown, + Content: string(md), } - err := a.computeExtraPostParameters(p) - if err != nil { + if err = a.computeExtraPostParameters(p); err != nil { _, _ = io.WriteString(w, err.Error()) return } diff --git a/markdown.go b/markdown.go index 53255fa..c1d0785 100644 --- a/markdown.go +++ b/markdown.go @@ -15,7 +15,6 @@ import ( "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" - "go.goblog.app/app/pkgs/bufferpool" ) func (a *goBlog) initMarkdown() { @@ -76,14 +75,6 @@ func (a *goBlog) initMarkdown() { ) } -func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) { - buffer := bufferpool.Get() - err = a.renderMarkdownToWriter(buffer, source, absoluteLinks) - rendered = buffer.Bytes() - bufferpool.Put(buffer) - return -} - func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) { if absoluteLinks { err = a.absoluteMd.Convert([]byte(source), w) @@ -97,24 +88,34 @@ func (a *goBlog) renderText(s string) string { if s == "" { return "" } - h, err := a.renderMarkdown(s, false) + pipeReader, pipeWriter := io.Pipe() + var err error + go func() { + err = a.renderMarkdownToWriter(pipeWriter, s, false) + _ = pipeWriter.Close() + }() + text := htmlTextFromReader(pipeReader) if err != nil { return "" } - return htmlText(string(h)) + return text } func (a *goBlog) renderMdTitle(s string) string { if s == "" { return "" } - buffer := bufferpool.Get() - defer bufferpool.Put(buffer) - err := a.titleMd.Convert([]byte(s), buffer) + pipeReader, pipeWriter := io.Pipe() + var err error + go func() { + err = a.titleMd.Convert([]byte(s), pipeWriter) + _ = pipeWriter.Close() + }() + text := htmlTextFromReader(pipeReader) if err != nil { return "" } - return htmlText(buffer.String()) + return text } // Extensions etc... @@ -177,27 +178,24 @@ func (c *customRenderer) renderImage(w util.BufWriter, source []byte, node ast.N return ast.WalkContinue, nil } n := node.(*ast.Image) - // Make URL absolute if it's relative - destination := util.URLEscape(n.Destination, true) - if c.absoluteLinks && c.publicAddress != "" && bytes.HasPrefix(destination, []byte("/")) { - destination = util.EscapeHTML(append([]byte(c.publicAddress), destination...)) - } else { - destination = util.EscapeHTML(destination) + dest := string(n.Destination) + // Make destination absolute if it's relative + if c.absoluteLinks && c.publicAddress != "" { + resolved, err := resolveURLReferences(c.publicAddress, dest) + if err != nil { + return ast.WalkStop, err + } + if len(resolved) > 0 { + dest = resolved[0] + } } - _, _ = w.WriteString("") - _, _ = w.WriteString("\"") 0 { + imgEls = append(imgEls, "title", string(n.Title)) } - _, _ = w.WriteString(">") + hb.writeElementOpen("img", imgEls...) + hb.writeElementClose("a") return ast.WalkSkipChildren, nil } diff --git a/markdown_test.go b/markdown_test.go index 5289f53..6739297 100644 --- a/markdown_test.go +++ b/markdown_test.go @@ -6,8 +6,17 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.goblog.app/app/pkgs/bufferpool" ) +func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) { + buffer := bufferpool.Get() + err = a.renderMarkdownToWriter(buffer, source, absoluteLinks) + rendered = buffer.Bytes() + bufferpool.Put(buffer) + return +} + func Test_markdown(t *testing.T) { t.Run("Basic Markdown tests", func(t *testing.T) { app := &goBlog{ diff --git a/mediaCompression_test.go b/mediaCompression_test.go index ac2d165..7b6fc63 100644 --- a/mediaCompression_test.go +++ b/mediaCompression_test.go @@ -15,7 +15,7 @@ import ( func Test_compress(t *testing.T) { fakeFileContent := "Test" hash := sha256.New() - io.WriteString(hash, fakeFileContent) + _, _ = io.WriteString(hash, fakeFileContent) fakeSha256 := fmt.Sprintf("%x", hash.Sum(nil)) var uf mediaStorageSaveFunc = func(filename string, f io.Reader) (location string, err error) { diff --git a/micropub.go b/micropub.go index 1fe73c1..cf6be88 100644 --- a/micropub.go +++ b/micropub.go @@ -113,8 +113,8 @@ func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) { a.micropubCreatePostFromForm(w, r) case contenttype.JSON: parsedMfItem := µformatItem{} - b, _ := io.ReadAll(io.LimitReader(r.Body, 10000000)) // 10 MB - if err := json.Unmarshal(b, parsedMfItem); err != nil { + err := json.NewDecoder(io.LimitReader(r.Body, 10000000)).Decode(parsedMfItem) + if err != nil { a.serveError(w, r, err.Error(), http.StatusBadRequest) return } diff --git a/postsFuncs.go b/postsFuncs.go index 80d073d..e655765 100644 --- a/postsFuncs.go +++ b/postsFuncs.go @@ -93,13 +93,14 @@ func (a *goBlog) postSummary(p *post) (summary string) { if summary != "" { return } - if splitted := strings.Split(p.Content, summaryDivider); len(splitted) > 1 { - rendered, _ := a.renderMarkdown(splitted[0], false) - summary = htmlText(string(rendered)) - } else { - rendered, _ := a.renderMarkdown(splitted[0], false) - summary = strings.Split(htmlText(string(rendered)), "\n\n")[0] + splitted := strings.Split(p.Content, summaryDivider) + hasDivider := len(splitted) > 1 + markdown := splitted[0] + summary = a.renderText(markdown) + if !hasDivider { + summary = strings.Split(summary, "\n\n")[0] } + summary = strings.TrimSpace(strings.ReplaceAll(summary, "\n\n", " ")) return } diff --git a/render.go b/render.go index f6e5c12..b793ef5 100644 --- a/render.go +++ b/render.go @@ -1,9 +1,9 @@ package main import ( + "io" "net/http" - "go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/contenttype" ) @@ -39,13 +39,14 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st // Write status code w.WriteHeader(statusCode) // Render - buf := bufferpool.Get() - mw := a.min.Writer(contenttype.HTML, buf) - hb := newHtmlBuilder(mw) - f(hb, data) - _ = mw.Close() - _, _ = buf.WriteTo(w) - bufferpool.Put(buf) + pipeReader, pipeWriter := io.Pipe() + go func() { + mw := a.min.Writer(contenttype.HTML, pipeWriter) + f(newHtmlBuilder(mw), data) + _ = mw.Close() + _ = pipeWriter.Close() + }() + _, _ = io.Copy(w, pipeReader) } func (a *goBlog) checkRenderData(r *http.Request, data *renderData) { diff --git a/templateAssets.go b/templateAssets.go index 2b31eba..0143a21 100644 --- a/templateAssets.go +++ b/templateAssets.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "crypto/sha256" "fmt" "io" @@ -34,15 +33,11 @@ func (a *goBlog) initTemplateAssets() error { return err } // Compile asset and close file - compiled, err := a.compileAsset(path, file) + err = a.compileAsset(strings.TrimPrefix(path, assetsFolder+"/"), file) _ = file.Close() if err != nil { return err } - // Add to map - if compiled != "" { - a.assetFileNames[strings.TrimPrefix(path, assetsFolder+"/")] = compiled - } } return nil }); err != nil { @@ -55,7 +50,7 @@ func (a *goBlog) initTemplateAssets() error { return nil } -func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) { +func (a *goBlog) compileAsset(name string, read io.Reader) error { ext := path.Ext(name) switch ext { case ".js": @@ -69,16 +64,18 @@ func (a *goBlog) compileAsset(name string, read io.Reader) (string, error) { hash := sha256.New() body, err := io.ReadAll(io.TeeReader(read, hash)) if err != nil { - return "", err + return err } // File name compiledFileName := fmt.Sprintf("%x%s", hash.Sum(nil), ext) - // Create struct + // Save file a.assetFiles[compiledFileName] = &assetFile{ contentType: mime.TypeByExtension(ext), body: body, } - return compiledFileName, err + // Save mapping of original file name to compiled file name + a.assetFileNames[name] = compiledFileName + return err } // Function for templates @@ -113,22 +110,15 @@ func (a *goBlog) initChromaCSS() error { return nil } // Initialize the style - chromaStyleBuilder := chromaGoBlogStyle.Builder() - chromaStyle, err := chromaStyleBuilder.Build() + chromaStyle, err := chromaGoBlogStyle.Builder().Build() if err != nil { return err } - // Write the CSS to a buffer - var cssBuffer bytes.Buffer - if err = chromahtml.New(chromahtml.ClassPrefix("c-")).WriteCSS(&cssBuffer, chromaStyle); err != nil { - return err - } - // Compile asset - compiled, err := a.compileAsset(chromaPath, &cssBuffer) - if err != nil { - return err - } - // Add to map - a.assetFileNames[chromaPath] = compiled - return nil + // Generate and minify CSS + pipeReader, pipeWriter := io.Pipe() + go func() { + _ = chromahtml.New(chromahtml.ClassPrefix("c-")).WriteCSS(pipeWriter, chromaStyle) + _ = pipeWriter.Close() + }() + return a.compileAsset(chromaPath, pipeReader) } diff --git a/utils.go b/utils.go index 5a28a3e..3fa809a 100644 --- a/utils.go +++ b/utils.go @@ -221,16 +221,18 @@ func mBytesString(size int64) string { } func htmlText(s string) string { + return htmlTextFromReader(strings.NewReader(s)) +} + +func htmlTextFromReader(r io.Reader) string { // Build policy to only allow a subset of HTML tags textPolicy := bluemonday.StrictPolicy() textPolicy.AllowElements("h1", "h2", "h3", "h4", "h5", "h6") // Headers textPolicy.AllowElements("p") // Paragraphs textPolicy.AllowElements("ol", "ul", "li") // Lists textPolicy.AllowElements("blockquote") // Blockquotes - // Filter HTML tags - htmlBuf := textPolicy.SanitizeReader(strings.NewReader(s)) - // Read HTML into document - doc, _ := goquery.NewDocumentFromReader(htmlBuf) + // Read filtered HTML into document + doc, _ := goquery.NewDocumentFromReader(textPolicy.SanitizeReader(r)) var text strings.Builder if bodyChild := doc.Find("body").Children(); bodyChild.Length() > 0 { // Input was real HTML, so build the text from the body