2017-07-08 23:13:36 +00:00
package yatwiki3
import (
2017-07-09 00:15:49 +00:00
"database/sql"
2017-07-09 01:00:45 +00:00
"errors"
"fmt"
2017-07-08 23:13:36 +00:00
"html/template"
"log"
"net/http"
2017-07-09 01:00:45 +00:00
"net/url"
2017-07-09 01:18:18 +00:00
"strconv"
2017-07-09 00:15:49 +00:00
"strings"
2017-07-08 23:13:36 +00:00
)
type WikiServer struct {
db * WikiDB
opts * ServerOptions
pageTmp * template . Template
}
func NewWikiServer ( opts * ServerOptions ) ( * WikiServer , error ) {
wdb , err := NewWikiDB ( opts . DBFilePath )
if err != nil {
return nil , err
}
tmpl , err := template . New ( "yatwiki/page" ) . Parse ( pageTemplate )
if err != nil {
panic ( err )
}
ws := WikiServer {
db : wdb ,
opts : opts ,
pageTmp : tmpl ,
}
return & ws , nil
}
func ( this * WikiServer ) Close ( ) {
this . db . Close ( )
}
func ( this * WikiServer ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Server" , "Yatwiki3" )
2017-07-09 00:15:49 +00:00
if r . Method == "GET" {
if r . URL . Path == this . opts . ExpectBaseURL + "wiki.css" {
w . Header ( ) . Set ( "Content-Type" , "text/css" )
content , _ := wikiCssBytes ( )
w . Write ( content )
return
} else if r . URL . Path == this . opts . ExpectBaseURL + "highlight.js" {
w . Header ( ) . Set ( "Content-Type" , "application/javascript" )
content , _ := highlightJsBytes ( )
w . Write ( content )
return
2017-07-09 00:14:28 +00:00
} else if r . URL . Path == this . opts . ExpectBaseURL + "favicon.ico" && len ( this . opts . FaviconFilePath ) > 0 {
w . Header ( ) . Set ( "Content-Type" , "image/x-icon" )
http . ServeFile ( w , r , this . opts . FaviconFilePath )
return
2017-07-09 00:15:49 +00:00
} else if r . URL . Path == this . opts . ExpectBaseURL + "formatting" {
pto := DefaultPageTemplateOptions ( this . opts )
pto . CurrentPageName = "Formatting help"
pto . Content = `
< h2 > Formatting help < / h2 > < br > < br >
< ul >
< li > [ h ] header [ / h ] < / li >
< li > [ b ] bold [ / b ] < / li >
< li > [ u ] underline [ / u ] < / li >
< li > [ i ] italic [ / i ] < / li >
< li > [ s ] strikethrough [ / s ] < / li >
< li > [ spoiler ] spoiler [ / spoiler ] < / li >
< li > [ list ] item [ * ] item [ / list ] < / li >
< li > [ url = address ] title [ / url ] < / li >
< li > [ article = page name ] title [ / article ] or [ rev = id ] title [ / rev ] < / li >
< li > [ img ] image - url [ / img ] < / li >
< li > [ imgur ] asdf . jpg [ / imgur ] < / li >
< li > [ code ] fixed width [ / code ] < / li >
< li > [ section = header ] content [ / section ] < / li >
< li > [ html ] raw html [ / html ] < / li >
< / ul > `
2017-07-09 01:00:45 +00:00
this . servePageResponse ( w , r , pto )
return
} else if r . URL . Path == this . opts . ExpectBaseURL + "index" {
titles , err := this . db . ListTitles ( )
if err != nil {
this . serveInternalError ( w , r , err )
return
}
totalRevs , err := this . db . TotalRevisions ( )
if err != nil {
this . serveInternalError ( w , r , err )
return
}
content := fmt . Sprintf ( ` <h2>Article Index</h2><br><em>There are %d edits to %d pages.</em><br><br><ul> ` , totalRevs , len ( titles ) )
for _ , title := range titles {
content += ` <li><a href=" ` + template . HTMLEscapeString ( this . opts . ExpectBaseURL + ` view/ ` + url . QueryEscape ( title ) ) + ` "> ` + template . HTMLEscapeString ( title ) + ` </a></li> `
}
content += ` </ul> `
pto := DefaultPageTemplateOptions ( this . opts )
pto . CurrentPageName = "Index"
pto . Content = template . HTML ( content )
this . servePageResponse ( w , r , pto )
2017-07-09 00:15:49 +00:00
return
} else if strings . HasPrefix ( r . URL . Path , this . opts . ExpectBaseURL + "view/" ) {
2017-07-09 01:00:11 +00:00
articleTitle , err := url . QueryUnescape ( r . URL . Path [ len ( this . opts . ExpectBaseURL + "view/" ) : ] )
if err != nil {
this . serveErrorMessage ( w , err )
return
}
2017-07-09 00:15:49 +00:00
a , err := this . db . GetLatestVersion ( articleTitle )
if err != nil {
2017-07-09 01:00:45 +00:00
if err == sql . ErrNoRows {
2017-07-09 01:04:45 +00:00
// Yatwiki2 always required a trailing slash at the end of the URL
// If this was an old link, it might not be present.
// Redirect if possible
if len ( articleTitle ) > 0 && articleTitle [ len ( articleTitle ) - 1 ] == '/' {
this . serveRedirect ( w , this . opts . ExpectBaseURL + "view/" + url . QueryEscape ( articleTitle [ 0 : len ( articleTitle ) - 1 ] ) )
return
}
2017-07-09 01:00:45 +00:00
this . serveErrorHTMLMessage ( w , this . noSuchArticleError ( articleTitle ) )
return
}
2017-07-09 00:15:49 +00:00
this . serveErrorMessage ( w , err )
return
}
pto := DefaultPageTemplateOptions ( this . opts )
pto . CurrentPageName = articleTitle
pto . CurrentPageIsArticle = true
bcr := NewBBCodeRenderer ( this . opts . ExpectBaseURL )
pto . Content = bcr . RenderHTML ( string ( a . Body ) )
pto . LoadCodeResources = bcr . CodePresent
2017-07-09 01:00:45 +00:00
this . servePageResponse ( w , r , pto )
2017-07-09 00:15:49 +00:00
return
2017-07-09 01:18:18 +00:00
} else if strings . HasPrefix ( r . URL . Path , this . opts . ExpectBaseURL + "raw/" ) {
revId , err := strconv . Atoi ( r . URL . Path [ len ( this . opts . ExpectBaseURL + "raw/" ) : ] )
if err != nil {
this . serveErrorMessage ( w , err )
return
}
a , err := this . db . GetRevision ( revId )
if err != nil {
if err == sql . ErrNoRows {
this . serveErrorMessage ( w , errors . New ( "No such revision." ) )
return
}
this . serveErrorMessage ( w , err )
return
}
w . Header ( ) . Set ( ` Content-Type ` , ` text/plain; charset=UTF-8 ` )
w . WriteHeader ( 200 )
w . Write ( a . Body )
return
2017-07-09 00:15:49 +00:00
}
}
// No match? Add 'Page not found' to next session response, and redirect to homepage
2017-07-09 01:00:45 +00:00
this . serveErrorMessage ( w , errors . New ( "Page not found" ) )
2017-07-09 00:15:49 +00:00
2017-07-09 01:00:45 +00:00
}
2017-07-09 00:15:49 +00:00
2017-07-09 01:00:45 +00:00
func ( this * WikiServer ) noSuchArticleError ( title string ) template . HTML {
return template . HTML ( ` No such article exists. <a href=" ` + this . opts . ExpectBaseURL + ` modify/ ` + template . HTMLEscapeString ( url . QueryEscape ( title ) ) + ` ">Click here</a> to create it. ` )
2017-07-09 00:15:49 +00:00
}
func ( this * WikiServer ) serveErrorMessage ( w http . ResponseWriter , message error ) {
2017-07-09 01:00:45 +00:00
this . serveErrorHTMLMessage ( w , template . HTML ( template . HTMLEscapeString ( message . Error ( ) ) ) )
}
2017-07-08 23:13:36 +00:00
2017-07-09 01:00:45 +00:00
func ( this * WikiServer ) serveInternalError ( w http . ResponseWriter , r * http . Request , e error ) {
log . Printf ( "Internal error from %s while accessing %s(%s): %s" , r . RemoteAddr , r . Method , r . URL . Path , e . Error ( ) )
http . Error ( w , "An internal error occurred. Please ask an administrator to check the log file." , 500 )
2017-07-08 23:13:36 +00:00
}
2017-07-09 01:00:45 +00:00
func ( this * WikiServer ) serveErrorHTMLMessage ( w http . ResponseWriter , msg template . HTML ) {
2017-07-09 01:04:45 +00:00
this . serveRedirect ( w , this . opts . ExpectBaseURL + "view/" + url . QueryEscape ( this . opts . DefaultPage ) + "?error=" + url . QueryEscape ( string ( msg ) ) )
}
func ( this * WikiServer ) serveRedirect ( w http . ResponseWriter , location string ) {
w . Header ( ) . Set ( "Location" , location )
2017-07-09 01:00:45 +00:00
w . WriteHeader ( 302 ) // moved (not permanently)
}
func ( this * WikiServer ) servePageResponse ( w http . ResponseWriter , r * http . Request , pto * pageTemplateOptions ) {
2017-07-09 01:18:18 +00:00
w . WriteHeader ( 200 )
2017-07-09 01:00:45 +00:00
pto . SessionMessage = template . HTML ( r . URL . Query ( ) . Get ( "error" ) ) // FIXME reflected XSS (although Chrome automatically blocks it..)
2017-07-08 23:13:36 +00:00
err := this . pageTmp . Execute ( w , pto )
if err != nil {
log . Println ( err . Error ( ) )
}
}