diff --git a/controllers/middlewares.go b/controllers/middlewares.go index e89d8f11..99d93658 100644 --- a/controllers/middlewares.go +++ b/controllers/middlewares.go @@ -9,8 +9,8 @@ import ( func errorMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Next() - if c.Writer.Status() == http.StatusNotFound && c.Writer.Size() == 0 { - NotFoundHandler(c) + if c.Writer.Status() != http.StatusOK && c.Writer.Size() <= 0 { + httpError(c, c.Writer.Status()) } } } diff --git a/controllers/router.go b/controllers/router.go index 3ee1db58..55cf35ab 100644 --- a/controllers/router.go +++ b/controllers/router.go @@ -19,6 +19,8 @@ func init() { //Router.Use(gzip.Gzip(gzip.DefaultCompression)) // FIXME I can't make it work :/ Router.Use(gin.Logger()) Router.Use(gin.Recovery()) + Router.Use(errorMiddleware()) + // Static file handlers // TODO Use config from cli // TODO Make sure the directory exists @@ -29,13 +31,13 @@ func init() { Router.StaticFS("/gpg/", http.Dir(GPGPublicKeyPath)) // We don't need CSRF here - Router.Any("/", SearchHandler).Use(errorMiddleware()) - Router.Any("/p/:page", SearchHandler).Use(errorMiddleware()) - Router.Any("/search", SearchHandler).Use(errorMiddleware()) - Router.Any("/search/:page", SearchHandler).Use(errorMiddleware()) - Router.Any("/verify/email/:token", UserVerifyEmailHandler).Use(errorMiddleware()) - Router.Any("/faq", FaqHandler).Use(errorMiddleware()) - Router.Any("/activities", ActivityListHandler).Use(errorMiddleware()) + Router.Any("/", SearchHandler) + Router.Any("/p/:page", SearchHandler) + Router.Any("/search", SearchHandler) + Router.Any("/search/:page", SearchHandler) + Router.Any("/verify/email/:token", UserVerifyEmailHandler) + Router.Any("/faq", FaqHandler) + Router.Any("/activities", ActivityListHandler) Router.Any("/feed", RSSHandler) Router.Any("/feed/p/:page", RSSHandler) Router.Any("/feed/magnet", RSSMagnetHandler) @@ -49,7 +51,6 @@ func init() { // !!! This line need to have the same download location as the one define in config.TorrentStorageLink !!! Router.Any("/download/:hash", DownloadTorrent) - // For now, no CSRF protection here, as API is not usable for uploads Router.Any("/upload", UploadHandler) Router.POST("/login", UserLoginPostHandler) Router.GET("/register", UserRegisterFormHandler) @@ -58,19 +59,25 @@ func init() { Router.POST("/logout", UserLogoutHandler) Router.GET("/notifications", UserNotificationsHandler) - torrentViewRoutes := Router.Group("/view", errorMiddleware()) + reportRoutes := Router.Group("/report") + { + //reporting a torrent + reportRoutes.GET("/:id", ReportViewTorrentHandler) + reportRoutes.POST("/:id", ReportTorrentHandler) + } + torrentViewRoutes := Router.Group("/view") { torrentViewRoutes.GET("/:id", ViewHandler) torrentViewRoutes.HEAD("/:id", ViewHeadHandler) torrentViewRoutes.POST("/:id", PostCommentHandler) } - torrentRoutes := Router.Group("/torrent", errorMiddleware()) + torrentRoutes := Router.Group("/torrent") { torrentRoutes.GET("/", TorrentEditUserPanel) torrentRoutes.POST("/", TorrentPostEditUserPanel) torrentRoutes.GET("/delete", TorrentDeleteUserPanel) } - userRoutes := Router.Group("/user").Use(errorMiddleware()) + userRoutes := Router.Group("/user") { userRoutes.GET("/:id/:username", UserProfileHandler) userRoutes.GET("/:id/:username/follow", UserFollowHandler) @@ -98,7 +105,7 @@ func init() { // INFO Everything under /mod should be wrapped by wrapModHandler. This make // sure the page is only accessible by moderators // We don't need CSRF here - modRoutes := Router.Group("/mod", errorMiddleware(), modMiddleware()) + modRoutes := Router.Group("/mod", modMiddleware()) { modRoutes.Any("/", IndexModPanel) modRoutes.GET("/torrents", TorrentsListPanel) @@ -127,8 +134,6 @@ func init() { apiMod := modRoutes.Group("/api") apiMod.Any("/torrents", APIMassMod) } - //reporting a torrent - Router.POST("/report/:id", ReportTorrentHandler) Router.Any("/captcha/*hash", captcha.ServeFiles) @@ -137,11 +142,16 @@ func init() { Router.GET("/settings", SeePublicSettingsHandler) Router.POST("/settings", ChangePublicSettingsHandler) - Router.Use(errorMiddleware()) - CSRFRouter = nosurf.New(Router) CSRFRouter.ExemptRegexp("/api(?:/.+)*") CSRFRouter.ExemptPath("/mod") CSRFRouter.ExemptPath("/upload") CSRFRouter.ExemptPath("/user/login") + CSRFRouter.SetFailureHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Invalid CSRF tokens", http.StatusBadRequest) + })) + CSRFRouter.SetBaseCookie(http.Cookie{ + Path: "/", + MaxAge: nosurf.MaxAge, + }) } diff --git a/controllers/template.go b/controllers/template.go index 3b8cf35a..13d2ed3c 100644 --- a/controllers/template.go +++ b/controllers/template.go @@ -41,6 +41,7 @@ func init() { } } func commonVars(c *gin.Context) jet.VarMap { + token := nosurf.Token(c.Request) msg := messages.GetMessages(c) vars.Set("Navigation", newNavigation()) vars.Set("Search", newSearchForm(c)) @@ -50,7 +51,7 @@ func commonVars(c *gin.Context) jet.VarMap { vars.Set("MascotURL", publicSettings.GetMascotUrlFromRequest(c)) vars.Set("User", getUser(c)) vars.Set("URL", c.Request.URL) - vars.Set("CsrfToken", nosurf.Token(c.Request)) + vars.Set("CsrfToken", token) vars.Set("Config", config.Conf) vars.Set("Infos", msg.GetAllInfos()) vars.Set("Errors", msg.GetAllErrors()) @@ -85,13 +86,20 @@ func renderTemplate(c *gin.Context, templateName string, vars jet.VarMap) { } func httpError(c *gin.Context, errorCode int) { - if errorCode == http.StatusNotFound { - c.Status(http.StatusNotFound) + switch errorCode { + case http.StatusNotFound: staticTemplate(c, path.Join(ErrorsDir, "404.jet.html")) + c.AbortWithStatus(errorCode) + return + case http.StatusBadRequest: + staticTemplate(c, path.Join(ErrorsDir, "400.jet.html")) + c.AbortWithStatus(errorCode) + return + case http.StatusInternalServerError: + staticTemplate(c, path.Join(ErrorsDir, "500.jet.html")) + c.AbortWithStatus(errorCode) return } - c.AbortWithStatus(errorCode) - return } func staticTemplate(c *gin.Context, templateName string) { diff --git a/controllers/view_torrent_handler.go b/controllers/view_torrent_handler.go index a81243f5..06a6d9be 100644 --- a/controllers/view_torrent_handler.go +++ b/controllers/view_torrent_handler.go @@ -35,7 +35,13 @@ func ViewHandler(c *gin.Context) { user := getUser(c) if c.Request.URL.Query()["success"] != nil { - messages.AddInfo("infos", "Torrent uploaded successfully!") + messages.AddInfoT("infos", "torrent_uploaded") + } + if c.Request.URL.Query()["badcaptcha"] != nil { + messages.AddErrorT("errors", "bad_captcha") + } + if c.Request.URL.Query()["reported"] != nil { + messages.AddInfoTf("infos", "report_msg", id) } torrent, err := torrents.FindByID(uint(id)) @@ -120,12 +126,15 @@ func PostCommentHandler(c *gin.Context) { // ReportTorrentHandler : Controller for sending a torrent report func ReportTorrentHandler(c *gin.Context) { + fmt.Println("report") id, _ := strconv.ParseInt(c.Param("id"), 10, 32) messages := msg.GetMessages(c) + captchaError := "?reported" currentUser := getUser(c) if currentUser.NeedsCaptcha() { userCaptcha := captcha.Extract(c) if !captcha.Authenticate(userCaptcha) { + captchaError = "?badcaptcha" messages.AddErrorT("errors", "bad_captcha") } } @@ -139,8 +148,36 @@ func ReportTorrentHandler(c *gin.Context) { if err != nil { messages.ImportFromError("errors", err) } + c.Redirect(http.StatusSeeOther, "/view/"+strconv.Itoa(int(torrent.ID))+captchaError) + } else { + ReportViewTorrentHandler(c) + } +} + +// ReportTorrentHandler : Controller for sending a torrent report +func ReportViewTorrentHandler(c *gin.Context) { + + type Report struct { + ID uint + CaptchaID string + } + + id, _ := strconv.ParseInt(c.Param("id"), 10, 32) + messages := msg.GetMessages(c) + currentUser := getUser(c) + if currentUser.ID > 0 { + torrent, err := torrents.FindByID(uint(id)) + if err != nil { + messages.Error(err) + } + captchaID := "" + if currentUser.NeedsCaptcha() { + captchaID = captcha.GetID() + } + formTemplate(c, "site/torrents/report.jet.html", Report{torrent.ID, captchaID}) + } else { + c.Status(404) } - ViewHandler(c) } // TorrentEditUserPanel : Controller for editing a user torrent by a user, after GET request diff --git a/public/img/400.png b/public/img/400.png new file mode 100644 index 00000000..951ac3a7 Binary files /dev/null and b/public/img/400.png differ diff --git a/public/img/500.png b/public/img/500.png new file mode 100644 index 00000000..73b53282 Binary files /dev/null and b/public/img/500.png differ diff --git a/templates/errors/400.jet.html b/templates/errors/400.jet.html new file mode 100644 index 00000000..2f604705 --- /dev/null +++ b/templates/errors/400.jet.html @@ -0,0 +1,9 @@ +{{ extends "layouts/index_site" }} +{{block title()}}{{ T("error_400")}}{{end}} +{{block content_body()}} +
+

{{ T("400_bad_request")}}

+ +
+{{end}} +{{block mascot()}}
{{end}} \ No newline at end of file diff --git a/templates/errors/500.jet.html b/templates/errors/500.jet.html new file mode 100644 index 00000000..adc349d6 --- /dev/null +++ b/templates/errors/500.jet.html @@ -0,0 +1,9 @@ +{{ extends "layouts/index_site" }} +{{block title()}}{{ T("error_500")}}{{end}} +{{block content_body()}} +
+

{{ T("500_internal_server_error")}}

+ +
+{{end}} +{{block mascot()}}
{{end}} \ No newline at end of file diff --git a/templates/site/torrents/report.jet.html b/templates/site/torrents/report.jet.html new file mode 100644 index 00000000..bed9470d --- /dev/null +++ b/templates/site/torrents/report.jet.html @@ -0,0 +1,25 @@ +{{ extends "layouts/index_site" }} +{{ import "layouts/partials/helpers/csrf" }} +{{ import "layouts/partials/helpers/captcha" }} +{{block title()}}{{ T("report_torrent_number", Form.ID) }}{{end}} +{{block content_body()}} +
+
+

{{ T("report_torrent_number", Form.ID) }}

+
+ {{ yield csrf_field() }} +
+

{{ T("report_type") }}

+
+
+
+
+
+
+ {{yield captcha(captchaid=Form.CaptchaID)}} +
+ +
+
+
+{{end}} \ No newline at end of file diff --git a/templates/site/torrents/view.jet.html b/templates/site/torrents/view.jet.html index 6f6a82ab..a26c8020 100644 --- a/templates/site/torrents/view.jet.html +++ b/templates/site/torrents/view.jet.html @@ -39,7 +39,9 @@ {{ T("torrent_language")}}: {{ range _, language := Torrent.Languages}} + {{ if language != "" }} {{ LanguageName(language, T) }} {{ LanguageName(language, T) }} + {{end}} {{end}} @@ -57,7 +59,7 @@ {{ T("edit") }} {{ else if User.CurrentUserIdentical(Torrent.UploaderID) }} {{ T("delete") }} - {{ T("edit") }} + {{ T("edit") }} {{end}} {{end}}
diff --git a/translations/en-us.all.json b/translations/en-us.all.json index abd92471..a66462a5 100644 --- a/translations/en-us.all.json +++ b/translations/en-us.all.json @@ -163,6 +163,14 @@ "id": "error_404", "translation": "Error 404" }, + { + "id": "error_400", + "translation": "Error 400" + }, + { + "id": "error_500", + "translation": "Error 500" + }, { "id": "upload", "translation": "Upload" @@ -187,6 +195,14 @@ "id": "404_not_found", "translation": "404 Not Found" }, + { + "id": "500_internal_server_error", + "translation": "500 Internal Server Error" + }, + { + "id": "400_bad_request", + "translation": "400 Bad Request" + }, { "id": "no_torrents_uploaded", "translation": "No torrents uploaded yet!" @@ -701,7 +717,7 @@ }, { "id": "report_torrent_number", - "translation": "Report Torrent #%s" + "translation": "Report Torrent #%d" }, { "id": "report_type", @@ -861,19 +877,19 @@ }, { "id": "torrent_deleted_by", - "translation": "Torrent #%s from %s has been deleted by %s." + "translation": "Torrent #%d from %s has been deleted by %s." }, { "id": "torrent_edited_by", - "translation": "Torrent #%s from %s has been edited by %s." + "translation": "Torrent #%d from %s has been edited by %s." }, { "id": "torrent_blocked_by", - "translation": "Torrent #%s from %s has been locked by %s." + "translation": "Torrent #%d from %s has been locked by %s." }, { "id": "torrent_blocked_by", - "translation": "Torrent #%s from %s has been unlocked by %s." + "translation": "Torrent #%d from %s has been unlocked by %s." }, { "id": "torrents_deleted", @@ -889,11 +905,11 @@ }, { "id": "comment_deleted_by", - "translation": "Comment #%s from %s has been deleted by %s." + "translation": "Comment #%d from %s has been deleted by %s." }, { "id": "comment_edited_by", - "translation": "Comment #%s from %s has been edited by %s." + "translation": "Comment #%d from %s has been edited by %s." }, { "id": "no_action_exist", @@ -901,7 +917,7 @@ }, { "id": "torrent_not_exist", - "translation": "Torrent with ID %s doesn't exist!" + "translation": "Torrent with ID %d doesn't exist!" }, { "id": "something_went_wrong", @@ -1253,7 +1269,7 @@ }, { "id": "report_msg", - "translation": "The torrent #%s has been reported!" + "translation": "The torrent #%d has been reported!" }, { "id": "email_not_valid",