import httplib import sublime import sublime_plugin import socket import types import threading gPrevHttpRequest = "" CHECK_DOWNLOAD_THREAD_TIME_MS = 1000 def monitorDownloadThread(downloadThread): if downloadThread.is_alive(): msg = downloadThread.getCurrentMessage() sublime.status_message(msg) sublime.set_timeout(lambda: monitorDownloadThread(downloadThread), CHECK_DOWNLOAD_THREAD_TIME_MS) else: downloadThread.showResultToPresenter() class HttpRequester(threading.Thread): REQUEST_TYPE_GET = "GET" REQUEST_TYPE_POST = "POST" REQUEST_TYPE_DELETE = "DELETE" REQUEST_TYPE_PUT = "PUT" httpRequestTypes = [REQUEST_TYPE_GET, REQUEST_TYPE_POST, REQUEST_TYPE_PUT, REQUEST_TYPE_DELETE] HTTP_URL = "http://" HTTPS_URL = "https://" httpProtocolTypes = [HTTP_URL, HTTPS_URL] HTTP_POST_BODY_START = "POST_BODY:" HTTP_PROXY_HEADER = "USE_PROXY" HTTPS_SSL_CLIENT_CERT = "CLIENT_SSL_CERT" HTTPS_SSL_CLIENT_KEY = "CLIENT_SSL_KEY" CONTENT_LENGTH_HEADER = "Content-lenght" MAX_BYTES_BUFFER_SIZE = 8192 FILE_TYPE_HTML = "html" FILE_TYPE_JSON = "json" FILE_TYPE_XML = "xml" httpContentTypes = [FILE_TYPE_HTML, FILE_TYPE_JSON, FILE_TYPE_XML] def __init__(self, resultsPresenter): self.totalBytesDownloaded = 0 self.contentLenght = 0 self.resultsPresenter = resultsPresenter threading.Thread.__init__(self) def request(self, selection): self.selection = selection self.start() sublime.set_timeout(lambda: monitorDownloadThread(self), CHECK_DOWNLOAD_THREAD_TIME_MS) def run(self): DEFAULT_TIMEOUT = 10 FAKE_CURL_UA = "curl/7.21.0 (i486-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.15 libssh2/1.2.6" MY_UA = "python httpRequester 1.0.0" selection = self.selection lines = selection.split("\n") # trim any whitespaces for all lines and remove lines starting with a pound character for idx in range(len(lines) - 1, -1, -1): lines[idx] = lines[idx].lstrip() lines[idx] = lines[idx].rstrip() if (len(lines[idx]) > 0): if lines[idx][0] == "#": del lines[idx] # get request web address and req. type from the first line (url, port, request_page, requestType, httpProtocol) = self.extractRequestParams(lines[0]) print "Requesting...." print requestType, " ", httpProtocol, " HOST ", url, " PORT ", port, " PAGE: ", request_page # get request headers from the lines below the http address (extra_headers, requestPOSTBody, proxyURL, proxyPort, clientSSLCertificateFile, clientSSLKeyFile) = self.extractExtraHeaders(lines) headers = {"User-Agent": FAKE_CURL_UA, "Accept": "*/*"} for key in extra_headers: headers[key] = extra_headers[key] # if valid POST body add Content-lenght header if len(requestPOSTBody) > 0: headers[self.CONTENT_LENGTH_HEADER] = len(requestPOSTBody) for key in headers: print "REQ HEADERS ", key, " : ", headers[key] respText = "" fileType = "" useProxy = False if len(proxyURL) > 0: useProxy = True # make http request try: if not(useProxy): if httpProtocol == self.HTTP_URL: conn = httplib.HTTPConnection(url, port, timeout=DEFAULT_TIMEOUT) else: if len(clientSSLCertificateFile) > 0 or len(clientSSLKeyFile) > 0: print "Using client SSL certificate: ", clientSSLCertificateFile print "Using client SSL key file: ", clientSSLKeyFile conn = httplib.HTTPSConnection( url, port, timeout=DEFAULT_TIMEOUT, cert_file=clientSSLCertificateFile, key_file=clientSSLKeyFile) else: conn = httplib.HTTPSConnection(url, port, timeout=DEFAULT_TIMEOUT) conn.request(requestType, request_page, requestPOSTBody, headers) else: print "Using proxy: ", proxyURL + ":" + str(proxyPort) conn = httplib.HTTPConnection(proxyURL, proxyPort, timeout=DEFAULT_TIMEOUT) conn.request(requestType, httpProtocol + url + request_page, requestPOSTBody, headers) resp = conn.getresponse() (respText, fileType) = self.getParsedResponse(resp) conn.close() except (socket.error, httplib.HTTPException, socket.timeout) as e: if not(isinstance(e, types.NoneType)): respText = "Error connecting: " + str(e) else: respText = "Error connecting" except AttributeError as e: print e respText = "HTTPS not supported by your Python version" self.respText = respText self.fileType = fileType def extractHttpRequestType(self, line): for type in self.httpRequestTypes: if line.find(type) == 0: return type return "" def extractWebAdressPart(self, line): webAddress = "" for protocol in self.httpProtocolTypes: requestPartions = line.partition(protocol) if requestPartions[1] == "": webAddress = requestPartions[0] else: webAddress = requestPartions[2] return (webAddress, protocol) return (webAddress, self.HTTP_URL) def extractRequestParams(self, requestLine): requestType = self.extractHttpRequestType(requestLine) if requestType == "": requestType = self.REQUEST_TYPE_GET else: partition = requestLine.partition(requestType) requestLine = partition[2].lstrip() # remove http:// or https:// from URL (webAddress, protocol) = self.extractWebAdressPart(requestLine) request_parts = webAddress.split("/") request_page = "" if len(request_parts) > 1: for idx in range(1, len(request_parts)): request_page = request_page + "/" + request_parts[idx] else: request_page = "/" url_parts = request_parts[0].split(":") url_idx = 0 url = url_parts[url_idx] if protocol == self.HTTP_URL: port = httplib.HTTP_PORT else: port = httplib.HTTPS_PORT if len(url_parts) > url_idx + 1: port = int(url_parts[url_idx + 1]) # convert requested page to utf-8 and replace spaces with + request_page = request_page.encode('utf-8') request_page = request_page.replace(' ', '+') return (url, port, request_page, requestType, protocol) def getHeaderNameAndValueFromLine(self, line): readingPOSTBody = False line = line.lstrip() line = line.rstrip() if line == self.HTTP_POST_BODY_START: readingPOSTBody = True else: header_parts = line.split(":") if len(header_parts) == 2: header_name = header_parts[0].rstrip() header_value = header_parts[1].lstrip() return (header_name, header_value, readingPOSTBody) else: # may be proxy address URL:port if len(header_parts) > 2: header_name = header_parts[0].rstrip() header_value = header_parts[1] header_value = header_value.lstrip() header_value = header_value.rstrip() for idx in range(2, len(header_parts)): currentValue = header_parts[idx] currentValue = currentValue.lstrip() currentValue = currentValue.rstrip() header_value = header_value + ":" + currentValue return (header_name, header_value, readingPOSTBody) return (None, None, readingPOSTBody) def extractExtraHeaders(self, headerLines): requestPOSTBody = "" readingPOSTBody = False lastLine = False numLines = len(headerLines) proxyURL = "" proxyPort = 0 clientSSLCertificateFile = "" clientSSLKeyFile = "" extra_headers = {} if len(headerLines) > 1: for i in range(1, numLines): lastLine = (i == numLines - 1) if not(readingPOSTBody): (header_name, header_value, readingPOSTBody) = self.getHeaderNameAndValueFromLine(headerLines[i]) if header_name is not None: if header_name == self.HTTP_PROXY_HEADER: (proxyURL, proxyPort) = self.getProxyURLandPort(header_value) elif header_name == self.HTTPS_SSL_CLIENT_CERT: clientSSLCertificateFile = header_value elif header_name == self.HTTPS_SSL_CLIENT_KEY: clientSSLKeyFile = header_value else: extra_headers[header_name] = header_value else: # read all following lines as HTTP POST body lineBreak = "" if not(lastLine): lineBreak = "\n" requestPOSTBody = requestPOSTBody + headerLines[i] + lineBreak return (extra_headers, requestPOSTBody, proxyURL, proxyPort, clientSSLCertificateFile, clientSSLKeyFile) def getProxyURLandPort(self, proxyAddress): proxyURL = "" proxyPort = 0 proxyParts = proxyAddress.split(":") proxyURL = proxyParts[0] if len(proxyParts) > 1: proxyURL = proxyParts[0] for idx in range(1, len(proxyParts) - 1): proxyURL = proxyURL + ":" + proxyParts[idx] lastIdx = len(proxyParts) - 1 proxyPort = int(proxyParts[lastIdx]) else: proxyPort = 80 return (proxyURL, proxyPort) def getParsedResponse(self, resp): fileType = self.FILE_TYPE_HTML resp_status = "%d " % resp.status + resp.reason + "\n" respText = resp_status for header in resp.getheaders(): respText += header[0] + ":" + header[1] + "\n" # get resp. file type (html, json and xml supported). fallback to html if header[0] == "content-type": fileType = self.getFileTypeFromContentType(header[1]) respText += "\n\n\n" self.contentLenght = int(resp.getheader("content-length", 0)) # download a 8KB buffer at a time respBody = resp.read(self.MAX_BYTES_BUFFER_SIZE) numDownloaded = len(respBody) self.totalBytesDownloaded = numDownloaded while numDownloaded == self.MAX_BYTES_BUFFER_SIZE: data = resp.read(self.MAX_BYTES_BUFFER_SIZE) respBody += data numDownloaded = len(data) self.totalBytesDownloaded += numDownloaded respText += respBody.decode("utf-8", "replace") return (respText, fileType) def getFileTypeFromContentType(self, contentType): fileType = self.FILE_TYPE_HTML contentType = contentType.lower() print "File type: ", contentType for cType in self.httpContentTypes: if cType in contentType: fileType = cType return fileType def getCurrentMessage(self): return "HttpRequester downloading " + str(self.totalBytesDownloaded) + " / " + str(self.contentLenght) def showResultToPresenter(self): self.resultsPresenter.createWindowWithText(self.respText, self.fileType) class HttpRequesterRefreshCommand(sublime_plugin.TextCommand): def run(self, edit): global gPrevHttpRequest selection = gPrevHttpRequest resultsPresenter = ResultsPresenter() httpRequester = HttpRequester(resultsPresenter) httpRequester.request(selection) class ResultsPresenter(): def __init__(self): pass def createWindowWithText(self, textToDisplay, fileType): newView = sublime.active_window().new_file() edit = newView.begin_edit() newView.insert(edit, 0, textToDisplay) newView.end_edit(edit) newView.set_scratch(True) newView.set_read_only(False) newView.set_name("http response") if fileType == HttpRequester.FILE_TYPE_HTML: newView.set_syntax_file("Packages/HTML/HTML.tmLanguage") if fileType == HttpRequester.FILE_TYPE_JSON: newView.set_syntax_file("Packages/JavaScript/JSON.tmLanguage") if fileType == HttpRequester.FILE_TYPE_XML: newView.set_syntax_file("Packages/XML/XML.tmLanguage") return newView.id() class HttpRequesterCommand(sublime_plugin.TextCommand): def run(self, edit): global gPrevHttpRequest selection = "" if self.has_selection(): for region in self.view.sel(): # Concatenate selected regions together. selection += self.view.substr(region) else: # Use entire document as selection entireDocument = sublime.Region(0, self.view.size()) selection = self.view.substr(entireDocument) gPrevHttpRequest = selection resultsPresenter = ResultsPresenter() httpRequester = HttpRequester(resultsPresenter) httpRequester.request(selection) def has_selection(self): has_selection = False # Only enable menu option if at least one region contains selected text. for region in self.view.sel(): if not region.empty(): has_selection = True return has_selection