views

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 26, 2017 License: BSD-3-Clause Imports: 25 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AdminConfigUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	models.WriteConfig(models.ConfigJSON, r.PostFormValue("config"))
	ctxCacheDate = time.Unix(0, 0)
	ctx.SetFlashMsg("Config updated")
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminFileMasterGroupHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	group := r.PostFormValue("group")
	var tmp string
	if group == models.EverybodyGroup || db.QueryRow(`SELECT id FROM groups WHERE name=?;`, group).Scan(&tmp) == nil {
		models.WriteConfig(models.FileMasterGroup, group)
	} else {
		ctx.SetFlashMsg("Group '" + group + "' not found")
	}
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminFooterUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	models.WriteConfig(models.FooterLinks, r.PostFormValue("footer"))
	ctxCacheDate = time.Unix(0, 0)
	ctx.SetFlashMsg("Footer updated")
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminGroupCreateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	groupname := r.PostFormValue("groupname")
	if groupname != models.EverybodyGroup {
		if err := models.ValidateName(groupname); err == nil {
			var tmp string
			if db.QueryRow(`SELECT id FROM groups WHERE name=?;`, groupname).Scan(&tmp) == sql.ErrNoRows {
				tNow := time.Now().Unix()
				db.Exec(`INSERT INTO groups(name, created_date, updated_date) VALUES(?, ?, ?);`, groupname, tNow, tNow)
			} else {
				ctx.SetFlashMsg("Group '" + groupname + "' already exits")
			}
		} else {
			ctx.SetFlashMsg(err.Error())
		}
	} else {
		ctx.SetFlashMsg("Group name '" + groupname + "' is reserved")
	}

	http.Redirect(w, r, "/admin/groups", http.StatusSeeOther)
})
View Source
var AdminGroupDeleteHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	groupname := r.PostFormValue("groupname")
	db.Exec(`DELETE FROM groups WHERE name=?;`, groupname)
	ctx.SetFlashMsg("Group deleted")
	http.Redirect(w, r, "/admin/groups", http.StatusSeeOther)
})
View Source
var AdminGroupHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}

	var groups []string
	rows := db.Query(`SELECT name FROM groups;`)
	for rows.Next() {
		var group string
		rows.Scan(&group)
		groups = append(groups, group)
	}
	templates.Render(w, "admingroups.html", map[string]interface{}{
		"ctx":    ctx,
		"groups": groups,
	})
})
View Source
var AdminGroupMemberCreateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	groupname := r.PostFormValue("groupname")
	username := r.PostFormValue("username")

	var groupID string
	if db.QueryRow(`SELECT id FROM groups WHERE name=?;`, groupname).Scan(&groupID) == nil {
		var userID string
		if db.QueryRow(`SELECT id FROM users WHERE username=?;`, username).Scan(&userID) == nil {
			var tmp string
			if db.QueryRow(`SELECT id FROM groupmembers WHERE userid=? AND groupid=?;`, userID, groupID).Scan(&tmp) == sql.ErrNoRows {
				tNow := time.Now().Unix()
				db.Exec(`INSERT INTO groupmembers(groupid, userid, created_date) VALUES(?, ?, ?);`, groupID, userID, tNow)
				ctx.SetFlashMsg("Added user '" + username + "'")
			} else {
				ctx.SetFlashMsg("User '" + username + "' already in this group")
			}
		} else {
			ctx.SetFlashMsg("User '" + username + "' not found")
		}
	}
	http.Redirect(w, r, "/admin/groupmembers?g="+groupname, http.StatusSeeOther)
})
View Source
var AdminGroupMemberDeleteHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	groupname := r.PostFormValue("groupname")
	username := r.PostFormValue("username")

	var userID string
	if db.QueryRow(`SELECT id FROM users WHERE username=?;`, username).Scan(&userID) == nil {
		db.Exec(`DELETE FROM groupmembers WHERE userid=?;`, userID)
		ctx.SetFlashMsg("User '" + username + "' removed from this group")
	} else {
		ctx.SetFlashMsg("User '" + username + "' not in this group")
	}
	http.Redirect(w, r, "/admin/groupmembers?g="+groupname, http.StatusSeeOther)
})
View Source
var AdminGroupMembersHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}
	groupname := r.FormValue("g")
	var members []string
	rows := db.Query(`SELECT users.username FROM groupmembers INNER JOIN users ON groupmembers.userid=users.id INNER JOIN groups ON groupmembers.groupid=groups.id AND groups.name=?;`, groupname)
	for rows.Next() {
		var member string
		rows.Scan(&member)
		members = append(members, member)
	}
	templates.Render(w, "admingroupmembers.html", map[string]interface{}{
		"ctx":       ctx,
		"groupname": groupname,
		"members":   members,
	})
})
View Source
var AdminHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}
	templates.Render(w, "admin.html", map[string]interface{}{
		"ctx":                 ctx,
		"PageMasterGroup":     models.ReadPageMasterGroup(),
		"FileMasterGroup":     models.ReadFileMasterGroup(),
		"config":              models.ReadConfig(models.ConfigJSON),
		"header":              models.ReadConfig(models.HeaderLinks),
		"footer":              models.ReadConfig(models.FooterLinks),
		"nav":                 models.ReadConfig(models.NavSections),
		"illegal_names":       models.ReadConfig(models.IllegalNames),
		"DefaultConfig":       models.DefaultConfigJSON,
		"DefaultHeader":       models.DefaultHeaderLinks,
		"DefaultFooter":       models.DefaultFooterLinks,
		"DefaultNav":          models.DefaultNavSections,
		"DefaultIllegalNames": models.DefaultIllegalNames,
	})
})
View Source
var AdminHeaderUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	models.WriteConfig(models.HeaderLinks, r.PostFormValue("header"))
	ctxCacheDate = time.Unix(0, 0)
	ctx.SetFlashMsg("Header updated")
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminIllegalNamesUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	models.WriteConfig(models.IllegalNames, r.PostFormValue("illegal_names"))
	ctxCacheDate = time.Unix(0, 0)
	ctx.SetFlashMsg("Illegal name list updated")
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminNavUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	models.WriteConfig(models.NavSections, r.PostFormValue("nav"))
	ctxCacheDate = time.Unix(0, 0)
	ctx.SetFlashMsg("Nav updated")
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminPageMasterGroupHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin || r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	group := r.PostFormValue("group")
	var tmp string
	if group == models.EverybodyGroup || db.QueryRow(`SELECT id FROM groups WHERE name=?;`, group).Scan(&tmp) == nil {
		models.WriteConfig(models.PageMasterGroup, group)
	} else {
		ctx.SetFlashMsg("Group '" + group + "' not found")
	}
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
})
View Source
var AdminUserHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}

	var users []string
	rows := db.Query(`SELECT username FROM users;`)
	for rows.Next() {
		var user string
		rows.Scan(&user)
		users = append(users, user)
	}
	templates.Render(w, "adminusers.html", map[string]interface{}{
		"ctx":   ctx,
		"users": users,
	})
})
View Source
var ChangepassHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}

	username := r.PostFormValue("username")
	oldPasswd := r.PostFormValue("oldpasswd")
	newPasswd := r.PostFormValue("passwd")
	newPasswd2 := r.PostFormValue("passwd2")

	if !ctx.IsAdmin {
		if err := models.VerifyPasswd(username, oldPasswd); err != nil {
			ctx.SetFlashMsg(err.Error())
			http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
			return
		}
	}

	if err := models.ValidatePasswd(newPasswd); err != nil {
		ctx.SetFlashMsg(err.Error())
		http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
		return
	}
	if newPasswd != newPasswd2 {
		ctx.SetFlashMsg("New passwords do not match")
		http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
		return
	}

	if err := models.UpdateUserPasswd(username, newPasswd); err != nil {
		ctx.SetFlashMsg(err.Error())
		http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
		return
	}

	ctx.SetFlashMsg("Password changed successfully")
	http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
	return
})
View Source
var FileCreateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	isPageMaster := ctx.IsAdmin || models.IsUserInFileMasterGroup(ctx.UserName)
	if !isPageMaster {
		templates.Render(w, "accessdenied.html", map[string]interface{}{
			"ctx": ctx,
		})
		return
	}
	dataDir := ctx.Config.DataDir
	if dataDir == "" {
		ctx.SetFlashMsg("DataDir not configured. Contact admin.")
		http.Redirect(w, r, "/files/#flash", http.StatusSeeOther)
		return
	}
	if dataDir[len(dataDir)-1] != '/' {
		dataDir = dataDir + "/"
	}
	r.ParseMultipartForm(32 * 1024 * 1024)
	file, handler, err := r.FormFile("file")
	if err == nil {
		defer file.Close()
		if handler.Filename != "" {
			title := handler.Filename
			if err := models.IsPageTitleValid(title); err == nil {
				var tmp string
				if db.QueryRow(`SELECT id FROM pages WHERE title=?;`, title).Scan(&tmp) == sql.ErrNoRows {
					f, err := os.OpenFile(dataDir+title, os.O_WRONLY|os.O_CREATE, 0666)
					if err == nil {
						defer f.Close()
						io.Copy(f, file)
						now := time.Now().Unix()
						db.Exec(`INSERT INTO pages(title, is_file, created_date, updated_date) VALUES(?, ?, ?, ?);`, title, true, now, now)
					} else {
						log.Panicf("[ERROR] Error writing file: %s\n", err)
					}
				} else {
					ctx.SetFlashMsg("File already exists")
				}
			} else {
				ctx.SetFlashMsg(err.Error())
			}
		} else {
			ctx.SetFlashMsg("Choose file to upload")
		}
	}
	http.Redirect(w, r, "/files/", http.StatusSeeOther)
})
View Source
var FileUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	isPageMaster := ctx.IsAdmin || models.IsUserInFileMasterGroup(ctx.UserName)
	if !isPageMaster {
		templates.Render(w, "accessdenied.html", map[string]interface{}{
			"ctx": ctx,
		})
		return
	}
	cTitle := r.FormValue("t")
	title := strings.Replace(cTitle, "_", " ", -1)
	action := r.PostFormValue("action")
	if action == "Update" {
		editGroup := r.PostFormValue("editgroup")
		readGroup := r.PostFormValue("readgroup")
		var editGroupID, readGroupID string
		db.QueryRow(`SELECT id FROM groups WHERE name=?;`, editGroup).Scan(&editGroupID)
		db.QueryRow(`SELECT id FROM groups WHERE name=?;`, readGroup).Scan(&readGroupID)
		if editGroupID != "" {
			db.Exec(`UPDATE pages SET editgroupid=? WHERE title=?;`, editGroupID, title)
		} else {
			db.Exec(`UPDATE pages SET editgroupid=NULL WHERE title=?;`, title)
		}
		if readGroupID != "" {
			db.Exec(`UPDATE pages SET readgroupid=? WHERE title=?;`, readGroupID, title)
		} else {
			db.Exec(`UPDATE pages SET readgroupid=NULL WHERE title=?;`, title)
		}
		ctx.SetFlashMsg("Updated file " + title)
	}
	if action == "Delete" {
		dataDir := ctx.Config.DataDir
		if dataDir != "" {
			if dataDir[len(dataDir)-1] != '/' {
				dataDir = dataDir + "/"
			}
			db.Exec(`DELETE FROM pages WHERE title=?;`, title)
			if err := os.Remove(dataDir + title); err != nil {
				log.Printf("[ERROR] Error deleting file: %s\n", err.Error())
			}
			ctx.SetFlashMsg("Deleted file " + title)
		} else {
			ctx.SetFlashMsg("DataDir not configured")
		}
	}
	http.Redirect(w, r, "/files/", http.StatusSeeOther)
})
View Source
var FilesHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	isPageMaster := ctx.IsAdmin || models.IsUserInFileMasterGroup(ctx.UserName)
	cTitle := r.URL.Path[7:]
	title := strings.Replace(cTitle, "_", " ", -1)
	if title == "" {

		if !isPageMaster {
			templates.Render(w, "accessdenied.html", map[string]interface{}{
				"ctx": ctx,
			})
			return
		}
		type File struct {
			Title     string
			CTitle    string
			ReadGroup string
			EditGroup string
		}
		var files []File
		rows := db.Query(`SELECT title, readgroupid, editgroupid FROM pages WHERE is_file=? ORDER BY title;`, true)
		for rows.Next() {
			file := File{}
			var readGroupID, editGroupID sql.NullString
			rows.Scan(&file.Title, &readGroupID, &editGroupID)
			file.CTitle = strings.Replace(file.Title, " ", "_", -1)
			if readGroupID.Valid {
				db.QueryRow(`SELECT name FROM groups WHERE id=?;`, readGroupID).Scan(&file.ReadGroup)
			}
			if editGroupID.Valid {
				db.QueryRow(`SELECT name FROM groups WHERE id=?;`, editGroupID).Scan(&file.EditGroup)
			}
			files = append(files, file)
		}
		templates.Render(w, "filelist.html", map[string]interface{}{
			"ctx":   ctx,
			"files": files,
		})
		return
	}
	dataDir := ctx.Config.DataDir
	if dataDir == "" {
		ErrNotFoundHandler(w, r)
		return
	}
	if dataDir[len(dataDir)-1] != '/' {
		dataDir = dataDir + "/"
	}
	row := db.QueryRow(`SELECT readgroupid FROM pages WHERE title=?;`, title)
	var readGroupID sql.NullString
	if row.Scan(&readGroupID) != nil {
		ErrNotFoundHandler(w, r)
		return
	}
	if !isPageMaster && readGroupID.Valid {
		row := db.QueryRow(`SELECT groupmembers.id FROM groupmembers INNER JOIN users ON users.id=groupmembers.userid AND users.username=? WHERE groupmembers.groupid=?;`, ctx.UserName, readGroupID)
		var tmp string
		if row.Scan(&tmp) != nil {
			templates.Render(w, "accessdenied.html", map[string]interface{}{
				"ctx": ctx,
			})
			return
		}
	}
	http.ServeFile(w, r, ctx.Config.DataDir+title)
})
View Source
var ForgotpassHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method == "POST" {
		username := r.PostFormValue("username")
		row := db.QueryRow(`SELECT email FROM users WHERE username=?;`, username)
		var email string
		if err := row.Scan(&email); err == nil {
			if strings.Contains(email, "@") {
				resetToken := randSeq(40)
				db.Exec(`UPDATE users SET reset_token=?, reset_token_date=? WHERE username=?;`, resetToken, int64(time.Now().Unix()), username)
				resetLink := "https://" + r.Host + "/resetpass?token=" + resetToken
				sub := ctx.Config.WikiName + " Password Recovery"
				msg := "Someone (hopefully you) requested we reset your password at " + ctx.Config.WikiName + ".\r\n" +
					"If you want to change it, visit " + resetLink + "\r\n\r\nIf not, just ignore this message."

				SendMail(email, sub, msg, ctx.Config)
				ctx.FlashMsg = "Password reset link has been sent to your email"
			} else {
				ctx.FlashMsg = "We don't have your email. Please contact the admin to reset your password"
			}
		} else {
			ctx.FlashMsg = "User not found"
		}
	}
	templates.Render(w, "forgotpass.html", map[string]interface{}{
		"ctx": ctx,
	})
})
View Source
var LoginHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	redirectURL, err := url.QueryUnescape(r.FormValue("next"))
	if redirectURL == "" || err != nil {
		redirectURL = "/"
	}
	if ctx.IsUserValid {
		http.Redirect(w, r, redirectURL, http.StatusSeeOther)
		return
	}

	if r.Method == "POST" {
		userName := r.PostFormValue("username")
		passwd := r.PostFormValue("passwd")
		if len(userName) > 200 || len(passwd) > 200 {
			fmt.Fprint(w, "username / password too long.")
			return
		}
		if err = ctx.Authenticate(userName, passwd); err == nil {
			http.SetCookie(w, &http.Cookie{Name: "sessionid", Path: "/", Value: ctx.SessionID, HttpOnly: true})
			http.Redirect(w, r, redirectURL, http.StatusSeeOther)
			return
		} else {
			ctx.FlashMsg = err.Error()
		}
	}
	templates.Render(w, "login.html", map[string]interface{}{
		"ctx":  ctx,
		"next": template.URL(url.QueryEscape(redirectURL)),
	})
})
View Source
var LogoutAllHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	username := r.FormValue("u")
	if !ctx.IsAdmin && (username != ctx.UserName) {
		ErrForbiddenHandler(w, r)
		return
	}

	var userID string
	if db.QueryRow(`SELECT id FROM users WHERE users.username=?;`, username).Scan(&userID) == nil {
		db.Exec(`DELETE FROM sessions WHERE userid=?;`, userID)
	}
	http.Redirect(w, r, "/", http.StatusSeeOther)
})
View Source
var PageCreateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	isPageMaster := ctx.IsAdmin || models.IsUserInPageMasterGroup(ctx.UserName)
	if !isPageMaster {
		templates.Render(w, "accessdenied.html", map[string]interface{}{
			"ctx": ctx,
		})
		return
	}
	title := r.PostFormValue("title")
	cTitle := strings.Replace(title, " ", "_", -1)
	var tmp string
	if db.QueryRow(`SELECT id FROM pages WHERE title=?;`, title).Scan(&tmp) != sql.ErrNoRows {
		ctx.SetFlashMsg("Page already exists")
		http.Redirect(w, r, "/pages/#flash", http.StatusSeeOther)
		return
	}
	if err := models.IsPageTitleValid(title); err != nil {
		ctx.SetFlashMsg(err.Error())
		http.Redirect(w, r, "/pages/#flash", http.StatusSeeOther)
		return
	}
	content := "# " + title
	tNow := time.Now().Unix()
	db.Exec(`INSERT INTO pages(title, content, discussion, created_date, updated_date) VALUES(?, ?, ?, ?, ?);`, title, content, content, tNow, tNow)
	pageMasterGroup := models.ReadPageMasterGroup()
	if pageMasterGroup != models.DefaultPageMasterGroup {
		var gID string
		if db.QueryRow(`SELECT id FROM groups WHERE name=?;`, pageMasterGroup).Scan(&gID) == nil {
			db.Exec(`UPDATE pages SET editgroupid=? WHERE title=?;`, gID, title)
		}
	}
	http.Redirect(w, r, "/editpage?t="+cTitle, http.StatusSeeOther)
})
View Source
var PageEditHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	isPageMaster := ctx.IsAdmin || models.IsUserInPageMasterGroup(ctx.UserName)
	cTitle := r.FormValue("t")
	title := strings.Replace(cTitle, "_", " ", -1)
	isDiscussion := (r.FormValue("d") != "")

	var editGroupID sql.NullString
	db.QueryRow(`SELECT editGroupID FROM pages WHERE title=?;`, title).Scan(&editGroupID)
	if !isPageMaster && editGroupID.Valid {
		var tmp string
		row := db.QueryRow(`SELECT groupmembers.id FROM groupmembers INNER JOIN users ON users.id=groupmembers.userid AND users.username=? WHERE groupmembers.groupid=?;`, ctx.UserName, editGroupID)
		if row.Scan(&tmp) != nil {
			templates.Render(w, "accessdenied.html", map[string]interface{}{
				"ctx": ctx,
			})
			return
		}
	}
	if r.Method == "POST" {
		action := r.PostFormValue("action")
		if r.PostFormValue("meta") == "" {
			if action == "Update" {
				content := r.PostFormValue("content")
				reURL := "/pages/" + cTitle
				if isDiscussion {
					db.Exec(`UPDATE pages SET discussion=? WHERE title=?;`, content, title)
					reURL += "?d=true"
				} else {
					db.Exec(`UPDATE pages SET content=? WHERE title=?;`, content, title)
				}
				http.Redirect(w, r, reURL, http.StatusSeeOther)
				return
			}
		} else {
			if action == "Update" {
				if ctx.IsAdmin {
					editGroup := r.PostFormValue("editgroup")
					readGroup := r.PostFormValue("readgroup")
					var editGroupID, readGroupID string
					db.QueryRow(`SELECT id FROM groups WHERE name=?;`, editGroup).Scan(&editGroupID)
					db.QueryRow(`SELECT id FROM groups WHERE name=?;`, readGroup).Scan(&readGroupID)
					if editGroupID != "" {
						db.Exec(`UPDATE pages SET editgroupid=? WHERE title=?;`, editGroupID, title)
					} else {
						db.Exec(`UPDATE pages SET editgroupid=NULL WHERE title=?;`, title)
					}
					if readGroupID != "" {
						db.Exec(`UPDATE pages SET readgroupid=? WHERE title=?;`, readGroupID, title)
					} else {
						db.Exec(`UPDATE pages SET readgroupid=NULL WHERE title=?;`, title)
					}
					ctx.SetFlashMsg("Updated post " + title)
				}
			}
			if action == "Delete" {
				if !isPageMaster {
					templates.Render(w, "accessdenied.html", map[string]interface{}{
						"ctx": ctx,
					})
					return
				}
				ctx.SetFlashMsg("Deleted post " + title)
				db.Exec(`DELETE FROM pages WHERE title=?;`, title)
			}
			http.Redirect(w, r, "/pages/", http.StatusSeeOther)
			return
		}
	}
	var row *db.Row
	if isDiscussion {
		row = db.QueryRow(`SELECT discussion FROM pages WHERE title=?;`, title)
	} else {
		row = db.QueryRow(`SELECT content FROM pages WHERE title=?;`, title)
	}
	var content string
	if row.Scan(&content) != nil {
		ErrNotFoundHandler(w, r)
		return
	}
	editURL := "/editpage?t=" + cTitle
	if isDiscussion {
		editURL += "&d=true"
	}
	templates.Render(w, "index.html", map[string]interface{}{
		"ctx":          ctx,
		"IsEditMode":   true,
		"URL":          "/pages/" + cTitle,
		"EditURL":      "/editpage?t=" + cTitle,
		"Title":        title,
		"cTitle":       cTitle,
		"IsDiscussion": isDiscussion,
		"Content":      content,
	})
})
View Source
var PagesHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	isPageMaster := ctx.IsAdmin || models.IsUserInPageMasterGroup(ctx.UserName)
	title := models.IndexPage
	cTitle := strings.Replace(title, " ", "_", -1)
	isDiscussion := (r.FormValue("d") != "")
	if r.URL.Path != "/" {
		cTitle = r.URL.Path[7:]
		title = strings.Replace(cTitle, "_", " ", -1)
		if title == models.IndexPage && !isDiscussion {
			http.Redirect(w, r, "/", http.StatusSeeOther)
			return
		}
	}
	if title == "" {

		if !isPageMaster {
			templates.Render(w, "accessdenied.html", map[string]interface{}{
				"ctx": ctx,
			})
			return
		}

		type Page struct {
			Title     string
			CTitle    string
			ReadGroup string
			EditGroup string
		}
		pages := []Page{}
		rows := db.Query(`SELECT title, readgroupid, editgroupid FROM pages WHERE is_file=? ORDER BY title;`, false)
		for rows.Next() {
			page := Page{}
			var readGroupID, editGroupID sql.NullString
			rows.Scan(&page.Title, &readGroupID, &editGroupID)
			page.CTitle = strings.Replace(page.Title, " ", "_", -1)
			if readGroupID.Valid {
				db.QueryRow(`SELECT name FROM groups WHERE id=?;`, readGroupID).Scan(&page.ReadGroup)
			}
			if editGroupID.Valid {
				db.QueryRow(`SELECT name FROM groups WHERE id=?;`, editGroupID).Scan(&page.EditGroup)
			}
			pages = append(pages, page)
		}
		templates.Render(w, "pagelist.html", map[string]interface{}{
			"ctx":   ctx,
			"pages": pages,
		})
		return
	}
	// Render the relevant wiki page
	var row *db.Row
	if isDiscussion {
		row = db.QueryRow(`SELECT readgroupid, discussion FROM pages WHERE title=?;`, title)
	} else {
		row = db.QueryRow(`SELECT readgroupid, content FROM pages WHERE title=?;`, title)
	}
	var content string
	var readGroupID sql.NullString
	if row.Scan(&readGroupID, &content) != nil {
		ErrNotFoundHandler(w, r)
		return
	}
	if !isPageMaster && readGroupID.Valid {
		row := db.QueryRow(`SELECT groupmembers.id FROM groupmembers INNER JOIN users ON users.id=groupmembers.userid AND users.username=? WHERE groupmembers.groupid=?;`, ctx.UserName, readGroupID)
		var tmp string
		if row.Scan(&tmp) != nil {
			templates.Render(w, "accessdenied.html", map[string]interface{}{
				"ctx": ctx,
			})
			return
		}
	}
	unsafe := blackfriday.Run([]byte(strings.Replace(content, "\r\n", "\n", -1)))
	html := string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))

	type Link struct {
		name string
		URL  string
	}
	type TOCItem struct {
		title     Link
		subtitles []Link
	}
	var toc []TOCItem
	html = headerRe.ReplaceAllStringFunc(html, func(h string) string {
		n := string(h[2])
		if n == "2" || n == "3" {
			if len(h) >= 10 {
				t := h[4 : len(h)-5]
				id := "m-" + n + "-" + base64.StdEncoding.EncodeToString([]byte(t))
				link := Link{t, "#" + id}
				if n == "2" {
					var tocItem TOCItem
					tocItem.title = link
					toc = append(toc, tocItem)
				}
				if n == "3" {
					if len(toc) > 0 {
						toc[len(toc)-1].subtitles = append(toc[len(toc)-1].subtitles, link)
					}
				}
				return "<h" + n + " id=\"" + id + "\">" + t + "</h" + n + ">"
			}
		}
		return h
	})
	tocHTML := "<div class=\"toc\"><ol>\n"
	for _, t := range toc {
		tocHTML += "<li><a href=\"" + t.title.URL + "\">" + t.title.name + "</a>"
		if len(t.subtitles) > 0 {
			tocHTML += "<ul>"
			for _, s := range t.subtitles {
				tocHTML += "<li><a href=\"" + s.URL + "\">" + s.name + "</a>"
			}
			tocHTML += "</ul>"
		}
		tocHTML += "</li>"
	}
	tocHTML += "\n</ol></div>"
	html = strings.Replace(html, "<p><strong>TOC</strong></p>", tocHTML, 1)
	if r.URL.Path != "" {
		ctx.PageTitle = title
	}
	editURL := "/editpage?t=" + cTitle
	if isDiscussion {
		editURL += "&d=true"
	}
	templates.Render(w, "index.html", map[string]interface{}{
		"ctx":          ctx,
		"Title":        title,
		"cTitle":       cTitle,
		"IsDiscussion": isDiscussion,
		"URL":          "/pages/" + cTitle,
		"EditURL":      editURL,
		"Content":      template.HTML(html),
	})
})
View Source
var ProfileBanHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	username := r.PostFormValue("username")
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}
	db.Exec(`UPDATE users SET is_banned=? WHERE username=?;`, true, username)

	var userID string
	if db.QueryRow(`SELECT id FROM users WHERE username=?;`, username).Scan(&userID) == nil {
		db.Exec(`DELETE FROM sessions WHERE userid=?;`, userID)
	}
	http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
})
View Source
var ProfileHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	username := r.FormValue("u")

	if !ctx.IsAdmin && (username != ctx.UserName) {
		ErrForbiddenHandler(w, r)
		return
	}

	row := db.QueryRow(`SELECT email, is_banned FROM users WHERE username=?;`, username)
	var email string
	var isBanned bool
	if err := row.Scan(&email, &isBanned); err != nil {
		ErrNotFoundHandler(w, r)
		return
	}
	if isBanned && !ctx.IsAdmin {
		ErrNotFoundHandler(w, r)
		return
	}

	templates.Render(w, "profile.html", map[string]interface{}{
		"ctx":      ctx,
		"username": username,
		"email":    email,
		"IsBanned": isBanned,
	})
})
View Source
var ProfileUnbanHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	username := r.PostFormValue("username")
	if !ctx.IsAdmin {
		ErrForbiddenHandler(w, r)
		return
	}
	db.Exec(`UPDATE users SET is_banned=? WHERE username=?;`, false, username)
	http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
})
View Source
var ProfileUpdateHandler = A(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method != "POST" {
		ErrForbiddenHandler(w, r)
		return
	}
	username := r.PostFormValue("username")
	if !ctx.IsAdmin && (username != ctx.UserName) {
		ErrForbiddenHandler(w, r)
		return
	}
	email := r.PostFormValue("email")
	db.Exec(`UPDATE users SET email=? WHERE username=?;`, email, username)
	ctx.SetFlashMsg("Email updated")
	http.Redirect(w, r, "/profile?u="+username, http.StatusSeeOther)
})
View Source
var ResetpassHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	resetToken := r.FormValue("token")

	row := db.QueryRow(`SELECT username, reset_token_date FROM users WHERE reset_token=?;`, resetToken)
	var username string
	var rDate int64
	if err := row.Scan(&username, &rDate); err != nil {
		ErrNotFoundHandler(w, r)
		return
	}
	resetTokenDate := time.Unix(rDate, 0)
	if resetTokenDate.Before(time.Now().Add(-100 * time.Hour)) {
		ErrNotFoundHandler(w, r)
		return
	}

	if r.Method == "POST" {
		passwd := r.PostFormValue("passwd")
		passwd2 := r.PostFormValue("passwd2")
		if passwd == passwd2 {
			if err := models.ValidatePasswd(passwd); err == nil {
				models.UpdateUserPasswd(username, passwd)
				db.Exec(`UPDATE users SET reset_token_date=0 WHERE username=?;`, username)
				http.Redirect(w, r, "/login", http.StatusSeeOther)
				return
			} else {
				ctx.FlashMsg = err.Error()
			}
		} else {
			ctx.FlashMsg = "New passwords do not match"
		}
	}
	templates.Render(w, "resetpass.html", map[string]interface{}{
		"ctx":        ctx,
		"ResetToken": resetToken,
	})
})
View Source
var SearchHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	searchTerms := r.FormValue("q")
	results := models.PageSearch(searchTerms)
	templates.Render(w, "search.html", map[string]interface{}{
		"ctx":         ctx,
		"Results":     results,
		"SearchTerms": searchTerms,
	})
})
View Source
var SignupHandler = UA(func(w http.ResponseWriter, r *http.Request, ctx *Context) {
	if r.Method == "POST" {
		username := r.PostFormValue("username")
		passwd := r.PostFormValue("passwd")
		passwd2 := r.PostFormValue("passwd2")

		if ctx.IsAdmin || !ctx.Config.SignupDisabled {
			if err := models.ValidateName(username); err == nil {
				if passwd == passwd2 {
					if err := models.ValidatePasswd(passwd); err == nil {
						if err := models.CreateUser(username, passwd, "", false); err == nil {
							if ctx.IsAdmin {
								http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
								return
							} else {
								if err := ctx.Authenticate(username, passwd); err == nil {
									http.SetCookie(w, &http.Cookie{Name: "sessionid", Path: "/", Value: ctx.SessionID, HttpOnly: true})
									http.Redirect(w, r, "/", http.StatusSeeOther)
									return
								} else {
									ctx.FlashMsg = err.Error()
								}
							}
						} else {
							ctx.FlashMsg = err.Error()
						}
					} else {
						ctx.FlashMsg = err.Error()
					}
				} else {
					ctx.FlashMsg = "Passwords don't match"
				}
			} else {
				ctx.FlashMsg = err.Error()
			}
		} else {
			ctx.FlashMsg = "Signup disabled. Contact admin."
		}
	}
	templates.Render(w, "signup.html", map[string]interface{}{
		"ctx": ctx,
	})
})

Functions

func A

func A(handler func(w http.ResponseWriter, r *http.Request, ctx *Context)) func(w http.ResponseWriter, r *http.Request)

func ErrForbiddenHandler

func ErrForbiddenHandler(w http.ResponseWriter, r *http.Request)

func ErrNotFoundHandler

func ErrNotFoundHandler(w http.ResponseWriter, r *http.Request)

func ErrServerHandler

func ErrServerHandler(w http.ResponseWriter, r *http.Request)

func FaviconHandler

func FaviconHandler(w http.ResponseWriter, r *http.Request)

func LogoutHandler

func LogoutHandler(w http.ResponseWriter, r *http.Request)

func ScriptHandler

func ScriptHandler(w http.ResponseWriter, r *http.Request)

func SendMail

func SendMail(to string, sub string, body string, config WikiConfig)

func StyleHandler

func StyleHandler(w http.ResponseWriter, r *http.Request)

func UA

func UA(handler func(w http.ResponseWriter, r *http.Request, ctx *Context)) func(w http.ResponseWriter, r *http.Request)

Types

type Context

type Context struct {
	SessionID   string
	UserName    string
	IsUserValid bool
	IsAdmin     bool
	AdminErrMsg string
	CSRFToken   string
	FlashMsg    string
	PageTitle   string
	Config      WikiConfig
	HeaderLinks []NavLink
	FooterLinks []NavLink
	NavSections []NavSection
}

func ReadContext

func ReadContext(sessionID string) Context

func (*Context) Authenticate

func (ctx *Context) Authenticate(username string, passwd string) error

func (*Context) SetFlashMsg

func (ctx *Context) SetFlashMsg(msg string)

func (*Context) ValidateCSRFToken

func (ctx *Context) ValidateCSRFToken(token string) error
type NavLink struct {
	Title string
	URL   string
}
type NavSection struct {
	Title string
	Links []NavLink
}

type WikiConfig

type WikiConfig struct {
	WikiName       string
	SignupDisabled bool
	DataDir        string
	SMTPHost       string
	SMTPPort       string
	SMTPUser       string
	SMTPPasswd     string
	FromEmail      string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL