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"
2019-04-19 06:36:26 +00:00
c "github.com/Azareal/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 {
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
2019-04-19 06:36:26 +00:00
func SitemapXml ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2019-11-04 11:55:52 +00:00
var s string
if c . Config . SslSchema {
s = "s"
2017-12-24 07:38:46 +00:00
}
2019-10-01 21:06:22 +00:00
sitemapItem := func ( path string ) {
2017-12-24 07:38:46 +00:00
w . Write ( [ ] byte ( ` < sitemap >
2019-11-04 11:55:52 +00:00
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
2017-12-24 07:38:46 +00:00
< / 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
2019-04-19 06:36:26 +00:00
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 {
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
}
2019-04-19 06:36:26 +00:00
func sitemapSwitch ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2019-10-01 21:06:22 +00:00
path := r . URL . Path [ len ( "/sitemaps/" ) : ]
2017-12-24 07:38:46 +00:00
for name , fuzzy := range fuzzySitemapRoutes {
if strings . HasPrefix ( path , name ) && strings . HasSuffix ( path , ".xml" ) {
2019-10-01 21:06:22 +00:00
spath := strings . TrimPrefix ( path , name )
2017-12-24 07:38:46 +00:00
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?
2019-04-19 06:36:26 +00:00
c . DebugLogf ( "Unable to convert string '%s' to integer in fuzzy route" , spath )
return c . NotFound ( w , r , nil )
2017-12-24 07:38:46 +00:00
}
return fuzzy . Handle ( w , r , page )
}
}
route , ok := sitemapRoutes [ path ]
if ! ok {
2019-04-19 06:36:26 +00:00
return c . NotFound ( w , r , nil )
2017-12-24 07:38:46 +00:00
}
return route ( w , r )
}
2019-04-19 06:36:26 +00:00
func SitemapForums ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2019-11-04 11:55:52 +00:00
var s string
if c . Config . SslSchema {
s = "s"
2017-12-24 07:38:46 +00:00
}
2019-10-01 21:06:22 +00:00
sitemapItem := func ( path string ) {
2017-12-24 07:38:46 +00:00
w . Write ( [ ] byte ( ` < url >
2019-11-04 11:55:52 +00:00
< loc > http ` + s + ` : //` + c.Site.URL + path + `</loc>
2017-12-24 07:38:46 +00:00
< / url >
` ) )
}
2019-04-19 06:36:26 +00:00
group , err := c . Groups . Get ( c . GuestUser . Group )
2017-12-24 07:38:46 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . 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
2019-10-01 21:06:22 +00:00
f := c . Forums . DirtyGet ( fid ) . Copy ( )
if f . ParentID == 0 && f . Name != "" && f . Active {
sitemapItem ( c . BuildForumURL ( c . NameToSlug ( f . Name ) , f . ID ) )
2017-12-24 07:38:46 +00:00
}
}
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
2019-04-19 06:36:26 +00:00
func SitemapTopics ( w http . ResponseWriter , r * http . Request ) c . RouteError {
2019-11-04 11:55:52 +00:00
var s string
if c . Config . SslSchema {
s = "s"
2017-12-24 07:38:46 +00:00
}
2019-10-01 21:06:22 +00:00
sitemapItem := func ( path string ) {
2017-12-24 07:38:46 +00:00
w . Write ( [ ] byte ( ` < sitemap >
2019-11-04 11:55:52 +00:00
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
2017-12-24 07:38:46 +00:00
< / sitemap >
` ) )
}
2019-04-19 06:36:26 +00:00
group , err := c . Groups . Get ( c . GuestUser . Group )
2017-12-24 07:38:46 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
var visibleForums [ ] c . Forum
2017-12-24 07:38:46 +00:00
for _ , fid := range group . CanSee {
2019-04-19 06:36:26 +00:00
forum := c . Forums . DirtyGet ( fid )
2017-12-24 07:38:46 +00:00
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
}
}
2019-04-19 06:36:26 +00:00
topicCount , err := c . TopicCountInForums ( visibleForums )
2017-12-24 07:38:46 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorXML ( err , w , r )
2017-12-24 07:38:46 +00:00
}
2019-10-01 21:06:22 +00:00
pageCount := topicCount / sitemapPageCap
2017-12-24 07:38:46 +00:00
//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
}
2019-04-19 06:36:26 +00:00
func SitemapTopic ( w http . ResponseWriter , r * http . Request , page int ) c . RouteError {
2019-11-04 11:55:52 +00:00
/ * var s string
if c . Config . SslSchema {
s = "s"
2017-12-24 07:38:46 +00:00
}
var sitemapItem = func ( path string ) {
w . Write ( [ ] byte ( ` < url >
2019-11-04 11:55:52 +00:00
< loc > http ` + s + ` : //` + c.Site.URL + "/" + path + `</loc>
2017-12-24 07:38:46 +00:00
< / url >
` ) )
} * /
2019-04-19 06:36:26 +00:00
group , err := c . Groups . Get ( c . GuestUser . Group )
2017-12-24 07:38:46 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . SilentInternalErrorXML ( errors . New ( "The guest group doesn't exist for some reason" ) , w , r )
2017-12-24 07:38:46 +00:00
}
2019-04-19 06:36:26 +00:00
var visibleForums [ ] c . Forum
2017-12-24 07:38:46 +00:00
for _ , fid := range group . CanSee {
2019-04-19 06:36:26 +00:00
forum := c . Forums . DirtyGet ( fid )
2017-12-24 07:38:46 +00:00
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
}
}
2019-04-19 06:36:26 +00:00
argList , qlist := c . ForumListToArgQ ( visibleForums )
topicCount , err := c . ArgQToTopicCount ( argList , qlist )
2017-12-24 07:38:46 +00:00
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorXML ( err , w , r )
2017-12-24 07:38:46 +00:00
}
2019-10-01 21:06:22 +00:00
pageCount := topicCount / sitemapPageCap
2017-12-24 07:38:46 +00:00
//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
}
2019-04-19 06:36:26 +00:00
func SitemapUsers ( w http . ResponseWriter , r * http . Request ) c . 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 {
2019-04-19 06:36:26 +00:00
User * c . MeUser
2018-08-11 15:53:42 +00:00
Site MeSite
}
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 {
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?
2020-03-15 07:06:44 +00:00
func APIMe ( w http . ResponseWriter , r * http . Request , u * c . User ) c . RouteError {
2018-08-11 15:53:42 +00:00
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
2020-03-04 10:09:39 +00:00
h := w . Header ( )
h . Set ( "Content-Type" , "application/json" )
2018-08-11 15:53:42 +00:00
// We don't want an intermediary accidentally caching this
// TODO: Use this header anywhere with a user check?
2020-03-04 10:09:39 +00:00
h . Set ( "Cache-Control" , "private" )
2018-08-11 15:53:42 +00:00
2020-03-15 07:06:44 +00:00
me := JsonMe { u . Me ( ) , MeSite { c . Site . MaxRequestSize } }
2018-08-11 15:53:42 +00:00
jsonBytes , err := json . Marshal ( me )
if err != nil {
2019-04-19 06:36:26 +00:00
return c . InternalErrorJS ( err , w , r )
2018-08-11 15:53:42 +00:00
}
w . Write ( jsonBytes )
return nil
}
2019-05-08 05:08:38 +00:00
func OpenSearchXml ( w http . ResponseWriter , r * http . Request ) c . RouteError {
furl := "http"
2019-11-04 11:55:52 +00:00
if c . Config . SslSchema {
2019-05-08 05:08:38 +00:00
furl += "s"
}
2019-10-01 21:06:22 +00:00
furl += "://" + c . Site . URL
2019-05-08 05:08:38 +00:00
w . Write ( [ ] byte ( ` < OpenSearchDescription xmlns = "http://a9.com/-/spec/opensearch/1.1/" xmlns : moz = "http://www.mozilla.org/2006/browser/search/" >
2019-10-01 21:06:22 +00:00
< ShortName > ` + c.Site.Name + ` < / ShortName >
2019-05-08 05:08:38 +00:00
< InputEncoding > UTF - 8 < / InputEncoding >
2019-10-01 21:06:22 +00:00
< 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 > ` ) )
return nil
}