Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

amex broken under requests but not urllib #162

Open
dtbartle opened this issue Jul 30, 2022 · 7 comments
Open

amex broken under requests but not urllib #162

dtbartle opened this issue Jul 30, 2022 · 7 comments

Comments

@dtbartle
Copy link

When using requests, content type is sent as "Content-type" while urllib sends "Content-Type". Per https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5, the latter is correct. This evidently breaks amex starting a couple days ago.

@dtbartle dtbartle changed the title amex broken due to Content-type vs Content-Type amex broken under requests but not urllib Jul 30, 2022
@csingley
Copy link
Owner

Nice catch, thanks for reporting. Should be fixed in 655549a

@dtbartle
Copy link
Author

Actually, not sure that's it, though removing "Content-type": mimetype, from http_headers does get it working...

@csingley
Copy link
Owner

No your're right, it's probably not. If you search Client.py (or this bug tracker) for "Amex" you'll find comments from the last round of header-related incantation engineering regarding XML quality scores etc. If urllib works for you and requests doesn't, you'd probably be better served by isolating ofxtools inside a virtualenv. OFX is deprecated as a transmission protocol, and life is short.

@dtbartle
Copy link
Author

I managed to reproduce this with curl; it's pretty wacky, and seems to to do with header ordering:

OK:
curl --http1.1 -X POST -v 'https://online.americanexpress.com/myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload' -H 'Accept: */*, application/x-ofx, application/xml;q=2.9' -d @file -H 'User-Agent: InetClntApp/5.0' -H 'Content-Type: application/x-ofx' -H 'Accept-Encoding: identity'

Forbidden:
curl --http1.1 -X POST -v 'https://online.americanexpress.com/myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload' -H 'User-Agent: InetClntApp/5.0' -H 'Accept: */*, application/x-ofx, application/xml;q=2.9' -d @file -H 'Content-Type: application/x-ofx' -H 'Accept-Encoding: identity'

@dtbartle
Copy link
Author

The requests library puts User-Agent first, I assume because it just updates the default headers, which list that first:

def default_headers():
    """
    :rtype: requests.structures.CaseInsensitiveDict
    """
    return CaseInsensitiveDict({
        'User-Agent': default_user_agent(),
        'Accept-Encoding': ', '.join(('gzip', 'deflate')),
        'Accept': '*/*',
        'Connection': 'keep-alive',
    })

You can work around this by setting headers on session, i.e. sess.headers = self.http_headers.

Forbidden:
send: b'POST /myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload HTTP/1.1\r\nHost: online.americanexpress.com\r\nUser-Agent: InetClntApp/3.0\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*, application/x-ofx, application/xml;q=2.9\r\nConnection: keep-alive\r\nContent-Type: application/x-ofx\r\nContent-Length: 694\r\n\r\n'

OK:
send: b'POST /myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload HTTP/1.1\r\nHost: online.americanexpress.com\r\nAccept-Encoding: identity\r\nUser-Agent: InetClntApp/3.0\r\nContent-type: application/ x-ofx\r\nAccept: */*, application/x-ofx, application/xml;q=0.9\r\nContent-Length: 694\r\n\r\n'

And for reference, what urllib sends (also OK):
send: b'POST /myca/ofxdl/desktop/desktopDownload.do?request_type=nl_ofxdownload HTTP/1.1\r\nAccept-Encoding: identity\r\nContent-Length: 694\r\nHost: online.americanexpress.com\r\nUser-Agent: InetClntApp/3.0\r\nContent-Type: application/x-ofx\r\nAccept: */*, application/x-ofx, application/xml;q=0.9\r\nConnection: close\r\n\r\n'

@csingley
Copy link
Owner

csingley commented Aug 1, 2022

You're in it to win it!

Whatever homegrown jank Amex has up on the web seems to be in the van of the "code to Quicken not the spec" movement. If you're hardcore, you'll want to install Quicken & mitmproxy to directly observe the header sequence being set by the genuine article, rather than repeatedly lobbing codebreaker style guesses at their server.

But if you want to break up the code behind the USE_REQUESTS switch in OFXClient.post_request(), and kludge the headers after constructing session but before POSTing the request... I mean OK, I'd take a diff, especially if you were intent enough to include a unit test of this behavior.

But, and I'm gonna keep bringing this up, you could also just avoid using requests, by running ofxtools in a virtual env that doesn't have requests installed. Everything about OFX download is fairly terrible, and there's just not that much life left in the system over which to amortize a bunch of dev work.

@dtbartle
Copy link
Author

dtbartle commented Aug 2, 2022

Yeah, I ended up just setting USE_REQUESTS = False for amex. I wonder if there should be a CLI flag or Client arg to disable the requests module, though?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants