Skip to content

Commit

Permalink
Add Hack for Discover
Browse files Browse the repository at this point in the history
Discover requires an exact set of headers in exact order, or it returns
HTTP 403.
  • Loading branch information
aclindsa committed Oct 8, 2018
1 parent 77b1546 commit 22a6d65
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions client.go
Expand Up @@ -63,6 +63,7 @@ func GetClient(URL string, bc *BasicClient) Client {
URL string
Func clientCreationFunc
}{
{"https://ofx.discovercard.com", NewDiscoverCardClient},
{"https://vesnc.vanguard.com/us/OfxDirectConnectServlet", NewVanguardClient},
}
for _, client := range clients {
Expand Down
103 changes: 103 additions & 0 deletions discovercard_client.go
@@ -0,0 +1,103 @@
package ofxgo

import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)

// DiscoverCardClient provides a Client implementation which handles
// DiscoverCard's broken HTTP header behavior. DiscoverCardClient uses default,
// non-zero settings, if its fields are not initialized.
type DiscoverCardClient struct {
*BasicClient
}

// NewDiscoverCardClient returns a Client interface configured to handle
// Discover Card's brand of idiosyncracy
func NewDiscoverCardClient(bc *BasicClient) Client {
return &DiscoverCardClient{bc}
}

func discoverCardHTTPPost(URL string, r io.Reader) (*http.Response, error) {
// Either convert or copy to a bytes.Buffer to be able to determine the
// request length for the Content-Length header
buf, ok := r.(*bytes.Buffer)
if !ok {
buf = &bytes.Buffer{}
_, err := io.Copy(buf, r)
if err != nil {
return nil, err
}
}

url, err := url.Parse(URL)
if err != nil {
return nil, err
}

path := url.Path
if path == "" {
path = "/"
}

// Discover requires only these headers and in this exact order, or it
// returns HTTP 403
headers := fmt.Sprintf("POST %s HTTP/1.1\r\n"+
"Content-Type: application/x-ofx\r\n"+
"Host: %s\r\n"+
"Content-Length: %d\r\n"+
"Connection: Keep-Alive\r\n"+
"\r\n", path, url.Hostname(), buf.Len())

host := url.Host
if url.Port() == "" {
host += ":443"
}

// BUGBUG: cannot do defer conn.Close() until body is read,
// we are "leaking" a socket here, but it will be finalized
conn, err := tls.Dial("tcp", host, nil)
if err != nil {
return nil, err
}

fmt.Fprint(conn, headers)
_, err = io.Copy(conn, buf)
if err != nil {
return nil, err
}

return http.ReadResponse(bufio.NewReader(conn), nil)
}

func (c *DiscoverCardClient) RawRequest(URL string, r io.Reader) (*http.Response, error) {
if !strings.HasPrefix(URL, "https://") {
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
}

response, err := discoverCardHTTPPost(URL, r)
if err != nil {
return nil, err
}

if response.StatusCode != 200 {
return nil, errors.New("OFXQuery request status: " + response.Status)
}

return response, nil
}

func (c *DiscoverCardClient) RequestNoParse(r *Request) (*http.Response, error) {
return clientRequestNoParse(c, r)
}

func (c *DiscoverCardClient) Request(r *Request) (*Response, error) {
return clientRequest(c, r)
}

0 comments on commit 22a6d65

Please sign in to comment.