1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

removed recipe for now

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2016-10-01 21:06:15 -07:00
parent 2507dc13e9
commit dd8d433573
1405 changed files with 0 additions and 363007 deletions

View File

@ -1,38 +0,0 @@
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
var (
users = []string{"Joe", "Veer", "Zion"}
)
func getUsers(c echo.Context) error {
return c.JSON(http.StatusOK, users)
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// CORS default
// Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
// e.Use(middleware.CORS())
// CORS restricted
// Allows requests from any `https://labstack.com` or `https://labstack.net` origin
// wth GET, PUT, POST or DELETE method.
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
}))
e.GET("/api/users", getUsers)
e.Run(standard.New(":1323"))
}

View File

@ -1,76 +0,0 @@
package main
import (
"net/http"
"strconv"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
type (
user struct {
ID int `json:"id"`
Name string `json:"name"`
}
)
var (
users = map[int]*user{}
seq = 1
)
//----------
// Handlers
//----------
func createUser(c echo.Context) error {
u := &user{
ID: seq,
}
if err := c.Bind(u); err != nil {
return err
}
users[u.ID] = u
seq++
return c.JSON(http.StatusCreated, u)
}
func getUser(c echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
return c.JSON(http.StatusOK, users[id])
}
func updateUser(c echo.Context) error {
u := new(user)
if err := c.Bind(u); err != nil {
return err
}
id, _ := strconv.Atoi(c.Param("id"))
users[id].Name = u.Name
return c.JSON(http.StatusOK, users[id])
}
func deleteUser(c echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
delete(users, id)
return c.NoContent(http.StatusNoContent)
}
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.POST("/users", createUser)
e.GET("/users/:id", getUser)
e.PUT("/users/:id", updateUser)
e.DELETE("/users/:id", deleteUser)
// Start server
e.Run(standard.New(":1323"))
}

View File

@ -1,2 +0,0 @@
rice
app.rice-box.go

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>go.rice Example</title>
<script src="/static/main.js" charset="utf-8"></script>
</head>
<body>
<h1>go.rice Example</h1>
</body>
</html>

View File

@ -1 +0,0 @@
alert("main.js");

View File

@ -1,21 +0,0 @@
package main
import (
"net/http"
"github.com/GeertJohan/go.rice"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
)
func main() {
e := echo.New()
// the file server for rice. "app" is the folder where the files come from.
assetHandler := http.FileServer(rice.MustFindBox("app").HTTPBox())
// serves the index.html from rice
e.GET("/", standard.WrapHandler(assetHandler))
// servers other static files
e.GET("/static/*", standard.WrapHandler(http.StripPrefix("/static/", assetHandler)))
e.Run(standard.New(":3000"))
}

View File

@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Multiple file upload</title>
</head>
<body>
<h1>Upload multiple files with fields</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
Name: <input type="text" name="name"><br>
Email: <input type="email" name="email"><br>
Files: <input type="file" name="files" multiple><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

View File

@ -1,66 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func upload(c echo.Context) error {
// Read form fields
name := c.FormValue("name")
email := c.FormValue("email")
//------------
// Read files
//------------
// Multipart form
form, err := c.MultipartForm()
if err != nil {
return err
}
files := form.File["files"]
for _, file := range files {
// Source
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
// Destination
dst, err := os.Create(file.Filename)
if err != nil {
return err
}
defer dst.Close()
// Copy
if _, err = io.Copy(dst, src); err != nil {
return err
}
}
return c.HTML(http.StatusOK, fmt.Sprintf("<p>Uploaded successfully %d files with fields name=%s and email=%s.</p>", len(files), name, email))
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.Static("public"))
e.POST("/upload", upload)
e.Run(standard.New(":1323"))
}

View File

@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Single file upload</title>
</head>
<body>
<h1>Upload single file with fields</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
Name: <input type="text" name="name"><br>
Email: <input type="email" name="email"><br>
Files: <input type="file" name="file"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

View File

@ -1,60 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func upload(c echo.Context) error {
// Read form fields
name := c.FormValue("name")
email := c.FormValue("email")
//-----------
// Read file
//-----------
// Source
file, err := c.FormFile("file")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
// Destination
dst, err := os.Create(file.Filename)
if err != nil {
return err
}
defer dst.Close()
// Copy
if _, err = io.Copy(dst, src); err != nil {
return err
}
return c.HTML(http.StatusOK, fmt.Sprintf("<p>File %s uploaded successfully with fields name=%s and email=%s.</p>", file.Filename, name, email))
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.Static("public"))
e.POST("/upload", upload)
e.Run(standard.New(":1323"))
}

79
recipe/glide.lock generated
View File

@ -1,79 +0,0 @@
hash: 4778db3e2f34d86e235c18c2a0d787754a34393d988a7db16e9609407c5b27ca
updated: 2016-10-01T20:48:35.75268732-07:00
imports:
- name: github.com/daaku/go.zipexe
version: a5fe2436ffcb3236e175e5149162b41cd28bd27d
- name: github.com/dgrijalva/jwt-go
version: 24c63f56522a87ec5339cc3567883f1039378fdb
- name: github.com/facebookgo/clock
version: 600d898af40aa09a7a93ecb9265d87b0504b6f03
- name: github.com/facebookgo/grace
version: 5729e484473f52048578af1b80d0008c7024089b
subpackages:
- gracehttp
- gracenet
- name: github.com/facebookgo/httpdown
version: a3b1354551a26449fbe05f5d855937f6e7acbd71
- name: github.com/facebookgo/stats
version: 1b76add642e42c6ffba7211ad7b3939ce654526e
- name: github.com/GeertJohan/go.rice
version: 9fdfd46f9806a9228aae341d65ab75c5235c383c
subpackages:
- embedded
- name: github.com/golang/protobuf
version: df1d3ca07d2d07bba352d5b73c4313b4e2a6203e
subpackages:
- proto
- name: github.com/gorilla/websocket
version: 2d1e4548da234d9cb742cc3628556fef86aafbac
- name: github.com/kardianos/osext
version: c2c54e542fb797ad986b31721e1baedf214ca413
- name: github.com/labstack/echo
version: 9c0e9d689f87624ad01748b1a963d356afbfdceb
subpackages:
- context
- engine
- engine/standard
- log
- middleware
- name: github.com/labstack/gommon
version: f3b1a1b3bd4726161e1200863d278df7da5e66ff
subpackages:
- bytes
- color
- log
- random
- name: github.com/mattn/go-colorable
version: 6c903ff4aa50920ca86087a280590b36b3152b9c
- name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/rs/cors
version: a62a804a8a009876ca59105f7899938a1349f4b3
- name: github.com/rs/xhandler
version: ed27b6fd65218132ee50cd95f38474a3d8a2cd12
- name: github.com/thoas/stats
version: 152b5d051953fdb6e45f14b6826962aadc032324
- name: github.com/tylerb/graceful
version: 50a48b6e73fcc75b45e22c05b79629a67c79e938
- name: github.com/valyala/fasttemplate
version: 3b874956e03f1636d171bda64b130f9135f42cff
- name: golang.org/x/net
version: cd95c68ba21fc7ee16ee38cc420473e3d8c4afd8
subpackages:
- context
- websocket
- name: golang.org/x/sys
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
subpackages:
- unix
- name: google.golang.org/appengine
version: 150dc57a1b433e64154302bdc40b6bb8aefa313a
subpackages:
- internal
- internal/app_identity
- internal/base
- internal/datastore
- internal/log
- internal/modules
- internal/remote_api
testImports: []

View File

@ -1,20 +0,0 @@
package: github.com/labstack/echo/recipe
import:
- package: github.com/GeertJohan/go.rice
- package: github.com/dgrijalva/jwt-go
- package: github.com/facebookgo/grace
subpackages:
- gracehttp
- package: github.com/gorilla/websocket
- package: github.com/labstack/echo
subpackages:
- engine
- engine/standard
- middleware
- package: github.com/rs/cors
- package: github.com/thoas/stats
- package: github.com/tylerb/graceful
- package: golang.org/x/net
subpackages:
- websocket
- package: google.golang.org/appengine

View File

@ -1,7 +0,0 @@
# Dockerfile extending the generic Go image with application files for a
# single application.
FROM gcr.io/google_appengine/golang
COPY . /go/src/app
RUN go-wrapper download
RUN go-wrapper install -tags appenginevm

View File

@ -1,21 +0,0 @@
// +build appengine
package main
import (
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"net/http"
)
func createMux() *echo.Echo {
e := echo.New()
// note: we don't need to provide the middleware or static handlers, that's taken care of by the platform
// app engine has it's own "main" wrapper - we just need to hook echo into the default handler
s := standard.New("")
s.SetHandler(e)
http.Handle("/", s)
return e
}

View File

@ -1,36 +0,0 @@
application: my-application-id # defined when you create your app using google dev console
module: default # see https://cloud.google.com/appengine/docs/go/
version: alpha # you can run multiple versions of an app and A/B test
runtime: go # see https://cloud.google.com/appengine/docs/go/
api_version: go1 # used when appengine supports different go versions
default_expiration: "1d" # for CDN serving of static files (use url versioning if long!)
handlers:
# all the static files that we normally serve ourselves are defined here and Google will handle
# serving them for us from it's own CDN / edge locations. For all the configuration options see:
# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers
- url: /
mime_type: text/html
static_files: public/index.html
upload: public/index.html
- url: /favicon.ico
mime_type: image/x-icon
static_files: public/favicon.ico
upload: public/favicon.ico
- url: /scripts
mime_type: text/javascript
static_dir: public/scripts
# static files normally don't touch the server that the app runs on but server-side template files
# needs to be readable by the app. The application_readable option makes sure they are available as
# part of the app deployment onto the instance.
- url: /templates
static_dir: /templates
application_readable: true
# finally, we route all other requests to our application. The script name just means "the go app"
- url: /.*
script: _go_app

View File

@ -1,29 +0,0 @@
// +build appenginevm
package main
import (
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"google.golang.org/appengine"
"net/http"
"runtime"
)
func createMux() *echo.Echo {
e := echo.New()
// note: we don't need to provide the middleware or static handlers
// for the appengine vm version - that's taken care of by the platform
return e
}
func main() {
// the appengine package provides a convenient method to handle the health-check requests
// and also run the app on the correct port. We just need to add Echo to the default handler
s := standard.New(":8080")
s.SetHandler(e)
http.Handle("/", s)
appengine.Main()
}

View File

@ -1,37 +0,0 @@
application: my-application-id # defined when you create your app using google dev console
module: default # see https://cloud.google.com/appengine/docs/go/
version: alpha # you can run multiple versions of an app and A/B test
runtime: go # see https://cloud.google.com/appengine/docs/go/
api_version: go1 # used when appengine supports different go versions
vm: true # for managed VMs only, remove for appengine classic
default_expiration: "1d" # for CDN serving of static files (use url versioning if long!)
handlers:
# all the static files that we normally serve ourselves are defined here and Google will handle
# serving them for us from it's own CDN / edge locations. For all the configuration options see:
# https://cloud.google.com/appengine/docs/go/config/appconfig#Go_app_yaml_Static_file_handlers
- url: /
mime_type: text/html
static_files: public/index.html
upload: public/index.html
- url: /favicon.ico
mime_type: image/x-icon
static_files: public/favicon.ico
upload: public/favicon.ico
- url: /scripts
mime_type: text/javascript
static_dir: public/scripts
# static files normally don't touch the server that the app runs on but server-side template files
# needs to be readable by the app. The application_readable option makes sure they are available as
# part of the app deployment onto the instance.
- url: /templates
static_dir: /templates
application_readable: true
# finally, we route all other requests to our application. The script name just means "the go app"
- url: /.*
script: _go_app

View File

@ -1,25 +0,0 @@
// +build !appengine,!appenginevm
package main
import (
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func createMux() *echo.Echo {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())
e.Use(middleware.Gzip())
e.Use(middleware.Static("public"))
return e
}
func main() {
e.Run(standard.New(":8080"))
}

View File

@ -1,4 +0,0 @@
package main
// reference our echo instance and create it early
var e = createMux()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Echo</title>
<link rel="shortcut icon" href="favicon.ico" />
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Echo!</h1>
<script src="/scripts/main.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
console.log("Echo!");

View File

@ -1 +0,0 @@
{{define "welcome"}}Hello, {{.}}!{{end}}

View File

@ -1,55 +0,0 @@
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/rs/cors"
)
type (
user struct {
ID string `json:"id"`
Name string `json:"name"`
}
)
var (
users map[string]user
)
func init() {
users = map[string]user{
"1": user{
ID: "1",
Name: "Wreck-It Ralph",
},
}
// hook into the echo instance to create an endpoint group
// and add specific middleware to it plus handlers
g := e.Group("/users")
g.Use(standard.WrapMiddleware(cors.Default().Handler))
g.POST("", createUser)
g.GET("", getUsers)
g.GET("/:id", getUser)
}
func createUser(c echo.Context) error {
u := new(user)
if err := c.Bind(u); err != nil {
return err
}
users[u.ID] = *u
return c.JSON(http.StatusCreated, u)
}
func getUsers(c echo.Context) error {
return c.JSON(http.StatusOK, users)
}
func getUser(c echo.Context) error {
return c.JSON(http.StatusOK, users[c.P(0)])
}

View File

@ -1,31 +0,0 @@
package main
import (
"html/template"
"io"
"net/http"
"github.com/labstack/echo"
)
type (
Template struct {
templates *template.Template
}
)
func init() {
t := &Template{
templates: template.Must(template.ParseFiles("templates/welcome.html")),
}
e.SetRenderer(t)
e.GET("/welcome", welcome)
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func welcome(c echo.Context) error {
return c.Render(http.StatusOK, "welcome", "Joe")
}

View File

@ -1,19 +0,0 @@
package main
import (
"net/http"
"github.com/facebookgo/grace/gracehttp"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Six sick bricks tick")
})
std := standard.New(":1323")
std.SetHandler(e)
gracehttp.Serve(std.Server)
}

View File

@ -1,21 +0,0 @@
package main
import (
"net/http"
"time"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/tylerb/graceful"
)
func main() {
// Setup
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Sue sews rose on slow joe crows nose")
})
std := standard.New(":1323")
std.SetHandler(e)
graceful.ListenAndServe(std.Server, 5*time.Second)
}

View File

@ -1,26 +0,0 @@
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Route => handler
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
})
// Start server
e.Run(standard.New(":1323"))
}

View File

@ -1,18 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIC+DCCAeCgAwIBAgIPFB3cowaziqTVN3I2utsEMA0GCSqGSIb3DQEBCwUAMBIx
EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTYwNTA5MTUwNDQ2WhcNMTcwNTA5MTUwNDQ2
WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw+jr8IJ7W
aUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2iqxyrrRNy
n3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q0SMGF+Yg
RnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3Xc/d96OY
35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phKmB22qnlg
PY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCBaAw
EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls
b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAL2SqeH2Z6zKje9XC6O/TC0DKRvE
L/d/1NVz5nr52Gqk+7DzU1QCWHD9+FLEX+wvzcploNoqcR+Xrh7N/2kNGymDdxP+
UlpmpzLQ6BwcjWgwMa4GMn3L/aczFFuhG9dZdcy9UV4fXtreLO0FMt4BuBQyKMK2
ZkWgw2rx3fMBgR4B99JpRr0wgqPHbFndvPYbMAEiYB4baRiIpkyBC6/JyZTNTBBH
+WyvuQKbip5WvVST+PjIy2eywJ2yGXKek3TOS8GTulVqUE5u7utzdOLJaLXIvF2K
BAbRa6CZ8+bZWRYpBujlu1mKjedzi/pS5BK5q/gPUWe8YcqEJVZWEHMakZ0=
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA2Q55Uf4UX00jIvyeu8RF4BQC6RCqwiQeg+k7a9LvQyIOQpaw
+jr8IJ7WaUUYe3vF1fIgtSsHVGnGtfYcTmgOCbqhQnU7LYuDYpVfXemNTvMkbT2i
qxyrrRNyn3FVMnf1O9HrRol8mfOCVf5j3GeqyCw1PXH1OgnNLDsdz7DoNUXt334Q
0SMGF+YgRnlAIzRmye1UPBPVupcSbQBPGEtBstBQMYtKQDf0bPfciV6nHMhTrmw3
Xc/d96OY35iZ0zveWUYVJfyi8xqPBtfjnomUs8UmQ47aZdL6mrWaHXinIbTI4phK
mB22qnlgPY//yrMM2BgEMGGi8FOFQG/n5f0zzQIDAQABAoIBABvDpTMeu/5gwJDW
HXvJJGMATEsBrcX9lsWqzMYDCkXdElPkMKkhaGTHzIdUpkgNUgYG3bYu1dYvXZKi
84X3+2u1KrF0i2hNAzZWAwfhOwkRQuSSUL9wgGk4Jp5Mgehdvcm0FVNFfOG4a0CS
tLVZsVe/h+PfMs4EuPDicvLBH0LI/dE+GeXGIhuF8/g7EN3S+f/WtCejrzah7Ghf
OsaBaBL0O5s82wU9PX0ZFxo4FADfwKLL0Zmbno8NC0cMsNOM2/B2dRF0eTlCDeAy
rxIXL7HEz+fWK96jCbDiMPyP9XstQxO2atZa2gC5vOJCwPpVVDbqW7FNop4VwUOW
bTtDrcUCgYEA6+hyuN0DcaAOM64rVyBbcMOD4L6Q00A1+9I1wBJtx1yNNfTS4iJc
EXxkLS8ae9xXYEGhiU0PyOufs+iTwLwvlbWcZUJailEoBG5EzC1BDZPE20hNcXav
zxxyQxRrzdItmMj8wa4TETVlcPGcKxNUw/UXqFIyNShtALse2mnYQQcCgYEA64sA
3WC+/e0jVoaPiIuCg2vt5QUiyfkblSUjsdBTlMWYIjlJNcGv81vepk3NIAPkTs71
HZuRwEDENm/7HDSG2SpyB9de8/b061Uo/o+ZhWqPOHx1m7mD9x96CZk4wtxwL14u
SJuwyujqmSYoUYW8FKjHS5n74hP9YUT7hXKWs4sCgYAx2eAMUp/8rd7yata7xZmt
HZPLtVlzWrlNqqEzInHSVCt/AGpj4PDlvQyKQ87r56cLLzNMiV1RjwEjin1WmC3S
DButhjUNz5KORSMCpnl9vgE2eXPsCzGhqZg3tqQFTWnXRHmtD/T1iPwTvurKa35Z
HnzOU/hKJW3LXr9pVj6dlwKBgQCXVaEBm1Y7GbB5uEziIxiAzch0O9+FOxsgsVME
vN/mlynO21WRR1eAGUetPBGN/1Ih3FCabEix6Cro+vuwvILjZqULKrIkN0hXJ0kG
fUba9IL+fOCnZANItJ2ZKyvP7wfZNz6vgfCN/iY0rdJ7xlv4PhSGG3I9aDCE3Who
7h2rTwKBgQCQ5s0JdS/EDX5h7LPPqpIV1kqMmcqvBonZN4TySfqxKSlhasO3oGgC
rYkygiYGU8WdooavGNSmY4+sCBY002PZQ8EcSp+KySpYLeiVrJZTc+ehzk93t2to
b1p1mmLFCM6SVJAIQ+y9jt5GCSEka0BlVoNfLM06OLcC9j8iIGsjrw==
-----END RSA PRIVATE KEY-----

View File

@ -1,48 +0,0 @@
package main
import (
"fmt"
"net/http"
"time"
"github.com/labstack/echo"
"github.com/labstack/echo/engine"
"github.com/labstack/echo/engine/standard"
)
func request(c echo.Context) error {
req := c.Request().(*standard.Request).Request
format := "<pre><strong>Request Information</strong>\n\n<code>Protocol: %s\nHost: %s\nRemote Address: %s\nMethod: %s\nPath: %s\n</code></pre>"
return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path))
}
func stream(c echo.Context) error {
res := c.Response().(*standard.Response).ResponseWriter
gone := res.(http.CloseNotifier).CloseNotify()
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
res.WriteHeader(http.StatusOK)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
fmt.Fprint(res, "<pre><strong>Clock Stream</strong>\n\n<code>")
for {
fmt.Fprintf(res, "%v\n", time.Now())
res.(http.Flusher).Flush()
select {
case <-ticker.C:
case <-gone:
break
}
}
}
func main() {
e := echo.New()
e.GET("/request", request)
e.GET("/stream", stream)
e.Run(standard.WithConfig(engine.Config{
Address: ":1323",
TLSCertFile: "cert.pem",
TLSKeyFile: "key.pem",
}))
}

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<title>JSONP</title>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript">
var host_prefix = 'http://localhost:1323';
$(document).ready(function() {
// JSONP version - add 'callback=?' to the URL - fetch the JSONP response to the request
$("#jsonp-button").click(function(e) {
e.preventDefault();
// The only difference on the client end is the addition of 'callback=?' to the URL
var url = host_prefix + '/jsonp?callback=?';
$.getJSON(url, function(jsonp) {
console.log(jsonp);
$("#jsonp-response").html(JSON.stringify(jsonp, null, 2));
});
});
});
</script>
</head>
<body>
<div class="container" style="margin-top: 50px;">
<input type="button" class="btn btn-primary btn-lg" id="jsonp-button" value="Get JSONP response">
<p>
<pre id="jsonp-response"></pre>
</p>
</div>
</body>
</html>

View File

@ -1,35 +0,0 @@
package main
import (
"math/rand"
"net/http"
"time"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.Static("public"))
// JSONP
e.GET("/jsonp", func(c echo.Context) error {
callback := c.QueryParam("callback")
var content struct {
Response string `json:"response"`
Timestamp time.Time `json:"timestamp"`
Random int `json:"random"`
}
content.Response = "Sent via JSONP"
content.Timestamp = time.Now().UTC()
content.Random = rand.Intn(1000)
return c.JSONP(http.StatusOK, callback, &content)
})
// Start server
e.Run(standard.New(":1323"))
}

View File

@ -1,87 +0,0 @@
package main
import (
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
// jwtCustomClaims are custom claims extending default ones.
type jwtCustomClaims struct {
Name string `json:"name"`
Admin bool `json:"admin"`
jwt.StandardClaims
}
func login(c echo.Context) error {
username := c.FormValue("username")
password := c.FormValue("password")
if username == "jon" && password == "shhh!" {
// Set custom claims
claims := &jwtCustomClaims{
"Jon Snow",
true,
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
},
}
// Create token with claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Generate encoded token and send it as response.
t, err := token.SignedString([]byte("secret"))
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{
"token": t,
})
}
return echo.ErrUnauthorized
}
func accessible(c echo.Context) error {
return c.String(http.StatusOK, "Accessible")
}
func restricted(c echo.Context) error {
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(*jwtCustomClaims)
name := claims.Name
return c.String(http.StatusOK, "Welcome "+name+"!")
}
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Login route
e.POST("/login", login)
// Unauthenticated route
e.GET("/", accessible)
// Restricted group
r := e.Group("/restricted")
// Configure middleware with the custom claims type
config := middleware.JWTConfig{
Claims: &jwtCustomClaims{},
SigningKey: []byte("secret"),
}
r.Use(middleware.JWTWithConfig(config))
r.GET("", restricted)
e.Run(standard.New(":1323"))
}

View File

@ -1,70 +0,0 @@
package main
import (
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
func login(c echo.Context) error {
username := c.FormValue("username")
password := c.FormValue("password")
if username == "jon" && password == "shhh!" {
// Create token
token := jwt.New(jwt.SigningMethodHS256)
// Set claims
claims := token.Claims.(jwt.MapClaims)
claims["name"] = "Jon Snow"
claims["admin"] = true
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
// Generate encoded token and send it as response.
t, err := token.SignedString([]byte("secret"))
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{
"token": t,
})
}
return echo.ErrUnauthorized
}
func accessible(c echo.Context) error {
return c.String(http.StatusOK, "Accessible")
}
func restricted(c echo.Context) error {
user := c.Get("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
name := claims["name"].(string)
return c.String(http.StatusOK, "Welcome "+name+"!")
}
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Login route
e.POST("/login", login)
// Unauthenticated route
e.GET("/", accessible)
// Restricted group
r := e.Group("/restricted")
r.Use(middleware.JWT([]byte("secret")))
r.GET("", restricted)
e.Run(standard.New(":1323"))
}

View File

@ -1,83 +0,0 @@
package main
import (
"net/http"
"strconv"
"sync"
"time"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
)
type (
Stats struct {
Uptime time.Time `json:"uptime"`
RequestCount uint64 `json:"requestCount"`
Statuses map[string]int `json:"statuses"`
mutex sync.RWMutex
}
)
func NewStats() *Stats {
return &Stats{
Uptime: time.Now(),
Statuses: make(map[string]int),
}
}
// Process is the middleware function.
func (s *Stats) Process(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
c.Error(err)
}
s.mutex.Lock()
defer s.mutex.Unlock()
s.RequestCount++
status := strconv.Itoa(c.Response().Status())
s.Statuses[status]++
return nil
}
}
// Handle is the endpoint to get stats.
func (s *Stats) Handle(c echo.Context) error {
s.mutex.RLock()
defer s.mutex.RUnlock()
return c.JSON(http.StatusOK, s)
}
// ServerHeader middleware adds a `Server` header to the response.
func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderServer, "Echo/2.0")
return next(c)
}
}
func main() {
e := echo.New()
// Debug mode
e.SetDebug(true)
//-------------------
// Custom middleware
//-------------------
// Stats
s := NewStats()
e.Use(s.Process)
e.GET("/stats", s.Handle) // Endpoint to get stats
// Server header
e.Use(ServerHeader)
// Handler
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
// Start server
e.Run(standard.New(":1323"))
}

View File

@ -1,46 +0,0 @@
package main
import (
"net/http"
"time"
"encoding/json"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
)
type (
Geolocation struct {
Altitude float64
Latitude float64
Longitude float64
}
)
var (
locations = []Geolocation{
{-97, 37.819929, -122.478255},
{1899, 39.096849, -120.032351},
{2619, 37.865101, -119.538329},
{42, 33.812092, -117.918974},
{15, 37.77493, -122.419416},
}
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderContentType, echo.ApplicationJSON)
c.Response().WriteHeader(http.StatusOK)
for _, l := range locations {
if err := json.NewEncoder(c.Response()).Encode(l); err != nil {
return err
}
c.Response().(http.Flusher).Flush()
time.Sleep(1 * time.Second)
}
return nil
})
e.Run(standard.New(":1323"))
}

View File

@ -1,79 +0,0 @@
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
type (
Host struct {
Echo *echo.Echo
}
)
func main() {
// Hosts
hosts := make(map[string]*Host)
//-----
// API
//-----
api := echo.New()
api.Use(middleware.Logger())
api.Use(middleware.Recover())
hosts["api.localhost:1323"] = &Host{api}
api.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "API")
})
//------
// Blog
//------
blog := echo.New()
blog.Use(middleware.Logger())
blog.Use(middleware.Recover())
hosts["blog.localhost:1323"] = &Host{blog}
blog.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Blog")
})
//---------
// Website
//---------
site := echo.New()
site.Use(middleware.Logger())
site.Use(middleware.Recover())
hosts["localhost:1323"] = &Host{site}
site.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Website")
})
// Server
e := echo.New()
e.Any("/*", func(c echo.Context) (err error) {
req := c.Request()
res := c.Response()
host := hosts[req.Host()]
if host == nil {
err = echo.ErrNotFound
} else {
host.Echo.ServeHTTP(req, res)
}
return
})
e.Run(standard.New(":1323"))
}

View File

@ -1,7 +0,0 @@
/example/example
/example/example.exe
/rice/rice
/rice/rice.exe
*.rice-box.go
*.rice-box.syso

View File

@ -1,4 +0,0 @@
Geert-Johan Riemer <geertjohan@geertjohan.net>
Paul Maddox <paul.maddox@gmail.com>
Vincent Petithory <vincent.petithory@gmail.com>

View File

@ -1,22 +0,0 @@
Copyright (c) 2013, Geert-Johan Riemer
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.

View File

@ -1,151 +0,0 @@
## go.rice
[![Wercker](https://img.shields.io/wercker/ci/54c7af4dcc09f9963725bb25.svg?style=flat-square)](https://app.wercker.com/#applications/54c7af4dcc09f9963725bb25)
[![Godoc](https://img.shields.io/badge/godoc-go.rice-blue.svg?style=flat-square)](https://godoc.org/github.com/GeertJohan/go.rice)
go.rice is a [Go](http://golang.org) package that makes working with resources such as html,js,css,images and templates very easy. During development `go.rice` will load required files directly from disk. Upon deployment it is easy to add all resource files to a executable using the `rice` tool, without changing the source code for your package. go.rice provides several methods to add resources to a binary.
### What does it do?
The first thing go.rice does is finding the correct absolute path for your resource files. Say you are executing go binary in your home directory, but your `html-files` are located in `$GOPATH/src/yourApplication/html-files`. `go.rice` will lookup the correct path for that directory (relative to the location of yourApplication). The only thing you have to do is include the resources using `rice.FindBox("html-files")`.
This only works when the source is available to the machine executing the binary. Which is always the case when the binary was installed with `go get` or `go install`. It might occur that you wish to simply provide a binary, without source. The `rice` tool analyses source code and finds call's to `rice.FindBox(..)` and adds the required directories to the executable binary. There are several methods to add these resources. You can 'embed' by generating go source code, or append the resource to the executable as zip file. In both cases `go.rice` will detect the embedded or appended resources and load those, instead of looking up files from disk.
### Installation
Use `go get` to install the package the `rice` tool.
```
go get github.com/GeertJohan/go.rice
go get github.com/GeertJohan/go.rice/rice
```
### Package usage
Import the package: `import "github.com/GeertJohan/go.rice"`
**Serving a static content folder over HTTP with a rice Box**
```go
http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
http.ListenAndServe(":8080", nil)
```
**Service a static content folder over HTTP at a non-root location**
```go
box := rice.MustFindBox("cssfiles")
cssFileServer := http.StripPrefix("/css/", http.FileServer(box.HTTPBox()))
http.Handle("/css/", cssFileServer)
http.ListenAndServe(":8080", nil)
```
Note the *trailing slash* in `/css/` in both the call to
`http.StripPrefix` and `http.Handle`.
**Loading a template**
```go
// find a rice.Box
templateBox, err := rice.FindBox("example-templates")
if err != nil {
log.Fatal(err)
}
// get file contents as string
templateString, err := templateBox.String("message.tmpl")
if err != nil {
log.Fatal(err)
}
// parse and execute the template
tmplMessage, err := template.New("message").Parse(templateString)
if err != nil {
log.Fatal(err)
}
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
```
Never call `FindBox()` or `MustFindBox()` from an `init()` function, as the boxes might have not been loaded at that time.
### Tool usage
The `rice` tool lets you add the resources to a binary executable so the files are not loaded from the filesystem anymore. This creates a 'standalone' executable. There are several ways to add the resources to a binary, each has pro's and con's but all will work without requiring changes to the way you load the resources.
#### embed-go
**Embed resources by generating Go source code**
This method must be executed before building. It generates a single Go source file called *rice-box.go* for each package, that is compiled by the go compiler into the binary.
The downside with this option is that the generated go source files can become very large, which will slow down compilation and require lots of memory to compile.
Execute the following commands:
```
rice embed-go
go build
```
*A Note on Symbolic Links*: `embed-go` uses the `os.Walk` function
from the standard library. The `os.Walk` function does **not** follow
symbolic links. So, when creating a box, be aware that any symbolic
links inside your box's directory will not be followed. **However**,
if the box itself is a symbolic link, its actual location will be
resolved first and then walked. In summary, if your box location is a
symbolic link, it will be followed but none of the symbolic links in
the box will be followed.
#### embed-syso
**Embed resources by generating a coff .syso file and some .go source code**
** This method is experimental and should not be used for production systems just yet **
This method must be executed before building. It generates a COFF .syso file and Go source file that are compiled by the go compiler into the binary.
Execute the following commands:
```
rice embed-syso
go build
```
#### append
**Append resources to executable as zip file**
This method changes an already built executable. It appends the resources as zip file to the binary. It makes compilation a lot faster and can be used with large resource files.
Downsides for appending are that it requires `zip` to be installed and does not provide a working Seek method.
Run the following commands to create a standalone executable.
```
go build -o example
rice append --exec example
```
**Note: requires zip command to be installed**
On windows, install zip from http://gnuwin32.sourceforge.net/packages/zip.htm or cygwin/msys toolsets.
#### Help information
Run `rice -h` for information about all options.
You can run the -h option for each sub-command, e.g. `rice append -h`.
### Order of precedence
When opening a new box, the rice package tries to locate the resources in the following order:
- embedded in generated go source
- appended as zip
- 'live' from filesystem
### License
This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license].
### TODO & Development
This package is not completed yet. Though it already provides working embedding, some important featuers are still missing.
- implement Readdir() correctly on virtualDir
- in-code TODO's
- find boxes in imported packages
Less important stuff:
- idea, os/arch dependent embeds. rice checks if embedding file has _os_arch or build flags. If box is not requested by file without buildflags, then the buildflags are applied to the embed file.
### Package documentation
You will find package documentation at [godoc.org/github.com/GeertJohan/go.rice][godoc].
[license]: https://github.com/GeertJohan/go.rice/blob/master/LICENSE
[godoc]: http://godoc.org/github.com/GeertJohan/go.rice

View File

@ -1,138 +0,0 @@
package rice
import (
"archive/zip"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/daaku/go.zipexe"
"github.com/kardianos/osext"
)
// appendedBox defines an appended box
type appendedBox struct {
Name string // box name
Files map[string]*appendedFile // appended files (*zip.File) by full path
}
type appendedFile struct {
zipFile *zip.File
dir bool
dirInfo *appendedDirInfo
children []*appendedFile
content []byte
}
// appendedBoxes is a public register of appendes boxes
var appendedBoxes = make(map[string]*appendedBox)
func init() {
// find if exec is appended
thisFile, err := osext.Executable()
if err != nil {
return // not appended or cant find self executable
}
closer, rd, err := zipexe.OpenCloser(thisFile)
if err != nil {
return // not appended
}
defer closer.Close()
for _, f := range rd.File {
// get box and file name from f.Name
fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
boxName := fileParts[0]
var fileName string
if len(fileParts) > 1 {
fileName = fileParts[1]
}
// find box or create new one if doesn't exist
box := appendedBoxes[boxName]
if box == nil {
box = &appendedBox{
Name: boxName,
Files: make(map[string]*appendedFile),
}
appendedBoxes[boxName] = box
}
// create and add file to box
af := &appendedFile{
zipFile: f,
}
if f.Comment == "dir" {
af.dir = true
af.dirInfo = &appendedDirInfo{
name: filepath.Base(af.zipFile.Name),
//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
time: time.Now(),
}
} else {
// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
// make a new byteslice
af.content = make([]byte, af.zipFile.FileInfo().Size())
// ignore reading empty files from zip (empty file still is a valid file to be read though!)
if len(af.content) > 0 {
// open io.ReadCloser
rc, err := af.zipFile.Open()
if err != nil {
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
} else {
_, err = rc.Read(af.content)
rc.Close()
if err != nil {
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
}
}
}
}
// add appendedFile to box file list
box.Files[fileName] = af
// add to parent dir (if any)
dirName := filepath.Dir(fileName)
if dirName == "." {
dirName = ""
}
if fileName != "" { // don't make box root dir a child of itself
if dir := box.Files[dirName]; dir != nil {
dir.children = append(dir.children, af)
}
}
}
}
// implements os.FileInfo.
// used for Readdir()
type appendedDirInfo struct {
name string
time time.Time
}
func (adi *appendedDirInfo) Name() string {
return adi.name
}
func (adi *appendedDirInfo) Size() int64 {
return 0
}
func (adi *appendedDirInfo) Mode() os.FileMode {
return os.ModeDir
}
func (adi *appendedDirInfo) ModTime() time.Time {
return adi.time
}
func (adi *appendedDirInfo) IsDir() bool {
return true
}
func (adi *appendedDirInfo) Sys() interface{} {
return nil
}

View File

@ -1,337 +0,0 @@
package rice
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/GeertJohan/go.rice/embedded"
)
// Box abstracts a directory for resources/files.
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
type Box struct {
name string
absolutePath string
embed *embedded.EmbeddedBox
appendd *appendedBox
}
var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS}
func findBox(name string, order []LocateMethod) (*Box, error) {
b := &Box{name: name}
// no support for absolute paths since gopath can be different on different machines.
// therefore, required box must be located relative to package requiring it.
if filepath.IsAbs(name) {
return nil, errors.New("given name/path is absolute")
}
var err error
for _, method := range order {
switch method {
case LocateEmbedded:
if embed := embedded.EmbeddedBoxes[name]; embed != nil {
b.embed = embed
return b, nil
}
case LocateAppended:
appendedBoxName := strings.Replace(name, `/`, `-`, -1)
if appendd := appendedBoxes[appendedBoxName]; appendd != nil {
b.appendd = appendd
return b, nil
}
case LocateFS:
// resolve absolute directory path
err := b.resolveAbsolutePathFromCaller()
if err != nil {
continue
}
// check if absolutePath exists on filesystem
info, err := os.Stat(b.absolutePath)
if err != nil {
continue
}
// check if absolutePath is actually a directory
if !info.IsDir() {
err = errors.New("given name/path is not a directory")
continue
}
return b, nil
case LocateWorkingDirectory:
// resolve absolute directory path
err := b.resolveAbsolutePathFromWorkingDirectory()
if err != nil {
continue
}
// check if absolutePath exists on filesystem
info, err := os.Stat(b.absolutePath)
if err != nil {
continue
}
// check if absolutePath is actually a directory
if !info.IsDir() {
err = errors.New("given name/path is not a directory")
continue
}
return b, nil
}
}
if err == nil {
err = fmt.Errorf("could not locate box %q", name)
}
return nil, err
}
// FindBox returns a Box instance for given name.
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
// When the given name is absolute, it's absolute. derp.
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
func FindBox(name string) (*Box, error) {
return findBox(name, defaultLocateOrder)
}
// MustFindBox returns a Box instance for given name, like FindBox does.
// It does not return an error, instead it panics when an error occurs.
func MustFindBox(name string) *Box {
box, err := findBox(name, defaultLocateOrder)
if err != nil {
panic(err)
}
return box
}
// This is injected as a mutable function literal so that we can mock it out in
// tests and return a fixed test file.
var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) {
_, callingGoFile, _, ok := runtime.Caller(nStackFrames)
if !ok {
return "", errors.New("couldn't find caller on stack")
}
// resolve to proper path
pkgDir := filepath.Dir(callingGoFile)
// fix for go cover
const coverPath = "_test/_obj_test"
if !filepath.IsAbs(pkgDir) {
if i := strings.Index(pkgDir, coverPath); i >= 0 {
pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath
pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute
}
}
return filepath.Join(pkgDir, name), nil
}
func (b *Box) resolveAbsolutePathFromCaller() error {
path, err := resolveAbsolutePathFromCaller(b.name, 4)
if err != nil {
return err
}
b.absolutePath = path
return nil
}
func (b *Box) resolveAbsolutePathFromWorkingDirectory() error {
path, err := os.Getwd()
if err != nil {
return err
}
b.absolutePath = filepath.Join(path, b.name)
return nil
}
// IsEmbedded indicates wether this box was embedded into the application
func (b *Box) IsEmbedded() bool {
return b.embed != nil
}
// IsAppended indicates wether this box was appended to the application
func (b *Box) IsAppended() bool {
return b.appendd != nil
}
// Time returns how actual the box is.
// When the box is embedded, it's value is saved in the embedding code.
// When the box is live, this methods returns time.Now()
func (b *Box) Time() time.Time {
if b.IsEmbedded() {
return b.embed.Time
}
//++ TODO: return time for appended box
return time.Now()
}
// Open opens a File from the box
// If there is an error, it will be of type *os.PathError.
func (b *Box) Open(name string) (*File, error) {
if Debug {
fmt.Printf("Open(%s)\n", name)
}
if b.IsEmbedded() {
if Debug {
fmt.Println("Box is embedded")
}
// trim prefix (paths are relative to box)
name = strings.TrimLeft(name, "/")
if Debug {
fmt.Printf("Trying %s\n", name)
}
// search for file
ef := b.embed.Files[name]
if ef == nil {
if Debug {
fmt.Println("Didn't find file in embed")
}
// file not found, try dir
ed := b.embed.Dirs[name]
if ed == nil {
if Debug {
fmt.Println("Didn't find dir in embed")
}
// dir not found, error out
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
if Debug {
fmt.Println("Found dir. Returning virtual dir")
}
vd := newVirtualDir(ed)
return &File{virtualD: vd}, nil
}
// box is embedded
if Debug {
fmt.Println("Found file. Returning virtual file")
}
vf := newVirtualFile(ef)
return &File{virtualF: vf}, nil
}
if b.IsAppended() {
// trim prefix (paths are relative to box)
name = strings.TrimLeft(name, "/")
// search for file
appendedFile := b.appendd.Files[name]
if appendedFile == nil {
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
// create new file
f := &File{
appendedF: appendedFile,
}
// if this file is a directory, we want to be able to read and seek
if !appendedFile.dir {
// looks like malformed data in zip, error now
if appendedFile.content == nil {
return nil, &os.PathError{
Op: "open",
Path: "name",
Err: errors.New("error reading data from zip file"),
}
}
// create new bytes.Reader
f.appendedFileReader = bytes.NewReader(appendedFile.content)
}
// all done
return f, nil
}
// perform os open
if Debug {
fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
}
file, err := os.Open(filepath.Join(b.absolutePath, name))
if err != nil {
return nil, err
}
return &File{realF: file}, nil
}
// Bytes returns the content of the file with given name as []byte.
func (b *Box) Bytes(name string) ([]byte, error) {
file, err := b.Open(name)
if err != nil {
return nil, err
}
defer file.Close()
content, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return content, nil
}
// MustBytes returns the content of the file with given name as []byte.
// panic's on error.
func (b *Box) MustBytes(name string) []byte {
bts, err := b.Bytes(name)
if err != nil {
panic(err)
}
return bts
}
// String returns the content of the file with given name as string.
func (b *Box) String(name string) (string, error) {
// check if box is embedded, optimized fast path
if b.IsEmbedded() {
// find file in embed
ef := b.embed.Files[name]
if ef == nil {
return "", os.ErrNotExist
}
// return as string
return ef.Content, nil
}
bts, err := b.Bytes(name)
if err != nil {
return "", err
}
return string(bts), nil
}
// MustString returns the content of the file with given name as string.
// panic's on error.
func (b *Box) MustString(name string) string {
str, err := b.String(name)
if err != nil {
panic(err)
}
return str
}
// Name returns the name of the box
func (b *Box) Name() string {
return b.name
}

View File

@ -1,28 +0,0 @@
package rice
// LocateMethod defines how a box is located.
type LocateMethod int
const (
LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path.
LocateAppended // Locate boxes appended to the executable.
LocateEmbedded // Locate embedded boxes.
LocateWorkingDirectory // Locate on the binary working directory
)
// Config allows customizing the box lookup behavior.
type Config struct {
// LocateOrder defines the priority order that boxes are searched for. By
// default, the package global FindBox searches for embedded boxes first,
// then appended boxes, and then finally boxes on the filesystem. That
// search order may be customized by provided the ordered list here. Leaving
// out a particular method will omit that from the search space. For
// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search
// the filesystem for boxes.
LocateOrder []LocateMethod
}
// FindBox searches for boxes using the LocateOrder of the config.
func (c *Config) FindBox(boxName string) (*Box, error) {
return findBox(boxName, c.LocateOrder)
}

View File

@ -1,136 +0,0 @@
package rice
import (
"fmt"
"io/ioutil"
"testing"
"github.com/GeertJohan/go.rice/embedded"
)
// For all test code in this package, define a set of test boxes.
var eb1 *embedded.EmbeddedBox
var ab1, ab2 *appendedBox
var fsb1, fsb2, fsb3 string // paths to filesystem boxes
func init() {
var err error
// Box1 exists in all three locations.
eb1 = &embedded.EmbeddedBox{Name: "box1"}
embedded.RegisterEmbeddedBox(eb1.Name, eb1)
ab1 = &appendedBox{Name: "box1"}
appendedBoxes["box1"] = ab1
fsb1, err = ioutil.TempDir("", "box1")
if err != nil {
panic(err)
}
// Box2 exists in only appended and FS.
ab2 = &appendedBox{Name: "box2"}
appendedBoxes["box2"] = ab2
fsb2, err = ioutil.TempDir("", "box2")
if err != nil {
panic(err)
}
// Box3 exists only on disk.
fsb3, err = ioutil.TempDir("", "box3")
if err != nil {
panic(err)
}
// Also, replace the default filesystem lookup path to directly support the
// on-disk temp directories.
resolveAbsolutePathFromCaller = func(name string, n int) (string, error) {
if name == "box1" {
return fsb1, nil
} else if name == "box2" {
return fsb2, nil
} else if name == "box3" {
return fsb3, nil
}
return "", fmt.Errorf("Unknown box name: %q", name)
}
}
func TestDefaultLookupOrder(t *testing.T) {
// Box1 exists in all three, so the default order should find the embedded.
b, err := FindBox("box1")
if err != nil {
t.Fatalf("Expected to find box1, got error: %v", err)
}
if b.embed != eb1 {
t.Fatalf("Expected to find embedded box, but got %#v", b)
}
// Box2 exists in appended and FS, so find the appended.
b2, err := FindBox("box2")
if err != nil {
t.Fatalf("Expected to find box2, got error: %v", err)
}
if b2.appendd != ab2 {
t.Fatalf("Expected to find appended box, but got %#v", b2)
}
// Box3 exists only on FS, so find it there.
b3, err := FindBox("box3")
if err != nil {
t.Fatalf("Expected to find box3, got error: %v", err)
}
if b3.absolutePath != fsb3 {
t.Fatalf("Expected to find FS box, but got %#v", b3)
}
}
func TestConfigLocateOrder(t *testing.T) {
cfg := Config{LocateOrder: []LocateMethod{LocateFS, LocateAppended, LocateEmbedded}}
fsb := []string{fsb1, fsb2, fsb3}
// All 3 boxes have a FS backend, so we should always find that.
for i, boxName := range []string{"box1", "box2", "box3"} {
b, err := cfg.FindBox(boxName)
if err != nil {
t.Fatalf("Expected to find %q, got error: %v", boxName, err)
}
if b.absolutePath != fsb[i] {
t.Fatalf("Expected to find FS box, but got %#v", b)
}
}
cfg.LocateOrder = []LocateMethod{LocateAppended, LocateFS, LocateEmbedded}
{
b, err := cfg.FindBox("box3")
if err != nil {
t.Fatalf("Expected to find box3, got error: %v", err)
}
if b.absolutePath != fsb3 {
t.Fatalf("Expected to find FS box, but got %#v", b)
}
}
{
b, err := cfg.FindBox("box2")
if err != nil {
t.Fatalf("Expected to find box2, got error: %v", err)
}
if b.appendd != ab2 {
t.Fatalf("Expected to find appended box, but got %#v", b)
}
}
// What if we don't list all the locate methods?
cfg.LocateOrder = []LocateMethod{LocateEmbedded}
{
b, err := cfg.FindBox("box2")
if err == nil {
t.Fatalf("Expected not to find box2, but something was found: %#v", b)
}
}
{
b, err := cfg.FindBox("box1")
if err != nil {
t.Fatalf("Expected to find box2, got error: %v", err)
}
if b.embed != eb1 {
t.Fatalf("Expected to find embedded box, but got %#v", b)
}
}
}

View File

@ -1,4 +0,0 @@
package rice
// Debug can be set to true to enable debugging.
var Debug = false

View File

@ -1,90 +0,0 @@
package rice
import (
"os"
"time"
"github.com/GeertJohan/go.rice/embedded"
)
// re-type to make exported methods invisible to user (godoc)
// they're not required for the user
// embeddedDirInfo implements os.FileInfo
type embeddedDirInfo embedded.EmbeddedDir
// Name returns the base name of the directory
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) Name() string {
return ed.Filename
}
// Size always returns 0
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) Size() int64 {
return 0
}
// Mode returns the file mode bits
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) Mode() os.FileMode {
return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x
}
// ModTime returns the modification time
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) ModTime() time.Time {
return ed.DirModTime
}
// IsDir returns the abbreviation for Mode().IsDir() (always true)
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) IsDir() bool {
return true
}
// Sys returns the underlying data source (always nil)
// (implementing os.FileInfo)
func (ed *embeddedDirInfo) Sys() interface{} {
return nil
}
// re-type to make exported methods invisible to user (godoc)
// they're not required for the user
// embeddedFileInfo implements os.FileInfo
type embeddedFileInfo embedded.EmbeddedFile
// Name returns the base name of the file
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) Name() string {
return ef.Filename
}
// Size returns the length in bytes for regular files; system-dependent for others
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) Size() int64 {
return int64(len(ef.Content))
}
// Mode returns the file mode bits
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) Mode() os.FileMode {
return os.FileMode(0555) // r-xr-xr-x
}
// ModTime returns the modification time
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) ModTime() time.Time {
return ef.FileModTime
}
// IsDir returns the abbreviation for Mode().IsDir() (always false)
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) IsDir() bool {
return false
}
// Sys returns the underlying data source (always nil)
// (implementing os.FileInfo)
func (ef *embeddedFileInfo) Sys() interface{} {
return nil
}

View File

@ -1,80 +0,0 @@
// Package embedded defines embedded data types that are shared between the go.rice package and generated code.
package embedded
import (
"fmt"
"path/filepath"
"strings"
"time"
)
const (
EmbedTypeGo = 0
EmbedTypeSyso = 1
)
// EmbeddedBox defines an embedded box
type EmbeddedBox struct {
Name string // box name
Time time.Time // embed time
EmbedType int // kind of embedding
Files map[string]*EmbeddedFile // ALL embedded files by full path
Dirs map[string]*EmbeddedDir // ALL embedded dirs by full path
}
// Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's
func (e *EmbeddedBox) Link() {
for path, ed := range e.Dirs {
fmt.Println(path)
ed.ChildDirs = make([]*EmbeddedDir, 0)
ed.ChildFiles = make([]*EmbeddedFile, 0)
}
for path, ed := range e.Dirs {
parentDirpath, _ := filepath.Split(path)
if strings.HasSuffix(parentDirpath, "/") {
parentDirpath = parentDirpath[:len(parentDirpath)-1]
}
parentDir := e.Dirs[parentDirpath]
if parentDir == nil {
panic("parentDir `" + parentDirpath + "` is missing in embedded box")
}
parentDir.ChildDirs = append(parentDir.ChildDirs, ed)
}
for path, ef := range e.Files {
dirpath, _ := filepath.Split(path)
if strings.HasSuffix(dirpath, "/") {
dirpath = dirpath[:len(dirpath)-1]
}
dir := e.Dirs[dirpath]
if dir == nil {
panic("dir `" + dirpath + "` is missing in embedded box")
}
dir.ChildFiles = append(dir.ChildFiles, ef)
}
}
// EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
type EmbeddedDir struct {
Filename string
DirModTime time.Time
ChildDirs []*EmbeddedDir // direct childs, as returned by virtualDir.Readdir()
ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir()
}
// EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file
type EmbeddedFile struct {
Filename string // filename
FileModTime time.Time
Content string
}
// EmbeddedBoxes is a public register of embedded boxes
var EmbeddedBoxes = make(map[string]*EmbeddedBox)
// RegisterEmbeddedBox registers an EmbeddedBox
func RegisterEmbeddedBox(name string, box *EmbeddedBox) {
if _, exists := EmbeddedBoxes[name]; exists {
panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name))
}
EmbeddedBoxes[name] = box
}

View File

@ -1,2 +0,0 @@
test content
break

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1 +0,0 @@
I have a message for you: {{.Message}}

View File

@ -1,69 +0,0 @@
package main
import (
"encoding/hex"
"fmt"
"log"
"net/http"
"os"
"text/template"
"github.com/GeertJohan/go.rice"
"github.com/davecgh/go-spew/spew"
)
func main() {
conf := rice.Config{
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
}
box, err := conf.FindBox("example-files")
if err != nil {
log.Fatalf("error opening rice.Box: %s\n", err)
}
// spew.Dump(box)
contentString, err := box.String("file.txt")
if err != nil {
log.Fatalf("could not read file contents as string: %s\n", err)
}
log.Printf("Read some file contents as string:\n%s\n", contentString)
contentBytes, err := box.Bytes("file.txt")
if err != nil {
log.Fatalf("could not read file contents as byteSlice: %s\n", err)
}
log.Printf("Read some file contents as byteSlice:\n%s\n", hex.Dump(contentBytes))
file, err := box.Open("file.txt")
if err != nil {
log.Fatalf("could not open file: %s\n", err)
}
spew.Dump(file)
// find/create a rice.Box
templateBox, err := rice.FindBox("example-templates")
if err != nil {
log.Fatal(err)
}
// get file contents as string
templateString, err := templateBox.String("message.tmpl")
if err != nil {
log.Fatal(err)
}
// parse and execute the template
tmplMessage, err := template.New("message").Parse(templateString)
if err != nil {
log.Fatal(err)
}
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
http.Handle("/", http.FileServer(box.HTTPBox()))
go func() {
fmt.Println("Serving files on :8080, press ctrl-C to exit")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatalf("error serving files: %v", err)
}
}()
select {}
}

View File

@ -1,144 +0,0 @@
package rice
import (
"bytes"
"errors"
"os"
"path/filepath"
)
// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
type File struct {
// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir
// real file on disk
realF *os.File
// when embedded (go)
virtualF *virtualFile
virtualD *virtualDir
// when appended (zip)
appendedF *appendedFile
appendedFileReader *bytes.Reader
// TODO: is appendedFileReader subject of races? Might need a lock here..
}
// Close is like (*os.File).Close()
// Visit http://golang.org/pkg/os/#File.Close for more information
func (f *File) Close() error {
if f.appendedF != nil {
if f.appendedFileReader == nil {
return errors.New("already closed")
}
f.appendedFileReader = nil
return nil
}
if f.virtualF != nil {
return f.virtualF.close()
}
if f.virtualD != nil {
return f.virtualD.close()
}
return f.realF.Close()
}
// Stat is like (*os.File).Stat()
// Visit http://golang.org/pkg/os/#File.Stat for more information
func (f *File) Stat() (os.FileInfo, error) {
if f.appendedF != nil {
if f.appendedF.dir {
return f.appendedF.dirInfo, nil
}
if f.appendedFileReader == nil {
return nil, errors.New("file is closed")
}
return f.appendedF.zipFile.FileInfo(), nil
}
if f.virtualF != nil {
return f.virtualF.stat()
}
if f.virtualD != nil {
return f.virtualD.stat()
}
return f.realF.Stat()
}
// Readdir is like (*os.File).Readdir()
// Visit http://golang.org/pkg/os/#File.Readdir for more information
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
if f.appendedF != nil {
if f.appendedF.dir {
fi := make([]os.FileInfo, 0, len(f.appendedF.children))
for _, childAppendedFile := range f.appendedF.children {
if childAppendedFile.dir {
fi = append(fi, childAppendedFile.dirInfo)
} else {
fi = append(fi, childAppendedFile.zipFile.FileInfo())
}
}
return fi, nil
}
//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
return nil, os.ErrInvalid
}
if f.virtualF != nil {
return f.virtualF.readdir(count)
}
if f.virtualD != nil {
return f.virtualD.readdir(count)
}
return f.realF.Readdir(count)
}
// Read is like (*os.File).Read()
// Visit http://golang.org/pkg/os/#File.Read for more information
func (f *File) Read(bts []byte) (int, error) {
if f.appendedF != nil {
if f.appendedFileReader == nil {
return 0, &os.PathError{
Op: "read",
Path: filepath.Base(f.appendedF.zipFile.Name),
Err: errors.New("file is closed"),
}
}
if f.appendedF.dir {
return 0, &os.PathError{
Op: "read",
Path: filepath.Base(f.appendedF.zipFile.Name),
Err: errors.New("is a directory"),
}
}
return f.appendedFileReader.Read(bts)
}
if f.virtualF != nil {
return f.virtualF.read(bts)
}
if f.virtualD != nil {
return f.virtualD.read(bts)
}
return f.realF.Read(bts)
}
// Seek is like (*os.File).Seek()
// Visit http://golang.org/pkg/os/#File.Seek for more information
func (f *File) Seek(offset int64, whence int) (int64, error) {
if f.appendedF != nil {
if f.appendedFileReader == nil {
return 0, &os.PathError{
Op: "seek",
Path: filepath.Base(f.appendedF.zipFile.Name),
Err: errors.New("file is closed"),
}
}
return f.appendedFileReader.Seek(offset, whence)
}
if f.virtualF != nil {
return f.virtualF.seek(offset, whence)
}
if f.virtualD != nil {
return f.virtualD.seek(offset, whence)
}
return f.realF.Seek(offset, whence)
}

View File

@ -1,21 +0,0 @@
package rice
import (
"net/http"
)
// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer.
// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
type HTTPBox struct {
*Box
}
// HTTPBox creates a new HTTPBox from an existing Box
func (b *Box) HTTPBox() *HTTPBox {
return &HTTPBox{b}
}
// Open returns a File using the http.File interface
func (hb *HTTPBox) Open(name string) (http.File, error) {
return hb.Box.Open(name)
}

View File

@ -1,168 +0,0 @@
package main
import (
"archive/zip"
"fmt"
"go/build"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/daaku/go.zipexe"
)
func operationAppend(pkgs []*build.Package) {
if runtime.GOOS == "windows" {
_, err := exec.LookPath("zip")
if err != nil {
fmt.Println("#### WARNING ! ####")
fmt.Println("`rice append` is known not to work under windows because the `zip` command is not available. Please let me know if you got this to work (and how).")
}
}
// MARKED FOR DELETION
// This is actually not required, the append command now has the option --exec required.
// // check if package is a command
// if !pkg.IsCommand() {
// fmt.Println("Error: can not append to non-main package. Please follow instructions at github.com/GeertJohan/go.rice")
// os.Exit(1)
// }
// create tmp zipfile
tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10)))
verbosef("Will create tmp zipfile: %s\n", tmpZipfileName)
tmpZipfile, err := os.Create(tmpZipfileName)
if err != nil {
fmt.Printf("Error creating tmp zipfile: %s\n", err)
os.Exit(1)
}
// find abs path for binary file
binfileName, err := filepath.Abs(flags.Append.Executable)
if err != nil {
fmt.Printf("Error finding absolute path for executable to append: %s\n", err)
os.Exit(1)
}
verbosef("Will append to file: %s\n", binfileName)
// check that command doesn't already have zip appended
if rd, _ := zipexe.Open(binfileName); rd != nil {
fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName)
os.Exit(1)
}
// open binfile
binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Printf("Error: unable to open executable file: %s\n", err)
os.Exit(1)
}
// create zip.Writer
zipWriter := zip.NewWriter(tmpZipfile)
for _, pkg := range pkgs {
// find boxes for this command
boxMap := findBoxes(pkg)
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
if len(boxMap) == 0 {
fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath)
continue
}
verbosef("\n")
for boxname := range boxMap {
appendedBoxName := strings.Replace(boxname, `/`, `-`, -1)
// walk box path's and insert files
boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname))
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
if info == nil {
fmt.Printf("Error: box \"%s\" not found on disk\n", path)
os.Exit(1)
}
// create zipFilename
zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath))
// write directories as empty file with comment "dir"
if info.IsDir() {
_, err := zipWriter.CreateHeader(&zip.FileHeader{
Name: zipFileName,
Comment: "dir",
})
if err != nil {
fmt.Printf("Error creating dir in tmp zip: %s\n", err)
os.Exit(1)
}
return nil
}
// create zipFileWriter
zipFileHeader, err := zip.FileInfoHeader(info)
if err != nil {
fmt.Printf("Error creating zip FileHeader: %v\n", err)
os.Exit(1)
}
zipFileHeader.Name = zipFileName
zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader)
if err != nil {
fmt.Printf("Error creating file in tmp zip: %s\n", err)
os.Exit(1)
}
srcFile, err := os.Open(path)
if err != nil {
fmt.Printf("Error opening file to append: %s\n", err)
os.Exit(1)
}
_, err = io.Copy(zipFileWriter, srcFile)
if err != nil {
fmt.Printf("Error copying file contents to zip: %s\n", err)
os.Exit(1)
}
srcFile.Close()
return nil
})
}
}
err = zipWriter.Close()
if err != nil {
fmt.Printf("Error closing tmp zipfile: %s\n", err)
os.Exit(1)
}
err = tmpZipfile.Sync()
if err != nil {
fmt.Printf("Error syncing tmp zipfile: %s\n", err)
os.Exit(1)
}
_, err = tmpZipfile.Seek(0, 0)
if err != nil {
fmt.Printf("Error seeking tmp zipfile: %s\n", err)
os.Exit(1)
}
_, err = binfile.Seek(0, 2)
if err != nil {
fmt.Printf("Error seeking bin file: %s\n", err)
os.Exit(1)
}
_, err = io.Copy(binfile, tmpZipfile)
if err != nil {
fmt.Printf("Error appending zipfile to executable: %s\n", err)
os.Exit(1)
}
zipA := exec.Command("zip", "-A", binfileName)
err = zipA.Run()
if err != nil {
fmt.Printf("Error setting zip offset: %s\n", err)
os.Exit(1)
}
}

View File

@ -1,33 +0,0 @@
package main
import (
"fmt"
"go/build"
"os"
"path/filepath"
"strings"
)
func operationClean(pkg *build.Package) {
filepath.Walk(pkg.Dir, func(filename string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("error walking pkg dir to clean files: %v\n", err)
os.Exit(1)
}
if info.IsDir() {
return nil
}
verbosef("checking file '%s'\n", filename)
if filepath.Base(filename) == "rice-box.go" ||
strings.HasSuffix(filename, ".rice-box.go") ||
strings.HasSuffix(filename, ".rice-box.syso") {
err := os.Remove(filename)
if err != nil {
fmt.Printf("error removing file (%s): %s\n", filename, err)
os.Exit(-1)
}
verbosef("removed file '%s'\n", filename)
}
return nil
})
}

View File

@ -1,158 +0,0 @@
package main
import (
"bytes"
"fmt"
"go/build"
"go/format"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
const boxFilename = "rice-box.go"
func operationEmbedGo(pkg *build.Package) {
boxMap := findBoxes(pkg)
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
if len(boxMap) == 0 {
fmt.Println("no calls to rice.FindBox() found")
return
}
verbosef("\n")
var boxes []*boxDataType
for boxname := range boxMap {
// find path and filename for this box
boxPath := filepath.Join(pkg.Dir, boxname)
// Check to see if the path for the box is a symbolic link. If so, simply
// box what the symbolic link points to. Note: the filepath.Walk function
// will NOT follow any nested symbolic links. This only handles the case
// where the root of the box is a symbolic link.
symPath, serr := os.Readlink(boxPath)
if serr == nil {
boxPath = symPath
}
// verbose info
verbosef("embedding box '%s' to '%s'\n", boxname, boxFilename)
// read box metadata
boxInfo, ierr := os.Stat(boxPath)
if ierr != nil {
fmt.Printf("Error: unable to access box at %s\n", boxPath)
os.Exit(1)
}
// create box datastructure (used by template)
box := &boxDataType{
BoxName: boxname,
UnixNow: boxInfo.ModTime().Unix(),
Files: make([]*fileDataType, 0),
Dirs: make(map[string]*dirDataType),
}
if !boxInfo.IsDir() {
fmt.Printf("Error: Box %s must point to a directory but points to %s instead\n",
boxname, boxPath)
os.Exit(1)
}
// fill box datastructure with file data
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("error walking box: %s\n", err)
os.Exit(1)
}
filename := strings.TrimPrefix(path, boxPath)
filename = strings.Replace(filename, "\\", "/", -1)
filename = strings.TrimPrefix(filename, "/")
if info.IsDir() {
dirData := &dirDataType{
Identifier: "dir" + nextIdentifier(),
FileName: filename,
ModTime: info.ModTime().Unix(),
ChildFiles: make([]*fileDataType, 0),
ChildDirs: make([]*dirDataType, 0),
}
verbosef("\tincludes dir: '%s'\n", dirData.FileName)
box.Dirs[dirData.FileName] = dirData
// add tree entry (skip for root, it'll create a recursion)
if dirData.FileName != "" {
pathParts := strings.Split(dirData.FileName, "/")
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
parentDir.ChildDirs = append(parentDir.ChildDirs, dirData)
}
} else {
fileData := &fileDataType{
Identifier: "file" + nextIdentifier(),
FileName: filename,
ModTime: info.ModTime().Unix(),
}
verbosef("\tincludes file: '%s'\n", fileData.FileName)
fileData.Content, err = ioutil.ReadFile(path)
if err != nil {
fmt.Printf("error reading file content while walking box: %s\n", err)
os.Exit(1)
}
box.Files = append(box.Files, fileData)
// add tree entry
pathParts := strings.Split(fileData.FileName, "/")
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
if parentDir == nil {
fmt.Printf("Error: parent of %s is not within the box\n", path)
os.Exit(1)
}
parentDir.ChildFiles = append(parentDir.ChildFiles, fileData)
}
return nil
})
boxes = append(boxes, box)
}
embedSourceUnformated := bytes.NewBuffer(make([]byte, 0))
// execute template to buffer
err := tmplEmbeddedBox.Execute(
embedSourceUnformated,
embedFileDataType{pkg.Name, boxes},
)
if err != nil {
log.Printf("error writing embedded box to file (template execute): %s\n", err)
os.Exit(1)
}
// format the source code
embedSource, err := format.Source(embedSourceUnformated.Bytes())
if err != nil {
log.Printf("error formatting embedSource: %s\n", err)
os.Exit(1)
}
// create go file for box
boxFile, err := os.Create(filepath.Join(pkg.Dir, boxFilename))
if err != nil {
log.Printf("error creating embedded box file: %s\n", err)
os.Exit(1)
}
defer boxFile.Close()
// write source to file
_, err = io.Copy(boxFile, bytes.NewBuffer(embedSource))
if err != nil {
log.Printf("error writing embedSource to file: %s\n", err)
os.Exit(1)
}
}

View File

@ -1,204 +0,0 @@
package main
import (
"bytes"
"encoding/gob"
"fmt"
"go/build"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"
"github.com/GeertJohan/go.rice/embedded"
"github.com/akavel/rsrc/coff"
)
type sizedReader struct {
*bytes.Reader
}
func (s sizedReader) Size() int64 {
return int64(s.Len())
}
var tmplEmbeddedSysoHelper *template.Template
func init() {
var err error
tmplEmbeddedSysoHelper, err = template.New("embeddedSysoHelper").Parse(`package {{.Package}}
// ############# GENERATED CODE #####################
// ## This file was generated by the rice tool.
// ## Do not edit unless you know what you're doing.
// ##################################################
// extern char _bricebox_{{.Symname}}[], _ericebox_{{.Symname}};
// int get_{{.Symname}}_length() {
// return &_ericebox_{{.Symname}} - _bricebox_{{.Symname}};
// }
import "C"
import (
"bytes"
"encoding/gob"
"github.com/GeertJohan/go.rice/embedded"
"unsafe"
)
func init() {
ptr := unsafe.Pointer(&C._bricebox_{{.Symname}})
bts := C.GoBytes(ptr, C.get_{{.Symname}}_length())
embeddedBox := &embedded.EmbeddedBox{}
err := gob.NewDecoder(bytes.NewReader(bts)).Decode(embeddedBox)
if err != nil {
panic("error decoding embedded box: "+err.Error())
}
embeddedBox.Link()
embedded.RegisterEmbeddedBox(embeddedBox.Name, embeddedBox)
}`)
if err != nil {
panic("could not parse template embeddedSysoHelper: " + err.Error())
}
}
type embeddedSysoHelperData struct {
Package string
Symname string
}
func operationEmbedSyso(pkg *build.Package) {
regexpSynameReplacer := regexp.MustCompile(`[^a-z0-9_]`)
boxMap := findBoxes(pkg)
// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ?
if len(boxMap) == 0 {
fmt.Println("no calls to rice.FindBox() found")
return
}
verbosef("\n")
for boxname := range boxMap {
// find path and filename for this box
boxPath := filepath.Join(pkg.Dir, boxname)
boxFilename := strings.Replace(boxname, "/", "-", -1)
boxFilename = strings.Replace(boxFilename, "..", "back", -1)
boxFilename = strings.Replace(boxFilename, ".", "-", -1)
// verbose info
verbosef("embedding box '%s'\n", boxname)
verbosef("\tto file %s\n", boxFilename)
// read box metadata
boxInfo, ierr := os.Stat(boxPath)
if ierr != nil {
fmt.Printf("Error: unable to access box at %s\n", boxPath)
os.Exit(1)
}
// create box datastructure (used by template)
box := &embedded.EmbeddedBox{
Name: boxname,
Time: boxInfo.ModTime(),
EmbedType: embedded.EmbedTypeSyso,
Files: make(map[string]*embedded.EmbeddedFile),
Dirs: make(map[string]*embedded.EmbeddedDir),
}
// fill box datastructure with file data
filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("error walking box: %s\n", err)
os.Exit(1)
}
filename := strings.TrimPrefix(path, boxPath)
filename = strings.Replace(filename, "\\", "/", -1)
filename = strings.TrimPrefix(filename, "/")
if info.IsDir() {
embeddedDir := &embedded.EmbeddedDir{
Filename: filename,
DirModTime: info.ModTime(),
}
verbosef("\tincludes dir: '%s'\n", embeddedDir.Filename)
box.Dirs[embeddedDir.Filename] = embeddedDir
// add tree entry (skip for root, it'll create a recursion)
if embeddedDir.Filename != "" {
pathParts := strings.Split(embeddedDir.Filename, "/")
parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")]
parentDir.ChildDirs = append(parentDir.ChildDirs, embeddedDir)
}
} else {
embeddedFile := &embedded.EmbeddedFile{
Filename: filename,
FileModTime: info.ModTime(),
Content: "",
}
verbosef("\tincludes file: '%s'\n", embeddedFile.Filename)
contentBytes, err := ioutil.ReadFile(path)
if err != nil {
fmt.Printf("error reading file content while walking box: %s\n", err)
os.Exit(1)
}
embeddedFile.Content = string(contentBytes)
box.Files[embeddedFile.Filename] = embeddedFile
}
return nil
})
// encode embedded box to gob file
boxGobBuf := &bytes.Buffer{}
err := gob.NewEncoder(boxGobBuf).Encode(box)
if err != nil {
fmt.Printf("error encoding box to gob: %v\n", err)
os.Exit(1)
}
verbosef("gob-encoded embeddedBox is %d bytes large\n", boxGobBuf.Len())
// write coff
symname := regexpSynameReplacer.ReplaceAllString(boxname, "_")
createCoffSyso(boxname, symname, "386", boxGobBuf.Bytes())
createCoffSyso(boxname, symname, "amd64", boxGobBuf.Bytes())
// write go
sysoHelperData := embeddedSysoHelperData{
Package: pkg.Name,
Symname: symname,
}
fileSysoHelper, err := os.Create(boxFilename + ".rice-box.go")
if err != nil {
fmt.Printf("error creating syso helper: %v\n", err)
os.Exit(1)
}
err = tmplEmbeddedSysoHelper.Execute(fileSysoHelper, sysoHelperData)
if err != nil {
fmt.Printf("error executing tmplEmbeddedSysoHelper: %v\n", err)
os.Exit(1)
}
}
}
func createCoffSyso(boxFilename string, symname string, arch string, data []byte) {
boxCoff := coff.NewRDATA()
switch arch {
case "386":
case "amd64":
boxCoff.FileHeader.Machine = 0x8664
default:
panic("invalid arch")
}
boxCoff.AddData("_bricebox_"+symname, sizedReader{bytes.NewReader(data)})
boxCoff.AddData("_ericebox_"+symname, io.NewSectionReader(strings.NewReader("\000\000"), 0, 2)) // TODO: why? copied from rsrc, which copied it from as-generated
boxCoff.Freeze()
err := writeCoff(boxCoff, boxFilename+"_"+arch+".rice-box.syso")
if err != nil {
fmt.Printf("error writing %s coff/.syso: %v\n", arch, err)
os.Exit(1)
}
}

View File

@ -1,150 +0,0 @@
package main
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
func badArgument(fileset *token.FileSet, p token.Pos) {
pos := fileset.Position(p)
filename := pos.Filename
base, err := os.Getwd()
if err == nil {
rpath, perr := filepath.Rel(base, pos.Filename)
if perr == nil {
filename = rpath
}
}
msg := fmt.Sprintf("%s:%d: Error: found call to rice.FindBox, "+
"but argument must be a string literal.\n", filename, pos.Line)
fmt.Println(msg)
os.Exit(1)
}
func findBoxes(pkg *build.Package) map[string]bool {
// create map of boxes to embed
var boxMap = make(map[string]bool)
// create one list of files for this package
filenames := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles))
filenames = append(filenames, pkg.GoFiles...)
filenames = append(filenames, pkg.CgoFiles...)
// loop over files, search for rice.FindBox(..) calls
for _, filename := range filenames {
// find full filepath
fullpath := filepath.Join(pkg.Dir, filename)
if strings.HasSuffix(filename, "rice-box.go") {
// Ignore *.rice-box.go files
verbosef("skipping file %q\n", fullpath)
continue
}
verbosef("scanning file %q\n", fullpath)
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fullpath, nil, 0)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var riceIsImported bool
ricePkgName := "rice"
for _, imp := range f.Imports {
if strings.HasSuffix(imp.Path.Value, "go.rice\"") {
if imp.Name != nil {
ricePkgName = imp.Name.Name
}
riceIsImported = true
break
}
}
if !riceIsImported {
// Rice wasn't imported, so we won't find a box.
continue
}
if ricePkgName == "_" {
// Rice pkg is unnamed, so we won't find a box.
continue
}
// Inspect AST, looking for calls to (Must)?FindBox.
// First parameter of the func must be a basic literal.
// Identifiers won't be resolved.
var nextIdentIsBoxFunc bool
var nextBasicLitParamIsBoxName bool
var boxCall token.Pos
var variableToRemember string
var validVariablesForBoxes map[string]bool = make(map[string]bool)
ast.Inspect(f, func(node ast.Node) bool {
if node == nil {
return false
}
switch x := node.(type) {
// this case fixes the var := func() style assignments, not assignments to vars declared separately from the assignment.
case *ast.AssignStmt:
var assign = node.(*ast.AssignStmt)
name, found := assign.Lhs[0].(*ast.Ident)
if found {
variableToRemember = name.Name
composite, first := assign.Rhs[0].(*ast.CompositeLit)
if first {
riceSelector, second := composite.Type.(*ast.SelectorExpr)
if second {
callCorrect := riceSelector.Sel.Name == "Config"
packageName, third := riceSelector.X.(*ast.Ident)
if third && callCorrect && packageName.Name == ricePkgName {
validVariablesForBoxes[name.Name] = true
verbosef("\tfound variable, saving to scan for boxes: %q\n", name.Name)
}
}
}
}
case *ast.Ident:
if nextIdentIsBoxFunc || ricePkgName == "." {
nextIdentIsBoxFunc = false
if x.Name == "FindBox" || x.Name == "MustFindBox" {
nextBasicLitParamIsBoxName = true
boxCall = x.Pos()
}
} else {
if x.Name == ricePkgName || validVariablesForBoxes[x.Name] {
nextIdentIsBoxFunc = true
}
}
case *ast.BasicLit:
if nextBasicLitParamIsBoxName {
if x.Kind == token.STRING {
nextBasicLitParamIsBoxName = false
// trim "" or ``
name := x.Value[1 : len(x.Value)-1]
boxMap[name] = true
verbosef("\tfound box %q\n", name)
} else {
badArgument(fset, boxCall)
}
}
default:
if nextIdentIsBoxFunc {
nextIdentIsBoxFunc = false
}
if nextBasicLitParamIsBoxName {
badArgument(fset, boxCall)
}
}
return true
})
}
return boxMap
}

View File

@ -1,298 +0,0 @@
package main
import (
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
type sourceFile struct {
Name string
Contents []byte
}
func expectBoxes(expected []string, actual map[string]bool) error {
if len(expected) != len(actual) {
return fmt.Errorf("expected %v, got %v", expected, actual)
}
for _, box := range expected {
if _, ok := actual[box]; !ok {
return fmt.Errorf("expected %v, got %v", expected, actual)
}
}
return nil
}
func setUpTestPkg(pkgName string, files []sourceFile) (*build.Package, func(), error) {
temp, err := ioutil.TempDir("", "go.rice-test")
if err != nil {
return nil, func() {}, err
}
cleanup := func() {
os.RemoveAll(temp)
}
dir := filepath.Join(temp, pkgName)
if err := os.Mkdir(dir, 0770); err != nil {
return nil, cleanup, err
}
for _, f := range files {
if err := ioutil.WriteFile(filepath.Join(dir, f.Name), f.Contents, 0660); err != nil {
return nil, cleanup, err
}
}
pkg, err := build.ImportDir(dir, 0)
return pkg, cleanup, err
}
func TestFindOneBox(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes.go",
[]byte(`package main
import (
"github.com/GeertJohan/go.rice"
)
func main() {
rice.MustFindBox("foo")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
expectedBoxes := []string{"foo"}
boxMap := findBoxes(pkg)
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
t.Error(err)
}
}
func TestFindOneBoxViaVariable(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes.go",
[]byte(`package main
import (
"github.com/GeertJohan/go.rice"
)
func main() {
conf := rice.Config{
LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS},
}
conf.MustFindBox("foo")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
expectedBoxes := []string{"foo"}
boxMap := findBoxes(pkg)
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
t.Error(err)
}
}
func TestFindMultipleBoxes(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes.go",
[]byte(`package main
import (
"github.com/GeertJohan/go.rice"
)
func main() {
rice.MustFindBox("foo")
rice.MustFindBox("bar")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
expectedBoxes := []string{"foo", "bar"}
boxMap := findBoxes(pkg)
if err := expectBoxes(expectedBoxes, boxMap); err != nil {
t.Error(err)
}
}
func TestNoBoxFoundIfRiceNotImported(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes.go",
[]byte(`package main
type fakerice struct {}
func (fr fakerice) FindBox(s string) {
}
func main() {
rice := fakerice{}
rice.FindBox("foo")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
boxMap := findBoxes(pkg)
if _, ok := boxMap["foo"]; ok {
t.Errorf("Unexpected box %q was found", "foo")
}
}
func TestUnrelatedBoxesAreNotFound(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes.go",
[]byte(`package foobar
import (
_ "github.com/GeertJohan/go.rice"
)
type fakerice struct {}
func (fr fakerice) FindBox(s string) {
}
func FindBox(s string) {
}
func LoadBoxes() {
rice := fakerice{}
rice.FindBox("foo")
FindBox("bar")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
boxMap := findBoxes(pkg)
for _, box := range []string{"foo", "bar"} {
if _, ok := boxMap[box]; ok {
t.Errorf("Unexpected box %q was found", box)
}
}
}
func TestMixGoodAndBadBoxes(t *testing.T) {
pkg, cleanup, err := setUpTestPkg("foobar", []sourceFile{
{
"boxes1.go",
[]byte(`package foobar
import (
_ "github.com/GeertJohan/go.rice"
)
type fakerice struct {}
func (fr fakerice) FindBox(s string) {
}
func FindBox(s string) {
}
func LoadBoxes1() {
rice := fakerice{}
rice.FindBox("foo")
FindBox("bar")
}
`),
},
{
"boxes2.go",
[]byte(`package foobar
import (
noodles "github.com/GeertJohan/go.rice"
)
func LoadBoxes2() {
FindBox("baz")
noodles.FindBox("veggies")
}
`),
},
{
"boxes3.go",
[]byte(`package foobar
import (
"github.com/GeertJohan/go.rice"
)
func LoadBoxes3() {
rice.FindBox("fish")
}
`),
},
{
"boxes4.go",
[]byte(`package foobar
import (
. "github.com/GeertJohan/go.rice"
)
func LoadBoxes3() {
MustFindBox("chicken")
}
`),
},
})
defer cleanup()
if err != nil {
t.Error(err)
return
}
boxMap := findBoxes(pkg)
for _, box := range []string{"foo", "bar", "baz"} {
if _, ok := boxMap[box]; ok {
t.Errorf("Unexpected box %q was found", box)
}
}
for _, box := range []string{"veggies", "fish", "chicken"} {
if _, ok := boxMap[box]; !ok {
t.Errorf("Expected box %q not found", box)
}
}
}

View File

@ -1,80 +0,0 @@
package main
import (
"fmt"
"go/build"
"os"
goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
)
// flags
var flags struct {
Verbose bool `long:"verbose" short:"v" description:"Show verbose debug information"`
ImportPaths []string `long:"import-path" short:"i" description:"Import path(s) to use. Using PWD when left empty. Specify multiple times for more import paths to append"`
Append struct {
Executable string `long:"exec" description:"Executable to append" required:"true"`
} `command:"append"`
EmbedGo struct{} `command:"embed-go" alias:"embed"`
EmbedSyso struct{} `command:"embed-syso"`
Clean struct{} `command:"clean"`
}
// flags parser
var flagsParser *goflags.Parser
// initFlags parses the given flags.
// when the user asks for help (-h or --help): the application exists with status 0
// when unexpected flags is given: the application exits with status 1
func parseArguments() {
// create flags parser in global var, for flagsParser.Active.Name (operation)
flagsParser = goflags.NewParser(&flags, goflags.Default)
// parse flags
args, err := flagsParser.Parse()
if err != nil {
// assert the err to be a flags.Error
flagError := err.(*goflags.Error)
if flagError.Type == goflags.ErrHelp {
// user asked for help on flags.
// program can exit successfully
os.Exit(0)
}
if flagError.Type == goflags.ErrUnknownFlag {
fmt.Println("Use --help to view available options.")
os.Exit(1)
}
if flagError.Type == goflags.ErrRequired {
os.Exit(1)
}
fmt.Printf("Error parsing flags: %s\n", err)
os.Exit(1)
}
// error on left-over arguments
if len(args) > 0 {
fmt.Printf("Unexpected arguments: %s\nUse --help to view available options.", args)
os.Exit(1)
}
// default ImportPath to pwd when not set
if len(flags.ImportPaths) == 0 {
pwd, err := os.Getwd()
if err != nil {
fmt.Printf("error getting pwd: %s\n", err)
os.Exit(1)
}
verbosef("using pwd as import path\n")
// find non-absolute path for this pwd
pkg, err := build.ImportDir(pwd, build.FindOnly)
if err != nil {
fmt.Printf("error using current directory as import path: %s\n", err)
os.Exit(1)
}
flags.ImportPaths = append(flags.ImportPaths, pkg.ImportPath)
verbosef("using import paths: %s\n", flags.ImportPaths)
return
}
}

View File

@ -1,14 +0,0 @@
package main
import (
"strconv"
"github.com/GeertJohan/go.incremental"
)
var identifierCount incremental.Uint64
func nextIdentifier() string {
num := identifierCount.Next()
return strconv.FormatUint(num, 36) // 0123456789abcdefghijklmnopqrstuvwxyz
}

View File

@ -1,68 +0,0 @@
package main
import (
"fmt"
"go/build"
"log"
"os"
)
func main() {
// parser arguments
parseArguments()
// find package for path
var pkgs []*build.Package
for _, importPath := range flags.ImportPaths {
pkg := pkgForPath(importPath)
pkgs = append(pkgs, pkg)
}
// switch on the operation to perform
switch flagsParser.Active.Name {
case "embed", "embed-go":
for _, pkg := range pkgs {
operationEmbedGo(pkg)
}
case "embed-syso":
log.Println("WARNING: embedding .syso is experimental..")
for _, pkg := range pkgs {
operationEmbedSyso(pkg)
}
case "append":
operationAppend(pkgs)
case "clean":
for _, pkg := range pkgs {
operationClean(pkg)
}
}
// all done
verbosef("\n")
verbosef("rice finished successfully\n")
}
// helper function to get *build.Package for given path
func pkgForPath(path string) *build.Package {
// get pwd for relative imports
pwd, err := os.Getwd()
if err != nil {
fmt.Printf("error getting pwd (required for relative imports): %s\n", err)
os.Exit(1)
}
// read full package information
pkg, err := build.Import(path, pwd, 0)
if err != nil {
fmt.Printf("error reading package: %s\n", err)
os.Exit(1)
}
return pkg
}
func verbosef(format string, stuff ...interface{}) {
if flags.Verbose {
log.Printf(format, stuff...)
}
}

View File

@ -1,98 +0,0 @@
package main
import (
"fmt"
"os"
"text/template"
)
var tmplEmbeddedBox *template.Template
func init() {
var err error
// parse embedded box template
tmplEmbeddedBox, err = template.New("embeddedBox").Parse(`package {{.Package}}
import (
"github.com/GeertJohan/go.rice/embedded"
"time"
)
{{range .Boxes}}
func init() {
// define files
{{range .Files}}{{.Identifier}} := &embedded.EmbeddedFile{
Filename: ` + "`" + `{{.FileName}}` + "`" + `,
FileModTime: time.Unix({{.ModTime}}, 0),
Content: string({{.Content | printf "%q"}}),
}
{{end}}
// define dirs
{{range .Dirs}}{{.Identifier}} := &embedded.EmbeddedDir{
Filename: ` + "`" + `{{.FileName}}` + "`" + `,
DirModTime: time.Unix({{.ModTime}}, 0),
ChildFiles: []*embedded.EmbeddedFile{
{{range .ChildFiles}}{{.Identifier}}, // {{.FileName}}
{{end}}
},
}
{{end}}
// link ChildDirs
{{range .Dirs}}{{.Identifier}}.ChildDirs = []*embedded.EmbeddedDir{
{{range .ChildDirs}}{{.Identifier}}, // {{.FileName}}
{{end}}
}
{{end}}
// register embeddedBox
embedded.RegisterEmbeddedBox(` + "`" + `{{.BoxName}}` + "`" + `, &embedded.EmbeddedBox{
Name: ` + "`" + `{{.BoxName}}` + "`" + `,
Time: time.Unix({{.UnixNow}}, 0),
Dirs: map[string]*embedded.EmbeddedDir{
{{range .Dirs}}"{{.FileName}}": {{.Identifier}},
{{end}}
},
Files: map[string]*embedded.EmbeddedFile{
{{range .Files}}"{{.FileName}}": {{.Identifier}},
{{end}}
},
})
}
{{end}}`)
if err != nil {
fmt.Printf("error parsing embedded box template: %s\n", err)
os.Exit(-1)
}
}
type embedFileDataType struct {
Package string
Boxes []*boxDataType
}
type boxDataType struct {
BoxName string
UnixNow int64
Files []*fileDataType
Dirs map[string]*dirDataType
}
type fileDataType struct {
Identifier string
FileName string
Content []byte
ModTime int64
}
type dirDataType struct {
Identifier string
FileName string
Content []byte
ModTime int64
ChildDirs []*dirDataType
ChildFiles []*fileDataType
}

View File

@ -1,22 +0,0 @@
package main
import (
"math/rand"
"time"
)
// randomString generates a pseudo-random alpha-numeric string with given length.
func randomString(length int) string {
rand.Seed(time.Now().UnixNano())
k := make([]rune, length)
for i := 0; i < length; i++ {
c := rand.Intn(35)
if c < 10 {
c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9')
} else {
c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z')
}
k[i] = rune(c)
}
return string(k)
}

View File

@ -1,42 +0,0 @@
package main
import (
"fmt"
"os"
"reflect"
"github.com/akavel/rsrc/binutil"
"github.com/akavel/rsrc/coff"
)
// copied from github.com/akavel/rsrc
// LICENSE: MIT
// Copyright 2013-2014 The rsrc Authors. (https://github.com/akavel/rsrc/blob/master/AUTHORS)
func writeCoff(coff *coff.Coff, fnameout string) error {
out, err := os.Create(fnameout)
if err != nil {
return err
}
defer out.Close()
w := binutil.Writer{W: out}
// write the resulting file to disk
binutil.Walk(coff, func(v reflect.Value, path string) error {
if binutil.Plain(v.Kind()) {
w.WriteLE(v.Interface())
return nil
}
vv, ok := v.Interface().(binutil.SizedReader)
if ok {
w.WriteFromSized(vv)
return binutil.WALK_SKIP
}
return nil
})
if w.Err != nil {
return fmt.Errorf("Error writing output file: %s", w.Err)
}
return nil
}

View File

@ -1,19 +0,0 @@
package rice
import "os"
// SortByName allows an array of os.FileInfo objects
// to be easily sorted by filename using sort.Sort(SortByName(array))
type SortByName []os.FileInfo
func (f SortByName) Len() int { return len(f) }
func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
// SortByModified allows an array of os.FileInfo objects
// to be easily sorted by modified date using sort.Sort(SortByModified(array))
type SortByModified []os.FileInfo
func (f SortByModified) Len() int { return len(f) }
func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() }
func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] }

View File

@ -1,252 +0,0 @@
package rice
import (
"errors"
"io"
"os"
"path/filepath"
"sort"
"github.com/GeertJohan/go.rice/embedded"
)
//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File
// Error indicating some function is not implemented yet (but available to satisfy an interface)
var ErrNotImplemented = errors.New("not implemented yet")
// virtualFile is a 'stateful' virtual file.
// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'.
// virtualFile is only internally visible and should be exposed through rice.File
type virtualFile struct {
*embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods
offset int64 // read position on the virtual file
closed bool // closed when true
}
// create a new virtualFile for given EmbeddedFile
func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile {
vf := &virtualFile{
EmbeddedFile: ef,
offset: 0,
closed: false,
}
return vf
}
//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid
func (vf *virtualFile) close() error {
if vf.closed {
return &os.PathError{
Op: "close",
Path: vf.EmbeddedFile.Filename,
Err: errors.New("already closed"),
}
}
vf.EmbeddedFile = nil
vf.closed = true
return nil
}
func (vf *virtualFile) stat() (os.FileInfo, error) {
if vf.closed {
return nil, &os.PathError{
Op: "stat",
Path: vf.EmbeddedFile.Filename,
Err: errors.New("bad file descriptor"),
}
}
return (*embeddedFileInfo)(vf.EmbeddedFile), nil
}
func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) {
if vf.closed {
return nil, &os.PathError{
Op: "readdir",
Path: vf.EmbeddedFile.Filename,
Err: errors.New("bad file descriptor"),
}
}
//TODO: return proper error for a readdir() call on a file
return nil, ErrNotImplemented
}
func (vf *virtualFile) read(bts []byte) (int, error) {
if vf.closed {
return 0, &os.PathError{
Op: "read",
Path: vf.EmbeddedFile.Filename,
Err: errors.New("bad file descriptor"),
}
}
end := vf.offset + int64(len(bts))
if end >= int64(len(vf.Content)) {
// end of file, so return what we have + EOF
n := copy(bts, vf.Content[vf.offset:])
vf.offset = 0
return n, io.EOF
}
n := copy(bts, vf.Content[vf.offset:end])
vf.offset += int64(n)
return n, nil
}
func (vf *virtualFile) seek(offset int64, whence int) (int64, error) {
if vf.closed {
return 0, &os.PathError{
Op: "seek",
Path: vf.EmbeddedFile.Filename,
Err: errors.New("bad file descriptor"),
}
}
var e error
//++ TODO: check if this is correct implementation for seek
switch whence {
case os.SEEK_SET:
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
vf.offset = offset
case os.SEEK_CUR:
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
vf.offset += offset
case os.SEEK_END:
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset
}
if e != nil {
return 0, &os.PathError{
Op: "seek",
Path: vf.Filename,
Err: e,
}
}
return vf.offset, nil
}
// virtualDir is a 'stateful' virtual directory.
// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'.
// virtualDir is only internally visible and should be exposed through rice.File
type virtualDir struct {
*embedded.EmbeddedDir
offset int // readdir position on the directory
closed bool
}
// create a new virtualDir for given EmbeddedDir
func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir {
vd := &virtualDir{
EmbeddedDir: ed,
offset: 0,
closed: false,
}
return vd
}
func (vd *virtualDir) close() error {
//++ TODO: needs sync mutex?
if vd.closed {
return &os.PathError{
Op: "close",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("already closed"),
}
}
vd.closed = true
return nil
}
func (vd *virtualDir) stat() (os.FileInfo, error) {
if vd.closed {
return nil, &os.PathError{
Op: "stat",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("bad file descriptor"),
}
}
return (*embeddedDirInfo)(vd.EmbeddedDir), nil
}
func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) {
if vd.closed {
return nil, &os.PathError{
Op: "readdir",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("bad file descriptor"),
}
}
// Build up the array of our contents
var files []os.FileInfo
// Add the child directories
for _, child := range vd.ChildDirs {
child.Filename = filepath.Base(child.Filename)
files = append(files, (*embeddedDirInfo)(child))
}
// Add the child files
for _, child := range vd.ChildFiles {
child.Filename = filepath.Base(child.Filename)
files = append(files, (*embeddedFileInfo)(child))
}
// Sort it by filename (lexical order)
sort.Sort(SortByName(files))
// Return all contents if that's what is requested
if n <= 0 {
vd.offset = 0
return files, nil
}
// If user has requested past the end of our list
// return what we can and send an EOF
if vd.offset+n >= len(files) {
offset := vd.offset
vd.offset = 0
return files[offset:], io.EOF
}
offset := vd.offset
vd.offset += n
return files[offset : offset+n], nil
}
func (vd *virtualDir) read(bts []byte) (int, error) {
if vd.closed {
return 0, &os.PathError{
Op: "read",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("bad file descriptor"),
}
}
return 0, &os.PathError{
Op: "read",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("is a directory"),
}
}
func (vd *virtualDir) seek(offset int64, whence int) (int64, error) {
if vd.closed {
return 0, &os.PathError{
Op: "seek",
Path: vd.EmbeddedDir.Filename,
Err: errors.New("bad file descriptor"),
}
}
return 0, &os.PathError{
Op: "seek",
Path: vd.Filename,
Err: errors.New("is a directory"),
}
}

View File

@ -1,122 +0,0 @@
package rice
import (
"os"
"path/filepath"
"sort"
"strings"
)
// Walk is like filepath.Walk()
// Visit http://golang.org/pkg/path/filepath/#Walk for more information
func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error {
pathFile, err := b.Open(path)
if err != nil {
return err
}
defer pathFile.Close()
pathInfo, err := pathFile.Stat()
if err != nil {
return err
}
if b.IsAppended() || b.IsEmbedded() {
return b.walk(path, pathInfo, walkFn)
}
// We don't have any embedded or appended box so use live filesystem mode
return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error {
// Strip out the box name from the returned paths
path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator))
return walkFn(path, info, err)
})
}
// walk recursively descends path.
// See walk() in $GOROOT/src/pkg/path/filepath/path.go
func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
err := walkFn(path, info, nil)
if err != nil {
if info.IsDir() && err == filepath.SkipDir {
return nil
}
return err
}
if !info.IsDir() {
return nil
}
names, err := b.readDirNames(path)
if err != nil {
return walkFn(path, info, err)
}
for _, name := range names {
filename := filepath.Join(path, name)
fileObject, err := b.Open(filename)
if err != nil {
return err
}
defer fileObject.Close()
fileInfo, err := fileObject.Stat()
if err != nil {
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
return err
}
} else {
err = b.walk(filename, fileInfo, walkFn)
if err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
}
return nil
}
// readDirNames reads the directory named by path and returns a sorted list of directory entries.
// See readDirNames() in $GOROOT/pkg/path/filepath/path.go
func (b *Box) readDirNames(path string) ([]string, error) {
f, err := b.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return nil, err
}
if !stat.IsDir() {
return nil, nil
}
infos, err := f.Readdir(0)
if err != nil {
return nil, err
}
var names []string
for _, info := range infos {
names = append(names, info.Name())
}
sort.Strings(names)
return names, nil
}

View File

@ -1,31 +0,0 @@
box: wercker/golang
build:
steps:
- setup-go-workspace
- script:
name: get dependencies
code: |
go get -d -t ./...
- script:
name: build
code: |
go build -x ./...
- script:
name: test
code: |
go test -cover ./...
- script:
name: vet
code: |
go vet ./...
- script:
name: lint
code: |
go get github.com/golang/lint/golint
golint .

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright © 2012-2015 Carlos Castillo
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.

View File

@ -1,5 +0,0 @@
go.zipexe
=========
This module was taken as-is from https://github.com/cookieo9/resources-go.
Documentation: https://godoc.org/github.com/daaku/go.zipexe

View File

@ -1,142 +0,0 @@
// Package zipexe attempts to open an executable binary file as a zip file.
package zipexe
import (
"archive/zip"
"debug/elf"
"debug/macho"
"debug/pe"
"errors"
"io"
"os"
)
// Opens a zip file by path.
func Open(path string) (*zip.Reader, error) {
_, rd, err := OpenCloser(path)
return rd, err
}
// OpenCloser is like Open but returns an additional Closer to avoid leaking open files.
func OpenCloser(path string) (io.Closer, *zip.Reader, error) {
file, err := os.Open(path)
if err != nil {
return nil, nil, err
}
finfo, err := file.Stat()
if err != nil {
return nil, nil, err
}
zr, err := NewReader(file, finfo.Size())
if err != nil {
return nil, nil, err
}
return file, zr, nil
}
// Open a zip file, specially handling various binaries that may have been
// augmented with zip data.
func NewReader(rda io.ReaderAt, size int64) (*zip.Reader, error) {
handlers := []func(io.ReaderAt, int64) (*zip.Reader, error){
zip.NewReader,
zipExeReaderMacho,
zipExeReaderElf,
zipExeReaderPe,
}
for _, handler := range handlers {
zfile, err := handler(rda, size)
if err == nil {
return zfile, nil
}
}
return nil, errors.New("Couldn't Open As Executable")
}
// zipExeReaderMacho treats the file as a Mach-O binary
// (Mac OS X / Darwin executable) and attempts to find a zip archive.
func zipExeReaderMacho(rda io.ReaderAt, size int64) (*zip.Reader, error) {
file, err := macho.NewFile(rda)
if err != nil {
return nil, err
}
var max int64
for _, load := range file.Loads {
seg, ok := load.(*macho.Segment)
if ok {
// Check if the segment contains a zip file
if zfile, err := zip.NewReader(seg, int64(seg.Filesz)); err == nil {
return zfile, nil
}
// Otherwise move end of file pointer
end := int64(seg.Offset + seg.Filesz)
if end > max {
max = end
}
}
}
// No zip file within binary, try appended to end
section := io.NewSectionReader(rda, max, size-max)
return zip.NewReader(section, section.Size())
}
// zipExeReaderPe treats the file as a Portable Exectuable binary
// (Windows executable) and attempts to find a zip archive.
func zipExeReaderPe(rda io.ReaderAt, size int64) (*zip.Reader, error) {
file, err := pe.NewFile(rda)
if err != nil {
return nil, err
}
var max int64
for _, sec := range file.Sections {
// Check if this section has a zip file
if zfile, err := zip.NewReader(sec, int64(sec.Size)); err == nil {
return zfile, nil
}
// Otherwise move end of file pointer
end := int64(sec.Offset + sec.Size)
if end > max {
max = end
}
}
// No zip file within binary, try appended to end
section := io.NewSectionReader(rda, max, size-max)
return zip.NewReader(section, section.Size())
}
// zipExeReaderElf treats the file as a ELF binary
// (linux/BSD/etc... executable) and attempts to find a zip archive.
func zipExeReaderElf(rda io.ReaderAt, size int64) (*zip.Reader, error) {
file, err := elf.NewFile(rda)
if err != nil {
return nil, err
}
var max int64
for _, sect := range file.Sections {
if sect.Type == elf.SHT_NOBITS {
continue
}
// Check if this section has a zip file
if zfile, err := zip.NewReader(sect, int64(sect.Size)); err == nil {
return zfile, nil
}
// Otherwise move end of file pointer
end := int64(sect.Offset + sect.Size)
if end > max {
max = end
}
}
// No zip file within binary, try appended to end
section := io.NewSectionReader(rda, max, size-max)
return zip.NewReader(section, section.Size())
}

View File

@ -1,4 +0,0 @@
.DS_Store
bin

View File

@ -1,8 +0,0 @@
language: go
go:
- 1.3
- 1.4
- 1.5
- 1.6
- tip

View File

@ -1,8 +0,0 @@
Copyright (c) 2012 Dave Grijalva
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.

View File

@ -1,97 +0,0 @@
## Migration Guide from v2 -> v3
Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code.
### `Token.Claims` is now an interface type
The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`.
`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property.
The old example for parsing a token looked like this..
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is now directly mapped to...
```go
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
}
```
`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type.
```go
type MyCustomClaims struct {
User string
*StandardClaims
}
if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil {
claims := token.Claims.(*MyCustomClaims)
fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt)
}
```
### `ParseFromRequest` has been moved
To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`.
`Extractors` do the work of picking the token string out of a request. The interface is simple and composable.
This simple parsing example:
```go
if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
}
```
is directly mapped to:
```go
if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
}
```
There are several concrete `Extractor` types provided for your convenience:
* `HeaderExtractor` will search a list of headers until one contains content.
* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content.
* `MultiExtractor` will try a list of `Extractors` in order until one returns content.
* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token.
* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument
* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header
### RSA signing methods no longer accept `[]byte` keys
Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse.
To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types.
```go
func keyLookupFunc(*Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
// Look up key
key, err := lookupPublicKey(token.Header["kid"])
if err != nil {
return nil, err
}
// Unpack key from PEM encoded PKCS8
return jwt.ParseRSAPublicKeyFromPEM(key)
}
```

View File

@ -1,85 +0,0 @@
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go)
**BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect.
## What the heck is a JWT?
JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens.
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
## What's in the box?
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
## Examples
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage:
* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac)
* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac)
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
## Extensions
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go
## Compliance
This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences:
* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
## Project Status & Versioning
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning.
## Usage Tips
### Signing vs Encryption
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
* The author of the token was in the possession of the signing secret
* The data has not been modified since it was signed
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
### Choosing a Signing Method
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric.
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
### JWT and OAuth
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
## More
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in to documentation.

View File

@ -1,105 +0,0 @@
## `jwt-go` Version History
#### 3.0.0
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code
* Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods.
* `ParseFromRequest` has been moved to `request` subpackage and usage has changed
* The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims.
* Other Additions and Changes
* Added `Claims` interface type to allow users to decode the claims into a custom type
* Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into.
* Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage
* Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims`
* Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`.
* Added several new, more specific, validation errors to error type bitmask
* Moved examples from README to executable example files
* Signing method registry is now thread safe
* Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser)
#### 2.7.0
This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes.
* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying
* Error text for expired tokens includes how long it's been expired
* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM`
* Documentation updates
#### 2.6.0
* Exposed inner error within ValidationError
* Fixed validation errors when using UseJSONNumber flag
* Added several unit tests
#### 2.5.0
* Added support for signing method none. You shouldn't use this. The API tries to make this clear.
* Updated/fixed some documentation
* Added more helpful error message when trying to parse tokens that begin with `BEARER `
#### 2.4.0
* Added new type, Parser, to allow for configuration of various parsing parameters
* You can now specify a list of valid signing methods. Anything outside this set will be rejected.
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go)
* Fixed some bugs with ECDSA parsing
#### 2.3.0
* Added support for ECDSA signing methods
* Added support for RSA PSS signing methods (requires go v1.4)
#### 2.2.0
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
#### 2.1.0
Backwards compatible API change that was missed in 2.0.0.
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
#### 2.0.0
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
* **Compatibility Breaking Changes**
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
* `KeyFunc` now returns `interface{}` instead of `[]byte`
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodHS256`
* Added public package global `SigningMethodHS384`
* Added public package global `SigningMethodHS512`
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodRS256`
* Added public package global `SigningMethodRS384`
* Added public package global `SigningMethodRS512`
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
* Refactored the RSA implementation to be easier to read
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
#### 1.0.2
* Fixed bug in parsing public keys from certificates
* Added more tests around the parsing of keys for RS256
* Code refactoring in RS256 implementation. No functional changes
#### 1.0.1
* Fixed panic if RS256 signing method was passed an invalid key
#### 1.0.0
* First versioned release
* API stabilized
* Supports creating, signing, parsing, and validating JWT tokens
* Supports RS256 and HS256 signing methods

View File

@ -1,134 +0,0 @@
package jwt
import (
"crypto/subtle"
"fmt"
"time"
)
// For a type to be a Claims object, it must just have a Valid method that determines
// if the token is invalid for any supported reason
type Claims interface {
Valid() error
}
// Structured version of Claims Section, as referenced at
// https://tools.ietf.org/html/rfc7519#section-4.1
// See examples for how to use this with your own claim types
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
func (c StandardClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if c.VerifyExpiresAt(now, false) == false {
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
vErr.Errors |= ValidationErrorExpired
}
if c.VerifyIssuedAt(now, false) == false {
vErr.Inner = fmt.Errorf("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if c.VerifyNotBefore(now, false) == false {
vErr.Inner = fmt.Errorf("token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
if vErr.valid() {
return nil
}
return vErr
}
// Compares the aud claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
return verifyAud(c.Audience, cmp, req)
}
// Compares the exp claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
return verifyExp(c.ExpiresAt, cmp, req)
}
// Compares the iat claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
return verifyIat(c.IssuedAt, cmp, req)
}
// Compares the iss claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
return verifyIss(c.Issuer, cmp, req)
}
// Compares the nbf claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
return verifyNbf(c.NotBefore, cmp, req)
}
// ----- helpers
func verifyAud(aud string, cmp string, required bool) bool {
if aud == "" {
return !required
}
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
return true
} else {
return false
}
}
func verifyExp(exp int64, now int64, required bool) bool {
if exp == 0 {
return !required
}
return now <= exp
}
func verifyIat(iat int64, now int64, required bool) bool {
if iat == 0 {
return !required
}
return now >= iat
}
func verifyIss(iss string, cmp string, required bool) bool {
if iss == "" {
return !required
}
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
return true
} else {
return false
}
}
func verifyNbf(nbf int64, now int64, required bool) bool {
if nbf == 0 {
return !required
}
return now >= nbf
}

View File

@ -1,13 +0,0 @@
`jwt` command-line tool
=======================
This is a simple tool to sign, verify and show JSON Web Tokens from
the command line.
The following will create and sign a token, then verify it and output the original claims:
echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
To simply display a token, use:
echo $JWT | jwt -show -

View File

@ -1,245 +0,0 @@
// A useful example app. You can use this to debug your tokens on the command line.
// This is also a great place to look at how you might use this library.
//
// Example usage:
// The following will create and sign a token, then verify it and output the original claims.
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"strings"
jwt "github.com/dgrijalva/jwt-go"
)
var (
// Options
flagAlg = flag.String("alg", "", "signing algorithm identifier")
flagKey = flag.String("key", "", "path to key file or '-' to read from stdin")
flagCompact = flag.Bool("compact", false, "output compact JSON")
flagDebug = flag.Bool("debug", false, "print out all kinds of debug data")
// Modes - exactly one of these is required
flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin")
flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin")
flagShow = flag.String("show", "", "path to JWT file or '-' to read from stdin")
)
func main() {
// Usage message if you ask for -help or if you mess up inputs.
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n")
flag.PrintDefaults()
}
// Parse command line options
flag.Parse()
// Do the thing. If something goes wrong, print error to stderr
// and exit with a non-zero status code
if err := start(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Figure out which thing to do and then do that
func start() error {
if *flagSign != "" {
return signToken()
} else if *flagVerify != "" {
return verifyToken()
} else if *flagShow != "" {
return showToken()
} else {
flag.Usage()
return fmt.Errorf("None of the required flags are present. What do you want me to do?")
}
}
// Helper func: Read input from specified file or stdin
func loadData(p string) ([]byte, error) {
if p == "" {
return nil, fmt.Errorf("No path specified")
}
var rdr io.Reader
if p == "-" {
rdr = os.Stdin
} else {
if f, err := os.Open(p); err == nil {
rdr = f
defer f.Close()
} else {
return nil, err
}
}
return ioutil.ReadAll(rdr)
}
// Print a json object in accordance with the prophecy (or the command line options)
func printJSON(j interface{}) error {
var out []byte
var err error
if *flagCompact == false {
out, err = json.MarshalIndent(j, "", " ")
} else {
out, err = json.Marshal(j)
}
if err == nil {
fmt.Println(string(out))
}
return err
}
// Verify a token and output the claims. This is a great example
// of how to verify and view a token.
func verifyToken() error {
// get the token
tokData, err := loadData(*flagVerify)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
}
// trim possible whitespace from token
tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
if *flagDebug {
fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
}
// Parse the token. Load the key from command line option
token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) {
data, err := loadData(*flagKey)
if err != nil {
return nil, err
}
if isEs() {
return jwt.ParseECPublicKeyFromPEM(data)
}
return data, nil
})
// Print some debug data
if *flagDebug && token != nil {
fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header)
fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims)
}
// Print an error if we can't parse for some reason
if err != nil {
return fmt.Errorf("Couldn't parse token: %v", err)
}
// Is token invalid?
if !token.Valid {
return fmt.Errorf("Token is invalid")
}
// Print the token details
if err := printJSON(token.Claims); err != nil {
return fmt.Errorf("Failed to output claims: %v", err)
}
return nil
}
// Create, sign, and output a token. This is a great, simple example of
// how to use this library to create and sign a token.
func signToken() error {
// get the token data from command line arguments
tokData, err := loadData(*flagSign)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
} else if *flagDebug {
fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData))
}
// parse the JSON of the claims
var claims jwt.MapClaims
if err := json.Unmarshal(tokData, &claims); err != nil {
return fmt.Errorf("Couldn't parse claims JSON: %v", err)
}
// get the key
var key interface{}
key, err = loadData(*flagKey)
if err != nil {
return fmt.Errorf("Couldn't read key: %v", err)
}
// get the signing alg
alg := jwt.GetSigningMethod(*flagAlg)
if alg == nil {
return fmt.Errorf("Couldn't find signing method: %v", *flagAlg)
}
// create a new token
token := jwt.NewWithClaims(alg, claims)
if isEs() {
if k, ok := key.([]byte); !ok {
return fmt.Errorf("Couldn't convert key data to key")
} else {
key, err = jwt.ParseECPrivateKeyFromPEM(k)
if err != nil {
return err
}
}
}
if out, err := token.SignedString(key); err == nil {
fmt.Println(out)
} else {
return fmt.Errorf("Error signing token: %v", err)
}
return nil
}
// showToken pretty-prints the token on the command line.
func showToken() error {
// get the token
tokData, err := loadData(*flagShow)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
}
// trim possible whitespace from token
tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
if *flagDebug {
fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
}
token, err := jwt.Parse(string(tokData), nil)
if token == nil {
return fmt.Errorf("malformed token: %v", err)
}
// Print the token details
fmt.Println("Header:")
if err := printJSON(token.Header); err != nil {
return fmt.Errorf("Failed to output header: %v", err)
}
fmt.Println("Claims:")
if err := printJSON(token.Claims); err != nil {
return fmt.Errorf("Failed to output claims: %v", err)
}
return nil
}
func isEs() bool {
return strings.HasPrefix(*flagAlg, "ES")
}

View File

@ -1,4 +0,0 @@
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
//
// See README.md for more info.
package jwt

View File

@ -1,147 +0,0 @@
package jwt
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"errors"
"math/big"
)
var (
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
)
// Implements the ECDSA family of signing methods signing methods
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash
KeySize int
CurveBits int
}
// Specific instances for EC256 and company
var (
SigningMethodES256 *SigningMethodECDSA
SigningMethodES384 *SigningMethodECDSA
SigningMethodES512 *SigningMethodECDSA
)
func init() {
// ES256
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
return SigningMethodES256
})
// ES384
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
return SigningMethodES384
})
// ES512
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
return SigningMethodES512
})
}
func (m *SigningMethodECDSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an ecdsa.PublicKey struct
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
// Get the key
var ecdsaKey *ecdsa.PublicKey
switch k := key.(type) {
case *ecdsa.PublicKey:
ecdsaKey = k
default:
return ErrInvalidKeyType
}
if len(sig) != 2*m.KeySize {
return ErrECDSAVerification
}
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
return nil
} else {
return ErrECDSAVerification
}
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an ecdsa.PrivateKey struct
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
// Get the key
var ecdsaKey *ecdsa.PrivateKey
switch k := key.(type) {
case *ecdsa.PrivateKey:
ecdsaKey = k
default:
return "", ErrInvalidKeyType
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return r, s
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
curveBits := ecdsaKey.Curve.Params().BitSize
if m.CurveBits != curveBits {
return "", ErrInvalidKey
}
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}
// We serialize the outpus (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return EncodeSegment(out), nil
} else {
return "", err
}
}

View File

@ -1,100 +0,0 @@
package jwt_test
import (
"crypto/ecdsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var ecdsaTestData = []struct {
name string
keys map[string]string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic ES256",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ",
"ES256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES384",
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld",
"ES384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES512",
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5",
"ES512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic ES256 invalid: foo => bar",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
"ES256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA public key: %v", err)
}
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA private key: %v", err)
}
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
}
}
}
}

View File

@ -1,67 +0,0 @@
package jwt
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
)
// Parse PEM encoded Elliptic Curve Private Key Structure
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
return nil, err
}
var pkey *ecdsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
return nil, ErrNotECPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *ecdsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, ErrNotECPublicKey
}
return pkey, nil
}

View File

@ -1,63 +0,0 @@
package jwt
import (
"errors"
)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
)
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
// Standard Claim validation errors
ValidationErrorAudience // AUD validation failed
ValidationErrorExpired // EXP validation failed
ValidationErrorIssuedAt // IAT validation failed
ValidationErrorIssuer // ISS validation failed
ValidationErrorNotValidYet // NBF validation failed
ValidationErrorId // JTI validation failed
ValidationErrorClaimsInvalid // Generic claims validation error
)
// Helper for constructing a ValidationError with a string error message
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
return &ValidationError{
text: errorText,
Errors: errorFlags,
}
}
// The error from Parse if token is not valid
type ValidationError struct {
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants
text string // errors that do not have a valid error just have text
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.Inner != nil {
return e.Inner.Error()
} else if e.text != "" {
return e.text
} else {
return "token is invalid"
}
return e.Inner.Error()
}
// No errors
func (e *ValidationError) valid() bool {
if e.Errors > 0 {
return false
}
return true
}

View File

@ -1,114 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
// Example (atypical) using the StandardClaims type by itself to parse a token.
// The StandardClaims type is designed to be embedded into your custom types
// to provide standard validation features. You can use it alone, but there's
// no way to retrieve other fields after parsing.
// See the CustomClaimsType example for intended usage.
func ExampleNewWithClaims_standardClaims() {
mySigningKey := []byte("AllYourBase")
// Create the Claims
claims := &jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleNewWithClaims_customClaimsType() {
mySigningKey := []byte("AllYourBase")
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// Create the Claims
claims := MyCustomClaims{
"bar",
jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_customClaimsType() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// sample token is expired. override time so it parses as valid
at(time.Unix(0, 0), func() {
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
})
// Output: bar 15000
}
// Override time value for tests. Restore default value after.
func at(t time.Time, f func()) {
jwt.TimeFunc = func() time.Time {
return t
}
f()
jwt.TimeFunc = time.Now
}
// An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

View File

@ -1,94 +0,0 @@
package jwt
import (
"crypto"
"crypto/hmac"
"errors"
)
// Implements the HMAC-SHA family of signing methods signing methods
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256 and company
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
ErrSignatureInvalid = errors.New("signature is invalid")
)
func init() {
// HS256
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
return SigningMethodHS256
})
// HS384
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
return SigningMethodHS384
})
// HS512
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
return SigningMethodHS512
})
}
func (m *SigningMethodHMAC) Alg() string {
return m.Name
}
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
// Verify the key is the right type
keyBytes, ok := key.([]byte)
if !ok {
return ErrInvalidKeyType
}
// Decode signature, for comparison
sig, err := DecodeSegment(signature)
if err != nil {
return err
}
// Can we use the specified hashing method?
if !m.Hash.Available() {
return ErrHashUnavailable
}
// This signing method is symmetric, so we validate the signature
// by reproducing the signature from the signing string and key, then
// comparing that against the provided signature.
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
if !hmac.Equal(sig, hasher.Sum(nil)) {
return ErrSignatureInvalid
}
// No validation errors. Signature is good.
return nil
}
// Implements the Sign method from SigningMethod for this signing method.
// Key must be []byte
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKey
}

View File

@ -1,64 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"time"
)
// For HMAC signing method, the key can be any []byte. It is recommended to generate
// a key using crypto/rand or something equivalent. You need the same key for signing
// and validating.
var hmacSampleSecret []byte
func init() {
// Load sample key data
if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil {
hmacSampleSecret = keyData
} else {
panic(e)
}
}
// Example creating, signing, and encoding a JWT token using the HMAC signing method
func ExampleNew_hmac() {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"foo": "bar",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU <nil>
}
// Example parsing and validating a token using the HMAC signing method
func ExampleParse_hmac() {
// sample token string taken from the New example
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU"
// Parse takes the token string and a function for looking up the key. The latter is especially
// useful if you use multiple keys for your application. The standard is to use 'kid' in the
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return hmacSampleSecret, nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Println(claims["foo"], claims["nbf"])
} else {
fmt.Println(err)
}
// Output: bar 1.4444784e+09
}

View File

@ -1,91 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var hmacTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"web sample",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS384",
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
"HS384",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS512",
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
"HS512",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"web sample: invalid",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
false,
},
}
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestHMACSign(t *testing.T) {
for _, data := range hmacTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}
func BenchmarkHS256Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey)
}
func BenchmarkHS384Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey)
}
func BenchmarkHS512Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey)
}

View File

@ -1,216 +0,0 @@
package jwt_test
// Example HTTP auth using asymmetric crypto/RSA keys
// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b
import (
"bytes"
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// location of the files used for signing and verification
const (
privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize
pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub
)
var (
verifyKey *rsa.PublicKey
signKey *rsa.PrivateKey
serverPort int
// storing sample username/password pairs
// don't do this on a real server
users = map[string]string{
"test": "known",
}
)
// read the key files before starting http handlers
func init() {
signBytes, err := ioutil.ReadFile(privKeyPath)
fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err)
verifyBytes, err := ioutil.ReadFile(pubKeyPath)
fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
fatal(err)
http.HandleFunc("/authenticate", authHandler)
http.HandleFunc("/restricted", restrictedHandler)
// Setup listener
listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
serverPort = listener.Addr().(*net.TCPAddr).Port
log.Println("Listening...")
go func() {
fatal(http.Serve(listener, nil))
}()
}
var start func()
func fatal(err error) {
if err != nil {
log.Fatal(err)
}
}
// Define some custom types were going to use within our tokens
type CustomerInfo struct {
Name string
Kind string
}
type CustomClaimsExample struct {
*jwt.StandardClaims
TokenType string
CustomerInfo
}
func Example_getTokenViaHTTP() {
// See func authHandler for an example auth handler that produces a token
res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{
"user": {"test"},
"pass": {"known"},
})
if err != nil {
fatal(err)
}
if res.StatusCode != 200 {
fmt.Println("Unexpected status code", res.StatusCode)
}
// Read the token out of the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
tokenString := strings.TrimSpace(buf.String())
// Parse the token
token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
fatal(err)
claims := token.Claims.(*CustomClaimsExample)
fmt.Println(claims.CustomerInfo.Name)
//Output: test
}
func Example_useTokenViaHTTP() {
// Make a sample token
// In a real world situation, this token will have been acquired from
// some other API call (see Example_getTokenViaHTTP)
token, err := createToken("foo")
fatal(err)
// Make request. See func restrictedHandler for example request processor
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil)
fatal(err)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
res, err := http.DefaultClient.Do(req)
fatal(err)
// Read the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
fmt.Println(buf.String())
// Output: Welcome, foo
}
func createToken(user string) (string, error) {
// create a signer for rsa 256
t := jwt.New(jwt.GetSigningMethod("RS256"))
// set our claims
t.Claims = &CustomClaimsExample{
&jwt.StandardClaims{
// set the expire time
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"level1",
CustomerInfo{user, "human"},
}
// Creat token string
return t.SignedString(signKey)
}
// reads the form values, checks them and creates the token
func authHandler(w http.ResponseWriter, r *http.Request) {
// make sure its post
if r.Method != "POST" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "No POST", r.Method)
return
}
user := r.FormValue("user")
pass := r.FormValue("pass")
log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass)
// check values
if user != "test" || pass != "known" {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintln(w, "Wrong info")
return
}
tokenString, err := createToken(user)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Sorry, error while Signing Token!")
log.Printf("Token Signing error: %v\n", err)
return
}
w.Header().Set("Content-Type", "application/jwt")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, tokenString)
}
// only accessible with a valid token
func restrictedHandler(w http.ResponseWriter, r *http.Request) {
// Get token from request
token, err := request.ParseFromRequestWithClaims(r, request.OAuth2Extractor, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
// If the token is missing or invalid, return error
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, "Invalid token:", err)
return
}
// Token is valid
fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name)
return
}

View File

@ -1,94 +0,0 @@
package jwt
import (
"encoding/json"
"errors"
// "fmt"
)
// Claims type that uses the map[string]interface{} for JSON decoding
// This is the default claims type if you don't supply one
type MapClaims map[string]interface{}
// Compares the aud claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyAudience(cmp string, req bool) bool {
aud, _ := m["aud"].(string)
return verifyAud(aud, cmp, req)
}
// Compares the exp claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
switch exp := m["exp"].(type) {
case float64:
return verifyExp(int64(exp), cmp, req)
case json.Number:
v, _ := exp.Int64()
return verifyExp(v, cmp, req)
}
return req == false
}
// Compares the iat claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
switch iat := m["iat"].(type) {
case float64:
return verifyIat(int64(iat), cmp, req)
case json.Number:
v, _ := iat.Int64()
return verifyIat(v, cmp, req)
}
return req == false
}
// Compares the iss claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
iss, _ := m["iss"].(string)
return verifyIss(iss, cmp, req)
}
// Compares the nbf claim against cmp.
// If required is false, this method will return true if the value matches or is unset
func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
switch nbf := m["nbf"].(type) {
case float64:
return verifyNbf(int64(nbf), cmp, req)
case json.Number:
v, _ := nbf.Int64()
return verifyNbf(v, cmp, req)
}
return req == false
}
// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
func (m MapClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
if m.VerifyExpiresAt(now, false) == false {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
if m.VerifyIssuedAt(now, false) == false {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if m.VerifyNotBefore(now, false) == false {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
if vErr.valid() {
return nil
}
return vErr
}

View File

@ -1,52 +0,0 @@
package jwt
// Implements the none signing method. This is required by the spec
// but you probably should never use it.
var SigningMethodNone *signingMethodNone
const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
var NoneSignatureTypeDisallowedError error
type signingMethodNone struct{}
type unsafeNoneMagicConstant string
func init() {
SigningMethodNone = &signingMethodNone{}
NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
return SigningMethodNone
})
}
func (m *signingMethodNone) Alg() string {
return "none"
}
// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
// accepting 'none' signing method
if _, ok := key.(unsafeNoneMagicConstant); !ok {
return NoneSignatureTypeDisallowedError
}
// If signing method is none, signature must be an empty string
if signature != "" {
return NewValidationError(
"'none' signing method with non-empty signature",
ValidationErrorSignatureInvalid,
)
}
// Accept 'none' signing method.
return nil
}
// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
if _, ok := key.(unsafeNoneMagicConstant); ok {
return "", nil
}
return "", NoneSignatureTypeDisallowedError
}

View File

@ -1,72 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"strings"
"testing"
)
var noneTestData = []struct {
name string
tokenString string
alg string
key interface{}
claims map[string]interface{}
valid bool
}{
{
"Basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic - no key",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
nil,
map[string]interface{}{"foo": "bar"},
false,
},
{
"Signed",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestNoneVerify(t *testing.T) {
for _, data := range noneTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], data.key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestNoneSign(t *testing.T) {
for _, data := range noneTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), data.key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}

View File

@ -1,131 +0,0 @@
package jwt
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
var err error
token := &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
if err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
}
// Verify signing method is in the required set
if p.ValidMethods != nil {
var signingMethodValid = false
var alg = token.Method.Alg()
for _, m := range p.ValidMethods {
if m == alg {
signingMethodValid = true
break
}
}
if !signingMethodValid {
// signing method is not in the listed set
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
}
}
// Lookup key
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}
vErr := &ValidationError{}
// Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
}
}
// Perform validation
token.Signature = parts[2]
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorSignatureInvalid
}
if vErr.valid() {
token.Valid = true
return token, nil
}
return token, vErr
}

View File

@ -1,261 +0,0 @@
package jwt_test
import (
"crypto/rsa"
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/test"
)
var keyFuncError error = fmt.Errorf("error loading key")
var (
jwtTestDefaultKey *rsa.PublicKey
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, keyFuncError }
nilKeyFunc jwt.Keyfunc = nil
)
func init() {
jwtTestDefaultKey = test.LoadRSAPublicKeyFromDisk("test/sample_key.pub")
}
var jwtTestData = []struct {
name string
tokenString string
keyfunc jwt.Keyfunc
claims jwt.Claims
valid bool
errors uint32
parser *jwt.Parser
}{
{
"basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
nil,
},
{
"basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorExpired,
nil,
},
{
"basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false,
jwt.ValidationErrorNotValidYet,
nil,
},
{
"expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
nil,
},
{
"basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nilKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"invalid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
&jwt.Parser{ValidMethods: []string{"HS256"}},
},
{
"valid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
},
{
"JSON Number",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": json.Number("123.4")},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"Standard Claims",
"",
defaultKeyFunc,
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
false,
jwt.ValidationErrorNotValidYet,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"SkipClaimsValidation during token parsing",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
true,
0,
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
},
}
func TestParser_Parse(t *testing.T) {
privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key")
// Iterate over test data set and run tests
for _, data := range jwtTestData {
// If the token string is blank, use helper function to generate string
if data.tokenString == "" {
data.tokenString = test.MakeSampleToken(data.claims, privateKey)
}
// Parse the token
var token *jwt.Token
var err error
var parser = data.parser
if parser == nil {
parser = new(jwt.Parser)
}
// Figure out correct claims type
switch data.claims.(type) {
case jwt.MapClaims:
token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc)
case *jwt.StandardClaims:
token, err = parser.ParseWithClaims(data.tokenString, &jwt.StandardClaims{}, data.keyfunc)
}
// Verify result matches expectation
if !reflect.DeepEqual(data.claims, token.Claims) {
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
}
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name)
}
if (err == nil && !token.Valid) || (err != nil && token.Valid) {
t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid", data.name)
}
if data.errors != 0 {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else {
ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error
if e := ve.Errors; e != data.errors {
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
}
if err.Error() == keyFuncError.Error() && ve.Inner != keyFuncError {
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, keyFuncError)
}
}
}
if data.valid && token.Signature == "" {
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
}
}
}
// Helper method for benchmarking various methods
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
t := jwt.New(method)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if _, err := t.SignedString(key); err != nil {
b.Fatal(err)
}
}
})
}

View File

@ -1,7 +0,0 @@
// Utility package for extracting JWT tokens from
// HTTP requests.
//
// The main function is ParseFromRequest and it's WithClaims variant.
// See examples for how to use the various Extractor implementations
// or roll your own.
package request

View File

@ -1,81 +0,0 @@
package request
import (
"errors"
"net/http"
)
// Errors
var (
ErrNoTokenInRequest = errors.New("no token present in request")
)
// Interface for extracting a token from an HTTP request.
// The ExtractToken method should return a token string or an error.
// If no token is present, you must return ErrNoTokenInRequest.
type Extractor interface {
ExtractToken(*http.Request) (string, error)
}
// Extractor for finding a token in a header. Looks at each specified
// header in order until there's a match
type HeaderExtractor []string
func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) {
// loop over header names and return the first one that contains data
for _, header := range e {
if ah := req.Header.Get(header); ah != "" {
return ah, nil
}
}
return "", ErrNoTokenInRequest
}
// Extract token from request arguments. This includes a POSTed form or
// GET URL arguments. Argument names are tried in order until there's a match.
// This extractor calls `ParseMultipartForm` on the request
type ArgumentExtractor []string
func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) {
// Make sure form is parsed
req.ParseMultipartForm(10e6)
// loop over arg names and return the first one that contains data
for _, arg := range e {
if ah := req.Form.Get(arg); ah != "" {
return ah, nil
}
}
return "", ErrNoTokenInRequest
}
// Tries Extractors in order until one returns a token string or an error occurs
type MultiExtractor []Extractor
func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
// loop over header names and return the first one that contains data
for _, extractor := range e {
if tok, err := extractor.ExtractToken(req); tok != "" {
return tok, nil
} else if err != ErrNoTokenInRequest {
return "", err
}
}
return "", ErrNoTokenInRequest
}
// Wrap an Extractor in this to post-process the value before it's handed off.
// See AuthorizationHeaderExtractor for an example
type PostExtractionFilter struct {
Extractor
Filter func(string) (string, error)
}
func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
if tok, err := e.Extractor.ExtractToken(req); tok != "" {
return e.Filter(tok)
} else {
return "", err
}
}

Some files were not shown because too many files have changed in this diff Show More