2018-03-17 08:16:43 +00:00
package routes
2017-12-24 07:38:46 +00:00
import (
2018-08-11 15:53:42 +00:00
"encoding/json"
2017-12-26 07:17:26 +00:00
"errors"
2017-12-24 07:38:46 +00:00
"net/http"
"strconv"
"strings"
2018-03-17 08:16:43 +00:00
"../common"
2017-12-24 07:38:46 +00:00
)
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds
2018-03-17 08:16:43 +00:00
func RobotsTxt ( w http . ResponseWriter , r * http . Request ) common . RouteError {
2018-01-15 08:24:18 +00:00
// TODO: Do we have to put * or something at the end of the paths?
2017-12-24 07:38:46 +00:00
_ , _ = w . Write ( [ ] byte ( ` User - agent : *
2018-01-18 12:31:25 +00:00
Disallow : / panel / *
2017-12-24 07:38:46 +00:00
Disallow : / topics / create /
2018-01-18 12:31:25 +00:00
Disallow : / user / edit / *
Disallow : / accounts / *
Disallow : / report / *
2017-12-24 07:38:46 +00:00
` ) )
return nil
}
var sitemapPageCap = 40000 // 40k, bump it up to 50k once we gzip this? Does brotli work on sitemaps?
func writeXMLHeader ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/xml" )
w . Write ( [ ] byte ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ) )
}
// TODO: Keep track of when a sitemap was last modifed and add a lastmod element for it
2018-03-17 08:16:43 +00:00
func SitemapXml ( w http . ResponseWriter , r * http . Request ) common . RouteError {
2017-12-24 07:38:46 +00:00
var sslBit string
if common . Site . EnableSsl {
sslBit = "s"
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < sitemap >
< loc > http ` + sslBit + ` : //` + common.Site.URL + "/" + path + `</loc>
< / sitemap >
` ) )
}
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
sitemapItem ( "sitemaps/topics.xml" )
2017-12-24 22:08:35 +00:00
//sitemapItem("sitemaps/forums.xml")
2017-12-24 07:38:46 +00:00
//sitemapItem("sitemaps/users.xml")
w . Write ( [ ] byte ( "</sitemapindex>" ) )
return nil
}
type FuzzyRoute struct {
Path string
Handle func ( http . ResponseWriter , * http . Request , int ) common . RouteError
}
// TODO: Add a sitemap API and clean things up
// TODO: ^-- Make sure that the API is concurrent
// TODO: Add a social group sitemap
var sitemapRoutes = map [ string ] func ( http . ResponseWriter , * http . Request ) common . RouteError {
2018-03-17 08:16:43 +00:00
"forums.xml" : SitemapForums ,
"topics.xml" : SitemapTopics ,
2017-12-24 07:38:46 +00:00
}
// TODO: Use a router capable of parsing this rather than hard-coding the logic in
var fuzzySitemapRoutes = map [ string ] FuzzyRoute {
2018-03-17 08:16:43 +00:00
"topics_page_" : FuzzyRoute { "topics_page_(%d).xml" , SitemapTopic } ,
2017-12-24 07:38:46 +00:00
}
func sitemapSwitch ( w http . ResponseWriter , r * http . Request ) common . RouteError {
var path = r . URL . Path [ len ( "/sitemaps/" ) : ]
for name , fuzzy := range fuzzySitemapRoutes {
if strings . HasPrefix ( path , name ) && strings . HasSuffix ( path , ".xml" ) {
var spath = strings . TrimPrefix ( path , name )
spath = strings . TrimSuffix ( spath , ".xml" )
page , err := strconv . Atoi ( spath )
if err != nil {
2018-02-19 04:26:01 +00:00
// ? What's this? Do we need it? Was it just a quick trace?
common . DebugLogf ( "Unable to convert string '%s' to integer in fuzzy route" , spath )
return common . NotFound ( w , r , nil )
2017-12-24 07:38:46 +00:00
}
return fuzzy . Handle ( w , r , page )
}
}
route , ok := sitemapRoutes [ path ]
if ! ok {
2018-02-19 04:26:01 +00:00
return common . NotFound ( w , r , nil )
2017-12-24 07:38:46 +00:00
}
return route ( w , r )
}
2018-03-17 08:16:43 +00:00
func SitemapForums ( w http . ResponseWriter , r * http . Request ) common . RouteError {
2017-12-24 07:38:46 +00:00
var sslBit string
if common . Site . EnableSsl {
sslBit = "s"
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < url >
< loc > http ` + sslBit + ` : //` + common.Site.URL + path + `</loc>
< / url >
` ) )
}
group , err := common . Groups . Get ( common . GuestUser . Group )
if err != nil {
2017-12-26 07:17:26 +00:00
return common . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
2017-12-24 07:38:46 +00:00
}
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
for _ , fid := range group . CanSee {
// Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else
var forum = common . Forums . DirtyGet ( fid ) . Copy ( )
if forum . ParentID == 0 && forum . Name != "" && forum . Active {
sitemapItem ( common . BuildForumURL ( common . NameToSlug ( forum . Name ) , forum . ID ) )
}
}
w . Write ( [ ] byte ( "</urlset>" ) )
return nil
}
// TODO: Add a global ratelimit. 10 50MB files (smaller if compressed better) per minute?
// ? We might have problems with banned users, if they have fewer ViewTopic permissions than guests as they'll be able to see this list. Then again, a banned user could just logout to see it
2018-03-17 08:16:43 +00:00
func SitemapTopics ( w http . ResponseWriter , r * http . Request ) common . RouteError {
2017-12-24 07:38:46 +00:00
var sslBit string
if common . Site . EnableSsl {
sslBit = "s"
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < sitemap >
< loc > http ` + sslBit + ` : //` + common.Site.URL + "/" + path + `</loc>
< / sitemap >
` ) )
}
group , err := common . Groups . Get ( common . GuestUser . Group )
if err != nil {
2017-12-26 07:17:26 +00:00
return common . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
2017-12-24 07:38:46 +00:00
}
2017-12-26 07:17:26 +00:00
var visibleForums [ ] common . Forum
2017-12-24 07:38:46 +00:00
for _ , fid := range group . CanSee {
forum := common . Forums . DirtyGet ( fid )
if forum . Name != "" && forum . Active {
2017-12-26 07:17:26 +00:00
visibleForums = append ( visibleForums , forum . Copy ( ) )
2017-12-24 07:38:46 +00:00
}
}
2017-12-26 07:17:26 +00:00
topicCount , err := common . TopicCountInForums ( visibleForums )
2017-12-24 07:38:46 +00:00
if err != nil {
2017-12-26 07:17:26 +00:00
return common . InternalErrorXML ( err , w , r )
2017-12-24 07:38:46 +00:00
}
var pageCount = topicCount / sitemapPageCap
//log.Print("topicCount", topicCount)
//log.Print("pageCount", pageCount)
2017-12-26 07:17:26 +00:00
writeXMLHeader ( w , r )
2017-12-24 07:38:46 +00:00
w . Write ( [ ] byte ( "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
for i := 0 ; i <= pageCount ; i ++ {
sitemapItem ( "sitemaps/topics_page_" + strconv . Itoa ( i ) + ".xml" )
}
w . Write ( [ ] byte ( "</sitemapindex>" ) )
return nil
}
2018-03-17 08:16:43 +00:00
func SitemapTopic ( w http . ResponseWriter , r * http . Request , page int ) common . RouteError {
2017-12-24 07:38:46 +00:00
/ * var sslBit string
if common . Site . EnableSsl {
sslBit = "s"
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < url >
< loc > http ` + sslBit + ` : //` + common.Site.URL + "/" + path + `</loc>
< / url >
` ) )
} * /
group , err := common . Groups . Get ( common . GuestUser . Group )
if err != nil {
2017-12-26 07:17:26 +00:00
return common . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
2017-12-24 07:38:46 +00:00
}
2017-12-26 07:17:26 +00:00
var visibleForums [ ] common . Forum
2017-12-24 07:38:46 +00:00
for _ , fid := range group . CanSee {
forum := common . Forums . DirtyGet ( fid )
if forum . Name != "" && forum . Active {
2017-12-26 07:17:26 +00:00
visibleForums = append ( visibleForums , forum . Copy ( ) )
2017-12-24 07:38:46 +00:00
}
}
2017-12-26 07:17:26 +00:00
argList , qlist := common . ForumListToArgQ ( visibleForums )
topicCount , err := common . ArgQToTopicCount ( argList , qlist )
2017-12-24 07:38:46 +00:00
if err != nil {
2017-12-26 07:17:26 +00:00
return common . InternalErrorXML ( err , w , r )
2017-12-24 07:38:46 +00:00
}
var pageCount = topicCount / sitemapPageCap
//log.Print("topicCount", topicCount)
//log.Print("pageCount", pageCount)
//log.Print("page",page)
if page > pageCount {
page = pageCount
}
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
w . Write ( [ ] byte ( "</urlset>" ) )
return nil
}
2018-03-17 08:16:43 +00:00
func SitemapUsers ( w http . ResponseWriter , r * http . Request ) common . RouteError {
2017-12-24 07:38:46 +00:00
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
return nil
}
2018-08-11 15:53:42 +00:00
type JsonMe struct {
User * common . MeUser
Site MeSite
}
// We don't want to expose too much information about the site, so we'll make this a small subset of common.site
type MeSite struct {
URL string
MaxRequestSize int
}
// APIMe returns information about the current logged-in user
// TODO: Find some way to stop intermediaries from doing compression to avoid the BREACH attack
// TODO: Decouple site settings into a different API? I'd like to avoid having too many requests, if possible, maybe we can use a different name for this?
func APIMe ( w http . ResponseWriter , r * http . Request , user common . User ) common . RouteError {
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
w . Header ( ) . Set ( "Content-Type" , "application/json" )
// We don't want an intermediary accidentally caching this
// TODO: Use this header anywhere with a user check?
w . Header ( ) . Set ( "Cache-Control" , "private" )
me := JsonMe { ( & user ) . Me ( ) , MeSite { common . Site . URL , common . Site . MaxRequestSize } }
jsonBytes , err := json . Marshal ( me )
if err != nil {
return common . InternalErrorJS ( err , w , r )
}
w . Write ( jsonBytes )
return nil
}