Skip to content

Commit a92ce5e

Browse files
committed
support allowing video content
video-as-image has become more common (giphy video/mp4 as "gif") add a flag to allow `video/*` content in addition to `image/*`. note that size restrictions are still applied.
1 parent 5c2962f commit a92ce5e

File tree

4 files changed

+100
-44
lines changed

4 files changed

+100
-44
lines changed

cmd/go-camo/main.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ func main() {
5151
AllowList string `long:"allow-list" description:"Text file of hostname allow regexes (one per line)"`
5252
BindAddress string `long:"listen" default:"0.0.0.0:8080" description:"Address:Port to bind to for HTTP"`
5353
BindAddressSSL string `long:"ssl-listen" description:"Address:Port to bind to for HTTPS/SSL/TLS"`
54-
MaxSize int64 `long:"max-size" default:"5120" description:"Max response image size (KB)"`
54+
MaxSize int64 `long:"max-size" default:"5120" description:"Max allowed response size (KB)"`
5555
ReqTimeout time.Duration `long:"timeout" default:"4s" description:"Upstream request timeout"`
5656
MaxRedirects int `long:"max-redirects" default:"3" description:"Maximum number of redirects to follow"`
5757
Stats bool `long:"stats" description:"Enable Stats"`
5858
NoLogTS bool `long:"no-log-ts" description:"Do not add a timestamp to logging"`
5959
DisableKeepAlivesFE bool `long:"no-fk" description:"Disable frontend http keep-alive support"`
6060
DisableKeepAlivesBE bool `long:"no-bk" description:"Disable backend http keep-alive support"`
61+
AllowContentVideo bool `long:"allow-content-video" description:"Additionally allow 'video/*' content"`
6162
Verbose bool `short:"v" long:"verbose" description:"Show verbose (debug) log level output"`
6263
}
6364

@@ -113,6 +114,9 @@ func main() {
113114
config.DisableKeepAlivesBE = opts.DisableKeepAlivesBE
114115
config.DisableKeepAlivesFE = opts.DisableKeepAlivesFE
115116

117+
// additonal content types to allow
118+
config.AllowContentVideo = opts.AllowContentVideo
119+
116120
if opts.AllowList != "" {
117121
b, err := ioutil.ReadFile(opts.AllowList)
118122
if err != nil {

pkg/camo/misc.go

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package camo
77
import (
88
"net"
99
"os"
10+
"regexp"
11+
"strings"
1012
"syscall"
1113
)
1214

@@ -68,3 +70,9 @@ func isRejectedIP(ip net.IP) bool {
6870

6971
return false
7072
}
73+
74+
func globToRegexp(globString string) (*regexp.Regexp, error) {
75+
gs := "^" + strings.Replace(globString, "*", ".*", 1) + "$"
76+
c, err := regexp.Compile(strings.TrimSpace(gs))
77+
return c, err
78+
}

pkg/camo/proxy.go

+41-15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type Config struct {
4141
// Keepalive enable/disable
4242
DisableKeepAlivesFE bool
4343
DisableKeepAlivesBE bool
44+
// additional content types to allow
45+
AllowContentVideo bool
4446
}
4547

4648
// ProxyMetrics interface for Proxy to use for stats/metrics.
@@ -55,10 +57,12 @@ type ProxyMetrics interface {
5557
// restrictions as well as regex host allow list support.
5658
type Proxy struct {
5759
// compiled allow list regex
58-
allowList []*regexp.Regexp
59-
metrics ProxyMetrics
60-
client *http.Client
61-
config *Config
60+
allowList []*regexp.Regexp
61+
acceptTypesRe []*regexp.Regexp
62+
metrics ProxyMetrics
63+
client *http.Client
64+
config *Config
65+
acceptTypesString string
6266
}
6367

6468
// ServerHTTP handles the client request, validates the request is validly
@@ -155,10 +159,8 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
155159
}
156160
}
157161

158-
// add an accept header if the client didn't send one
159-
if nreq.Header.Get("Accept") == "" {
160-
nreq.Header.Add("Accept", "image/*")
161-
}
162+
// add/squash an accept header if the client didn't send one
163+
nreq.Header.Set("Accept", p.acceptTypesString)
162164

163165
nreq.Header.Add("User-Agent", p.config.ServerName)
164166
nreq.Header.Add("Via", p.config.ServerName)
@@ -204,10 +206,16 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
204206
switch resp.StatusCode {
205207
case 200:
206208
// check content type
207-
if !strings.HasPrefix(resp.Header.Get("Content-Type"), "image/") {
208-
mlog.Debugm("Non-Image content-type returned", mlog.Map{"type": u})
209-
http.Error(w, "Non-Image content-type returned",
210-
http.StatusBadRequest)
209+
match := false
210+
for _, re := range p.acceptTypesRe {
211+
if re.MatchString(resp.Header.Get("Content-Type")) {
212+
match = true
213+
break
214+
}
215+
}
216+
if !match {
217+
mlog.Debugm("Unsupported content-type returned", mlog.Map{"type": u})
218+
http.Error(w, "Unsupported content-type returned", http.StatusBadRequest)
211219
return
212220
}
213221
case 300:
@@ -341,8 +349,26 @@ func New(pc Config) (*Proxy, error) {
341349
allow = append(allow, c)
342350
}
343351

352+
acceptTypes := []string{"image/*"}
353+
// add additional accept types
354+
if pc.AllowContentVideo {
355+
acceptTypes = append(acceptTypes, "video/*")
356+
}
357+
358+
var acceptTypesRe []*regexp.Regexp
359+
for _, v := range acceptTypes {
360+
c, err = globToRegexp(v)
361+
if err != nil {
362+
return nil, err
363+
}
364+
acceptTypesRe = append(acceptTypesRe, c)
365+
}
366+
344367
return &Proxy{
345-
client: client,
346-
config: &pc,
347-
allowList: allow}, nil
368+
client: client,
369+
config: &pc,
370+
allowList: allow,
371+
acceptTypesString: strings.Join(acceptTypes, ", "),
372+
acceptTypesRe: acceptTypesRe}, nil
373+
348374
}

pkg/camo/proxy_test.go

+46-28
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import (
1818
)
1919

2020
var camoConfig = Config{
21-
HMACKey: []byte("0x24FEEDFACEDEADBEEFCAFE"),
22-
MaxSize: 5120 * 1024,
23-
RequestTimeout: time.Duration(10) * time.Second,
24-
MaxRedirects: 3,
25-
ServerName: "go-camo",
21+
HMACKey: []byte("0x24FEEDFACEDEADBEEFCAFE"),
22+
MaxSize: 5120 * 1024,
23+
RequestTimeout: time.Duration(10) * time.Second,
24+
MaxRedirects: 3,
25+
ServerName: "go-camo",
26+
AllowContentVideo: false,
2627
}
2728

2829
func makeReq(testURL string) (*http.Request, error) {
@@ -56,14 +57,14 @@ func processRequest(req *http.Request, status int, camoConfig Config) (*httptest
5657
return record, nil
5758
}
5859

59-
func makeTestReq(testURL string, status int) (*httptest.ResponseRecorder, error) {
60+
func makeTestReq(testURL string, status int, config Config) (*httptest.ResponseRecorder, error) {
6061
req, err := makeReq(testURL)
6162
if err != nil {
6263
return nil, err
6364
}
64-
record, err := processRequest(req, status, camoConfig)
65+
record, err := processRequest(req, status, config)
6566
if err != nil {
66-
return nil, err
67+
return record, err
6768
}
6869
return record, nil
6970
}
@@ -86,7 +87,7 @@ func TestNotFound(t *testing.T) {
8687
func TestSimpleValidImageURL(t *testing.T) {
8788
t.Parallel()
8889
testURL := "http://www.google.com/images/srpr/logo11w.png"
89-
record, err := makeTestReq(testURL, 200)
90+
record, err := makeTestReq(testURL, 200, camoConfig)
9091
if assert.Nil(t, err) {
9192
// validate headers
9293
assert.Equal(t, "test", record.HeaderMap.Get("X-Go-Camo"), "Expected custom response header not found")
@@ -97,128 +98,145 @@ func TestSimpleValidImageURL(t *testing.T) {
9798
func TestGoogleChartURL(t *testing.T) {
9899
t.Parallel()
99100
testURL := "http://chart.apis.google.com/chart?chs=920x200&chxl=0:%7C2010-08-13%7C2010-09-12%7C2010-10-12%7C2010-11-11%7C1:%7C0%7C0%7C0%7C0%7C0%7C0&chm=B,EBF5FB,0,0,0&chco=008Cd6&chls=3,1,0&chg=8.3,20,1,4&chd=s:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&chxt=x,y&cht=lc"
100-
_, err := makeTestReq(testURL, 200)
101+
_, err := makeTestReq(testURL, 200, camoConfig)
101102
assert.Nil(t, err)
102103
}
103104

104105
func TestChunkedImageFile(t *testing.T) {
105106
t.Parallel()
106107
testURL := "https://www.igvita.com/posts/12/spdyproxy-diagram.png"
107-
_, err := makeTestReq(testURL, 200)
108+
_, err := makeTestReq(testURL, 200, camoConfig)
108109
assert.Nil(t, err)
109110
}
110111

111112
func TestFollowRedirects(t *testing.T) {
112113
t.Parallel()
113114
testURL := "http://cl.ly/1K0X2Y2F1P0o3z140p0d/boom-headshot.gif"
114-
_, err := makeTestReq(testURL, 200)
115+
_, err := makeTestReq(testURL, 200, camoConfig)
115116
assert.Nil(t, err)
116117
}
117118

118119
func TestStrangeFormatRedirects(t *testing.T) {
119120
t.Parallel()
120121
testURL := "http://cl.ly/DPcp/Screen%20Shot%202012-01-17%20at%203.42.32%20PM.png"
121-
_, err := makeTestReq(testURL, 200)
122+
_, err := makeTestReq(testURL, 200, camoConfig)
122123
assert.Nil(t, err)
123124
}
124125

125126
func TestRedirectsWithPathOnly(t *testing.T) {
126127
t.Parallel()
127128
testURL := "http://httpbin.org/redirect-to?url=%2Fredirect-to%3Furl%3Dhttp%3A%2F%2Fwww.google.com%2Fimages%2Fsrpr%2Flogo11w.png"
128-
_, err := makeTestReq(testURL, 200)
129+
_, err := makeTestReq(testURL, 200, camoConfig)
129130
assert.Nil(t, err)
130131
}
131132

132133
func TestFollowTempRedirects(t *testing.T) {
133134
t.Parallel()
134135
testURL := "http://httpbin.org/redirect-to?url=http://www.google.com/images/srpr/logo11w.png"
135-
_, err := makeTestReq(testURL, 200)
136+
_, err := makeTestReq(testURL, 200, camoConfig)
136137
assert.Nil(t, err)
137138
}
138139

139140
func TestBadContentType(t *testing.T) {
140141
t.Parallel()
141142
testURL := "http://httpbin.org/response-headers?Content-Type=what"
142-
_, err := makeTestReq(testURL, 400)
143+
_, err := makeTestReq(testURL, 400, camoConfig)
144+
assert.Nil(t, err)
145+
}
146+
147+
func TestVideoContentTypeAllowed(t *testing.T) {
148+
t.Parallel()
149+
150+
camoConfigWithVideo := Config{
151+
HMACKey: []byte("0x24FEEDFACEDEADBEEFCAFE"),
152+
MaxSize: 180 * 1024,
153+
RequestTimeout: time.Duration(10) * time.Second,
154+
MaxRedirects: 3,
155+
ServerName: "go-camo",
156+
AllowContentVideo: true,
157+
}
158+
159+
testURL := "http://mirrors.standaloneinstaller.com/video-sample/small.mp4"
160+
_, err := makeTestReq(testURL, 200, camoConfigWithVideo)
143161
assert.Nil(t, err)
144162
}
145163

146164
func Test404InfiniRedirect(t *testing.T) {
147165
t.Parallel()
148166
testURL := "http://httpbin.org/redirect/4"
149-
_, err := makeTestReq(testURL, 404)
167+
_, err := makeTestReq(testURL, 404, camoConfig)
150168
assert.Nil(t, err)
151169
}
152170

153171
func Test404URLWithoutHTTPHost(t *testing.T) {
154172
t.Parallel()
155173
testURL := "/picture/Mincemeat/Pimp.jpg"
156-
_, err := makeTestReq(testURL, 404)
174+
_, err := makeTestReq(testURL, 404, camoConfig)
157175
assert.Nil(t, err)
158176
}
159177

160178
func Test404ImageLargerThan5MB(t *testing.T) {
161179
t.Parallel()
162180
testURL := "http://apod.nasa.gov/apod/image/0505/larryslookout_spirit_big.jpg"
163-
_, err := makeTestReq(testURL, 404)
181+
_, err := makeTestReq(testURL, 404, camoConfig)
164182
assert.Nil(t, err)
165183
}
166184

167185
func Test404HostNotFound(t *testing.T) {
168186
t.Parallel()
169187
testURL := "http://flabergasted.cx"
170-
_, err := makeTestReq(testURL, 404)
188+
_, err := makeTestReq(testURL, 404, camoConfig)
171189
assert.Nil(t, err)
172190
}
173191

174192
func Test404OnExcludes(t *testing.T) {
175193
t.Parallel()
176194
testURL := "http://iphone.internal.example.org/foo.cgi"
177-
_, err := makeTestReq(testURL, 404)
195+
_, err := makeTestReq(testURL, 404, camoConfig)
178196
assert.Nil(t, err)
179197
}
180198

181199
func Test404OnNonImageContent(t *testing.T) {
182200
t.Parallel()
183201
testURL := "https://github.com/atmos/cinderella/raw/master/bootstrap.sh"
184-
_, err := makeTestReq(testURL, 404)
202+
_, err := makeTestReq(testURL, 404, camoConfig)
185203
assert.Nil(t, err)
186204
}
187205

188206
func Test404On10xIpRange(t *testing.T) {
189207
t.Parallel()
190208
testURL := "http://10.0.0.1/foo.cgi"
191-
_, err := makeTestReq(testURL, 404)
209+
_, err := makeTestReq(testURL, 404, camoConfig)
192210
assert.Nil(t, err)
193211
}
194212

195213
func Test404On169Dot254Net(t *testing.T) {
196214
t.Parallel()
197215
testURL := "http://169.254.0.1/foo.cgi"
198-
_, err := makeTestReq(testURL, 404)
216+
_, err := makeTestReq(testURL, 404, camoConfig)
199217
assert.Nil(t, err)
200218
}
201219

202220
func Test404On172Dot16Net(t *testing.T) {
203221
t.Parallel()
204222
for i := 16; i < 32; i++ {
205223
testURL := "http://172.%d.0.1/foo.cgi"
206-
_, err := makeTestReq(fmt.Sprintf(testURL, i), 404)
224+
_, err := makeTestReq(fmt.Sprintf(testURL, i), 404, camoConfig)
207225
assert.Nil(t, err)
208226
}
209227
}
210228

211229
func Test404On192Dot168Net(t *testing.T) {
212230
t.Parallel()
213231
testURL := "http://192.168.0.1/foo.cgi"
214-
_, err := makeTestReq(testURL, 404)
232+
_, err := makeTestReq(testURL, 404, camoConfig)
215233
assert.Nil(t, err)
216234
}
217235

218236
func Test404OnLocalhost(t *testing.T) {
219237
t.Parallel()
220238
testURL := "http://localhost/foo.cgi"
221-
record, err := makeTestReq(testURL, 404)
239+
record, err := makeTestReq(testURL, 404, camoConfig)
222240
if assert.Nil(t, err) {
223241
assert.Equal(t, "Bad url host\n", record.Body.String(), "Expected 404 response body but got '%s' instead", record.Body.String())
224242
}
@@ -228,7 +246,7 @@ func Test404OnLocalhost(t *testing.T) {
228246
func Test404OnLoopback(t *testing.T) {
229247
t.Parallel()
230248
testURL := "http://i.i.com.com/foo.cgi"
231-
record, err := makeTestReq(testURL, 404)
249+
record, err := makeTestReq(testURL, 404, camoConfig)
232250
if assert.Nil(t, err) {
233251
assert.Equal(t, "Denylist host failure\n", record.Body.String(), "Expected 404 response body but got '%s' instead", record.Body.String())
234252
}

0 commit comments

Comments
 (0)