diff --git a/activityStreams.go b/activityStreams.go index 6883908..d66b53c 100644 --- a/activityStreams.go +++ b/activityStreams.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/x509" "encoding/json" "encoding/pem" @@ -12,12 +13,14 @@ import ( var asContext = []string{"https://www.w3.org/ns/activitystreams"} +const asRequestKey requestContextKey = "asRequest" + func manipulateAsPath(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if ap := appConfig.ActivityPub; ap != nil && ap.Enabled { if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) { - // Is ActivityStream, add ".as" to differentiate cache and also trigger as function - r.URL.Path += ".as" + next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), asRequestKey, true))) + return } } next.ServeHTTP(rw, r) diff --git a/authentication.go b/authentication.go index f0ecd41..315b130 100644 --- a/authentication.go +++ b/authentication.go @@ -96,36 +96,40 @@ func checkIsLogin(next http.Handler) http.Handler { } func checkLogin(w http.ResponseWriter, r *http.Request) bool { - if r.Method == http.MethodPost && - r.Header.Get(contentType) == contentTypeWWWForm && - r.FormValue("loginaction") == "login" { - // Do original request - loginbody, _ := base64.StdEncoding.DecodeString(r.FormValue("loginbody")) - req, _ := http.NewRequest(r.FormValue("loginmethod"), r.RequestURI, bytes.NewReader(loginbody)) - // Copy original headers - loginheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("loginheaders")) - var headers http.Header - _ = json.Unmarshal(loginheaders, &headers) - for k, v := range headers { - req.Header[k] = v - } - // Check credential - if checkCredentials(r.FormValue("username"), r.FormValue("password")) { - tokenCookie, err := createTokenCookie(r.FormValue("username")) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return true - } - // Add cookie to original request - req.AddCookie(tokenCookie) - // Send cookie - http.SetCookie(w, tokenCookie) - } - // Serve original request - d.ServeHTTP(w, req) - return true + if r.Method != http.MethodPost { + return false } - return false + if r.Header.Get(contentType) != contentTypeWWWForm { + return false + } + if r.FormValue("loginaction") != "login" { + return false + } + // Do original request + loginbody, _ := base64.StdEncoding.DecodeString(r.FormValue("loginbody")) + req, _ := http.NewRequest(r.FormValue("loginmethod"), r.RequestURI, bytes.NewReader(loginbody)) + // Copy original headers + loginheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("loginheaders")) + var headers http.Header + _ = json.Unmarshal(loginheaders, &headers) + for k, v := range headers { + req.Header[k] = v + } + // Check credential + if checkCredentials(r.FormValue("username"), r.FormValue("password")) { + tokenCookie, err := createTokenCookie(r.FormValue("username")) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return true + } + // Add cookie to original request + req.AddCookie(tokenCookie) + // Send cookie + http.SetCookie(w, tokenCookie) + } + // Serve original request + d.ServeHTTP(w, req) + return true } type authClaims struct { diff --git a/cache.go b/cache.go index f3b7658..c25813f 100644 --- a/cache.go +++ b/cache.go @@ -7,7 +7,9 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "strconv" + "strings" "time" "github.com/araddon/dateparse" @@ -81,7 +83,23 @@ func cacheMiddleware(next http.Handler) http.Handler { } func cacheKey(r *http.Request) string { - return r.URL.String() + def := cacheURLString(r.URL) + // Special cases + if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest { + return "as-" + def + } + // Default + return def +} + +func cacheURLString(u *url.URL) string { + var buf strings.Builder + _, _ = buf.WriteString(u.EscapedPath()) + if u.RawQuery != "" { + _ = buf.WriteByte('?') + _, _ = buf.WriteString(u.RawQuery) + } + return buf.String() } func setCacheHeaders(w http.ResponseWriter, cache *cacheItem) { diff --git a/config.go b/config.go index 759e89d..6ba07fa 100644 --- a/config.go +++ b/config.go @@ -25,7 +25,6 @@ type config struct { type configServer struct { Logging bool `mapstructure:"logging"` LogFile string `mapstructure:"logFile"` - Debug bool `mapstructure:"Debug"` Port int `mapstructure:"port"` PublicAddress string `mapstructure:"publicAddress"` ShortPublicAddress string `mapstructure:"shortPublicAddress"` @@ -204,7 +203,6 @@ func initConfig() error { // Defaults viper.SetDefault("server.logging", false) viper.SetDefault("server.logFile", "data/access.log") - viper.SetDefault("server.debug", false) viper.SetDefault("server.port", 8080) viper.SetDefault("server.publicAddress", "http://localhost:8080") viper.SetDefault("server.publicHttps", false) diff --git a/go.mod b/go.mod index 6cf005c..32f95d7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/caddyserver/certmagic v0.12.0 github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/go-chi/chi v1.5.2 + github.com/go-chi/chi v1.5.3 github.com/go-fed/httpsig v1.1.0 github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gofrs/flock v0.8.0 // indirect @@ -33,7 +33,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.6 github.com/mholt/acmez v0.1.3 // indirect github.com/microcosm-cc/bluemonday v1.0.4 - github.com/miekg/dns v1.1.38 // indirect + github.com/miekg/dns v1.1.39 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/pelletier/go-toml v1.8.1 // indirect github.com/smartystreets/assertions v1.2.0 // indirect @@ -53,9 +53,9 @@ require ( golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/mod v0.4.1 // indirect - golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d // indirect + golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect + golang.org/x/sys v0.0.0-20210223212115-eede4237b368 // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/text v0.3.5 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 0d5f3e6..6c203a2 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-chi/chi v1.5.2 h1:YcLIBANL4OTaAOcTdp//sskGa0yGACQMCtbnr7YEn0Q= -github.com/go-chi/chi v1.5.2/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= +github.com/go-chi/chi v1.5.3 h1:+DVDS9/D3MTbEu3WrrH3oz9oP6PlSPSNj8LLw3X17yU= +github.com/go-chi/chi v1.5.3/go.mod h1:Q8xfe6s3fjZyMr8ZTv5jL+vxhVaFyCq2s+RvSfzTD0E= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -205,8 +205,8 @@ github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDE github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw= -github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.39 h1:6dRfDGnHiXOMmTZkwWANy7bBXXlKls5Qu+pn+Ue0TLo= +github.com/miekg/dns v1.1.39/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -371,8 +371,8 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -405,8 +405,8 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210223212115-eede4237b368 h1:fDE3p0qf2V1co1vfj3/o87Ps8Hq6QTGNxJ5Xe7xSp80= +golang.org/x/sys v0.0.0-20210223212115-eede4237b368/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= diff --git a/http.go b/http.go index 71190e2..6ff0b0e 100644 --- a/http.go +++ b/http.go @@ -100,11 +100,6 @@ func buildHandler() (http.Handler, error) { r.Use(checkIsCaptcha) r.Use(checkLoggedIn) - // Profiler - if appConfig.Server.Debug { - r.Mount("/debug", middleware.Profiler()) - } - // Micropub r.Route(micropubPath, func(mpRouter chi.Router) { mpRouter.Use(checkIndieAuth) diff --git a/main.go b/main.go index c5aeea0..f954760 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,31 @@ package main import ( + "flag" "log" "os" "os/signal" + "runtime/pprof" "syscall" ) +var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") +var memprofile = flag.String("memprofile", "", "write memory profile to `file`") + func main() { + // Init CPU profiling + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal("could not create CPU profile: ", err) + } + defer f.Close() + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal("could not start CPU profile: ", err) + } + defer pprof.StopCPUProfile() + } // Initialize config log.Println("Initialize configuration...") err := initConfig() @@ -90,6 +108,19 @@ func main() { <-quit log.Println("Stopping...") + // Write memory profile + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal("could not create memory profile: ", err) + } + defer f.Close() + // runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + log.Fatal("could not write memory profile: ", err) + } + } + // Close DB err = closeDb() if err != nil { diff --git a/posts.go b/posts.go index da8d5e8..d61f7ab 100644 --- a/posts.go +++ b/posts.go @@ -41,10 +41,6 @@ const ( ) func servePost(w http.ResponseWriter, r *http.Request) { - as := strings.HasSuffix(r.URL.Path, ".as") - if as { - r.URL.Path = strings.TrimSuffix(r.URL.Path, ".as") - } p, err := getPost(r.URL.Path) if err == errPostNotFound { serve404(w, r) @@ -53,7 +49,7 @@ func servePost(w http.ResponseWriter, r *http.Request) { serveError(w, r, err.Error(), http.StatusInternalServerError) return } - if as { + if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest { p.serveActivityStreams(w) return } @@ -109,8 +105,7 @@ func (p *postPaginationAdapter) Slice(offset, length int, data interface{}) erro func serveHome(blog string, path string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - as := strings.HasSuffix(r.URL.Path, ".as") - if as { + if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest { appConfig.Blogs[blog].serveActivityStreams(blog, w, r) return } diff --git a/utils.go b/utils.go index 8c8f48b..140d33c 100644 --- a/utils.go +++ b/utils.go @@ -16,15 +16,15 @@ import ( type requestContextKey string func urlize(str string) string { - newStr := "" - for _, c := range strings.Split(strings.ToLower(str), "") { - if c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c >= "0" && c <= "9" { - newStr += c - } else if c == " " { - newStr += "-" + var sb strings.Builder + for _, c := range strings.ToLower(str) { + if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' { + _, _ = sb.WriteRune(c) + } else if c == ' ' { + _, _ = sb.WriteRune('-') } } - return newStr + return sb.String() } func sortedStrings(s []string) []string { @@ -105,11 +105,10 @@ func resolveURLReferences(base string, refs ...string) ([]string, error) { } func unescapedPath(p string) string { - u, err := url.PathUnescape(p) - if err != nil { - return p + if u, err := url.PathUnescape(p); err == nil { + return u } - return u + return p } func slashIfEmpty(s string) string {