2018-03-17 08:16:43 +00:00
package routes
2017-12-24 07:38:46 +00:00
import (
2022-02-21 03:53:13 +00:00
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
2017-12-24 07:38:46 +00:00
2022-02-21 03:53:13 +00:00
c "git.tuxpa.in/a/gosora/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
2019-04-19 06:36:26 +00:00
func RobotsTxt ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
// TODO: Do we have to put * or something at the end of the paths?
_ , _ = 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
` ) )
2022-02-21 03:32:53 +00:00
return nil
2017-12-24 07:38:46 +00:00
}
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 ) {
2022-02-21 03:32:53 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/xml" )
w . Write ( [ ] byte ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ) )
2017-12-24 07:38:46 +00:00
}
// TODO: Keep track of when a sitemap was last modifed and add a lastmod element for it
2019-04-19 06:36:26 +00:00
func SitemapXml ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
var s string
if c . Config . SslSchema {
s = "s"
}
sitemapItem := func ( path string ) {
w . Write ( [ ] byte ( ` < sitemap >
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
2017-12-24 07:38:46 +00:00
< / sitemap >
` ) )
2022-02-21 03:32:53 +00:00
}
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
sitemapItem ( "sitemaps/topics.xml" )
//sitemapItem("sitemaps/forums.xml")
//sitemapItem("sitemaps/users.xml")
w . Write ( [ ] byte ( "</sitemapindex>" ) )
return nil
2017-12-24 07:38:46 +00:00
}
type FuzzyRoute struct {
2022-02-21 03:32:53 +00:00
Path string
Handle func ( http . ResponseWriter , * http . Request , int ) c . RouteError
2017-12-24 07:38:46 +00:00
}
// TODO: Add a sitemap API and clean things up
// TODO: ^-- Make sure that the API is concurrent
// TODO: Add a social group sitemap
2019-04-19 06:36:26 +00:00
var sitemapRoutes = map [ string ] func ( http . ResponseWriter , * http . Request ) c . RouteError {
2022-02-21 03:32:53 +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 {
2022-02-21 03:32:53 +00:00
"topics_page_" : { "topics_page_(%d).xml" , SitemapTopic } ,
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
func sitemapSwitch ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
path := r . URL . Path [ len ( "/sitemaps/" ) : ]
for name , fuzzy := range fuzzySitemapRoutes {
if strings . HasPrefix ( path , name ) && strings . HasSuffix ( path , ".xml" ) {
spath := strings . TrimPrefix ( path , name )
spath = strings . TrimSuffix ( spath , ".xml" )
page , err := strconv . Atoi ( spath )
if err != nil {
// ? What's this? Do we need it? Was it just a quick trace?
c . DebugLogf ( "Unable to convert string '%s' to integer in fuzzy route" , spath )
return c . NotFound ( w , r , nil )
}
return fuzzy . Handle ( w , r , page )
}
}
route , ok := sitemapRoutes [ path ]
if ! ok {
return c . NotFound ( w , r , nil )
}
return route ( w , r )
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
func SitemapForums ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
var s string
if c . Config . SslSchema {
s = "s"
}
sitemapItem := func ( path string ) {
w . Write ( [ ] byte ( ` < url >
< loc > http ` + s + ` : //` + c.Site.URL + path + `</loc>
2017-12-24 07:38:46 +00:00
< / url >
` ) )
2022-02-21 03:32:53 +00:00
}
group , err := c . Groups . Get ( c . GuestUser . Group )
if err != nil {
return c . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
}
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
f := c . Forums . DirtyGet ( fid ) . Copy ( )
if f . ParentID == 0 && f . Name != "" && f . Active {
sitemapItem ( c . BuildForumURL ( c . NameToSlug ( f . Name ) , f . ID ) )
}
}
w . Write ( [ ] byte ( "</urlset>" ) )
return nil
2017-12-24 07:38:46 +00:00
}
// 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
2019-04-19 06:36:26 +00:00
func SitemapTopics ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
var s string
if c . Config . SslSchema {
s = "s"
}
sitemapItem := func ( path string ) {
w . Write ( [ ] byte ( ` < sitemap >
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
2017-12-24 07:38:46 +00:00
< / sitemap >
` ) )
2022-02-21 03:32:53 +00:00
}
group , err := c . Groups . Get ( c . GuestUser . Group )
if err != nil {
return c . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
}
var visibleForums [ ] c . Forum
for _ , fid := range group . CanSee {
forum := c . Forums . DirtyGet ( fid )
if forum . Name != "" && forum . Active {
visibleForums = append ( visibleForums , forum . Copy ( ) )
}
}
topicCount , err := c . TopicCountInForums ( visibleForums )
if err != nil {
return c . InternalErrorXML ( err , w , r )
}
pageCount := topicCount / sitemapPageCap
//log.Print("topicCount", topicCount)
//log.Print("pageCount", pageCount)
writeXMLHeader ( w , r )
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
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
func SitemapTopic ( w http . ResponseWriter , r * http . Request , page int ) c . RouteError {
2022-02-21 03:32:53 +00:00
/ * var s string
if c . Config . SslSchema {
s = "s"
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < url >
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
< / url >
` ) )
} * /
group , err := c . Groups . Get ( c . GuestUser . Group )
if err != nil {
return c . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
}
var visibleForums [ ] c . Forum
for _ , fid := range group . CanSee {
forum := c . Forums . DirtyGet ( fid )
if forum . Name != "" && forum . Active {
visibleForums = append ( visibleForums , forum . Copy ( ) )
}
}
argList , qlist := c . ForumListToArgQ ( visibleForums )
topicCount , err := c . ArgQToTopicCount ( argList , qlist )
if err != nil {
return c . InternalErrorXML ( err , w , r )
}
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
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
func SitemapUsers ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
writeXMLHeader ( w , r )
w . Write ( [ ] byte ( "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" ) )
return nil
2017-12-24 07:38:46 +00:00
}
2018-08-11 15:53:42 +00:00
type JsonMe struct {
2022-02-21 03:32:53 +00:00
User * c . MeUser
Site MeSite
2018-08-11 15:53:42 +00:00
}
2019-04-19 06:36:26 +00:00
// We don't want to expose too much information about the site, so we'll make this a small subset of c.site
2018-08-11 15:53:42 +00:00
type MeSite struct {
2022-02-21 03:32:53 +00:00
MaxReqSize int
StaticPrefix string
2018-08-11 15:53:42 +00:00
}
// 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?
2020-03-18 09:21:34 +00:00
func APIMe ( w http . ResponseWriter , r * http . Request , u * c . User ) c . RouteError {
2022-02-21 03:32:53 +00:00
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
h := w . Header ( )
h . Set ( "Content-Type" , "application/json" )
// We don't want an intermediary accidentally caching this
// TODO: Use this header anywhere with a user check?
h . Set ( "Cache-Control" , "private" )
me := JsonMe { u . Me ( ) , MeSite { c . Site . MaxRequestSize , c . StaticFiles . Prefix } }
jsonBytes , err := json . Marshal ( me )
if err != nil {
return c . InternalErrorJS ( err , w , r )
}
w . Write ( jsonBytes )
return nil
2018-08-11 15:53:42 +00:00
}
2019-05-08 05:08:38 +00:00
func OpenSearchXml ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2022-02-21 03:32:53 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/xml" )
furl := "http"
if c . Config . SslSchema {
furl += "s"
}
furl += "://" + c . Site . URL
w . Write ( [ ] byte ( ` < OpenSearchDescription xmlns = "http://a9.com/-/spec/opensearch/1.1/" xmlns : moz = "http://www.mozilla.org/2006/browser/search/" >
< ShortName > ` + c.Site.Name + ` < / ShortName >
< InputEncoding > UTF - 8 < / InputEncoding >
< Url type = "text/html" template = "` + furl + `/topics/?q={searchTerms}" / >
< Url type = "application/opensearchdescription+xml" rel = "self" template = "` + furl + `/opensearch.xml" / >
< moz : SearchForm > ` + furl + ` < / moz : SearchForm >
2019-05-08 05:08:38 +00:00
< / OpenSearchDescription > ` ) )
2022-02-21 03:32:53 +00:00
return nil
2019-05-08 05:08:38 +00:00
}