diff --git a/history/operations.go b/history/operations.go
index 0507483..e16c367 100644
--- a/history/operations.go
+++ b/history/operations.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"sync"
+ "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -29,7 +30,7 @@ const (
type HistoryOp struct {
// All errors are appended here.
Errs []error
- opType OpType
+ Type OpType
userMsg string
name string
email string
@@ -39,8 +40,10 @@ type HistoryOp struct {
func Operation(opType OpType) *HistoryOp {
gitMutex.Lock()
hop := &HistoryOp{
- Errs: []error{},
- opType: opType,
+ Errs: []error{},
+ name: "anon",
+ email: "anon@mycorrhiza",
+ Type: opType,
}
return hop
}
@@ -116,9 +119,11 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
return hop
}
-// WithSignature sets a signature for the future commit. You need to pass a username only, the rest is upon us (including email and time).
-func (hop *HistoryOp) WithSignature(username string) *HistoryOp {
- hop.name = username
- hop.email = username + "@mycorrhiza" // A fake email, why not
+// WithUser sets a user for the commit.
+func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
+ if u.Group != user.UserAnon {
+ hop.name = u.Name
+ hop.email = u.Name + "@mycorrhiza"
+ }
return hop
}
diff --git a/http_mutators.go b/http_mutators.go
index 59f464b..232fed3 100644
--- a/http_mutators.go
+++ b/http_mutators.go
@@ -11,12 +11,14 @@ import (
)
func init() {
- http.HandleFunc("/upload-binary/", handlerUploadBinary)
- http.HandleFunc("/upload-text/", handlerUploadText)
+ // Those that do not actually mutate anything:
http.HandleFunc("/edit/", handlerEdit)
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
- http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
http.HandleFunc("/rename-ask/", handlerRenameAsk)
+ // And those that do mutate something:
+ http.HandleFunc("/upload-binary/", handlerUploadBinary)
+ http.HandleFunc("/upload-text/", handlerUploadText)
+ http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
http.HandleFunc("/rename-confirm/", handlerRenameConfirm)
}
@@ -38,20 +40,16 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "rename-confirm")
- hyphaData, isOld = HyphaStorage[hyphaName]
+ _, isOld = HyphaStorage[hyphaName]
newName = CanonicalName(rq.PostFormValue("new-name"))
_, newNameIsUsed = HyphaStorage[newName]
- recursive bool
+ recursive = rq.PostFormValue("recursive") == "true"
+ u = user.FromRequest(rq).OrAnon()
)
- if ok := user.CanProceed(rq, "rename-confirm"); !ok {
+ switch {
+ case !u.CanProceed("rename-confirm"):
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
log.Println("Rejected", rq.URL)
- return
- }
- if rq.PostFormValue("recursive") == "true" {
- recursive = true
- }
- switch {
case newNameIsUsed:
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
fmt.Sprintf("Hypha named %s already exists.", hyphaName, hyphaName))
@@ -65,12 +63,12 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
"Invalid new name. Names cannot contain characters ^?!:#@><*|\"\\'&%
")
default:
- if hop := hyphaData.RenameHypha(hyphaName, newName, recursive); len(hop.Errs) == 0 {
- http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
- } else {
+ if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not rename hypha",
fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: %v
", hop.Errs))
+ } else {
+ http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
}
}
}
@@ -96,27 +94,27 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
var (
hyphaName = HyphaNameFromRq(rq, "delete-confirm")
hyphaData, isOld = HyphaStorage[hyphaName]
+ u = user.FromRequest(rq)
)
- if ok := user.CanProceed(rq, "delete-confirm"); !ok {
+ if !u.CanProceed("delete-confirm") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
log.Println("Rejected", rq.URL)
return
}
- if isOld {
- // If deleted successfully
- if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 {
- http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
- } else {
- HttpErr(w, http.StatusInternalServerError, hyphaName,
- "Error: could not delete hypha",
- fmt.Sprintf("Could not delete this hypha due to an internal error. Server errors: %v
", hop.Errs))
- }
- } else {
+ if !isOld {
// The precondition is to have the hypha in the first place.
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
"Error: no such hypha",
"Could not delete this hypha because it does not exist.")
+ return
}
+ if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 {
+ HttpErr(w, http.StatusInternalServerError, hyphaName,
+ "Error: could not delete hypha",
+ fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: %v
", hop.Errs))
+ return
+ }
+ http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
// handlerEdit shows the edit form. It doesn't edit anything actually.
@@ -151,23 +149,20 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
- hyphaName = HyphaNameFromRq(rq, "upload-text")
- hyphaData, isOld = HyphaStorage[hyphaName]
- textData = rq.PostFormValue("text")
+ hyphaName = HyphaNameFromRq(rq, "upload-text")
+ textData = rq.PostFormValue("text")
+ u = user.FromRequest(rq).OrAnon()
)
if ok := user.CanProceed(rq, "upload-text"); !ok {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL)
return
}
- if !isOld {
- hyphaData = &HyphaData{}
- }
if textData == "" {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
return
}
- if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 {
+ if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else {
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
@@ -177,36 +172,37 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
// handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
- hyphaName := HyphaNameFromRq(rq, "upload-binary")
- if ok := user.CanProceed(rq, "upload-binary"); !ok {
+ var (
+ hyphaName = HyphaNameFromRq(rq, "upload-binary")
+ u = user.FromRequest(rq)
+ )
+ if !u.CanProceed("upload-binary") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
log.Println("Rejected", rq.URL)
return
}
- rq.ParseMultipartForm(10 << 20)
+ rq.ParseMultipartForm(10 << 20) // Set upload limit
file, handler, err := rq.FormFile("binary")
if file != nil {
defer file.Close()
}
+
// If file is not passed:
if err != nil {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
return
}
+
// If file is passed:
var (
- hyphaData, isOld = HyphaStorage[hyphaName]
- mime = handler.Header.Get("Content-Type")
+ mime = handler.Header.Get("Content-Type")
+ hop = UploadBinary(hyphaName, mime, file, u)
)
- if !isOld {
- hyphaData = &HyphaData{}
- }
- hop := hyphaData.UploadBinary(hyphaName, mime, file, isOld)
if len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
- } else {
- http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
+ return
}
+ http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
diff --git a/hypha.go b/hypha.go
index a49b785..0e6e583 100644
--- a/hypha.go
+++ b/hypha.go
@@ -12,6 +12,7 @@ import (
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/markup"
+ "github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -33,6 +34,15 @@ func init() {
}
}
+// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
+func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) {
+ hyphaData, isOld = HyphaStorage[hyphaName]
+ if hyphaData == nil {
+ hyphaData = &HyphaData{}
+ }
+ return
+}
+
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
type HyphaData struct {
textPath string
@@ -40,10 +50,16 @@ type HyphaData struct {
}
// uploadHelp is a helper function for UploadText and UploadBinary
-func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, originalFullPath *string, isOld bool, data []byte) *history.HistoryOp {
+func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
var (
- fullPath = filepath.Join(WikiDir, hyphaName+ext)
+ hyphaData, isOld = GetHyphaData(hyphaName)
+ fullPath = filepath.Join(WikiDir, hyphaName+ext)
+ originalFullPath = &hyphaData.textPath
)
+ if hop.Type == history.TypeEditBinary {
+ originalFullPath = &hyphaData.binaryPath
+ }
+
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
return hop.WithError(err)
}
@@ -51,30 +67,34 @@ func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, o
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
return hop.WithError(err)
}
+
if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
if err := history.Rename(*originalFullPath, fullPath); err != nil {
return hop.WithError(err)
}
log.Println("Move", *originalFullPath, "to", fullPath)
}
+ // New hyphae must be added to the hypha storage
if !isOld {
- HyphaStorage[hyphaName] = hd
+ HyphaStorage[hyphaName] = hyphaData
}
*originalFullPath = fullPath
- log.Printf("%v\n", *hd)
return hop.WithFiles(fullPath).
- WithSignature("anon").
+ WithUser(u).
Apply()
}
-// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`.
-func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp {
- hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName))
- return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData))
+// UploadText loads a new text part from `textData` for hypha `hyphaName`.
+func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
+ return uploadHelp(
+ history.
+ Operation(history.TypeEditText).
+ WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)),
+ hyphaName, ".myco", []byte(textData), u)
}
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
-func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp {
+func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp {
var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime))
data, err = ioutil.ReadAll(file)
@@ -82,16 +102,15 @@ func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, i
if err != nil {
return hop.WithError(err).Apply()
}
-
- return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data)
+ return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u)
}
// DeleteHypha deletes hypha and makes a history record about that.
-func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp {
+func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeDeleteHypha).
WithFilesRemoved(hd.textPath, hd.binaryPath).
WithMsg(fmt.Sprintf("Delete ‘%s’", hyphaName)).
- WithSignature("anon").
+ WithUser(u).
Apply()
if len(hop.Errs) == 0 {
delete(HyphaStorage, hyphaName)
@@ -138,7 +157,7 @@ func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
}
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
-func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *history.HistoryOp {
+func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
var (
replaceName = func(str string) string {
return strings.Replace(str, hyphaName, newName, 1)
@@ -157,7 +176,7 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
}
hop.WithFilesRenamed(renameMap).
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
- WithSignature("anon").
+ WithUser(u).
Apply()
if len(hop.Errs) == 0 {
relocateHyphaData(hyphaNames, replaceName)
diff --git a/user/group.go b/user/group.go
index d06fb29..6a9a296 100644
--- a/user/group.go
+++ b/user/group.go
@@ -62,9 +62,9 @@ func (ug UserGroup) CanAccessRoute(route string) bool {
}
func CanProceed(rq *http.Request, route string) bool {
- ug := UserAnon
- if u := FromRequest(rq); u != nil {
- ug = u.Group
- }
- return ug.CanAccessRoute(route)
+ return FromRequest(rq).OrAnon().CanProceed(route)
+}
+
+func (u *User) CanProceed(route string) bool {
+ return u.Group.CanAccessRoute(route)
}
diff --git a/user/user.go b/user/user.go
index 3c999c8..7169760 100644
--- a/user/user.go
+++ b/user/user.go
@@ -44,7 +44,7 @@ func FromRequest(rq *http.Request) *User {
if err != nil {
return nil
}
- return UserStorage.userByToken(cookie.Value)
+ return UserStorage.userByToken(cookie.Value).OrAnon()
}
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {