diff --git a/common/parser.go b/common/parser.go index cda043f3..0f4395b8 100644 --- a/common/parser.go +++ b/common/parser.go @@ -180,10 +180,16 @@ func tryStepForward(i int, step int, runes []rune) (int, bool) { return i - step, false } +// TODO: Write a test for this +func tryStepBackward(i int, step int, runes []rune) (int, bool) { + if i == 0 { + return i, false + } + return i - 1, true +} + // TODO: Preparse Markdown and normalize it into HTML? func PreparseMessage(msg string) string { - //fmt.Println("initial msg: ", msg) - //fmt.Println("initial []byte(msg): ", []byte(msg)) // TODO: Kick this check down a level into SanitiseBody? if !utf8.ValidString(msg) { return "" @@ -201,8 +207,6 @@ func PreparseMessage(msg string) string { // There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there msg = SanitiseBody(msg) - //fmt.Println("before msg: ", msg) - //fmt.Println("before []byte(msg): ", []byte(msg)) var runes = []rune(msg) msg = "" @@ -263,7 +267,6 @@ func PreparseMessage(msg string) string { for i := 0; i < len(runes); i++ { char := runes[i] if char == '&' && peekMatch(i, "lt;", runes) { - //fmt.Println("found less than") var ok bool i, ok = tryStepForward(i, 4, runes) if !ok { @@ -271,8 +274,6 @@ func PreparseMessage(msg string) string { break } char := runes[i] - //fmt.Println("char: ", char) - //fmt.Println("string(char): ", string(char)) if int(char) >= len(allowedTags) { //fmt.Println("sentinel char out of bounds") msg += "&" @@ -311,29 +312,19 @@ func PreparseMessage(msg string) string { var newI = -1 var out string toActionList := tagToAction[char] - //fmt.Println("toActionList: ", toActionList) for _, toAction := range toActionList { - //fmt.Printf("toAction: %+v\n", toAction) // TODO: Optimise this, maybe with goto or a function call to avoid scanning the text twice? if (toAction.PartialMode && !closeTag && peekMatch(i, toAction.Suffix, runes)) || peekMatch(i, toAction.Suffix+">", runes) { - //fmt.Println("peekMatched") newI, out = toAction.Do(toAction, !closeTag, i, runes) - //fmt.Println("newI: ", newI) - //fmt.Println("i: ", i) - //fmt.Println("string(runes[i]): ", string(runes[i])) if newI != -1 { i = newI } else if out != "" { i += len(toAction.Suffix + ">") } - //fmt.Println("i: ", i) - //fmt.Println("string(runes[i]): ", string(runes[i])) - //fmt.Println("out: ", out) break } } if out == "" { - //fmt.Println("no out") msg += "&" if closeTag { i -= 5 @@ -343,24 +334,54 @@ func PreparseMessage(msg string) string { } else if out != " " { msg += out } + } else if char == '@' && (i == 0 || runes[i-1] < 33) { + // TODO: Handle usernames containing spaces, maybe in the front-end with AJAX + // Do not mention-ify ridiculously long things + var ok bool + i, ok = tryStepForward(i, 1, runes) + if !ok { + msg += "@" + continue + } + start := i + + for j := 0; i < len(runes) && j < Config.MaxUsernameLength; j++ { + cchar := runes[i] + if cchar < 33 { + break + } + i++ + } + + username := string(runes[start:i]) + if username == "" { + msg += "@" + i = start - 1 + continue + } + + //fmt.Printf("username: %+v\n", username) + user, err := Users.GetByName(username) + if err != nil { + if err != ErrNoRows { + LogError(err) + } + msg += "@" + i = start - 1 + continue + } + msg += "@" + strconv.Itoa(user.ID) + i-- } else { msg += string(char) } } - //fmt.Println("running autoclosers") - //fmt.Println("msg: ", msg) for _, actionList := range tagToAction { - //if len(actionList) > 0 { - // fmt.Println("actionList: ", actionList) - //} for _, toAction := range actionList { - //fmt.Printf("toAction: %+v\n", toAction) if toAction.Depth > 0 { - //fmt.Println("autoclosing") for ; toAction.Depth > 0; toAction.Depth-- { _, out := toAction.Do(toAction, false, len(runes), runes) - //fmt.Println("out: ", out) if out != "" { msg += out } @@ -368,8 +389,6 @@ func PreparseMessage(msg string) string { } } } - //fmt.Println("msg: ", msg) - return strings.TrimSpace(shortcodeToUnicode(msg)) } diff --git a/common/user_store.go b/common/user_store.go index 46d23640..13b78a70 100644 --- a/common/user_store.go +++ b/common/user_store.go @@ -19,6 +19,7 @@ var ErrLongUsername = errors.New("this username is too long") type UserStore interface { DirtyGet(id int) *User Get(id int) (*User, error) + GetByName(name string) (*User, error) Exists(id int) bool GetOffset(offset int, perPage int) (users []*User, err error) //BulkGet(ids []int) ([]*User, error) @@ -36,6 +37,7 @@ type DefaultUserStore struct { cache UserCache get *sql.Stmt + getByName *sql.Stmt getOffset *sql.Stmt exists *sql.Stmt register *sql.Stmt @@ -53,6 +55,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) { return &DefaultUserStore{ cache: cache, get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group", "uid = ?", "", ""), + getByName: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Where("name = ?").Prepare(), getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here @@ -98,6 +101,19 @@ func (mus *DefaultUserStore) Get(id int) (*User, error) { return user, err } +// TODO: Log weird cache errors? Not just here but in every *Cache? +// ! This bypasses the cache, use frugally +func (mus *DefaultUserStore) GetByName(name string) (*User, error) { + user := &User{Loggedin: true} + err := mus.getByName.QueryRow(name).Scan(&user.ID, &user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.RawAvatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Liked, &user.LastIP, &user.TempGroup) + + user.Init() + if err == nil { + mus.cache.Set(user) + } + return user, err +} + // TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps // TODO: Make this a little more consistent with DefaultGroupStore's GetRange method func (store *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, err error) { diff --git a/misc_test.go b/misc_test.go index c71d0506..ca9a4c45 100644 --- a/misc_test.go +++ b/misc_test.go @@ -1223,6 +1223,14 @@ func TestPreparser(t *testing.T) { msgList = addMETri(msgList, "<>", "</><>") msgList = addMETri(msgList, "<>", "<>") msgList = addMETri(msgList, "", "</>") + msgList = addMETri(msgList, "@", "@") + msgList = addMETri(msgList, "@Admin", "@1") + msgList = addMETri(msgList, "@Bah", "@Bah") + msgList = addMETri(msgList, " @Admin", "@1") + msgList = addMETri(msgList, "\n@Admin", "@1") + msgList = addMETri(msgList, "@Admin\n", "@1") + msgList = addMETri(msgList, "@Admin\ndd", "@1\ndd") + msgList = addMETri(msgList, "d@Admin", "d@Admin") //msgList = addMETri(msgList, "byte 0", string([]byte{0}), "") msgList = addMETri(msgList, "byte 'a'", string([]byte{'a'}), "a") //msgList = addMETri(msgList, "byte 255", string([]byte{255}), "")