diff --git a/freeze.go b/freeze.go index 3f2d20b..cd4ba4d 100644 --- a/freeze.go +++ b/freeze.go @@ -194,6 +194,8 @@ func (channel *Channel) Freeze() (fc frozenChannel, err os.Error) { } fc.Links = links + fc.DescriptionBlob = channel.DescriptionBlob + return } diff --git a/message.go b/message.go index 25e9a15..a720739 100644 --- a/message.go +++ b/message.go @@ -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,11 +470,15 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { // Description change if chanstate.Description != nil { - key, err := blobstore.Put([]byte(*chanstate.Description)) - if err != nil { - server.Panicf("Blobstore error: %v", err.String()) + 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 } - channel.DescriptionBlob = key } // Position change @@ -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) diff --git a/server.go b/server.go index 06c9fbf..9fe56cf 100644 --- a/server.go +++ b/server.go @@ -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("") + } + } + + 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