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 idiosyncrasy 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) } // RawRequest is a convenience wrapper around http.Post. It is exposed only for // when you need to read/inspect the raw HTTP response yourself. 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 } // RequestNoParse marshals a Request to XML, makes an HTTP request, and returns // the raw HTTP response func (c *DiscoverCardClient) RequestNoParse(r *Request) (*http.Response, error) { return clientRequestNoParse(c, r) } // Request marshals a Request to XML, makes an HTTP request, and then // unmarshals the response into a Response object. func (c *DiscoverCardClient) Request(r *Request) (*Response, error) { return clientRequest(c, r) }