24 Commits

Author SHA1 Message Date
2bc26c5966 bump version to 3.1.1 2017-10-15 20:01:59 +13:00
f5767db840 load contented without jquery, but it's present by the callback 2017-10-15 20:00:04 +13:00
edf88d1f31 doc: changelog update 2017-10-15 19:58:04 +13:00
262c3ba903 contented: update integration to 1.1.0 2017-10-15 19:56:03 +13:00
a260d102ee doc: update readme, bump version to 3.1.0 2017-10-08 17:09:15 +13:00
179617d058 contented integration 2017-10-08 17:08:26 +13:00
5347efb51a bump version to 3.0.3 2017-08-13 18:29:21 +12:00
e3cee5b94c doc: changelog update 2017-08-13 18:27:04 +12:00
e4cf02cde7 restructure error handling to prevent reflected XSS 2017-08-13 18:25:58 +12:00
06e5b4ddf9 bump version to 3.0.1 2017-08-13 18:07:29 +12:00
dbf5e1b246 gitignore 2017-08-13 18:05:34 +12:00
fbad854279 doc: preliminary changelog update 2017-08-13 18:05:28 +12:00
d78429129f also use path escaping function for whole-template links 2017-08-13 18:04:26 +12:00
d937ea6562 prevent viewing history for unknown articles 2017-08-13 17:58:01 +12:00
884acd3040 remove debugging code 2017-08-13 17:53:21 +12:00
e515d73052 use PathEscape instead of QueryEscape for titles in URLs 2017-08-13 17:51:44 +12:00
c87eaa637a doc: preliminary changelog update 2017-08-13 17:33:27 +12:00
043720a086 new TrustXForwardedFor option 2017-08-13 17:32:54 +12:00
a12af6967c fix crash with [html] tags 2017-08-13 17:28:44 +12:00
9792c262a2 readme: fix version tags for download alignment on website 2017-08-13 13:52:40 +12:00
6d9079e1dc doc: update git repo in docs 2017-07-12 18:46:49 +12:00
1c4505a2d9 rename package yatwiki3->yatwiki 2017-07-12 18:43:11 +12:00
11a4f97212 bump all versions to 3.0.1 2017-07-12 18:41:39 +12:00
74b3124997 doc: add 'go get' information to readme 2017-07-11 20:23:17 +12:00
24 changed files with 147 additions and 68 deletions

2
.gitignore vendored
View File

@@ -6,5 +6,5 @@ build/
cmd/yatwiki-server/yatwiki-server cmd/yatwiki-server/yatwiki-server
# Development db files # Development db files
cmd/yatwiki-server/wiki.db cmd/yatwiki-server/*.db
cmd/yatwiki-server/config.json cmd/yatwiki-server/config.json

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"crypto/md5" "crypto/md5"
@@ -11,8 +11,16 @@ func RemoteAddrToIPAddress(remoteAddr string) string {
return strings.TrimRight(strings.TrimRight(remoteAddr, `0123456789`), `:`) // trim trailing port; IPv4 and IPv6-safe return strings.TrimRight(strings.TrimRight(remoteAddr, `0123456789`), `:`) // trim trailing port; IPv4 and IPv6-safe
} }
func Author(r *http.Request) string { func Author(r *http.Request, trustXForwardedFor bool) string {
userAgentHash := md5.Sum([]byte(r.UserAgent())) userAgentHash := md5.Sum([]byte(r.UserAgent()))
return RemoteAddrToIPAddress(r.RemoteAddr) + "-" + hex.EncodeToString(userAgentHash[:])[:6] ipAddress := RemoteAddrToIPAddress(r.RemoteAddr)
if trustXForwardedFor {
if xff := r.Header.Get("X-Forwarded-For"); len(xff) > 0 {
ipAddress = xff
}
}
return ipAddress + "-" + hex.EncodeToString(userAgentHash[:])[:6]
} }

2
DB.go
View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"

View File

@@ -2,7 +2,7 @@
# Makefile for YATWiki3 # Makefile for YATWiki3
# #
VERSION:=3.0 VERSION:=3.1.1
SOURCES:=Makefile \ SOURCES:=Makefile \
static \ static \
@@ -34,7 +34,7 @@ clean:
# #
staticResources.go: static/ static/* staticResources.go: static/ static/*
go-bindata -o staticResources.go -prefix static -pkg yatwiki3 static go-bindata -o staticResources.go -prefix static -pkg yatwiki static
# #

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"time" "time"
@@ -13,6 +13,7 @@ type ServerOptions struct {
DBFilePath string DBFilePath string
FaviconFilePath string FaviconFilePath string
AllowDBDownload bool AllowDBDownload bool
TrustXForwardedFor bool // Introduced in 3.0.1 - default false
RecentChanges int RecentChanges int
RecentChangesRSS int RecentChangesRSS int
GzipCompressionLevel int GzipCompressionLevel int
@@ -20,6 +21,7 @@ type ServerOptions struct {
ExternalBaseURL string ExternalBaseURL string
DeclareRSSLanguage string DeclareRSSLanguage string
DeclareRSSEmail string DeclareRSSEmail string
ContentedServer string
} }
func DefaultOptions() *ServerOptions { func DefaultOptions() *ServerOptions {
@@ -32,6 +34,7 @@ func DefaultOptions() *ServerOptions {
DBFilePath: "wiki.db", DBFilePath: "wiki.db",
FaviconFilePath: "", // no favicon FaviconFilePath: "", // no favicon
AllowDBDownload: true, AllowDBDownload: true,
TrustXForwardedFor: false,
RecentChanges: 20, RecentChanges: 20,
RecentChangesRSS: 10, RecentChangesRSS: 10,
GzipCompressionLevel: 9, GzipCompressionLevel: 9,
@@ -39,5 +42,6 @@ func DefaultOptions() *ServerOptions {
ExternalBaseURL: "http://127.0.0.1/", ExternalBaseURL: "http://127.0.0.1/",
DeclareRSSLanguage: "en-GB", DeclareRSSLanguage: "en-GB",
DeclareRSSEmail: `nobody@example.com`, DeclareRSSEmail: `nobody@example.com`,
ContentedServer: "",
} }
} }

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"
@@ -37,7 +37,13 @@ func NewWikiServer(opts *ServerOptions) (*WikiServer, error) {
} }
} }
tmpl, err := template.New("yatwiki/page").Parse(pageTemplate) tmpl := template.New("yatwiki/page")
tmpl.Funcs(map[string]interface{}{
"pathcomponent": func(s string) string {
return url.PathEscape(s)
},
})
_, err = tmpl.Parse(pageTemplate)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -128,7 +134,7 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} else if remainingPath == "" { } else if remainingPath == "" {
this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.QueryEscape(this.opts.DefaultPage)) this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.PathEscape(this.opts.DefaultPage))
return return
} else if remainingPath == "random" { } else if remainingPath == "random" {
@@ -139,11 +145,11 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
chosenArticle := titles[rand.Intn(len(titles))] chosenArticle := titles[rand.Intn(len(titles))]
this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.QueryEscape(chosenArticle)) this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.PathEscape(chosenArticle))
return return
} else if strings.HasPrefix(remainingPath, "view/") { } else if strings.HasPrefix(remainingPath, "view/") {
articleTitle, err := url.QueryUnescape(remainingPath[len("view/"):]) articleTitle, err := url.PathUnescape(remainingPath[len("view/"):])
if err != nil { if err != nil {
this.serveErrorMessage(w, err) this.serveErrorMessage(w, err)
return return
@@ -152,7 +158,7 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} else if strings.HasPrefix(remainingPath, "modify/") { } else if strings.HasPrefix(remainingPath, "modify/") {
articleTitle, err := url.QueryUnescape(remainingPath[len("modify/"):]) articleTitle, err := url.PathUnescape(remainingPath[len("modify/"):])
if err != nil { if err != nil {
this.serveErrorMessage(w, err) this.serveErrorMessage(w, err)
return return
@@ -161,7 +167,7 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} else if strings.HasPrefix(remainingPath, "history/") { } else if strings.HasPrefix(remainingPath, "history/") {
articleTitle, err := url.QueryUnescape(remainingPath[len("history/"):]) articleTitle, err := url.PathUnescape(remainingPath[len("history/"):])
if err != nil { if err != nil {
this.serveErrorMessage(w, err) this.serveErrorMessage(w, err)
return return
@@ -255,13 +261,13 @@ func (this *WikiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
err = this.db.SaveArticle(title, Author(r), body, int64(expectRev)) err = this.db.SaveArticle(title, Author(r, this.opts.TrustXForwardedFor), body, int64(expectRev))
if err != nil { if err != nil {
this.serveErrorMessage(w, err) this.serveErrorMessage(w, err)
return return
} }
this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.QueryEscape(title)) this.serveRedirect(w, this.opts.ExpectBaseURL+`view/`+url.PathEscape(title))
return return
} }

View File

@@ -14,6 +14,7 @@ As of the 3.0 release, YATWiki is now a standalone server instead of a PHP scrip
- IP-based ban system - IP-based ban system
- Article index, random article, download database backup - Article index, random article, download database backup
- Source code highlighting (thanks [url=https://github.com/isagalaev/highlight.js]highlight.js[/url]) - Source code highlighting (thanks [url=https://github.com/isagalaev/highlight.js]highlight.js[/url])
- Optional integration with `contented` for file/image uploads
Written in Golang, PHP Written in Golang, PHP
@@ -28,9 +29,29 @@ You can start YATWiki by running the binary. A default configuration file and da
Bind address (default "127.0.0.1:80") Bind address (default "127.0.0.1:80")
` `
=GO GET=
This package can be installed via go get: `go get code.ivysaur.me/yatwiki`
[go-get]code.ivysaur.me/yatwiki git https://git.ivysaur.me/code.ivysaur.me/yatwiki.git[/go-get]
=CHANGELOG= =CHANGELOG=
2017-07-11 v3.0 2017-10-15 3.1.1
- Update `contented` integration (requires `contented` >= 1.1.0)
2017-10-08 3.1.0
- Feature: Support content upload to a `contented` server
2017-08-11 3.0.2
- Fix an issue with XSS prevention for web browsers other than Chrome
2017-08-11 3.0.1
- Feature: New `TrustXForwardedFor` config option for usage behind reverse proxies
- Fix an issue with article titles containing `+`
- Fix an issue with `[html]` tags
- Fix an issue with viewing history for unknown articles
2017-07-11 3.0
- YATWiki was rewritten in Go. - YATWiki was rewritten in Go.
- Enhancement: Standalone binary server - Enhancement: Standalone binary server
- Enhancement: No longer requires cookies for error messages - Enhancement: No longer requires cookies for error messages
@@ -43,11 +64,13 @@ You can start YATWiki by running the binary. A default configuration file and da
- Fix a number of issues with handling of base URLs in links - Fix a number of issues with handling of base URLs in links
- Fix a cosmetic issue with file caching for CSS content - Fix a cosmetic issue with file caching for CSS content
2016-11-16 (no public release) 2016-11-16 20161116
- (no public release)
- Enhancement: Always open the formatting help in a new tab - Enhancement: Always open the formatting help in a new tab
- Fix a cosmetic issue with display of backslash characters caused by Meiryo font - Fix a cosmetic issue with display of backslash characters caused by Meiryo font
2016-08-24 (no public release) 2016-08-24 20160824
- (no public release)
- Feature: Add Compare button to both top and bottom of article revision list - Feature: Add Compare button to both top and bottom of article revision list
- Fix an issue with noncompliant HTML when comparing diffs - Fix an issue with noncompliant HTML when comparing diffs

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"encoding/json" "encoding/json"
@@ -45,10 +45,10 @@ func (this *BBCodeRenderer) bbcode(data string) string {
pregReplaceRule{regexp.MustCompile(`(?si)\[\*\]`), `</li><li>`, nil}, pregReplaceRule{regexp.MustCompile(`(?si)\[\*\]`), `</li><li>`, nil},
pregReplaceRule{regexp.MustCompile(`(?si)\[url=(.*?)\](.*?)\[/url\]`), `<a rel="noreferrer" href="$1">$2</a>`, nil}, pregReplaceRule{regexp.MustCompile(`(?si)\[url=(.*?)\](.*?)\[/url\]`), `<a rel="noreferrer" href="$1">$2</a>`, nil},
pregReplaceRule{regexp.MustCompile(`(?si)\[article=(.*?)\](.*?)\[/article\]`), "", func(m []string) string { pregReplaceRule{regexp.MustCompile(`(?si)\[article=(.*?)\](.*?)\[/article\]`), "", func(m []string) string {
return `<a href="` + template.HTMLEscapeString(this.baseUrl+`view/`+url.QueryEscape(m[1])) + `">` + m[2] + `</a>` return `<a href="` + template.HTMLEscapeString(this.baseUrl+`view/`+url.PathEscape(m[1])) + `">` + m[2] + `</a>`
}}, }},
pregReplaceRule{regexp.MustCompile(`(?si)\[rev=(.*?)\](.*?)\[/rev\]`), "", func(m []string) string { pregReplaceRule{regexp.MustCompile(`(?si)\[rev=(.*?)\](.*?)\[/rev\]`), "", func(m []string) string {
return `<a href="` + template.HTMLEscapeString(this.baseUrl+`archive/`+url.QueryEscape(m[1])) + `">` + m[2] + `</a>` return `<a href="` + template.HTMLEscapeString(this.baseUrl+`archive/`+url.PathEscape(m[1])) + `">` + m[2] + `</a>`
}}, }},
pregReplaceRule{regexp.MustCompile(`(?si)\[imgur\](.*?)\.(...)\[/imgur\]`), pregReplaceRule{regexp.MustCompile(`(?si)\[imgur\](.*?)\.(...)\[/imgur\]`),
@@ -177,7 +177,7 @@ func (this *BBCodeRenderer) displayfmt(s string) string {
} }
epos += spos epos += spos
jsonInnerContent, _ := json.Marshal(s[spos : epos-spos]) jsonInnerContent, _ := json.Marshal(s[spos:epos])
ret += `<div class="html"><a href="javascript:;" onclick="` + template.HTMLEscapeString(`els(this, `+string(jsonInnerContent)+`);`) + `">` + this.DynamicContentWarning + `</a></div>` ret += `<div class="html"><a href="javascript:;" onclick="` + template.HTMLEscapeString(`els(this, `+string(jsonInnerContent)+`);`) + `">` + this.DynamicContentWarning + `</a></div>`
hpos = epos + 7 hpos = epos + 7

View File

@@ -8,7 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"code.ivysaur.me/yatwiki3" "code.ivysaur.me/yatwiki"
) )
func main() { func main() {
@@ -17,13 +17,13 @@ func main() {
configPath := flag.String("config", "config.json", "Configuration file") configPath := flag.String("config", "config.json", "Configuration file")
flag.Parse() flag.Parse()
opts := yatwiki3.ServerOptions{} opts := yatwiki.ServerOptions{}
cfg, err := ioutil.ReadFile(*configPath) cfg, err := ioutil.ReadFile(*configPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
opts = *yatwiki3.DefaultOptions() opts = *yatwiki.DefaultOptions()
if cfg, err := json.MarshalIndent(opts, "", "\t"); err == nil { if cfg, err := json.MarshalIndent(opts, "", "\t"); err == nil {
err := ioutil.WriteFile(*configPath, cfg, 0644) err := ioutil.WriteFile(*configPath, cfg, 0644)
if err != nil { if err != nil {
@@ -43,7 +43,7 @@ func main() {
} }
} }
ws, err := yatwiki3.NewWikiServer(&opts) ws, err := yatwiki.NewWikiServer(&opts)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"bytes" "bytes"

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"fmt" "fmt"
@@ -17,7 +17,9 @@ type pageTemplateOptions struct {
LoadCodeResources bool LoadCodeResources bool
DefaultPage string DefaultPage string
AllowDownload bool AllowDownload bool
SessionMessage template.HTML SessionMessage string
PageNotExistsError bool
PageNotExistsTarget string
} }
func DefaultPageTemplateOptions(opts *ServerOptions) *pageTemplateOptions { func DefaultPageTemplateOptions(opts *ServerOptions) *pageTemplateOptions {
@@ -85,13 +87,13 @@ function els(e,s){ // no js exec in innerHTML
</head> </head>
<body> <body>
<div class="header"> <div class="header">
<a href="{{.BaseURL}}view/{{.DefaultPage | urlquery}}" title="Home"><div class="sprite hm"></div></a> <a href="{{.BaseURL}}view/{{.DefaultPage | pathcomponent}}" title="Home"><div class="sprite hm"></div></a>
<a href="javascript:;" onclick="tid('spm');tid('tr1');tid('tr2');" title="Menu"><div class="sprite sp"></div></a> <a href="javascript:;" onclick="tid('spm');tid('tr1');tid('tr2');" title="Menu"><div class="sprite sp"></div></a>
<a href="{{.BaseURL}}modify/{{.NewArticleTitle | urlquery}}" title="New Page"><div class="sprite nw"></div></a> <a href="{{.BaseURL}}modify/{{.NewArticleTitle | pathcomponent}}" title="New Page"><div class="sprite nw"></div></a>
{{if .CurrentPageIsArticle }} {{if .CurrentPageIsArticle }}
<div class="sep"></div> <div class="sep"></div>
<a href="{{.BaseURL}}history/{{.CurrentPageName | urlquery}}" title="Page History"><div class="sprite hs"></div></a> <a href="{{.BaseURL}}history/{{.CurrentPageName | pathcomponent}}" title="Page History"><div class="sprite hs"></div></a>
<a href="{{.BaseURL}}modify/{{.CurrentPageName | urlquery}}" title="Modify Page"><div class="sprite ed"></div></a> <a href="{{.BaseURL}}modify/{{.CurrentPageName | pathcomponent}}" title="Modify Page"><div class="sprite ed"></div></a>
{{end}} {{end}}
</div> </div>
<div id="tr1" style="display:none;"></div> <div id="tr1" style="display:none;"></div>
@@ -105,6 +107,12 @@ function els(e,s){ // no js exec in innerHTML
{{end}} {{end}}
</div> </div>
<div class="content"> <div class="content">
{{if .PageNotExistsError}}
<div class="info">
No such article exists.
<a href="{{.BaseURL}}modify/{{.PageNotExistsTarget | pathcomponent}}">Click here</a> to create it.
</div>
{{end}}
{{if len .SessionMessage}} {{if len .SessionMessage}}
<div class="info">{{.SessionMessage}}</div> <div class="info">{{.SessionMessage}}</div>
{{end}} {{end}}

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"
@@ -30,7 +30,7 @@ func (this *WikiServer) routeArchive(w http.ResponseWriter, r *http.Request, rev
`<div class="info">`+ `<div class="info">`+
`You are viewing specific revision of this page, last modified `+ `You are viewing specific revision of this page, last modified `+
time.Unix(a.Modified, 0).In(this.loc).Format(this.opts.DateFormat)+`. `+ time.Unix(a.Modified, 0).In(this.loc).Format(this.opts.DateFormat)+`. `+
`Click <a href="`+template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(a.Title))+`">here</a> to see the latest revision.`+ `Click <a href="`+template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(a.Title))+`">here</a> to see the latest revision.`+
`</div>`, `</div>`,
) + bcr.RenderHTML(string(a.Body)) ) + bcr.RenderHTML(string(a.Body))
pto.LoadCodeResources = bcr.CodePresent pto.LoadCodeResources = bcr.CodePresent

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"bytes" "bytes"
@@ -8,7 +8,7 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"code.ivysaur.me/yatwiki3/diff" "code.ivysaur.me/yatwiki/diff"
) )
func (this *WikiServer) routeDiff(w http.ResponseWriter, r *http.Request, oldRev, newRev int) { func (this *WikiServer) routeDiff(w http.ResponseWriter, r *http.Request, oldRev, newRev int) {

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"html/template" "html/template"
@@ -8,12 +8,8 @@ import (
"time" "time"
) )
func (this *WikiServer) noSuchArticleError(title string) template.HTML { func (this *WikiServer) serveErrorMessage(w http.ResponseWriter, err error) {
return template.HTML(`No such article exists. <a href="` + this.opts.ExpectBaseURL + `modify/` + template.HTMLEscapeString(url.QueryEscape(title)) + `">Click here</a> to create it.`) this.serveErrorText(w, err.Error())
}
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) { func (this *WikiServer) serveInternalError(w http.ResponseWriter, r *http.Request, e error) {
@@ -21,8 +17,12 @@ func (this *WikiServer) serveInternalError(w http.ResponseWriter, r *http.Reques
http.Error(w, "An internal error occurred. Please ask an administrator to check the log file.", 500) 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) { func (this *WikiServer) serveErrorText(w http.ResponseWriter, msg string) {
this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.QueryEscape(this.opts.DefaultPage)+"?error="+url.QueryEscape(string(msg))) this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(this.opts.DefaultPage)+"?error="+url.QueryEscape(msg))
}
func (this *WikiServer) serveNoSuchArticle(w http.ResponseWriter, lookingFor string) {
this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(this.opts.DefaultPage)+"?notfound="+url.QueryEscape(lookingFor))
} }
func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) { func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) {
@@ -32,7 +32,14 @@ func (this *WikiServer) serveRedirect(w http.ResponseWriter, location string) {
func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request, pto *pageTemplateOptions) { func (this *WikiServer) servePageResponse(w http.ResponseWriter, r *http.Request, pto *pageTemplateOptions) {
w.WriteHeader(200) w.WriteHeader(200)
pto.SessionMessage = template.HTML(r.URL.Query().Get("error")) // FIXME reflected XSS (although Chrome automatically blocks it..)
if noSuchArticleTarget, ok := r.URL.Query()["notfound"]; ok {
pto.PageNotExistsError = true
pto.PageNotExistsTarget = noSuchArticleTarget[0]
} else {
pto.SessionMessage = r.URL.Query().Get("error")
}
err := this.pageTmp.Execute(w, pto) err := this.pageTmp.Execute(w, pto)
if err != nil { if err != nil {
@@ -46,5 +53,5 @@ func (this *WikiServer) formatTimestamp(m int64) string {
} }
func (this *WikiServer) viewLink(articleTitle string) template.HTML { func (this *WikiServer) viewLink(articleTitle string) template.HTML {
return template.HTML(`&quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;`) return template.HTML(`&quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;`)
} }

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"net/http" "net/http"

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"
@@ -12,7 +12,7 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
revs, err := this.db.GetRevisionHistory(articleTitle) revs, err := this.db.GetRevisionHistory(articleTitle)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
this.serveErrorHTMLMessage(w, this.noSuchArticleError(articleTitle)) this.serveNoSuchArticle(w, articleTitle)
return return
} }
@@ -20,12 +20,17 @@ func (this *WikiServer) routeHistory(w http.ResponseWriter, r *http.Request, art
return return
} }
if len(revs) == 0 {
this.serveNoSuchArticle(w, articleTitle)
return
}
pto := DefaultPageTemplateOptions(this.opts) pto := DefaultPageTemplateOptions(this.opts)
pto.CurrentPageName = articleTitle pto.CurrentPageName = articleTitle
pto.CurrentPageIsArticle = true pto.CurrentPageIsArticle = true
content := `<h2>Page History</h2><br>` + content := `<h2>Page History</h2><br>` +
`<em>There have been ` + fmt.Sprintf("%d", len(revs)) + ` edits to the page &quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;.</em>` + `<em>There have been ` + fmt.Sprintf("%d", len(revs)) + ` edits to the page &quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;.</em>` +
`<br><br>` + `<br><br>` +
`<form method="GET" action="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`diff`) + `">` + `<form method="GET" action="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`diff`) + `">` +
`<table>` `<table>`

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"fmt" "fmt"
@@ -22,7 +22,7 @@ func (this *WikiServer) routeIndex(w http.ResponseWriter, r *http.Request) {
content := fmt.Sprintf(`<h2>Article Index</h2><br><em>There are %d edits to %d pages.</em><br><br><ul>`, totalRevs, len(titles)) 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 { for _, title := range titles {
content += `<li><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(title)) + `">` + template.HTMLEscapeString(title) + `</a></li>` content += `<li><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(title)) + `">` + template.HTMLEscapeString(title) + `</a></li>`
} }
content += `</ul>` content += `</ul>`

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"
@@ -32,7 +32,7 @@ func (this *WikiServer) routeModify(w http.ResponseWriter, r *http.Request, arti
pageTitleHTML = `Creating new article` pageTitleHTML = `Creating new article`
baseRev = 0 baseRev = 0
} else { } else {
pageTitleHTML = `Editing article &quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;` pageTitleHTML = `Editing article &quot;<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(articleTitle)) + `">` + template.HTMLEscapeString(articleTitle) + `</a>&quot;`
baseRev = a.ID baseRev = a.ID
existingBody = string(a.Body) existingBody = string(a.Body)
} }
@@ -49,6 +49,24 @@ func (this *WikiServer) routeModify(w http.ResponseWriter, r *http.Request, arti
</label> </label>
<input type="submit" value="Save &raquo;"> <input type="submit" value="Save &raquo;">
| <a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`formatting`) + `" target="_blank">formatting&nbsp;help</a> | <a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`formatting`) + `" target="_blank">formatting&nbsp;help</a>
`
if len(this.opts.ContentedServer) > 0 {
content += `
<script type="text/javascript" src="` + this.opts.ContentedServer + `sdk.js"></script>
| <a href="javascript:;" id="open-contented-uploader">upload...</a>
<script type="text/javascript">
document.getElementById("open-contented-uploader").addEventListener("click", function() {
contented.init("#contentctr", function(items) {
for (var i = 0; i < items.length; ++i) {
$("#contentctr textarea").append(" " + contented.getPreviewURL(items[i]) + " ");
}
});
});
</script>
`
}
content += `
</div> </div>
<div id="contentctr"><textarea name="content">` + template.HTMLEscapeString(existingBody) + `</textarea></div> <div id="contentctr"><textarea name="content">` + template.HTMLEscapeString(existingBody) + `</textarea></div>
</form> </form>

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"fmt" "fmt"
@@ -33,7 +33,7 @@ func (this *WikiServer) routeRecentChangesRSS(w http.ResponseWriter, r *http.Req
<author>` + template.HTMLEscapeString(this.opts.DeclareRSSEmail+` (`+this.opts.PageTitle+` `+a.Author+`)`) + `</author> <author>` + template.HTMLEscapeString(this.opts.DeclareRSSEmail+` (`+this.opts.PageTitle+` `+a.Author+`)`) + `</author>
<pubDate>` + template.HTMLEscapeString(time.Unix(a.Modified, 0).In(this.loc).Format(time.RFC1123Z)) + `</pubDate> <pubDate>` + template.HTMLEscapeString(time.Unix(a.Modified, 0).In(this.loc).Format(time.RFC1123Z)) + `</pubDate>
<description>` + template.HTMLEscapeString(` <description>` + template.HTMLEscapeString(`
<a href="`+template.HTMLEscapeString(this.opts.ExternalBaseURL+`view/`+url.QueryEscape(a.Title))+`">latest version</a> <a href="`+template.HTMLEscapeString(this.opts.ExternalBaseURL+`view/`+url.PathEscape(a.Title))+`">latest version</a>
| |
<a href="`+template.HTMLEscapeString(this.opts.ExternalBaseURL+`archive/`+fmt.Sprintf("%d", a.ID))+`">revision `+fmt.Sprintf("%d", a.ID)+`</a> <a href="`+template.HTMLEscapeString(this.opts.ExternalBaseURL+`archive/`+fmt.Sprintf("%d", a.ID))+`">revision `+fmt.Sprintf("%d", a.ID)+`</a>
| |

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"errors" "errors"
@@ -41,7 +41,7 @@ func (this *WikiServer) routeRecentChanges(w http.ResponseWriter, r *http.Reques
`<table>` `<table>`
for _, rev := range recents { for _, rev := range recents {
content += `<tr>` + content += `<tr>` +
`<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.QueryEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a>` + `<td><a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`view/`+url.PathEscape(rev.Title)) + `">` + template.HTMLEscapeString(rev.Title) + `</a>` +
` [<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">a</a>]` + ` [<a href="` + template.HTMLEscapeString(this.opts.ExpectBaseURL+`archive/`+fmt.Sprintf("%d", rev.ID)) + `">a</a>]` +
`</td>` + `</td>` +
`<td>` + this.formatTimestamp(rev.Modified) + ` by ` + template.HTMLEscapeString(rev.Author) + `</td>` + `<td>` + this.formatTimestamp(rev.Modified) + ` by ` + template.HTMLEscapeString(rev.Author) + `</td>` +

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"database/sql" "database/sql"
@@ -15,11 +15,11 @@ func (this *WikiServer) routeView(w http.ResponseWriter, r *http.Request, articl
// If this was an old link, it might not be present. // If this was an old link, it might not be present.
// Redirect if possible // Redirect if possible
if len(articleTitle) > 0 && articleTitle[len(articleTitle)-1] == '/' { if len(articleTitle) > 0 && articleTitle[len(articleTitle)-1] == '/' {
this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.QueryEscape(articleTitle[0:len(articleTitle)-1])) this.serveRedirect(w, this.opts.ExpectBaseURL+"view/"+url.PathEscape(articleTitle[0:len(articleTitle)-1]))
return return
} }
this.serveErrorHTMLMessage(w, this.noSuchArticleError(articleTitle)) this.serveNoSuchArticle(w, articleTitle)
return return
} }
this.serveErrorMessage(w, err) this.serveErrorMessage(w, err)

View File

@@ -1,4 +1,4 @@
package yatwiki3 package yatwiki
import ( import (
"regexp" "regexp"

View File

@@ -4,7 +4,7 @@
// static/wiki.css // static/wiki.css
// DO NOT EDIT! // DO NOT EDIT!
package yatwiki3 package yatwiki
import ( import (
"bytes" "bytes"