forked from jung-kurt/caddy-cgi
-
Notifications
You must be signed in to change notification settings - Fork 7
/
module.go
147 lines (134 loc) · 4.44 KB
/
module.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Copyright (c) 2020 Andreas Schneider
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package cgi
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/dustin/go-humanize"
"go.uber.org/zap"
)
func init() {
caddy.RegisterModule(CGI{})
httpcaddyfile.RegisterHandlerDirective("cgi", parseCaddyfile)
}
// CGI implements a CGI handler that executes binary files following the
// CGI protocol, passing parameters via environment variables and evaluating
// the response as the HTTP response.
type CGI struct {
// Name of executable script or binary
Executable string `json:"executable"`
// Working directory (default, current Caddy working directory)
WorkingDirectory string `json:"workingDirectory,omitempty"`
// The script path of the uri.
ScriptName string `json:"scriptName,omitempty"`
// Arguments to submit to executable
Args []string `json:"args,omitempty"`
// Environment key value pairs (key=value) for this particular app
Envs []string `json:"envs,omitempty"`
// Environment keys to pass through for all apps
PassEnvs []string `json:"passEnvs,omitempty"`
// True to pass all environment variables to CGI executable
PassAll bool `json:"passAllEnvs,omitempty"`
// True to return inspection page rather than call CGI executable
Inspect bool `json:"inspect,omitempty"`
// Size of the in memory buffer to buffer chunked transfers
// if this size is exceeded a temporary file is used
BufferLimit int64 `json:"buffer_limit,omitempty"`
// If set, output from the CGI script is immediately flushed whenever
// some bytes have been read.
UnbufferedOutput bool `json:"unbufferedOutput,omitempty"`
logger *zap.Logger
}
// Interface guards
var (
_ caddyhttp.MiddlewareHandler = (*CGI)(nil)
_ caddyfile.Unmarshaler = (*CGI)(nil)
_ caddy.Provisioner = (*CGI)(nil)
)
func (c CGI) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.cgi",
New: func() caddy.Module { return &CGI{} },
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (c *CGI) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Consume 'em all. Matchers should be used to differentiate multiple instantiations.
// If they are not used, we simply combine them first-to-last.
for d.Next() {
args := d.RemainingArgs()
if len(args) < 1 {
return d.Err("an executable needs to be specified")
}
c.Executable = args[0]
c.Args = args[1:]
for d.NextBlock(0) {
switch d.Val() {
case "dir":
if !d.Args(&c.WorkingDirectory) {
return d.ArgErr()
}
case "script_name":
if !d.Args(&c.ScriptName) {
return d.ArgErr()
}
case "env":
c.Envs = d.RemainingArgs()
if len(c.Envs) == 0 {
return d.ArgErr()
}
case "pass_env":
c.PassEnvs = d.RemainingArgs()
if len(c.PassEnvs) == 0 {
return d.ArgErr()
}
case "pass_all_env":
c.PassAll = true
case "inspect":
c.Inspect = true
case "buffer_limit":
if !d.NextArg() {
return d.ArgErr()
}
size, err := humanize.ParseBytes(d.Val())
if err != nil {
return d.Errf("invalid buffer limit '%s': %v", d.Val(), err)
}
c.BufferLimit = int64(size)
case "unbuffered_output":
c.UnbufferedOutput = true
default:
return d.Errf("unknown subdirective: %q", d.Val())
}
}
}
return nil
}
func (c *CGI) Provision(ctx caddy.Context) error {
c.logger = ctx.Logger(c)
if c.BufferLimit <= 0 {
c.BufferLimit = 4 << 20
}
return nil
}
// parseCaddyfile unmarshals tokens from h into a new Middleware.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
c := new(CGI)
err := c.UnmarshalCaddyfile(h.Dispenser)
return c, err
}