From 59754b489d0dae002a3591a0451ec00c38dc379a Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 25 Jan 2018 04:57:33 +0000 Subject: [PATCH] Block requests without known hosts for security reasons. We now track stats for Uptimebot. We now track stats for malformed requests. Added json as a valid column type. Continued work on poll posts. Added the polls table. Added the polls_voters table. Added the poll column to the topics table. Changed the type of the poll column on the replies table from boolean to int. --- common/poll_cache.go | 202 ++++++++++++++++++++++++++++ common/poll_store.go | 118 ++++++++++++++++ common/reply.go | 11 +- common/site.go | 2 + common/topic.go | 9 +- gen_router.go | 30 +++-- gen_tables.go | 15 ++- main.go | 4 + public/global.js | 5 +- query_gen/lib/mssql.go | 3 + query_gen/lib/mysql.go | 2 + query_gen/tables.go | 24 +++- router_gen/main.go | 16 ++- schema/mssql/query_polls.sql | 7 + schema/mssql/query_polls_voters.sql | 5 + schema/mssql/query_replies.sql | 2 +- schema/mssql/query_topics.sql | 1 + schema/mysql/query_polls.sql | 7 + schema/mysql/query_polls_voters.sql | 5 + schema/mysql/query_replies.sql | 2 +- schema/mysql/query_topics.sql | 1 + schema/pgsql/query_polls.sql | 7 + schema/pgsql/query_polls_voters.sql | 5 + schema/pgsql/query_replies.sql | 2 +- schema/pgsql/query_topics.sql | 1 + template_list.go | 16 +++ templates/forum.html | 6 + templates/topics.html | 10 ++ themes/cosora/public/main.css | 17 +++ 29 files changed, 508 insertions(+), 27 deletions(-) create mode 100644 common/poll_cache.go create mode 100644 common/poll_store.go create mode 100644 schema/mssql/query_polls.sql create mode 100644 schema/mssql/query_polls_voters.sql create mode 100644 schema/mysql/query_polls.sql create mode 100644 schema/mysql/query_polls_voters.sql create mode 100644 schema/pgsql/query_polls.sql create mode 100644 schema/pgsql/query_polls_voters.sql diff --git a/common/poll_cache.go b/common/poll_cache.go new file mode 100644 index 00000000..5c51197a --- /dev/null +++ b/common/poll_cache.go @@ -0,0 +1,202 @@ +package common + +import ( + "sync" + "sync/atomic" +) + +type PollCache interface { + Get(id int) (*Poll, error) + GetUnsafe(id int) (*Poll, error) + BulkGet(ids []int) (list []*Poll) + Set(item *Poll) error + Add(item *Poll) error + AddUnsafe(item *Poll) error + Remove(id int) error + RemoveUnsafe(id int) error + Flush() + Length() int + SetCapacity(capacity int) + GetCapacity() int +} + +type MemoryPollCache struct { + items map[int]*Poll + length int64 + capacity int + + sync.RWMutex +} + +// NewMemoryPollCache gives you a new instance of MemoryPollCache +func NewMemoryPollCache(capacity int) *MemoryPollCache { + return &MemoryPollCache{ + items: make(map[int]*Poll), + capacity: capacity, + } +} + +func (mus *MemoryPollCache) Get(id int) (*Poll, error) { + mus.RLock() + item, ok := mus.items[id] + mus.RUnlock() + if ok { + return item, nil + } + return item, ErrNoRows +} + +func (mus *MemoryPollCache) BulkGet(ids []int) (list []*Poll) { + list = make([]*Poll, len(ids)) + mus.RLock() + for i, id := range ids { + list[i] = mus.items[id] + } + mus.RUnlock() + return list +} + +func (mus *MemoryPollCache) GetUnsafe(id int) (*Poll, error) { + item, ok := mus.items[id] + if ok { + return item, nil + } + return item, ErrNoRows +} + +func (mus *MemoryPollCache) Set(item *Poll) error { + mus.Lock() + user, ok := mus.items[item.ID] + if ok { + mus.Unlock() + *user = *item + } else if int(mus.length) >= mus.capacity { + mus.Unlock() + return ErrStoreCapacityOverflow + } else { + mus.items[item.ID] = item + mus.Unlock() + atomic.AddInt64(&mus.length, 1) + } + return nil +} + +func (mus *MemoryPollCache) Add(item *Poll) error { + if int(mus.length) >= mus.capacity { + return ErrStoreCapacityOverflow + } + mus.Lock() + mus.items[item.ID] = item + mus.length = int64(len(mus.items)) + mus.Unlock() + return nil +} + +func (mus *MemoryPollCache) AddUnsafe(item *Poll) error { + if int(mus.length) >= mus.capacity { + return ErrStoreCapacityOverflow + } + mus.items[item.ID] = item + mus.length = int64(len(mus.items)) + return nil +} + +func (mus *MemoryPollCache) Remove(id int) error { + mus.Lock() + _, ok := mus.items[id] + if !ok { + mus.Unlock() + return ErrNoRows + } + delete(mus.items, id) + mus.Unlock() + atomic.AddInt64(&mus.length, -1) + return nil +} + +func (mus *MemoryPollCache) RemoveUnsafe(id int) error { + _, ok := mus.items[id] + if !ok { + return ErrNoRows + } + delete(mus.items, id) + atomic.AddInt64(&mus.length, -1) + return nil +} + +func (mus *MemoryPollCache) Flush() { + mus.Lock() + mus.items = make(map[int]*Poll) + mus.length = 0 + mus.Unlock() +} + +// ! Is this concurrent? +// Length returns the number of users in the memory cache +func (mus *MemoryPollCache) Length() int { + return int(mus.length) +} + +func (mus *MemoryPollCache) SetCapacity(capacity int) { + mus.capacity = capacity +} + +func (mus *MemoryPollCache) GetCapacity() int { + return mus.capacity +} + +type NullPollCache struct { +} + +// NewNullPollCache gives you a new instance of NullPollCache +func NewNullPollCache() *NullPollCache { + return &NullPollCache{} +} + +func (mus *NullPollCache) Get(id int) (*Poll, error) { + return nil, ErrNoRows +} + +func (mus *NullPollCache) BulkGet(ids []int) (list []*Poll) { + return list +} + +func (mus *NullPollCache) GetUnsafe(id int) (*Poll, error) { + return nil, ErrNoRows +} + +func (mus *NullPollCache) Set(_ *Poll) error { + return nil +} + +func (mus *NullPollCache) Add(item *Poll) error { + _ = item + return nil +} + +func (mus *NullPollCache) AddUnsafe(item *Poll) error { + _ = item + return nil +} + +func (mus *NullPollCache) Remove(id int) error { + return nil +} + +func (mus *NullPollCache) RemoveUnsafe(id int) error { + return nil +} + +func (mus *NullPollCache) Flush() { +} + +func (mus *NullPollCache) Length() int { + return 0 +} + +func (mus *NullPollCache) SetCapacity(_ int) { +} + +func (mus *NullPollCache) GetCapacity() int { + return 0 +} diff --git a/common/poll_store.go b/common/poll_store.go new file mode 100644 index 00000000..df99f71c --- /dev/null +++ b/common/poll_store.go @@ -0,0 +1,118 @@ +package common + +import "database/sql" +import "../query_gen/lib" + +var Polls PollStore + +type Poll struct { + ID int + Type int // 0: Single choice, 1: Multiple choice, 2: Multiple choice w/ points + //AntiCheat bool // Apply various mitigations for cheating + // GroupPower map[gid]points // The number of points a group can spend in this poll, defaults to 1 + + Options []string + Results map[int]int // map[optionIndex]points + VoteCount int +} + +type Pollable interface { + SetPoll(pollID int) error +} + +type PollStore interface { + Get(id int) (*Poll, error) + Exists(id int) bool + Create(parent Pollable, pollType int, pollOptions []string) (int, error) + Reload(id int) error + //GlobalCount() int + + SetCache(cache PollCache) + GetCache() PollCache +} + +type DefaultPollStore struct { + cache PollCache + + get *sql.Stmt + exists *sql.Stmt + create *sql.Stmt + delete *sql.Stmt + //pollCount *sql.Stmt +} + +func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) { + acc := qgen.Builder.Accumulator() + if cache == nil { + cache = NewNullPollCache() + } + // TODO: Add an admin version of registerStmt with more flexibility? + return &DefaultPollStore{ + cache: cache, + get: acc.Select("polls").Columns("type, options, votes").Where("pollID = ?").Prepare(), + exists: acc.Select("polls").Columns("pollID").Where("pollID = ?").Prepare(), + create: acc.Insert("polls").Columns("type, options").Fields("?,?").Prepare(), + //pollCount: acc.SimpleCount("polls", "", ""), + }, acc.FirstError() +} + +func (store *DefaultPollStore) Exists(id int) bool { + err := store.exists.QueryRow(id).Scan(&id) + if err != nil && err != ErrNoRows { + LogError(err) + } + return err != ErrNoRows +} + +func (store *DefaultPollStore) Get(id int) (*Poll, error) { + poll, err := store.cache.Get(id) + if err == nil { + return poll, nil + } + + poll = &Poll{ID: id} + var optionTxt []byte + err = store.get.QueryRow(id).Scan(&poll.Type, optionTxt, &poll.VoteCount) + if err == nil { + store.cache.Set(poll) + } + return poll, err +} + +func (store *DefaultPollStore) Reload(id int) error { + poll := &Poll{ID: id} + var optionTxt []byte + err := store.get.QueryRow(id).Scan(&poll.Type, optionTxt, &poll.VoteCount) + if err != nil { + store.cache.Remove(id) + return err + } + _ = store.cache.Set(poll) + return nil +} + +func (store *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions []string) (id int, err error) { + res, err := store.create.Exec(pollType, pollOptions) + if err != nil { + return 0, err + } + + lastID, err := res.LastInsertId() + if err != nil { + return 0, err + } + return int(lastID), parent.SetPoll(int(lastID)) // TODO: Delete the poll if SetPoll fails +} + +func (store *DefaultPollStore) SetCache(cache PollCache) { + store.cache = cache +} + +// TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it. +func (store *DefaultPollStore) GetCache() PollCache { + _, ok := store.cache.(*NullPollCache) + if ok { + return nil + } + return store.cache +} diff --git a/common/reply.go b/common/reply.go index 9c96f8b3..362bcbba 100644 --- a/common/reply.go +++ b/common/reply.go @@ -66,6 +66,7 @@ type ReplyStmts struct { isLiked *sql.Stmt createLike *sql.Stmt edit *sql.Stmt + setPoll *sql.Stmt delete *sql.Stmt addLikesToReply *sql.Stmt removeRepliesFromTopic *sql.Stmt @@ -76,7 +77,8 @@ func init() { replyStmts = ReplyStmts{ isLiked: acc.Select("likes").Columns("targetItem").Where("sentBy = ? and targetItem = ? and targetType = 'replies'").Prepare(), createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy").Fields("?,?,?,?").Prepare(), - edit: acc.Update("replies").Set("content = ?, parsed_content = ?, poll = ?").Where("rid = ?").Prepare(), + edit: acc.Update("replies").Set("content = ?, parsed_content = ?").Where("rid = ? AND poll = 0").Prepare(), + setPoll: acc.Update("replies").Set("content = '', parsed_content = '', poll = ?").Where("rid = ? AND poll = 0").Prepare(), delete: acc.Delete("replies").Where("rid = ?").Prepare(), addLikesToReply: acc.Update("replies").Set("likeCount = likeCount + ?").Where("rid = ?").Prepare(), removeRepliesFromTopic: acc.Update("topics").Set("postCount = postCount - ?").Where("tid = ?").Prepare(), @@ -127,12 +129,13 @@ func (reply *Reply) SetPost(content string) error { } content = PreparseMessage(html.UnescapeString(content)) parsedContent := ParseMessage(content, topic.ParentID, "forums") - _, err = replyStmts.edit.Exec(content, parsedContent, 0, reply.ID) + _, err = replyStmts.edit.Exec(content, parsedContent, reply.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll return err } -func (reply *Reply) SetPoll(content string) error { - return nil +func (reply *Reply) SetPoll(pollID int) error { + _, err := replyStmts.setPoll.Exec(pollID, reply.ID) // TODO: Sniff if this changed anything to see if we hit a poll + return err } func (reply *Reply) Topic() (*Topic, error) { diff --git a/common/site.go b/common/site.go index 699585cb..b792f2ed 100644 --- a/common/site.go +++ b/common/site.go @@ -22,6 +22,7 @@ type site struct { Name string Email string URL string + Host string Port string EnableSsl bool EnableEmails bool @@ -82,6 +83,7 @@ type devConfig struct { func ProcessConfig() error { Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1) + Site.Host = Site.URL if Site.Port != "80" && Site.Port != "443" { Site.URL = strings.TrimSuffix(Site.URL, "/") Site.URL = strings.TrimSuffix(Site.URL, "\\") diff --git a/common/topic.go b/common/topic.go index 2da01d50..fa0a43bc 100644 --- a/common/topic.go +++ b/common/topic.go @@ -120,6 +120,7 @@ type TopicStmts struct { addLikesToTopic *sql.Stmt delete *sql.Stmt edit *sql.Stmt + setPoll *sql.Stmt createActionReply *sql.Stmt getTopicUser *sql.Stmt // TODO: Can we get rid of this? @@ -141,7 +142,8 @@ func init() { createLike: acc.Insert("likes").Columns("weight, targetItem, targetType, sentBy").Fields("?,?,?,?").Prepare(), addLikesToTopic: acc.Update("topics").Set("likeCount = likeCount + ?").Where("tid = ?").Prepare(), delete: acc.Delete("topics").Where("tid = ?").Prepare(), - edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), + edit: acc.Update("topics").Set("title = ?, content = ?, parsed_content = ?").Where("tid = ?").Prepare(), // TODO: Only run the content update bits on non-polls, does this matter? + setPoll: acc.Update("topics").Set("content = '', parsed_content = '', poll = ?").Where("tid = ? AND poll = 0").Prepare(), createActionReply: acc.Insert("replies").Columns("tid, actionType, ipaddress, createdBy, createdAt, lastUpdated, content, parsed_content").Fields("?,?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),'',''").Prepare(), getTopicUser: acc.SimpleLeftJoin("topics", "users", "topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, topics.ipaddress, topics.postCount, topics.likeCount, users.name, users.avatar, users.group, users.url_prefix, users.url_name, users.level", "topics.createdBy = users.uid", "tid = ?", "", ""), @@ -257,6 +259,11 @@ func (topic *Topic) Update(name string, content string) error { return err } +func (topic *Topic) SetPoll(pollID int) error { + _, err := topicStmts.setPoll.Exec(pollID, topic.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll + return err +} + // TODO: Have this go through the ReplyStore? func (topic *Topic) CreateActionReply(action string, ipaddress string, user User) (err error) { _, err = topicStmts.createActionReply.Exec(topic.ID, action, ipaddress, user.ID) diff --git a/gen_router.go b/gen_router.go index 6bf39bbe..3ee916d2 100644 --- a/gen_router.go +++ b/gen_router.go @@ -311,8 +311,10 @@ var agentMapEnum = map[string]int{ "duckduckgo": 11, "discord": 12, "cloudflarealwayson": 13, - "lynx": 14, - "blank": 15, + "uptimebot": 14, + "lynx": 15, + "blank": 16, + "malformed": 17, } var reverseAgentMapEnum = map[int]string{ 0: "unknown", @@ -329,8 +331,10 @@ var reverseAgentMapEnum = map[int]string{ 11: "duckduckgo", 12: "discord", 13: "cloudflarealwayson", - 14: "lynx", - 15: "blank", + 14: "uptimebot", + 15: "lynx", + 16: "blank", + 17: "malformed", } // TODO: Stop spilling these into the package scope? @@ -392,20 +396,23 @@ func (router *GenRouter) RemoveFunc(pattern string) error { // TODO: GetDefaultRoute func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { + if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { w.WriteHeader(405) w.Write([]byte("")) - log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Malformed Request") + log.Print("UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { for _, vvalue := range value { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump(17) return } @@ -420,6 +427,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) @@ -435,6 +443,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) @@ -458,6 +467,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) @@ -507,11 +517,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { case strings.Contains(ua,"Discordbot"): common.AgentViewCounter.Bump(12) case strings.Contains(ua,"Lynx"): - common.AgentViewCounter.Bump(14) + common.AgentViewCounter.Bump(15) case strings.Contains(ua,"CloudFlare-AlwaysOnline"): common.AgentViewCounter.Bump(13) + case strings.Contains(ua,"Uptimebot"): + common.AgentViewCounter.Bump(14) case ua == "": - common.AgentViewCounter.Bump(15) + common.AgentViewCounter.Bump(16) if common.Dev.DebugMode { log.Print("Blank UA: ", req.UserAgent()) log.Print("Method: ", req.Method) @@ -522,6 +534,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) @@ -539,6 +552,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) diff --git a/gen_tables.go b/gen_tables.go index 795a1739..dea7c928 100644 --- a/gen_tables.go +++ b/gen_tables.go @@ -2,14 +2,15 @@ package main var dbTablePrimaryKeys = map[string]string{ - "replies":"rid", - "attachments":"attachID", - "users_replies":"rid", - "activity_stream":"asid", - "word_filters":"wfid", - "forums":"fid", "users_groups":"gid", "users_groups_scheduler":"uid", - "topics":"tid", + "replies":"rid", + "word_filters":"wfid", + "users_replies":"rid", + "activity_stream":"asid", "users":"uid", + "forums":"fid", + "topics":"tid", + "attachments":"attachID", + "polls":"pollID", } diff --git a/main.go b/main.go index 830d425b..f171f810 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,10 @@ func afterDBInit() (err error) { if err != nil { return err } + common.Polls, err = common.NewDefaultPollStore(common.NewMemoryPollCache(100)) // TODO: Max number of polls held in cache, make this a config item + if err != nil { + return err + } common.GlobalViewCounter, err = common.NewGlobalViewCounter() if err != nil { diff --git a/public/global.js b/public/global.js index 67bff5ce..0a4275a7 100644 --- a/public/global.js +++ b/public/global.js @@ -591,5 +591,8 @@ $(document).ready(function(){ if(event.which == 39) this.querySelectorAll("#nextFloat a")[0].click(); }; - + $("#add_poll_button").click(function(event){ + event.preventDefault(); + $(".poll_content_row").removeClass("auto_hide"); + }); }); diff --git a/query_gen/lib/mssql.go b/query_gen/lib/mssql.go index 1ad29a0c..834273d2 100644 --- a/query_gen/lib/mssql.go +++ b/query_gen/lib/mssql.go @@ -60,6 +60,9 @@ func (adapter *MssqlAdapter) CreateTable(name string, table string, charset stri case "text": column.Type = "nvarchar" max = true + case "json": + column.Type = "nvarchar" + max = true case "boolean": column.Type = "bit" } diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index 70213def..60288ab0 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -49,6 +49,8 @@ func (adapter *MysqlAdapter) CreateTable(name string, table string, charset stri // Make it easier to support Cassandra in the future if column.Type == "createdAt" { column.Type = "datetime" + } else if column.Type == "json" { + column.Type = "text" } var size string diff --git a/query_gen/tables.go b/query_gen/tables.go index de308434..8d0ef734 100644 --- a/query_gen/tables.go +++ b/query_gen/tables.go @@ -162,6 +162,7 @@ func createTables(adapter qgen.Adapter) error { qgen.DBTableColumn{"words", "int", 0, false, false, "0"}, qgen.DBTableColumn{"views", "int", 0, false, false, "0"}, qgen.DBTableColumn{"css_class", "varchar", 100, false, false, "''"}, + qgen.DBTableColumn{"poll", "int", 0, false, false, "0"}, qgen.DBTableColumn{"data", "varchar", 200, false, false, "''"}, }, []qgen.DBTableKey{ @@ -184,7 +185,7 @@ func createTables(adapter qgen.Adapter) error { qgen.DBTableColumn{"likeCount", "int", 0, false, false, "0"}, qgen.DBTableColumn{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why? qgen.DBTableColumn{"actionType", "varchar", 20, false, false, "''"}, - qgen.DBTableColumn{"poll", "boolean", 0, false, false, "0"}, + qgen.DBTableColumn{"poll", "int", 0, false, false, "0"}, }, []qgen.DBTableKey{ qgen.DBTableKey{"rid", "primary"}, @@ -217,6 +218,27 @@ func createTables(adapter qgen.Adapter) error { []qgen.DBTableKey{}, ) + qgen.Install.CreateTable("polls", "utf8mb4", "utf8mb4_general_ci", + []qgen.DBTableColumn{ + qgen.DBTableColumn{"pollID", "int", 0, false, true, ""}, + qgen.DBTableColumn{"type", "int", 0, false, false, "0"}, + qgen.DBTableColumn{"options", "json", 0, false, false, ""}, + qgen.DBTableColumn{"votes", "int", 0, false, false, "0"}, + }, + []qgen.DBTableKey{ + qgen.DBTableKey{"pollID", "primary"}, + }, + ) + + qgen.Install.CreateTable("polls_voters", "utf8mb4", "utf8mb4_general_ci", + []qgen.DBTableColumn{ + qgen.DBTableColumn{"pollID", "int", 0, false, false, ""}, + qgen.DBTableColumn{"uid", "int", 0, false, false, ""}, + qgen.DBTableColumn{"option", "int", 0, false, false, "0"}, + }, + []qgen.DBTableKey{}, + ) + qgen.Install.CreateTable("users_replies", "utf8mb4", "utf8mb4_general_ci", []qgen.DBTableColumn{ qgen.DBTableColumn{"rid", "int", 0, false, true, ""}, diff --git a/router_gen/main.go b/router_gen/main.go index 4d6b8d43..04f75c64 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -173,8 +173,10 @@ func main() { "duckduckgo", "discord", "cloudflarealwayson", + "uptimebot", "lynx", "blank", + "malformed", } tmplVars.AllAgentMap = make(map[string]int) @@ -276,20 +278,23 @@ func (router *GenRouter) RemoveFunc(pattern string) error { // TODO: GetDefaultRoute func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' { + if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { w.WriteHeader(405) w.Write([]byte("")) - log.Print("Suspicious UA: ", req.UserAgent()) + log.Print("Malformed Request") + log.Print("UA: ", req.UserAgent()) log.Print("Method: ", req.Method) for key, value := range req.Header { for _, vvalue := range value { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) log.Print("req.RemoteAddr: ", req.RemoteAddr) + common.AgentViewCounter.Bump({{.AllAgentMap.malformed}}) return } @@ -304,6 +309,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) @@ -319,6 +325,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Print("Header '" + key + "': " + vvalue + "!!") } } + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("req.Referer(): ", req.Referer()) @@ -342,6 +349,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) @@ -394,6 +402,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { common.AgentViewCounter.Bump({{.AllAgentMap.lynx}}) case strings.Contains(ua,"CloudFlare-AlwaysOnline"): common.AgentViewCounter.Bump({{.AllAgentMap.cloudflarealwayson}}) + case strings.Contains(ua,"Uptimebot"): + common.AgentViewCounter.Bump({{.AllAgentMap.uptimebot}}) case ua == "": common.AgentViewCounter.Bump({{.AllAgentMap.blank}}) if common.Dev.DebugMode { @@ -406,6 +416,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) @@ -423,6 +434,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } log.Print("prefix: ", prefix) + log.Print("req.Host: ", req.Host) log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.RawQuery: ", req.URL.RawQuery) log.Print("extraData: ", extraData) diff --git a/schema/mssql/query_polls.sql b/schema/mssql/query_polls.sql new file mode 100644 index 00000000..b6125582 --- /dev/null +++ b/schema/mssql/query_polls.sql @@ -0,0 +1,7 @@ +CREATE TABLE [polls] ( + [pollID] int not null IDENTITY, + [type] int DEFAULT 0 not null, + [options] nvarchar (MAX) not null, + [votes] int DEFAULT 0 not null, + primary key([pollID]) +); \ No newline at end of file diff --git a/schema/mssql/query_polls_voters.sql b/schema/mssql/query_polls_voters.sql new file mode 100644 index 00000000..8229cc95 --- /dev/null +++ b/schema/mssql/query_polls_voters.sql @@ -0,0 +1,5 @@ +CREATE TABLE [polls_voters] ( + [pollID] int not null, + [uid] int not null, + [option] int DEFAULT 0 not null +); \ No newline at end of file diff --git a/schema/mssql/query_replies.sql b/schema/mssql/query_replies.sql index 484c549f..aa74cc37 100644 --- a/schema/mssql/query_replies.sql +++ b/schema/mssql/query_replies.sql @@ -12,6 +12,6 @@ CREATE TABLE [replies] ( [likeCount] int DEFAULT 0 not null, [words] int DEFAULT 1 not null, [actionType] nvarchar (20) DEFAULT '' not null, - [poll] bit DEFAULT 0 not null, + [poll] int DEFAULT 0 not null, primary key([rid]) ); \ No newline at end of file diff --git a/schema/mssql/query_topics.sql b/schema/mssql/query_topics.sql index 3e9a789d..d0455fda 100644 --- a/schema/mssql/query_topics.sql +++ b/schema/mssql/query_topics.sql @@ -16,6 +16,7 @@ CREATE TABLE [topics] ( [words] int DEFAULT 0 not null, [views] int DEFAULT 0 not null, [css_class] nvarchar (100) DEFAULT '' not null, + [poll] int DEFAULT 0 not null, [data] nvarchar (200) DEFAULT '' not null, primary key([tid]) ); \ No newline at end of file diff --git a/schema/mysql/query_polls.sql b/schema/mysql/query_polls.sql new file mode 100644 index 00000000..475bc9c6 --- /dev/null +++ b/schema/mysql/query_polls.sql @@ -0,0 +1,7 @@ +CREATE TABLE `polls` ( + `pollID` int not null AUTO_INCREMENT, + `type` int DEFAULT 0 not null, + `options` text not null, + `votes` int DEFAULT 0 not null, + primary key(`pollID`) +) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/mysql/query_polls_voters.sql b/schema/mysql/query_polls_voters.sql new file mode 100644 index 00000000..a7d6d1ba --- /dev/null +++ b/schema/mysql/query_polls_voters.sql @@ -0,0 +1,5 @@ +CREATE TABLE `polls_voters` ( + `pollID` int not null, + `uid` int not null, + `option` int DEFAULT 0 not null +) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/mysql/query_replies.sql b/schema/mysql/query_replies.sql index 5e16708d..636b46d5 100644 --- a/schema/mysql/query_replies.sql +++ b/schema/mysql/query_replies.sql @@ -12,6 +12,6 @@ CREATE TABLE `replies` ( `likeCount` int DEFAULT 0 not null, `words` int DEFAULT 1 not null, `actionType` varchar(20) DEFAULT '' not null, - `poll` boolean DEFAULT 0 not null, + `poll` int DEFAULT 0 not null, primary key(`rid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/mysql/query_topics.sql b/schema/mysql/query_topics.sql index 9713466c..10f2a89d 100644 --- a/schema/mysql/query_topics.sql +++ b/schema/mysql/query_topics.sql @@ -16,6 +16,7 @@ CREATE TABLE `topics` ( `words` int DEFAULT 0 not null, `views` int DEFAULT 0 not null, `css_class` varchar(100) DEFAULT '' not null, + `poll` int DEFAULT 0 not null, `data` varchar(200) DEFAULT '' not null, primary key(`tid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; \ No newline at end of file diff --git a/schema/pgsql/query_polls.sql b/schema/pgsql/query_polls.sql new file mode 100644 index 00000000..6c46e6df --- /dev/null +++ b/schema/pgsql/query_polls.sql @@ -0,0 +1,7 @@ +CREATE TABLE `polls` ( + `pollID` serial not null, + `type` int DEFAULT 0 not null, + `options` json not null, + `votes` int DEFAULT 0 not null, + primary key(`pollID`) +); \ No newline at end of file diff --git a/schema/pgsql/query_polls_voters.sql b/schema/pgsql/query_polls_voters.sql new file mode 100644 index 00000000..ff249360 --- /dev/null +++ b/schema/pgsql/query_polls_voters.sql @@ -0,0 +1,5 @@ +CREATE TABLE `polls_voters` ( + `pollID` int not null, + `uid` int not null, + `option` int DEFAULT 0 not null +); \ No newline at end of file diff --git a/schema/pgsql/query_replies.sql b/schema/pgsql/query_replies.sql index a127a868..4139cb79 100644 --- a/schema/pgsql/query_replies.sql +++ b/schema/pgsql/query_replies.sql @@ -12,6 +12,6 @@ CREATE TABLE `replies` ( `likeCount` int DEFAULT 0 not null, `words` int DEFAULT 1 not null, `actionType` varchar (20) DEFAULT '' not null, - `poll` boolean DEFAULT 0 not null, + `poll` int DEFAULT 0 not null, primary key(`rid`) ); \ No newline at end of file diff --git a/schema/pgsql/query_topics.sql b/schema/pgsql/query_topics.sql index 9d256376..bf93f1b8 100644 --- a/schema/pgsql/query_topics.sql +++ b/schema/pgsql/query_topics.sql @@ -16,6 +16,7 @@ CREATE TABLE `topics` ( `words` int DEFAULT 0 not null, `views` int DEFAULT 0 not null, `css_class` varchar (100) DEFAULT '' not null, + `poll` int DEFAULT 0 not null, `data` varchar (200) DEFAULT '' not null, primary key(`tid`) ); \ No newline at end of file diff --git a/template_list.go b/template_list.go index bc65f5b0..a7f498b1 100644 --- a/template_list.go +++ b/template_list.go @@ -900,9 +900,19 @@ var topics_23 = []byte(` +
+
+
+ + + +
+
+
+ `) var topics_24 = []byte(` @@ -1071,9 +1081,15 @@ var forum_21 = []byte(`" type="hidden">
+
+
+ Poll stuff +
+
+ `) var forum_22 = []byte(` diff --git a/templates/forum.html b/templates/forum.html index 7e07e19f..92a80417 100644 --- a/templates/forum.html +++ b/templates/forum.html @@ -55,9 +55,15 @@
+
+
+ Poll stuff +
+
+ {{if .CurrentUser.Perms.UploadFiles}} diff --git a/templates/topics.html b/templates/topics.html index 46924161..c2187b43 100644 --- a/templates/topics.html +++ b/templates/topics.html @@ -74,9 +74,19 @@
+
+
+
+ + + +
+
+
+ {{if .CurrentUser.Perms.UploadFiles}} diff --git a/themes/cosora/public/main.css b/themes/cosora/public/main.css index b5d265af..4e458d1f 100644 --- a/themes/cosora/public/main.css +++ b/themes/cosora/public/main.css @@ -502,6 +502,20 @@ input, select { width: 100%; } +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + width: 10px; + border: black; + background-color: var(--element-background-color); +} +.poll_content_row { + padding-left: 20px; + padding-top: 4px; + padding-bottom: 2px; +} + .formbutton { margin-top: 12px; margin-left: auto; @@ -534,6 +548,9 @@ input, select { margin-left: 10px; margin-top: 8px; } +.quick_button_row #add_poll_button { + background: hsl(209, 47%, 56%); +} .quick_button_row .add_file_button { background: hsl(129, 57%, 56%); }