package yatwiki3 import ( "database/sql" "errors" "fmt" "html/template" "log" "net/http" "net/url" "strings" ) 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") 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 } 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 } else if r.URL.Path == this.opts.ExpectBaseURL+"formatting" { pto := DefaultPageTemplateOptions(this.opts) pto.CurrentPageName = "Formatting help" pto.Content = `

Formatting help



` 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(`

Article Index


There are %d edits to %d pages.

` pto := DefaultPageTemplateOptions(this.opts) pto.CurrentPageName = "Index" pto.Content = template.HTML(content) this.servePageResponse(w, r, pto) return } else if strings.HasPrefix(r.URL.Path, this.opts.ExpectBaseURL+"view/") { articleTitle, err := url.QueryUnescape(r.URL.Path[len(this.opts.ExpectBaseURL+"view/"):]) if err != nil { this.serveErrorMessage(w, err) return } a, err := this.db.GetLatestVersion(articleTitle) if err != nil { if err == sql.ErrNoRows { // 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 } this.serveErrorHTMLMessage(w, this.noSuchArticleError(articleTitle)) return } 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 this.servePageResponse(w, r, pto) return } } // No match? Add 'Page not found' to next session response, and redirect to homepage this.serveErrorMessage(w, errors.New("Page not found")) } func (this *WikiServer) noSuchArticleError(title string) template.HTML { return template.HTML(`No such article exists. Click here to create it.`) } func (this *WikiServer) serveErrorMessage(w http.ResponseWriter, message error) { this.serveErrorHTMLMessage(w, template.HTML(template.HTMLEscapeString(message.Error()))) } 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) } func (this *WikiServer) serveErrorHTMLMessage(w http.ResponseWriter, msg template.HTML) { 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) w.WriteHeader(302) // moved (not permanently) } func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request, pto *pageTemplateOptions) { pto.SessionMessage = template.HTML(r.URL.Query().Get("error")) // FIXME reflected XSS (although Chrome automatically blocks it..) err := this.pageTmp.Execute(w, pto) if err != nil { log.Println(err.Error()) } }