146 lines
5.4 KiB
Python
146 lines
5.4 KiB
Python
import sys
|
|
import os.path
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib"))
|
|
import re
|
|
import requests
|
|
from requests.status_codes import codes
|
|
try:
|
|
import http.client as httplib
|
|
except ImportError:
|
|
import httplib
|
|
import commandline
|
|
import sublime
|
|
try:
|
|
from io import StringIO
|
|
except ImportError:
|
|
from StringIO import StringIO # Linux version blows up when importing io.StringIO
|
|
import logging
|
|
|
|
logging.basicConfig(format='%(asctime)s %(message)s')
|
|
logger = logging.getLogger()
|
|
|
|
|
|
class CurlSession(object):
|
|
ERR_UNKNOWN_CODE = "Curl failed with an unrecognized code"
|
|
CURL_ERRORS = {
|
|
2: "Curl failed initialization.",
|
|
5: "Curl could not resolve the proxy specified.",
|
|
6: "Curl could not resolve the remote host.\n\nPlease verify that your Internet"
|
|
" connection works properly."
|
|
}
|
|
|
|
class FakeSocket(StringIO):
|
|
def makefile(self, *args, **kw):
|
|
return self
|
|
|
|
def __init__(self, verify=None):
|
|
self.verify = verify
|
|
|
|
def _parse_http(self, text):
|
|
# if the response text starts with a 302, skip to the next non-302 header
|
|
if re.match(r'^HTTP/.*?\s302 Found', text):
|
|
m = re.search(r'(HTTP/\d+\.\d+\s(?!302 Found).*$)', text, re.S)
|
|
if not m:
|
|
raise Exception("Unrecognized response: %s" % text)
|
|
else:
|
|
text = m.group(1)
|
|
|
|
# if the response text starts with a "200 Connection established" but continues with a 201,
|
|
# skip the 200 header. This happens when using a proxy.
|
|
#
|
|
# e.g. HTTP/1.1 200 Connection established
|
|
# Via: 1.1 proxy
|
|
# Connection: Keep-Alive
|
|
# Proxy-Connection: Keep-Alive
|
|
#
|
|
# HTTP/1.1 201 Created
|
|
# Server: GitHub.com
|
|
# ...
|
|
# Status: 201 Created
|
|
# ...
|
|
if re.match(r'^HTTP/.*?\s200 Connection established', text):
|
|
m = re.search(r'(HTTP/\d+\.\d+\s(?!200 Connection established).*$)', text, re.S)
|
|
if not m:
|
|
raise Exception("Unrecognized response: %s" % text)
|
|
else:
|
|
text = m.group(1)
|
|
|
|
# remove Transfer-Encoding: chunked header, as it causes reading the response to fail
|
|
# first do a quick check for it, so we can avoid doing the expensive negative-lookbehind
|
|
# regex if we don't need it
|
|
if "Transfer-Encoding: chunked" in text:
|
|
# we do the negative-lookbehind to make sure we only strip the Transfer-Encoding
|
|
# string in the header
|
|
text = re.sub(r'(?<!\r\n\r\n).*?Transfer-Encoding: chunked\r\n', '', text, count=1)
|
|
|
|
logger.debug("CurlSession - getting socket from %s" % text)
|
|
socket = self.FakeSocket(text)
|
|
response = httplib.HTTPResponse(socket)
|
|
response.begin()
|
|
return response
|
|
|
|
def _build_response(self, text):
|
|
logger.debug("CurlSession: building response from %s" % text)
|
|
raw_response = self._parse_http(text)
|
|
response = requests.models.Response()
|
|
response.encoding = 'utf-8'
|
|
response.status_code = raw_response.status
|
|
response.headers = dict(raw_response.getheaders())
|
|
response._content = raw_response.read()
|
|
return response
|
|
|
|
def request(self, method, url, headers=None, params=None, data=None, auth=None, allow_redirects=False, config=None, proxies=None):
|
|
try:
|
|
curl = commandline.find_binary('curl')
|
|
except commandline.BinaryNotFoundError:
|
|
sublime.error_message("I couldn't find \"curl\" on your system. Curl is required on Linux. Please install it and try again.")
|
|
return
|
|
|
|
curl_options = ['-i', '-L', '--user-agent', 'Sublime Github', '-s']
|
|
if auth:
|
|
curl_options.extend(['--user', "%s:%s" % auth])
|
|
if self.verify:
|
|
curl_options.extend(['--cacert', self.verify])
|
|
if headers:
|
|
for k, v in headers.items():
|
|
curl_options.extend(['-H', "%s: %s" % (k, v)])
|
|
if method in ('post', 'patch'):
|
|
curl_options.extend(['-d', data])
|
|
if method == 'patch':
|
|
curl_options.extend(['-X', 'PATCH'])
|
|
if params:
|
|
url += '?' + '&'.join(['='.join([k, str(v)]) for k, v in params.items()])
|
|
if proxies and proxies.get('https', None):
|
|
curl_options.extend(['-x', proxies['https']])
|
|
|
|
command = [curl] + curl_options + [url]
|
|
|
|
logger.debug("CurlSession: invoking curl with %s" % command)
|
|
try:
|
|
command_response = commandline.execute(command)
|
|
except commandline.CommandExecutionError as e:
|
|
logger.error("Curl execution: %s" % repr(e))
|
|
self._handle_curl_error(e.errorcode)
|
|
return
|
|
|
|
response = self._build_response(command_response)
|
|
response.url = url
|
|
return response
|
|
|
|
def post(self, *args, **kwargs):
|
|
return self.request("post", *args, **kwargs)
|
|
|
|
def _handle_curl_error(self, error):
|
|
sublime.error_message(
|
|
self.CURL_ERRORS.get(error, "%s: %s" % (self.ERR_UNKNOWN_CODE, error)))
|
|
|
|
|
|
def session(verify=None, force_curl=False):
|
|
if not force_curl and hasattr(httplib, "HTTPSConnection"):
|
|
session = requests.Session()
|
|
session.verify = verify
|
|
return session
|
|
else: # try curl
|
|
return CurlSession(verify=verify)
|