Filter text according to server rules before storing.

This commit is contained in:
Mikkel Krautz 2011-05-29 02:28:57 +02:00
parent 4e89b124fb
commit 739cd1ca9b
3 changed files with 163 additions and 11 deletions

View file

@ -194,6 +194,8 @@ func (channel *Channel) Freeze() (fc frozenChannel, err os.Error) {
}
fc.Links = links
fc.DescriptionBlob = channel.DescriptionBlob
return
}

View file

@ -225,8 +225,11 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
// Extract the description and perform sanity checks.
if chanstate.Description != nil {
description = *chanstate.Description
// fixme(mkrautz): Check length
description, err = server.FilterText(*chanstate.Description)
if err != nil {
client.sendPermissionDeniedType("TextTooLong")
return
}
}
// Extract the the name of channel and check whether it's valid.
@ -467,12 +470,16 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
// Description change
if chanstate.Description != nil {
key, err := blobstore.Put([]byte(*chanstate.Description))
if len(description) == 0 {
channel.DescriptionBlob = ""
} else {
key, err := blobstore.Put([]byte(description))
if err != nil {
server.Panicf("Blobstore error: %v", err.String())
}
channel.DescriptionBlob = key
}
}
// Position change
if chanstate.Position != nil {
@ -654,17 +661,27 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
// Only allow empty text.
if len(comment) > 0 {
client.Panic("Cannot clear another user's comment")
client.sendPermissionDeniedType("TextTooLong")
return
}
}
// todo(mkrautz): Check if the text is allowed.
filtered, err := server.FilterText(comment)
if err != nil {
client.sendPermissionDeniedType("TextTooLong")
return
}
userstate.Comment = proto.String(filtered)
}
// Texture change
if userstate.Texture != nil {
// Check the length of the texture
maximg := server.cfg.IntValue("MaxImageMessageLength")
if maximg > 0 && len(userstate.Texture) > maximg {
client.sendPermissionDeniedType("TextTooLong")
return
}
}
// Registration
@ -960,8 +977,17 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) {
return
}
// fixme(mkrautz): Check text message length.
// fixme(mkrautz): Sanitize text as well.
filtered, err := server.FilterText(*txtmsg.Message)
if err != nil {
client.sendPermissionDeniedType("TextTooLong")
return
}
if len(filtered) == 0 {
return
}
txtmsg.Message = proto.String(filtered)
clients := make(map[uint32]*Client)

124
server.go
View file

@ -31,6 +31,7 @@ import (
"path/filepath"
"strings"
"time"
"xml"
)
// The default port a Murmur server listens on
@ -1190,6 +1191,129 @@ func (server *Server) IsBanned(conn net.Conn) bool {
return false
}
// Filter incoming text according to the server's current rules.
func (server *Server) FilterText(text string) (filtered string, err os.Error) {
// This function filters incoming text from clients according to three server settings:
//
// AllowHTML:
// If false, all HTML shall be stripped.
// When stripping br tags, append a newline to the output stream.
// When stripping p tags, append a newline after the end tag.
//
// MaxTextMessageLength:
// Text length for "plain" messages (messages without images)
//
// MaxImageTextMessageLength:
// Text length for messages with images.
max := server.cfg.IntValue("MaxTextMessageLength")
maximg := server.cfg.IntValue("MaxImageMessageLength")
if !server.cfg.BoolValue("AllowHTML") {
if strings.Index(text, "<") == -1 {
filtered = strings.TrimSpace(text)
} else {
// Strip away all HTML
out := bytes.NewBuffer(nil)
buf := bytes.NewBufferString(text)
parser := xml.NewParser(buf)
parser.Strict = false
parser.AutoClose = xml.HTMLAutoClose
parser.Entity = xml.HTMLEntity
for {
tok, err := parser.Token()
if err == os.EOF {
break
} else if err != nil {
return "", err
}
switch t := tok.(type) {
case xml.CharData:
out.Write(t)
case xml.EndElement:
if t.Name.Local == "p" || t.Name.Local == "br" {
out.WriteString("\n")
}
}
}
filtered = strings.TrimSpace(out.String())
}
if max != 0 && len(filtered) > max {
return "", os.NewError("Message exceeds max length")
}
} else {
// No limits
if max == 0 && maximg == 0 {
return text, nil
}
// Too big for images?
if maximg != 0 && len(text) > maximg {
return "", os.NewError("Message exceeds max image message length")
}
// Under max plain length?
if max == 0 || len(text) <= max {
return text, nil
}
// Over max length, under image limit. If text doesn't include
// any HTML, this is a no-go. If there is XML, we can attempt to
// strip away data URIs to see if we can get the message to fit
// into the plain message limit.
if strings.Index(text, "<") == -1 {
return "", os.NewError("Over plain length")
}
// Simplify the received HTML data by stripping away data URIs
out := bytes.NewBuffer(nil)
buf := bytes.NewBufferString(text)
parser := xml.NewParser(buf)
parser.Strict = false
parser.AutoClose = xml.HTMLAutoClose
parser.Entity = xml.HTMLEntity
for {
tok, err := parser.Token()
if err == os.EOF {
break
} else if err != nil {
return "", err
}
switch t := tok.(type) {
case xml.CharData:
out.Write(t)
case xml.StartElement:
out.WriteString("<")
xml.Escape(out, []byte(t.Name.Local))
for _, attr := range t.Attr {
if t.Name.Local == "img" && attr.Name.Local == "src" {
continue
}
out.WriteString(" ")
xml.Escape(out, []byte(attr.Name.Local))
out.WriteString(`="`)
out.WriteString(attr.Value)
out.WriteString(`"`)
}
out.WriteString(">")
case xml.EndElement:
out.WriteString("</")
xml.Escape(out, []byte(t.Name.Local))
out.WriteString(">")
}
}
filtered = strings.TrimSpace(out.String())
if len(filtered) > max {
return "", os.NewError("Data URI stripped message longer than max length")
}
}
return
}
// The accept loop of the server.
func (s *Server) ListenAndMurmur() {
// Launch the event handler goroutine