# -*- coding: utf-8 -*- # _http.py # Copyright (C) 2015-2017 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ twisted.web utils for bonafide. """ import cookielib import os import urllib from leap.common.files import get_mtime from twisted.logger import Logger from twisted.internet import defer, protocol, reactor from twisted.internet.ssl import Certificate from twisted.python.filepath import FilePath from twisted.web.client import Agent, CookieAgent from twisted.web.client import BrowserLikePolicyForHTTPS from twisted.web.http_headers import Headers from twisted.web.iweb import IBodyProducer from zope.interface import implements log = Logger() def cookieAgentFactory(verify_path, connectTimeout=30): customPolicy = BrowserLikePolicyForHTTPS( Certificate.loadPEM(FilePath(verify_path).getContent())) agent = Agent(reactor, customPolicy, connectTimeout=connectTimeout) cookiejar = cookielib.CookieJar() return CookieAgent(agent, cookiejar) class Unchanged(Exception): pass class Forbidden(Exception): pass # TODO this should be ported to use treq client. def httpRequest(agent, url, values=None, headers=None, method='POST', token=None, saveto=None): if values is None: values = {} if headers is None: headers = {} data = '' mtime = None if values: data = urllib.urlencode(values) headers['Content-Type'] = ['application/x-www-form-urlencoded'] isfile = os.path.isfile if saveto is not None and isfile(saveto): # TODO - I think we need a force parameter, because we might have a # malformed file. Or maybe just remove the file if sanity check does # not pass. mtime = get_mtime(saveto) if mtime is not None: headers['if-modified-since'] = [mtime] if token: headers['Authorization'] = ['Token token="%s"' % (bytes(token))] def handle_response(response): log.debug("RESPONSE %s %s %s" % (method, response.code, url)) if response.code == 204: d = defer.succeed('') elif response.code == 401: raise Forbidden() if saveto and mtime and response.code == 304: log.debug('304 (Not modified): %s' % url) raise Unchanged() else: class SimpleReceiver(protocol.Protocol): def __init__(s, d): s.buf = '' s.d = d def dataReceived(s, data): s.buf += data def connectionLost(s, reason): # TODO: test if reason is twisted.web.client.ResponseDone, # if not, do an errback s.d.callback(s.buf) d = defer.Deferred() response.deliverBody(SimpleReceiver(d)) return d def passthru(failure): failure.trap(Unchanged, Forbidden) d = agent.request(method, url, Headers(headers), StringProducer(data) if data else None) d.addCallback(handle_response) if saveto: d.addCallback(lambda body: _write_to_file(body, saveto)) d.addErrback(passthru) return d def _write_to_file(content, path): folder = os.path.split(path)[0] if not os.path.isdir(folder): os.makedirs(folder) with open(path, 'w') as f: f.write(content) # touch it to update its utime os.utime(path, None) class StringProducer(object): implements(IBodyProducer) def __init__(self, body): self.body = body self.length = len(body) def startProducing(self, consumer): consumer.write(self.body) return defer.succeed(None) def pauseProducing(self): pass def stopProducing(self): pass