├── .eslintignore
├── internal
├── testdata
│ ├── disk
│ │ ├── embed.go
│ │ ├── file2.txt
│ │ ├── file.txt
│ │ └── under
│ │ │ └── sub
│ │ │ └── subfile
│ ├── embedded
│ │ ├── file.txt
│ │ ├── under
│ │ │ └── sub
│ │ │ │ └── subfile
│ │ └── embed.go
│ └── panic.txt
├── defaults
│ ├── defaults.go
│ └── defaults_test.go
├── fakesmtp
│ ├── connection.go
│ └── server.go
└── httpx
│ ├── content_type.go
│ └── content_type_test.go
├── plugins
├── plugdeps
│ ├── plugin_test.go
│ ├── pop.go
│ ├── command.go
│ ├── plugin.go
│ ├── plugins_test.go
│ ├── plugdeps_test.go
│ ├── plugins.go
│ └── plugdeps.go
├── log.go
├── events.go
├── log_debug.go
├── command.go
├── plugcmds
│ ├── plug_map_test.go
│ ├── available_test.go
│ ├── plug_map.go
│ └── available.go
├── plugins_test.go
├── cache.go
└── decorate.go
├── .github
├── CODEOWNERS
├── FUNDING.yml
└── workflows
│ ├── standard-stale.yml
│ └── standard-go-test.yml
├── app_test.go
├── .codecov.yml
├── .csslintrc
├── runtime
├── version.go
└── build.go
├── mail
├── body.go
├── internal
│ └── mail
│ │ ├── doc.go
│ │ ├── .gitignore
│ │ ├── mime.go
│ │ ├── .travis.yml
│ │ ├── errors.go
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE
│ │ ├── auth.go
│ │ ├── send.go
│ │ ├── CHANGELOG.md
│ │ └── README.md
├── attachment.go
├── sender.go
├── mail.go
├── mail_test.go
├── smtp_sender_test.go
├── message.go
├── smtp_sender.go
└── README.md
├── logger.go
├── binding
├── decoders
│ ├── time.go
│ ├── parse_time.go
│ ├── null_time.go
│ ├── decoders.go
│ ├── null_time_test.go
│ └── time_test.go
├── bindable.go
├── file.go
├── bindable_test.go
├── types.go
├── xml_request_type_binder.go
├── json_content_type_binder.go
├── html_content_type_binder.go
├── binding_test.go
├── request_binder.go
├── request_binder_test.go
├── file_request_type_binder.go
├── binding.go
└── file_test.go
├── request_data_test.go
├── request_data.go
├── error_templates.go
├── Dockerfile
├── .golangci.yml
├── render
├── renderer.go
├── render_test.go
├── json_test.go
├── xml_test.go
├── func_test.go
├── string_test.go
├── json.go
├── plain_test.go
├── template_engine.go
├── xml.go
├── helpers.go
├── string_map_test.go
├── plain.go
├── string.go
├── func.go
├── markdown_test.go
├── js.go
├── options.go
├── template_helpers.go
├── string_map.go
├── sse.go
├── render.go
├── download_test.go
├── html.go
├── template_helpers_test.go
├── html_test.go
├── download.go
└── js_test.go
├── .gitignore
├── response_test.go
├── .codeclimate.yml
├── worker
├── job.go
└── worker.go
├── method_override_test.go
├── servers
├── tls.go
├── simple.go
├── servers.go
└── listener.go
├── method_override.go
├── handler.go
├── Dockerfile.slim.build
├── buffalo.go
├── BACKERS.md
├── LICENSE.txt
├── route_info_test.go
├── Dockerfile.build
├── flash.go
├── home.go
├── options_test.go
├── not_found_test.go
├── plugins.go
├── response.go
├── session.go
├── session_test.go
├── notfound.prod.html
├── error.prod.html
├── routenamer.go
├── cookies_test.go
├── request_logger.go
├── cookies.go
├── app.go
├── events.go
├── go.mod
├── route.go
├── resource.go
├── fs.go
├── context.go
├── wrappers.go
├── wrappers_test.go
├── fs_test.go
├── grifts.go
├── route_info.go
└── flash_test.go
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 |
--------------------------------------------------------------------------------
/internal/testdata/disk/embed.go:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/internal/testdata/disk/file2.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/internal/testdata/disk/file.txt:
--------------------------------------------------------------------------------
1 | This file is on disk.
--------------------------------------------------------------------------------
/plugins/plugdeps/plugin_test.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
--------------------------------------------------------------------------------
/internal/testdata/embedded/file.txt:
--------------------------------------------------------------------------------
1 | This file is embedded.
--------------------------------------------------------------------------------
/internal/testdata/disk/under/sub/subfile:
--------------------------------------------------------------------------------
1 | This file is on disk/sub.
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Default owner
2 | * @gobuffalo/core-managers
--------------------------------------------------------------------------------
/internal/testdata/embedded/under/sub/subfile:
--------------------------------------------------------------------------------
1 | This file is on embedded/sub.
--------------------------------------------------------------------------------
/internal/testdata/panic.txt:
--------------------------------------------------------------------------------
1 | This file must not be accessible from buffalo.FS.
--------------------------------------------------------------------------------
/app_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | func voidHandler(c Context) error {
4 | return nil
5 | }
6 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | range: "60...70"
3 |
4 | status:
5 | project: false
6 | patch: false
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: markbates
4 | patreon: buffalo
5 |
--------------------------------------------------------------------------------
/.csslintrc:
--------------------------------------------------------------------------------
1 | --exclude-exts=.min.css
2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
3 |
--------------------------------------------------------------------------------
/runtime/version.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | // Version is the current version of the buffalo binary
4 | var Version = "v1.1.0"
5 |
--------------------------------------------------------------------------------
/plugins/log.go:
--------------------------------------------------------------------------------
1 | // +build !debug
2 |
3 | package plugins
4 |
5 | func log(_ string, fn func() error) error {
6 | return fn()
7 | }
8 |
--------------------------------------------------------------------------------
/plugins/plugdeps/pop.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | var pop = Plugin{
4 | Binary: "buffalo-pop",
5 | GoGet: "github.com/gobuffalo/buffalo-pop/v2",
6 | }
7 |
--------------------------------------------------------------------------------
/mail/body.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | // Body represents one of the bodies in the Message could be main or alternative
4 | type Body struct {
5 | Content string
6 | ContentType string
7 | }
8 |
--------------------------------------------------------------------------------
/internal/testdata/embedded/embed.go:
--------------------------------------------------------------------------------
1 | package embedded
2 |
3 | import (
4 | "embed"
5 | )
6 |
7 | //go:embed *
8 | var files embed.FS
9 |
10 | func FS() embed.FS {
11 | return files
12 | }
13 |
--------------------------------------------------------------------------------
/mail/internal/mail/doc.go:
--------------------------------------------------------------------------------
1 | // Package gomail provides a simple interface to compose emails and to mail them
2 | // efficiently.
3 | //
4 | // More info on Github: https://github.com/go-mail/mail
5 | //
6 | package mail
7 |
--------------------------------------------------------------------------------
/plugins/events.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | const (
4 | EvtSetupStarted = "buffalo-plugins:setup:started"
5 | EvtSetupErr = "buffalo-plugins:setup:err"
6 | EvtSetupFinished = "buffalo-plugins:setup:finished"
7 | )
8 |
--------------------------------------------------------------------------------
/logger.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "github.com/gobuffalo/logger"
5 | )
6 |
7 | // Logger interface is used throughout Buffalo
8 | // apps to log a whole manner of things.
9 | type Logger = logger.FieldLogger
10 |
--------------------------------------------------------------------------------
/mail/attachment.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import "io"
4 |
5 | // Attachment are files added into a email message
6 | type Attachment struct {
7 | Name string
8 | Reader io.Reader
9 | ContentType string
10 | Embedded bool
11 | }
12 |
--------------------------------------------------------------------------------
/binding/decoders/time.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | // TimeDecoderFn is a custom type decoder func for Time fields
4 | func TimeDecoderFn() func([]string) (interface{}, error) {
5 | return func(vals []string) (interface{}, error) {
6 | return parseTime(vals)
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/plugins/log_debug.go:
--------------------------------------------------------------------------------
1 | // +build debug
2 |
3 | package plugins
4 |
5 | import (
6 | "fmt"
7 | "time"
8 | )
9 |
10 | func log(name string, fn func() error) error {
11 | start := time.Now()
12 | defer fmt.Println(name, time.Now().Sub(start))
13 | return fn()
14 | }
15 |
--------------------------------------------------------------------------------
/binding/bindable.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import "net/http"
4 |
5 | // Bindable when implemented, on a type
6 | // will override any Binders that have been
7 | // configured when using buffalo#Context.Bind
8 | type Bindable interface {
9 | Bind(*http.Request) error
10 | }
11 |
--------------------------------------------------------------------------------
/.github/workflows/standard-stale.yml:
--------------------------------------------------------------------------------
1 | name: Standard Autocloser
2 |
3 | on:
4 | schedule:
5 | - cron: "30 1 * * *"
6 |
7 | jobs:
8 | call-standard-autocloser:
9 | name: Autocloser
10 | uses: gobuffalo/.github/.github/workflows/stale.yml@v1
11 | secrets: inherit
12 |
--------------------------------------------------------------------------------
/.github/workflows/standard-go-test.yml:
--------------------------------------------------------------------------------
1 | name: Standard Test
2 |
3 | on:
4 | push:
5 | branches: [main v1]
6 | pull_request:
7 |
8 | jobs:
9 | call-standard-test:
10 | name: Test
11 | uses: gobuffalo/.github/.github/workflows/go-test.yml@main
12 | secrets: inherit
13 |
--------------------------------------------------------------------------------
/mail/internal/mail/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Binaries for programs and plugins
4 | *.exe
5 | *.dll
6 | *.so
7 | *.dylib
8 |
9 | # Test binary, build with `go test -c`
10 | *.test
11 |
12 | # Output of the go coverage tool, specifically when used with LiteIDE
13 | *.out
14 |
15 |
16 | # IDE's
17 | .idea/
18 |
--------------------------------------------------------------------------------
/request_data_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func Test_newRequestData(t *testing.T) {
10 |
11 | r := require.New(t)
12 | ts := newRequestData()
13 | r.NotNil(ts)
14 | r.NotNil(ts.moot)
15 | r.NotNil(ts.d)
16 | }
17 |
--------------------------------------------------------------------------------
/request_data.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import "sync"
4 |
5 | type requestData struct {
6 | d map[string]interface{}
7 | moot *sync.RWMutex
8 | }
9 |
10 | func newRequestData() *requestData {
11 | return &requestData{
12 | d: make(map[string]interface{}),
13 | moot: &sync.RWMutex{},
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/error_templates.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | _ "embed" // needed to embed the templates.
5 | )
6 |
7 | var (
8 | //go:embed error.dev.html
9 | devErrorTmpl string
10 |
11 | //go:embed error.prod.html
12 | prodErrorTmpl string
13 |
14 | //go:embed notfound.prod.html
15 | prodNotFoundTmpl string
16 | )
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gobuffalo/buffalo:latest
2 |
3 | ARG CODECOV_TOKEN
4 |
5 | ENV GOPROXY https://proxy.golang.org
6 | ENV BP /src/buffalo
7 |
8 | RUN rm -rf $BP
9 | RUN mkdir -p $BP
10 |
11 | WORKDIR $BP
12 | COPY . .
13 |
14 | RUN go mod tidy
15 | RUN go test -tags "sqlite integration_test" -cover -race -v ./...
16 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | fast: true
2 | linters:
3 | enable-all: false
4 | disable-all: false
5 | enable:
6 | - govet
7 | - golint
8 | - goimports
9 | - deadcode
10 | - typecheck
11 | - ineffassign
12 | - misspell
13 | - nakedret
14 | - unconvert
15 | - megacheck
16 | - varcheck
17 | disable:
18 | - errcheck
--------------------------------------------------------------------------------
/mail/sender.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | // Sender interface for any upcoming mailers.
4 | type Sender interface {
5 | Send(Message) error
6 | }
7 |
8 | // BatchSender interface for sending batch or single mail
9 | type BatchSender interface {
10 | Sender
11 | SendBatch(messages ...Message) (errorsByMessages []error, generalError error)
12 | }
13 |
--------------------------------------------------------------------------------
/render/renderer.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import "io"
4 |
5 | // Renderer interface that must be satisfied to be used with
6 | // buffalo.Context.Render
7 | type Renderer interface {
8 | ContentType() string
9 | Render(io.Writer, Data) error
10 | }
11 |
12 | // Data type to be provided to the Render function on the
13 | // Renderer interface.
14 | type Data map[string]interface{}
15 |
--------------------------------------------------------------------------------
/mail/internal/mail/mime.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "mime"
5 | "mime/quotedprintable"
6 | "strings"
7 | )
8 |
9 | var newQPWriter = quotedprintable.NewWriter
10 |
11 | type mimeEncoder struct {
12 | mime.WordEncoder
13 | }
14 |
15 | var (
16 | bEncoding = mimeEncoder{mime.BEncoding}
17 | qEncoding = mimeEncoder{mime.QEncoding}
18 | lastIndexByte = strings.LastIndexByte
19 | )
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | doc
4 | tmp
5 | pkg
6 | *.gem
7 | *.pid
8 | coverage
9 | coverage.data
10 | *.pbxuser
11 | *.mode1v3
12 | .svn
13 | profile
14 | .console_history
15 | .sass-cache/*
16 | .rake_tasks~
17 | *.log.lck
18 | solr/
19 | .jhw-cache/
20 | jhw.*
21 | *.sublime*
22 | node_modules/
23 | dist/
24 | generated/
25 | .vendor/
26 | bin/*
27 | gin-bin
28 | .idea/
29 | .vscode/settings.json
30 |
--------------------------------------------------------------------------------
/render/render_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "github.com/psanford/memfs"
5 | )
6 |
7 | type Widget struct {
8 | Name string
9 | }
10 |
11 | func (w Widget) ToPath() string {
12 | return w.Name
13 | }
14 |
15 | func NewEngine() *Engine {
16 | return New(Options{
17 | TemplatesFS: memfs.New(),
18 | AssetsFS: memfs.New(),
19 | })
20 | }
21 |
22 | type rendFriend func(string, RendererFunc) Renderer
23 |
--------------------------------------------------------------------------------
/mail/internal/mail/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.2
5 | - 1.3
6 | - 1.4
7 | - 1.5
8 | - 1.6
9 | - 1.7
10 | - 1.8
11 | - 1.9
12 | - master
13 |
14 | # safelist
15 | branches:
16 | only:
17 | - master
18 | - v2
19 |
20 | notifications:
21 | email: false
22 |
23 | before_install:
24 | - mkdir -p $GOPATH/src/gopkg.in &&
25 | ln -s ../github.com/go-mail/mail $GOPATH/src/gopkg.in/mail.v2
26 |
--------------------------------------------------------------------------------
/binding/file.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "mime/multipart"
5 | )
6 |
7 | // File holds information regarding an uploaded file
8 | type File struct {
9 | multipart.File
10 | *multipart.FileHeader
11 | }
12 |
13 | // Valid if there is an actual uploaded file
14 | func (f File) Valid() bool {
15 | return f.File != nil
16 | }
17 |
18 | func (f File) String() string {
19 | if f.File == nil {
20 | return ""
21 | }
22 | return f.Filename
23 | }
24 |
--------------------------------------------------------------------------------
/response_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_Response_MultipleWrite(t *testing.T) {
12 | r := require.New(t)
13 | resWr := httptest.NewRecorder()
14 | res := Response{
15 | ResponseWriter: resWr,
16 | }
17 |
18 | res.WriteHeader(http.StatusOK)
19 | res.WriteHeader(http.StatusInternalServerError)
20 |
21 | r.Equal(res.Status, http.StatusOK)
22 | }
23 |
--------------------------------------------------------------------------------
/mail/internal/mail/errors.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import "fmt"
4 |
5 | // A SendError represents the failure to transmit a Message, detailing the cause
6 | // of the failure and index of the Message within a batch.
7 | type SendError struct {
8 | // Index specifies the index of the Message within a batch.
9 | Index uint
10 | Cause error
11 | }
12 |
13 | func (err *SendError) Error() string {
14 | return fmt.Sprintf("gomail: could not send email %d: %v",
15 | err.Index+1, err.Cause)
16 | }
17 |
--------------------------------------------------------------------------------
/render/json_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_JSON(t *testing.T) {
12 | r := require.New(t)
13 |
14 | e := NewEngine()
15 |
16 | re := e.JSON(Data{"hello": "world"})
17 | r.Equal("application/json; charset=utf-8", re.ContentType())
18 |
19 | bb := &bytes.Buffer{}
20 |
21 | r.NoError(re.Render(bb, nil))
22 | r.Equal(`{"hello":"world"}`, strings.TrimSpace(bb.String()))
23 | }
24 |
--------------------------------------------------------------------------------
/plugins/plugdeps/command.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import "encoding/json"
4 |
5 | // Command is the plugin command you want to control further
6 | type Command struct {
7 | Name string `toml:"name" json:"name"`
8 | Flags []string `toml:"flags,omitempty" json:"flags,omitempty"`
9 | Commands []Command `toml:"command,omitempty" json:"commands,omitempty"`
10 | }
11 |
12 | // String implementation of fmt.Stringer
13 | func (p Command) String() string {
14 | b, _ := json.Marshal(p)
15 | return string(b)
16 | }
17 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | golint:
4 | enabled: true
5 | checks:
6 | GoLint/Naming/MixedCaps:
7 | enabled: false
8 | govet:
9 | enabled: true
10 | gofmt:
11 | enabled: true
12 | fixme:
13 | enabled: true
14 | ratings:
15 | paths:
16 | - "**.go"
17 | exclude_paths:
18 | - "generators/*/templates/**/*"
19 | - "buffalo/cmd/filetests/*"
20 | - "examples/**/*"
21 | - "vendor/**/*"
22 | - "**/*_test.go"
23 | - "*_test.go"
24 | - "**_test.go"
25 | - "middleware_test.go"
26 |
--------------------------------------------------------------------------------
/binding/bindable_test.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | type orbison struct {
12 | bound bool
13 | }
14 |
15 | func (o *orbison) Bind(req *http.Request) error {
16 | o.bound = true
17 | return nil
18 | }
19 |
20 | func Test_Bindable(t *testing.T) {
21 | r := require.New(t)
22 |
23 | req := httptest.NewRequest("GET", "/", nil)
24 | o := &orbison{}
25 | r.False(o.bound)
26 | r.NoError(Exec(req, o))
27 | r.True(o.bound)
28 | }
29 |
--------------------------------------------------------------------------------
/binding/decoders/parse_time.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | func parseTime(vals []string) (time.Time, error) {
8 | var t time.Time
9 | var err error
10 |
11 | // don't try to parse empty time values, it will raise an error
12 | if len(vals) == 0 || vals[0] == "" {
13 | return t, nil
14 | }
15 |
16 | for _, layout := range timeFormats {
17 | t, err = time.Parse(layout, vals[0])
18 | if err == nil {
19 | return t, nil
20 | }
21 | }
22 |
23 | if err != nil {
24 | return t, err
25 | }
26 |
27 | return t, nil
28 | }
29 |
--------------------------------------------------------------------------------
/binding/types.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // ContenTypeBinder are those capable of handling a request type like JSON or XML
8 | type ContenTypeBinder interface {
9 | BinderFunc() Binder
10 | ContentTypes() []string
11 | }
12 |
13 | // Binder takes a request and binds it to an interface.
14 | // If there is a problem it should return an error.
15 | type Binder func(*http.Request, interface{}) error
16 |
17 | // CustomTypeDecoder converts a custom type from the request into its exact type.
18 | type CustomTypeDecoder func([]string) (interface{}, error)
19 |
--------------------------------------------------------------------------------
/internal/defaults/defaults.go:
--------------------------------------------------------------------------------
1 | package defaults
2 |
3 | func String(s1, s2 string) string {
4 | if s1 == "" {
5 | return s2
6 | }
7 | return s1
8 | }
9 |
10 | func Int(i1, i2 int) int {
11 | if i1 == 0 {
12 | return i2
13 | }
14 | return i1
15 | }
16 |
17 | func Int64(i1, i2 int64) int64 {
18 | if i1 == 0 {
19 | return i2
20 | }
21 | return i1
22 | }
23 |
24 | func Float32(i1, i2 float32) float32 {
25 | if i1 == 0.0 {
26 | return i2
27 | }
28 | return i1
29 | }
30 |
31 | func Float64(i1, i2 float64) float64 {
32 | if i1 == 0.0 {
33 | return i2
34 | }
35 | return i1
36 | }
37 |
--------------------------------------------------------------------------------
/render/xml_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_XML(t *testing.T) {
12 | r := require.New(t)
13 |
14 | type user struct {
15 | Name string
16 | }
17 |
18 | e := NewEngine()
19 |
20 | re := e.XML(user{Name: "Mark"})
21 | r.Equal("application/xml; charset=utf-8", re.ContentType())
22 |
23 | bb := &bytes.Buffer{}
24 |
25 | r.NoError(re.Render(bb, nil))
26 | r.Equal("\n\n Mark\n", strings.TrimSpace(bb.String()))
27 | }
28 |
--------------------------------------------------------------------------------
/worker/job.go:
--------------------------------------------------------------------------------
1 | package worker
2 |
3 | import "encoding/json"
4 |
5 | // Args are the arguments passed into a job
6 | type Args map[string]interface{}
7 |
8 | func (a Args) String() string {
9 | b, _ := json.Marshal(a)
10 | return string(b)
11 | }
12 |
13 | // Job to be processed by a Worker
14 | type Job struct {
15 | // Queue the job should be placed into
16 | Queue string
17 | // Args that will be passed to the Handler when run
18 | Args Args
19 | // Handler that will be run by the worker
20 | Handler string
21 | }
22 |
23 | func (j Job) String() string {
24 | b, _ := json.Marshal(j)
25 | return string(b)
26 | }
27 |
--------------------------------------------------------------------------------
/method_override_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "testing"
7 |
8 | "github.com/gobuffalo/buffalo/render"
9 | "github.com/gobuffalo/httptest"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func Test_MethodOverride(t *testing.T) {
14 | r := require.New(t)
15 |
16 | a := New(Options{})
17 | a.PUT("/", func(c Context) error {
18 | return c.Render(http.StatusOK, render.String("you put me!"))
19 | })
20 |
21 | w := httptest.New(a)
22 | res := w.HTML("/").Post(url.Values{"_method": []string{"PUT"}})
23 | r.Equal(http.StatusOK, res.Code)
24 | r.Equal("you put me!", res.Body.String())
25 | }
26 |
--------------------------------------------------------------------------------
/render/func_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_Func(t *testing.T) {
12 | r := require.New(t)
13 |
14 | table := []rendFriend{
15 | Func,
16 | New(Options{}).Func,
17 | }
18 |
19 | for _, tt := range table {
20 | bb := &bytes.Buffer{}
21 |
22 | re := tt("foo/bar", func(w io.Writer, data Data) error {
23 | _, err := w.Write([]byte(data["name"].(string)))
24 | return err
25 | })
26 |
27 | r.Equal("foo/bar", re.ContentType())
28 | err := re.Render(bb, Data{"name": "Mark"})
29 | r.NoError(err)
30 | r.Equal("Mark", bb.String())
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/render/string_test.go:
--------------------------------------------------------------------------------
1 | package render_test
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/gobuffalo/buffalo/render"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_String(t *testing.T) {
12 | r := require.New(t)
13 |
14 | j := render.New(render.Options{}).String
15 |
16 | re := j("<%= name %>")
17 | r.Equal("text/plain; charset=utf-8", re.ContentType())
18 |
19 | var examples = []string{"Mark", "Jém"}
20 | for _, example := range examples {
21 | example := example
22 | bb := &bytes.Buffer{}
23 | err := re.Render(bb, map[string]interface{}{"name": example})
24 | r.NoError(err)
25 | r.Equal(example, bb.String())
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/binding/xml_request_type_binder.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "encoding/xml"
5 | "net/http"
6 | )
7 |
8 | // XMLRequestTypeBinder is in charge of binding XML request types.
9 | type XMLRequestTypeBinder struct{}
10 |
11 | // BinderFunc returns the Binder for this RequestTypeBinder
12 | func (xm XMLRequestTypeBinder) BinderFunc() Binder {
13 | return func(req *http.Request, value interface{}) error {
14 | return xml.NewDecoder(req.Body).Decode(value)
15 | }
16 | }
17 |
18 | // ContentTypes that will be wired to this the XML Binder
19 | func (xm XMLRequestTypeBinder) ContentTypes() []string {
20 | return []string{
21 | "application/xml",
22 | "text/xml",
23 | "xml",
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/internal/fakesmtp/connection.go:
--------------------------------------------------------------------------------
1 | package fakesmtp
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "net"
7 | )
8 |
9 | //Connection of a client with our server
10 | type Connection struct {
11 | conn net.Conn
12 | address string
13 | time int64
14 | bufin *bufio.Reader
15 | bufout *bufio.Writer
16 | }
17 |
18 | //write something to the client on the connection
19 | func (c *Connection) write(s string) {
20 | c.bufout.WriteString(s + "\r\n")
21 | c.bufout.Flush()
22 | }
23 |
24 | //read a string from the connected client
25 | func (c *Connection) read() string {
26 | reply, err := c.bufin.ReadString('\n')
27 |
28 | if err != nil {
29 | fmt.Println("e ", err)
30 | }
31 | return reply
32 | }
33 |
--------------------------------------------------------------------------------
/internal/httpx/content_type.go:
--------------------------------------------------------------------------------
1 | package httpx
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/gobuffalo/buffalo/internal/defaults"
8 | )
9 |
10 | func ContentType(req *http.Request) string {
11 | ct := defaults.String(req.Header.Get("Content-Type"), req.Header.Get("Accept"))
12 | ct = strings.TrimSpace(ct)
13 | var cts []string
14 | if strings.Contains(ct, ",") {
15 | cts = strings.Split(ct, ",")
16 | } else {
17 | cts = strings.Split(ct, ";")
18 | }
19 | for _, c := range cts {
20 | c = strings.TrimSpace(c)
21 | if strings.HasPrefix(c, "*/*") {
22 | continue
23 | }
24 | return strings.ToLower(c)
25 | }
26 | if ct == "*/*" {
27 | return ""
28 | }
29 | return ct
30 | }
31 |
--------------------------------------------------------------------------------
/render/json.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | )
7 |
8 | type jsonRenderer struct {
9 | value interface{}
10 | }
11 |
12 | func (s jsonRenderer) ContentType() string {
13 | return "application/json; charset=utf-8"
14 | }
15 |
16 | func (s jsonRenderer) Render(w io.Writer, data Data) error {
17 | return json.NewEncoder(w).Encode(s.value)
18 | }
19 |
20 | // JSON renders the value using the "application/json"
21 | // content type.
22 | func JSON(v interface{}) Renderer {
23 | return jsonRenderer{value: v}
24 | }
25 |
26 | // JSON renders the value using the "application/json"
27 | // content type.
28 | func (e *Engine) JSON(v interface{}) Renderer {
29 | return JSON(v)
30 | }
31 |
--------------------------------------------------------------------------------
/binding/json_content_type_binder.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | )
7 |
8 | // JSONContentTypeBinder is in charge of binding JSON request types.
9 | type JSONContentTypeBinder struct{}
10 |
11 | // BinderFunc returns the Binder for this JSONRequestTypeBinder
12 | func (js JSONContentTypeBinder) BinderFunc() Binder {
13 | return func(req *http.Request, value interface{}) error {
14 | return json.NewDecoder(req.Body).Decode(value)
15 | }
16 | }
17 |
18 | // ContentTypes that will be wired to this the JSON Binder
19 | func (js JSONContentTypeBinder) ContentTypes() []string {
20 | return []string{
21 | "application/json",
22 | "text/json",
23 | "json",
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/mail/internal/mail/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thank you for contributing to Gomail! Here are a few guidelines:
2 |
3 | ## Bugs
4 |
5 | If you think you found a bug, create an issue and supply the minimum amount
6 | of code triggering the bug so it can be reproduced.
7 |
8 |
9 | ## Fixing a bug
10 |
11 | If you want to fix a bug, you can send a pull request. It should contains a
12 | new test or update an existing one to cover that bug.
13 |
14 |
15 | ## New feature proposal
16 |
17 | If you think Gomail lacks a feature, you can open an issue or send a pull
18 | request. I want to keep Gomail code and API as simple as possible so please
19 | describe your needs so we can discuss whether this feature should be added to
20 | Gomail or not.
21 |
--------------------------------------------------------------------------------
/plugins/command.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | // Command that the plugin supplies
4 | type Command struct {
5 | // Name "foo"
6 | Name string `json:"name"`
7 | // UseCommand "bar"
8 | UseCommand string `json:"use_command"`
9 | // BuffaloCommand "generate"
10 | BuffaloCommand string `json:"buffalo_command"`
11 | // Description "generates a foo"
12 | Description string `json:"description,omitempty"`
13 | Aliases []string `json:"aliases,omitempty"`
14 | Binary string `json:"-"`
15 | Flags []string `json:"flags,omitempty"`
16 | // Filters events to listen to ("" or "*") is all events
17 | ListenFor string `json:"listen_for,omitempty"`
18 | }
19 |
20 | // Commands is a slice of Command
21 | type Commands []Command
22 |
--------------------------------------------------------------------------------
/render/plain_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/psanford/memfs"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_Plain(t *testing.T) {
12 | r := require.New(t)
13 |
14 | rootFS := memfs.New()
15 | r.NoError(rootFS.WriteFile("test.txt", []byte("<%= name %>"), 0644))
16 |
17 | e := NewEngine()
18 | e.TemplatesFS = rootFS
19 |
20 | re := e.Plain("test.txt")
21 | r.Equal("text/plain; charset=utf-8", re.ContentType())
22 |
23 | var examples = []string{"Mark", "Jém"}
24 | for _, example := range examples {
25 | example := example
26 | bb := &bytes.Buffer{}
27 | r.NoError(re.Render(bb, Data{"name": example}))
28 | r.Equal(example, bb.String())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/servers/tls.go:
--------------------------------------------------------------------------------
1 | package servers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | // TLS server
10 | type TLS struct {
11 | *http.Server
12 | CertFile string
13 | KeyFile string
14 | }
15 |
16 | // SetAddr sets the servers address, if it hasn't already been set
17 | func (s *TLS) SetAddr(addr string) {
18 | if s.Server.Addr == "" {
19 | s.Server.Addr = addr
20 | }
21 | }
22 |
23 | // String returns a string representation of a Listener
24 | func (s *TLS) String() string {
25 | return fmt.Sprintf("TLS server on %s", s.Server.Addr)
26 | }
27 |
28 | // Start the server
29 | func (s *TLS) Start(c context.Context, h http.Handler) error {
30 | s.Handler = h
31 | return s.ListenAndServeTLS(s.CertFile, s.KeyFile)
32 | }
33 |
--------------------------------------------------------------------------------
/method_override.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gobuffalo/buffalo/internal/defaults"
7 | )
8 |
9 | // MethodOverride is the default implementation for the
10 | // Options#MethodOverride. By default it will look for a form value
11 | // name `_method` and change the request method if that is
12 | // present and the original request is of type "POST". This is
13 | // added automatically when using `New` Buffalo, unless
14 | // an alternative is defined in the Options.
15 | func MethodOverride(res http.ResponseWriter, req *http.Request) {
16 | if req.Method == "POST" {
17 | req.Method = defaults.String(req.FormValue("_method"), "POST")
18 | req.Form.Del("_method")
19 | req.PostForm.Del("_method")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/render/template_engine.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | )
7 |
8 | // TemplateEngine needs to be implemented for a template system to be able to be used with Buffalo.
9 | type TemplateEngine func(input string, data map[string]interface{}, helpers map[string]interface{}) (string, error)
10 |
11 | // GoTemplateEngine implements the TemplateEngine interface for using standard Go templates
12 | func GoTemplateEngine(input string, data map[string]interface{}, helpers map[string]interface{}) (string, error) {
13 | t := template.New(input)
14 |
15 | t, err := t.Parse(input)
16 | if err != nil {
17 | return "", err
18 | }
19 |
20 | bb := &bytes.Buffer{}
21 | err = t.Execute(bb, data)
22 | return bb.String(), err
23 | }
24 |
--------------------------------------------------------------------------------
/handler.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | // Handler is the basis for all of Buffalo. A Handler
4 | // will be given a Context interface that represents the
5 | // give request/response. It is the responsibility of the
6 | // Handler to handle the request/response correctly. This
7 | // could mean rendering a template, JSON, etc... or it could
8 | // mean returning an error.
9 | /*
10 | func (c Context) error {
11 | return c.Render(http.StatusOK, render.String("Hello World!"))
12 | }
13 |
14 | func (c Context) error {
15 | return c.Redirect(http.StatusMovedPermanently, "http://github.com/gobuffalo/buffalo")
16 | }
17 |
18 | func (c Context) error {
19 | return c.Error(http.StatusUnprocessableEntity, fmt.Errorf("oops!!"))
20 | }
21 | */
22 | type Handler func(Context) error
23 |
--------------------------------------------------------------------------------
/render/xml.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "encoding/xml"
5 | "io"
6 | )
7 |
8 | type xmlRenderer struct {
9 | value interface{}
10 | }
11 |
12 | func (s xmlRenderer) ContentType() string {
13 | return "application/xml; charset=utf-8"
14 | }
15 |
16 | func (s xmlRenderer) Render(w io.Writer, data Data) error {
17 | io.WriteString(w, xml.Header)
18 | enc := xml.NewEncoder(w)
19 | enc.Indent("", " ")
20 | return enc.Encode(s.value)
21 | }
22 |
23 | // XML renders the value using the "application/xml"
24 | // content type.
25 | func XML(v interface{}) Renderer {
26 | return xmlRenderer{value: v}
27 | }
28 |
29 | // XML renders the value using the "application/xml"
30 | // content type.
31 | func (e *Engine) XML(v interface{}) Renderer {
32 | return XML(v)
33 | }
34 |
--------------------------------------------------------------------------------
/servers/simple.go:
--------------------------------------------------------------------------------
1 | package servers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | // Simple server
10 | type Simple struct {
11 | *http.Server
12 | }
13 |
14 | // SetAddr sets the servers address, if it hasn't already been set
15 | func (s *Simple) SetAddr(addr string) {
16 | if s.Server.Addr == "" {
17 | s.Server.Addr = addr
18 | }
19 | }
20 |
21 | // String returns a string representation of a Simple server
22 | func (s *Simple) String() string {
23 | return fmt.Sprintf("simple server on %s", s.Server.Addr)
24 | }
25 |
26 | // Start the server
27 | func (s *Simple) Start(c context.Context, h http.Handler) error {
28 | s.Handler = h
29 | return s.ListenAndServe()
30 | }
31 |
32 | // New Simple server
33 | func New() *Simple {
34 | return &Simple{
35 | Server: &http.Server{},
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/plugins/plugdeps/plugin.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gobuffalo/meta"
7 | )
8 |
9 | // Plugin represents a Go plugin for Buffalo applications
10 | type Plugin struct {
11 | Binary string `toml:"binary" json:"binary"`
12 | GoGet string `toml:"go_get,omitempty" json:"go_get,omitempty"`
13 | Local string `toml:"local,omitempty" json:"local,omitempty"`
14 | Commands []Command `toml:"command,omitempty" json:"commands,omitempty"`
15 | Tags meta.BuildTags `toml:"tags,omitempty" json:"tags,omitempty"`
16 | }
17 |
18 | // String implementation of fmt.Stringer
19 | func (p Plugin) String() string {
20 | b, _ := json.Marshal(p)
21 | return string(b)
22 | }
23 |
24 | func (p Plugin) key() string {
25 | return p.Binary + p.GoGet + p.Local
26 | }
27 |
--------------------------------------------------------------------------------
/mail/mail.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "context"
5 | "sync"
6 |
7 | "github.com/gobuffalo/buffalo"
8 | "github.com/gobuffalo/buffalo/render"
9 | )
10 |
11 | // NewMessage builds a new message.
12 | func NewMessage() Message {
13 | return Message{
14 | Context: context.Background(),
15 | Headers: map[string]string{},
16 | Data: render.Data{},
17 | moot: &sync.RWMutex{},
18 | }
19 | }
20 |
21 | // NewFromData builds a new message with raw template data given
22 | func NewFromData(data render.Data) Message {
23 | d := render.Data{}
24 | for k, v := range data {
25 | d[k] = v
26 | }
27 | m := NewMessage()
28 | m.Data = d
29 | return m
30 | }
31 |
32 | // New builds a new message with the current buffalo.Context
33 | func New(c buffalo.Context) Message {
34 | m := NewFromData(c.Data())
35 | m.Context = c
36 | return m
37 | }
38 |
--------------------------------------------------------------------------------
/render/helpers.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "html/template"
5 | "net/http"
6 |
7 | "github.com/gobuffalo/helpers/forms"
8 | "github.com/gobuffalo/helpers/forms/bootstrap"
9 | "github.com/gobuffalo/plush/v5"
10 | "github.com/gobuffalo/tags/v3"
11 | )
12 |
13 | func init() {
14 | plush.Helpers.Add("paginator", func(pagination interface{}, opts map[string]interface{}, help plush.HelperContext) (template.HTML, error) {
15 | if opts["path"] == nil {
16 | if req, ok := help.Value("request").(*http.Request); ok {
17 | opts["path"] = req.URL.String()
18 | }
19 | }
20 | t, err := tags.Pagination(pagination, opts)
21 | if err != nil {
22 | return "", err
23 | }
24 | return t.HTML(), nil
25 | })
26 | plush.Helpers.Add(forms.RemoteFormKey, bootstrap.RemoteForm)
27 | plush.Helpers.Add(forms.RemoteFormForKey, bootstrap.RemoteFormFor)
28 | }
29 |
--------------------------------------------------------------------------------
/Dockerfile.slim.build:
--------------------------------------------------------------------------------
1 | FROM golang:1.17-alpine
2 |
3 | EXPOSE 3000
4 |
5 | ENV GOPROXY=https://proxy.golang.org
6 |
7 | RUN apk add --no-cache --upgrade apk-tools \
8 | && apk add --no-cache bash curl openssl git build-base nodejs npm sqlite sqlite-dev mysql-client vim postgresql libpq postgresql-contrib libc6-compat
9 |
10 | # Installing linter
11 | RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \
12 | | sh -s -- -b $(go env GOPATH)/bin v1.24.0
13 |
14 | # Installing Yarn
15 | RUN npm i -g --no-progress yarn \
16 | && yarn config set yarn-offline-mirror /npm-packages-offline-cache \
17 | && yarn config set yarn-offline-mirror-pruning true
18 |
19 | # Installing buffalo binary
20 | RUN go install github.com/gobuffalo/cli/cmd/buffalo@latest
21 | RUN go get github.com/gobuffalo/buffalo-pop/v2
22 |
23 | RUN mkdir /src
24 | WORKDIR /src
25 |
--------------------------------------------------------------------------------
/buffalo.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package buffalo is a Go web development eco-system, designed to make your life easier.
3 |
4 | Buffalo helps you to generate a web project that already has everything from front-end (JavaScript, SCSS, etc.) to back-end (database, routing, etc.) already hooked up and ready to run. From there it provides easy APIs to build your web application quickly in Go.
5 |
6 | Buffalo **isn't just a framework**, it's a holistic web development environment and project structure that **lets developers get straight to the business** of, well, building their business.
7 | */
8 | package buffalo
9 |
10 | // we need to import the runtime package
11 | // as its needed by `buffalo build` and without
12 | // this import the package doesn't get vendored
13 | // by go mod vendor or by dep. this import fixes
14 | // this problem.
15 | import _ "github.com/gobuffalo/buffalo/runtime" // Load the runtime package variables
16 |
--------------------------------------------------------------------------------
/binding/decoders/null_time.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | import "github.com/gobuffalo/nulls"
4 |
5 | // NullTimeDecoderFn is a custom type decoder func for null.Time fields
6 | func NullTimeDecoderFn() func([]string) (interface{}, error) {
7 | return func(vals []string) (interface{}, error) {
8 | var ti nulls.Time
9 |
10 | // If vals is empty, return a nulls.Time with Valid = false (i.e. NULL).
11 | // The parseTime() function called below does this check as well, but
12 | // because it doesn't return an error in the case where vals is empty,
13 | // we have no way to determine from its response that the nulls.Time
14 | // should actually be NULL.
15 | if len(vals) == 0 || vals[0] == "" {
16 | return ti, nil
17 | }
18 |
19 | t, err := parseTime(vals)
20 | if err != nil {
21 | return ti, err
22 | }
23 |
24 | ti.Time = t
25 | ti.Valid = true
26 |
27 | return ti, nil
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/binding/html_content_type_binder.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/monoculum/formam"
7 | )
8 |
9 | // HTMLContentTypeBinder is in charge of binding HTML request types.
10 | type HTMLContentTypeBinder struct {
11 | decoder *formam.Decoder
12 | }
13 |
14 | // ContentTypes that will be used to identify HTML requests
15 | func (ht HTMLContentTypeBinder) ContentTypes() []string {
16 | return []string{
17 | "application/html",
18 | "text/html",
19 | "application/x-www-form-urlencoded",
20 | "html",
21 | }
22 | }
23 |
24 | // BinderFunc that will take care of the HTML binding
25 | func (ht HTMLContentTypeBinder) BinderFunc() Binder {
26 | return func(req *http.Request, i interface{}) error {
27 | err := req.ParseForm()
28 | if err != nil {
29 | return err
30 | }
31 |
32 | if err := ht.decoder.Decode(req.Form, i); err != nil {
33 | return err
34 | }
35 |
36 | return nil
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/servers/servers.go:
--------------------------------------------------------------------------------
1 | package servers
2 |
3 | import (
4 | "context"
5 | "net"
6 | "net/http"
7 | )
8 |
9 | // Server allows for custom server implementations
10 | type Server interface {
11 | Shutdown(context.Context) error
12 | Start(context.Context, http.Handler) error
13 | SetAddr(string)
14 | }
15 |
16 | // Wrap converts a standard *http.Server to a buffalo.Server
17 | func Wrap(s *http.Server) Server {
18 | return &Simple{Server: s}
19 | }
20 |
21 | // WrapTLS Server converts a standard *http.Server to a buffalo.Server
22 | // but makes sure it is run with TLS.
23 | func WrapTLS(s *http.Server, certFile string, keyFile string) Server {
24 | return &TLS{
25 | Server: s,
26 | CertFile: certFile,
27 | KeyFile: keyFile,
28 | }
29 | }
30 |
31 | // WrapListener wraps an *http.Server and a net.Listener
32 | func WrapListener(s *http.Server, l net.Listener) Server {
33 | return &Listener{
34 | Server: s,
35 | Listener: l,
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/runtime/build.go:
--------------------------------------------------------------------------------
1 | package runtime
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | // BuildInfo holds information about the build
10 | type BuildInfo struct {
11 | Version string `json:"version"`
12 | Time time.Time `json:"-"`
13 | }
14 |
15 | // String implements fmt.String
16 | func (b BuildInfo) String() string {
17 | return fmt.Sprintf("%s (%s)", b.Version, b.Time)
18 | }
19 |
20 | var build = BuildInfo{
21 | Version: "",
22 | Time: time.Time{},
23 | }
24 |
25 | // Build returns the information about the current build
26 | // of the application. In development mode this will almost
27 | // always run zero values for BuildInfo.
28 | func Build() BuildInfo {
29 | return build
30 | }
31 |
32 | var so sync.Once
33 |
34 | // SetBuild allows the setting of build information only once.
35 | // This is typically managed by the binary built by `buffalo build`.
36 | func SetBuild(b BuildInfo) {
37 | so.Do(func() {
38 | build = b
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/render/string_map_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "sort"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func Test_stringMap(t *testing.T) {
11 | r := require.New(t)
12 |
13 | sm := &stringMap{}
14 |
15 | sm.Store("a", `A`)
16 |
17 | s, ok := sm.Load("a")
18 | r.True(ok)
19 | r.Equal(`A`, s)
20 |
21 | s, ok = sm.LoadOrStore("b", `B`)
22 | r.True(ok)
23 | r.Equal(`B`, s)
24 |
25 | s, ok = sm.LoadOrStore("b", `BB`)
26 | r.True(ok)
27 | r.Equal(`B`, s)
28 |
29 | var keys []string
30 |
31 | sm.Range(func(key string, value string) bool {
32 | keys = append(keys, key)
33 | return true
34 | })
35 |
36 | sort.Strings(keys)
37 |
38 | r.Equal(sm.Keys(), keys)
39 |
40 | sm.Delete("b")
41 | r.Equal([]string{"a", "b"}, keys)
42 |
43 | sm.Delete("b")
44 | _, ok = sm.Load("b")
45 | r.False(ok)
46 |
47 | func(m *stringMap) {
48 | m.Store("c", `C`)
49 | }(sm)
50 | s, ok = sm.Load("c")
51 | r.True(ok)
52 | r.Equal(`C`, s)
53 | }
54 |
--------------------------------------------------------------------------------
/internal/httpx/content_type_test.go:
--------------------------------------------------------------------------------
1 | package httpx
2 |
3 | import (
4 | "net/http/httptest"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func Test_ContentType(t *testing.T) {
11 | r := require.New(t)
12 |
13 | table := []struct {
14 | Header string
15 | Value string
16 | Expected string
17 | }{
18 | {"content-type", "a", "a"},
19 | {"Content-Type", "c,d", "c"},
20 | {"Content-Type", "e;f", "e"},
21 | {"Content-Type", "", ""},
22 | {"Accept", "", ""},
23 | {"Accept", "*/*", ""},
24 | {"Accept", "*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript", "text/javascript"},
25 | {"accept", "text/javascript,application/javascript,application/ecmascript,application/x-ecmascript", "text/javascript"},
26 | }
27 |
28 | for _, tt := range table {
29 | req := httptest.NewRequest("GET", "/", nil)
30 | req.Header.Set(tt.Header, tt.Value)
31 | r.Equal(tt.Expected, ContentType(req))
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/binding/decoders/decoders.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | import (
4 | "sync"
5 | "time"
6 | )
7 |
8 | var (
9 | lock = &sync.RWMutex{}
10 |
11 | // timeFormats are the base time formats supported by the time.Time and
12 | // nulls.Time Decoders you can prepend custom formats to this list
13 | // by using RegisterTimeFormats.
14 | timeFormats = []string{
15 | time.RFC3339,
16 | "01/02/2006",
17 | "2006-01-02",
18 | "2006-01-02T15:04",
19 | time.ANSIC,
20 | time.UnixDate,
21 | time.RubyDate,
22 | time.RFC822,
23 | time.RFC822Z,
24 | time.RFC850,
25 | time.RFC1123,
26 | time.RFC1123Z,
27 | time.RFC3339Nano,
28 | time.Kitchen,
29 | time.Stamp,
30 | time.StampMilli,
31 | time.StampMicro,
32 | time.StampNano,
33 | }
34 | )
35 |
36 | // RegisterTimeFormats allows to add custom time layouts that
37 | // the binder will be able to use for decoding.
38 | func RegisterTimeFormats(layouts ...string) {
39 | lock.Lock()
40 | defer lock.Unlock()
41 |
42 | timeFormats = append(layouts, timeFormats...)
43 | }
44 |
--------------------------------------------------------------------------------
/mail/mail_test.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "html/template"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/gobuffalo/buffalo"
9 | "github.com/gobuffalo/buffalo/render"
10 | "github.com/gobuffalo/httptest"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func Test_NewFromData(t *testing.T) {
15 | r := require.New(t)
16 | m := NewFromData(map[string]interface{}{
17 | "foo": "bar",
18 | })
19 | r.Equal("bar", m.Data["foo"])
20 | }
21 |
22 | func Test_New(t *testing.T) {
23 | r := require.New(t)
24 |
25 | var m Message
26 | app := buffalo.New(buffalo.Options{})
27 | app.GET("/", func(c buffalo.Context) error {
28 | c.Set("foo", "bar")
29 | m = New(c)
30 | return c.Render(http.StatusOK, render.String(""))
31 | })
32 | w := httptest.New(app)
33 | w.HTML("/").Get()
34 |
35 | r.NotNil(m)
36 | r.Equal("bar", m.Data["foo"])
37 | rp, ok := m.Data["rootPath"].(buffalo.RouteHelperFunc)
38 | r.True(ok)
39 | x, err := rp(map[string]interface{}{})
40 | r.NoError(err)
41 | r.Equal(template.HTML("/"), x)
42 | }
43 |
--------------------------------------------------------------------------------
/render/plain.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | // Plain renders the named files using the 'text/html'
4 | // content type and the github.com/gobuffalo/plush
5 | // package for templating. If more than 1 file is provided
6 | // the second file will be considered a "layout" file
7 | // and the first file will be the "content" file which will
8 | // be placed into the "layout" using "<%= yield %>".
9 | func Plain(names ...string) Renderer {
10 | e := New(Options{})
11 | return e.Plain(names...)
12 | }
13 |
14 | // Plain renders the named files using the 'text/plain'
15 | // content type and the github.com/gobuffalo/plush
16 | // package for templating. If more than 1 file is provided
17 | // the second file will be considered a "layout" file
18 | // and the first file will be the "content" file which will
19 | // be placed into the "layout" using "<%= yield %>".
20 | func (e *Engine) Plain(names ...string) Renderer {
21 | hr := &templateRenderer{
22 | Engine: e,
23 | contentType: "text/plain; charset=utf-8",
24 | names: names,
25 | }
26 | return hr
27 | }
28 |
--------------------------------------------------------------------------------
/BACKERS.md:
--------------------------------------------------------------------------------
1 | # Financial Backers of the Buffalo Project
2 |
3 | Buffalo is a community-driven project that is run by individuals who believe that Buffalo is the way to quickly, and easily, build high quality, scalable applications in Go.
4 |
5 | Financial contributions to the Buffalo go towards ongoing development costs, servers, swag, conferences, etc...
6 |
7 | If you, or your company, use Buffalo, please consider supporting this effort to make rapid web development in Go, simple, easy, and fun!
8 |
9 | [http://patreon.com/buffalo](http://patreon.com/buffalo)
10 |
11 | ---
12 |
13 | ## Platinum Sponsors
14 |
15 | * **[Gopher Guides](https://www.gopherguides.com)**
16 | * **[PaperCall.io](https://www.papercall.io)**
17 | * [Your Company Here](http://patreon.com/buffalo)
18 |
19 | ### Gold Sponsors
20 |
21 | * [Your Company Here](http://patreon.com/buffalo)
22 |
23 | ### Premium Backers
24 |
25 | * [Your Company Here](http://patreon.com/buffalo)
26 |
27 | #### Generous Backers
28 |
29 | * **[Zhorty](https://zhorty.com)**
30 | * [Your Company Here](http://patreon.com/buffalo)
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2016 Mark Bates
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 |
--------------------------------------------------------------------------------
/servers/listener.go:
--------------------------------------------------------------------------------
1 | package servers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net"
7 | "net/http"
8 | )
9 |
10 | // Listener server for using a pre-defined net.Listener
11 | type Listener struct {
12 | *http.Server
13 | Listener net.Listener
14 | }
15 |
16 | // SetAddr sets the servers address, if it hasn't already been set
17 | func (s *Listener) SetAddr(addr string) {
18 | if s.Server.Addr == "" {
19 | s.Server.Addr = addr
20 | }
21 | }
22 |
23 | // String returns a string representation of a Listener
24 | func (s *Listener) String() string {
25 | return fmt.Sprintf("listener on %s", s.Server.Addr)
26 | }
27 |
28 | // Start the server
29 | func (s *Listener) Start(c context.Context, h http.Handler) error {
30 | s.Handler = h
31 | return s.Serve(s.Listener)
32 | }
33 |
34 | // UnixSocket returns a new Listener on that address
35 | func UnixSocket(addr string) (*Listener, error) {
36 | listener, err := net.Listen("unix", addr)
37 | if err != nil {
38 | return nil, err
39 | }
40 | return &Listener{
41 | Server: &http.Server{},
42 | Listener: listener,
43 | }, nil
44 | }
45 |
--------------------------------------------------------------------------------
/plugins/plugcmds/plug_map_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
2 |
3 | package plugcmds
4 |
5 | import (
6 | "sort"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_plugMap(t *testing.T) {
13 | r := require.New(t)
14 |
15 | sm := &plugMap{}
16 |
17 | sm.Store("a", plug{})
18 |
19 | s, ok := sm.Load("a")
20 | r.True(ok)
21 | r.Equal(plug{}, s)
22 |
23 | s, ok = sm.LoadOrStore("b", plug{})
24 | r.True(ok)
25 | r.Equal(plug{}, s)
26 |
27 | s, ok = sm.LoadOrStore("b", plug{})
28 | r.True(ok)
29 | r.Equal(plug{}, s)
30 |
31 | var keys []string
32 |
33 | sm.Range(func(key string, value plug) bool {
34 | keys = append(keys, key)
35 | return true
36 | })
37 |
38 | sort.Strings(keys)
39 |
40 | r.Equal(sm.Keys(), keys)
41 |
42 | sm.Delete("b")
43 | r.Equal([]string{"a", "b"}, keys)
44 |
45 | sm.Delete("b")
46 | _, ok = sm.Load("b")
47 | r.False(ok)
48 | p := plug{
49 | BuffaloCommand: "foo",
50 | }
51 | func(m *plugMap) {
52 | m.Store("c", p)
53 | }(sm)
54 | s, ok = sm.Load("c")
55 | r.True(ok)
56 | r.Equal(p, s)
57 | }
58 |
--------------------------------------------------------------------------------
/plugins/plugdeps/plugins_test.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_Plugins_Encode(t *testing.T) {
13 | r := require.New(t)
14 |
15 | bb := &bytes.Buffer{}
16 |
17 | plugs := New()
18 | plugs.Add(pop, heroku, local)
19 |
20 | r.NoError(plugs.Encode(bb))
21 |
22 | fmt.Println(bb.String())
23 | act := strings.TrimSpace(bb.String())
24 | exp := strings.TrimSpace(eToml)
25 | r.Equal(exp, act)
26 | }
27 |
28 | func Test_Plugins_Decode(t *testing.T) {
29 | r := require.New(t)
30 |
31 | plugs := New()
32 | r.NoError(plugs.Decode(strings.NewReader(eToml)))
33 |
34 | names := []string{"buffalo-hello.rb", "buffalo-heroku", "buffalo-pop"}
35 | list := plugs.List()
36 |
37 | r.Len(list, len(names))
38 | for i, p := range list {
39 | r.Equal(names[i], p.Binary)
40 | }
41 | }
42 |
43 | func Test_Plugins_Remove(t *testing.T) {
44 | r := require.New(t)
45 |
46 | plugs := New()
47 | plugs.Add(pop, heroku)
48 | r.Len(plugs.List(), 2)
49 | plugs.Remove(pop)
50 | r.Len(plugs.List(), 1)
51 | }
52 |
--------------------------------------------------------------------------------
/internal/defaults/defaults_test.go:
--------------------------------------------------------------------------------
1 | package defaults
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func Test_String(t *testing.T) {
10 | a := assert.New(t)
11 |
12 | a.Equal(String("", "foo"), "foo")
13 | a.Equal(String("bar", "foo"), "bar")
14 | var s string
15 | a.Equal(String(s, "foo"), "foo")
16 | }
17 |
18 | func Test_Int(t *testing.T) {
19 | a := assert.New(t)
20 |
21 | a.Equal(Int(0, 1), 1)
22 | a.Equal(Int(2, 1), 2)
23 | var s int
24 | a.Equal(Int(s, 1), 1)
25 | }
26 |
27 | func Test_Int64(t *testing.T) {
28 | a := assert.New(t)
29 |
30 | a.Equal(Int64(0, 1), int64(1))
31 | a.Equal(Int64(2, 1), int64(2))
32 | var s int64
33 | a.Equal(Int64(s, 1), int64(1))
34 | }
35 |
36 | func Test_Float32(t *testing.T) {
37 | a := assert.New(t)
38 |
39 | a.Equal(Float32(0, 1), float32(1))
40 | a.Equal(Float32(2, 1), float32(2))
41 | var s float32
42 | a.Equal(Float32(s, 1), float32(1))
43 | }
44 |
45 | func Test_Float64(t *testing.T) {
46 | a := assert.New(t)
47 |
48 | a.Equal(Float64(0, 1), float64(1))
49 | a.Equal(Float64(2, 1), float64(2))
50 | var s float64
51 | a.Equal(Float64(s, 1), float64(1))
52 | }
53 |
--------------------------------------------------------------------------------
/mail/internal/mail/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Alexandre Cesaro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/mail/internal/mail/auth.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/smtp"
7 | )
8 |
9 | // loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
10 | type loginAuth struct {
11 | username string
12 | password string
13 | host string
14 | }
15 |
16 | func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
17 | if !server.TLS {
18 | advertised := false
19 | for _, mechanism := range server.Auth {
20 | if mechanism == "LOGIN" {
21 | advertised = true
22 | break
23 | }
24 | }
25 | if !advertised {
26 | return "", nil, fmt.Errorf("gomail: unencrypted connection")
27 | }
28 | }
29 | if server.Name != a.host {
30 | return "", nil, fmt.Errorf("gomail: wrong host name")
31 | }
32 | return "LOGIN", nil, nil
33 | }
34 |
35 | func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
36 | if !more {
37 | return nil, nil
38 | }
39 |
40 | switch {
41 | case bytes.Equal(fromServer, []byte("Username:")):
42 | return []byte(a.username), nil
43 | case bytes.Equal(fromServer, []byte("Password:")):
44 | return []byte(a.password), nil
45 | default:
46 | return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/render/string.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | )
7 |
8 | type stringRenderer struct {
9 | *Engine
10 | body string
11 | }
12 |
13 | func (s stringRenderer) ContentType() string {
14 | return "text/plain; charset=utf-8"
15 | }
16 |
17 | func (s stringRenderer) Render(w io.Writer, data Data) error {
18 | te, ok := s.TemplateEngines["text"]
19 | if !ok {
20 | return fmt.Errorf("could not find a template engine for text")
21 | }
22 | t, err := te(s.body, data, s.Helpers)
23 | if err != nil {
24 | return err
25 | }
26 | _, err = w.Write([]byte(t))
27 | return err
28 | }
29 |
30 | // String renderer that will run the string through
31 | // the github.com/gobuffalo/plush package and return
32 | // "text/plain" as the content type.
33 | func String(s string, args ...interface{}) Renderer {
34 | e := New(Options{})
35 | return e.String(s, args...)
36 | }
37 |
38 | // String renderer that will run the string through
39 | // the github.com/gobuffalo/plush package and return
40 | // "text/plain" as the content type.
41 | func (e *Engine) String(s string, args ...interface{}) Renderer {
42 | if len(args) > 0 {
43 | s = fmt.Sprintf(s, args...)
44 | }
45 | return stringRenderer{
46 | Engine: e,
47 | body: s,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/render/func.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import "io"
4 |
5 | // RendererFunc is the interface for the the function
6 | // needed by the Func renderer.
7 | type RendererFunc func(io.Writer, Data) error
8 |
9 | type funcRenderer struct {
10 | contentType string
11 | renderFunc RendererFunc
12 | }
13 |
14 | // ContentType returns the content type for this render.
15 | // Examples would be "text/html" or "application/json".
16 | func (s funcRenderer) ContentType() string {
17 | return s.contentType
18 | }
19 |
20 | // Render the provided Data to the provider Writer using the
21 | // RendererFunc provide.
22 | func (s funcRenderer) Render(w io.Writer, data Data) error {
23 | return s.renderFunc(w, data)
24 | }
25 |
26 | // Func renderer allows for easily building one of renderers
27 | // using just a RendererFunc and not having to build a whole
28 | // implementation of the Render interface.
29 | func Func(s string, fn RendererFunc) Renderer {
30 | return funcRenderer{
31 | contentType: s,
32 | renderFunc: fn,
33 | }
34 | }
35 |
36 | // Func renderer allows for easily building one of renderers
37 | // using just a RendererFunc and not having to build a whole
38 | // implementation of the Render interface.
39 | func (e *Engine) Func(s string, fn RendererFunc) Renderer {
40 | return Func(s, fn)
41 | }
42 |
--------------------------------------------------------------------------------
/plugins/plugcmds/available_test.go:
--------------------------------------------------------------------------------
1 | package plugcmds
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/gobuffalo/events"
9 | "github.com/spf13/cobra"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func Test_Available_Add(t *testing.T) {
14 | r := require.New(t)
15 |
16 | a := NewAvailable()
17 | err := a.Add("generate", &cobra.Command{
18 | Use: "foo",
19 | Short: "generates foo",
20 | Aliases: []string{"f"},
21 | })
22 | r.NoError(err)
23 |
24 | r.Len(a.Commands(), 2)
25 | }
26 |
27 | func Test_Available_Encode(t *testing.T) {
28 | r := require.New(t)
29 |
30 | bb := &bytes.Buffer{}
31 |
32 | a := NewAvailable()
33 | err := a.Add("generate", &cobra.Command{
34 | Use: "foo",
35 | Short: "generates foo",
36 | Aliases: []string{"f"},
37 | })
38 | r.NoError(err)
39 |
40 | r.NoError(a.Encode(bb))
41 | const exp = `[{"name":"foo","use_command":"foo","buffalo_command":"generate","description":"generates foo","aliases":["f"]}]`
42 | r.Equal(exp, strings.TrimSpace(bb.String()))
43 | }
44 |
45 | func Test_Available_Listen(t *testing.T) {
46 | r := require.New(t)
47 |
48 | a := NewAvailable()
49 | err := a.Listen(func(e events.Event) error {
50 | return nil
51 | })
52 | r.NoError(err)
53 |
54 | r.Len(a.Commands(), 2)
55 | }
56 |
--------------------------------------------------------------------------------
/route_info_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "net/http"
7 | "testing"
8 |
9 | "github.com/gobuffalo/buffalo/render"
10 | "github.com/gobuffalo/httptest"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func Test_RouteInfo_ServeHTTP_SQL_Error(t *testing.T) {
15 | r := require.New(t)
16 |
17 | app := New(Options{})
18 | app.GET("/good", func(c Context) error {
19 | return c.Render(http.StatusOK, render.String("hi"))
20 | })
21 |
22 | app.GET("/bad", func(c Context) error {
23 | return sql.ErrNoRows
24 | })
25 |
26 | app.GET("/bad-2", func(c Context) error {
27 | return sql.ErrTxDone
28 | })
29 |
30 | app.GET("/gone-unwrap", func(c Context) error {
31 | return c.Error(http.StatusGone, sql.ErrTxDone)
32 | })
33 |
34 | app.GET("/gone-wrap", func(c Context) error {
35 | return c.Error(http.StatusGone, fmt.Errorf("some error wrapping here: %w", sql.ErrNoRows))
36 | })
37 |
38 | w := httptest.New(app)
39 |
40 | res := w.HTML("/good").Get()
41 | r.Equal(http.StatusOK, res.Code)
42 |
43 | res = w.HTML("/bad").Get()
44 | r.Equal(http.StatusNotFound, res.Code)
45 |
46 | res = w.HTML("/gone-wrap").Get()
47 | r.Equal(http.StatusGone, res.Code)
48 |
49 | res = w.HTML("/gone-unwrap").Get()
50 | r.Equal(http.StatusGone, res.Code)
51 | }
52 |
--------------------------------------------------------------------------------
/binding/decoders/null_time_test.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/gobuffalo/nulls"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func Test_NullTimeCustomDecoder_Decode(t *testing.T) {
12 | r := require.New(t)
13 |
14 | testCases := []struct {
15 | input string
16 | expected time.Time
17 | expValid bool
18 | expectErr bool
19 | }{
20 | {
21 | input: "2017-01-01",
22 | expected: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),
23 | expValid: true,
24 | },
25 | {
26 | input: "2018-07-13T15:34",
27 | expected: time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),
28 | expValid: true,
29 | },
30 | {
31 | input: "2018-20-10T30:15",
32 | expected: time.Time{},
33 | expValid: false,
34 | },
35 | {
36 | input: "",
37 | expected: time.Time{},
38 | expValid: false,
39 | },
40 | }
41 |
42 | for _, testCase := range testCases {
43 |
44 | tt, err := NullTimeDecoderFn()([]string{testCase.input})
45 | r.IsType(tt, nulls.Time{})
46 | nt := tt.(nulls.Time)
47 |
48 | if testCase.expectErr {
49 | r.Error(err)
50 | r.Equal(nt.Valid, false)
51 | continue
52 | }
53 |
54 | r.Equal(testCase.expected, nt.Time)
55 | r.Equal(testCase.expValid, nt.Valid)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/render/markdown_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/psanford/memfs"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | const mdTemplate = "my-template.md"
13 |
14 | func Test_MD_WithoutLayout(t *testing.T) {
15 | r := require.New(t)
16 |
17 | rootFS := memfs.New()
18 | r.NoError(rootFS.WriteFile(mdTemplate, []byte("<%= name %>"), 0644))
19 |
20 | e := NewEngine()
21 | e.TemplatesFS = rootFS
22 |
23 | h := e.HTML(mdTemplate)
24 | r.Equal("text/html; charset=utf-8", h.ContentType())
25 | bb := &bytes.Buffer{}
26 |
27 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
28 | r.Equal("
Mark
", strings.TrimSpace(bb.String()))
29 | }
30 |
31 | func Test_MD_WithLayout(t *testing.T) {
32 | r := require.New(t)
33 |
34 | rootFS := memfs.New()
35 | r.NoError(rootFS.WriteFile(mdTemplate, []byte("<%= name %>"), 0644))
36 | r.NoError(rootFS.WriteFile(htmlLayout, []byte("<%= yield %>"), 0644))
37 |
38 | e := NewEngine()
39 | e.TemplatesFS = rootFS
40 | e.HTMLLayout = htmlLayout
41 |
42 | h := e.HTML(mdTemplate)
43 | r.Equal("text/html; charset=utf-8", h.ContentType())
44 | bb := &bytes.Buffer{}
45 |
46 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
47 | r.Equal("Mark
\n", strings.TrimSpace(bb.String()))
48 | }
49 |
--------------------------------------------------------------------------------
/render/js.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | // JavaScript renders the named files using the 'application/javascript'
4 | // content type and the github.com/gobuffalo/plush
5 | // package for templating. If more than 1 file is provided
6 | // the second file will be considered a "layout" file
7 | // and the first file will be the "content" file which will
8 | // be placed into the "layout" using "<%= yield %>".
9 | func JavaScript(names ...string) Renderer {
10 | e := New(Options{})
11 | return e.JavaScript(names...)
12 | }
13 |
14 | // JavaScript renders the named files using the 'application/javascript'
15 | // content type and the github.com/gobuffalo/plush
16 | // package for templating. If more than 1 file is provided
17 | // the second file will be considered a "layout" file
18 | // and the first file will be the "content" file which will
19 | // be placed into the "layout" using "<%= yield %>". If no
20 | // second file is provided and an `JavaScriptLayout` is specified
21 | // in the options, then that layout file will be used
22 | // automatically.
23 | func (e *Engine) JavaScript(names ...string) Renderer {
24 | if e.JavaScriptLayout != "" && len(names) == 1 {
25 | names = append(names, e.JavaScriptLayout)
26 | }
27 | hr := &templateRenderer{
28 | Engine: e,
29 | contentType: "application/javascript",
30 | names: names,
31 | }
32 | return hr
33 | }
34 |
--------------------------------------------------------------------------------
/binding/decoders/time_test.go:
--------------------------------------------------------------------------------
1 | package decoders
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestParseTimeErrorParsing(t *testing.T) {
11 | r := require.New(t)
12 |
13 | _, err := parseTime([]string{"this is sparta"})
14 | r.Error(err)
15 | }
16 |
17 | func TestParseTime(t *testing.T) {
18 | r := require.New(t)
19 |
20 | testCases := []struct {
21 | input string
22 | expected time.Time
23 | expectErr bool
24 | }{
25 | {
26 | input: "2017-01-01",
27 | expected: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),
28 | expectErr: false,
29 | },
30 | {
31 | input: "2018-07-13T15:34",
32 | expected: time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),
33 | expectErr: false,
34 | },
35 | {
36 | input: "2018-20-10T30:15",
37 | expected: time.Time{},
38 | expectErr: true,
39 | },
40 | }
41 |
42 | for _, tc := range testCases {
43 | tt, err := parseTime([]string{tc.input})
44 | if !tc.expectErr {
45 | r.NoError(err)
46 | }
47 |
48 | r.Equal(tc.expected, tt)
49 | }
50 | }
51 |
52 | func TestParseTimeConflicting(t *testing.T) {
53 | r := require.New(t)
54 |
55 | RegisterTimeFormats("2006-02-01")
56 | tt, err := parseTime([]string{"2017-01-10"})
57 |
58 | r.NoError(err)
59 | expected := time.Date(2017, time.October, 1, 0, 0, 0, 0, time.UTC)
60 | r.Equal(expected, tt)
61 | }
62 |
--------------------------------------------------------------------------------
/plugins/plugins_test.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | import (
4 | "context"
5 | "os"
6 | "strings"
7 | "testing"
8 | "time"
9 |
10 | "github.com/gobuffalo/envy"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestAskBin_respectsTimeout(t *testing.T) {
15 | r := require.New(t)
16 |
17 | from, err := envy.MustGet("BUFFALO_PLUGIN_PATH")
18 | if err != nil {
19 | t.Skipf("BUFFALO_PLUGIN_PATH not set.")
20 | return
21 | }
22 |
23 | if fileEntries, err := os.ReadDir(from); err == nil {
24 | found := false
25 | for _, e := range fileEntries {
26 | if strings.HasPrefix(e.Name(), "buffalo-") {
27 | from = e.Name()
28 | found = true
29 | break
30 | }
31 | }
32 | if !found {
33 | t.Skipf("no plugins found")
34 | return
35 | }
36 | } else {
37 | r.Error(err, "plugin path not able to be read")
38 | return
39 | }
40 |
41 | const tooShort = time.Millisecond
42 | impossible, cancel := context.WithTimeout(context.Background(), tooShort)
43 | defer cancel()
44 |
45 | done := make(chan struct{})
46 | go func() {
47 | askBin(impossible, from)
48 | close(done)
49 | }()
50 |
51 | select {
52 | case <-time.After(tooShort + 80*time.Millisecond):
53 | r.Fail("did not time-out quickly enough")
54 | case <-done:
55 | t.Log("timed-out successfully")
56 | }
57 |
58 | if _, ok := findInCache(from); ok {
59 | r.Fail("expected plugin not to be added to cache on failure, but it was in cache")
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Dockerfile.build:
--------------------------------------------------------------------------------
1 | FROM golang:1.17
2 |
3 | EXPOSE 3000
4 |
5 | ENV GOPROXY=https://proxy.golang.org
6 |
7 | RUN apt-get update \
8 | && apt-get install -y -q build-essential sqlite3 libsqlite3-dev postgresql libpq-dev vim
9 |
10 | # Installing Node 12
11 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash
12 | RUN apt-get update && apt-get install nodejs
13 |
14 | # Installing Postgres
15 | RUN sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' \
16 | && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | apt-key add - \
17 | && apt-get update \
18 | && apt-get install -y -q postgresql postgresql-contrib libpq-dev\
19 | && rm -rf /var/lib/apt/lists/* \
20 | && service postgresql start && \
21 | # Setting up password for postgres
22 | su -c "psql -c \"ALTER USER postgres WITH PASSWORD 'postgres';\"" - postgres
23 |
24 | # Installing yarn
25 | RUN npm install -g --no-progress yarn \
26 | && yarn config set yarn-offline-mirror /npm-packages-offline-cache \
27 | && yarn config set yarn-offline-mirror-pruning true
28 |
29 | # Install golangci
30 | RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
31 | # Installing buffalo binary
32 | RUN go install github.com/gobuffalo/cli/cmd/buffalo@latest
33 | RUN go get github.com/gobuffalo/buffalo-pop/v2
34 |
35 | RUN mkdir /src
36 | WORKDIR /src
37 |
--------------------------------------------------------------------------------
/worker/worker.go:
--------------------------------------------------------------------------------
1 | package worker
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | // Handler function that will be run by the worker and given
9 | // a slice of arguments
10 | type Handler func(Args) error
11 |
12 | // Worker interface that needs to be implemented to be considered
13 | // a "worker"
14 | type Worker interface {
15 | // Start the worker with the given context
16 | Start(context.Context) error
17 | // Stop the worker
18 | Stop() error
19 | // Perform a job as soon as possibly
20 | Perform(Job) error
21 | // PerformAt performs a job at a particular time
22 | PerformAt(Job, time.Time) error
23 | // PerformIn performs a job after waiting for a specified amount of time
24 | PerformIn(Job, time.Duration) error
25 | // Register a Handler
26 | Register(string, Handler) error
27 | }
28 |
29 | /* TODO(sio4): #road-to-v1 - redefine Worker interface clearer
30 | 1. The Start() functions of current implementations including Simple,
31 | Gocraft Work Adapter do not block and immediately return the error.
32 | However, App.Serve() calls them within a go routine.
33 | 2. The Perform() family of functions can be called before the worker
34 | was started once the worker configured. Could be fine but there should
35 | be some guidiance for its usage.
36 | 3. The Perform() function could be interpreted as "Do it" by its name but
37 | their actual job is "Enqueue it" even though Simple worker has no clear
38 | boundary between them. It could make confusion.
39 | */
40 |
--------------------------------------------------------------------------------
/flash.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import "encoding/json"
4 |
5 | // flashKey is the prefix inside the Session.
6 | const flashKey = "_flash_"
7 |
8 | //Flash is a struct that helps with the operations over flash messages.
9 | type Flash struct {
10 | data map[string][]string
11 | }
12 |
13 | //Delete removes a particular key from the Flash.
14 | func (f Flash) Delete(key string) {
15 | delete(f.data, key)
16 | }
17 |
18 | //Clear removes all keys from the Flash.
19 | func (f *Flash) Clear() {
20 | f.data = map[string][]string{}
21 | }
22 |
23 | //Set allows to set a list of values into a particular key.
24 | func (f Flash) Set(key string, values []string) {
25 | f.data[key] = values
26 | }
27 |
28 | //Add adds a flash value for a flash key, if the key already has values the list for that value grows.
29 | func (f Flash) Add(key, value string) {
30 | if len(f.data[key]) == 0 {
31 | f.data[key] = []string{value}
32 | return
33 | }
34 |
35 | f.data[key] = append(f.data[key], value)
36 | }
37 |
38 | //Persist the flash inside the session.
39 | func (f Flash) persist(session *Session) {
40 | b, _ := json.Marshal(f.data)
41 | session.Set(flashKey, b)
42 | }
43 |
44 | //newFlash creates a new Flash and loads the session data inside its data.
45 | func newFlash(session *Session) *Flash {
46 | result := &Flash{
47 | data: map[string][]string{},
48 | }
49 |
50 | if session.Session != nil {
51 | if f := session.Get(flashKey); f != nil {
52 | json.Unmarshal(f.([]byte), &result.data)
53 | }
54 | }
55 | return result
56 | }
57 |
--------------------------------------------------------------------------------
/render/options.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "io/fs"
5 |
6 | "github.com/gobuffalo/plush/v5/helpers/hctx"
7 | )
8 |
9 | // Helpers to be included in all templates
10 | type Helpers hctx.Map
11 |
12 | // Options for render.Engine
13 | type Options struct {
14 | // HTMLLayout is the default layout to be used with all HTML renders.
15 | HTMLLayout string
16 |
17 | // JavaScriptLayout is the default layout to be used with all JavaScript renders.
18 | JavaScriptLayout string
19 |
20 | // TemplateFS is the fs.FS that holds the templates
21 | TemplatesFS fs.FS
22 |
23 | // AssetsFS is the fs.FS that holds the of the public assets the app will serve.
24 | AssetsFS fs.FS
25 |
26 | // Helpers to be rendered with the templates
27 | Helpers Helpers
28 |
29 | // TemplateEngine to be used for rendering HTML templates
30 | TemplateEngines map[string]TemplateEngine
31 |
32 | // DefaultContentType instructs the engine what it should fall back to if
33 | // the "content-type" is unknown
34 | DefaultContentType string
35 |
36 | // TemplateMetadataKeys allows users to specify custom keys for template metadata
37 | // If nil, uses default Buffalo metadata approach
38 | TemplateMetadataKeys map[string]string
39 |
40 | TemplateBaseDir string
41 | }
42 |
43 | // Default metadata keys
44 | var defaultTemplateMetadataKeys = map[string]string{
45 | "template_file": "_buffalo_template_file",
46 | "base_name": "_buffalo_base_name",
47 | "extension": "_buffalo_extension",
48 | "last_modified": "_buffalo_last_modification",
49 | }
50 |
--------------------------------------------------------------------------------
/home.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "github.com/gorilla/mux"
5 | )
6 |
7 | /* TODO: consider to split out Home (or Router, whatever) from App #road-to-v1
8 | Group and Domain based multi-homing are actually not an App if the concept
9 | of the App represents the application. The App should be only one for whole
10 | application.
11 |
12 | For an extreme example, App.Group().Stop() or even App.Group().Serve() are
13 | still valid function calls while they should not be allowed and the result
14 | could be strage.
15 | */
16 |
17 | // Home is a container for Domains and Groups that independently serves a
18 | // group of pages with its own Middleware and ErrorHandlers. It is usually
19 | // a multi-homed server domain or group of paths under a certain prefix.
20 | //
21 | // While the App is for managing whole application life cycle along with its
22 | // default Home, including initializing and stopping its all components such
23 | // as listeners and long-running jobs, Home is only for a specific group of
24 | // services to serve its service logic efficiently.
25 | type Home struct {
26 | app *App // will replace App.root
27 | appSelf *App // temporary while the App is in action.
28 | // replace Options' Name, Host, and Prefix
29 | name string
30 | host string
31 | prefix string
32 |
33 | // moved from App
34 | // Middleware returns the current MiddlewareStack for the App/Group.
35 | Middleware *MiddlewareStack `json:"-"`
36 | ErrorHandlers ErrorHandlers `json:"-"`
37 | router *mux.Router
38 | filepaths []string
39 | }
40 |
--------------------------------------------------------------------------------
/options_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/gobuffalo/envy"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestOptions_NewOptions(t *testing.T) {
13 | tests := []struct {
14 | name string
15 | env string
16 | secret string
17 | expectErr string
18 | }{
19 | {name: "Development doesn't fail with no secret", env: "development", secret: "", expectErr: "securecookie:"},
20 | {name: "Development doesn't fail with secret set", env: "development", secret: "secrets", expectErr: "securecookie:"},
21 | {name: "Test doesn't fail with secret set", env: "test", secret: "", expectErr: "securecookie:"},
22 | {name: "Test doesn't fail with secret set", env: "test", secret: "secrets", expectErr: "securecookie:"},
23 | {name: "Production fails with no secret", env: "production", secret: "", expectErr: "securecookie:"},
24 | {name: "Production doesn't fail with secret set", env: "production", secret: "secrets", expectErr: "securecookie:"},
25 | }
26 |
27 | for _, test := range tests {
28 | t.Run(test.name, func(t *testing.T) {
29 | r := require.New(t)
30 | envy.Temp(func() {
31 | envy.Set("GO_ENV", test.env)
32 | envy.Set("SESSION_SECRET", test.secret)
33 |
34 | opts := NewOptions()
35 |
36 | req, _ := http.NewRequest("GET", "/", strings.NewReader(""))
37 | req.AddCookie(&http.Cookie{Name: "_buffalo_session"})
38 |
39 | _, err := opts.SessionStore.New(req, "_buffalo_session")
40 |
41 | r.Error(err)
42 | r.Contains(err.Error(), test.expectErr)
43 | })
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/render/template_helpers.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "encoding/json"
5 | "html/template"
6 | "io"
7 | "path"
8 | "path/filepath"
9 |
10 | ht "github.com/gobuffalo/helpers/tags"
11 | "github.com/gobuffalo/tags/v3"
12 | )
13 |
14 | type helperTag struct {
15 | name string
16 | fn func(string, tags.Options) template.HTML
17 | }
18 |
19 | func (s *templateRenderer) addAssetsHelpers(helpers Helpers) Helpers {
20 | helpers["assetPath"] = s.assetPath
21 |
22 | ah := []helperTag{
23 | {"javascriptTag", ht.JS},
24 | {"stylesheetTag", ht.CSS},
25 | {"imgTag", ht.Img},
26 | }
27 |
28 | for _, h := range ah {
29 | func(h helperTag) {
30 | helpers[h.name] = func(file string, options tags.Options) (template.HTML, error) {
31 | if options == nil {
32 | options = tags.Options{}
33 | }
34 | f, err := s.assetPath(file)
35 | if err != nil {
36 | return "", err
37 | }
38 | return h.fn(f, options), nil
39 | }
40 | }(h)
41 | }
42 |
43 | return helpers
44 | }
45 |
46 | var assetMap = stringMap{}
47 |
48 | func assetPathFor(file string) string {
49 | filePath, ok := assetMap.Load(file)
50 | if filePath == "" || !ok {
51 | filePath = file
52 | }
53 | return path.Join("/assets", filePath)
54 | }
55 |
56 | func loadManifest(manifest io.Reader) error {
57 | m := map[string]string{}
58 |
59 | if err := json.NewDecoder(manifest).Decode(&m); err != nil {
60 | return err
61 | }
62 | for k, v := range m {
63 | // I don't think v has backslash but if so, correct them when
64 | // creating the map instead using the value in `assetPathFor()`.
65 | assetMap.Store(k, filepath.ToSlash(v))
66 | }
67 | return nil
68 | }
69 |
--------------------------------------------------------------------------------
/not_found_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/gobuffalo/httptest"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_App_Dev_NotFound(t *testing.T) {
13 | r := require.New(t)
14 |
15 | a := New(Options{})
16 | a.Env = "development"
17 | a.GET("/foo", func(c Context) error { return nil })
18 |
19 | w := httptest.New(a)
20 | res := w.HTML("/bad").Get()
21 |
22 | body := res.Body.String()
23 | r.Contains(body, "404 - ERROR!")
24 | r.Contains(body, "/foo")
25 | r.Equal(http.StatusNotFound, res.Code)
26 | }
27 |
28 | func Test_App_Dev_NotFound_JSON(t *testing.T) {
29 | r := require.New(t)
30 |
31 | a := New(Options{})
32 | a.Env = "development"
33 | a.GET("/foo", func(c Context) error { return nil })
34 |
35 | w := httptest.New(a)
36 | res := w.JSON("/bad").Get()
37 | r.Equal(http.StatusNotFound, res.Code)
38 |
39 | jb := map[string]interface{}{}
40 | err := json.NewDecoder(res.Body).Decode(&jb)
41 | r.NoError(err)
42 | r.Equal(float64(http.StatusNotFound), jb["code"])
43 | }
44 |
45 | func Test_App_Override_NotFound(t *testing.T) {
46 | r := require.New(t)
47 |
48 | a := New(Options{})
49 | a.ErrorHandlers[http.StatusNotFound] = func(status int, err error, c Context) error {
50 | c.Response().WriteHeader(http.StatusNotFound)
51 | c.Response().Write([]byte("oops!!!"))
52 | return nil
53 | }
54 | a.GET("/foo", func(c Context) error { return nil })
55 |
56 | w := httptest.New(a)
57 | res := w.HTML("/bad").Get()
58 | r.Equal(http.StatusNotFound, res.Code)
59 |
60 | body := res.Body.String()
61 | r.Equal(body, "oops!!!")
62 | r.NotContains(body, "/foo")
63 | }
64 |
--------------------------------------------------------------------------------
/plugins.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "os/exec"
8 | "strings"
9 | "sync"
10 |
11 | "github.com/gobuffalo/buffalo/plugins"
12 | "github.com/gobuffalo/envy"
13 | "github.com/gobuffalo/events"
14 | )
15 |
16 | var loadPlugins sync.Once
17 |
18 | // LoadPlugins will add listeners for any plugins that support "events"
19 | func LoadPlugins() error {
20 | var errResult error
21 | loadPlugins.Do(func() {
22 | // don't send plugins events during testing
23 | if envy.Get("GO_ENV", "development") == "test" {
24 | return
25 | }
26 | plugs, err := plugins.Available()
27 | if err != nil {
28 | errResult = err
29 | return
30 | }
31 | for _, cmds := range plugs {
32 | for _, c := range cmds {
33 | if c.BuffaloCommand != "events" {
34 | continue
35 | }
36 | err := func(c plugins.Command) error {
37 | n := fmt.Sprintf("[PLUGIN] %s %s", c.Binary, c.Name)
38 | fn := func(e events.Event) {
39 | b, err := json.Marshal(e)
40 | if err != nil {
41 | fmt.Println("error trying to marshal event", e, err)
42 | return
43 | }
44 | cmd := exec.Command(c.Binary, c.UseCommand, string(b))
45 | cmd.Stderr = os.Stderr
46 | cmd.Stdout = os.Stdout
47 | cmd.Stdin = os.Stdin
48 | if err := cmd.Run(); err != nil {
49 | fmt.Println("error trying to send event", strings.Join(cmd.Args, " "), err)
50 | }
51 | }
52 | _, err := events.NamedListen(n, events.Filter(c.ListenFor, fn))
53 | return err
54 | }(c)
55 | if err != nil {
56 | errResult = err
57 | return
58 | }
59 | }
60 | }
61 | })
62 | return errResult
63 | }
64 |
--------------------------------------------------------------------------------
/response.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "bufio"
5 | "encoding/binary"
6 | "fmt"
7 | "net"
8 | "net/http"
9 | )
10 |
11 | // Response implements the http.ResponseWriter interface and allows
12 | // for the capture of the response status and size to be used for things
13 | // like logging requests.
14 | type Response struct {
15 | Status int
16 | Size int
17 | http.ResponseWriter
18 | }
19 |
20 | // WriteHeader sets the status code for a response
21 | func (w *Response) WriteHeader(code int) {
22 | if code == w.Status {
23 | return
24 | }
25 |
26 | if w.Status > 0 {
27 | fmt.Printf("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.Status, code)
28 | return
29 | }
30 |
31 | w.Status = code
32 | w.ResponseWriter.WriteHeader(code)
33 | }
34 |
35 | // Write the body of the response
36 | func (w *Response) Write(b []byte) (int, error) {
37 | w.Size = binary.Size(b)
38 | return w.ResponseWriter.Write(b)
39 | }
40 |
41 | // Hijack implements the http.Hijacker interface to allow for things like websockets.
42 | func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
43 | if hj, ok := w.ResponseWriter.(http.Hijacker); ok {
44 | return hj.Hijack()
45 | }
46 | return nil, nil, fmt.Errorf("does not implement http.Hijack")
47 | }
48 |
49 | // Flush the response
50 | func (w *Response) Flush() {
51 | if f, ok := w.ResponseWriter.(http.Flusher); ok {
52 | f.Flush()
53 | }
54 | }
55 |
56 | type closeNotifier interface {
57 | CloseNotify() <-chan bool
58 | }
59 |
60 | // CloseNotify implements the http.CloseNotifier interface
61 | func (w *Response) CloseNotify() <-chan bool {
62 | if cn, ok := w.ResponseWriter.(closeNotifier); ok {
63 | return cn.CloseNotify()
64 | }
65 | return nil
66 | }
67 |
--------------------------------------------------------------------------------
/session.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gorilla/sessions"
7 | )
8 |
9 | // Session wraps the "github.com/gorilla/sessions" API
10 | // in something a little cleaner and a bit more useable.
11 | type Session struct {
12 | Session *sessions.Session
13 | req *http.Request
14 | res http.ResponseWriter
15 | }
16 |
17 | // Save the current session.
18 | func (s *Session) Save() error {
19 | return s.Session.Save(s.req, s.res)
20 | }
21 |
22 | // Get a value from the current session.
23 | func (s *Session) Get(name interface{}) interface{} {
24 | return s.Session.Values[name]
25 | }
26 |
27 | // GetOnce gets a value from the current session and then deletes it.
28 | func (s *Session) GetOnce(name interface{}) interface{} {
29 | if x, ok := s.Session.Values[name]; ok {
30 | s.Delete(name)
31 | return x
32 | }
33 | return nil
34 | }
35 |
36 | // Set a value onto the current session. If a value with that name
37 | // already exists it will be overridden with the new value.
38 | func (s *Session) Set(name, value interface{}) {
39 | s.Session.Values[name] = value
40 | }
41 |
42 | // Delete a value from the current session.
43 | func (s *Session) Delete(name interface{}) {
44 | delete(s.Session.Values, name)
45 | }
46 |
47 | // Clear the current session
48 | func (s *Session) Clear() {
49 | for k := range s.Session.Values {
50 | s.Delete(k)
51 | }
52 | }
53 |
54 | // Get a session using a request and response.
55 | func (a *App) getSession(r *http.Request, w http.ResponseWriter) *Session {
56 | if a.root != nil {
57 | return a.root.getSession(r, w)
58 | }
59 | session, _ := a.SessionStore.Get(r, a.SessionName)
60 | return &Session{
61 | Session: session,
62 | req: r,
63 | res: w,
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/render/string_map.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "sort"
5 | "sync"
6 | )
7 |
8 | // stringMap wraps sync.Map and uses the following types:
9 | // key: string
10 | // value: string
11 | type stringMap struct {
12 | data sync.Map
13 | }
14 |
15 | // Delete the key from the map
16 | func (m *stringMap) Delete(key string) {
17 | m.data.Delete(key)
18 | }
19 |
20 | // Load the key from the map.
21 | // Returns string or bool.
22 | // A false return indicates either the key was not found
23 | // or the value is not of type string
24 | func (m *stringMap) Load(key string) (string, bool) {
25 | i, ok := m.data.Load(key)
26 | if !ok {
27 | return ``, false
28 | }
29 | s, ok := i.(string)
30 | return s, ok
31 | }
32 |
33 | // LoadOrStore will return an existing key or
34 | // store the value if not already in the map
35 | func (m *stringMap) LoadOrStore(key string, value string) (string, bool) {
36 | i, _ := m.data.LoadOrStore(key, value)
37 | s, ok := i.(string)
38 | return s, ok
39 | }
40 |
41 | // Range over the string values in the map
42 | func (m *stringMap) Range(f func(key string, value string) bool) {
43 | m.data.Range(func(k, v interface{}) bool {
44 | key, ok := k.(string)
45 | if !ok {
46 | return false
47 | }
48 | value, ok := v.(string)
49 | if !ok {
50 | return false
51 | }
52 | return f(key, value)
53 | })
54 | }
55 |
56 | // Store a string in the map
57 | func (m *stringMap) Store(key string, value string) {
58 | m.data.Store(key, value)
59 | }
60 |
61 | // Keys returns a list of keys in the map
62 | func (m *stringMap) Keys() []string {
63 | var keys []string
64 | m.Range(func(key string, value string) bool {
65 | keys = append(keys, key)
66 | return true
67 | })
68 | sort.Strings(keys)
69 | return keys
70 | }
71 |
--------------------------------------------------------------------------------
/session_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/gobuffalo/buffalo/render"
10 | "github.com/gobuffalo/httptest"
11 |
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | func Test_Session_SingleCookie(t *testing.T) {
16 | r := require.New(t)
17 |
18 | sessionName := "_test_session"
19 | a := New(Options{SessionName: sessionName})
20 | rr := render.New(render.Options{})
21 |
22 | a.GET("/", func(c Context) error {
23 | return c.Render(http.StatusCreated, rr.String(""))
24 | })
25 |
26 | w := httptest.New(a)
27 | res := w.HTML("/").Get()
28 |
29 | var sessionCookies []string
30 | for _, c := range res.Header().Values("Set-Cookie") {
31 | if strings.HasPrefix(c, sessionName) {
32 | sessionCookies = append(sessionCookies, c)
33 | }
34 | }
35 |
36 | r.Equal(1, len(sessionCookies))
37 | }
38 |
39 | func Test_Session_CustomValue(t *testing.T) {
40 | r := require.New(t)
41 |
42 | a := New(Options{})
43 | rr := render.New(render.Options{})
44 |
45 | // Root path sets a custom session value
46 | a.GET("/", func(c Context) error {
47 | c.Session().Set("example", "test")
48 | return c.Render(http.StatusCreated, rr.String(""))
49 | })
50 | // /session path prints custom session value as response
51 | a.GET("/session", func(c Context) error {
52 | sessionValue := c.Session().Get("example")
53 | return c.Render(http.StatusCreated, rr.String(fmt.Sprintf("%s", sessionValue)))
54 | })
55 |
56 | w := httptest.New(a)
57 | _ = w.HTML("/").Get()
58 |
59 | // Create second request that should contain the cookie from the first response
60 | reqGetSession := w.HTML("/session")
61 | resGetSession := reqGetSession.Get()
62 |
63 | r.Equal(resGetSession.Body.String(), "test")
64 | }
65 |
--------------------------------------------------------------------------------
/binding/binding_test.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | type blogPost struct {
12 | Tags []string
13 | Dislikes int
14 | Likes int32
15 | }
16 |
17 | func Test_Register(t *testing.T) {
18 | r := require.New(t)
19 |
20 | Register("foo/bar", func(*http.Request, interface{}) error {
21 | return nil
22 | })
23 |
24 | r.NotNil(BaseRequestBinder.binders["foo/bar"])
25 |
26 | req, err := http.NewRequest("POST", "/", nil)
27 | r.NoError(err)
28 |
29 | req.Header.Set("Content-Type", "foo/bar")
30 | req.Form = url.Values{
31 | "Tags": []string{"AAA"},
32 | "Likes": []string{"12"},
33 | "Dislikes": []string{"1000"},
34 | }
35 |
36 | req.ParseForm()
37 |
38 | var post blogPost
39 | r.NoError(Exec(req, &post))
40 |
41 | r.Equal([]string(nil), post.Tags)
42 | r.Equal(int32(0), post.Likes)
43 | r.Equal(0, post.Dislikes)
44 |
45 | }
46 |
47 | func Test_RegisterCustomDecoder(t *testing.T) {
48 | r := require.New(t)
49 |
50 | RegisterCustomDecoder(func(vals []string) (interface{}, error) {
51 | return []string{"X"}, nil
52 | }, []interface{}{[]string{}}, nil)
53 |
54 | RegisterCustomDecoder(func(vals []string) (interface{}, error) {
55 | return 0, nil
56 | }, []interface{}{int(0)}, nil)
57 |
58 | post := blogPost{}
59 | req, err := http.NewRequest("POST", "/", nil)
60 | r.NoError(err)
61 |
62 | req.Header.Set("Content-Type", "application/html")
63 | req.Form = url.Values{
64 | "Tags": []string{"AAA"},
65 | "Likes": []string{"12"},
66 | "Dislikes": []string{"1000"},
67 | }
68 | req.ParseForm()
69 |
70 | r.NoError(Exec(req, &post))
71 | r.Equal([]string{"X"}, post.Tags)
72 | r.Equal(int32(12), post.Likes)
73 | r.Equal(0, post.Dislikes)
74 | }
75 |
--------------------------------------------------------------------------------
/plugins/plugdeps/plugdeps_test.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/gobuffalo/meta"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | var heroku = Plugin{
14 | Binary: "buffalo-heroku",
15 | GoGet: "github.com/gobuffalo/buffalo-heroku",
16 | Commands: []Command{
17 | {Name: "deploy", Flags: []string{"-v"}},
18 | },
19 | Tags: []string{"foo", "bar"},
20 | }
21 |
22 | var local = Plugin{
23 | Binary: "buffalo-hello.rb",
24 | Local: "./plugins/buffalo-hello.rb",
25 | }
26 |
27 | func Test_ConfigPath(t *testing.T) {
28 | r := require.New(t)
29 |
30 | x := ConfigPath(meta.App{Root: "foo"})
31 | r.Equal(x, filepath.Join("foo", "config", "buffalo-plugins.toml"))
32 | }
33 |
34 | func Test_List_Off(t *testing.T) {
35 | r := require.New(t)
36 |
37 | app := meta.App{}
38 | plugs, err := List(app)
39 | r.Error(err)
40 | r.True(errors.Is(err, ErrMissingConfig))
41 | r.Len(plugs.List(), 0)
42 | }
43 |
44 | func Test_List_On(t *testing.T) {
45 | r := require.New(t)
46 |
47 | app := meta.New(os.TempDir())
48 |
49 | p := ConfigPath(app)
50 | r.NoError(os.MkdirAll(filepath.Dir(p), 0755))
51 | f, err := os.Create(p)
52 | r.NoError(err)
53 | f.WriteString(eToml)
54 | r.NoError(f.Close())
55 |
56 | plugs, err := List(app)
57 | r.NoError(err)
58 | r.Len(plugs.List(), 3)
59 | }
60 |
61 | const eToml = `[[plugin]]
62 | binary = "buffalo-hello.rb"
63 | local = "./plugins/buffalo-hello.rb"
64 |
65 | [[plugin]]
66 | binary = "buffalo-heroku"
67 | go_get = "github.com/gobuffalo/buffalo-heroku"
68 | tags = ["foo", "bar"]
69 |
70 | [[plugin.command]]
71 | name = "deploy"
72 | flags = ["-v"]
73 |
74 | [[plugin]]
75 | binary = "buffalo-pop"
76 | go_get = "github.com/gobuffalo/buffalo-pop/v2"
77 | `
78 |
--------------------------------------------------------------------------------
/notfound.prod.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
56 |
57 |
58 |
59 |
60 |
61 |
Not Found
62 |
63 |
The page you're looking for does not exist, you may have mistyped the address or the page may have been
64 | moved.
65 |
66 |
67 |
powered by gobuffalo.io
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/error.prod.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
55 |
56 |
57 |
58 |
59 |
60 |
We're Sorry!
61 |
62 |
It looks like something went wrong! Don't worry, we are aware of the problem and are looking into it.
63 |
Sorry if this has caused you any problems. Please check back again later.
64 |
65 |
66 |
powered by gobuffalo.io
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/binding/request_binder.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "sync"
9 |
10 | "github.com/gobuffalo/buffalo/internal/httpx"
11 | )
12 |
13 | var (
14 | errBlankContentType = errors.New("blank content type")
15 | )
16 |
17 | // RequestBinder is in charge of binding multiple requests types to
18 | // struct.
19 | type RequestBinder struct {
20 | lock *sync.RWMutex
21 | binders map[string]Binder
22 | }
23 |
24 | // Register maps a request Content-Type (application/json)
25 | // to a Binder.
26 | func (rb *RequestBinder) Register(contentType string, fn Binder) {
27 | rb.lock.Lock()
28 | defer rb.lock.Unlock()
29 |
30 | rb.binders[strings.ToLower(contentType)] = fn
31 | }
32 |
33 | // Exec binds a request with a passed value, depending on the content type
34 | // It will look for the correct RequestTypeBinder and use it.
35 | func (rb *RequestBinder) Exec(req *http.Request, value interface{}) error {
36 | rb.lock.Lock()
37 | defer rb.lock.Unlock()
38 |
39 | if ba, ok := value.(Bindable); ok {
40 | return ba.Bind(req)
41 | }
42 |
43 | ct := httpx.ContentType(req)
44 | if ct == "" {
45 | return errBlankContentType
46 | }
47 |
48 | binder := rb.binders[ct]
49 | if binder == nil {
50 | return fmt.Errorf("could not find a binder for %s", ct)
51 | }
52 |
53 | return binder(req, value)
54 | }
55 |
56 | // NewRequestBinder creates our request binder with support for
57 | // XML, JSON, HTTP and File request types.
58 | func NewRequestBinder(requestBinders ...ContenTypeBinder) *RequestBinder {
59 | result := &RequestBinder{
60 | lock: &sync.RWMutex{},
61 | binders: map[string]Binder{},
62 | }
63 |
64 | for _, requestBinder := range requestBinders {
65 | for _, contentType := range requestBinder.ContentTypes() {
66 | result.Register(contentType, requestBinder.BinderFunc())
67 | }
68 | }
69 |
70 | return result
71 | }
72 |
--------------------------------------------------------------------------------
/routenamer.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/gobuffalo/flect"
7 | "github.com/gobuffalo/flect/name"
8 | )
9 |
10 | // RouteNamer is in charge of naming a route from the
11 | // path assigned, this name typically will be used if no
12 | // name is assined with .Name(...).
13 | type RouteNamer interface {
14 | // NameRoute receives the path and returns the name
15 | // for the route.
16 | NameRoute(string) string
17 | }
18 |
19 | // BaseRouteNamer is the default route namer used by apps.
20 | type baseRouteNamer struct{}
21 |
22 | func (drn baseRouteNamer) NameRoute(p string) string {
23 | if p == "/" || p == "" {
24 | return "root"
25 | }
26 |
27 | resultParts := []string{}
28 | parts := strings.Split(p, "/")
29 |
30 | for index, part := range parts {
31 |
32 | originalPart := parts[index]
33 |
34 | var previousPart string
35 | if index > 0 {
36 | previousPart = parts[index-1]
37 | }
38 |
39 | var nextPart string
40 | if len(parts) > index+1 {
41 | nextPart = parts[index+1]
42 | }
43 |
44 | isIdentifierPart := strings.Contains(part, "{") && (strings.Contains(part, flect.Singularize(previousPart)))
45 | isSimplifiedID := part == `{id}`
46 |
47 | if isIdentifierPart || isSimplifiedID || part == "" {
48 | continue
49 | }
50 |
51 | if strings.Contains(nextPart, "{") {
52 | part = flect.Singularize(part)
53 | }
54 |
55 | if originalPart == "new" || originalPart == "edit" {
56 | resultParts = append([]string{part}, resultParts...)
57 | continue
58 | }
59 |
60 | if strings.Contains(previousPart, "}") {
61 | resultParts = append(resultParts, part)
62 | continue
63 | }
64 |
65 | resultParts = append(resultParts, part)
66 | }
67 |
68 | if len(resultParts) == 0 {
69 | return "unnamed"
70 | }
71 |
72 | underscore := strings.TrimSpace(strings.Join(resultParts, "_"))
73 | return name.VarCase(underscore)
74 | }
75 |
--------------------------------------------------------------------------------
/plugins/plugcmds/plug_map.go:
--------------------------------------------------------------------------------
1 | //go:generate mapgen -name "plug" -zero "plug{}" -go-type "plug" -pkg "" -a "nil" -b "nil" -c "nil" -bb "nil" -destination "plugcmds"
2 | // Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
3 |
4 | package plugcmds
5 |
6 | import (
7 | "sort"
8 | "sync"
9 | )
10 |
11 | // plugMap wraps sync.Map and uses the following types:
12 | // key: string
13 | // value: plug
14 | type plugMap struct {
15 | data sync.Map
16 | }
17 |
18 | // Delete the key from the map
19 | func (m *plugMap) Delete(key string) {
20 | m.data.Delete(key)
21 | }
22 |
23 | // Load the key from the map.
24 | // Returns plug or bool.
25 | // A false return indicates either the key was not found
26 | // or the value is not of type plug
27 | func (m *plugMap) Load(key string) (plug, bool) {
28 | i, ok := m.data.Load(key)
29 | if !ok {
30 | return plug{}, false
31 | }
32 | s, ok := i.(plug)
33 | return s, ok
34 | }
35 |
36 | // LoadOrStore will return an existing key or
37 | // store the value if not already in the map
38 | func (m *plugMap) LoadOrStore(key string, value plug) (plug, bool) {
39 | i, _ := m.data.LoadOrStore(key, value)
40 | s, ok := i.(plug)
41 | return s, ok
42 | }
43 |
44 | // Range over the plug values in the map
45 | func (m *plugMap) Range(f func(key string, value plug) bool) {
46 | m.data.Range(func(k, v interface{}) bool {
47 | key, ok := k.(string)
48 | if !ok {
49 | return false
50 | }
51 | value, ok := v.(plug)
52 | if !ok {
53 | return false
54 | }
55 | return f(key, value)
56 | })
57 | }
58 |
59 | // Store a plug in the map
60 | func (m *plugMap) Store(key string, value plug) {
61 | m.data.Store(key, value)
62 | }
63 |
64 | // Keys returns a list of keys in the map
65 | func (m *plugMap) Keys() []string {
66 | var keys []string
67 | m.Range(func(key string, value plug) bool {
68 | keys = append(keys, key)
69 | return true
70 | })
71 | sort.Strings(keys)
72 | return keys
73 | }
74 |
--------------------------------------------------------------------------------
/binding/request_binder_test.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_RequestBinder_Exec(t *testing.T) {
13 | r := require.New(t)
14 |
15 | var used bool
16 | BaseRequestBinder.Register("paganotoni/test", func(*http.Request, interface{}) error {
17 | used = true
18 | return nil
19 | })
20 |
21 | req, err := http.NewRequest("GET", "/home", strings.NewReader(""))
22 | req.Header.Add("content-type", "paganotoni/test")
23 | r.NoError(err)
24 |
25 | data := &struct{}{}
26 | r.NoError(BaseRequestBinder.Exec(req, data))
27 | r.True(used)
28 | }
29 |
30 | func Test_RequestBinder_Exec_BlankContentType(t *testing.T) {
31 | r := require.New(t)
32 |
33 | req, err := http.NewRequest("GET", "/home", strings.NewReader(""))
34 | r.NoError(err)
35 |
36 | data := &struct{}{}
37 | r.Equal(BaseRequestBinder.Exec(req, data), errBlankContentType)
38 | }
39 |
40 | func Test_RequestBinder_Exec_Bindable(t *testing.T) {
41 | r := require.New(t)
42 |
43 | BaseRequestBinder.Register("paganotoni/orbison", func(req *http.Request, val interface{}) error {
44 | switch v := val.(type) {
45 | case orbison:
46 | v.bound = false
47 | }
48 |
49 | return errors.New("this should not be called")
50 | })
51 |
52 | req, err := http.NewRequest("GET", "/home", strings.NewReader(""))
53 | req.Header.Add("content-type", "paganotoni/orbison")
54 | r.NoError(err)
55 |
56 | data := &orbison{}
57 | r.NoError(BaseRequestBinder.Exec(req, data))
58 | r.True(data.bound)
59 | }
60 |
61 | func Test_RequestBinder_Exec_NoBinder(t *testing.T) {
62 | r := require.New(t)
63 |
64 | req, err := http.NewRequest("GET", "/home", strings.NewReader(""))
65 | req.Header.Add("content-type", "paganotoni/other")
66 | r.NoError(err)
67 |
68 | err = BaseRequestBinder.Exec(req, &struct{}{})
69 | r.Error(err)
70 | r.Equal(err.Error(), "could not find a binder for paganotoni/other")
71 | }
72 |
--------------------------------------------------------------------------------
/cookies_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 | "time"
8 |
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestCookies_Get(t *testing.T) {
13 | r := require.New(t)
14 | req := httptest.NewRequest("POST", "/", nil)
15 | req.Header.Set("Cookie", "name=Arthur Dent; answer=42")
16 |
17 | c := Cookies{req, nil}
18 |
19 | v, err := c.Get("name")
20 | r.NoError(err)
21 | r.Equal("Arthur Dent", v)
22 |
23 | v, err = c.Get("answer")
24 | r.NoError(err)
25 | r.Equal("42", v)
26 |
27 | _, err = c.Get("unknown")
28 | r.EqualError(err, http.ErrNoCookie.Error())
29 | }
30 |
31 | func TestCookies_Set(t *testing.T) {
32 | r := require.New(t)
33 | res := httptest.NewRecorder()
34 |
35 | c := Cookies{&http.Request{}, res}
36 |
37 | c.Set("name", "Rob Pike", time.Hour*24)
38 |
39 | h := res.Header().Get("Set-Cookie")
40 | r.Equal("name=\"Rob Pike\"; Max-Age=86400", h)
41 | }
42 |
43 | func TestCookies_SetWithPath(t *testing.T) {
44 | r := require.New(t)
45 | res := httptest.NewRecorder()
46 |
47 | c := Cookies{&http.Request{}, res}
48 |
49 | c.SetWithPath("name", "Rob Pike", "/foo")
50 |
51 | h := res.Header().Get("Set-Cookie")
52 | r.Equal("name=\"Rob Pike\"; Path=/foo", h)
53 | }
54 |
55 | func TestCookies_SetWithExpirationTime(t *testing.T) {
56 | r := require.New(t)
57 | res := httptest.NewRecorder()
58 |
59 | c := Cookies{&http.Request{}, res}
60 |
61 | e := time.Date(2017, 7, 29, 19, 28, 45, 0, time.UTC)
62 | c.SetWithExpirationTime("name", "Rob Pike", e)
63 |
64 | h := res.Header().Get("Set-Cookie")
65 | r.Equal("name=\"Rob Pike\"; Expires=Sat, 29 Jul 2017 19:28:45 GMT", h)
66 | }
67 |
68 | func TestCookies_Delete(t *testing.T) {
69 | r := require.New(t)
70 | res := httptest.NewRecorder()
71 |
72 | c := Cookies{&http.Request{}, res}
73 |
74 | c.Delete("remove-me")
75 |
76 | h := res.Header().Get("Set-Cookie")
77 | r.Equal("remove-me=v; Expires=Thu, 01 Jan 1970 00:00:00 GMT", h)
78 | }
79 |
--------------------------------------------------------------------------------
/binding/file_request_type_binder.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 | "reflect"
6 |
7 | "github.com/monoculum/formam"
8 | )
9 |
10 | // FileRequestTypeBinder is in charge of binding File request types.
11 | type FileRequestTypeBinder struct {
12 | decoder *formam.Decoder
13 | }
14 |
15 | // ContentTypes returns the list of content types for FileRequestTypeBinder
16 | func (ht FileRequestTypeBinder) ContentTypes() []string {
17 | return []string{
18 | "multipart/form-data",
19 | }
20 | }
21 |
22 | // BinderFunc that will take care of the HTML File binding
23 | func (ht FileRequestTypeBinder) BinderFunc() Binder {
24 | return func(req *http.Request, i interface{}) error {
25 | err := req.ParseMultipartForm(MaxFileMemory)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | if err := ht.decoder.Decode(req.Form, i); err != nil {
31 | return err
32 | }
33 |
34 | form := req.MultipartForm.File
35 | if len(form) == 0 {
36 | return nil
37 | }
38 |
39 | ri := reflect.Indirect(reflect.ValueOf(i))
40 | rt := ri.Type()
41 | for n := range form {
42 | f := ri.FieldByName(n)
43 | if !f.IsValid() {
44 | for i := 0; i < rt.NumField(); i++ {
45 | sf := rt.Field(i)
46 | if sf.Tag.Get("form") == n {
47 | f = ri.Field(i)
48 | break
49 | }
50 | }
51 | }
52 | if !f.IsValid() {
53 | continue
54 | }
55 | if f.Kind() == reflect.Slice {
56 | for _, fh := range req.MultipartForm.File[n] {
57 | mf, err := fh.Open()
58 | if err != nil {
59 | return err
60 | }
61 |
62 | f.Set(reflect.Append(f, reflect.ValueOf(File{
63 | File: mf,
64 | FileHeader: fh,
65 | })))
66 | }
67 | continue
68 | }
69 | if _, ok := f.Interface().(File); ok {
70 | mf, mh, err := req.FormFile(n)
71 | if err != nil {
72 | return err
73 | }
74 | f.Set(reflect.ValueOf(File{
75 | File: mf,
76 | FileHeader: mh,
77 | }))
78 | }
79 | }
80 |
81 | return nil
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/render/sse.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | type sse struct {
10 | Data interface{} `json:"data"`
11 | Type string `json:"type"`
12 | }
13 |
14 | func (s *sse) String() string {
15 | b, _ := json.Marshal(s)
16 | return fmt.Sprintf("data: %s\n\n", string(b))
17 | }
18 |
19 | func (s *sse) Bytes() []byte {
20 | return []byte(s.String())
21 | }
22 |
23 | // EventSource is designed to work with JavaScript EventSource objects.
24 | // see https://developer.mozilla.org/en-US/docs/Web/API/EventSource for
25 | // more details
26 | type EventSource struct {
27 | w http.ResponseWriter
28 | fl http.Flusher
29 | }
30 |
31 | func (es *EventSource) Write(t string, d interface{}) error {
32 | s := &sse{Type: t, Data: d}
33 | _, err := es.w.Write(s.Bytes())
34 | if err != nil {
35 | return err
36 | }
37 | es.Flush()
38 | return nil
39 | }
40 |
41 | // Flush messages down the pipe. If messages aren't flushed they
42 | // won't be sent.
43 | func (es *EventSource) Flush() {
44 | es.fl.Flush()
45 | }
46 |
47 | type closeNotifier interface {
48 | CloseNotify() <-chan bool
49 | }
50 |
51 | // CloseNotify return true across the channel when the connection
52 | // in the browser has been severed.
53 | func (es *EventSource) CloseNotify() <-chan bool {
54 | if cn, ok := es.w.(closeNotifier); ok {
55 | return cn.CloseNotify()
56 | }
57 | return nil
58 | }
59 |
60 | // NewEventSource returns a new EventSource instance while ensuring
61 | // that the http.ResponseWriter is able to handle EventSource messages.
62 | // It also makes sure to set the proper response heads.
63 | func NewEventSource(w http.ResponseWriter) (*EventSource, error) {
64 | es := &EventSource{w: w}
65 | var ok bool
66 | es.fl, ok = w.(http.Flusher)
67 | if !ok {
68 | return es, fmt.Errorf("streaming is not supported")
69 | }
70 |
71 | es.w.Header().Set("Content-Type", "text/event-stream")
72 | es.w.Header().Set("Cache-Control", "no-cache")
73 | es.w.Header().Set("Connection", "keep-alive")
74 | es.w.Header().Set("Access-Control-Allow-Origin", "*")
75 | return es, nil
76 | }
77 |
--------------------------------------------------------------------------------
/plugins/cache.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "os"
9 | "os/user"
10 | "path/filepath"
11 | "sync"
12 |
13 | "github.com/gobuffalo/envy"
14 | )
15 |
16 | type cachedPlugin struct {
17 | Commands Commands `json:"commands"`
18 | CheckSum string `json:"check_sum"`
19 | }
20 |
21 | type cachedPlugins map[string]cachedPlugin
22 |
23 | // CachePath returns the path to the plugins cache
24 | var CachePath = func() string {
25 | home := "."
26 | if usr, err := user.Current(); err == nil {
27 | home = usr.HomeDir
28 | }
29 | return filepath.Join(home, ".buffalo", "plugin.cache")
30 | }()
31 |
32 | var cacheMoot sync.RWMutex
33 |
34 | var cacheOn = envy.Get("BUFFALO_PLUGIN_CACHE", "on")
35 |
36 | var cache = func() cachedPlugins {
37 | m := cachedPlugins{}
38 | if cacheOn != "on" {
39 | return m
40 | }
41 | f, err := os.Open(CachePath)
42 | if err != nil {
43 | return m
44 | }
45 | defer f.Close()
46 | if err := json.NewDecoder(f).Decode(&m); err != nil {
47 | f.Close()
48 | os.Remove(f.Name())
49 | }
50 | return m
51 | }()
52 |
53 | func findInCache(path string) (cachedPlugin, bool) {
54 | cacheMoot.RLock()
55 | defer cacheMoot.RUnlock()
56 | cp, ok := cache[path]
57 | return cp, ok
58 | }
59 |
60 | func saveCache() error {
61 | if cacheOn != "on" {
62 | return nil
63 | }
64 | cacheMoot.Lock()
65 | defer cacheMoot.Unlock()
66 | os.MkdirAll(filepath.Dir(CachePath), 0744)
67 | f, err := os.Create(CachePath)
68 | if err != nil {
69 | return err
70 | }
71 | return json.NewEncoder(f).Encode(cache)
72 | }
73 |
74 | func sum(path string) string {
75 | f, err := os.Open(path)
76 | if err != nil {
77 | return ""
78 | }
79 | defer f.Close()
80 | hash := sha256.New()
81 | if _, err := io.Copy(hash, f); err != nil {
82 | return ""
83 | }
84 | sum := hash.Sum(nil)
85 |
86 | s := fmt.Sprintf("%x", sum)
87 | return s
88 | }
89 |
90 | func addToCache(path string, cp cachedPlugin) {
91 | if cp.CheckSum == "" {
92 | cp.CheckSum = sum(path)
93 | }
94 | cacheMoot.Lock()
95 | defer cacheMoot.Unlock()
96 | cache[path] = cp
97 | }
98 |
--------------------------------------------------------------------------------
/request_logger.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/hex"
6 | "net/http"
7 | "time"
8 |
9 | humanize "github.com/dustin/go-humanize"
10 | "github.com/gobuffalo/buffalo/internal/httpx"
11 | )
12 |
13 | // RequestLogger can be be overridden to a user specified
14 | // function that can be used to log the request.
15 | var RequestLogger = RequestLoggerFunc
16 |
17 | func randString(i int) (string, error) {
18 | if i == 0 {
19 | i = 64
20 | }
21 | b := make([]byte, i)
22 | _, err := rand.Read(b)
23 | return hex.EncodeToString(b), err
24 | }
25 |
26 | // RequestLoggerFunc is the default implementation of the RequestLogger.
27 | // By default it will log a uniq "request_id", the HTTP Method of the request,
28 | // the path that was requested, the duration (time) it took to process the
29 | // request, the size of the response (and the "human" size), and the status
30 | // code of the response.
31 | func RequestLoggerFunc(h Handler) Handler {
32 | return func(c Context) error {
33 | rs, err := randString(10)
34 | if err != nil {
35 | return err
36 | }
37 | var irid interface{}
38 | if irid = c.Session().Get("requestor_id"); irid == nil {
39 | rs, err := randString(10)
40 | if err != nil {
41 | return err
42 | }
43 | irid = rs
44 | c.Session().Set("requestor_id", irid)
45 | }
46 |
47 | rid := irid.(string) + "-" + rs
48 | c.Set("request_id", rid)
49 | c.LogField("request_id", rid)
50 |
51 | start := time.Now()
52 | defer func() {
53 | ws, ok := c.Response().(*Response)
54 | if !ok {
55 | ws = &Response{ResponseWriter: c.Response()}
56 | ws.Status = http.StatusOK
57 | }
58 | req := c.Request()
59 | ct := httpx.ContentType(req)
60 | if ct != "" {
61 | c.LogField("content_type", ct)
62 | }
63 | c.LogFields(map[string]interface{}{
64 | "method": req.Method,
65 | "path": req.URL.String(),
66 | "duration": time.Since(start),
67 | "size": ws.Size,
68 | "human_size": humanize.Bytes(uint64(ws.Size)),
69 | "status": ws.Status,
70 | })
71 | c.Logger().Info(req.URL.String())
72 | }()
73 | return h(c)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/render/render.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "github.com/gobuffalo/helpers"
5 | "github.com/gobuffalo/helpers/forms"
6 | "github.com/gobuffalo/helpers/forms/bootstrap"
7 | "github.com/gobuffalo/plush/v5"
8 | )
9 |
10 | // Engine used to power all defined renderers.
11 | // This allows you to configure the system to your
12 | // preferred settings, instead of just getting
13 | // the defaults.
14 | type Engine struct {
15 | Options
16 | }
17 |
18 | // New render.Engine ready to go with your Options
19 | // and some defaults we think you might like.
20 | func New(opts Options) *Engine {
21 | if opts.Helpers == nil {
22 | opts.Helpers = Helpers{}
23 | }
24 |
25 | if len(opts.Helpers) == 0 {
26 | opts.Helpers = defaultHelpers()
27 | }
28 |
29 | if opts.TemplateEngines == nil {
30 | opts.TemplateEngines = map[string]TemplateEngine{}
31 | }
32 | if _, ok := opts.TemplateEngines["html"]; !ok {
33 | opts.TemplateEngines["html"] = plush.BuffaloRenderer
34 | }
35 | if _, ok := opts.TemplateEngines["plush"]; !ok {
36 | opts.TemplateEngines["plush"] = plush.BuffaloRenderer
37 | }
38 | if _, ok := opts.TemplateEngines["text"]; !ok {
39 | opts.TemplateEngines["text"] = plush.BuffaloRenderer
40 | }
41 | if _, ok := opts.TemplateEngines["txt"]; !ok {
42 | opts.TemplateEngines["txt"] = plush.BuffaloRenderer
43 | }
44 | if _, ok := opts.TemplateEngines["js"]; !ok {
45 | opts.TemplateEngines["js"] = plush.BuffaloRenderer
46 | }
47 | if _, ok := opts.TemplateEngines["md"]; !ok {
48 | opts.TemplateEngines["md"] = MDTemplateEngine
49 | }
50 | if _, ok := opts.TemplateEngines["tmpl"]; !ok {
51 | opts.TemplateEngines["tmpl"] = GoTemplateEngine
52 | }
53 |
54 | if opts.DefaultContentType == "" {
55 | opts.DefaultContentType = "text/html; charset=utf-8"
56 | }
57 |
58 | if opts.TemplateMetadataKeys == nil {
59 | opts.TemplateMetadataKeys = defaultTemplateMetadataKeys
60 | }
61 |
62 | e := &Engine{
63 | Options: opts,
64 | }
65 | return e
66 | }
67 |
68 | func defaultHelpers() Helpers {
69 | h := Helpers(helpers.ALL())
70 | h[forms.FormKey] = bootstrap.Form
71 | h[forms.FormForKey] = bootstrap.FormFor
72 | h["form_for"] = bootstrap.FormFor
73 | return h
74 | }
75 |
--------------------------------------------------------------------------------
/plugins/plugdeps/plugins.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import (
4 | "io"
5 | "sort"
6 | "sync"
7 |
8 | "github.com/BurntSushi/toml"
9 | )
10 |
11 | // Plugins manages the config/buffalo-plugins.toml file
12 | // as well as the plugins available from the file.
13 | type Plugins struct {
14 | plugins map[string]Plugin
15 | moot *sync.RWMutex
16 | }
17 |
18 | // Encode the list of plugins, in TOML format, to the reader
19 | func (plugs *Plugins) Encode(w io.Writer) error {
20 | tp := tomlPlugins{
21 | Plugins: plugs.List(),
22 | }
23 |
24 | if err := toml.NewEncoder(w).Encode(tp); err != nil {
25 | return err
26 | }
27 | return nil
28 | }
29 |
30 | // Decode the list of plugins, in TOML format, from the reader
31 | func (plugs *Plugins) Decode(r io.Reader) error {
32 | tp := &tomlPlugins{
33 | Plugins: []Plugin{},
34 | }
35 | if _, err := toml.NewDecoder(r).Decode(tp); err != nil {
36 | return err
37 | }
38 | for _, p := range tp.Plugins {
39 | plugs.Add(p)
40 | }
41 | return nil
42 | }
43 |
44 | // List of dependent plugins listed in order of Plugin.String()
45 | func (plugs *Plugins) List() []Plugin {
46 | m := map[string]Plugin{}
47 | plugs.moot.RLock()
48 | for _, p := range plugs.plugins {
49 | m[p.key()] = p
50 | }
51 | plugs.moot.RUnlock()
52 | var pp []Plugin
53 | for _, v := range m {
54 | pp = append(pp, v)
55 | }
56 | sort.Slice(pp, func(a, b int) bool {
57 | return pp[a].Binary < pp[b].Binary
58 | })
59 | return pp
60 | }
61 |
62 | // Add plugin(s) to the list of dependencies
63 | func (plugs *Plugins) Add(pp ...Plugin) {
64 | plugs.moot.Lock()
65 | for _, p := range pp {
66 | plugs.plugins[p.key()] = p
67 | }
68 | plugs.moot.Unlock()
69 | }
70 |
71 | // Remove plugin(s) from the list of dependencies
72 | func (plugs *Plugins) Remove(pp ...Plugin) {
73 | plugs.moot.Lock()
74 | for _, p := range pp {
75 | delete(plugs.plugins, p.key())
76 | }
77 | plugs.moot.Unlock()
78 | }
79 |
80 | // New returns a configured *Plugins value
81 | func New() *Plugins {
82 | plugs := &Plugins{
83 | plugins: map[string]Plugin{},
84 | moot: &sync.RWMutex{},
85 | }
86 | return plugs
87 | }
88 |
89 | type tomlPlugins struct {
90 | Plugins []Plugin `toml:"plugin"`
91 | }
92 |
--------------------------------------------------------------------------------
/render/download_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "io"
7 | "net/http"
8 | "net/http/httptest"
9 | "strconv"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/require"
13 | )
14 |
15 | type dlRenderer func(context.Context, string, io.Reader) Renderer
16 |
17 | type dlContext struct {
18 | context.Context
19 | rw http.ResponseWriter
20 | }
21 |
22 | func (c dlContext) Response() http.ResponseWriter {
23 | return c.rw
24 | }
25 |
26 | var data = []byte("data")
27 |
28 | func Test_Download_KnownExtension(t *testing.T) {
29 | r := require.New(t)
30 |
31 | table := []dlRenderer{
32 | Download,
33 | New(Options{}).Download,
34 | }
35 |
36 | for _, dl := range table {
37 | ctx := dlContext{rw: httptest.NewRecorder()}
38 |
39 | re := dl(ctx, "filename.pdf", bytes.NewReader(data))
40 |
41 | bb := &bytes.Buffer{}
42 | r.NoError(re.Render(bb, nil))
43 |
44 | r.Equal(data, bb.Bytes())
45 | r.Equal(strconv.Itoa(len(data)), ctx.Response().Header().Get("Content-Length"))
46 | r.Equal("attachment; filename=filename.pdf", ctx.Response().Header().Get("Content-Disposition"))
47 | r.Equal("application/pdf", re.ContentType())
48 | }
49 | }
50 |
51 | func Test_Download_UnknownExtension(t *testing.T) {
52 | r := require.New(t)
53 |
54 | table := []dlRenderer{
55 | Download,
56 | New(Options{}).Download,
57 | }
58 |
59 | for _, dl := range table {
60 | ctx := dlContext{rw: httptest.NewRecorder()}
61 | re := dl(ctx, "filename", bytes.NewReader(data))
62 |
63 | bb := &bytes.Buffer{}
64 | r.NoError(re.Render(bb, nil))
65 |
66 | r.Equal(data, bb.Bytes())
67 | r.Equal(strconv.Itoa(len(data)), ctx.Response().Header().Get("Content-Length"))
68 | r.Equal("attachment; filename=filename", ctx.Response().Header().Get("Content-Disposition"))
69 | r.Equal("application/octet-stream", re.ContentType())
70 | }
71 | }
72 |
73 | func Test_InvalidContext(t *testing.T) {
74 | r := require.New(t)
75 |
76 | table := []dlRenderer{
77 | Download,
78 | New(Options{}).Download,
79 | }
80 |
81 | for _, dl := range table {
82 | re := dl(context.TODO(), "filename", bytes.NewReader(data))
83 |
84 | bb := &bytes.Buffer{}
85 | r.Error(re.Render(bb, nil))
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/render/html.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "html"
5 | "strings"
6 |
7 | "github.com/gobuffalo/github_flavored_markdown"
8 | "github.com/gobuffalo/plush/v5"
9 | )
10 |
11 | // HTML renders the named files using the 'text/html'
12 | // content type and the github.com/gobuffalo/plush/v5
13 | // package for templating. If more than 1 file is provided
14 | // the second file will be considered a "layout" file
15 | // and the first file will be the "content" file which will
16 | // be placed into the "layout" using "<%= yield %>".
17 | func HTML(names ...string) Renderer {
18 | e := New(Options{})
19 | return e.HTML(names...)
20 | }
21 |
22 | // HTML renders the named files using the 'text/html'
23 | // content type and the github.com/gobuffalo/plush/v5
24 | // package for templating. If more than 1 file is provided
25 | // the second file will be considered a "layout" file
26 | // and the first file will be the "content" file which will
27 | // be placed into the "layout" using "<%= yield %>". If no
28 | // second file is provided and an `HTMLLayout` is specified
29 | // in the options, then that layout file will be used
30 | // automatically.
31 | func (e *Engine) HTML(names ...string) Renderer {
32 | // just allow leading slash and remove them here.
33 | // generated actions were various by buffalo versions.
34 | tmp := []string{}
35 | for _, name := range names {
36 | tmp = append(tmp, strings.TrimPrefix(name, "/"))
37 | }
38 | names = tmp
39 |
40 | if e.HTMLLayout != "" && len(names) == 1 {
41 | names = append(names, e.HTMLLayout)
42 | }
43 | hr := &templateRenderer{
44 | Engine: e,
45 | contentType: "text/html; charset=utf-8",
46 | names: names,
47 | }
48 | return hr
49 | }
50 |
51 | // MDTemplateEngine runs the input through github flavored markdown before sending it to the Plush engine.
52 | func MDTemplateEngine(input string, data map[string]interface{}, helpers map[string]interface{}) (string, error) {
53 | if ct, ok := data["contentType"].(string); ok && ct == "text/plain" {
54 | return plush.BuffaloRenderer(input, data, helpers)
55 | }
56 | source := github_flavored_markdown.Markdown([]byte(input))
57 | source = []byte(html.UnescapeString(string(source)))
58 | return plush.BuffaloRenderer(string(source), data, helpers)
59 | }
60 |
--------------------------------------------------------------------------------
/cookies.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "time"
6 | )
7 |
8 | // Cookies allows you to easily get cookies from the request, and set cookies on the response.
9 | type Cookies struct {
10 | req *http.Request
11 | res http.ResponseWriter
12 | }
13 |
14 | // Get returns the value of the cookie with the given name. Returns http.ErrNoCookie if there's no cookie with that name in the request.
15 | func (c *Cookies) Get(name string) (string, error) {
16 | ck, err := c.req.Cookie(name)
17 | if err != nil {
18 | return "", err
19 | }
20 |
21 | return ck.Value, nil
22 | }
23 |
24 | // Set a cookie on the response, which will expire after the given duration.
25 | func (c *Cookies) Set(name, value string, maxAge time.Duration) {
26 | ck := http.Cookie{
27 | Name: name,
28 | Value: value,
29 | MaxAge: int(maxAge.Seconds()),
30 | }
31 |
32 | http.SetCookie(c.res, &ck)
33 | }
34 |
35 | // SetWithExpirationTime sets a cookie that will expire at a specific time.
36 | // Note that the time is determined by the client's browser, so it might not expire at the expected time,
37 | // for example if the client has changed the time on their computer.
38 | func (c *Cookies) SetWithExpirationTime(name, value string, expires time.Time) {
39 | ck := http.Cookie{
40 | Name: name,
41 | Value: value,
42 | Expires: expires,
43 | }
44 |
45 | http.SetCookie(c.res, &ck)
46 | }
47 |
48 | // SetWithPath sets a cookie path on the server in which the cookie will be available on.
49 | // If set to '/', the cookie will be available within the entire domain.
50 | // If set to '/foo/', the cookie will only be available within the /foo/ directory and
51 | // all sub-directories such as /foo/bar/ of domain.
52 | func (c *Cookies) SetWithPath(name, value, path string) {
53 | ck := http.Cookie{
54 | Name: name,
55 | Value: value,
56 | Path: path,
57 | }
58 |
59 | http.SetCookie(c.res, &ck)
60 | }
61 |
62 | // Delete sets a header that tells the browser to remove the cookie with the given name.
63 | func (c *Cookies) Delete(name string) {
64 | ck := http.Cookie{
65 | Name: name,
66 | Value: "v",
67 | // Setting a time in the distant past, like the unix epoch, removes the cookie,
68 | // since it has long expired.
69 | Expires: time.Unix(0, 0),
70 | }
71 |
72 | http.SetCookie(c.res, &ck)
73 | }
74 |
--------------------------------------------------------------------------------
/plugins/decorate.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 | "path/filepath"
8 | "runtime"
9 | "strings"
10 |
11 | "github.com/gobuffalo/envy"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | // ErrPlugMissing error for when a plugin is missing
16 | var ErrPlugMissing = fmt.Errorf("plugin missing")
17 |
18 | // Decorate setup cobra Commands for plugins
19 | func Decorate(c Command) *cobra.Command {
20 | var flags []string
21 | if len(c.Flags) > 0 {
22 | flags = append(flags, c.Flags...)
23 | }
24 | cc := &cobra.Command{
25 | Use: c.Name,
26 | Short: fmt.Sprintf("[PLUGIN] %s", c.Description),
27 | Aliases: c.Aliases,
28 | RunE: func(cmd *cobra.Command, args []string) error {
29 | plugCmd := c.Name
30 | if c.UseCommand != "" {
31 | plugCmd = c.UseCommand
32 | }
33 |
34 | ax := []string{plugCmd}
35 | if plugCmd == "-" {
36 | ax = []string{}
37 | }
38 |
39 | ax = append(ax, args...)
40 | ax = append(ax, flags...)
41 |
42 | bin, err := LookPath(c.Binary)
43 | if err != nil {
44 | return err
45 | }
46 |
47 | ex := exec.Command(bin, ax...)
48 | if runtime.GOOS != "windows" {
49 | ex.Env = append(envy.Environ(), "BUFFALO_PLUGIN=1")
50 | }
51 | ex.Stdin = os.Stdin
52 | ex.Stdout = os.Stdout
53 | ex.Stderr = os.Stderr
54 | return log(strings.Join(ex.Args, " "), ex.Run)
55 | },
56 | }
57 | cc.DisableFlagParsing = true
58 | return cc
59 | }
60 |
61 | // LookPath for plugin
62 | func LookPath(s string) (string, error) {
63 | if _, err := os.Stat(s); err == nil {
64 | return s, nil
65 | }
66 |
67 | if lp, err := exec.LookPath(s); err == nil {
68 | return lp, err
69 | }
70 |
71 | var bin string
72 | pwd, err := os.Getwd()
73 | if err != nil {
74 | return "", err
75 | }
76 |
77 | var looks []string
78 | if from, err := envy.MustGet("BUFFALO_PLUGIN_PATH"); err == nil {
79 | looks = append(looks, from)
80 | } else {
81 | looks = []string{filepath.Join(pwd, "plugins"), filepath.Join(envy.GoPath(), "bin"), envy.Get("PATH", "")}
82 | }
83 |
84 | for _, p := range looks {
85 | lp := filepath.Join(p, s)
86 | if lp, err = filepath.EvalSymlinks(lp); err == nil {
87 | bin = lp
88 | break
89 | }
90 | }
91 |
92 | if len(bin) == 0 {
93 | return "", ErrPlugMissing
94 | }
95 | return bin, nil
96 | }
97 |
--------------------------------------------------------------------------------
/plugins/plugdeps/plugdeps.go:
--------------------------------------------------------------------------------
1 | package plugdeps
2 |
3 | import (
4 | "fmt"
5 | "io/fs"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 |
10 | "github.com/gobuffalo/meta"
11 | )
12 |
13 | // ErrMissingConfig is if config/buffalo-plugins.toml file is not found. Use plugdeps#On(app) to test if plugdeps are being used
14 | var ErrMissingConfig = fmt.Errorf("could not find a buffalo-plugins config file at %s", ConfigPath(meta.New(".")))
15 |
16 | // List all of the plugins the application depends on. Will return ErrMissingConfig
17 | // if the app is not using config/buffalo-plugins.toml to manage their plugins.
18 | // Use plugdeps#On(app) to test if plugdeps are being used.
19 | func List(app meta.App) (*Plugins, error) {
20 | plugs := New()
21 | if app.WithPop {
22 | plugs.Add(pop)
23 | }
24 |
25 | lp, err := listLocal(app)
26 | if err != nil {
27 | return plugs, err
28 | }
29 | plugs.Add(lp.List()...)
30 |
31 | if !On(app) {
32 | return plugs, ErrMissingConfig
33 | }
34 |
35 | p := ConfigPath(app)
36 | tf, err := os.Open(p)
37 | if err != nil {
38 | return plugs, err
39 | }
40 | if err := plugs.Decode(tf); err != nil {
41 | return plugs, err
42 | }
43 |
44 | return plugs, nil
45 | }
46 |
47 | func listLocal(app meta.App) (*Plugins, error) {
48 | plugs := New()
49 | pRoot := filepath.Join(app.Root, "plugins")
50 | if _, err := os.Stat(pRoot); err != nil {
51 | return plugs, nil
52 | }
53 | err := filepath.WalkDir(pRoot, func(path string, d fs.DirEntry, err error) error {
54 | if d.IsDir() {
55 | return nil
56 | }
57 | if !strings.HasPrefix(d.Name(), "buffalo-") {
58 | return nil
59 | }
60 |
61 | plugs.Add(Plugin{
62 | Binary: d.Name(),
63 | Local: "." + strings.TrimPrefix(path, app.Root),
64 | })
65 | return nil
66 | })
67 | if err != nil {
68 | return plugs, err
69 | }
70 |
71 | return plugs, nil
72 | }
73 |
74 | // ConfigPath returns the path to the config/buffalo-plugins.toml file
75 | // relative to the app
76 | func ConfigPath(app meta.App) string {
77 | return filepath.Join(app.Root, "config", "buffalo-plugins.toml")
78 | }
79 |
80 | // On checks for the existence of config/buffalo-plugins.toml if this
81 | // file exists its contents will be used to list plugins. If the file is not
82 | // found, then the BUFFALO_PLUGIN_PATH and ./plugins folders are consulted.
83 | func On(app meta.App) bool {
84 | _, err := os.Stat(ConfigPath(app))
85 | return err == nil
86 | }
87 |
--------------------------------------------------------------------------------
/app.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "sync"
7 |
8 | "github.com/gobuffalo/envy"
9 | "github.com/gorilla/mux"
10 | )
11 |
12 | // App is where it all happens! It holds on to options,
13 | // the underlying router, the middleware, and more.
14 | // Without an App you can't do much!
15 | type App struct {
16 | Options
17 | // Middleware, ErrorHandlers, router, and filepaths are moved to Home.
18 | Home
19 | moot *sync.RWMutex
20 | routes RouteList
21 | // TODO: to be deprecated #road-to-v1
22 | root *App
23 | children []*App
24 |
25 | // Routenamer for the app. This field provides the ability to override the
26 | // base route namer for something more specific to the app.
27 | RouteNamer RouteNamer
28 | }
29 |
30 | // Muxer returns the underlying mux router to allow
31 | // for advance configurations
32 | func (a *App) Muxer() *mux.Router {
33 | return a.router
34 | }
35 |
36 | // New returns a new instance of App and adds some sane, and useful, defaults.
37 | func New(opts Options) *App {
38 | LoadPlugins()
39 | envy.Load()
40 |
41 | opts = optionsWithDefaults(opts)
42 |
43 | a := &App{
44 | Options: opts,
45 | Home: Home{
46 | name: opts.Name,
47 | host: opts.Host,
48 | prefix: opts.Prefix,
49 | ErrorHandlers: ErrorHandlers{
50 | http.StatusNotFound: defaultErrorHandler,
51 | http.StatusInternalServerError: defaultErrorHandler,
52 | },
53 | router: mux.NewRouter(),
54 | },
55 | moot: &sync.RWMutex{},
56 | routes: RouteList{},
57 | children: []*App{},
58 |
59 | RouteNamer: baseRouteNamer{},
60 | }
61 | a.Home.app = a // replace root.
62 | a.Home.appSelf = a // temporary, reverse reference to the group app.
63 |
64 | notFoundHandler := func(errorf string, code int) http.HandlerFunc {
65 | return func(res http.ResponseWriter, req *http.Request) {
66 | c := a.newContext(RouteInfo{}, res, req)
67 | err := fmt.Errorf(errorf, req.Method, req.URL.Path)
68 | _ = a.ErrorHandlers.Get(code)(code, err, c)
69 | }
70 | }
71 |
72 | a.router.NotFoundHandler = notFoundHandler("path not found: %s %s", http.StatusNotFound)
73 | a.router.MethodNotAllowedHandler = notFoundHandler("method not found: %s %s", http.StatusMethodNotAllowed)
74 |
75 | if a.MethodOverride == nil {
76 | a.MethodOverride = MethodOverride
77 | }
78 |
79 | a.Middleware = newMiddlewareStack(RequestLogger)
80 | a.Use(a.defaultErrorMiddleware)
81 | a.Use(a.PanicHandler)
82 |
83 | return a
84 | }
85 |
--------------------------------------------------------------------------------
/render/template_helpers_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "fmt"
5 | "html/template"
6 | "testing"
7 |
8 | "github.com/gobuffalo/tags/v3"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | type tagHelper = func(string, tags.Options) (template.HTML, error)
13 |
14 | func tag(name string) (tagHelper, error) {
15 | e := NewEngine()
16 | tr := e.Template("").(*templateRenderer)
17 |
18 | h := tr.addAssetsHelpers(Helpers{})
19 | jt := h[name]
20 | f, ok := jt.(func(string, tags.Options) (template.HTML, error))
21 | if !ok {
22 | return f, fmt.Errorf("expected tagHelper got %T", jt)
23 | }
24 | return f, nil
25 | }
26 |
27 | func Test_javascriptTag(t *testing.T) {
28 | r := require.New(t)
29 |
30 | f, err := tag("javascriptTag")
31 | r.NoError(err)
32 |
33 | s, err := f("application.js", nil)
34 | r.NoError(err)
35 | r.Equal(template.HTML(``), s)
36 | }
37 |
38 | func Test_javascriptTag_Options(t *testing.T) {
39 | r := require.New(t)
40 |
41 | f, err := tag("javascriptTag")
42 | r.NoError(err)
43 |
44 | s, err := f("application.js", tags.Options{"class": "foo"})
45 | r.NoError(err)
46 | r.Equal(template.HTML(``), s)
47 | }
48 |
49 | func Test_stylesheetTag(t *testing.T) {
50 | r := require.New(t)
51 |
52 | f, err := tag("stylesheetTag")
53 | r.NoError(err)
54 |
55 | s, err := f("application.css", nil)
56 | r.NoError(err)
57 | r.Equal(template.HTML(``), s)
58 | }
59 |
60 | func Test_stylesheetTag_Options(t *testing.T) {
61 | r := require.New(t)
62 |
63 | f, err := tag("stylesheetTag")
64 | r.NoError(err)
65 |
66 | s, err := f("application.css", tags.Options{"class": "foo"})
67 | r.NoError(err)
68 | r.Equal(template.HTML(``), s)
69 | }
70 |
71 | func Test_imgTag(t *testing.T) {
72 | r := require.New(t)
73 |
74 | f, err := tag("imgTag")
75 | r.NoError(err)
76 |
77 | s, err := f("foo.png", nil)
78 | r.NoError(err)
79 | r.Equal(template.HTML(`
`), s)
80 | }
81 |
82 | func Test_imgTag_Options(t *testing.T) {
83 | r := require.New(t)
84 |
85 | f, err := tag("imgTag")
86 | r.NoError(err)
87 |
88 | s, err := f("foo.png", tags.Options{"class": "foo"})
89 | r.NoError(err)
90 | r.Equal(template.HTML(`
`), s)
91 | }
92 |
--------------------------------------------------------------------------------
/mail/smtp_sender_test.go:
--------------------------------------------------------------------------------
1 | package mail_test
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/gobuffalo/buffalo/internal/fakesmtp"
8 | "github.com/gobuffalo/buffalo/mail"
9 | "github.com/gobuffalo/buffalo/render"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | var sender mail.Sender
14 | var rend *render.Engine
15 | var smtpServer *fakesmtp.Server
16 |
17 | const smtpPort = "2002"
18 |
19 | func init() {
20 | rend = render.New(render.Options{})
21 | smtpServer, _ = fakesmtp.New(smtpPort)
22 | sender, _ = mail.NewSMTPSender("127.0.0.1", smtpPort, "username", "password")
23 |
24 | go smtpServer.Start(smtpPort)
25 | }
26 |
27 | func TestSendPlain(t *testing.T) {
28 | smtpServer.Clear()
29 | r := require.New(t)
30 |
31 | m := mail.NewMessage()
32 | m.From = "mark@example.com"
33 | m.To = []string{"something@something.com"}
34 | m.Subject = "Cool Message"
35 | m.CC = []string{"other@other.com", "my@other.com"}
36 | m.Bcc = []string{"secret@other.com"}
37 |
38 | m.AddAttachment("someFile.txt", "text/plain", bytes.NewBuffer([]byte("hello")))
39 | m.AddAttachment("otherFile.txt", "text/plain", bytes.NewBuffer([]byte("bye")))
40 | m.AddEmbedded("test.jpg", bytes.NewBuffer([]byte("not a real image")))
41 | m.AddBody(rend.String("Hello <%= Name %>"), render.Data{"Name": "Antonio"})
42 | r.Equal(m.Bodies[0].Content, "Hello Antonio")
43 |
44 | m.SetHeader("X-SMTPAPI", `{"send_at": 1409348513}`)
45 |
46 | err := sender.Send(m)
47 | r.Nil(err)
48 |
49 | lastMessage := smtpServer.LastMessage()
50 |
51 | r.Contains(lastMessage, "FROM:")
52 | r.Contains(lastMessage, "RCPT TO:")
53 | r.Contains(lastMessage, "RCPT TO:")
54 | r.Contains(lastMessage, "RCPT TO:")
55 | r.Contains(lastMessage, "Subject: Cool Message")
56 | r.Contains(lastMessage, "Cc: other@other.com, my@other.com")
57 | r.Contains(lastMessage, "Content-Type: text/plain")
58 | r.Contains(lastMessage, "Hello Antonio")
59 | r.Contains(lastMessage, "Content-Disposition: attachment; filename=\"someFile.txt\"")
60 | r.Contains(lastMessage, "aGVsbG8=") //base64 of the file content
61 | r.Contains(lastMessage, "Content-Disposition: attachment; filename=\"otherFile.txt\"")
62 | r.Contains(lastMessage, "Ynll") //base64 of the file content
63 | r.Contains(lastMessage, "Content-Disposition: inline; filename=\"test.jpg\"")
64 | r.Contains(lastMessage, "bm90IGEgcmVhbCBpbWFnZQ==") //base64 of the file content
65 |
66 | r.Contains(lastMessage, `X-SMTPAPI: {"send_at": 1409348513}`)
67 | }
68 |
--------------------------------------------------------------------------------
/events.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | // TODO: TODO-v1 check if they are really need to be exported.
4 | /* The event id should be unique across packages as the format of
5 | "::" as documented. They
6 | should not be used by another packages to keep it informational. To make
7 | it sure, they need to be internal.
8 | Especially for plugable conponents like servers or workers, they can have
9 | their own event definition if they need but the buffalo runtime can emit
10 | generalize events when e.g. the runtime calls configured worker.
11 | */
12 | const (
13 | // EvtAppStart is emitted when buffalo.App#Serve is called
14 | EvtAppStart = "buffalo:app:start"
15 | // EvtAppStartErr is emitted when an error occurs calling buffalo.App#Serve
16 | EvtAppStartErr = "buffalo:app:start:err"
17 |
18 | // EvtAppStop is emitted when buffalo.App#Stop is called
19 | EvtAppStop = "buffalo:app:stop"
20 | // EvtAppStopErr is emitted when an error occurs calling buffalo.App#Stop
21 | EvtAppStopErr = "buffalo:app:stop:err"
22 |
23 | // EvtRouteStarted is emitted when a requested route is being processed
24 | EvtRouteStarted = "buffalo:route:started"
25 | // EvtRouteFinished is emitted when a requested route is completed
26 | EvtRouteFinished = "buffalo:route:finished"
27 | // EvtRouteErr is emitted when there is a problem handling processing a route
28 | EvtRouteErr = "buffalo:route:err"
29 |
30 | // EvtServerStart is emitted when buffalo is about to start servers
31 | EvtServerStart = "buffalo:server:start"
32 | // EvtServerStartErr is emitted when an error occurs when starting servers
33 | EvtServerStartErr = "buffalo:server:start:err"
34 | // EvtServerStop is emitted when buffalo is about to stop servers
35 | EvtServerStop = "buffalo:server:stop"
36 | // EvtServerStopErr is emitted when an error occurs when stopping servers
37 | EvtServerStopErr = "buffalo:server:stop:err"
38 |
39 | // EvtWorkerStart is emitted when buffalo is about to start workers
40 | EvtWorkerStart = "buffalo:worker:start"
41 | // EvtWorkerStartErr is emitted when an error occurs when starting workers
42 | EvtWorkerStartErr = "buffalo:worker:start:err"
43 | // EvtWorkerStop is emitted when buffalo is about to stop workers
44 | EvtWorkerStop = "buffalo:worker:stop"
45 | // EvtWorkerStopErr is emitted when an error occurs when stopping workers
46 | EvtWorkerStopErr = "buffalo:worker:stop:err"
47 |
48 | // EvtFailureErr is emitted when something can't be processed at all. it is a bad thing
49 | EvtFailureErr = "buffalo:failure:err"
50 | )
51 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gobuffalo/buffalo
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.2.1
7 | github.com/dustin/go-humanize v1.0.1
8 | github.com/gobuffalo/envy v1.10.2
9 | github.com/gobuffalo/events v1.4.3
10 | github.com/gobuffalo/flect v1.0.2
11 | github.com/gobuffalo/github_flavored_markdown v1.1.3
12 | github.com/gobuffalo/grift v1.5.2
13 | github.com/gobuffalo/helpers v0.6.10
14 | github.com/gobuffalo/httptest v1.5.2
15 | github.com/gobuffalo/logger v1.0.7
16 | github.com/gobuffalo/meta v0.3.3
17 | github.com/gobuffalo/nulls v0.4.2
18 | github.com/gobuffalo/plush/v5 v5.0.4
19 | github.com/gobuffalo/refresh v1.13.3
20 | github.com/gobuffalo/tags/v3 v3.1.4
21 | github.com/gorilla/handlers v1.5.1
22 | github.com/gorilla/mux v1.8.0
23 | github.com/gorilla/sessions v1.2.1
24 | github.com/monoculum/formam v3.5.5+incompatible
25 | github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef
26 | github.com/sirupsen/logrus v1.9.0
27 | github.com/spf13/cobra v1.6.1
28 | github.com/stretchr/testify v1.9.0
29 | golang.org/x/text v0.25.0
30 | )
31 |
32 | require (
33 | github.com/aymerick/douceur v0.2.0 // indirect
34 | github.com/davecgh/go-spew v1.1.1 // indirect
35 | github.com/fatih/color v1.13.0 // indirect
36 | github.com/fatih/structs v1.1.0 // indirect
37 | github.com/felixge/httpsnoop v1.0.1 // indirect
38 | github.com/fsnotify/fsnotify v1.6.0 // indirect
39 | github.com/gobuffalo/validate/v3 v3.3.3 // indirect
40 | github.com/gofrs/uuid v4.2.0+incompatible // indirect
41 | github.com/gorilla/css v1.0.0 // indirect
42 | github.com/gorilla/securecookie v1.1.1 // indirect
43 | github.com/inconshreveable/mousetrap v1.0.1 // indirect
44 | github.com/joho/godotenv v1.4.0 // indirect
45 | github.com/mattn/go-colorable v0.1.9 // indirect
46 | github.com/mattn/go-isatty v0.0.14 // indirect
47 | github.com/microcosm-cc/bluemonday v1.0.20 // indirect
48 | github.com/mitchellh/go-homedir v1.1.0 // indirect
49 | github.com/pmezard/go-difflib v1.0.0 // indirect
50 | github.com/rogpeppe/go-internal v1.9.0 // indirect
51 | github.com/sergi/go-diff v1.2.0 // indirect
52 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
53 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
54 | github.com/spf13/pflag v1.0.5 // indirect
55 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect
56 | golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
57 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
58 | gopkg.in/yaml.v3 v3.0.1 // indirect
59 | )
60 |
--------------------------------------------------------------------------------
/route.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "fmt"
5 | "html/template"
6 | "net/url"
7 | "sort"
8 | "strings"
9 | )
10 |
11 | // Routes returns a list of all of the routes defined
12 | // in this application.
13 | func (a *App) Routes() RouteList {
14 | // CHKME: why this function is exported? can we deprecate it?
15 | if a.root != nil {
16 | return a.root.routes
17 | }
18 | return a.routes
19 | }
20 |
21 | func addExtraParamsTo(path string, opts map[string]interface{}) string {
22 | pendingParams := map[string]string{}
23 | keys := []string{}
24 | for k, v := range opts {
25 | if strings.Contains(path, fmt.Sprintf("%v", v)) {
26 | continue
27 | }
28 |
29 | keys = append(keys, k)
30 | pendingParams[k] = fmt.Sprintf("%v", v)
31 | }
32 |
33 | if len(keys) == 0 {
34 | return path
35 | }
36 |
37 | if !strings.Contains(path, "?") {
38 | path = path + "?"
39 | } else {
40 | if !strings.HasSuffix(path, "?") {
41 | path = path + "&"
42 | }
43 | }
44 |
45 | sort.Strings(keys)
46 |
47 | for index, k := range keys {
48 | format := "%v=%v"
49 |
50 | if index > 0 {
51 | format = "&%v=%v"
52 | }
53 |
54 | path = path + fmt.Sprintf(format, url.QueryEscape(k), url.QueryEscape(pendingParams[k]))
55 | }
56 |
57 | return path
58 | }
59 |
60 | //RouteHelperFunc represents the function that takes the route and the opts and build the path
61 | type RouteHelperFunc func(opts map[string]interface{}) (template.HTML, error)
62 |
63 | // RouteList contains a mapping of the routes defined
64 | // in the application. This listing contains, Method, Path,
65 | // and the name of the Handler defined to process that route.
66 | type RouteList []*RouteInfo
67 |
68 | var methodOrder = map[string]string{
69 | "GET": "1",
70 | "POST": "2",
71 | "PUT": "3",
72 | "DELETE": "4",
73 | }
74 |
75 | func (a RouteList) Len() int { return len(a) }
76 | func (a RouteList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
77 | func (a RouteList) Less(i, j int) bool {
78 | // NOTE: it was used for sorting of app.routes but we don't sort the routes anymore.
79 | // keep it for compatibility but could be deprecated.
80 | x := a[i].App.host + a[i].Path + methodOrder[a[i].Method]
81 | y := a[j].App.host + a[j].Path + methodOrder[a[j].Method]
82 | return x < y
83 | }
84 |
85 | // Lookup search a specific PathName in the RouteList and return the *RouteInfo
86 | func (a RouteList) Lookup(name string) (*RouteInfo, error) {
87 | for _, ri := range a {
88 | if ri.PathName == name {
89 | return ri, nil
90 | }
91 | }
92 | return nil, fmt.Errorf("path name not found")
93 | }
94 |
--------------------------------------------------------------------------------
/resource.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | // Resource interface allows for the easy mapping
9 | // of common RESTful actions to a set of paths. See
10 | // the a.Resource documentation for more details.
11 | // NOTE: When skipping Resource handlers, you need to first declare your
12 | // resource handler as a type of buffalo.Resource for the Skip function to
13 | // properly recognize and match it.
14 | /*
15 | // Works:
16 | var cr Resource
17 | cr = &carsResource{&buffaloBaseResource{}}
18 | g = a.Resource("/cars", cr)
19 | g.Use(SomeMiddleware)
20 | g.Middleware.Skip(SomeMiddleware, cr.Show)
21 |
22 | // Doesn't Work:
23 | cr := &carsResource{&buffaloBaseResource{}}
24 | g = a.Resource("/cars", cr)
25 | g.Use(SomeMiddleware)
26 | g.Middleware.Skip(SomeMiddleware, cr.Show)
27 | */
28 | type Resource interface {
29 | List(Context) error
30 | Show(Context) error
31 | Create(Context) error
32 | Update(Context) error
33 | Destroy(Context) error
34 | }
35 |
36 | // Middler can be implemented to specify additional
37 | // middleware specific to the resource
38 | type Middler interface {
39 | Use() []MiddlewareFunc
40 | }
41 |
42 | // BaseResource fills in the gaps for any Resource interface
43 | // functions you don't want/need to implement.
44 | /*
45 | type UsersResource struct {
46 | Resource
47 | }
48 |
49 | func (ur *UsersResource) List(c Context) error {
50 | return c.Render(http.StatusOK, render.String("hello")
51 | }
52 |
53 | // This will fulfill the Resource interface, despite only having
54 | // one of the functions defined.
55 | &UsersResource{&BaseResource{})
56 | */
57 | type BaseResource struct{}
58 |
59 | // List default implementation. Returns a 404
60 | func (v BaseResource) List(c Context) error {
61 | return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
62 | }
63 |
64 | // Show default implementation. Returns a 404
65 | func (v BaseResource) Show(c Context) error {
66 | return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
67 | }
68 |
69 | // Create default implementation. Returns a 404
70 | func (v BaseResource) Create(c Context) error {
71 | return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
72 | }
73 |
74 | // Update default implementation. Returns a 404
75 | func (v BaseResource) Update(c Context) error {
76 | return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
77 | }
78 |
79 | // Destroy default implementation. Returns a 404
80 | func (v BaseResource) Destroy(c Context) error {
81 | return c.Error(http.StatusNotFound, fmt.Errorf("resource not implemented"))
82 | }
83 |
--------------------------------------------------------------------------------
/fs.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "fmt"
5 | "io/fs"
6 | "os"
7 | )
8 |
9 | // FS wraps a directory and an embed FS that are expected to have the same contents.
10 | // it prioritizes the directory FS and falls back to the embedded FS if the file cannot
11 | // be found on disk. This is useful during development or when deploying with
12 | // assets not embedded in the binary.
13 | //
14 | // Additionally FS hiddes any file named embed.go from the FS.
15 | type FS struct {
16 | embed fs.FS
17 | dir fs.FS
18 | }
19 |
20 | // NewFS returns a new FS that wraps the given directory and embedded FS.
21 | // the embed.FS is expected to embed the same files as the directory FS.
22 | func NewFS(embed fs.ReadDirFS, dir string) FS {
23 | return FS{
24 | embed: embed,
25 | dir: os.DirFS(dir),
26 | }
27 | }
28 |
29 | // Open opens the named file.
30 | //
31 | // When Open returns an error, it should be of type *PathError with the Op
32 | // field set to "open", the Path field set to name, and the Err field
33 | // describing the problem.
34 | //
35 | // Open should reject attempts to open names that do not satisfy
36 | // ValidPath(name), returning a *PathError with Err set to ErrInvalid or
37 | // ErrNotExist.
38 | func (f FS) Open(name string) (fs.File, error) {
39 | if name == "embed.go" {
40 | return nil, &fs.PathError{
41 | Op: "open",
42 | Path: name,
43 | Err: fs.ErrNotExist,
44 | }
45 | }
46 | file, err := f.getFile(name)
47 | if name == "." {
48 | // NOTE: It always returns the root from the "disk" instead
49 | // "embed". However, it could be fine since the the purpose
50 | // of buffalo.FS isn't supporting full featured filesystem.
51 | return rootFile{file}, err
52 | }
53 | return file, err
54 | }
55 |
56 | func (f FS) getFile(name string) (fs.File, error) {
57 | file, err := f.dir.Open(name)
58 | if err == nil {
59 | return file, nil
60 | }
61 |
62 | return f.embed.Open(name)
63 | }
64 |
65 | // rootFile wraps the "." directory for hidding the embed.go file.
66 | type rootFile struct {
67 | fs.File
68 | }
69 |
70 | // ReadDir implements the fs.ReadDirFile interface.
71 | func (f rootFile) ReadDir(n int) (entries []fs.DirEntry, err error) {
72 | dir, ok := f.File.(fs.ReadDirFile)
73 | if !ok {
74 | return nil, fmt.Errorf("%T is not a directory", f.File)
75 | }
76 |
77 | entries, err = dir.ReadDir(n)
78 | entries = hideEmbedFile(entries)
79 | return entries, err
80 | }
81 |
82 | func hideEmbedFile(entries []fs.DirEntry) []fs.DirEntry {
83 | result := make([]fs.DirEntry, 0, len(entries))
84 |
85 | for _, entry := range entries {
86 | if entry.Name() != "embed.go" {
87 | result = append(result, entry)
88 | }
89 | }
90 | return result
91 | }
92 |
--------------------------------------------------------------------------------
/mail/message.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "context"
5 | "io"
6 | "sync"
7 |
8 | "bytes"
9 |
10 | "github.com/gobuffalo/buffalo/render"
11 | )
12 |
13 | //Message represents an Email message
14 | type Message struct {
15 | Context context.Context
16 | From string
17 | To []string
18 | CC []string
19 | Bcc []string
20 | Subject string
21 | Headers map[string]string
22 | Data render.Data
23 | Bodies []Body
24 | Attachments []Attachment
25 | moot *sync.RWMutex
26 | }
27 |
28 | func (m *Message) merge(data render.Data) render.Data {
29 | d := render.Data{}
30 | m.moot.Lock()
31 | for k, v := range m.Data {
32 | d[k] = v
33 | }
34 | m.moot.Unlock()
35 | for k, v := range data {
36 | d[k] = v
37 | }
38 | return d
39 | }
40 |
41 | // AddBody the message by receiving a renderer and rendering data, first message will be
42 | // used as the main message Body rest of them will be passed as alternative bodies on the
43 | // email message
44 | func (m *Message) AddBody(r render.Renderer, data render.Data) error {
45 | buf := bytes.NewBuffer([]byte{})
46 | err := r.Render(buf, m.merge(data))
47 |
48 | if err != nil {
49 | return err
50 | }
51 |
52 | m.Bodies = append(m.Bodies, Body{
53 | Content: buf.String(),
54 | ContentType: r.ContentType(),
55 | })
56 |
57 | return nil
58 | }
59 |
60 | // AddBodies Allows to add multiple bodies to the message, it returns errors that
61 | // could happen in the rendering.
62 | func (m *Message) AddBodies(data render.Data, renderers ...render.Renderer) error {
63 | for _, r := range renderers {
64 | err := m.AddBody(r, data)
65 | if err != nil {
66 | return err
67 | }
68 | }
69 |
70 | return nil
71 | }
72 |
73 | //AddAttachment adds the attachment to the list of attachments the Message has.
74 | func (m *Message) AddAttachment(name, contentType string, r io.Reader) error {
75 | m.Attachments = append(m.Attachments, Attachment{
76 | Name: name,
77 | ContentType: contentType,
78 | Reader: r,
79 | Embedded: false,
80 | })
81 |
82 | return nil
83 | }
84 |
85 | //AddEmbedded adds the attachment to the list of attachments
86 | // the Message has and uses inline instead of attachement property.
87 | func (m *Message) AddEmbedded(name string, r io.Reader) error {
88 | m.Attachments = append(m.Attachments, Attachment{
89 | Name: name,
90 | Reader: r,
91 | Embedded: true,
92 | })
93 |
94 | return nil
95 | }
96 |
97 | // SetHeader sets the heder field and value for the message
98 | func (m *Message) SetHeader(field, value string) {
99 | m.Headers[field] = value
100 | }
101 |
--------------------------------------------------------------------------------
/render/html_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/psanford/memfs"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | const htmlLayout = "layout.html"
13 | const htmlAltLayout = "alt_layout.plush.html"
14 | const htmlTemplate = "my-template.html"
15 |
16 | func Test_HTML_WithoutLayout(t *testing.T) {
17 | r := require.New(t)
18 |
19 | rootFS := memfs.New()
20 | r.NoError(rootFS.WriteFile(htmlTemplate, []byte("<%= name %>"), 0644))
21 |
22 | e := NewEngine()
23 | e.TemplatesFS = rootFS
24 |
25 | h := e.HTML(htmlTemplate)
26 | r.Equal("text/html; charset=utf-8", h.ContentType())
27 | bb := &bytes.Buffer{}
28 |
29 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
30 | r.Equal("Mark", strings.TrimSpace(bb.String()))
31 | }
32 |
33 | func Test_HTML_WithLayout(t *testing.T) {
34 | r := require.New(t)
35 |
36 | rootFS := memfs.New()
37 | r.NoError(rootFS.WriteFile(htmlTemplate, []byte("<%= name %>"), 0644))
38 | r.NoError(rootFS.WriteFile(htmlLayout, []byte("<%= yield %>"), 0644))
39 |
40 | e := NewEngine()
41 | e.TemplatesFS = rootFS
42 | e.HTMLLayout = htmlLayout
43 |
44 | h := e.HTML(htmlTemplate)
45 | r.Equal("text/html; charset=utf-8", h.ContentType())
46 | bb := &bytes.Buffer{}
47 |
48 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
49 | r.Equal("Mark", strings.TrimSpace(bb.String()))
50 | }
51 |
52 | func Test_HTML_WithLayout_Override(t *testing.T) {
53 | r := require.New(t)
54 |
55 | rootFS := memfs.New()
56 | r.NoError(rootFS.WriteFile(htmlTemplate, []byte("<%= name %>"), 0644))
57 | r.NoError(rootFS.WriteFile(htmlLayout, []byte("<%= yield %>"), 0644))
58 | r.NoError(rootFS.WriteFile(htmlAltLayout, []byte("<%= yield %>"), 0644))
59 |
60 | e := NewEngine()
61 | e.TemplatesFS = rootFS
62 | e.HTMLLayout = htmlLayout
63 |
64 | h := e.HTML(htmlTemplate, htmlAltLayout)
65 | r.Equal("text/html; charset=utf-8", h.ContentType())
66 | bb := &bytes.Buffer{}
67 |
68 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
69 | r.Equal("Mark", strings.TrimSpace(bb.String()))
70 | }
71 |
72 | func Test_HTML_LeadingSlash(t *testing.T) {
73 | r := require.New(t)
74 |
75 | rootFS := memfs.New()
76 | r.NoError(rootFS.WriteFile(htmlTemplate, []byte("<%= name %>"), 0644))
77 | r.NoError(rootFS.WriteFile(htmlLayout, []byte("<%= yield %>"), 0644))
78 |
79 | e := NewEngine()
80 | e.TemplatesFS = rootFS
81 | e.HTMLLayout = htmlLayout
82 |
83 | h := e.HTML("/my-template.html") // instead of "my-template.html"
84 | r.Equal("text/html; charset=utf-8", h.ContentType())
85 | bb := &bytes.Buffer{}
86 |
87 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
88 | r.Equal("Mark", strings.TrimSpace(bb.String()))
89 | }
90 |
--------------------------------------------------------------------------------
/context.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "net/url"
7 |
8 | "github.com/gobuffalo/buffalo/binding"
9 | "github.com/gobuffalo/buffalo/internal/httpx"
10 | "github.com/gobuffalo/buffalo/render"
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | // Context holds on to information as you
15 | // pass it down through middleware, Handlers,
16 | // templates, etc... It strives to make your
17 | // life a happier one.
18 | type Context interface {
19 | context.Context
20 | Response() http.ResponseWriter
21 | Request() *http.Request
22 | Session() *Session
23 | Cookies() *Cookies
24 | Params() ParamValues
25 | Param(string) string
26 | Set(string, interface{})
27 | LogField(string, interface{})
28 | LogFields(map[string]interface{})
29 | Logger() Logger
30 | Bind(interface{}) error
31 | Render(int, render.Renderer) error
32 | Error(int, error) error
33 | Redirect(int, string, ...interface{}) error
34 | Data() map[string]interface{}
35 | Flash() *Flash
36 | File(string) (binding.File, error)
37 | }
38 |
39 | // ParamValues will most commonly be url.Values,
40 | // but isn't it great that you set your own? :)
41 | type ParamValues interface {
42 | Get(string) string
43 | }
44 |
45 | func (a *App) newContext(info RouteInfo, res http.ResponseWriter, req *http.Request) Context {
46 | if ws, ok := res.(*Response); ok {
47 | res = ws
48 | }
49 |
50 | // Parse URL Params
51 | params := url.Values{}
52 | vars := mux.Vars(req)
53 | for k, v := range vars {
54 | params.Add(k, v)
55 | }
56 |
57 | // Parse URL Query String Params
58 | // For POST, PUT, and PATCH requests, it also parse the request body as a form.
59 | // Request body parameters take precedence over URL query string values in params
60 | if err := req.ParseForm(); err == nil {
61 | for k, v := range req.Form {
62 | for _, vv := range v {
63 | params.Add(k, vv)
64 | }
65 | }
66 | }
67 |
68 | session := a.getSession(req, res)
69 |
70 | ct := httpx.ContentType(req)
71 |
72 | data := newRequestData()
73 | data.d = map[string]interface{}{
74 | "app": a,
75 | "env": a.Env,
76 | "routes": a.Routes(),
77 | "current_route": info,
78 | "current_path": req.URL.Path,
79 | "contentType": ct,
80 | "method": req.Method,
81 | }
82 |
83 | for _, route := range a.Routes() {
84 | cRoute := route
85 | data.d[cRoute.PathName] = cRoute.BuildPathHelper()
86 | }
87 |
88 | return &DefaultContext{
89 | Context: req.Context(),
90 | contentType: ct,
91 | response: res,
92 | request: req,
93 | params: params,
94 | logger: a.Logger,
95 | session: session,
96 | flash: newFlash(session),
97 | data: data,
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/binding/binding.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/gobuffalo/buffalo/binding/decoders"
8 | "github.com/gobuffalo/nulls"
9 | "github.com/monoculum/formam"
10 | )
11 |
12 | var (
13 | // MaxFileMemory can be used to set the maximum size, in bytes, for files to be
14 | // stored in memory during uploaded for multipart requests.
15 | // See https://golang.org/pkg/net/http/#Request.ParseMultipartForm for more
16 | // information on how this impacts file uploads.
17 | MaxFileMemory int64 = 5 * 1024 * 1024
18 |
19 | // formDecoder (formam) that will be used across ContentTypeBinders
20 | formDecoder = buildFormDecoder()
21 |
22 | // BaseRequestBinder is an instance of the requestBinder, it comes with preconfigured
23 | // content type binders for HTML, JSON, XML and Files, as well as custom types decoders
24 | // for time.Time and nulls.Time
25 | BaseRequestBinder = NewRequestBinder(
26 | HTMLContentTypeBinder{
27 | decoder: formDecoder,
28 | },
29 | JSONContentTypeBinder{},
30 | XMLRequestTypeBinder{},
31 | FileRequestTypeBinder{
32 | decoder: formDecoder,
33 | },
34 | )
35 | )
36 |
37 | // buildFormDecoder that will be used in the package. This method adds some custom decoders for time.Time and nulls.Time.
38 | func buildFormDecoder() *formam.Decoder {
39 | decoder := formam.NewDecoder(&formam.DecoderOptions{
40 | TagName: "form",
41 | IgnoreUnknownKeys: true,
42 | })
43 |
44 | decoder.RegisterCustomType(decoders.TimeDecoderFn(), []interface{}{time.Time{}}, nil)
45 | decoder.RegisterCustomType(decoders.NullTimeDecoderFn(), []interface{}{nulls.Time{}}, nil)
46 |
47 | return decoder
48 | }
49 |
50 | // RegisterTimeFormats allows to add custom time layouts that
51 | // the binder will be able to use for decoding.
52 | func RegisterTimeFormats(layouts ...string) {
53 | decoders.RegisterTimeFormats(layouts...)
54 | }
55 |
56 | // RegisterCustomDecoder allows to define custom decoders for certain types
57 | // In the request.
58 | func RegisterCustomDecoder(fn CustomTypeDecoder, types []interface{}, fields []interface{}) {
59 | rawFunc := (func([]string) (interface{}, error))(fn)
60 | formDecoder.RegisterCustomType(rawFunc, types, fields)
61 | }
62 |
63 | // Register maps a request Content-Type (application/json)
64 | // to a Binder.
65 | func Register(contentType string, fn Binder) {
66 | BaseRequestBinder.Register(contentType, fn)
67 | }
68 |
69 | // Exec will bind the interface to the request.Body. The type of binding
70 | // is dependent on the "Content-Type" for the request. If the type
71 | // is "application/json" it will use "json.NewDecoder". If the type
72 | // is "application/xml" it will use "xml.NewDecoder". The default
73 | // binder is "https://github.com/monoculum/formam".
74 | func Exec(req *http.Request, value interface{}) error {
75 | return BaseRequestBinder.Exec(req, value)
76 | }
77 |
--------------------------------------------------------------------------------
/wrappers.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 |
7 | "github.com/gobuffalo/buffalo/internal/httpx"
8 | "github.com/gorilla/mux"
9 | )
10 |
11 | // WrapHandler wraps a standard http.Handler and transforms it
12 | // into a buffalo.Handler.
13 | func WrapHandler(h http.Handler) Handler {
14 | return func(c Context) error {
15 | h.ServeHTTP(c.Response(), c.Request())
16 | return nil
17 | }
18 | }
19 |
20 | // WrapHandlerFunc wraps a standard http.HandlerFunc and
21 | // transforms it into a buffalo.Handler.
22 | func WrapHandlerFunc(h http.HandlerFunc) Handler {
23 | return WrapHandler(h)
24 | }
25 |
26 | // WrapBuffaloHandler wraps a buffalo.Handler to a standard http.Handler
27 | //
28 | // NOTE: A buffalo Handler expects a buffalo Context. WrapBuffaloHandler uses
29 | // the same logic as DefaultContext where possible, but some functionality
30 | // (e.g. sessions and logging) WILL NOT work with this unwrap function. If
31 | // those features are needed a custom UnwrapHandlerFunc needs to be
32 | // implemented that provides a Context implementing those features.
33 | func WrapBuffaloHandler(h Handler) http.Handler {
34 | return WrapBuffaloHandlerFunc(h)
35 | }
36 |
37 | // WrapBuffaloHandlerFunc wraps a buffalo.Handler to a standard http.HandlerFunc
38 | //
39 | // NOTE: A buffalo Handler expects a buffalo Context. WrapBuffaloHandlerFunc uses
40 | // the same logic as DefaultContext where possible, but some functionality
41 | // (e.g. sessions and logging) WILL NOT work with this unwrap function. If
42 | // those features are needed a custom WrapBuffaloHandlerFunc needs to be
43 | // implemented that provides a Context implementing those features.
44 | func WrapBuffaloHandlerFunc(h Handler) http.HandlerFunc {
45 | return func(res http.ResponseWriter, req *http.Request) {
46 | if ws, ok := res.(*Response); ok {
47 | res = ws
48 | }
49 |
50 | // Parse URL Params
51 | params := url.Values{}
52 | vars := mux.Vars(req)
53 | for k, v := range vars {
54 | params.Add(k, v)
55 | }
56 |
57 | // Parse URL Query String Params
58 | // For POST, PUT, and PATCH requests, it also parse the request body as a form.
59 | // Request body parameters take precedence over URL query string values in params
60 | if err := req.ParseForm(); err == nil {
61 | for k, v := range req.Form {
62 | for _, vv := range v {
63 | params.Add(k, vv)
64 | }
65 | }
66 | }
67 |
68 | ct := httpx.ContentType(req)
69 |
70 | data := newRequestData()
71 | data.d = map[string]interface{}{
72 | "current_path": req.URL.Path,
73 | "contentType": ct,
74 | "method": req.Method,
75 | }
76 | c := &DefaultContext{
77 | Context: req.Context(),
78 | contentType: ct,
79 | response: res,
80 | request: req,
81 | params: params,
82 | flash: &Flash{data: map[string][]string{}},
83 | data: data,
84 | }
85 | h(c)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/render/download.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "mime"
8 | "net/http"
9 | "path/filepath"
10 | "strconv"
11 | )
12 |
13 | type downloadRenderer struct {
14 | ctx context.Context
15 | name string
16 | reader io.Reader
17 | }
18 |
19 | func (r downloadRenderer) ContentType() string {
20 | ext := filepath.Ext(r.name)
21 | t := mime.TypeByExtension(ext)
22 | if t == "" {
23 | t = "application/octet-stream"
24 | }
25 |
26 | return t
27 | }
28 |
29 | func (r downloadRenderer) Render(w io.Writer, d Data) error {
30 | written, err := io.Copy(w, r.reader)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | ctx, ok := r.ctx.(responsible)
36 | if !ok {
37 | return fmt.Errorf("context has no response writer")
38 | }
39 |
40 | header := ctx.Response().Header()
41 | disposition := fmt.Sprintf("attachment; filename=%s", r.name)
42 | header.Add("Content-Disposition", disposition)
43 | contentLength := strconv.Itoa(int(written))
44 | header.Add("Content-Length", contentLength)
45 |
46 | return nil
47 | }
48 |
49 | // Download renders a file attachment automatically setting following headers:
50 | //
51 | // Content-Type
52 | // Content-Length
53 | // Content-Disposition
54 | //
55 | // Content-Type is set using mime#TypeByExtension with the filename's extension. Content-Type will default to
56 | // application/octet-stream if using a filename with an unknown extension.
57 | //
58 | // Note: the purpose of this function is not serving static files but to support
59 | // downloading of dynamically genrated data as a file. For example, you can use
60 | // this function when you implement CSV file download feature for the result of
61 | // a database query.
62 | //
63 | // Do not use this function for large io.Reader. It could cause out of memory if
64 | // the size of io.Reader is too big.
65 | func Download(ctx context.Context, name string, r io.Reader) Renderer {
66 | return downloadRenderer{
67 | ctx: ctx,
68 | name: name,
69 | reader: r,
70 | }
71 | }
72 |
73 | // Download renders a file attachment automatically setting following headers:
74 | //
75 | // Content-Type
76 | // Content-Length
77 | // Content-Disposition
78 | //
79 | // Content-Type is set using mime#TypeByExtension with the filename's extension. Content-Type will default to
80 | // application/octet-stream if using a filename with an unknown extension.
81 | //
82 | // Note: the purpose of this method is not serving static files but to support
83 | // downloading of dynamically genrated data as a file. For example, you can use
84 | // this method when you implement CSV file download feature for the result of
85 | // a database query.
86 | //
87 | // Do not use this method for large io.Reader. It could cause out of memory if
88 | // the size of io.Reader is too big.
89 | func (e *Engine) Download(ctx context.Context, name string, r io.Reader) Renderer {
90 | return Download(ctx, name, r)
91 | }
92 |
93 | type responsible interface {
94 | Response() http.ResponseWriter
95 | }
96 |
--------------------------------------------------------------------------------
/mail/internal/mail/send.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | stdmail "net/mail"
7 | )
8 |
9 | // Sender is the interface that wraps the Send method.
10 | //
11 | // Send sends an email to the given addresses.
12 | type Sender interface {
13 | Send(from string, to []string, msg io.WriterTo) error
14 | }
15 |
16 | // SendCloser is the interface that groups the Send and Close methods.
17 | type SendCloser interface {
18 | Sender
19 | Close() error
20 | }
21 |
22 | // A SendFunc is a function that sends emails to the given addresses.
23 | //
24 | // The SendFunc type is an adapter to allow the use of ordinary functions as
25 | // email senders. If f is a function with the appropriate signature, SendFunc(f)
26 | // is a Sender object that calls f.
27 | type SendFunc func(from string, to []string, msg io.WriterTo) error
28 |
29 | // Send calls f(from, to, msg).
30 | func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
31 | return f(from, to, msg)
32 | }
33 |
34 | // Send sends emails using the given Sender.
35 | func Send(s Sender, msg ...*Message) []error {
36 | errors := make([]error, len(msg))
37 | for i, m := range msg {
38 | if err := send(s, m); err != nil {
39 | errors[i] = &SendError{Cause: err, Index: uint(i)}
40 | }
41 | }
42 |
43 | return errors
44 | }
45 |
46 | func send(s Sender, m *Message) error {
47 | from, err := m.getFrom()
48 | if err != nil {
49 | return err
50 | }
51 |
52 | to, err := m.getRecipients()
53 | if err != nil {
54 | return err
55 | }
56 |
57 | if err := s.Send(from, to, m); err != nil {
58 | return err
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func (m *Message) getFrom() (string, error) {
65 | from := m.header["Sender"]
66 | if len(from) == 0 {
67 | from = m.header["From"]
68 | if len(from) == 0 {
69 | return "", fmt.Errorf(`gomail: invalid message, "From" field is absent`)
70 | }
71 | }
72 |
73 | return parseAddress(from[0])
74 | }
75 |
76 | func (m *Message) getRecipients() ([]string, error) {
77 | n := 0
78 | for _, field := range []string{"To", "Cc", "Bcc"} {
79 | if addresses, ok := m.header[field]; ok {
80 | n += len(addresses)
81 | }
82 | }
83 | list := make([]string, 0, n)
84 |
85 | for _, field := range []string{"To", "Cc", "Bcc"} {
86 | if addresses, ok := m.header[field]; ok {
87 | for _, a := range addresses {
88 | addr, err := parseAddress(a)
89 | if err != nil {
90 | return nil, err
91 | }
92 | list = addAddress(list, addr)
93 | }
94 | }
95 | }
96 |
97 | return list, nil
98 | }
99 |
100 | func addAddress(list []string, addr string) []string {
101 | for _, a := range list {
102 | if addr == a {
103 | return list
104 | }
105 | }
106 |
107 | return append(list, addr)
108 | }
109 |
110 | func parseAddress(field string) (string, error) {
111 | addr, err := stdmail.ParseAddress(field)
112 | if err != nil {
113 | return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
114 | }
115 | return addr.Address, nil
116 | }
117 |
--------------------------------------------------------------------------------
/wrappers_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/gobuffalo/buffalo/render"
8 | "github.com/gobuffalo/httptest"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_WrapHandlerFunc(t *testing.T) {
13 | r := require.New(t)
14 |
15 | a := New(Options{})
16 | a.GET("/foo", WrapHandlerFunc(func(res http.ResponseWriter, req *http.Request) {
17 | res.Write([]byte("hello"))
18 | }))
19 |
20 | w := httptest.New(a)
21 | res := w.HTML("/foo").Get()
22 |
23 | r.Equal("hello", res.Body.String())
24 | }
25 |
26 | func Test_WrapHandler(t *testing.T) {
27 | r := require.New(t)
28 |
29 | a := New(Options{})
30 | a.GET("/foo", WrapHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
31 | res.Write([]byte("hello"))
32 | })))
33 |
34 | w := httptest.New(a)
35 | res := w.HTML("/foo").Get()
36 |
37 | r.Equal("hello", res.Body.String())
38 | }
39 |
40 | func Test_WrapBuffaloHandler(t *testing.T) {
41 | r := require.New(t)
42 |
43 | tt := []struct {
44 | verb string
45 | path string
46 | status int
47 | }{
48 | {"GET", "/", 200},
49 | {"GET", "/foo", 201},
50 | {"POST", "/", 300},
51 | {"POST", "/foo", 400},
52 | }
53 | for _, x := range tt {
54 | bf := func(c Context) error {
55 | req := c.Request()
56 | return c.Render(x.status, render.String(req.Method+req.URL.Path))
57 | }
58 |
59 | h := WrapBuffaloHandler(bf)
60 | r.NotNil(h)
61 |
62 | req := httptest.NewRequest(x.verb, x.path, nil)
63 | res := httptest.NewRecorder()
64 |
65 | h.ServeHTTP(res, req)
66 |
67 | r.Equal(x.status, res.Code)
68 | r.Contains(res.Body.String(), x.verb+x.path)
69 | }
70 | }
71 |
72 | func Test_WrapBuffaloHandlerFunc(t *testing.T) {
73 | r := require.New(t)
74 |
75 | tt := []struct {
76 | verb string
77 | path string
78 | status int
79 | }{
80 | {"GET", "/", 200},
81 | {"GET", "/foo", 201},
82 | {"POST", "/", 300},
83 | {"POST", "/foo", 400},
84 | }
85 | for _, x := range tt {
86 | bf := func(c Context) error {
87 | req := c.Request()
88 | return c.Render(x.status, render.String(req.Method+req.URL.Path))
89 | }
90 |
91 | h := WrapBuffaloHandlerFunc(bf)
92 | r.NotNil(h)
93 |
94 | req := httptest.NewRequest(x.verb, x.path, nil)
95 | res := httptest.NewRecorder()
96 |
97 | h(res, req)
98 |
99 | r.Equal(x.status, res.Code)
100 | r.Contains(res.Body.String(), x.verb+x.path)
101 | }
102 | }
103 |
104 | func Benchmark_WrapBuffaloHandler(b *testing.B) {
105 | r := require.New(b)
106 |
107 | status := http.StatusOK
108 |
109 | bf := func(c Context) error {
110 | return c.Render(status, render.String(http.StatusText(status)))
111 | }
112 |
113 | req := httptest.NewRequest(http.MethodGet, "/foo", nil)
114 | res := httptest.NewRecorder()
115 |
116 | b.StartTimer()
117 | for i := 0; i < b.N; i++ {
118 |
119 | h := WrapBuffaloHandler(bf)
120 | r.NotNil(h)
121 |
122 | h.ServeHTTP(res, req)
123 |
124 | r.Equal(status, res.Code)
125 | r.Contains(res.Body.String(), http.StatusText(status))
126 | }
127 | b.StopTimer()
128 | }
129 |
--------------------------------------------------------------------------------
/mail/smtp_sender.go:
--------------------------------------------------------------------------------
1 | package mail
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strconv"
7 |
8 | gomail "github.com/gobuffalo/buffalo/mail/internal/mail"
9 | )
10 |
11 | //SMTPSender allows to send Emails by connecting to a SMTP server.
12 | type SMTPSender struct {
13 | Dialer *gomail.Dialer
14 | }
15 |
16 | //Send a message using SMTP configuration or returns an error if something goes wrong.
17 | func (sm SMTPSender) Send(message Message) error {
18 | return sm.Dialer.DialAndSend(sm.prepareMessage(message))
19 | }
20 |
21 | // SendBatch of message with one connection, returns general error or errors specific for each message
22 | func (sm SMTPSender) SendBatch(messages ...Message) (errorsByMessages []error, generalError error) {
23 | preparedMessages := make([]*gomail.Message, len(messages))
24 | for i, message := range messages {
25 | preparedMessages[i] = sm.prepareMessage(message)
26 | }
27 |
28 | s, err := sm.Dialer.Dial()
29 | if err != nil {
30 | return nil, err
31 | }
32 | defer s.Close()
33 |
34 | return gomail.Send(s, preparedMessages...), nil
35 | }
36 | func (sm SMTPSender) prepareMessage(message Message) *gomail.Message {
37 | gm := gomail.NewMessage()
38 |
39 | gm.SetHeader("From", message.From)
40 | gm.SetHeader("To", message.To...)
41 | gm.SetHeader("Subject", message.Subject)
42 | gm.SetHeader("Cc", message.CC...)
43 | gm.SetHeader("Bcc", message.Bcc...)
44 |
45 | sm.addBodies(message, gm)
46 | sm.addAttachments(message, gm)
47 |
48 | for field, value := range message.Headers {
49 | gm.SetHeader(field, value)
50 | }
51 |
52 | return gm
53 | }
54 |
55 | func (sm SMTPSender) addBodies(message Message, gm *gomail.Message) {
56 | if len(message.Bodies) == 0 {
57 | return
58 | }
59 |
60 | mainBody := message.Bodies[0]
61 | gm.SetBody(mainBody.ContentType, mainBody.Content, gomail.SetPartEncoding(gomail.Unencoded))
62 |
63 | for i := 1; i < len(message.Bodies); i++ {
64 | alt := message.Bodies[i]
65 | gm.AddAlternative(alt.ContentType, alt.Content, gomail.SetPartEncoding(gomail.Unencoded))
66 | }
67 | }
68 |
69 | func (sm SMTPSender) addAttachments(message Message, gm *gomail.Message) {
70 |
71 | for _, at := range message.Attachments {
72 | currentAttachement := at
73 | settings := gomail.SetCopyFunc(func(w io.Writer) error {
74 | _, err := io.Copy(w, currentAttachement.Reader)
75 | return err
76 | })
77 |
78 | if currentAttachement.Embedded {
79 | gm.Embed(currentAttachement.Name, settings)
80 | } else {
81 | gm.Attach(currentAttachement.Name, settings)
82 | }
83 |
84 | }
85 | }
86 |
87 | //NewSMTPSender builds a SMTP mail based in passed config.
88 | func NewSMTPSender(host string, port string, user string, password string) (SMTPSender, error) {
89 | iport, err := strconv.Atoi(port)
90 |
91 | if err != nil {
92 | return SMTPSender{}, fmt.Errorf("invalid port for the SMTP mail")
93 | }
94 |
95 | dialer := &gomail.Dialer{
96 | Host: host,
97 | Port: iport,
98 | }
99 |
100 | if user != "" {
101 | dialer.Username = user
102 | dialer.Password = password
103 | }
104 |
105 | return SMTPSender{
106 | Dialer: dialer,
107 | }, nil
108 | }
109 |
--------------------------------------------------------------------------------
/fs_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "io"
5 | "io/fs"
6 | "testing"
7 |
8 | "github.com/gobuffalo/buffalo/internal/testdata/embedded"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func Test_FS_Disallows_Parent_Folders(t *testing.T) {
13 | r := require.New(t)
14 |
15 | fsys := NewFS(embedded.FS(), "internal/testdata/disk")
16 | r.NotNil(fsys)
17 |
18 | f, err := fsys.Open("../panic.txt")
19 | r.ErrorIs(err, fs.ErrNotExist)
20 | r.Nil(f)
21 |
22 | f, err = fsys.Open("try/../to/../trick/../panic.txt")
23 | r.ErrorIs(err, fs.ErrNotExist)
24 | r.Nil(f)
25 | }
26 |
27 | func Test_FS_Hides_embed_go(t *testing.T) {
28 | r := require.New(t)
29 |
30 | fsys := NewFS(embedded.FS(), "internal/testdata/disk")
31 | r.NotNil(fsys)
32 |
33 | f, err := fsys.Open("embed.go")
34 | r.ErrorIs(err, fs.ErrNotExist)
35 | r.Nil(f)
36 | }
37 |
38 | func Test_FS_Prioritizes_Disk(t *testing.T) {
39 | r := require.New(t)
40 |
41 | fsys := NewFS(embedded.FS(), "internal/testdata/disk")
42 | r.NotNil(fsys)
43 |
44 | f, err := fsys.Open("file.txt")
45 | r.NoError(err)
46 |
47 | b, err := io.ReadAll(f)
48 | r.NoError(err)
49 |
50 | r.Equal("This file is on disk.", string(b))
51 |
52 | // should handle slash-separated path for all systems including Windows
53 | f, err = fsys.Open("under/sub/subfile")
54 | r.NoError(err)
55 |
56 | b, err = io.ReadAll(f)
57 | r.NoError(err)
58 |
59 | r.Equal("This file is on disk/sub.", string(b))
60 | }
61 |
62 | func Test_FS_Uses_Embed_If_No_Disk(t *testing.T) {
63 | r := require.New(t)
64 |
65 | fsys := NewFS(embedded.FS(), "internal/testdata/empty")
66 | r.NotNil(fsys)
67 |
68 | f, err := fsys.Open("file.txt")
69 | r.NoError(err)
70 |
71 | b, err := io.ReadAll(f)
72 | r.NoError(err)
73 |
74 | r.Equal("This file is embedded.", string(b))
75 |
76 | // should handle slash-separated path for all systems including Windows
77 | f, err = fsys.Open("under/sub/subfile")
78 | r.NoError(err)
79 |
80 | b, err = io.ReadAll(f)
81 | r.NoError(err)
82 |
83 | r.Equal("This file is on embedded/sub.", string(b))
84 | }
85 |
86 | func Test_FS_ReadDirFile(t *testing.T) {
87 | r := require.New(t)
88 |
89 | fsys := NewFS(embedded.FS(), "internal/testdata/disk")
90 | r.NotNil(fsys)
91 |
92 | f, err := fsys.Open(".")
93 | r.NoError(err)
94 |
95 | dir, ok := f.(fs.ReadDirFile)
96 | r.True(ok, "folder does not implement fs.ReadDirFile interface")
97 |
98 | // First read should return at most 1 file
99 | entries, err := dir.ReadDir(1)
100 | r.NoError(err)
101 |
102 | // The actual len will be 0 because the first file read is the embed.go file
103 | // this is counter-intuitive, but it's how the fs.ReadDirFile interface is specified;
104 | // if err == nil, just continue to call ReadDir until io.EOF is returned.
105 | r.LessOrEqual(len(entries), 1, "a call to ReadDir must at most return n entries")
106 |
107 | // Second read should return at most 2 files
108 | entries, err = dir.ReadDir(3)
109 | r.NoError(err)
110 |
111 | // The actual len will be 2 (file.txt & file2.txt + under/)
112 | r.LessOrEqual(len(entries), 3, "a call to ReadDir must at most return n entries")
113 |
114 | // trying to read next 2 files (none left)
115 | entries, err = dir.ReadDir(2)
116 | r.ErrorIs(err, io.EOF)
117 | r.Empty(entries)
118 | }
119 |
--------------------------------------------------------------------------------
/grifts.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "crypto/rand"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "text/tabwriter"
9 |
10 | "github.com/gobuffalo/grift/grift"
11 | )
12 |
13 | // Grifts decorates the app with tasks
14 | func Grifts(app *App) {
15 | routesGrift(app)
16 | middlewareGrift(app)
17 | secretGrift()
18 | }
19 |
20 | func secretGrift() {
21 | grift.Desc("secret", "Generate a cryptographically secure secret key")
22 | grift.Add("secret", func(c *grift.Context) error {
23 | b := make([]byte, 64)
24 | _, err := rand.Read(b)
25 | if err != nil {
26 | return err
27 | }
28 | fmt.Println(string(b))
29 | return nil
30 | })
31 | }
32 |
33 | func middlewareGrift(a *App) {
34 | grift.Desc("middleware", "Prints out your middleware stack")
35 | grift.Add("middleware", func(c *grift.Context) error {
36 | printMiddleware(a)
37 | return nil
38 | })
39 | }
40 |
41 | func printMiddleware(a *App) {
42 | printMiddlewareByRoute(a)
43 | }
44 |
45 | func printMiddlewareByRoute(a *App) {
46 | mws := map[string]string{}
47 | // TODO: middleware is 'per App' can it be a loop for Apps?
48 | for _, r := range a.Routes() {
49 | key := r.App.host + r.App.name
50 | if mws[key] == "" {
51 | pname := r.App.host
52 | if parent := getParentApp(r.App.root, r.App.Name); parent != nil {
53 | pname += parent.Name
54 | }
55 |
56 | mws[key] = r.App.Middleware.String()
57 | if pname == key || mws[pname] != mws[key] {
58 | fmt.Printf("-> %s\n", key)
59 | printMiddlewareStackWithIndent(mws[key])
60 | } else {
61 | fmt.Printf("-> %s (see: %v)\n", key, pname)
62 | }
63 | }
64 | s := "\n" + mws[key]
65 | for k := range r.App.Middleware.skips {
66 | mw := strings.Split(k, funcKeyDelimeter)[0]
67 | h := strings.Split(k, funcKeyDelimeter)[1]
68 | if h == r.HandlerName {
69 | s = strings.Replace(s, "\n"+mw, "", 1)
70 | }
71 | }
72 | if "\n"+mws[key] != s {
73 | ahn := strings.Split(r.HandlerName, "/")
74 | hn := ahn[len(ahn)-1]
75 | fmt.Printf("-> %s %s (by %s)\n", r.Method, r.App.host+r.Path, hn)
76 | printMiddlewareStackWithIndent(s)
77 | }
78 | }
79 | }
80 |
81 | func getParentApp(r *App, name string) *App {
82 | if r == nil {
83 | return nil
84 | }
85 | for _, x := range r.children {
86 | if x.Name == name {
87 | return r
88 | }
89 | if len(x.children) > 0 {
90 | if ret := getParentApp(x, name); ret != nil {
91 | return ret
92 | }
93 | }
94 | }
95 | return nil
96 | }
97 |
98 | func printMiddlewareStackWithIndent(s string) {
99 | if s == "" {
100 | s = "[none]"
101 | }
102 | s = strings.Replace(s, "\n", "\n\t", -1)
103 | fmt.Printf("\t%v\n", strings.TrimSpace(s))
104 | }
105 |
106 | func routesGrift(a *App) {
107 | grift.Desc("routes", "Print out all defined routes")
108 | grift.Add("routes", func(c *grift.Context) error {
109 | routes := a.Routes()
110 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug)
111 | fmt.Fprintln(w, "METHOD\t HOST\t PATH\t ALIASES\t NAME\t HANDLER")
112 | fmt.Fprintln(w, "------\t ----\t ----\t -------\t ----\t -------")
113 | for _, r := range routes {
114 | fmt.Fprintf(w, "%s\t %s\t %s\t %s\t %s\t %s\n", r.Method, r.App.host, r.Path, strings.Join(r.Aliases, " "), r.PathName, r.HandlerName)
115 | }
116 | w.Flush()
117 | return nil
118 | })
119 | }
120 |
--------------------------------------------------------------------------------
/mail/README.md:
--------------------------------------------------------------------------------
1 | # github.com/gobuffalo/buffalo/mail
2 |
3 | This package is intended to allow easy Email sending with Buffalo, it allows you to define your custom `mail.Sender` for the provider you would like to use.
4 |
5 | ## Generator
6 |
7 | ```bash
8 | buffalo generate mailer welcome_email
9 | ```
10 |
11 | ## Example Usage
12 |
13 | ```go
14 | //actions/mail.go
15 | package x
16 |
17 | import (
18 | "log"
19 | "net/http"
20 |
21 | "github.com/gobuffalo/buffalo/render"
22 | "github.com/gobuffalo/envy"
23 | "github.com/gobuffalo/plush"
24 | "github.com/gobuffalo/buffalo/mail"
25 | "errors"
26 | "gitlab.com/wawandco/app/models"
27 | )
28 |
29 | var smtp mail.Sender
30 | var r *render.Engine
31 |
32 | func init() {
33 |
34 | //Pulling config from the env.
35 | port := envy.Get("SMTP_PORT", "1025")
36 | host := envy.Get("SMTP_HOST", "localhost")
37 | user := envy.Get("SMTP_USER", "")
38 | password := envy.Get("SMTP_PASSWORD", "")
39 |
40 | var err error
41 | smtp, err = mail.NewSMTPSender(host, port, user, password)
42 |
43 | if err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | //The rendering engine, this is usually generated inside actions/render.go in your buffalo app.
48 | r = render.New(render.Options{
49 | TemplatesFS: mailTemplates,
50 | })
51 | }
52 |
53 | //SendContactMessage Sends contact message to contact@myapp.com
54 | func SendContactMessage(c *models.Contact) error {
55 |
56 | //Creates a new message
57 | m := mail.NewMessage()
58 | m.From = "sender@myapp.com"
59 | m.Subject = "New Contact"
60 | m.To = []string{"contact@myapp.com"}
61 |
62 | // Data that will be used inside the templates when rendering.
63 | data := map[string]interface{}{
64 | "contact": c,
65 | }
66 |
67 | // You can add multiple bodies to the message you're creating to have content-types alternatives.
68 | err := m.AddBodies(data, r.HTML("mail/contact.html"), r.Plain("mail/contact.txt"))
69 |
70 | if err != nil {
71 | return err
72 | }
73 |
74 | err = smtp.Send(m)
75 | if err != nil {
76 | return err
77 | }
78 |
79 | return nil
80 | }
81 |
82 | ```
83 |
84 | This `SendContactMessage` could be called by one of your actions, p.e. the action that handles your contact form submission.
85 |
86 | ```go
87 | //actions/contact.go
88 | ...
89 |
90 | func ContactFormHandler(c buffalo.Context) error {
91 | contact := &models.Contact{}
92 | c.Bind(contact)
93 |
94 | //Calling to send the message
95 | SendContactMessage(contact)
96 | return c.Redirect(http.StatusFound, "contact/thanks")
97 | }
98 | ...
99 | ```
100 |
101 | If you're using Gmail or need to configure your SMTP connection you can use the Dialer property on the SMTPSender, p.e: (for Gmail)
102 |
103 | ```go
104 | ...
105 | var smtp mail.Sender
106 |
107 | func init() {
108 | port := envy.Get("SMTP_PORT", "465")
109 | // or 587 with TLS
110 |
111 | host := envy.Get("SMTP_HOST", "smtp.gmail.com")
112 | user := envy.Get("SMTP_USER", "your@email.com")
113 | password := envy.Get("SMTP_PASSWORD", "yourp4ssw0rd")
114 |
115 | var err error
116 | sender, err := mail.NewSMTPSender(host, port, user, password)
117 | sender.Dialer.SSL = true
118 |
119 | //or if TLS
120 | sender.Dialer.TLSConfig = &tls.Config{...}
121 |
122 | smtp = sender
123 | }
124 | ...
125 | ```
126 |
--------------------------------------------------------------------------------
/route_info.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "html/template"
8 | "net/http"
9 | "reflect"
10 | "strings"
11 |
12 | "github.com/gobuffalo/flect"
13 |
14 | "github.com/gobuffalo/events"
15 | "github.com/gorilla/mux"
16 | )
17 |
18 | // RouteInfo provides information about the underlying route that
19 | // was built.
20 | type RouteInfo struct {
21 | Method string `json:"method"`
22 | Path string `json:"path"`
23 | HandlerName string `json:"handler"`
24 | ResourceName string `json:"resourceName,omitempty"`
25 | PathName string `json:"pathName"`
26 | Aliases []string `json:"aliases"`
27 | MuxRoute *mux.Route `json:"-"`
28 | Handler Handler `json:"-"`
29 | App *App `json:"-"`
30 | }
31 |
32 | // String returns a JSON representation of the RouteInfo
33 | func (ri RouteInfo) String() string {
34 | b, _ := json.MarshalIndent(ri, "", " ")
35 | return string(b)
36 | }
37 |
38 | // Alias path patterns to the this route. This is not the
39 | // same as a redirect.
40 | func (ri *RouteInfo) Alias(aliases ...string) *RouteInfo {
41 | ri.Aliases = append(ri.Aliases, aliases...)
42 | for _, a := range aliases {
43 | ri.App.router.Handle(a, ri).Methods(ri.Method)
44 | }
45 | return ri
46 | }
47 |
48 | // Name allows users to set custom names for the routes.
49 | func (ri *RouteInfo) Name(name string) *RouteInfo {
50 | routeIndex := -1
51 | for index, route := range ri.App.Routes() {
52 | if route.App.host == ri.App.host && route.Path == ri.Path && route.Method == ri.Method {
53 | routeIndex = index
54 | break
55 | }
56 | }
57 |
58 | name = flect.Camelize(name)
59 |
60 | if !strings.HasSuffix(name, "Path") {
61 | name = name + "Path"
62 | }
63 |
64 | ri.PathName = name
65 | if routeIndex != -1 {
66 | ri.App.Routes()[routeIndex] = reflect.ValueOf(ri).Interface().(*RouteInfo)
67 | }
68 |
69 | return ri
70 | }
71 |
72 | // BuildPathHelper Builds a routeHelperfunc for a particular RouteInfo
73 | func (ri *RouteInfo) BuildPathHelper() RouteHelperFunc {
74 | cRoute := ri
75 | return func(opts map[string]interface{}) (template.HTML, error) {
76 | pairs := []string{}
77 | for k, v := range opts {
78 | pairs = append(pairs, k)
79 | pairs = append(pairs, fmt.Sprintf("%v", v))
80 | }
81 |
82 | url, err := cRoute.MuxRoute.URL(pairs...)
83 | if err != nil {
84 | return "", fmt.Errorf("missing parameters for %v: %s", cRoute.Path, err)
85 | }
86 |
87 | result := url.String()
88 | result = addExtraParamsTo(result, opts)
89 |
90 | return template.HTML(result), nil
91 | }
92 | }
93 |
94 | func (ri RouteInfo) ServeHTTP(res http.ResponseWriter, req *http.Request) {
95 | a := ri.App
96 |
97 | c := a.newContext(ri, res, req)
98 | payload := events.Payload{
99 | "route": ri,
100 | "app": a,
101 | "context": c,
102 | }
103 |
104 | events.EmitPayload(EvtRouteStarted, payload)
105 | err := a.Middleware.handler(ri)(c)
106 |
107 | if err != nil {
108 | status := http.StatusInternalServerError
109 | var he HTTPError
110 | if errors.As(err, &he) {
111 | status = he.Status
112 | }
113 | events.EmitError(EvtRouteErr, err, payload)
114 | // things have really hit the fan if we're here!!
115 | a.Logger.Error(err)
116 | c.Response().WriteHeader(status)
117 | c.Response().Write([]byte(err.Error()))
118 | }
119 |
120 | events.EmitPayload(EvtRouteFinished, payload)
121 | }
122 |
--------------------------------------------------------------------------------
/mail/internal/mail/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## *Unreleased*
6 |
7 | ## [2.3.0] - 2018-11-10
8 |
9 | ### Added
10 |
11 | - #12: Adds `SendError` to provide additional info about the cause and index of
12 | a failed attempt to transmit a batch of messages.
13 | - go-gomail#78: Adds new `Message` methods for attaching and embedding
14 | `io.Reader`s: `AttachReader` and `EmbedReader`.
15 | - #39: Adds support for Go modules (Go 1.11+).
16 |
17 | ### Fixed
18 |
19 | - #26: Fixes RFC 1341 compliance by properly capitalizing the
20 | `MIME-Version` header.
21 | - #30: Fixes IO errors being silently dropped in `Message.WriteTo`.
22 |
23 | ## [2.2.0] - 2018-03-01
24 |
25 | ### Added
26 |
27 | - #20: Adds `Message.SetBoundary` to allow specifying a custom MIME boundary.
28 | - #22: Adds `Message.SetBodyWriter` to make it easy to use text/template and
29 | html/template for message bodies. Contributed by Quantcast.
30 | - #25: Adds `Dialer.StartTLSPolicy` so that `MandatoryStartTLS` can be required,
31 | or `NoStartTLS` can disable it. Contributed by Quantcast.
32 |
33 | ## [2.1.0] - 2017-12-14
34 |
35 | ### Added
36 |
37 | - go-gomail#40: Adds `Dialer.LocalName` field to allow specifying the hostname
38 | sent with SMTP's HELO command.
39 | - go-gomail#47: `Message.SetBody`, `Message.AddAlternative`, and
40 | `Message.AddAlternativeWriter` allow specifying the encoding of message parts.
41 | - `Dialer.Dial`'s returned `SendCloser` automatically redials after a timeout.
42 | - go-gomail#55, go-gomail#56: Adds `Rename` to allow specifying filename
43 | of an attachment.
44 | - go-gomail#100: Exports `NetDialTimeout` to allow setting a custom dialer.
45 | - go-gomail#70: Adds `Dialer.Timeout` field to allow specifying a timeout for
46 | dials, reads, and writes.
47 |
48 | ### Changed
49 |
50 | - go-gomail#52: `Dialer.Dial` automatically uses CRAM-MD5 when available.
51 | - `Dialer.Dial` specifies a default timeout of 10 seconds.
52 | - Gomail is forked from to
53 | .
54 |
55 | ### Deprecated
56 |
57 | - go-gomail#52: `NewPlainDialer` is deprecated in favor of `NewDialer`.
58 |
59 | ### Fixed
60 |
61 | - go-gomail#41, go-gomail#42: Fixes a panic when a `Message` contains a
62 | nil header.
63 | - go-gomail#44: Fixes `AddAlternativeWriter` replacing the message body instead
64 | of adding a body part.
65 | - go-gomail#53: Folds long header lines for RFC 2047 compliance.
66 | - go-gomail#54: Fixes `Message.FormatAddress` when name is blank.
67 |
68 | ## [2.0.0] - 2015-09-02
69 |
70 | - Mailer has been removed. It has been replaced by Dialer and Sender.
71 | - `File` type and the `CreateFile` and `OpenFile` functions have been removed.
72 | - `Message.Attach` and `Message.Embed` have a new signature.
73 | - `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
74 | instead.
75 | - `Message.Export` has been removed. `Message.WriteTo` can be used instead.
76 | - `Message.DelHeader` has been removed.
77 | - The `Bcc` header field is no longer sent. It is far more simpler and
78 | efficient: the same message is sent to all recipients instead of sending a
79 | different email to each Bcc address.
80 | - LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN
81 | authentication mechanism when needed.
82 | - Go 1.2 is now required instead of Go 1.3. No external dependency are used when
83 | using Go 1.5.
84 |
--------------------------------------------------------------------------------
/internal/fakesmtp/server.go:
--------------------------------------------------------------------------------
1 | package fakesmtp
2 |
3 | // This server is inspired by https://github.com/andrewarrow/jungle_smtp
4 | // and most of its functionality have been taken from the original repo and updated to
5 | // work better for buffalo.
6 |
7 | import (
8 | "bufio"
9 | "net"
10 | "strings"
11 | "sync"
12 | "time"
13 | )
14 |
15 | //Server is our fake server that will be listening for SMTP connections.
16 | type Server struct {
17 | Listener net.Listener
18 | messages []string
19 | mutex sync.Mutex
20 | }
21 |
22 | //Start listens for connections on the given port
23 | func (s *Server) Start(port string) error {
24 | for {
25 | conn, err := s.Listener.Accept()
26 | if err != nil {
27 | return err
28 | }
29 |
30 | s.Handle(&Connection{
31 | conn: conn,
32 | address: conn.RemoteAddr().String(),
33 | time: time.Now().Unix(),
34 | bufin: bufio.NewReader(conn),
35 | bufout: bufio.NewWriter(conn),
36 | })
37 | }
38 | }
39 |
40 | //Handle a connection from a client
41 | func (s *Server) Handle(c *Connection) {
42 | s.mutex.Lock()
43 | defer s.mutex.Unlock()
44 |
45 | s.messages = append(s.messages, "")
46 |
47 | s.readHello(c)
48 | s.readSender(c)
49 | s.readRecipients(c)
50 | s.readData(c)
51 |
52 | c.conn.Close()
53 | }
54 |
55 | //Requests and notifies readed the Hello
56 | func (s *Server) readHello(c *Connection) {
57 | c.write("220 Welcome")
58 | text := c.read()
59 | s.addMessageLine(text)
60 |
61 | c.write("250 Received")
62 | }
63 |
64 | //readSender reads the Sender from the connection
65 | func (s *Server) readSender(c *Connection) {
66 | text := c.read()
67 | s.addMessageLine(text)
68 | c.write("250 Sender")
69 | }
70 |
71 | //readRecipients reads recipients from the connection
72 | func (s *Server) readRecipients(c *Connection) {
73 | text := c.read()
74 | s.addMessageLine(text)
75 |
76 | c.write("250 Recipient")
77 | text = c.read()
78 | for strings.Contains(text, "RCPT") {
79 | s.addMessageLine(text)
80 | c.write("250 Recipient")
81 | text = c.read()
82 | }
83 | }
84 |
85 | //readData reads the message data.
86 | func (s *Server) readData(c *Connection) {
87 | c.write("354 Ok Send data ending with .")
88 |
89 | for {
90 | text := c.read()
91 | bytes := []byte(text)
92 | s.addMessageLine(text)
93 | // 46 13 10
94 | if bytes[0] == 46 && bytes[1] == 13 && bytes[2] == 10 {
95 | break
96 | }
97 | }
98 | c.write("250 server has transmitted the message")
99 | }
100 |
101 | //addMessageLine ads a line to the last message
102 | func (s *Server) addMessageLine(text string) {
103 | s.messages[len(s.Messages())-1] = s.LastMessage() + text
104 | }
105 |
106 | //LastMessage returns the last message on the server
107 | func (s *Server) LastMessage() string {
108 | if len(s.Messages()) == 0 {
109 | return ""
110 | }
111 |
112 | return s.Messages()[len(s.Messages())-1]
113 | }
114 |
115 | //Messages returns the list of messages on the server
116 | func (s *Server) Messages() []string {
117 | return s.messages
118 | }
119 |
120 | //Clear the server messages
121 | func (s *Server) Clear() {
122 | s.mutex.Lock()
123 | defer s.mutex.Unlock()
124 |
125 | s.messages = []string{}
126 | }
127 |
128 | //New returns a pointer to a new Server instance listening on the given port.
129 | func New(port string) (*Server, error) {
130 | s := &Server{messages: []string{}}
131 |
132 | listener, err := net.Listen("tcp", "0.0.0.0:"+port)
133 | if err != nil {
134 | return s, err
135 | }
136 | s.Listener = listener
137 | return s, nil
138 | }
139 |
--------------------------------------------------------------------------------
/mail/internal/mail/README.md:
--------------------------------------------------------------------------------
1 | # Gomail
2 | [](https://travis-ci.org/go-mail/mail) [](http://gocover.io/github.com/go-mail/mail) [](https://godoc.org/github.com/go-mail/mail)
3 |
4 | This is an actively maintained fork of [Gomail][1] and includes fixes and
5 | improvements for a number of outstanding issues. The current progress is
6 | as follows:
7 |
8 | - [x] Timeouts and retries can be specified outside of the 10 second default.
9 | - [x] Proxying is supported through specifying a custom [NetDialTimeout][2].
10 | - [ ] Filenames are properly encoded for non-ASCII characters.
11 | - [ ] Email addresses are properly encoded for non-ASCII characters.
12 | - [ ] Embedded files and attachments are tested for their existence.
13 | - [ ] An `io.Reader` can be supplied when embedding and attaching files.
14 |
15 | See [Transitioning Existing Codebases][3] for more information on switching.
16 |
17 | [1]: https://github.com/go-gomail/gomail
18 | [2]: https://godoc.org/gopkg.in/mail.v2#NetDialTimeout
19 | [3]: #transitioning-existing-codebases
20 |
21 | ## Introduction
22 |
23 | Gomail is a simple and efficient package to send emails. It is well tested and
24 | documented.
25 |
26 | Gomail can only send emails using an SMTP server. But the API is flexible and it
27 | is easy to implement other methods for sending emails using a local Postfix, an
28 | API, etc.
29 |
30 | It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used.
31 |
32 |
33 | ## Features
34 |
35 | Gomail supports:
36 | - Attachments
37 | - Embedded images
38 | - HTML and text templates
39 | - Automatic encoding of special characters
40 | - SSL and TLS
41 | - Sending multiple emails with the same SMTP connection
42 |
43 |
44 | ## Documentation
45 |
46 | https://godoc.org/github.com/go-mail/mail
47 |
48 |
49 | ## Download
50 |
51 | If you're already using a dependency manager, like [dep][dep], use the following
52 | import path:
53 |
54 | ```
55 | github.com/go-mail/mail
56 | ```
57 |
58 | If you *aren't* using vendoring, `go get` the [Gopkg.in](http://gopkg.in)
59 | import path:
60 |
61 | ```
62 | gopkg.in/mail.v2
63 | ```
64 |
65 | [dep]: https://github.com/golang/dep#readme
66 |
67 | ## Examples
68 |
69 | See the [examples in the documentation](https://godoc.org/github.com/go-mail/mail#example-package).
70 |
71 |
72 | ## FAQ
73 |
74 | ### x509: certificate signed by unknown authority
75 |
76 | If you get this error it means the certificate used by the SMTP server is not
77 | considered valid by the client running Gomail. As a quick workaround you can
78 | bypass the verification of the server's certificate chain and host name by using
79 | `SetTLSConfig`:
80 |
81 | ```go
82 | package main
83 |
84 | import (
85 | "crypto/tls"
86 |
87 | "gopkg.in/mail.v2"
88 | )
89 |
90 | func main() {
91 | d := mail.NewDialer("smtp.example.com", 587, "user", "123456")
92 | d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
93 |
94 | // Send emails using d.
95 | }
96 | ```
97 |
98 | Note, however, that this is insecure and should not be used in production.
99 |
100 | ### Transitioning Existing Codebases
101 |
102 | If you're already using the original Gomail, switching is as easy as updating
103 | the import line to:
104 |
105 | ```
106 | import gomail "gopkg.in/mail.v2"
107 | ```
108 |
109 | ## Contribute
110 |
111 | Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
112 | more info.
113 |
114 |
115 | ## Change log
116 |
117 | See [CHANGELOG.md](CHANGELOG.md).
118 |
119 |
120 | ## License
121 |
122 | [MIT](LICENSE)
123 |
124 |
125 | ## Support & Contact
126 |
127 | You can ask questions on the [Gomail
128 | thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
129 | in the Go mailing-list.
130 |
--------------------------------------------------------------------------------
/flash_test.go:
--------------------------------------------------------------------------------
1 | package buffalo
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 | "text/template"
7 |
8 | "github.com/gobuffalo/buffalo/render"
9 | "github.com/gobuffalo/httptest"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func Test_FlashAdd(t *testing.T) {
14 | r := require.New(t)
15 | f := newFlash(&Session{})
16 |
17 | r.Equal(f.data, map[string][]string{})
18 |
19 | f.Add("error", "something")
20 | r.Equal(f.data, map[string][]string{
21 | "error": {"something"},
22 | })
23 |
24 | f.Add("error", "other")
25 | r.Equal(f.data, map[string][]string{
26 | "error": {"something", "other"},
27 | })
28 | }
29 |
30 | func Test_FlashRender(t *testing.T) {
31 | r := require.New(t)
32 | a := New(Options{})
33 | rr := render.New(render.Options{})
34 |
35 | a.GET("/", func(c Context) error {
36 | c.Flash().Add("errors", "Error AJ set")
37 | c.Flash().Add("errors", "Error DAL set")
38 |
39 | return c.Render(http.StatusCreated, rr.String(errorsTPL))
40 | })
41 |
42 | w := httptest.New(a)
43 | res := w.HTML("/").Get()
44 |
45 | r.Contains(res.Body.String(), "Error AJ set")
46 | r.Contains(res.Body.String(), "Error DAL set")
47 | }
48 |
49 | func Test_FlashRenderEmpty(t *testing.T) {
50 | r := require.New(t)
51 | a := New(Options{})
52 | rr := render.New(render.Options{})
53 |
54 | a.GET("/", func(c Context) error {
55 | return c.Render(http.StatusCreated, rr.String(errorsTPL))
56 | })
57 |
58 | w := httptest.New(a)
59 |
60 | res := w.HTML("/").Get()
61 | r.NotContains(res.Body.String(), "Flash:")
62 | }
63 |
64 | const errorsTPL = `
65 | <%= for (k, v) in flash["errors"] { %>
66 | Flash:
67 | <%= k %>:<%= v %>
68 | <% } %>
69 | `
70 |
71 | func Test_FlashRenderEntireFlash(t *testing.T) {
72 | r := require.New(t)
73 | a := New(Options{})
74 | rr := render.New(render.Options{})
75 |
76 | a.GET("/", func(c Context) error {
77 | c.Flash().Add("something", "something to say!")
78 | return c.Render(http.StatusCreated, rr.String(keyTPL))
79 | })
80 |
81 | w := httptest.New(a)
82 | res := w.HTML("/").Get()
83 | r.Contains(res.Body.String(), "something to say!")
84 | }
85 |
86 | const keyTPL = `<%= for (k, v) in flash { %>
87 | Flash:
88 | <%= k %>:<%= v %>
89 | <% } %>
90 | `
91 |
92 | func Test_FlashRenderCustomKey(t *testing.T) {
93 | r := require.New(t)
94 | a := New(Options{})
95 | rr := render.New(render.Options{})
96 |
97 | a.GET("/", func(c Context) error {
98 | c.Flash().Add("something", "something to say!")
99 | return c.Render(http.StatusCreated, rr.String(keyTPL))
100 | })
101 |
102 | w := httptest.New(a)
103 | res := w.HTML("/").Get()
104 | r.Contains(res.Body.String(), "something to say!")
105 | }
106 |
107 | func Test_FlashRenderCustomKeyNotDefined(t *testing.T) {
108 | r := require.New(t)
109 | a := New(Options{})
110 | rr := render.New(render.Options{})
111 |
112 | a.GET("/", func(c Context) error {
113 | return c.Render(http.StatusCreated, rr.String(customKeyTPL))
114 | })
115 |
116 | w := httptest.New(a)
117 | res := w.HTML("/").Get()
118 | r.NotContains(res.Body.String(), "something to say!")
119 | }
120 |
121 | const customKeyTPL = `
122 | {{#each flash.other as |k value|}}
123 | {{value}}
124 | {{/each}}
125 | `
126 |
127 | func Test_FlashNotClearedOnRedirect(t *testing.T) {
128 | r := require.New(t)
129 | a := New(Options{})
130 | rr := render.New(render.Options{})
131 |
132 | a.GET("/flash", func(c Context) error {
133 | c.Flash().Add("success", "Antonio, you're welcome!")
134 | return c.Redirect(http.StatusSeeOther, "/")
135 | })
136 |
137 | a.GET("/", func(c Context) error {
138 | template := `Message: <%= flash["success"] %>`
139 | return c.Render(http.StatusCreated, rr.String(template))
140 | })
141 |
142 | w := httptest.New(a)
143 | res := w.HTML("/flash").Get()
144 | r.Equal(res.Code, http.StatusSeeOther)
145 | r.Equal(res.Location(), "/")
146 |
147 | res = w.HTML("/").Get()
148 | r.Contains(res.Body.String(), template.HTMLEscapeString("Antonio, you're welcome!"))
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/render/js_test.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/psanford/memfs"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | const jsLayout = "layout.js"
13 | const jsAltLayout = "alt_layout.plush.js"
14 | const jsTemplate = "my-template.js"
15 |
16 | func Test_JavaScript_WithoutLayout(t *testing.T) {
17 | r := require.New(t)
18 |
19 | rootFS := memfs.New()
20 | r.NoError(rootFS.WriteFile(jsTemplate, []byte("alert(<%= name %>)"), 0644))
21 |
22 | e := NewEngine()
23 | e.TemplatesFS = rootFS
24 |
25 | h := e.JavaScript(jsTemplate)
26 | r.Equal("application/javascript", h.ContentType())
27 | bb := &bytes.Buffer{}
28 |
29 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
30 | r.Equal("alert(Mark)", strings.TrimSpace(bb.String()))
31 | }
32 |
33 | func Test_JavaScript_WithLayout(t *testing.T) {
34 | r := require.New(t)
35 |
36 | rootFS := memfs.New()
37 | r.NoError(rootFS.WriteFile(jsTemplate, []byte("alert(<%= name %>)"), 0644))
38 | r.NoError(rootFS.WriteFile(jsLayout, []byte("$(<%= yield %>)"), 0644))
39 |
40 | e := NewEngine()
41 | e.TemplatesFS = rootFS
42 | e.JavaScriptLayout = jsLayout
43 |
44 | h := e.JavaScript(jsTemplate)
45 | r.Equal("application/javascript", h.ContentType())
46 | bb := &bytes.Buffer{}
47 |
48 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
49 | r.Equal("$(alert(Mark))", strings.TrimSpace(bb.String()))
50 | }
51 |
52 | func Test_JavaScript_WithLayout_Override(t *testing.T) {
53 | r := require.New(t)
54 |
55 | rootFS := memfs.New()
56 | r.NoError(rootFS.WriteFile(jsTemplate, []byte("alert(<%= name %>)"), 0644))
57 | r.NoError(rootFS.WriteFile(jsLayout, []byte("$(<%= yield %>)"), 0644))
58 | r.NoError(rootFS.WriteFile(jsAltLayout, []byte("_(<%= yield %>)"), 0644))
59 |
60 | e := NewEngine()
61 | e.TemplatesFS = rootFS
62 | e.JavaScriptLayout = jsLayout
63 |
64 | h := e.JavaScript(jsTemplate, jsAltLayout)
65 | r.Equal("application/javascript", h.ContentType())
66 | bb := &bytes.Buffer{}
67 |
68 | r.NoError(h.Render(bb, Data{"name": "Mark"}))
69 | r.Equal("_(alert(Mark))", strings.TrimSpace(bb.String()))
70 | }
71 |
72 | func Test_JavaScript_Partial_Without_Extension(t *testing.T) {
73 | const tmpl = "let a = 1;\n<%= partial(\"part\") %>"
74 | const part = "alert('Hi <%= name %>!');"
75 |
76 | r := require.New(t)
77 |
78 | rootFS := memfs.New()
79 | r.NoError(rootFS.WriteFile(jsTemplate, []byte(tmpl), 0644))
80 | r.NoError(rootFS.WriteFile("_part.js", []byte(part), 0644))
81 |
82 | e := NewEngine()
83 | e.TemplatesFS = rootFS
84 | h := e.JavaScript(jsTemplate)
85 | r.Equal("application/javascript", h.ContentType())
86 | bb := &bytes.Buffer{}
87 |
88 | r.NoError(h.Render(bb, Data{"name": "Yonghwan"}))
89 | r.Equal("let a = 1;\nalert('Hi Yonghwan!');", bb.String())
90 | }
91 |
92 | func Test_JavaScript_Partial(t *testing.T) {
93 | const tmpl = "let a = 1;\n<%= partial(\"part.js\") %>"
94 | const part = "alert('Hi <%= name %>!');"
95 |
96 | r := require.New(t)
97 |
98 | rootFS := memfs.New()
99 | r.NoError(rootFS.WriteFile(jsTemplate, []byte(tmpl), 0644))
100 | r.NoError(rootFS.WriteFile("_part.js", []byte(part), 0644))
101 |
102 | e := NewEngine()
103 | e.TemplatesFS = rootFS
104 |
105 | h := e.JavaScript(jsTemplate)
106 | r.Equal("application/javascript", h.ContentType())
107 | bb := &bytes.Buffer{}
108 |
109 | r.NoError(h.Render(bb, Data{"name": "Yonghwan"}))
110 | r.Equal("let a = 1;\nalert('Hi Yonghwan!');", bb.String())
111 | }
112 |
113 | func Test_JavaScript_HTML_Partial(t *testing.T) {
114 | const tmpl = "let a = \"<%= partial(\"part.html\") %>\""
115 | const part = ``
118 |
119 | r := require.New(t)
120 |
121 | rootFS := memfs.New()
122 | r.NoError(rootFS.WriteFile(jsTemplate, []byte(tmpl), 0644))
123 | r.NoError(rootFS.WriteFile("_part.html", []byte(part), 0644))
124 |
125 | e := NewEngine()
126 | e.TemplatesFS = rootFS
127 |
128 | h := e.JavaScript(jsTemplate)
129 | r.Equal("application/javascript", h.ContentType())
130 | bb := &bytes.Buffer{}
131 |
132 | r.NoError(h.Render(bb, Data{}))
133 | r.Contains(bb.String(), `id`)
134 | r.Contains(bb.String(), `foo`)
135 |
136 | // To check it has escaped the partial
137 | r.NotContains(bb.String(), ``)
138 | }
139 |
--------------------------------------------------------------------------------
/binding/file_test.go:
--------------------------------------------------------------------------------
1 | package binding_test
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "mime/multipart"
8 | "net/http"
9 | "net/http/httptest"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | "testing"
14 |
15 | "github.com/gobuffalo/buffalo"
16 | "github.com/gobuffalo/buffalo/binding"
17 | "github.com/gobuffalo/buffalo/render"
18 | "github.com/stretchr/testify/require"
19 | )
20 |
21 | type WithFile struct {
22 | MyFile binding.File
23 | }
24 |
25 | type NamedFileSlice struct {
26 | MyFiles []binding.File `form:"thefiles"`
27 | }
28 |
29 | type NamedFile struct {
30 | MyFile binding.File `form:"afile"`
31 | }
32 |
33 | func App() *buffalo.App {
34 | a := buffalo.New(buffalo.Options{})
35 | a.POST("/on-struct", func(c buffalo.Context) error {
36 | wf := &WithFile{}
37 | if err := c.Bind(wf); err != nil {
38 | return err
39 | }
40 | return c.Render(http.StatusCreated, render.String(wf.MyFile.Filename))
41 | })
42 | a.POST("/named-file", func(c buffalo.Context) error {
43 | wf := &NamedFile{}
44 | if err := c.Bind(wf); err != nil {
45 | return err
46 | }
47 | return c.Render(http.StatusCreated, render.String(wf.MyFile.Filename))
48 | })
49 | a.POST("/named-file-slice", func(c buffalo.Context) error {
50 | wmf := &NamedFileSlice{}
51 | if err := c.Bind(wmf); err != nil {
52 | return err
53 | }
54 | result := make([]string, len(wmf.MyFiles))
55 | for i, f := range wmf.MyFiles {
56 | result[i] += fmt.Sprintf("%s", f.Filename)
57 |
58 | }
59 | return c.Render(http.StatusCreated, render.String(strings.Join(result, ",")))
60 | })
61 | a.POST("/on-context", func(c buffalo.Context) error {
62 | f, err := c.File("MyFile")
63 | if err != nil {
64 | return err
65 | }
66 | return c.Render(http.StatusCreated, render.String(f.Filename))
67 | })
68 |
69 | return a
70 | }
71 |
72 | func Test_File_Upload_On_Struct(t *testing.T) {
73 | r := require.New(t)
74 |
75 | req, err := newFileUploadRequest("/on-struct", "MyFile", "file_test.go")
76 | r.NoError(err)
77 | res := httptest.NewRecorder()
78 |
79 | App().ServeHTTP(res, req)
80 |
81 | r.Equal(http.StatusCreated, res.Code)
82 | r.Equal("file_test.go", res.Body.String())
83 | }
84 |
85 | func Test_File_Upload_On_Struct_WithTag_WithMultipleFiles(t *testing.T) {
86 | r := require.New(t)
87 |
88 | req, err := newFileUploadRequest("/named-file-slice", "thefiles", "file_test.go", "file.go", "types.go")
89 | r.NoError(err)
90 | res := httptest.NewRecorder()
91 |
92 | App().ServeHTTP(res, req)
93 |
94 | r.Equal(http.StatusCreated, res.Code)
95 | r.Equal("file_test.go,file.go,types.go", res.Body.String())
96 | }
97 |
98 | func Test_File_Upload_On_Struct_WithTag(t *testing.T) {
99 | r := require.New(t)
100 |
101 | req, err := newFileUploadRequest("/named-file", "afile", "file_test.go")
102 | r.NoError(err)
103 | res := httptest.NewRecorder()
104 |
105 | App().ServeHTTP(res, req)
106 |
107 | r.Equal(http.StatusCreated, res.Code)
108 | r.Equal("file_test.go", res.Body.String())
109 | }
110 |
111 | func Test_File_Upload_On_Context(t *testing.T) {
112 | r := require.New(t)
113 |
114 | req, err := newFileUploadRequest("/on-context", "MyFile", "file_test.go")
115 | r.NoError(err)
116 | res := httptest.NewRecorder()
117 |
118 | App().ServeHTTP(res, req)
119 |
120 | r.Equal(http.StatusCreated, res.Code)
121 | r.Equal("file_test.go", res.Body.String())
122 | }
123 |
124 | // this helper method was inspired by this blog post by Matt Aimonetti:
125 | // https://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/
126 | func newFileUploadRequest(uri string, paramName string, paths ...string) (*http.Request, error) {
127 | body := &bytes.Buffer{}
128 | writer := multipart.NewWriter(body)
129 |
130 | for _, path := range paths {
131 | file, err := os.Open(path)
132 | if err != nil {
133 | return nil, err
134 | }
135 | defer file.Close()
136 | part, err := writer.CreateFormFile(paramName, filepath.Base(path))
137 | if err != nil {
138 | return nil, err
139 | }
140 | if _, err = io.Copy(part, file); err != nil {
141 | return nil, err
142 | }
143 | }
144 |
145 | if err := writer.Close(); err != nil {
146 | return nil, err
147 | }
148 | req, err := http.NewRequest("POST", uri, body)
149 | req.Header.Set("Content-Type", writer.FormDataContentType())
150 | return req, err
151 | }
152 |
--------------------------------------------------------------------------------
/plugins/plugcmds/available.go:
--------------------------------------------------------------------------------
1 | package plugcmds
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "github.com/gobuffalo/buffalo/plugins"
10 | "github.com/gobuffalo/events"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | // NewAvailable returns a fully formed Available type
15 | func NewAvailable() *Available {
16 | return &Available{
17 | plugs: plugMap{},
18 | }
19 | }
20 |
21 | // Available used to manage all of the available commands
22 | // for the plugin
23 | type Available struct {
24 | plugs plugMap
25 | }
26 |
27 | type plug struct {
28 | BuffaloCommand string
29 | Cmd *cobra.Command
30 | Plugin plugins.Command
31 | }
32 |
33 | func (p plug) String() string {
34 | b, _ := json.Marshal(p.Plugin)
35 | return string(b)
36 | }
37 |
38 | // Cmd returns the "available" command
39 | func (a *Available) Cmd() *cobra.Command {
40 | return &cobra.Command{
41 | Use: "available",
42 | Short: "a list of available buffalo plugins",
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | return a.Encode(os.Stdout)
45 | },
46 | }
47 | }
48 |
49 | // Commands returns all of the commands that are available
50 | func (a *Available) Commands() []*cobra.Command {
51 | cmds := []*cobra.Command{a.Cmd()}
52 | a.plugs.Range(func(_ string, p plug) bool {
53 | cmds = append(cmds, p.Cmd)
54 | return true
55 | })
56 | return cmds
57 | }
58 |
59 | // Add a new command to this list of available ones.
60 | // The bufCmd should corresponding buffalo command that
61 | // command should live below.
62 | //
63 | // Special "commands":
64 | // "root" - is the `buffalo` command
65 | // "events" - listens for emitted events
66 | func (a *Available) Add(bufCmd string, cmd *cobra.Command) error {
67 | if len(cmd.Aliases) == 0 {
68 | cmd.Aliases = []string{}
69 | }
70 | p := plug{
71 | BuffaloCommand: bufCmd,
72 | Cmd: cmd,
73 | Plugin: plugins.Command{
74 | Name: cmd.Use,
75 | BuffaloCommand: bufCmd,
76 | Description: cmd.Short,
77 | Aliases: cmd.Aliases,
78 | UseCommand: cmd.Use,
79 | },
80 | }
81 | a.plugs.Store(p.String(), p)
82 | return nil
83 | }
84 |
85 | // Mount all of the commands that are available
86 | // on to the other command. This is the recommended
87 | // approach for using Available.
88 | // a.Mount(rootCmd)
89 | func (a *Available) Mount(cmd *cobra.Command) {
90 | // mount all the cmds on to the cobra command
91 | cmd.AddCommand(a.Cmd())
92 | a.plugs.Range(func(_ string, p plug) bool {
93 | cmd.AddCommand(p.Cmd)
94 | return true
95 | })
96 | }
97 |
98 | // Encode into the required Buffalo plugins available
99 | // format
100 | func (a *Available) Encode(w io.Writer) error {
101 | var plugs plugins.Commands
102 | a.plugs.Range(func(_ string, p plug) bool {
103 | plugs = append(plugs, p.Plugin)
104 | return true
105 | })
106 | return json.NewEncoder(w).Encode(plugs)
107 | }
108 |
109 | // Listen adds a command for github.com/gobuffalo/events.
110 | // This will listen for ALL events. Use ListenFor to
111 | // listen to a regex of events.
112 | func (a *Available) Listen(fn func(e events.Event) error) error {
113 | return a.Add("events", buildListen(fn))
114 | }
115 |
116 | // ListenFor adds a command for github.com/gobuffalo/events.
117 | // This will only listen for events that match the regex provided.
118 | func (a *Available) ListenFor(rx string, fn func(e events.Event) error) error {
119 | cmd := buildListen(fn)
120 | p := plug{
121 | BuffaloCommand: "events",
122 | Cmd: cmd,
123 | Plugin: plugins.Command{
124 | Name: cmd.Use,
125 | BuffaloCommand: "events",
126 | Description: cmd.Short,
127 | Aliases: cmd.Aliases,
128 | UseCommand: cmd.Use,
129 | ListenFor: rx,
130 | },
131 | }
132 | a.plugs.Store(p.String(), p)
133 | return nil
134 | }
135 |
136 | func buildListen(fn func(e events.Event) error) *cobra.Command {
137 | listenCmd := &cobra.Command{
138 | Use: "listen",
139 | Short: "listens to github.com/gobuffalo/events",
140 | Aliases: []string{},
141 | RunE: func(cmd *cobra.Command, args []string) error {
142 | if len(args) == 0 {
143 | return fmt.Errorf("must pass a payload")
144 | }
145 |
146 | e := events.Event{}
147 | err := json.Unmarshal([]byte(args[0]), &e)
148 | if err != nil {
149 | return err
150 | }
151 |
152 | return fn(e)
153 | },
154 | }
155 | return listenCmd
156 | }
157 |
--------------------------------------------------------------------------------