Skip to content

Commit f5358e3

Browse files
committed
Merge branch 'file-transport'
2 parents 4983ff1 + 369473a commit f5358e3

4 files changed

Lines changed: 228 additions & 5 deletions

File tree

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ It has the following features:
1818

1919
This can be done since the name resolution is done in devproxy, which is configured to map any request for http://example.com to http://127.0.0.1:3000.
2020

21-
* Transparent TLS wrapping (simulation of an SSL/TLS-enabled environment)
21+
* Transparent TLS termination / wrapping (simulation of an SSL/TLS-enabled environment)
2222

2323
You can also make it possible to direct the request to `https://example.com/` to the upstream by adding the configuration like the following:
2424

@@ -70,6 +70,42 @@ It has the following features:
7070
X-Cgi-Path-Info: $3
7171
```
7272

73+
* Serving files on the filesystem
74+
75+
You can also serve the local files by specifying `file:` scheme as an upstream:
76+
77+
```
78+
hosts:
79+
http://example.com:
80+
- ^(/.*)$: file:///some-document-root$1
81+
```
82+
83+
**WARNING**: this feature does such naive path translation that is easily exploitable for path traversals beyond the document root. Never expose the server to public when it is used.
84+
85+
```
86+
file_tx:
87+
root: /var/empty
88+
mime_type_file: /usr/share/mime/globs
89+
mime_type_file_format: xdg-globs
90+
```
91+
92+
Toplevel `file_tx` section configures the file transport.
93+
94+
* `root` (string, optional)
95+
96+
Specifies the base directory for resolving a absolute path when a relative form of file URI yields from the match.
97+
98+
* `mime_type_file` (string, optional)
99+
100+
Specifies the path to the MIME-type-to-extension mapping file used to deduce a MIME type from the file's extension.
101+
102+
devproxy will use Go's standard `mime.TypeForExtension()` function when unspecified.
103+
104+
* `mime_type_file_format` (string, optional)
105+
106+
Specifies the format for the MIME type file. Accepted values are `apache` and `xdg-globs`.
107+
108+
73109
* Proxy chaining
74110

75111
You can direct outgoing requests to another proxy server. This is useful in a restricted network environment.

config.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"time"
5454

5555
"github.com/cloudfoundry-incubator/candiedyaml"
56+
"github.com/moriyoshi/mimetypes"
5657
"github.com/pkg/errors"
5758
)
5859

@@ -88,11 +89,17 @@ type ProxyConfig struct {
8889
TLSConfig *tls.Config
8990
}
9091

92+
type FileTransportConfig struct {
93+
RootDirectory string
94+
MimeTypes mimetypes.MediaTypeRegistry
95+
}
96+
9197
type Config struct {
9298
Hosts map[string]*PerHostConfig
9399
Proxy ProxyConfig
94100
MITM MITMConfig
95101
ResponseFilters []ResponseFilter
102+
FileTransport FileTransportConfig
96103
NowGetter func() (time.Time, error)
97104
}
98105

@@ -955,6 +962,69 @@ func (ctx *ConfigReaderContext) extractResponseFilters(configMap map[string]inte
955962
return
956963
}
957964

965+
func (ctx *ConfigReaderContext) extractFileTransportConfig(configMap map[string]interface{}) (retval FileTransportConfig, err error) {
966+
__fileTx, ok := configMap["file_tx"]
967+
if !ok {
968+
return
969+
}
970+
971+
_fileTx, ok := __fileTx.(map[interface{}]interface{})
972+
if !ok {
973+
err = errors.Errorf("%s: invalid structure under file_tx", ctx.Filename)
974+
return
975+
}
976+
977+
{
978+
root := "."
979+
_root, ok := _fileTx["root"]
980+
if ok {
981+
root, ok = _root.(string)
982+
if !ok {
983+
err = errors.Errorf("%s: invalid value for file_tx/root", ctx.Filename)
984+
return
985+
}
986+
}
987+
988+
if !filepath.IsAbs(root) {
989+
var wd string
990+
wd, err = os.Getwd()
991+
if err != nil {
992+
return
993+
}
994+
root = filepath.Clean(filepath.Join(wd, root))
995+
}
996+
997+
retval.RootDirectory = root
998+
}
999+
1000+
{
1001+
_mimeTypeFile, ok := _fileTx["mime_type_file"]
1002+
if ok {
1003+
mimeTypeFile, ok := _mimeTypeFile.(string)
1004+
if !ok {
1005+
err = errors.Errorf("%s: invalid value for file_tx/mime_type_file", ctx.Filename)
1006+
return
1007+
}
1008+
mimeTypeFileFormat := "apache"
1009+
_mimeTypeFileFormat, ok := _fileTx["mime_type_file_format"]
1010+
if ok {
1011+
mimeTypeFileFormat, ok = _mimeTypeFileFormat.(string)
1012+
if !ok {
1013+
err = errors.Errorf("%s: invalid value for file_tx/mime_type_file_format", ctx.Filename)
1014+
return
1015+
}
1016+
}
1017+
retval.MimeTypes, err = mimetypes.Load(mimeTypeFile, mimeTypeFileFormat)
1018+
if err != nil {
1019+
err = errors.Wrapf(err, "failed to load %s", mimeTypeFile)
1020+
return
1021+
}
1022+
}
1023+
}
1024+
1025+
return
1026+
}
1027+
9581028
func defaultNow() (time.Time, error) {
9591029
return time.Now(), nil
9601030
}
@@ -991,11 +1061,18 @@ func loadConfig(yamlFile string, progname string) (*Config, error) {
9911061
if err != nil {
9921062
return nil, err
9931063
}
1064+
1065+
fileTransport, err := ctx.extractFileTransportConfig(configMap)
1066+
if err != nil {
1067+
return nil, err
1068+
}
1069+
9941070
return &Config{
9951071
Hosts: perHostConfigs,
9961072
Proxy: proxy,
9971073
MITM: mitm,
9981074
ResponseFilters: responseFilters,
1075+
FileTransport: fileTransport,
9991076
NowGetter: defaultNow,
10001077
}, nil
10011078
}

filetx.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2016 Moriyoshi Koizumi
3+
* Copyright (c) 2012 Elazar Leibovich.
4+
* Copyright (c) 2012 The Go Authors.
5+
*
6+
* All rights reserved.
7+
*
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are
10+
* met:
11+
*
12+
* * Redistributions of source code must retain the above copyright
13+
* notice, this list of conditions and the following disclaimer.
14+
* * Redistributions in binary form must reproduce the above
15+
* copyright notice, this list of conditions and the following disclaimer
16+
* in the documentation and/or other materials provided with the
17+
* distribution.
18+
* * Neither the name of Elazar Leibovich. nor the names of its
19+
* contributors may be used to endorse or promote products derived from
20+
* this software without specific prior written permission.
21+
*
22+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
package main
35+
36+
import (
37+
"fmt"
38+
"mime"
39+
"net/http"
40+
"os"
41+
"path/filepath"
42+
43+
"github.com/moriyoshi/mimetypes"
44+
_ "github.com/moriyoshi/mimetypes/loaders"
45+
"github.com/moriyoshi/simplefiletx"
46+
)
47+
48+
var httpMetaKeys = []string{"Content-Type"}
49+
50+
const defaultMimeType = "application/octet-stream"
51+
52+
type MyOpener struct {
53+
MimeTypes mimetypes.MediaTypeRegistry
54+
}
55+
56+
type MyReaderWithStat struct {
57+
inner simplefiletx.ReaderWithStat
58+
name string
59+
opener *MyOpener
60+
}
61+
62+
func (opener *MyOpener) Open(name string) (simplefiletx.ReaderWithStat, error) {
63+
f, err := os.Open(name)
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
return &MyReaderWithStat{f, name, opener}, nil
69+
}
70+
71+
func (rws *MyReaderWithStat) Read(b []byte) (int, error) {
72+
return rws.inner.Read(b)
73+
}
74+
75+
func (rws *MyReaderWithStat) Close() error {
76+
return rws.inner.Close()
77+
}
78+
79+
func (rws *MyReaderWithStat) Stat() (os.FileInfo, error) {
80+
return rws.inner.Stat()
81+
}
82+
83+
func (rws *MyReaderWithStat) GetHTTPMetadataKeys() ([]string, error) {
84+
return httpMetaKeys, nil
85+
}
86+
87+
func (rws *MyReaderWithStat) GetHTTPMetadata(k string) ([]string, error) {
88+
if k == "Content-Type" {
89+
ext := filepath.Ext(rws.name)
90+
var mimeType string
91+
if rws.opener.MimeTypes != nil {
92+
mimeType = rws.opener.MimeTypes.TypeByExtension(ext)
93+
} else {
94+
mimeType = mime.TypeByExtension(ext)
95+
}
96+
if mimeType == "" {
97+
mimeType = defaultMimeType
98+
}
99+
return []string{mimeType}, nil
100+
}
101+
return nil, fmt.Errorf("no such key: %s", k)
102+
}
103+
104+
func NewFileTransport(config FileTransportConfig) http.RoundTripper {
105+
return &simplefiletx.SimpleFileTransport{
106+
BaseDir: config.RootDirectory,
107+
Opener: &MyOpener{config.MimeTypes},
108+
}
109+
}

main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ func (ctx *DevProxy) newHttpTransport() *httpx.Transport {
188188
Proxy2: ctx.newProxyURLBuilder(),
189189
}
190190
transport.RegisterProtocol("fastcgi", &fastCGIRoundTripper{Logger: ctx.Logger})
191+
transport.RegisterProtocol("file", NewFileTransport(ctx.Config.FileTransport))
191192
return transport
192193
}
193194

@@ -242,10 +243,10 @@ func (ctx *DevProxy) prepareMITMCertificate(hosts []string) (*tls.Certificate, e
242243
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment | x509.KeyUsageDigitalSignature,
243244
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
244245
BasicConstraintsValid: true,
245-
IsCA: false,
246-
MaxPathLen: 0,
247-
MaxPathLenZero: true,
248-
DNSNames: hosts,
246+
IsCA: false,
247+
MaxPathLen: 0,
248+
MaxPathLenZero: true,
249+
DNSNames: hosts,
249250
}
250251
derBytes, err := x509.CreateCertificate(ctx.CryptoRandReader, &template, ca.Certificate, ca.Certificate.PublicKey, ca.PrivateKey)
251252
if err != nil {

0 commit comments

Comments
 (0)