package main import ( "context" "crypto" "database/sql" "fmt" "html/template" "log" "net/http" "reflect" "strings" "time" "git.nergen.net/nergen.net-guestbook/sqlc_gb" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" _ "github.com/mattn/go-sqlite3" "github.com/microcosm-cc/bluemonday" ) var templates map[string]*template.Template var ctx context.Context var queries *sqlc_gb.Queries var p *bluemonday.Policy func init() { if templates == nil { templates = make(map[string]*template.Template) } // Do this once for each unique policy, and use the policy for the life of the program // Policy creation/editing is not safe to use in multiple goroutines // p = bluemonday.UGCPolicy() // The policy can then be used to sanitize lots of input and it is safe to use the policy // in multiple goroutines p = bluemonday.NewPolicy() p.AllowElements("b", "code", "strong", "p", "blockquote", "pre", "br", "dt", "dl", "dd", "h1", "h2", "h3", "h4", "em", "sup", "div", "ol", "ul", "li") templates["index.html"] = template.Must(template.New("boilerplate.gohtml").Funcs(template.FuncMap{ "safeHTML": func(b string) template.HTML { return template.HTML(b) }, }).ParseFiles("templates/guestbook-body.gohtml", "templates/boilerplate.gohtml")) } func main() { h1 := func(w http.ResponseWriter, r *http.Request) { posts, err := queries.GetPosts(ctx) if err != nil { log.Fatal(err) } templates["index.html"].Execute(w, posts) } h2 := func(w http.ResponseWriter, r *http.Request) { msg_markdown := r.PostFormValue("msg") msg_html := string(mdToHTML([]byte(msg_markdown))) msg_sanitized_html := p.Sanitize(msg_html) if msg_html != msg_sanitized_html { fmt.Println("message was sanitized") } if strings.TrimSpace(msg_sanitized_html) == "

" { fmt.Println("message turned out empty") return } post, err := queries.AddPost(ctx, sqlc_gb.AddPostParams{ Msg: msg_sanitized_html, // Msg: msg_html, Stamp: time.Now(), }) if err != nil { log.Fatal(err) } templates["index.html"].ExecuteTemplate(w, "rendered-post", post) } db, err := sql.Open("sqlite3", "gb.db") if err != nil { log.Fatal(err) } defer db.Close() ctx = context.Background() queries = sqlc_gb.New(db) if err = queries.TableCreate(ctx); err != nil { log.Fatal(err) } http.HandleFunc("/", h1) http.HandleFunc("/add-post/", h2) log.Fatal(http.ListenAndServe(":8000", nil)) } func mdToHTML(md []byte) []byte { // create markdown parser with extensions extensions := parser.CommonExtensions | parser.NoEmptyLineBeforeBlock | parser.HardLineBreak | parser.Footnotes p := parser.NewWithExtensions(extensions) doc := p.Parse(md) // create HTML renderer with extensions htmlFlags := html.CommonFlags | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) return markdown.Render(doc, renderer) } func Hash(objs ...interface{}) string { digester := crypto.MD5.New() for _, ob := range objs { fmt.Fprint(digester, reflect.TypeOf(ob)) fmt.Fprint(digester, ob) } return fmt.Sprintf("%x", digester.Sum(nil)) }