Переглянути джерело

merge with local devs

tags/v0.9.0
jimzhan 9 роки тому
джерело
коміт
3e0d8885a0
14 змінених файлів з 868 додано та 10 видалено
  1. +11
    -9
      README.md
  2. BIN
      cmd/rex/rex
  3. +1
    -1
      cmd/rex/run.go
  4. +28
    -0
      livereload/javascript.go
  5. +147
    -0
      livereload/livereload.go
  6. +42
    -0
      livereload/message.go
  7. +108
    -0
      livereload/module.go
  8. +79
    -0
      livereload/tunnel.go
  9. +140
    -0
      modules/compress.go
  10. +23
    -0
      modules/compress_test.go
  11. +36
    -0
      modules/header.go
  12. +186
    -0
      modules/xsrf.go
  13. +33
    -0
      response.go
  14. +34
    -0
      utils.go

+ 11
- 9
README.md Переглянути файл

@@ -1,7 +1,9 @@
<a href="#"><img alt="rex" src="https://raw.githubusercontent.com/go-rex/rex/assets/images/rex.png" width="160px" height="64px"></a>
===

Rex is a powerful framework for modular web development in Golang.
Rex is a powerful toolkit for modular web development in Golang, designed to work directly with net/http.

<img alt="wrk" src="https://raw.githubusercontent.com/goanywhere/rex/assets/images/wrk.png">

## Getting Started

@@ -14,16 +16,16 @@ $ go get -v github.com/goanywhere/rex/...
## Features
* Flexible Env-based configurations.
* Awesome routing system provided by [Gorilla/Mux](//github.com/gorilla/mux).
* Django-syntax like templating-language backed by [flosch/pongo](//github.com/flosch/pongo2).
* Group routing system with middleware modules supports
* Non-intrusive/Modular design, extremely easy to use.
* Standard & modular system based on [http.Handler](http://godoc.org/net/http#Handler) interface.
* Command line tools
* Auto-compile for .go & .html
* Browser-based Live reload supports
* Auto-compile/reload for .go & .html sources
* Browser-based Live reload supports for HTML templates
* **Fully compatible with the [http.Handler](http://godoc.org/net/http#Handler)/[http.HandlerFunc](http://godoc.org/net/http#HandlerFunc) interface.**


After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first server, we named it `server.go` here.
After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first server.

``` go
package main
@@ -85,7 +87,7 @@ Hey, dude, why not just use those popular approaches, like file-based config? We

## Modules

Modules work between http requests and the router, they are no different than the standard http.Handler. Existing modules (aka. middleware) from other frameworks like logging, authorization, session, gzipping are very easy to integrate into Rex. As long as it complies the `rex.Module` interface (shorcut to standard `func(http.Handler) http.Handler`), you can simply add one like this:
Modules (aka. middleware) work between http requests and the router, they are no different than the standard http.Handler. Existing modules from other frameworks like logging, authorization, session, gzipping are very easy to integrate into Rex. As long as it complies the standard `func(http.Handler) http.Handler` signature, you can simply add one like this:

``` go
app.Use(modules.XSRF)
@@ -96,7 +98,7 @@ Since a module is just the standard http.Handler, writing a custom module is als

``` go
app.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Custom Middleware Module Started")
next.ServeHTTP(writer, request)
log.Printf("Custom Middleware Module Ended")
@@ -134,12 +136,12 @@ Positive! Rex is an internal/fundamental project at GoAnywhere. We developed it
- [X] Test Suite
- [X] New Project Template
- [X] CLI Apps Integrations
- [X] Improved Template Rendering
- [X] Performance Boost
- [X] Hot-Compile Runner
- [X] Live Reload Integration
- [X] Template Functions
- [X] Common Modules
- [ ] Full Test Converage
- [ ] Improved Template Rendering
- [ ] Project Wiki
- [ ] Continuous Integration
- [ ] Stable API


+ 1
- 1
cmd/rex/run.go Переглянути файл

@@ -37,7 +37,7 @@ import (
"github.com/codegangsta/cli"

"github.com/goanywhere/rex/internal"
"github.com/goanywhere/rex/modules/livereload"
"github.com/goanywhere/rex/livereload"

"github.com/goanywhere/x/cmd"
"github.com/goanywhere/x/env"

+ 28
- 0
livereload/javascript.go
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 147
- 0
livereload/livereload.go Переглянути файл

@@ -0,0 +1,147 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package livereload

import (
"encoding/json"
"net/http"
"sync"

"github.com/gorilla/websocket"
)

/* ----------------------------------------------------------------------
* WebSocket Server
* ----------------------------------------------------------------------*/
var (
once sync.Once

broadcast chan []byte
tunnels map[*tunnel]bool

in chan *tunnel
out chan *tunnel

mutex sync.RWMutex

upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}

URL = struct {
WebSocket string
JavaScript string
}{
WebSocket: "/livereload",
JavaScript: "/livereload.js",
}
)

// Alert sends a notice message to browser's livereload.js.
func Alert(message string) {
go func() {
var bytes, _ = json.Marshal(&alert{
Command: "alert",
Message: message,
})
broadcast <- bytes
}()
}

// Reload sends a reload message to browser's livereload.js.
func Reload() {
go func() {
var bytes, _ = json.Marshal(&reload{
Command: "reload",
Path: URL.WebSocket,
LiveCSS: true,
})
broadcast <- bytes
}()
}

// run watches/dispatches all tunnel & tunnel messages.
func run() {
for {
select {
case tunnel := <-in:
mutex.Lock()
defer mutex.Unlock()
tunnels[tunnel] = true

case tunnel := <-out:
mutex.Lock()
defer mutex.Unlock()
delete(tunnels, tunnel)
close(tunnel.message)

case m := <-broadcast:
for tunnel := range tunnels {
select {
case tunnel.message <- m:
default:
mutex.Lock()
defer mutex.Unlock()
delete(tunnels, tunnel)
close(tunnel.message)
}
}
}
}
}

// Serve serves as a livereload server for accepting I/O tunnel messages.
func ServeWebSocket(w http.ResponseWriter, r *http.Request) {
var socket, err = upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
tunnel := new(tunnel)
tunnel.socket = socket
tunnel.message = make(chan []byte, 256)

in <- tunnel
defer func() { out <- tunnel }()

tunnel.connect()
}

// ServeJavaScript serves livereload.js for browser.
func ServeJavaScript(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
w.Write(javascript)
}

// Start activates livereload server for accepting tunnel messages.
func Start() {
once.Do(func() {
broadcast = make(chan []byte)
tunnels = make(map[*tunnel]bool)

in = make(chan *tunnel)
out = make(chan *tunnel)

go run()
})
}

+ 42
- 0
livereload/message.go Переглянути файл

@@ -0,0 +1,42 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package livereload

type (
hello struct {
Command string `json:"command"`
Protocols []string `json:"protocols"`
ServerName string `json:"serverName"`
}

alert struct {
Command string `json:"command"`
Message string `json:"message"`
}

reload struct {
Command string `json:"command"`
Path string `json:"path"` // as full as possible/known, absolute path preferred, file name only is OK
LiveCSS bool `json:"liveCSS"` // false to disable live CSS refresh
}
)

+ 108
- 0
livereload/module.go Переглянути файл

@@ -0,0 +1,108 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package livereload

import (
"bytes"
"compress/gzip"
"compress/zlib"
"fmt"
"io"
"net/http"
"regexp"
"strings"

"github.com/goanywhere/x/env"
)

type writer struct {
http.ResponseWriter
host string
}

func (self *writer) addJavaScript(data []byte) []byte {
javascript := fmt.Sprintf(`<script src="//%s%s"></script>
</head>`, self.host, URL.JavaScript)
return regexp.MustCompile(`</head>`).ReplaceAll(data, []byte(javascript))
}

func (self *writer) Write(data []byte) (size int, e error) {
if strings.Contains(self.Header().Get("Content-Type"), "html") {
var encoding = self.Header().Get("Content-Encoding")
if encoding == "" {
data = self.addJavaScript(data)
} else {
var reader io.ReadCloser
var buffer *bytes.Buffer = new(bytes.Buffer)

if encoding == "gzip" {
// decode to add javascript reference.
reader, _ = gzip.NewReader(bytes.NewReader(data))
io.Copy(buffer, reader)
output := self.addJavaScript(buffer.Bytes())
reader.Close()
buffer.Reset()
// encode back to HTML with added javascript reference.
writer := gzip.NewWriter(buffer)
writer.Write(output)
writer.Close()
data = buffer.Bytes()

} else if encoding == "deflate" {
// decode to add javascript reference.
reader, _ = zlib.NewReader(bytes.NewReader(data))
io.Copy(buffer, reader)
output := self.addJavaScript(buffer.Bytes())
reader.Close()
buffer.Reset()
// encode back to HTML with added javascript reference.
writer := zlib.NewWriter(buffer)
writer.Write(output)
writer.Close()
data = buffer.Bytes()
}
}
}
return self.ResponseWriter.Write(data)
}

func Module(next http.Handler) http.Handler {
// ONLY run this under debug mode.
if !env.Bool("DEBUG", true) {
return next
}
Start()
fn := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == URL.WebSocket {
ServeWebSocket(w, r)

} else if r.URL.Path == URL.JavaScript {
ServeJavaScript(w, r)

} else {
writer := &writer{w, r.Host}
next.ServeHTTP(writer, r)
}
}
return http.HandlerFunc(fn)
}

+ 79
- 0
livereload/tunnel.go Переглянути файл

@@ -0,0 +1,79 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package livereload

import (
"encoding/json"
"regexp"

"github.com/gorilla/websocket"
)

var regexHandshake = regexp.MustCompile(`"command"\s*:\s*"hello"`)

/* ----------------------------------------------------------------------
* WebSocket Server Tunnel
* ----------------------------------------------------------------------*/
type tunnel struct {
socket *websocket.Conn
message chan []byte
}

// connect reads/writes message for livereload.js.
func (self *tunnel) connect() {
// ***********************
// WebSocket Tunnel#Write
// ***********************
go func() {
for message := range self.message {
if err := self.socket.WriteMessage(websocket.TextMessage, message); err != nil {
break
} else {
if regexHandshake.Find(message) != nil {
// Keep the tunnel opened after handshake(hello command).
Reload()
}
}
}
self.socket.Close()
}()
// ***********************
// WebSocket Tunnel#Read
// ***********************
for {
_, message, err := self.socket.ReadMessage()
if err != nil {
break
}
switch true {
case regexHandshake.Find(message) != nil:
var bytes, _ = json.Marshal(&hello{
Command: "hello",
Protocols: []string{"http://livereload.com/protocols/official-7"},
ServerName: "Rex#Livereload",
})
self.message <- bytes
}
}
self.socket.Close()
}

+ 140
- 0
modules/compress.go Переглянути файл

@@ -0,0 +1,140 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package modules

import (
"bytes"
"compress/flate"
"compress/gzip"
"io"
"net/http"
"regexp"
"strings"
)

var (
regexAcceptEncoding = regexp.MustCompile(`(gzip|deflate|\*)(;q=(1(\.0)?|0(\.[0-9])?))?`)
regexContentType = regexp.MustCompile(`((message|text)\/.+)|((application\/).*(javascript|json|xml))`)
)

type compression interface {
io.WriteCloser
}

type compressor struct {
http.ResponseWriter
encodings []string
}

// AcceptEncodings fetches the requested encodings from client with priority.
func (self *compressor) acceptEncodings(request *http.Request) (encodings []string) {
// find all encodings supported by backend server.
matches := regexAcceptEncoding.FindAllString(request.Header.Get("Accept-Encoding"), -1)
for _, item := range matches {
units := strings.SplitN(item, ";", 2)
// top priority with q=1|q=1.0|Not Specified.
if len(units) == 1 {
encodings = append(encodings, units[0])

} else {
if strings.HasPrefix(units[1], "q=1") {
// insert the specified top priority to the first.
encodings = append([]string{units[0]}, encodings...)

} else if strings.HasSuffix(units[1], "0") {
// not acceptable at client side.
continue
} else {
// lower priority encoding
encodings = append(encodings, units[0])
}
}
}
return
}

func (self *compressor) filter(src []byte) ([]byte, string) {
var mimetype = self.Header().Get("Content-Type")
if mimetype == "" {
mimetype = http.DetectContentType(src)
self.Header().Set("Content-Type", mimetype)
}

if self.Header().Get("Content-Encoding") != "" {
return src, ""
}

if !regexContentType.MatchString(strings.TrimSpace(strings.SplitN(mimetype, ";", 2)[0])) {
return src, ""
}

// okay to start compressing.
var e error
var encoding string
var writer compression
var buffer *bytes.Buffer = new(bytes.Buffer)
// try compress the data, if any error occrued, fallback to ResponseWriter.
if self.encodings[0] == "deflate" {
encoding = "deflate"
writer, e = flate.NewWriter(buffer, flate.DefaultCompression)
} else {
encoding = "gzip"
writer, e = gzip.NewWriterLevel(buffer, gzip.DefaultCompression)
}
_, e = writer.Write(src)
writer.Close()
if e == nil {
return buffer.Bytes(), encoding
}
// fallback to standard http.ResponseWriter, nothing happened~ (~__~"")
return src, ""
}

func (self *compressor) Write(data []byte) (size int, err error) {
if bytes, encoding := self.filter(data); encoding != "" {
self.Header().Set("Content-Encoding", encoding)
self.Header().Add("Vary", "Accept-Encoding")
self.Header().Del("Content-Length")
return self.ResponseWriter.Write(bytes)
}
return self.ResponseWriter.Write(data)
}

func Compress(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Sec-WebSocket-Key") != "" || r.Method == "HEAD" {
next.ServeHTTP(w, r)
} else {
compressor := new(compressor)
compressor.ResponseWriter = w

encodings := compressor.acceptEncodings(r)
if len(encodings) == 0 {
next.ServeHTTP(w, r)
} else {
compressor.encodings = encodings
next.ServeHTTP(compressor, r)
}
}
})
}

+ 23
- 0
modules/compress_test.go Переглянути файл

@@ -0,0 +1,23 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package modules

+ 36
- 0
modules/header.go Переглянути файл

@@ -0,0 +1,36 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package modules

import "net/http"

func Header(values map[string]string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for key, value := range values {
w.Header().Set(key, value)
}
next.ServeHTTP(w, r)
})
}
}

+ 186
- 0
modules/xsrf.go Переглянути файл

@@ -0,0 +1,186 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package modules

import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"
"time"

"github.com/goanywhere/x/crypto"
)

const (
xsrfCookieName = "xsrf"
xsrfHeaderName = "X-XSRF-Token"
xsrfFieldName = "xsrftoken"

xsrfMaxAge = 3600 * 24 * 365
xsrfTimeout = time.Hour * 24 * 365
)

var (
errXSRFReferer = "Referer URL is missing from the request or the value was malformed."
errXSRFToken = "Invalid XSRF tokens"

xsrfPattern = regexp.MustCompile("[^0-9a-zA-Z-_]")
unsafeMethods = regexp.MustCompile("^(DELETE|POST|PUT)$")
)

type xsrf struct {
*http.Request
http.ResponseWriter
token string
}

// See http://en.wikipedia.org/wiki/Same-origin_policy
func (self *xsrf) checkOrigin() bool {
if self.Request.URL.Scheme == "https" {
// See [OWASP]; Checking the Referer Header.
referer, err := url.Parse(self.Request.Header.Get("Referer"))

if err != nil || referer.String() == "" ||
referer.Scheme != self.Request.URL.Scheme ||
referer.Host != self.Request.URL.Host {

return false
}
}
return true
}

func (self *xsrf) checkToken(token string) bool {
// Header always takes precedance of form field since some popular
// JavaScript frameworks allow global custom headers for all AJAX requests.
query := self.Request.Header.Get(xsrfFieldName)
if query == "" {
query = self.Request.FormValue(xsrfFieldName)
}

// 1) basic length comparison.
if query == "" || len(query) != len(token) {
return false
}
// *sanitize* incoming masked token.
query = xsrfPattern.ReplaceAllString(query, "")

// 2) byte-based comparison.
a, _ := base64.URLEncoding.DecodeString(token)
b, _ := base64.URLEncoding.DecodeString(query)
if subtle.ConstantTimeCompare(a, b) != 1 {
return false
}

// 3) issued time checking.
index := bytes.LastIndex(b, []byte{'|'})
if index != 40 {
return false
}

nanos, err := strconv.ParseInt(string(b[index+1:]), 10, 64)
if err != nil {
return false
}
now := time.Now()
issueTime := time.Unix(0, nanos)

if now.Sub(issueTime) >= xsrfTimeout {
return false
}

// Ensure the token is not from the *future*, allow 1 minute grace period.
if issueTime.After(now.Add(1 * time.Minute)) {
return false
}

return true
}

func (self *xsrf) generate() {
// Ensure we have XSRF token in the cookie first.
var token string
if cookie, err := self.Request.Cookie(xsrfCookieName); err == nil {
if cookie.Value != "" {
token = cookie.Value
}
}
if token == "" {
// Generate a base64-encoded token.
nano := time.Now().UnixNano()
hash := hmac.New(sha1.New, []byte(crypto.Random(32)))
fmt.Fprintf(hash, "%s|%d", crypto.Random(12), nano)
raw := fmt.Sprintf("%s|%d", hex.EncodeToString(hash.Sum(nil)), nano)
token = base64.URLEncoding.EncodeToString([]byte(raw))

// The max-age directive takes priority over Expires.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
cookie := new(http.Cookie)
cookie.Name = xsrfCookieName
cookie.Value = token
cookie.MaxAge = xsrfMaxAge
cookie.Path = "/"
cookie.HttpOnly = true
http.SetCookie(self.ResponseWriter, cookie)
}
self.ResponseWriter.Header()[xsrfHeaderName] = []string{token}
self.token = token
}

// ---------------------------------------------------------------------------
// XSRF Middleware Supports
// ---------------------------------------------------------------------------
func XSRF(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
x := new(xsrf)
x.Request = r
x.ResponseWriter = w
x.generate()

if unsafeMethods.MatchString(r.Method) {
// Ensure the URL came for "Referer" under HTTPS.
if !x.checkOrigin() {
http.Error(w, errXSRFReferer, http.StatusForbidden)
}

// length => bytes => issue time checkpoints.
if !x.checkToken(x.token) {
http.Error(w, errXSRFToken, http.StatusForbidden)
}
}

// ensure browser will invalidate the cached XSRF token.
w.Header().Add("Vary", "Cookie")

next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

+ 33
- 0
response.go Переглянути файл

@@ -0,0 +1,33 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package rex

import "net/http"

type response interface {
SetStatus(code int)
SetContentType(contentType string)
}

func Render(w http.ResponseWriter) {
}

+ 34
- 0
utils.go Переглянути файл

@@ -0,0 +1,34 @@
/* ----------------------------------------------------------------------
* ______ ___ __
* / ____/___ / | ____ __ ___ __/ /_ ___ ________
* / / __/ __ \/ /| | / __ \/ / / / | /| / / __ \/ _ \/ ___/ _ \
* / /_/ / /_/ / ___ |/ / / / /_/ /| |/ |/ / / / / __/ / / __/
* \____/\____/_/ |_/_/ /_/\__. / |__/|__/_/ /_/\___/_/ \___/
* /____/
*
* (C) Copyright 2015 GoAnywhere (http://goanywhere.io).
* ----------------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ----------------------------------------------------------------------*/
package rex

import (
"net/http"

"github.com/gorilla/mux"
)

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
return mux.Vars(r)
}

Завантаження…
Відмінити
Зберегти