2018-02-03 05:47:14 +00:00
|
|
|
package routes
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-05-15 05:59:52 +00:00
|
|
|
"database/sql"
|
2018-02-03 05:47:14 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
2018-05-15 05:59:52 +00:00
|
|
|
"path/filepath"
|
2018-02-03 05:47:14 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2018-10-27 03:21:02 +00:00
|
|
|
"github.com/Azareal/Gosora/common"
|
2018-11-01 06:43:56 +00:00
|
|
|
"github.com/Azareal/Gosora/common/phrases"
|
2018-10-27 03:21:02 +00:00
|
|
|
"github.com/Azareal/Gosora/query_gen"
|
2018-02-03 05:47:14 +00:00
|
|
|
)
|
|
|
|
|
2018-05-14 08:56:56 +00:00
|
|
|
var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Make this a common.Config value
|
2018-02-03 05:47:14 +00:00
|
|
|
|
|
|
|
// GET functions
|
|
|
|
func StaticFile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
file, ok := common.StaticFiles.Get(r.URL.Path)
|
|
|
|
if !ok {
|
2018-02-19 04:26:01 +00:00
|
|
|
common.DebugLogf("Failed to find '%s'", r.URL.Path)
|
2018-02-03 05:47:14 +00:00
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
h := w.Header()
|
|
|
|
|
|
|
|
// Surely, there's a more efficient way of doing this?
|
2018-08-13 12:01:27 +00:00
|
|
|
t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since"))
|
2018-02-03 05:47:14 +00:00
|
|
|
if err == nil && file.Info.ModTime().Before(t.Add(1*time.Second)) {
|
|
|
|
w.WriteHeader(http.StatusNotModified)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
h.Set("Last-Modified", file.FormattedModTime)
|
|
|
|
h.Set("Content-Type", file.Mimetype)
|
|
|
|
h.Set("Cache-Control", cacheControlMaxAge) //Cache-Control: max-age=31536000
|
|
|
|
h.Set("Vary", "Accept-Encoding")
|
2018-08-13 12:01:27 +00:00
|
|
|
|
2018-08-22 01:32:07 +00:00
|
|
|
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && file.GzipLength > 0 {
|
2018-02-03 05:47:14 +00:00
|
|
|
h.Set("Content-Encoding", "gzip")
|
|
|
|
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
|
|
|
|
io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead?
|
|
|
|
} else {
|
|
|
|
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
|
|
|
|
io.Copy(w, bytes.NewReader(file.Data))
|
|
|
|
}
|
|
|
|
// Other options instead of io.Copy: io.CopyN(), w.Write(), http.ServeContent()
|
|
|
|
}
|
|
|
|
|
2018-11-12 09:23:36 +00:00
|
|
|
func Overview(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
|
2018-11-01 06:43:56 +00:00
|
|
|
header.Title = phrases.GetTitlePhrase("overview")
|
2018-04-22 12:33:56 +00:00
|
|
|
header.Zone = "overview"
|
2018-06-06 00:21:22 +00:00
|
|
|
pi := common.Page{header, tList, nil}
|
2018-11-12 09:23:36 +00:00
|
|
|
return renderTemplate("overview", w, r, header, pi)
|
2018-02-03 05:47:14 +00:00
|
|
|
}
|
|
|
|
|
2018-11-12 09:23:36 +00:00
|
|
|
func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header, name string) common.RouteError {
|
2018-04-22 12:33:56 +00:00
|
|
|
header.Zone = "custom_page"
|
2018-06-06 00:21:22 +00:00
|
|
|
name = common.SanitiseSingleLine(name)
|
|
|
|
page, err := common.Pages.GetByName(name)
|
|
|
|
if err == nil {
|
|
|
|
header.Title = page.Title
|
|
|
|
pi := common.CustomPagePage{header, page}
|
2018-11-12 09:23:36 +00:00
|
|
|
return renderTemplate("custom_page", w, r, header, pi)
|
2018-06-06 00:21:22 +00:00
|
|
|
} else if err != sql.ErrNoRows {
|
|
|
|
return common.InternalError(err, w, r)
|
|
|
|
}
|
|
|
|
|
2018-02-03 05:47:14 +00:00
|
|
|
// ! Is this safe?
|
|
|
|
if common.Templates.Lookup("page_"+name+".html") == nil {
|
2018-04-22 12:33:56 +00:00
|
|
|
return common.NotFound(w, r, header)
|
2018-02-03 05:47:14 +00:00
|
|
|
}
|
|
|
|
|
2018-11-12 09:23:36 +00:00
|
|
|
header.Title = phrases.GetTitlePhrase("page")
|
2018-06-06 00:21:22 +00:00
|
|
|
pi := common.Page{header, tList, nil}
|
2018-02-03 05:47:14 +00:00
|
|
|
// TODO: Pass the page name to the pre-render hook?
|
2018-06-06 00:21:22 +00:00
|
|
|
if common.RunPreRenderHook("pre_render_tmpl_page", w, r, &user, &pi) {
|
2018-02-19 04:26:01 +00:00
|
|
|
return nil
|
2018-02-03 05:47:14 +00:00
|
|
|
}
|
2018-06-06 00:21:22 +00:00
|
|
|
err = common.Templates.ExecuteTemplate(w, "page_"+name+".html", pi)
|
2018-02-03 05:47:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return common.InternalError(err, w, r)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-15 05:59:52 +00:00
|
|
|
|
|
|
|
type AttachmentStmts struct {
|
|
|
|
get *sql.Stmt
|
|
|
|
}
|
|
|
|
|
|
|
|
var attachmentStmts AttachmentStmts
|
|
|
|
|
2018-06-06 00:21:22 +00:00
|
|
|
// TODO: Abstract this with an attachment store
|
2018-05-15 05:59:52 +00:00
|
|
|
func init() {
|
|
|
|
common.DbInits.Add(func(acc *qgen.Accumulator) error {
|
|
|
|
attachmentStmts = AttachmentStmts{
|
|
|
|
get: acc.Select("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Prepare(),
|
|
|
|
}
|
|
|
|
return acc.FirstError()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func ShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, filename string) common.RouteError {
|
|
|
|
filename = common.Stripslashes(filename)
|
|
|
|
var ext = filepath.Ext("./attachs/" + filename)
|
|
|
|
if !common.AllowedFileExts.Contains(strings.TrimPrefix(ext, ".")) {
|
|
|
|
return common.LocalError("Bad extension", w, r, user)
|
|
|
|
}
|
|
|
|
|
|
|
|
sectionID, err := strconv.Atoi(r.FormValue("sectionID"))
|
|
|
|
if err != nil {
|
|
|
|
return common.LocalError("The sectionID is not an integer", w, r, user)
|
|
|
|
}
|
|
|
|
var sectionTable = r.FormValue("sectionType")
|
|
|
|
|
|
|
|
var originTable string
|
|
|
|
var originID, uploadedBy int
|
|
|
|
err = attachmentStmts.get.QueryRow(filename, sectionID, sectionTable).Scan(§ionID, §ionTable, &originID, &originTable, &uploadedBy, &filename)
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return common.NotFound(w, r, nil)
|
|
|
|
} else if err != nil {
|
|
|
|
return common.InternalError(err, w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
if sectionTable == "forums" {
|
|
|
|
_, ferr := common.SimpleForumUserCheck(w, r, &user, sectionID)
|
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
|
|
|
}
|
|
|
|
if !user.Perms.ViewTopic {
|
|
|
|
return common.NoPermissions(w, r, user)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return common.LocalError("Unknown section", w, r, user)
|
|
|
|
}
|
|
|
|
|
|
|
|
if originTable != "topics" && originTable != "replies" {
|
|
|
|
return common.LocalError("Unknown origin", w, r, user)
|
|
|
|
}
|
|
|
|
|
2018-11-20 04:00:32 +00:00
|
|
|
if !user.Loggedin {
|
|
|
|
w.Header().Set("Cache-Control", "max-age="+strconv.Itoa(int(common.Year)))
|
|
|
|
} else {
|
|
|
|
guest := common.GuestUser
|
|
|
|
_, ferr := common.SimpleForumUserCheck(w, r, &guest, sectionID)
|
|
|
|
if ferr != nil {
|
|
|
|
return ferr
|
|
|
|
}
|
|
|
|
if guest.Perms.ViewTopic {
|
|
|
|
w.Header().Set("Cache-Control", "max-age="+strconv.Itoa(int(common.Year)))
|
|
|
|
} else {
|
|
|
|
w.Header().Set("Cache-Control", "private")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-15 05:59:52 +00:00
|
|
|
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
|
|
|
|
http.ServeFile(w, r, "./attachs/"+filename)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Set the cookie domain
|
|
|
|
func ChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
|
|
|
//headerLite, _ := SimpleUserCheck(w, r, &user)
|
|
|
|
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
|
|
|
|
isJs := (r.PostFormValue("isJs") == "1")
|
2018-05-31 06:51:31 +00:00
|
|
|
newTheme := common.SanitiseSingleLine(r.PostFormValue("newTheme"))
|
2018-05-15 05:59:52 +00:00
|
|
|
|
|
|
|
theme, ok := common.Themes[newTheme]
|
|
|
|
if !ok || theme.HideFromThemes {
|
|
|
|
return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs)
|
|
|
|
}
|
|
|
|
|
|
|
|
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(common.Year)}
|
|
|
|
http.SetCookie(w, &cookie)
|
|
|
|
|
|
|
|
if !isJs {
|
|
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
|
|
} else {
|
|
|
|
_, _ = w.Write(successJSONBytes)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|