2017-03-16 19:31:26 -04:00
|
|
|
package ofxgo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
2017-04-01 21:33:56 -04:00
|
|
|
"strings"
|
2017-03-16 19:31:26 -04:00
|
|
|
)
|
|
|
|
|
2017-04-16 20:38:45 -04:00
|
|
|
// Client serves to aggregate OFX client settings that may be necessary to talk
|
|
|
|
// to a particular server due to quirks in that server's implementation. Client
|
|
|
|
// also provides the Request, RequestNoParse, and RawRequest helper methods to
|
|
|
|
// aid in making and parsing requests. Client uses default, non-zero settings,
|
|
|
|
// even if its fields are not initialized.
|
2017-03-16 19:31:26 -04:00
|
|
|
type Client struct {
|
|
|
|
// Request fields to overwrite with the client's values. If nonempty,
|
|
|
|
// defaults are used
|
|
|
|
SpecVersion string // VERSION in header
|
2017-04-12 21:40:42 -04:00
|
|
|
AppID string // SONRQ>APPID
|
2017-03-16 19:31:26 -04:00
|
|
|
AppVer string // SONRQ>APPVER
|
|
|
|
|
|
|
|
// Don't insert newlines or indentation when marshalling to SGML/XML
|
|
|
|
NoIndent bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var defaultClient Client
|
|
|
|
|
2017-04-16 20:38:45 -04:00
|
|
|
// OfxVersion returns a string representation of the OFX specification version
|
|
|
|
// this Client will marshal Requests as. Defaults to "203" if the client's
|
|
|
|
// SpecVersion field is empty.
|
2017-03-16 19:31:26 -04:00
|
|
|
func (c *Client) OfxVersion() string {
|
|
|
|
if len(c.SpecVersion) > 0 {
|
|
|
|
return c.SpecVersion
|
|
|
|
}
|
2017-04-16 20:38:45 -04:00
|
|
|
return "203"
|
2017-03-16 19:31:26 -04:00
|
|
|
}
|
|
|
|
|
2017-04-16 20:38:45 -04:00
|
|
|
// ID returns this Client's OFX AppID field, defaulting to "OFXGO" if
|
|
|
|
// unspecified.
|
2017-04-12 21:40:42 -04:00
|
|
|
func (c *Client) ID() String {
|
|
|
|
if len(c.AppID) > 0 {
|
|
|
|
return String(c.AppID)
|
2017-03-16 19:31:26 -04:00
|
|
|
}
|
2017-04-16 20:38:45 -04:00
|
|
|
return String("OFXGO")
|
2017-03-16 19:31:26 -04:00
|
|
|
}
|
|
|
|
|
2017-04-16 20:38:45 -04:00
|
|
|
// Version returns this Client's version number as a string, defaulting to
|
|
|
|
// "0001" if unspecified.
|
2017-03-16 19:31:26 -04:00
|
|
|
func (c *Client) Version() String {
|
|
|
|
if len(c.AppVer) > 0 {
|
|
|
|
return String(c.AppVer)
|
|
|
|
}
|
2017-04-16 20:38:45 -04:00
|
|
|
return String("0001")
|
2017-03-16 19:31:26 -04:00
|
|
|
}
|
|
|
|
|
2017-04-16 20:38:45 -04:00
|
|
|
// IndentRequests returns true if the marshaled XML should be indented (and
|
|
|
|
// contain newlines, since the two are linked in the current implementation)
|
2017-03-16 19:31:26 -04:00
|
|
|
func (c *Client) IndentRequests() bool {
|
|
|
|
return !c.NoIndent
|
|
|
|
}
|
|
|
|
|
2017-03-17 21:36:20 -04:00
|
|
|
// RawRequest is little more than a thin wrapper around http.Post
|
|
|
|
//
|
|
|
|
// In most cases, you should probably be using Request() instead, but
|
|
|
|
// RawRequest can be useful if you need to read the raw unparsed http response
|
|
|
|
// yourself (perhaps for downloading an OFX file for use by an external
|
|
|
|
// program, or debugging server behavior), or have a handcrafted request you'd
|
|
|
|
// like to try.
|
|
|
|
//
|
|
|
|
// Caveats: RawRequest does *not* take client settings into account as
|
|
|
|
// Request() does, so your particular server may or may not like whatever we
|
|
|
|
// read from 'r'. The caller is responsible for closing the http Response.Body
|
|
|
|
// (see the http module's documentation for more information)
|
2017-03-16 19:31:26 -04:00
|
|
|
func RawRequest(URL string, r io.Reader) (*http.Response, error) {
|
2017-04-01 21:33:56 -04:00
|
|
|
if !strings.HasPrefix(URL, "https://") {
|
|
|
|
return nil, errors.New("Refusing to send OFX request with possible plain-text password over non-https protocol")
|
|
|
|
}
|
|
|
|
|
2017-03-16 19:31:26 -04:00
|
|
|
response, err := http.Post(URL, "application/x-ofx", r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.StatusCode != 200 {
|
|
|
|
return nil, errors.New("OFXQuery request status: " + response.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2017-03-22 19:56:59 -04:00
|
|
|
// RequestNoParse marshals a Request object into XML, makes an HTTP request,
|
|
|
|
// and returns the raw HTTP response. Unlike RawRequest(), it takes client
|
|
|
|
// settings into account. Unlike Request(), it doesn't parse the response into
|
|
|
|
// a Request object.
|
2017-03-16 19:31:26 -04:00
|
|
|
//
|
2017-03-22 19:56:59 -04:00
|
|
|
// Caveat: The caller is responsible for closing the http Response.Body (see
|
|
|
|
// the http module's documentation for more information)
|
|
|
|
func (c *Client) RequestNoParse(r *Request) (*http.Response, error) {
|
2017-03-28 20:42:22 -04:00
|
|
|
r.SetClientFields(c)
|
2017-03-16 19:31:26 -04:00
|
|
|
|
|
|
|
b, err := r.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-03-22 19:56:59 -04:00
|
|
|
return RawRequest(r.URL, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request marshals a Request object into XML, makes an HTTP request against
|
|
|
|
// it's URL, and then unmarshals the response into a Response object.
|
|
|
|
//
|
|
|
|
// Before being marshaled, some of the the Request object's values are
|
|
|
|
// overwritten, namely those dictated by the Client's configuration (Version,
|
2017-04-12 21:40:42 -04:00
|
|
|
// AppID, AppVer fields), and the client's curren time (DtClient). These are
|
2017-03-22 19:56:59 -04:00
|
|
|
// updated in place in the supplied Request object so they may later be
|
|
|
|
// inspected by the caller.
|
|
|
|
func (c *Client) Request(r *Request) (*Response, error) {
|
|
|
|
response, err := c.RequestNoParse(r)
|
2017-03-16 19:31:26 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
|
|
|
ofxresp, err := ParseResponse(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ofxresp, nil
|
|
|
|
}
|