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 à :
Parent
e6f0a56f65
révision
ed61de6276
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 > lol</p>\n</blockquote>\n"}, // Limit number of blockquotes
|
||||
{"> 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
|
||||
}{
|
||||
{"", ""},
|
||||
{">", ">"}, // 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
|
||||
}{
|
||||
{"", ""},
|
||||
{">", ">"}, // 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
|
||||
{">", ">"}, // 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")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
/bbcode
|
||||
/bbcode.test
|
|
@ -0,0 +1,3 @@
|
|||
language: go
|
||||
go: 1.2
|
||||
script: go test -v
|
|
@ -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.
|
|
@ -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>
|
||||
```
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 ./...
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
Référencer dans un nouveau ticket