Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0

Add bbcode support (#1433)

* Add bbcode support

Closes #687
As the issue suggested, I added bbcodes support to the forms.
Now we support basic bbcodes, markdown and html tags

* Add new dependencies
Cette révision appartient à :
akuma06 2017-08-29 01:56:44 +02:00 révisé par ewhal
Parent e6f0a56f65
révision ed61de6276
17 fichiers modifiés avec 1001 ajouts et 302 suppressions

9
Godeps/Godeps.json générée
Voir le fichier

@ -58,6 +58,10 @@
"Comment": "v1.0-4-g7e5a8ee",
"Rev": "7e5a8eef611ee84dd359503f3969f80df4c50723"
},
{
"ImportPath": "github.com/frustra/bbcode",
"Rev": "e3d2906cb2697dd3b846aa433cc6bb03df33b086"
},
{
"ImportPath": "github.com/gin-gonic/gin",
"Comment": "v1.1.4-1-gd5b353c",
@ -97,11 +101,6 @@
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "6a1fa9404c0aebf36c879bc50152edcc953910d2"
},
{
"ImportPath": "github.com/gorilla/context",
"Comment": "v1.1-7-g08b5f42",
"Rev": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
},
{
"ImportPath": "github.com/gorilla/feeds",
"Rev": "441264de03a8117ed530ae8e049d8f601a33a099"

Voir le fichier

@ -2,11 +2,15 @@ package sanitize
import (
"bytes"
"encoding/xml"
"html/template"
"log"
"io"
"regexp"
"strings"
"github.com/NyaaPantsu/nyaa/utils/log"
"github.com/frustra/bbcode"
"github.com/microcosm-cc/bluemonday"
md "github.com/russross/blackfriday"
"golang.org/x/net/html"
@ -28,13 +32,31 @@ var htmlFlags = 0 |
md.HTML_NOREFERRER_LINKS |
md.HTML_HREF_TARGET_BLANK
type htmlTag struct {
XMLName xml.Name `xml:"html"`
Body body `xml:"body"`
}
type body struct {
Content string `xml:",innerxml"`
}
func init() {
HTMLMdRenderer = md.HtmlRenderer(htmlFlags, "", "")
BBCodesRenderer = bbcode.NewCompiler(true, true) // autoCloseTags, ignoreUnmatchedClosingTags
BBCodesRenderer.SetTag("url", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out, appendExpr := bbcode.DefaultTagCompilers["url"](node)
out.Attrs["rel"] = "nofollow"
return out, appendExpr
})
}
// HTMLMdRenderer render for markdown to html
var HTMLMdRenderer md.Renderer
// BBCodesRenderer render bbcodes to html
var BBCodesRenderer bbcode.Compiler
// MarkdownToHTML : convert markdown to html
// TODO: restrict certain types of markdown
func MarkdownToHTML(markdown string) template.HTML {
@ -51,7 +73,11 @@ func MarkdownToHTML(markdown string) template.HTML {
/* Sanitize a message passed as a string according to a setted model or allowing a set of html tags and output a string
*/
func Sanitize(msg string, elements ...string) string {
// Convert BBCodes to HTML
msg = ParseBBCodes(msg)
// Repair HTML
msg = repairHTMLTags(msg) // We repair possible broken html tags
// HTML Sanitize
p := bluemonday.NewPolicy()
if len(elements) > 0 {
if elements[0] == "default" { // default model same as UGC without div
@ -262,17 +288,29 @@ func Sanitize(msg string, elements ...string) string {
return p.Sanitize(msg)
}
/*
* Should close any opened tags and strip any empty end tags
*/
// repairHTMLTags Should close any opened tags and strip any empty end tags
func repairHTMLTags(brokenHTML string) string {
reader := strings.NewReader(brokenHTML)
root, err := html.Parse(reader)
if err != nil {
log.Fatal(err)
if !log.CheckError(err) {
return ""
}
var b bytes.Buffer
html.Render(&b, root)
fixedHTML := b.String()
return fixedHTML
var buf bytes.Buffer
w := io.Writer(&buf)
html.Render(w, root)
fixedHTML := htmlTag{}
err = xml.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(&fixedHTML)
if !log.CheckError(err) {
return ""
}
return fixedHTML.Body.Content
}
// ParseBBCodes returns the bbcode compiler with the bbcode tags to parse
func ParseBBCodes(msg string) string {
msg = BBCodesRenderer.Compile(msg)
// For some reason, BBCodes compiler return escaped html
// We need to unescape it
return html.UnescapeString(msg)
}

Voir le fichier

@ -0,0 +1,90 @@
package sanitize
import (
"html/template"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMarkdownToHTML(t *testing.T) {
assert := assert.New(t)
tests := []struct {
Test string
Result template.HTML
}{
{"", ""},
{"> lll", "<blockquote>\n<p>lll</p>\n</blockquote>\n"},
{"> lll > lol", "<blockquote>\n<p>lll &gt; lol</p>\n</blockquote>\n"}, // Limit number of blockquotes
{"&gt; lll", "<blockquote>\n<p>lll</p>\n</blockquote>\n"},
{"\n", ""},
{"<b>lol</b>", "<p><b>lol</b></p>\n"}, // keep HTML tags
{"[b]lol[/b]", "<p>[b]lol[/b]</p>\n"}, // keep BBCode tags
{"**[b]lol[/b]**", "<p><strong>[b]lol[/b]</strong></p>\n"}, // Render Markdown
}
for _, test := range tests {
assert.Equal(test.Result, MarkdownToHTML(test.Test), "Should be equal")
}
}
func TestParseBBCodes(t *testing.T) {
assert := assert.New(t)
tests := []struct {
Test string
Result string
}{
{"", ""},
{"&gt;", "&gt;"}, // keep escaped html
{"<b>lol</b>", "<b>lol</b>"}, // keep html tags
{"[b]lol[/b]", "<b>lol</b>"}, // Convert bbcodes
{"[u][b]lol[/u]", "<u><b>lol</b></u>"}, // Close unclosed tags
}
for _, test := range tests {
assert.Equal(test.Result, ParseBBCodes(test.Test), "Should be equal")
}
assert.Contains(ParseBBCodes("[url=http://kk.cc/]lol[/url]"), "rel=\"nofollow\"") // rel="nofollow" for urls
}
func TestRepairHTMLTags(t *testing.T) {
assert := assert.New(t)
tests := []struct {
Test string
Result string
}{
{"", ""},
{"&gt;", "&gt;"}, // keep escaped html
{"<b>lol</b>", "<b>lol</b>"}, // keep html tags
{"<b><u>lol</b>", "<b><u>lol</u></b>"}, // close unclosed tags encapsulated
{"<b><u>lol", "<b><u>lol</u></b>"}, // close unclosed tags non encapsulated
{"<b><u>lol</em>", "<b><u>lol</u></b>"}, // close unclosed tags non encaptsulated + remove useless end tags
{"<div><b><u>lol</em></div>", "<div><b><u>lol</u></b></div>"}, // close unclosed tags + remove useless end tags encaptsulated
}
for _, test := range tests {
assert.Equal(test.Result, repairHTMLTags(test.Test), "Should be equal")
}
}
func TestSanitize(t *testing.T) {
assert := assert.New(t)
tests := []struct {
Test string
Result string
}{
{"", ""},
{"[b]lol[/b]", "<b>lol</b>"}, // Should convert bbcodes
{"&gt;", "&gt;"}, // keep escaped html
{"<b>lol</b>", "<b>lol</b>"}, // keep html tags
{"<b><u>lol</b>", "<b><u>lol</u></b>"}, // close unclosed tags encapsulated
{"<b><u>lol", "<b><u>lol</u></b>"}, // close unclosed tags non encapsulated
{"<b><u>lol</em>", "<b><u>lol</u></b>"}, // close unclosed tags non encaptsulated + remove useless end tags
{"<div><b><u>lol</em></div>", "<b><u>lol</u></b>"}, // close unclosed tags + remove useless end tags encaptsulated and remove div tag
{"Hello <STYLE>.XSS{background-image:url(\"javascript:alert('XSS')\");}</STYLE><A CLASS=XSS></A>World", "Hello World"}, // Remove css XSS
{"<a href=\"javascript:alert('XSS1')\" onmouseover=\"alert('XSS2')\">XSS<a>", "XSS"}, // Remove javascript xss
{"<a href=\"http://www.google.com/\"><img src=\"https://ssl.gstatic.com/accounts/ui/logo_2x.png\"/></a>", "<a href=\"http://www.google.com/\" rel=\"nofollow\"><img src=\"https://ssl.gstatic.com/accounts/ui/logo_2x.png\"/></a>"}, // We allow img and linl
{"<img src=\"data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=\">", ""}, // But not allow datauri img by default
{"<objet></object><embed></embed><base><iframe />", ""}, // Not allowed elements by default
}
for _, test := range tests {
assert.Equal(test.Result, Sanitize(test.Test, "default"), "Should be equal")
}
}

2
vendor/github.com/frustra/bbcode/.gitignore générée externe Fichier normal
Voir le fichier

@ -0,0 +1,2 @@
/bbcode
/bbcode.test

3
vendor/github.com/frustra/bbcode/.travis.yml générée externe Fichier normal
Voir le fichier

@ -0,0 +1,3 @@
language: go
go: 1.2
script: go test -v

19
vendor/github.com/frustra/bbcode/LICENSE générée externe Fichier normal
Voir le fichier

@ -0,0 +1,19 @@
Copyright (C) 2015 Frustra.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

123
vendor/github.com/frustra/bbcode/README.md générée externe Fichier normal
Voir le fichier

@ -0,0 +1,123 @@
# bbcode [![Build Status](https://travis-ci.org/frustra/bbcode.png?branch=master)](http://travis-ci.org/frustra/bbcode)
frustra/bbcode is a fast BBCode compiler for Go. It supports custom tags, safe html output (for user-specified input),
and allows for graceful parsing of syntax errors similar to the output of a regex bbcode compiler.
Visit the godoc here: [http://godoc.org/github.com/frustra/bbcode](http://godoc.org/github.com/frustra/bbcode)
## Usage
To get started compiling some text, create a compiler instance:
```go
compiler := bbcode.NewCompiler(true, true) // autoCloseTags, ignoreUnmatchedClosingTags
fmt.Println(compiler.Compile("[b]Hello World[/b]"))
// Output:
// <b>Hello World</b>
```
## Supported BBCode Syntax
```
[tag]basic tag[/tag]
[tag1][tag2]nested tags[/tag2][/tag1]
[tag=value]tag with value[/tag]
[tag arg=value]tag with named argument[/tag]
[tag="quote value"]tag with quoted value[/tag]
[tag=value foo="hello world" bar=baz]multiple tag arguments[/tag]
```
## Default Tags
* `[b]text[/b]` --> `<b>text</b>` (b, i, u, and s all map the same)
* `[url]link[/url]` --> `<a href="link">link</a>`
* `[url=link]text[/url]` --> `<a href="link">text</a>`
* `[img]link[/img]` --> `<img src="link">`
* `[img=link]alt[/img]` --> `<img alt="alt" title="alt" src="link">`
* `[center]text[/center]` --> `<div style="text-align: center;">text</div>`
* `[color=red]text[/color]` --> `<span style="color: red;">text</span>`
* `[size=2]text[/size]` --> `<span class="size2">text</span>`
* `[quote]text[/quote]` --> `<blockquote><cite>Quote</cite>text</blockquote>`
* `[quote=Somebody]text[/quote]` --> `<blockquote><cite>Somebody said:</cite>text</blockquote>`
* `[quote name=Somebody]text[/quote]` --> `<blockquote><cite>Somebody said:</cite>text</blockquote>`
* `[code][b]anything[/b][/code]` --> `<pre>[b]anything[/b]</pre>`
Lists are not currently implemented as a default tag, but can be added as a custom tag.
A working implementation of list tags can be found [here](https://gist.github.com/xthexder/44f4b9cec3ed7876780d)
## Adding Custom Tags
Custom tag handlers can be added to a compiler using the `compiler.SetTag(tag, handler)` function:
```go
compiler.SetTag("center", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
// Create a new div element to output
out := bbcode.NewHTMLTag("")
out.Name = "div"
// Set the style attribute of our output div
out.Attrs["style"] = "text-align: center;"
// Returning true here means continue to parse child nodes.
// This should be false if children are parsed by this tag's handler, like in the [code] tag.
return out, true
})
```
Tag values can be read from the opening tag like this:
Main tag value `[tag={value}]`: `node.GetOpeningTag().Value`
Tag arguments `[tag name={value}]`: `node.GetOpeningTag().Args["name"]`
`bbcode.NewHTMLTag(text)` creates a text node by default. By setting `tag.Name`, the node because an html tag prefixed by the text. The closing html tag is not rendered unless child elements exist. The closing tag can be forced by adding a blank text node:
```go
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.AppendChild(nil) // equivalent to out.AppendChild(bbcode.NewHTMLTag(""))
```
For more examples of tag definitions, look at the default tag implementations in [compiler.go](https://github.com/frustra/bbcode/blob/master/compiler.go)
## Overriding Default Tags
The built-in tags can be overridden simply by redefining the tag with `compiler.SetTag(tag, handler)`
To remove a tag, set the tag handler to nil:
```go
compiler.SetTag("quote", nil)
```
The default tags can also be modified without completely redefining the tag by calling the default handler:
```go
compiler.SetTag("url", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out, appendExpr := bbcode.DefaultTagCompilers["url"](node)
out.Attrs["class"] = "bbcode-link"
return out, appendExpr
})
```
## Auto-Close Tags
Input:
```
[center][b]text[/center]
```
Enabled Output:
```html
<div style="text-align: center;"><b>text</b></div>
```
Disabled Output:
```html
<div style="text-align: center;">[b]text</div>
```
## Ignore Unmatched Closing Tags
Input:
```
[center]text[/b][/center]
```
Enabled Output:
```html
<div style="text-align: center;">text</div>
```
Disabled Output:
```html
<div style="text-align: center;">text[/b]</div>
```

42
vendor/github.com/frustra/bbcode/bbcode.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,42 @@
// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
// Package bbcode implements a parser and HTML generator for BBCode.
package bbcode
import "sort"
type BBOpeningTag struct {
Name string
Value string
Args map[string]string
Raw string
}
type BBClosingTag struct {
Name string
Raw string
}
func (t *BBOpeningTag) String() string {
str := t.Name
if len(t.Value) > 0 {
str += "=" + t.Value
}
keys := make([]string, len(t.Args))
i := 0
for key := range t.Args {
keys[i] = key
i++
}
sort.Strings(keys)
for _, key := range keys {
v := t.Args[key]
str += " " + key
if len(v) > 0 {
str += "=" + v
}
}
return str
}

256
vendor/github.com/frustra/bbcode/compiler.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,256 @@
// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package bbcode
import (
"fmt"
"strconv"
"strings"
)
type TagCompilerFunc func(*BBCodeNode) (*HTMLTag, bool)
type Compiler struct {
tagCompilers map[string]TagCompilerFunc
defaultCompiler TagCompilerFunc
AutoCloseTags bool
IgnoreUnmatchedClosingTags bool
}
func NewCompiler(autoCloseTags, ignoreUnmatchedClosingTags bool) Compiler {
compiler := Compiler{make(map[string]TagCompilerFunc), DefaultTagCompiler, autoCloseTags, ignoreUnmatchedClosingTags}
for tag, compilerFunc := range DefaultTagCompilers {
compiler.SetTag(tag, compilerFunc)
}
return compiler
}
func (c Compiler) Compile(str string) string {
tokens := Lex(str)
tree := Parse(tokens)
return c.CompileTree(tree).String()
}
func (c Compiler) SetDefault(compiler TagCompilerFunc) {
if compiler == nil {
panic("Default tag compiler can't be nil")
} else {
c.defaultCompiler = compiler
}
}
func (c Compiler) SetTag(tag string, compiler TagCompilerFunc) {
if compiler == nil {
delete(c.tagCompilers, tag)
} else {
c.tagCompilers[tag] = compiler
}
}
// CompileTree transforms BBCodeNode into an HTML tag.
func (c Compiler) CompileTree(node *BBCodeNode) *HTMLTag {
var out = NewHTMLTag("")
if node.ID == TEXT {
out.Value = node.Value.(string)
InsertNewlines(out)
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else if node.ID == CLOSING_TAG {
if !c.IgnoreUnmatchedClosingTags {
out.Value = node.Value.(BBClosingTag).Raw
InsertNewlines(out)
}
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else if node.ClosingTag == nil && !c.AutoCloseTags {
out.Value = node.Value.(BBOpeningTag).Raw
InsertNewlines(out)
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
} else {
in := node.GetOpeningTag()
compileFunc, ok := c.tagCompilers[in.Name]
if !ok {
compileFunc = c.defaultCompiler
}
var appendExpr bool
node.Compiler = &c
out, appendExpr = compileFunc(node)
if appendExpr {
if len(node.Children) == 0 {
out.AppendChild(NewHTMLTag(""))
} else {
for _, child := range node.Children {
out.AppendChild(c.CompileTree(child))
}
}
}
}
return out
}
func CompileText(in *BBCodeNode) string {
out := ""
if in.ID == TEXT {
out = in.Value.(string)
}
for _, child := range in.Children {
out += CompileText(child)
}
return out
}
func CompileRaw(in *BBCodeNode) *HTMLTag {
out := NewHTMLTag("")
if in.ID == TEXT {
out.Value = in.Value.(string)
} else if in.ID == CLOSING_TAG {
out.Value = in.Value.(BBClosingTag).Raw
} else {
out.Value = in.Value.(BBOpeningTag).Raw
}
for _, child := range in.Children {
out.AppendChild(CompileRaw(child))
}
if in.ID == OPENING_TAG && in.ClosingTag != nil {
tag := NewHTMLTag(in.ClosingTag.Raw)
out.AppendChild(tag)
}
return out
}
var DefaultTagCompilers map[string]TagCompilerFunc
var DefaultTagCompiler TagCompilerFunc
func init() {
DefaultTagCompiler = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag(node.GetOpeningTag().Raw)
InsertNewlines(out)
if len(node.Children) == 0 {
out.AppendChild(NewHTMLTag(""))
} else {
for _, child := range node.Children {
out.AppendChild(node.Compiler.CompileTree(child))
}
}
if node.ClosingTag != nil {
tag := NewHTMLTag(node.ClosingTag.Raw)
InsertNewlines(tag)
out.AppendChild(tag)
}
return out, false
}
DefaultTagCompilers = make(map[string]TagCompilerFunc)
DefaultTagCompilers["url"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "a"
value := node.GetOpeningTag().Value
if value == "" {
text := CompileText(node)
if len(text) > 0 {
out.Attrs["href"] = ValidURL(text)
}
} else {
out.Attrs["href"] = ValidURL(value)
}
return out, true
}
DefaultTagCompilers["img"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "img"
value := node.GetOpeningTag().Value
if value == "" {
out.Attrs["src"] = ValidURL(CompileText(node))
} else {
out.Attrs["src"] = ValidURL(value)
text := CompileText(node)
if len(text) > 0 {
out.Attrs["alt"] = text
out.Attrs["title"] = out.Attrs["alt"]
}
}
return out, false
}
DefaultTagCompilers["center"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: center;"
return out, true
}
DefaultTagCompilers["color"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "span"
sanitize := func(r rune) rune {
if r == '#' || r == ',' || r == '.' || r == '(' || r == ')' || r == '%' {
return r
} else if r >= '0' && r <= '9' {
return r
} else if r >= 'a' && r <= 'z' {
return r
} else if r >= 'A' && r <= 'Z' {
return r
}
return -1
}
color := strings.Map(sanitize, node.GetOpeningTag().Value)
out.Attrs["style"] = "color: " + color + ";"
return out, true
}
DefaultTagCompilers["size"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "span"
if size, err := strconv.Atoi(node.GetOpeningTag().Value); err == nil {
out.Attrs["class"] = fmt.Sprintf("size%d", size)
}
return out, true
}
DefaultTagCompilers["quote"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "blockquote"
who := ""
in := node.GetOpeningTag()
if name, ok := in.Args["name"]; ok && name != "" {
who = name
} else {
who = in.Value
}
cite := NewHTMLTag("")
cite.Name = "cite"
if who != "" {
cite.AppendChild(NewHTMLTag(who + " said:"))
} else {
cite.AppendChild(NewHTMLTag("Quote"))
}
return out.AppendChild(cite), true
}
DefaultTagCompilers["code"] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = "pre"
for _, child := range node.Children {
out.AppendChild(CompileRaw(child))
}
return out, false
}
for _, tag := range []string{"i", "b", "u", "s"} {
DefaultTagCompilers[tag] = func(node *BBCodeNode) (*HTMLTag, bool) {
out := NewHTMLTag("")
out.Name = node.GetOpeningTag().Name
return out, true
}
}
}

94
vendor/github.com/frustra/bbcode/html.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,94 @@
// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package bbcode
import (
"html"
"net/url"
"strings"
)
// HTMLTag represents a DOM node.
type HTMLTag struct {
Name string
Value string
Attrs map[string]string
Children []*HTMLTag
}
// NewHTMLTag creates a new HTMLTag with string contents specified by value.
func NewHTMLTag(value string) *HTMLTag {
return &HTMLTag{
Value: value,
Attrs: make(map[string]string),
Children: make([]*HTMLTag, 0),
}
}
func (t *HTMLTag) String() string {
var value string
if len(t.Value) > 0 {
value = html.EscapeString(t.Value)
}
var attrString string
for key, value := range t.Attrs {
attrString += " " + key + `="` + strings.Replace(html.EscapeString(value), "\n", "", -1) + `"`
}
if len(t.Children) > 0 {
var childrenString string
for _, child := range t.Children {
childrenString += child.String()
}
if len(t.Name) > 0 {
return value + "<" + t.Name + attrString + ">" + childrenString + "</" + t.Name + ">"
} else {
return value + childrenString
}
} else if len(t.Name) > 0 {
return value + "<" + t.Name + attrString + ">"
} else {
return value
}
}
func (t *HTMLTag) AppendChild(child *HTMLTag) *HTMLTag {
if child == nil {
t.Children = append(t.Children, NewHTMLTag(""))
} else {
t.Children = append(t.Children, child)
}
return t
}
func InsertNewlines(out *HTMLTag) {
if strings.ContainsRune(out.Value, '\n') {
parts := strings.Split(out.Value, "\n")
for i, part := range parts {
if i == 0 {
out.Value = parts[i]
} else {
out.AppendChild(NewlineTag())
if len(part) > 0 {
out.AppendChild(NewHTMLTag(part))
}
}
}
}
}
// Returns a new HTMLTag representing a line break
func NewlineTag() *HTMLTag {
var out = NewHTMLTag("")
out.Name = "br"
return out
}
func ValidURL(raw string) string {
u, err := url.Parse(raw)
if err != nil {
return ""
}
return u.String()
}

260
vendor/github.com/frustra/bbcode/lexer.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,260 @@
// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package bbcode
import (
"bytes"
"strings"
)
type Token struct {
ID string
Value interface{}
}
type lexer struct {
input string
tokens chan Token
start int
end int
pos int
tagName string
tagValue string
tagTmpName string
tagTmpValue string
tagArgs map[string]string
}
const (
TEXT = "text"
OPENING_TAG = "opening"
CLOSING_TAG = "closing"
)
func newLexer(str string) *lexer {
return &lexer{
input: str,
tokens: make(chan Token),
}
}
func Lex(str string) chan Token {
lex := newLexer(str)
go lex.runStateMachine()
return lex.tokens
}
func (l *lexer) runStateMachine() {
for state := lexText; state != nil; {
state = state(l)
}
close(l.tokens)
}
func (l *lexer) emit(id string, value interface{}) {
if l.pos > 0 {
// fmt.Println(l.input)
// fmt.Printf("Emit %s: %+v\n", id, value)
l.tokens <- Token{id, value}
l.input = l.input[l.pos:]
l.pos = 0
}
}
type stateFn func(*lexer) stateFn
func lexText(l *lexer) stateFn {
for l.pos < len(l.input) {
if l.input[l.pos] == '[' {
l.emit(TEXT, l.input[:l.pos])
return lexOpenBracket
}
l.pos++
}
l.emit(TEXT, l.input)
return nil
}
func lexOpenBracket(l *lexer) stateFn {
l.pos++
closingTag := false
for l.pos < len(l.input) {
switch l.input[l.pos] {
case '[', ']':
return lexText
default:
if l.input[l.pos] == '/' && !closingTag {
closingTag = true
} else if l.input[l.pos] != ' ' && l.input[l.pos] != '\t' && l.input[l.pos] != '\n' {
if closingTag {
return lexClosingTag
} else {
l.tagName = ""
l.tagValue = ""
l.tagArgs = make(map[string]string)
return lexTagName
}
}
}
l.pos++
}
l.emit(TEXT, l.input)
return nil
}
func lexClosingTag(l *lexer) stateFn {
whiteSpace := false
l.start = l.pos
l.end = l.pos
for l.pos < len(l.input) {
switch l.input[l.pos] {
case '[':
return lexText
case ']':
l.pos++
l.emit(CLOSING_TAG, BBClosingTag{strings.ToLower(l.input[l.start:l.end]), l.input[:l.pos]})
return lexText
case ' ', '\t', '\n':
whiteSpace = true
default:
if whiteSpace {
return lexText
} else {
l.end++
}
}
l.pos++
}
l.emit(TEXT, l.input)
return nil
}
func lexTagName(l *lexer) stateFn {
l.tagTmpValue = ""
whiteSpace := false
l.start = l.pos
l.end = l.pos
for l.pos < len(l.input) {
switch l.input[l.pos] {
case '[':
return lexText
case ']':
l.tagTmpName = l.input[l.start:l.end]
return lexTagArgs
case '=':
l.tagTmpName = l.input[l.start:l.end]
return lexTagValue
case ' ', '\t', '\n':
whiteSpace = true
default:
if whiteSpace {
l.tagTmpName = l.input[l.start:l.end]
return lexTagArgs
} else {
l.end++
}
}
l.pos++
}
l.emit(TEXT, l.input)
return nil
}
func lexTagValue(l *lexer) stateFn {
l.pos++
loop:
for l.pos < len(l.input) {
switch l.input[l.pos] {
case ' ', '\t', '\n':
l.pos++
case '"', '\'':
return lexQuotedValue
default:
break loop
}
}
l.start = l.pos
l.end = l.pos
for l.pos < len(l.input) {
switch l.input[l.pos] {
case '[':
return lexText
case ']':
l.tagTmpValue = l.input[l.start:l.end]
return lexTagArgs
case ' ', '\t', '\n':
l.tagTmpValue = l.input[l.start:l.end]
return lexTagArgs
default:
l.end++
}
l.pos++
}
l.emit(TEXT, l.input)
return nil
}
func lexQuotedValue(l *lexer) stateFn {
quoteChar := l.input[l.pos]
l.pos++
l.start = l.pos
var buf bytes.Buffer
escape := false
for l.pos < len(l.input) {
if escape {
if l.input[l.pos] == 'n' {
buf.WriteRune('\n')
} else {
buf.WriteRune(rune(l.input[l.pos]))
}
escape = false
} else {
switch l.input[l.pos] {
case '\\':
escape = true
case '\n':
l.pos = l.start
return lexText
case quoteChar:
l.pos++
l.tagTmpValue = buf.String()
return lexTagArgs
default:
buf.WriteRune(rune(l.input[l.pos]))
}
}
l.pos++
}
l.pos = l.start
return lexText
}
func lexTagArgs(l *lexer) stateFn {
if len(l.tagName) > 0 {
l.tagArgs[strings.ToLower(l.tagTmpName)] = l.tagTmpValue
} else {
l.tagName = l.tagTmpName
l.tagValue = l.tagTmpValue
}
for l.pos < len(l.input) {
switch l.input[l.pos] {
case '[':
return lexText
case ']':
l.pos++
l.emit(OPENING_TAG, BBOpeningTag{strings.ToLower(l.tagName), l.tagValue, l.tagArgs, l.input[:l.pos]})
return lexText
case ' ', '\t', '\n':
l.pos++
default:
l.tagTmpName = ""
return lexTagName
}
}
l.emit(TEXT, l.input)
return nil
}

60
vendor/github.com/frustra/bbcode/parser.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,60 @@
// Copyright 2015 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.
package bbcode
type BBCodeNode struct {
Token
Parent *BBCodeNode
Children []*BBCodeNode
ClosingTag *BBClosingTag
Compiler *Compiler
Info interface{}
}
func (n *BBCodeNode) GetOpeningTag() *BBOpeningTag {
if tag, ok := n.Value.(BBOpeningTag); ok {
return &tag
} else {
return nil
}
}
func (n *BBCodeNode) appendChild(t Token) *BBCodeNode {
if t.ID == CLOSING_TAG {
curr := n
closing := t.Value.(BBClosingTag)
for curr.Parent != nil {
if curr.ID == OPENING_TAG && curr.Value.(BBOpeningTag).Name == closing.Name {
curr.ClosingTag = &closing
return curr.Parent
}
curr = curr.Parent
}
}
// Join consecutive TEXT tokens
if len(n.Children) == 0 && t.ID == TEXT && n.ID == TEXT {
n.Value = n.Value.(string) + t.Value.(string)
return n
}
node := &BBCodeNode{t, n, make([]*BBCodeNode, 0, 5), nil, nil, nil}
n.Children = append(n.Children, node)
if t.ID == OPENING_TAG {
return node
} else {
return n
}
}
func Parse(tokens chan Token) *BBCodeNode {
root := &BBCodeNode{Token{TEXT, ""}, nil, make([]*BBCodeNode, 0, 5), nil, nil, nil}
curr := root
for tok := range tokens {
curr = curr.appendChild(tok)
}
return root
}

Voir le fichier

@ -1,19 +0,0 @@
language: go
sudo: false
matrix:
include:
- go: 1.3
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

Voir le fichier

@ -1,27 +0,0 @@
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Voir le fichier

@ -1,10 +0,0 @@
context
=======
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
gorilla/context is a general purpose registry for global request variables.
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

Voir le fichier

@ -1,143 +0,0 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package context
import (
"net/http"
"sync"
"time"
)
var (
mutex sync.RWMutex
data = make(map[*http.Request]map[interface{}]interface{})
datat = make(map[*http.Request]int64)
)
// Set stores a value for a given key in a given request.
func Set(r *http.Request, key, val interface{}) {
mutex.Lock()
if data[r] == nil {
data[r] = make(map[interface{}]interface{})
datat[r] = time.Now().Unix()
}
data[r][key] = val
mutex.Unlock()
}
// Get returns a value stored for a given key in a given request.
func Get(r *http.Request, key interface{}) interface{} {
mutex.RLock()
if ctx := data[r]; ctx != nil {
value := ctx[key]
mutex.RUnlock()
return value
}
mutex.RUnlock()
return nil
}
// GetOk returns stored value and presence state like multi-value return of map access.
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
mutex.RLock()
if _, ok := data[r]; ok {
value, ok := data[r][key]
mutex.RUnlock()
return value, ok
}
mutex.RUnlock()
return nil, false
}
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
func GetAll(r *http.Request) map[interface{}]interface{} {
mutex.RLock()
if context, ok := data[r]; ok {
result := make(map[interface{}]interface{}, len(context))
for k, v := range context {
result[k] = v
}
mutex.RUnlock()
return result
}
mutex.RUnlock()
return nil
}
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
// the request was registered.
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
mutex.RLock()
context, ok := data[r]
result := make(map[interface{}]interface{}, len(context))
for k, v := range context {
result[k] = v
}
mutex.RUnlock()
return result, ok
}
// Delete removes a value stored for a given key in a given request.
func Delete(r *http.Request, key interface{}) {
mutex.Lock()
if data[r] != nil {
delete(data[r], key)
}
mutex.Unlock()
}
// Clear removes all values stored for a given request.
//
// This is usually called by a handler wrapper to clean up request
// variables at the end of a request lifetime. See ClearHandler().
func Clear(r *http.Request) {
mutex.Lock()
clear(r)
mutex.Unlock()
}
// clear is Clear without the lock.
func clear(r *http.Request) {
delete(data, r)
delete(datat, r)
}
// Purge removes request data stored for longer than maxAge, in seconds.
// It returns the amount of requests removed.
//
// If maxAge <= 0, all request data is removed.
//
// This is only used for sanity check: in case context cleaning was not
// properly set some request data can be kept forever, consuming an increasing
// amount of memory. In case this is detected, Purge() must be called
// periodically until the problem is fixed.
func Purge(maxAge int) int {
mutex.Lock()
count := 0
if maxAge <= 0 {
count = len(data)
data = make(map[*http.Request]map[interface{}]interface{})
datat = make(map[*http.Request]int64)
} else {
min := time.Now().Unix() - int64(maxAge)
for r := range data {
if datat[r] < min {
clear(r)
count++
}
}
}
mutex.Unlock()
return count
}
// ClearHandler wraps an http.Handler and clears request values at the end
// of a request lifetime.
func ClearHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer Clear(r)
h.ServeHTTP(w, r)
})
}

Voir le fichier

@ -1,88 +0,0 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package context stores values shared during a request lifetime.
Note: gorilla/context, having been born well before `context.Context` existed,
does not play well > with the shallow copying of the request that
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
(added to net/http Go 1.7 onwards) performs. You should either use *just*
gorilla/context, or moving forward, the new `http.Request.Context()`.
For example, a router can set variables extracted from the URL and later
application handlers can access those values, or it can be used to store
sessions values to be saved at the end of a request. There are several
others common uses.
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
Here's the basic usage: first define the keys that you will need. The key
type is interface{} so a key can be of any type that supports equality.
Here we define a key using a custom int type to avoid name collisions:
package foo
import (
"github.com/gorilla/context"
)
type key int
const MyKey key = 0
Then set a variable. Variables are bound to an http.Request object, so you
need a request instance to set a value:
context.Set(r, MyKey, "bar")
The application can later access the variable using the same key you provided:
func MyHandler(w http.ResponseWriter, r *http.Request) {
// val is "bar".
val := context.Get(r, foo.MyKey)
// returns ("bar", true)
val, ok := context.GetOk(r, foo.MyKey)
// ...
}
And that's all about the basic usage. We discuss some other ideas below.
Any type can be stored in the context. To enforce a given type, make the key
private and wrap Get() and Set() to accept and return values of a specific
type:
type key int
const mykey key = 0
// GetMyKey returns a value for this package from the request values.
func GetMyKey(r *http.Request) SomeType {
if rv := context.Get(r, mykey); rv != nil {
return rv.(SomeType)
}
return nil
}
// SetMyKey sets a value for this package in the request values.
func SetMyKey(r *http.Request, val SomeType) {
context.Set(r, mykey, val)
}
Variables must be cleared at the end of a request, to remove all values
that were stored. This can be done in an http.Handler, after a request was
served. Just call Clear() passing the request:
context.Clear(r)
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
variables at the end of a request lifetime.
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
so if you are using either of them you don't need to clear the context manually.
*/
package context