http: add --http-headers flag for setting arbitrary headers

This commit is contained in:
Nick Craig-Wood 2019-08-12 15:29:35 +01:00
parent cd7ca2a320
commit 5e0a30509c
2 changed files with 71 additions and 10 deletions

View File

@ -46,6 +46,21 @@ func init() {
Value: "https://user:pass@example.com", Value: "https://user:pass@example.com",
Help: "Connect to example.com using a username and password", Help: "Connect to example.com using a username and password",
}}, }},
}, {
Name: "headers",
Help: `Set HTTP headers for all transactions
Use this to set additional HTTP headers for all transactions
The input format is comma separated list of key,value pairs. Standard
[CSV encoding](https://godoc.org/encoding/csv) may be used.
For example to set a Cookie use 'Cookie,name=value', or '"Cookie","name=value"'.
You can set multiple headers, eg '"Cookie","name=value","Authorization","xxx"'.
`,
Default: fs.CommaSepList{},
Advanced: true,
}, { }, {
Name: "no_slash", Name: "no_slash",
Help: `Set this if the site doesn't end directories with / Help: `Set this if the site doesn't end directories with /
@ -71,6 +86,7 @@ directories.`,
type Options struct { type Options struct {
Endpoint string `config:"url"` Endpoint string `config:"url"`
NoSlash bool `config:"no_slash"` NoSlash bool `config:"no_slash"`
Headers fs.CommaSepList `config:"headers"`
} }
// Fs stores the interface to the remote HTTP files // Fs stores the interface to the remote HTTP files
@ -115,6 +131,10 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
return nil, err return nil, err
} }
if len(opt.Headers)%2 != 0 {
return nil, errors.New("odd number of headers supplied")
}
if !strings.HasSuffix(opt.Endpoint, "/") { if !strings.HasSuffix(opt.Endpoint, "/") {
opt.Endpoint += "/" opt.Endpoint += "/"
} }
@ -140,12 +160,16 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
return http.ErrUseLastResponse return http.ErrUseLastResponse
} }
// check to see if points to a file // check to see if points to a file
res, err := noRedir.Head(u.String()) req, err := http.NewRequest("HEAD", u.String(), nil)
if err == nil {
addHeaders(req, opt)
res, err := noRedir.Do(req)
err = statusError(res, err) err = statusError(res, err)
if err == nil { if err == nil {
isFile = true isFile = true
} }
} }
}
newRoot := u.String() newRoot := u.String()
if isFile { if isFile {
@ -316,6 +340,20 @@ func parse(base *url.URL, in io.Reader) (names []string, err error) {
return names, nil return names, nil
} }
// Adds the configured headers to the request if any
func addHeaders(req *http.Request, opt *Options) {
for i := 0; i < len(opt.Headers); i += 2 {
key := opt.Headers[i]
value := opt.Headers[i+1]
req.Header.Add(key, value)
}
}
// Adds the configured headers to the request if any
func (f *Fs) addHeaders(req *http.Request) {
addHeaders(req, &f.opt)
}
// Read the directory passed in // Read the directory passed in
func (f *Fs) readDir(dir string) (names []string, err error) { func (f *Fs) readDir(dir string) (names []string, err error) {
URL := f.url(dir) URL := f.url(dir)
@ -326,7 +364,13 @@ func (f *Fs) readDir(dir string) (names []string, err error) {
if !strings.HasSuffix(URL, "/") { if !strings.HasSuffix(URL, "/") {
return nil, errors.Errorf("internal error: readDir URL %q didn't end in /", URL) return nil, errors.Errorf("internal error: readDir URL %q didn't end in /", URL)
} }
res, err := f.httpClient.Get(URL) // Do the request
req, err := http.NewRequest("GET", URL, nil)
if err != nil {
return nil, errors.Wrap(err, "readDir failed")
}
f.addHeaders(req)
res, err := f.httpClient.Do(req)
if err == nil { if err == nil {
defer fs.CheckClose(res.Body, &err) defer fs.CheckClose(res.Body, &err)
if res.StatusCode == http.StatusNotFound { if res.StatusCode == http.StatusNotFound {
@ -450,7 +494,12 @@ func (o *Object) url() string {
// stat updates the info field in the Object // stat updates the info field in the Object
func (o *Object) stat() error { func (o *Object) stat() error {
url := o.url() url := o.url()
res, err := o.fs.httpClient.Head(url) req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return errors.Wrap(err, "stat failed")
}
o.fs.addHeaders(req)
res, err := o.fs.httpClient.Do(req)
if err == nil && res.StatusCode == http.StatusNotFound { if err == nil && res.StatusCode == http.StatusNotFound {
return fs.ErrorObjectNotFound return fs.ErrorObjectNotFound
} }
@ -502,6 +551,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
for k, v := range fs.OpenOptionHeaders(options) { for k, v := range fs.OpenOptionHeaders(options) {
req.Header.Add(k, v) req.Header.Add(k, v)
} }
o.fs.addHeaders(req)
// Do the request // Do the request
res, err := o.fs.httpClient.Do(req) res, err := o.fs.httpClient.Do(req)

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"testing" "testing"
"time" "time"
@ -26,6 +27,7 @@ var (
remoteName = "TestHTTP" remoteName = "TestHTTP"
testPath = "test" testPath = "test"
filesPath = filepath.Join(testPath, "files") filesPath = filepath.Join(testPath, "files")
headers = []string{"X-Potato", "sausage", "X-Rhubarb", "cucumber"}
) )
// prepareServer the test server and return a function to tidy it up afterwards // prepareServer the test server and return a function to tidy it up afterwards
@ -33,8 +35,16 @@ func prepareServer(t *testing.T) (configmap.Simple, func()) {
// file server for test/files // file server for test/files
fileServer := http.FileServer(http.Dir(filesPath)) fileServer := http.FileServer(http.Dir(filesPath))
// test the headers are there then pass on to fileServer
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
what := fmt.Sprintf("%s %s: Header ", r.Method, r.URL.Path)
assert.Equal(t, headers[1], r.Header.Get(headers[0]), what+headers[0])
assert.Equal(t, headers[3], r.Header.Get(headers[2]), what+headers[2])
fileServer.ServeHTTP(w, r)
})
// Make the test server // Make the test server
ts := httptest.NewServer(fileServer) ts := httptest.NewServer(handler)
// Configure the remote // Configure the remote
config.LoadConfig() config.LoadConfig()
@ -47,6 +57,7 @@ func prepareServer(t *testing.T) (configmap.Simple, func()) {
m := configmap.Simple{ m := configmap.Simple{
"type": "http", "type": "http",
"url": ts.URL, "url": ts.URL,
"headers": strings.Join(headers, ","),
} }
// return a function to tidy up // return a function to tidy up