diff --git a/database.go b/database.go index c1e6f95f..6b65af9d 100644 --- a/database.go +++ b/database.go @@ -63,11 +63,11 @@ func initDatabase() (err error) { } log.Print("Loading the forum permissions.") - err = buildForumPermissions() + fpstore = NewForumPermsStore() + err = fpstore.Init() if err != nil { return err } - fpstore = NewForumPermsStore() log.Print("Loading the settings.") err = LoadSettings() diff --git a/forum_perms_store.go b/forum_perms_store.go index 2c24057f..62879008 100644 --- a/forum_perms_store.go +++ b/forum_perms_store.go @@ -1,5 +1,10 @@ package main +import ( + "encoding/json" + "log" +) + var fpstore *ForumPermsStore type ForumPermsStore struct { @@ -9,6 +14,156 @@ func NewForumPermsStore() *ForumPermsStore { return &ForumPermsStore{} } +func (fps *ForumPermsStore) Init() error { + fids, err := fstore.GetAllIDs() + if err != nil { + return err + } + if dev.SuperDebug { + log.Print("fids: ", fids) + } + + rows, err := getForumsPermissionsStmt.Query() + if err != nil { + return err + } + defer rows.Close() + + if dev.DebugMode { + log.Print("Adding the forum permissions") + if dev.SuperDebug { + log.Print("forumPerms[gid][fid]") + } + } + + // Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice + forumPerms = make(map[int]map[int]ForumPerms) + for rows.Next() { + var gid, fid int + var perms []byte + var pperms ForumPerms + err = rows.Scan(&gid, &fid, &perms) + if err != nil { + return err + } + + if dev.SuperDebug { + log.Print("perms: ", string(perms)) + } + err = json.Unmarshal(perms, &pperms) + if err != nil { + return err + } + pperms.ExtData = make(map[string]bool) + pperms.Overrides = true + _, ok := forumPerms[gid] + if !ok { + forumPerms[gid] = make(map[int]ForumPerms) + } + + if dev.SuperDebug { + log.Print("gid: ", gid) + log.Print("fid: ", fid) + log.Printf("perms: %+v\n", pperms) + } + forumPerms[gid][fid] = pperms + } + + return fps.cascadePermSetToGroups(forumPerms, fids) +} + +// TODO: Need a more thread-safe way of doing this. Possibly with sync.Map? +func (fps *ForumPermsStore) Reload(fid int) error { + if dev.DebugMode { + log.Printf("Reloading the forum permissions for forum #%d", fid) + } + fids, err := fstore.GetAllIDs() + if err != nil { + return err + } + + rows, err := db.Query("select gid, permissions from forums_permissions where fid = ? order by gid asc", fid) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + var gid int + var perms []byte + var pperms ForumPerms + err := rows.Scan(&gid, &perms) + if err != nil { + return err + } + err = json.Unmarshal(perms, &pperms) + if err != nil { + return err + } + pperms.ExtData = make(map[string]bool) + pperms.Overrides = true + _, ok := forumPerms[gid] + if !ok { + forumPerms[gid] = make(map[int]ForumPerms) + } + forumPerms[gid][fid] = pperms + } + + return fps.cascadePermSetToGroups(forumPerms, fids) +} + +func (fps *ForumPermsStore) cascadePermSetToGroups(forumPerms map[int]map[int]ForumPerms, fids []int) error { + groups, err := gstore.GetAll() + if err != nil { + return err + } + + for _, group := range groups { + if dev.DebugMode { + log.Printf("Updating the forum permissions for Group #%d", group.ID) + } + group.Forums = []ForumPerms{BlankForumPerms} + group.CanSee = []int{} + fps.cascadePermSetToGroup(forumPerms, group, fids) + + if dev.SuperDebug { + log.Printf("group.CanSee (length %d): %+v \n", len(group.CanSee), group.CanSee) + log.Printf("group.Forums (length %d): %+v\n", len(group.Forums), group.Forums) + } + } + return nil +} + +func (fps *ForumPermsStore) cascadePermSetToGroup(forumPerms map[int]map[int]ForumPerms, group *Group, fids []int) { + for _, fid := range fids { + if dev.SuperDebug { + log.Printf("Forum #%+v\n", fid) + } + forumPerm, ok := forumPerms[group.ID][fid] + if ok { + //log.Print("Overriding permissions for forum #%d",fid) + group.Forums = append(group.Forums, forumPerm) + } else { + //log.Printf("Inheriting from group defaults for forum #%d",fid) + forumPerm = BlankForumPerms + group.Forums = append(group.Forums, forumPerm) + } + if forumPerm.Overrides { + if forumPerm.ViewTopic { + group.CanSee = append(group.CanSee, fid) + } + } else if group.Perms.ViewTopic { + group.CanSee = append(group.CanSee, fid) + } + + if dev.SuperDebug { + log.Print("group.ID: ", group.ID) + log.Printf("forumPerm: %+v\n", forumPerm) + log.Print("group.CanSee: ", group.CanSee) + } + } +} + func (fps *ForumPermsStore) Get(fid int, gid int) (fperms ForumPerms, err error) { // TODO: Add a hook here and have plugin_guilds use it group, err := gstore.Get(gid) diff --git a/group_store.go b/group_store.go index 848668f9..4cb5ea6a 100644 --- a/group_store.go +++ b/group_store.go @@ -157,21 +157,24 @@ func (mgs *MemoryGroupStore) Reload(id int) error { func (mgs *MemoryGroupStore) initGroup(group *Group) error { err := json.Unmarshal(group.PermissionsText, &group.Perms) if err != nil { + log.Printf("group: %+v\n", group) + log.Print("bad group perms: ", group.PermissionsText) return err } if dev.DebugMode { - log.Print(group.Name + ": ") - log.Printf("%+v\n", group.Perms) + log.Printf(group.Name+": %+v\n", group.Perms) } err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms) if err != nil { + log.Printf("group: %+v\n", group) + log.Print("bad group plugin perms: ", group.PluginPermsText) return err } if dev.DebugMode { - log.Print(group.Name + ": ") - log.Printf("%+v\n", group.PluginPerms) + log.Printf(group.Name+": %+v\n", group.PluginPerms) } + //group.Perms.ExtData = make(map[string]bool) // TODO: Can we optimise the bit where this cascades down to the user now? if group.IsAdmin || group.IsMod { @@ -204,7 +207,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod } defer tx.Rollback() - insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions", "?,?,?,?,?,?") + insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions, plugin_perms", "?,?,?,?,?,?,'{}'") if err != nil { return 0, err } @@ -279,7 +282,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod mgs.Unlock() for _, forum := range fdata { - err = rebuildForumPermissions(forum.ID) + err = fpstore.Reload(forum.ID) if err != nil { return gid, err } diff --git a/install-linux b/install-linux index da75ec05..6b23f0f3 100644 --- a/install-linux +++ b/install-linux @@ -22,6 +22,9 @@ go get -u gopkg.in/sourcemap.v1 echo "Installing OttoJS" go get -u github.com/robertkrimen/otto +echo "Installing the Riot Search Engine" +go get -u github.com/robertkrimen/otto + echo "Building the installer" cd ./install diff --git a/install.bat b/install.bat index 592383bd..2bbe9354 100644 --- a/install.bat +++ b/install.bat @@ -71,6 +71,13 @@ if %errorlevel% neq 0 ( exit /b %errorlevel% ) +echo Installing the Riot Search Engine +go get -u github.com/go-ego/riot +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) + echo Building the installer go generate diff --git a/langs/english.json b/langs/english.json index afc9d649..d8a19939 100644 --- a/langs/english.json +++ b/langs/english.json @@ -42,6 +42,16 @@ "SettingLabels": { "activation_type": "Activate All,Email Activation,Admin Approval" }, + "PermPresets": { + "all":"Public", + "announce":"Announcements", + "members":"Member Only", + "staff":"Staff Only", + "admins":"Admin Only", + "archive":"Archive", + "custom":"Custom", + "unknown":"Unknown" + }, "Accounts": { "VerifyEmailSubject": "Validate Your Email @ {{name}}", "VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused." diff --git a/misc_test.go b/misc_test.go index 41e11a37..962b6056 100644 --- a/misc_test.go +++ b/misc_test.go @@ -421,6 +421,8 @@ func userStoreTest(t *testing.T, newUserID int) { _, err = users.Get(newUserID) recordMustNotExist(t, err, "UID #%d shouldn't exist", newUserID) + + // TODO: Add tests for the Cache* methods } // TODO: Add an error message to this? @@ -445,6 +447,36 @@ func expect(t *testing.T, item bool, errmsg string) { } } +func TestPermsMiddleware(t *testing.T) { + if !gloinited { + err := gloinit() + if err != nil { + t.Fatal(err) + } + } + if !pluginsInited { + initPlugins() + } + + dummyResponseRecorder := httptest.NewRecorder() + bytesBuffer := bytes.NewBuffer([]byte("")) + dummyRequest := httptest.NewRequest("", "/forum/1", bytesBuffer) + user := getDummyUser() + + ferr := SuperModOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr != nil, "Blank users shouldn't be supermods") + + user.IsSuperMod = false + ferr = SuperModOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr != nil, "Non-supermods shouldn't be allowed through supermod gates") + + user.IsSuperMod = true + ferr = SuperModOnly(dummyResponseRecorder, dummyRequest, *user) + expect(t, ferr == nil, "Supermods should be allowed through supermod gates") + + // TODO: Loop over the Control Panel routes and make sure only supermods can get in +} + func TestTopicStore(t *testing.T) { if !gloinited { err := gloinit() @@ -525,31 +557,44 @@ func TestForumStore(t *testing.T) { if forum.ID != 1 { t.Error("forum.ID doesn't not match the requested FID. Got '" + strconv.Itoa(forum.ID) + "' instead.'") } - if forum.Name != "Reports" { - t.Error("FID #0 is named '" + forum.Name + "' and not 'Reports'") - } + // TODO: Check the preset and forum permissions + expect(t, forum.Name == "Reports", fmt.Sprintf("FID #0 is named '%s' and not 'Reports'", forum.Name)) + expect(t, !forum.Active, fmt.Sprintf("The reports forum shouldn't be active")) + var expectDesc = "All the reports go here" + expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc)) forum, err = fstore.Get(2) recordMustExist(t, err, "Couldn't find FID #1") - _ = forum + expect(t, forum.ID == 2, fmt.Sprintf("The FID should be 2 not %d", forum.ID)) + expect(t, forum.Name == "General", fmt.Sprintf("The name of the forum should be 'General' not '%s'", forum.Name)) + expect(t, forum.Active, fmt.Sprintf("The general forum should be active")) + expectDesc = "A place for general discussions which don't fit elsewhere" + expect(t, forum.Desc == expectDesc, fmt.Sprintf("The forum description should be '%s' not '%s'", expectDesc, forum.Desc)) ok := fstore.Exists(-1) - if ok { - t.Error("FID #-1 shouldn't exist") - } - + expect(t, !ok, "FID #-1 shouldn't exist") ok = fstore.Exists(0) - if ok { - t.Error("FID #0 shouldn't exist") - } - + expect(t, !ok, "FID #0 shouldn't exist") ok = fstore.Exists(1) - if !ok { - t.Error("FID #1 should exist") + expect(t, ok, "FID #1 should exist") + + // TODO: Test forum creation + // TODO: Test forum deletion + // TODO: Test forum update +} + +// TODO: Implement this +func TestForumPermsStore(t *testing.T) { + if !gloinited { + gloinit() + } + if !pluginsInited { + initPlugins() } } +// TODO: Test the group permissions func TestGroupStore(t *testing.T) { if !gloinited { gloinit() @@ -646,7 +691,19 @@ func TestGroupStore(t *testing.T) { expect(t, group.IsMod, "This should be a mod group") expect(t, !group.IsBanned, "This shouldn't be a ban group") + // Make sure the data is static + gstore.Reload(gid) + + group, err = gstore.Get(gid) + expectNilErr(t, err) + expect(t, group.ID == gid, "The group ID should match the requested ID") + expect(t, !group.IsAdmin, "This shouldn't be an admin group") + expect(t, group.IsMod, "This should be a mod group") + expect(t, !group.IsBanned, "This shouldn't be a ban group") + // TODO: Test group deletion + // TODO: Test group reload + // TODO: Test group cache set } func TestReplyStore(t *testing.T) { @@ -714,6 +771,7 @@ func TestSlugs(t *testing.T) { msgList = addMEPair(msgList, "--", "untitled") msgList = addMEPair(msgList, "é", "é") msgList = addMEPair(msgList, "-é-", "é") + msgList = addMEPair(msgList, "-你好-", "untitled") for _, item := range msgList { t.Log("Testing string '" + item.Msg + "'") diff --git a/panel_routes.go b/panel_routes.go index 0477cf8c..1839787b 100644 --- a/panel_routes.go +++ b/panel_routes.go @@ -338,7 +338,6 @@ func routePanelForumsEdit(w http.ResponseWriter, r *http.Request, user User, sfi if err == ErrNoRows { return LocalError("The forum you're trying to edit doesn't exist.", w, r, user) } else if err != nil { - return InternalError(err, w, r) } @@ -465,6 +464,8 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use return InternalErrorJSQ(err, w, r, isJs) } + // ! IMPORTANT + // TODO: Refactor this forumUpdateMutex.Lock() defer forumUpdateMutex.Unlock() if changed { @@ -488,7 +489,6 @@ func routePanelForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, use } err = fstore.Reload(fid) if err != nil { - // TODO: Log this? -- Another admin might have deleted it return LocalErrorJSQ("Unable to reload forum", w, r, user, isJs) } } diff --git a/permissions.go b/permissions.go index b738fe7d..d408f1de 100644 --- a/permissions.go +++ b/permissions.go @@ -349,7 +349,7 @@ func permmapToQuery(permmap map[string]ForumPerms, fid int) error { permUpdateMutex.Lock() defer permUpdateMutex.Unlock() - return rebuildForumPermissions(fid) + return fpstore.Reload(fid) } func replaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[int]ForumPerms) error { @@ -393,163 +393,7 @@ func replaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, return nil } -// TODO: Need a more thread-safe way of doing this. Possibly with sync.Map? -func rebuildForumPermissions(fid int) error { - if dev.DebugMode { - log.Print("Loading the forum permissions") - } - fids, err := fstore.GetAllIDs() - if err != nil { - return err - } - - rows, err := db.Query("select gid, permissions from forums_permissions where fid = ? order by gid asc", fid) - if err != nil { - return err - } - defer rows.Close() - - if dev.DebugMode { - log.Print("Updating the forum permissions") - } - for rows.Next() { - var gid int - var perms []byte - var pperms ForumPerms - err := rows.Scan(&gid, &perms) - if err != nil { - return err - } - err = json.Unmarshal(perms, &pperms) - if err != nil { - return err - } - pperms.ExtData = make(map[string]bool) - pperms.Overrides = true - _, ok := forumPerms[gid] - if !ok { - forumPerms[gid] = make(map[int]ForumPerms) - } - forumPerms[gid][fid] = pperms - } - - return cascadePermSetToGroups(forumPerms, fids) -} - -func buildForumPermissions() error { - fids, err := fstore.GetAllIDs() - if err != nil { - return err - } - if dev.SuperDebug { - log.Print("fids: ", fids) - } - - rows, err := getForumsPermissionsStmt.Query() - if err != nil { - return err - } - defer rows.Close() - - if dev.DebugMode { - log.Print("Adding the forum permissions") - if dev.SuperDebug { - log.Print("forumPerms[gid][fid]") - } - } - - // Temporarily store the forum perms in a map before transferring it to a much faster and thread-safe slice - forumPerms = make(map[int]map[int]ForumPerms) - for rows.Next() { - var gid, fid int - var perms []byte - var pperms ForumPerms - err = rows.Scan(&gid, &fid, &perms) - if err != nil { - return err - } - - if dev.SuperDebug { - log.Print("perms: ", string(perms)) - } - err = json.Unmarshal(perms, &pperms) - if err != nil { - return err - } - pperms.ExtData = make(map[string]bool) - pperms.Overrides = true - _, ok := forumPerms[gid] - if !ok { - forumPerms[gid] = make(map[int]ForumPerms) - } - - if dev.SuperDebug { - log.Print("gid: ", gid) - log.Print("fid: ", fid) - log.Printf("perms: %+v;", pperms) - } - forumPerms[gid][fid] = pperms - } - - return cascadePermSetToGroups(forumPerms, fids) -} - -func cascadePermSetToGroups(forumPerms map[int]map[int]ForumPerms, fids []int) error { - groups, err := gstore.GetAll() - if err != nil { - return err - } - - for _, group := range groups { - if dev.DebugMode { - log.Printf("Updating the forum permissions for Group #%d", group.ID) - } - group.Forums = []ForumPerms{BlankForumPerms} - group.CanSee = []int{} - cascadePermSetToGroup(forumPerms, group, fids) - - if dev.SuperDebug { - log.Printf("group.CanSee %+v\n", group.CanSee) - log.Printf("group.Forums %+v\n", group.Forums) - log.Print("len(group.CanSee): ", len(group.CanSee)) - log.Print("len(group.Forums): ", len(group.Forums)) // This counts blank aka 0 - } - } - return nil -} - -func cascadePermSetToGroup(forumPerms map[int]map[int]ForumPerms, group *Group, fids []int) { - for _, fid := range fids { - if dev.SuperDebug { - log.Printf("Forum #%+v\n", fid) - } - forumPerm, ok := forumPerms[group.ID][fid] - if ok { - // Override group perms - //log.Print("Overriding permissions for forum #" + strconv.Itoa(fid)) - group.Forums = append(group.Forums, forumPerm) - } else { - // Inherit from Group - //log.Print("Inheriting from default for forum #" + strconv.Itoa(fid)) - forumPerm = BlankForumPerms - group.Forums = append(group.Forums, forumPerm) - } - if forumPerm.Overrides { - if forumPerm.ViewTopic { - group.CanSee = append(group.CanSee, fid) - } - } else if group.Perms.ViewTopic { - group.CanSee = append(group.CanSee, fid) - } - - if dev.SuperDebug { - log.Print("group.ID: ", group.ID) - log.Printf("forumPerm: %+v\n", forumPerm) - log.Print("group.CanSee: ", group.CanSee) - } - } -} - +// TODO: Refactor this and write tests for it func forumPermsToGroupForumPreset(fperms ForumPerms) string { if !fperms.Overrides { return "default" @@ -614,24 +458,12 @@ func stripInvalidPreset(preset string) string { // TODO: Move this into the phrase system? func presetToLang(preset string) string { - switch preset { - case "all": - return "Public" - case "announce": - return "Announcements" - case "members": - return "Member Only" - case "staff": - return "Staff Only" - case "admins": - return "Admin Only" - case "archive": - return "Archive" - case "custom": - return "Custom" - default: - return "" + phrases := GetAllPermPresets() + phrase, ok := phrases[preset] + if !ok { + phrase = phrases["unknown"] } + return phrase } // TODO: Is this racey? diff --git a/phrases.go b/phrases.go index 746dd865..b18d9214 100644 --- a/phrases.go +++ b/phrases.go @@ -39,6 +39,7 @@ type LanguagePack struct { GlobalPerms map[string]string LocalPerms map[string]string SettingLabels map[string]string + PermPresets map[string]string Accounts map[string]string // TODO: Apply these phrases in the software proper } @@ -139,6 +140,10 @@ func GetAllSettingLabels() map[string]string { return currentLangPack.Load().(*LanguagePack).SettingLabels } +func GetAllPermPresets() map[string]string { + return currentLangPack.Load().(*LanguagePack).PermPresets +} + func GetAccountPhrase(name string) string { res, ok := currentLangPack.Load().(*LanguagePack).Accounts[name] if !ok { diff --git a/plugin_bbcode.go b/plugin_bbcode.go index 26e18d0b..de9db304 100644 --- a/plugin_bbcode.go +++ b/plugin_bbcode.go @@ -261,8 +261,7 @@ func bbcodeFullParse(msg string) string { i += 6 } //if msglen >= (i+5) { - // log.Print("boo2") - // log.Print(string(msgbytes[i:i+5])) + // log.Print("boo2: ", string(msgbytes[i:i+5])) //} complexBbc = true } @@ -317,73 +316,15 @@ func bbcodeFullParse(msg string) string { //log.Print("BBCode Pre:","`"+string(msgbytes)+"`") //log.Print("----") for ; i < len(msgbytes); i++ { - MainLoop: if msgbytes[i] == '[' { - OuterComplex: if msgbytes[i+1] == 'u' { if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' { - start = i + 5 - outbytes = append(outbytes, msgbytes[lastTag:i]...) - i = start - i += partialURLBytesLen(msgbytes[start:]) - //log.Print("Partial Bytes:",string(msgbytes[start:])) - //log.Print("-----") - if !bytes.Equal(msgbytes[i:i+6], []byte("[/url]")) { - //log.Print("Invalid Bytes:",string(msgbytes[i:i+6])) - //log.Print("-----") - outbytes = append(outbytes, invalidURL...) - goto MainLoop - } - - outbytes = append(outbytes, urlOpen...) - outbytes = append(outbytes, msgbytes[start:i]...) - outbytes = append(outbytes, urlOpen2...) - outbytes = append(outbytes, msgbytes[start:i]...) - outbytes = append(outbytes, urlClose...) - i += 6 - lastTag = i + i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, msgbytes, outbytes) + continue } } else if msgbytes[i+1] == 'r' { if bytes.Equal(msgbytes[i+2:i+6], []byte("and]")) { - outbytes = append(outbytes, msgbytes[lastTag:i]...) - start = i + 6 - i = start - for ; ; i++ { - if msgbytes[i] == '[' { - if !bytes.Equal(msgbytes[i+1:i+7], []byte("/rand]")) { - outbytes = append(outbytes, bbcodeMissingTag...) - goto OuterComplex - } - break - } else if (len(msgbytes) - 1) < (i + 10) { - outbytes = append(outbytes, bbcodeMissingTag...) - goto OuterComplex - } - } - - number, err := strconv.ParseInt(string(msgbytes[start:i]), 10, 64) - if err != nil { - outbytes = append(outbytes, bbcodeInvalidNumber...) - goto MainLoop - } - - // TODO: Add support for negative numbers? - if number < 0 { - outbytes = append(outbytes, bbcodeNoNegative...) - goto MainLoop - } - - var dat []byte - if number == 0 { - dat = []byte("0") - } else { - dat = []byte(strconv.FormatInt((random.Int63n(number)), 10)) - } - - outbytes = append(outbytes, dat...) - //log.Print("Outputted the random number") - i += 7 - lastTag = i + i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, msgbytes, outbytes) } } } @@ -411,3 +352,71 @@ func bbcodeFullParse(msg string) string { return msg } + +func bbcodeParseURL(i int, start int, lastTag int, msgbytes []byte, outbytes []byte) (int, int, int, []byte) { + start = i + 5 + outbytes = append(outbytes, msgbytes[lastTag:i]...) + i = start + i += partialURLBytesLen(msgbytes[start:]) + //log.Print("Partial Bytes: ", string(msgbytes[start:])) + //log.Print("-----") + if !bytes.Equal(msgbytes[i:i+6], []byte("[/url]")) { + //log.Print("Invalid Bytes: ", string(msgbytes[i:i+6])) + //log.Print("-----") + outbytes = append(outbytes, invalidURL...) + return i, start, lastTag, outbytes + } + + outbytes = append(outbytes, urlOpen...) + outbytes = append(outbytes, msgbytes[start:i]...) + outbytes = append(outbytes, urlOpen2...) + outbytes = append(outbytes, msgbytes[start:i]...) + outbytes = append(outbytes, urlClose...) + i += 6 + lastTag = i + + return i, start, lastTag, outbytes +} + +func bbcodeParseRand(i int, start int, lastTag int, msgbytes []byte, outbytes []byte) (int, int, int, []byte) { + outbytes = append(outbytes, msgbytes[lastTag:i]...) + start = i + 6 + i = start + for ; ; i++ { + if msgbytes[i] == '[' { + if !bytes.Equal(msgbytes[i+1:i+7], []byte("/rand]")) { + outbytes = append(outbytes, bbcodeMissingTag...) + return i, start, lastTag, outbytes + } + break + } else if (len(msgbytes) - 1) < (i + 10) { + outbytes = append(outbytes, bbcodeMissingTag...) + return i, start, lastTag, outbytes + } + } + + number, err := strconv.ParseInt(string(msgbytes[start:i]), 10, 64) + if err != nil { + outbytes = append(outbytes, bbcodeInvalidNumber...) + return i, start, lastTag, outbytes + } + + // TODO: Add support for negative numbers? + if number < 0 { + outbytes = append(outbytes, bbcodeNoNegative...) + return i, start, lastTag, outbytes + } + + var dat []byte + if number == 0 { + dat = []byte("0") + } else { + dat = []byte(strconv.FormatInt((random.Int63n(number)), 10)) + } + + outbytes = append(outbytes, dat...) + //log.Print("Outputted the random number") + i += 7 + lastTag = i + return i, start, lastTag, outbytes +} diff --git a/plugin_test.go b/plugin_test.go index 54b8d817..bb2093bd 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -48,6 +48,8 @@ func TestBBCodeRender(t *testing.T) { msgList = addMEPair(msgList, "[quote][b]hi[/b][/quote]", "hi") msgList = addMEPair(msgList, "[quote][b]h[/b][/quote]", "h") msgList = addMEPair(msgList, "[quote][b][/b][/quote]", "") + msgList = addMEPair(msgList, "-你好-", "-你好-") + msgList = addMEPair(msgList, "[i]-你好-[/i]", "-你好-") // TODO: More of these Unicode tests? Emoji, Chinese, etc.? t.Log("Testing bbcodeFullParse") for _, item := range msgList { @@ -249,6 +251,8 @@ func TestMarkdownRender(t *testing.T) { msgList = addMEPair(msgList, "* *", " ") msgList = addMEPair(msgList, "** **", " ") msgList = addMEPair(msgList, "*** ***", " ") + msgList = addMEPair(msgList, "-你好-", "-你好-") + msgList = addMEPair(msgList, "*-你好-*", "-你好-") // TODO: More of these Unicode tests? Emoji, Chinese, etc.? for _, item := range msgList { res = markdownParse(item.Msg) diff --git a/query_gen/lib/mysql.go b/query_gen/lib/mysql.go index 41cf844b..b5b5b0a8 100644 --- a/query_gen/lib/mysql.go +++ b/query_gen/lib/mysql.go @@ -118,21 +118,7 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str return "", errors.New("No input data found for SimpleInsert") } - var querystr = "INSERT INTO `" + table + "`(" - - // Escape the column names, just in case we've used a reserved keyword - for _, column := range processColumns(columns) { - if column.Type == "function" { - querystr += column.Left + "," - } else { - querystr += "`" + column.Left + "`," - } - } - - // Remove the trailing comma - querystr = querystr[0 : len(querystr)-1] - - querystr += ") VALUES (" + var querystr = "INSERT INTO `" + table + "`(" + adapter.buildColumns(columns) + ") VALUES (" for _, field := range processFields(fields) { nameLen := len(field.Name) if field.Name[0] == '"' && field.Name[nameLen-1] == '"' && nameLen >= 3 { @@ -149,6 +135,18 @@ func (adapter *MysqlAdapter) SimpleInsert(name string, table string, columns str return querystr + ")", nil } +func (adapter *MysqlAdapter) buildColumns(columns string) (querystr string) { + // Escape the column names, just in case we've used a reserved keyword + for _, column := range processColumns(columns) { + if column.Type == "function" { + querystr += column.Left + "," + } else { + querystr += "`" + column.Left + "`," + } + } + return querystr[0 : len(querystr)-1] +} + // ! DEPRECATED func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns string, fields string) (string, error) { if name == "" { @@ -164,20 +162,7 @@ func (adapter *MysqlAdapter) SimpleReplace(name string, table string, columns st return "", errors.New("No input data found for SimpleInsert") } - var querystr = "REPLACE INTO `" + table + "`(" - - // Escape the column names, just in case we've used a reserved keyword - for _, column := range processColumns(columns) { - if column.Type == "function" { - querystr += column.Left + "," - } else { - querystr += "`" + column.Left + "`," - } - } - // Remove the trailing comma - querystr = querystr[0 : len(querystr)-1] - - querystr += ") VALUES (" + var querystr = "REPLACE INTO `" + table + "`(" + adapter.buildColumns(columns) + ") VALUES (" for _, field := range processFields(fields) { querystr += field.Name + "," } @@ -260,29 +245,13 @@ func (adapter *MysqlAdapter) SimpleUpdate(name string, table string, set string, } querystr += "," } - // Remove the trailing comma querystr = querystr[0 : len(querystr)-1] - // Add support for BETWEEN x.x - if len(where) != 0 { - querystr += " WHERE" - for _, loc := range processWhere(where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - querystr += " `" + token.Contents + "`" - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + whereStr, err := adapter.buildWhere(where) + if err != nil { + return querystr, err } + querystr += whereStr adapter.pushStatement(name, "update", querystr) return querystr, nil @@ -335,32 +304,8 @@ func (adapter *MysqlAdapter) Purge(name string, table string) (string, error) { return "DELETE FROM `" + table + "`", nil } -func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) { - if name == "" { - return "", errors.New("You need a name for this statement") - } - if table == "" { - return "", errors.New("You need a name for this table") - } - if len(columns) == 0 { - return "", errors.New("No columns found for SimpleSelect") - } - - // Slice up the user friendly strings into something easier to process - var colslice = strings.Split(strings.TrimSpace(columns), ",") - - var querystr = "SELECT " - - // Escape the column names, just in case we've used a reserved keyword - for _, column := range colslice { - querystr += "`" + strings.TrimSpace(column) + "`," - } - // Remove the trailing comma - querystr = querystr[0 : len(querystr)-1] - - querystr += " FROM `" + table + "`" - - // Add support for BETWEEN x.x +// TODO: Add support for BETWEEN x.x +func (adapter *MysqlAdapter) buildWhere(where string) (querystr string, err error) { if len(where) != 0 { querystr += " WHERE" for _, loc := range processWhere(where) { @@ -373,14 +318,17 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str case "string": querystr += " '" + token.Contents + "'" default: - panic("This token doesn't exist o_o") + return querystr, errors.New("This token doesn't exist o_o") } } querystr += " AND" } querystr = querystr[0 : len(querystr)-4] } + return querystr, nil +} +func (adapter *MysqlAdapter) buildOrderby(orderby string) (querystr string) { if len(orderby) != 0 { querystr += " ORDER BY " for _, column := range processOrderby(orderby) { @@ -389,10 +337,35 @@ func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns str } querystr = querystr[0 : len(querystr)-1] } + return querystr +} - if limit != "" { - querystr += " LIMIT " + limit +func (adapter *MysqlAdapter) SimpleSelect(name string, table string, columns string, where string, orderby string, limit string) (string, error) { + if name == "" { + return "", errors.New("You need a name for this statement") } + if table == "" { + return "", errors.New("You need a name for this table") + } + if len(columns) == 0 { + return "", errors.New("No columns found for SimpleSelect") + } + + var querystr = "SELECT " + + // Slice up the user friendly strings into something easier to process + var colslice = strings.Split(strings.TrimSpace(columns), ",") + for _, column := range colslice { + querystr += "`" + strings.TrimSpace(column) + "`," + } + querystr = querystr[0 : len(querystr)-1] + + whereStr, err := adapter.buildWhere(where) + if err != nil { + return querystr, err + } + + querystr += " FROM `" + table + "`" + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) querystr = strings.TrimSpace(querystr) adapter.pushStatement(name, "select", querystr) @@ -435,54 +408,15 @@ func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s } querystr += source + alias + "," } - // Remove the trailing comma querystr = querystr[0 : len(querystr)-1] - querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON " - for _, joiner := range processJoiner(joiners) { - querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " - } - // Remove the trailing AND - querystr = querystr[0 : len(querystr)-4] - - // Add support for BETWEEN x.x - if len(where) != 0 { - querystr += " WHERE" - for _, loc := range processWhere(where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - halves := strings.Split(token.Contents, ".") - if len(halves) == 2 { - querystr += " `" + halves[0] + "`.`" + halves[1] + "`" - } else { - querystr += " `" + token.Contents + "`" - } - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + whereStr, err := adapter.buildJoinWhere(where) + if err != nil { + return querystr, err } - if len(orderby) != 0 { - querystr += " ORDER BY " - for _, column := range processOrderby(orderby) { - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," - } - querystr = querystr[0 : len(querystr)-1] - } - - if limit != "" { - querystr += " LIMIT " + limit - } + querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON " + adapter.buildJoiners(joiners) + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) querystr = strings.TrimSpace(querystr) adapter.pushStatement(name, "select", querystr) @@ -529,50 +463,12 @@ func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 // Remove the trailing comma querystr = querystr[0 : len(querystr)-1] - querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON " - for _, joiner := range processJoiner(joiners) { - querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " - } - // Remove the trailing AND - querystr = querystr[0 : len(querystr)-4] - - // Add support for BETWEEN x.x - if len(where) != 0 { - querystr += " WHERE" - for _, loc := range processWhere(where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - halves := strings.Split(token.Contents, ".") - if len(halves) == 2 { - querystr += " `" + halves[0] + "`.`" + halves[1] + "`" - } else { - querystr += " `" + token.Contents + "`" - } - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + whereStr, err := adapter.buildJoinWhere(where) + if err != nil { + return querystr, err } - if len(orderby) != 0 { - querystr += " ORDER BY " - for _, column := range processOrderby(orderby) { - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," - } - querystr = querystr[0 : len(querystr)-1] - } - - if limit != "" { - querystr += " LIMIT " + limit - } + querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON " + adapter.buildJoiners(joiners) + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) querystr = strings.TrimSpace(querystr) adapter.pushStatement(name, "select", querystr) @@ -581,7 +477,6 @@ func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DB_Insert, sel DB_Select) (string, error) { /* Insert Portion */ - var querystr = "INSERT INTO `" + ins.Table + "`(" // Escape the column names, just in case we've used a reserved keyword @@ -613,40 +508,12 @@ func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DB_Insert, sel } querystr = querystr[0 : len(querystr)-1] - querystr += " FROM `" + sel.Table + "`" - - // Add support for BETWEEN x.x - if len(sel.Where) != 0 { - querystr += " WHERE" - for _, loc := range processWhere(sel.Where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - querystr += " `" + token.Contents + "`" - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + whereStr, err := adapter.buildWhere(sel.Where) + if err != nil { + return querystr, err } - if len(sel.Orderby) != 0 { - querystr += " ORDER BY " - for _, column := range processOrderby(sel.Orderby) { - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," - } - querystr = querystr[0 : len(querystr)-1] - } - - if sel.Limit != "" { - querystr += " LIMIT " + sel.Limit - } + querystr += " FROM `" + sel.Table + "`" + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) querystr = strings.TrimSpace(querystr) adapter.pushStatement(name, "insert", querystr) @@ -655,7 +522,6 @@ func (adapter *MysqlAdapter) SimpleInsertSelect(name string, ins DB_Insert, sel func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { /* Insert Portion */ - var querystr = "INSERT INTO `" + ins.Table + "`(" // Escape the column names, just in case we've used a reserved keyword @@ -689,16 +555,32 @@ func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DB_Insert, se } querystr = querystr[0 : len(querystr)-1] - querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " - for _, joiner := range processJoiner(sel.Joiners) { + whereStr, err := adapter.buildJoinWhere(sel.Where) + if err != nil { + return querystr, err + } + + querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) + + querystr = strings.TrimSpace(querystr) + adapter.pushStatement(name, "insert", querystr) + return querystr, nil +} + +// TODO: Make this more consistent with the other build* methods? +func (adapter *MysqlAdapter) buildJoiners(joiners string) (querystr string) { + for _, joiner := range processJoiner(joiners) { querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " } - querystr = querystr[0 : len(querystr)-4] + // Remove the trailing AND + return querystr[0 : len(querystr)-4] +} - // Add support for BETWEEN x.x - if len(sel.Where) != 0 { +// Add support for BETWEEN x.x +func (adapter *MysqlAdapter) buildJoinWhere(where string) (querystr string, err error) { + if len(where) != 0 { querystr += " WHERE" - for _, loc := range processWhere(sel.Where) { + for _, loc := range processWhere(where) { for _, token := range loc.Expr { switch token.Type { case "function", "operator", "number", "substitute": @@ -713,34 +595,25 @@ func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DB_Insert, se case "string": querystr += " '" + token.Contents + "'" default: - panic("This token doesn't exist o_o") + return querystr, errors.New("This token doesn't exist o_o") } } querystr += " AND" } querystr = querystr[0 : len(querystr)-4] } - - if len(sel.Orderby) != 0 { - querystr += " ORDER BY " - for _, column := range processOrderby(sel.Orderby) { - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," - } - querystr = querystr[0 : len(querystr)-1] - } - - if sel.Limit != "" { - querystr += " LIMIT " + sel.Limit - } - - querystr = strings.TrimSpace(querystr) - adapter.pushStatement(name, "insert", querystr) return querystr, nil } +func (adapter *MysqlAdapter) buildLimit(limit string) (querystr string) { + if limit != "" { + querystr += " LIMIT " + limit + } + return querystr +} + func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DB_Insert, sel DB_Join) (string, error) { /* Insert Portion */ - var querystr = "INSERT INTO `" + ins.Table + "`(" // Escape the column names, just in case we've used a reserved keyword @@ -774,56 +647,19 @@ func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DB_Insert, s } querystr = querystr[0 : len(querystr)-1] - querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " - for _, joiner := range processJoiner(sel.Joiners) { - querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND " - } - querystr = querystr[0 : len(querystr)-4] - - // Add support for BETWEEN x.x - if len(sel.Where) != 0 { - querystr += " WHERE" - for _, loc := range processWhere(sel.Where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - halves := strings.Split(token.Contents, ".") - if len(halves) == 2 { - querystr += " `" + halves[0] + "`.`" + halves[1] + "`" - } else { - querystr += " `" + token.Contents + "`" - } - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + whereStr, err := adapter.buildJoinWhere(sel.Where) + if err != nil { + return querystr, err } - if len(sel.Orderby) != 0 { - querystr += " ORDER BY " - for _, column := range processOrderby(sel.Orderby) { - querystr += column.Column + " " + strings.ToUpper(column.Order) + "," - } - querystr = querystr[0 : len(querystr)-1] - } - - if sel.Limit != "" { - querystr += " LIMIT " + sel.Limit - } + querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) querystr = strings.TrimSpace(querystr) adapter.pushStatement(name, "insert", querystr) return querystr, nil } -func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (string, error) { +func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string, limit string) (querystr string, err error) { if name == "" { return "", errors.New("You need a name for this statement") } @@ -831,31 +667,12 @@ func (adapter *MysqlAdapter) SimpleCount(name string, table string, where string return "", errors.New("You need a name for this table") } - var querystr = "SELECT COUNT(*) AS `count` FROM `" + table + "`" - - // TODO: Add support for BETWEEN x.x - if len(where) != 0 { - querystr += " WHERE" - //log.Print("SimpleCount: ", name) - //log.Print("where: ", where) - //log.Print("processWhere: ", processWhere(where)) - for _, loc := range processWhere(where) { - for _, token := range loc.Expr { - switch token.Type { - case "function", "operator", "number", "substitute": - querystr += " " + token.Contents - case "column": - querystr += " `" + token.Contents + "`" - case "string": - querystr += " '" + token.Contents + "'" - default: - panic("This token doesn't exist o_o") - } - } - querystr += " AND" - } - querystr = querystr[0 : len(querystr)-4] + querystr = "SELECT COUNT(*) AS `count` FROM `" + table + "`" + whereStr, err := adapter.buildWhere(where) + if err != nil { + return "", err } + querystr += whereStr if limit != "" { querystr += " LIMIT " + limit diff --git a/template_list.go b/template_list.go index 3b088c26..30630c8e 100644 --- a/template_list.go +++ b/template_list.go @@ -351,7 +351,7 @@ var topic_alt_29 = []byte(`