KoD 0.8
- tanti miglioramenti sotto il cofano, supporto iniziale al futuro kodi 19 - Nuova modalità di visualizzazione per episodio successivo - fixato wstream tramite l'aggiunta della finestra per risolvere il reCaptcha - aggiunta sezione segnala un problema in Aiuto - altri fix e migliorie varie a canali e server
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
__future_module__ = True
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
from __builtin__ import *
|
||||
# Overwrite any old definitions with the equivalent future.builtins ones:
|
||||
from future.builtins import *
|
||||
else:
|
||||
raise ImportError('This package should not be accessible on Python 3. '
|
||||
'Either you are trying to run from the python-future src folder '
|
||||
'or your installation of python-future is corrupted.')
|
||||
@@ -1,4 +1,6 @@
|
||||
# https://github.com/VeNoMouS/cloudscraper/tree/master
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import ssl
|
||||
@@ -9,6 +11,14 @@ try:
|
||||
except ImportError:
|
||||
import copy_reg as copyreg
|
||||
|
||||
try:
|
||||
from HTMLParser import HTMLParser
|
||||
except ImportError:
|
||||
if sys.version_info >= (3, 4):
|
||||
import html
|
||||
else:
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from copy import deepcopy
|
||||
from time import sleep
|
||||
from collections import OrderedDict
|
||||
@@ -31,13 +41,17 @@ except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
from urlparse import urlparse, urljoin
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
# Add exceptions path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'exceptions'))
|
||||
import cloudflare_exceptions # noqa: E402
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
__version__ = '1.2.19'
|
||||
__version__ = '1.2.24'
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -91,6 +105,7 @@ class CloudScraper(Session):
|
||||
'allow_brotli',
|
||||
True if 'brotli' in sys.modules.keys() else False
|
||||
)
|
||||
|
||||
self.user_agent = User_Agent(
|
||||
allow_brotli=self.allow_brotli,
|
||||
browser=kwargs.pop('browser', None)
|
||||
@@ -107,13 +122,16 @@ class CloudScraper(Session):
|
||||
# Set a random User-Agent if no custom User-Agent has been set
|
||||
# ------------------------------------------------------------------------------- #
|
||||
self.headers = self.user_agent.headers
|
||||
if not self.cipherSuite:
|
||||
self.cipherSuite = self.user_agent.cipherSuite
|
||||
|
||||
if isinstance(self.cipherSuite, list):
|
||||
self.cipherSuite = ':'.join(self.cipherSuite)
|
||||
|
||||
self.mount(
|
||||
'https://',
|
||||
CipherSuiteAdapter(
|
||||
cipherSuite=':'.join(self.user_agent.cipherSuite)
|
||||
if not self.cipherSuite else ':'.join(self.cipherSuite)
|
||||
if isinstance(self.cipherSuite, list) else self.cipherSuite
|
||||
cipherSuite=self.cipherSuite
|
||||
)
|
||||
)
|
||||
|
||||
@@ -138,6 +156,20 @@ class CloudScraper(Session):
|
||||
except ValueError as e:
|
||||
print("Debug Error: {}".format(getattr(e, 'message', e)))
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Unescape / decode html entities
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def unescape(html_text):
|
||||
if sys.version_info >= (3, 0):
|
||||
if sys.version_info >= (3, 4):
|
||||
return html.unescape(html_text)
|
||||
|
||||
return HTMLParser().unescape(html_text)
|
||||
|
||||
return HTMLParser().unescape(html_text)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# Decode Brotli on older versions of urllib3 manually
|
||||
# ------------------------------------------------------------------------------- #
|
||||
@@ -186,7 +218,7 @@ class CloudScraper(Session):
|
||||
sys.tracebacklimit = 0
|
||||
_ = self._solveDepthCnt
|
||||
self._solveDepthCnt = 0
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_Loop_Protection(
|
||||
"!!Loop Protection!! We have tried to solve {} time(s) in a row.".format(_)
|
||||
)
|
||||
|
||||
@@ -269,7 +301,7 @@ class CloudScraper(Session):
|
||||
def is_Challenge_Request(self, resp):
|
||||
if self.is_Firewall_Blocked(resp):
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError('Cloudflare has blocked this request (Code 1020 Detected).')
|
||||
raise cloudflare_exceptions.Cloudflare_Block('Cloudflare has blocked this request (Code 1020 Detected).')
|
||||
|
||||
if self.is_reCaptcha_Challenge(resp) or self.is_IUAM_Challenge(resp):
|
||||
return True
|
||||
@@ -280,17 +312,18 @@ class CloudScraper(Session):
|
||||
# Try to solve cloudflare javascript challenge.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def IUAM_Challenge_Response(body, url, interpreter):
|
||||
def IUAM_Challenge_Response(self, body, url, interpreter):
|
||||
try:
|
||||
challengeUUID = re.search(
|
||||
r'id="challenge-form" action="(?P<challengeUUID>\S+)"',
|
||||
body, re.M | re.DOTALL
|
||||
).groupdict().get('challengeUUID', '')
|
||||
|
||||
payload = OrderedDict(re.findall(r'name="(r|jschl_vc|pass)"\svalue="(.*?)"', body))
|
||||
|
||||
except AttributeError:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_Error_IUAM(
|
||||
"Cloudflare IUAM detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
@@ -301,7 +334,7 @@ class CloudScraper(Session):
|
||||
interpreter
|
||||
).solveChallenge(body, hostParsed.netloc)
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_Error_IUAM(
|
||||
'Unable to parse Cloudflare anti-bots page: {}'.format(
|
||||
getattr(e, 'message', e)
|
||||
)
|
||||
@@ -311,7 +344,7 @@ class CloudScraper(Session):
|
||||
'url': '{}://{}{}'.format(
|
||||
hostParsed.scheme,
|
||||
hostParsed.netloc,
|
||||
challengeUUID
|
||||
self.unescape(challengeUUID)
|
||||
),
|
||||
'data': payload
|
||||
}
|
||||
@@ -320,8 +353,7 @@ class CloudScraper(Session):
|
||||
# Try to solve the reCaptcha challenge via 3rd party.
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def reCaptcha_Challenge_Response(provider, provider_params, body, url):
|
||||
def reCaptcha_Challenge_Response(self, provider, provider_params, body, url):
|
||||
try:
|
||||
payload = re.search(
|
||||
r'(name="r"\svalue="(?P<r>\S+)"|).*?challenge-form" action="(?P<challengeUUID>\S+)".*?'
|
||||
@@ -330,7 +362,7 @@ class CloudScraper(Session):
|
||||
).groupdict()
|
||||
except (AttributeError):
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_Error_reCaptcha(
|
||||
"Cloudflare reCaptcha detected, unfortunately we can't extract the parameters correctly."
|
||||
)
|
||||
|
||||
@@ -339,7 +371,7 @@ class CloudScraper(Session):
|
||||
'url': '{}://{}{}'.format(
|
||||
hostParsed.scheme,
|
||||
hostParsed.netloc,
|
||||
payload.get('challengeUUID', '')
|
||||
self.unescape(payload.get('challengeUUID', ''))
|
||||
),
|
||||
'data': OrderedDict([
|
||||
('r', payload.get('r', '')),
|
||||
@@ -377,7 +409,7 @@ class CloudScraper(Session):
|
||||
|
||||
if not self.recaptcha or not isinstance(self.recaptcha, dict) or not self.recaptcha.get('provider'):
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_reCaptcha_Provider(
|
||||
"Cloudflare reCaptcha detected, unfortunately you haven't loaded an anti reCaptcha provider "
|
||||
"correctly via the 'recaptcha' parameter."
|
||||
)
|
||||
@@ -413,7 +445,7 @@ class CloudScraper(Session):
|
||||
self.delay = delay
|
||||
except (AttributeError, ValueError):
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Cloudflare IUAM possibility malformed, issue extracing delay value.")
|
||||
raise cloudflare_exceptions.Cloudflare_Error_IUAM("Cloudflare IUAM possibility malformed, issue extracing delay value.")
|
||||
|
||||
sleep(self.delay)
|
||||
|
||||
@@ -473,34 +505,25 @@ class CloudScraper(Session):
|
||||
return challengeSubmitResponse
|
||||
else:
|
||||
cloudflare_kwargs = deepcopy(kwargs)
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{'Referer': challengeSubmitResponse.url}
|
||||
)
|
||||
|
||||
if not urlparse(challengeSubmitResponse.headers['Location']).netloc:
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{'Referer': '{}://{}'.format(urlParsed.scheme, urlParsed.netloc)}
|
||||
)
|
||||
return self.request(
|
||||
resp.request.method,
|
||||
'{}://{}{}'.format(
|
||||
urlParsed.scheme,
|
||||
urlParsed.netloc,
|
||||
challengeSubmitResponse.headers['Location']
|
||||
),
|
||||
**cloudflare_kwargs
|
||||
redirect_location = urljoin(
|
||||
challengeSubmitResponse.url,
|
||||
challengeSubmitResponse.headers['Location']
|
||||
)
|
||||
else:
|
||||
redirectParsed = urlparse(challengeSubmitResponse.headers['Location'])
|
||||
cloudflare_kwargs['headers'] = updateAttr(
|
||||
cloudflare_kwargs,
|
||||
'headers',
|
||||
{'Referer': '{}://{}'.format(redirectParsed.scheme, redirectParsed.netloc)}
|
||||
)
|
||||
return self.request(
|
||||
resp.request.method,
|
||||
challengeSubmitResponse.headers['Location'],
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
redirect_location = challengeSubmitResponse.headers['Location']
|
||||
|
||||
return self.request(
|
||||
resp.request.method,
|
||||
redirect_location,
|
||||
**cloudflare_kwargs
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# We shouldn't be here...
|
||||
@@ -561,7 +584,7 @@ class CloudScraper(Session):
|
||||
break
|
||||
else:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError(
|
||||
raise cloudflare_exceptions.Cloudflare_Error_IUAM(
|
||||
"Unable to find Cloudflare cookies. Does the site actually "
|
||||
"have Cloudflare IUAM (I'm Under Attack Mode) enabled?"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class Cloudflare_Loop_Protection(Exception):
|
||||
"""
|
||||
Raise error for recursive depth protection
|
||||
"""
|
||||
|
||||
|
||||
class Cloudflare_Block(Exception):
|
||||
"""
|
||||
Raise error for Cloudflare 1020 block
|
||||
"""
|
||||
|
||||
|
||||
class Cloudflare_Error_IUAM(Exception):
|
||||
"""
|
||||
Raise error for problem extracting IUAM paramters from Cloudflare payload
|
||||
"""
|
||||
|
||||
|
||||
class Cloudflare_Error_reCaptcha(Exception):
|
||||
"""
|
||||
Raise error for problem extracting reCaptcha paramters from Cloudflare payload
|
||||
"""
|
||||
|
||||
|
||||
class Cloudflare_reCaptcha_Provider(Exception):
|
||||
"""
|
||||
Raise error for reCaptcha from Cloudflare, no provider loaded.
|
||||
"""
|
||||
@@ -0,0 +1,49 @@
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class reCaptcha_Service_Unavailable(Exception):
|
||||
"""
|
||||
Raise error for external services that cannot be reached
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Error_From_API(Exception):
|
||||
"""
|
||||
Raise error for error from API response.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Account_Error(Exception):
|
||||
"""
|
||||
Raise error for reCaptcha provider account problem.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Timeout(Exception):
|
||||
"""
|
||||
Raise error for reCaptcha provider taking too long.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Bad_Parameter(NotImplementedError):
|
||||
"""
|
||||
Raise error for bad or missing Parameter.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Bad_Job_ID(Exception):
|
||||
"""
|
||||
Raise error for invalid job id.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Report_Error(Exception):
|
||||
"""
|
||||
Raise error for reCaptcha provider unable to report bad solve.
|
||||
"""
|
||||
|
||||
|
||||
class reCaptcha_Import_Error(Exception):
|
||||
"""
|
||||
Raise error for reCaptcha, cannot import a module.
|
||||
"""
|
||||
@@ -1,13 +1,17 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import requests
|
||||
import reCaptcha_exceptions
|
||||
|
||||
try:
|
||||
import polling
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Please install the python module 'polling' via pip or download it from https://github.com/justiniso/polling/")
|
||||
raise reCaptcha_exceptions.reCaptcha_Import_Error(
|
||||
"Please install the python module 'polling' via pip or download it from "
|
||||
"https://github.com/justiniso/polling/"
|
||||
)
|
||||
|
||||
from . import reCaptcha
|
||||
|
||||
@@ -24,7 +28,7 @@ class captchaSolver(reCaptcha):
|
||||
@staticmethod
|
||||
def checkErrorStatus(response, request_type):
|
||||
if response.status_code in [500, 502]:
|
||||
raise RuntimeError('2Captcha: Server Side Error {}'.format(response.status_code))
|
||||
raise reCaptcha_exceptions.reCaptcha_Service_Unavailable('2Captcha: Server Side Error {}'.format(response.status_code))
|
||||
|
||||
errors = {
|
||||
'in.php': {
|
||||
@@ -71,16 +75,23 @@ class captchaSolver(reCaptcha):
|
||||
}
|
||||
|
||||
if response.json().get('status') is False and response.json().get('request') in errors.get(request_type):
|
||||
raise RuntimeError('{} {}'.format(response.json().get('request'), errors.get(request_type).get(response.json().get('request'))))
|
||||
raise reCaptcha_exceptions.reCaptcha_Error_From_API(
|
||||
'{} {}'.format(
|
||||
response.json().get('request'),
|
||||
errors.get(request_type).get(response.json().get('request'))
|
||||
)
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def reportJob(self, jobID):
|
||||
if not jobID:
|
||||
raise RuntimeError("2Captcha: Error bad job id to request reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
"2Captcha: Error bad job id to request reCaptcha."
|
||||
)
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.status_code in [200, 303] and response.json().get('status') == 1:
|
||||
if response.ok and response.json().get('status') == 1:
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response, 'res.php')
|
||||
@@ -105,7 +116,9 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return True
|
||||
else:
|
||||
raise RuntimeError("2Captcha: Error - Failed to report bad reCaptcha solve.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Report_Error(
|
||||
"2Captcha: Error - Failed to report bad reCaptcha solve."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -114,7 +127,7 @@ class captchaSolver(reCaptcha):
|
||||
raise RuntimeError("2Captcha: Error bad job id to request reCaptcha.")
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.status_code in [200, 303] and response.json().get('status') == 1:
|
||||
if response.ok and response.json().get('status') == 1:
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response, 'res.php')
|
||||
@@ -139,13 +152,15 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return response.json().get('request')
|
||||
else:
|
||||
raise RuntimeError("2Captcha: Error failed to solve reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"2Captcha: Error failed to solve reCaptcha."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def requestSolve(self, site_url, site_key):
|
||||
def _checkRequest(response):
|
||||
if response.status_code in [200, 303] and response.json().get("status") == 1 and response.json().get('request'):
|
||||
if response.ok and response.json().get("status") == 1 and response.json().get('request'):
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response, 'in.php')
|
||||
@@ -173,7 +188,9 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return response.json().get('request')
|
||||
else:
|
||||
raise RuntimeError('2Captcha: Error no job id was returned.')
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
'2Captcha: Error no job id was returned.'
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -181,7 +198,9 @@ class captchaSolver(reCaptcha):
|
||||
jobID = None
|
||||
|
||||
if not reCaptchaParams.get('api_key'):
|
||||
raise ValueError("2Captcha: Missing api_key parameter.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Parameter(
|
||||
"2Captcha: Missing api_key parameter."
|
||||
)
|
||||
|
||||
self.api_key = reCaptchaParams.get('api_key')
|
||||
|
||||
@@ -196,9 +215,13 @@ class captchaSolver(reCaptcha):
|
||||
if jobID:
|
||||
self.reportJob(jobID)
|
||||
except polling.TimeoutException:
|
||||
raise RuntimeError("2Captcha: reCaptcha solve took to long and also failed reporting the job.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"2Captcha: reCaptcha solve took to long and also failed reporting the job the job id {}.".format(jobID)
|
||||
)
|
||||
|
||||
raise RuntimeError("2Captcha: reCaptcha solve took to long to execute, aborting.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"2Captcha: reCaptcha solve took to long to execute job id {}, aborting.".format(jobID)
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import requests
|
||||
import reCaptcha_exceptions
|
||||
|
||||
try:
|
||||
import polling
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.tracebacklimit = 0
|
||||
raise reCaptcha_exceptions.reCaptcha_Import_Error(
|
||||
"Please install the python module 'polling' via pip or download it from "
|
||||
"https://github.com/justiniso/polling/"
|
||||
)
|
||||
|
||||
from . import reCaptcha
|
||||
|
||||
|
||||
class captchaSolver(reCaptcha):
|
||||
|
||||
def __init__(self):
|
||||
super(captchaSolver, self).__init__('9kw')
|
||||
self.host = 'https://www.9kw.eu/index.cgi'
|
||||
self.maxtimeout = 180
|
||||
self.session = requests.Session()
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def checkErrorStatus(response):
|
||||
if response.status_code in [500, 502]:
|
||||
raise reCaptcha_exceptions.reCaptcha_Service_Unavailable(
|
||||
'9kw: Server Side Error {}'.format(response.status_code)
|
||||
)
|
||||
|
||||
error_codes = {
|
||||
1: 'No API Key available.',
|
||||
2: 'No API key found.',
|
||||
3: 'No active API key found.',
|
||||
4: 'API Key has been disabled by the operator. ',
|
||||
5: 'No user found.',
|
||||
6: 'No data found.',
|
||||
7: 'Found No ID.',
|
||||
8: 'found No captcha.',
|
||||
9: 'No image found.',
|
||||
10: 'Image size not allowed.',
|
||||
11: 'credit is not sufficient.',
|
||||
12: 'what was done.',
|
||||
13: 'No answer contain.',
|
||||
14: 'Captcha already been answered.',
|
||||
15: 'Captcha to quickly filed.',
|
||||
16: 'JD check active.',
|
||||
17: 'Unknown problem.',
|
||||
18: 'Found No ID.',
|
||||
19: 'Incorrect answer.',
|
||||
20: 'Do not timely filed (Incorrect UserID).',
|
||||
21: 'Link not allowed.',
|
||||
22: 'Prohibited submit.',
|
||||
23: 'Entering prohibited.',
|
||||
24: 'Too little credit.',
|
||||
25: 'No entry found.',
|
||||
26: 'No Conditions accepted.',
|
||||
27: 'No coupon code found in the database.',
|
||||
28: 'Already unused voucher code.',
|
||||
29: 'maxTimeout under 60 seconds.',
|
||||
30: 'User not found.',
|
||||
31: 'An account is not yet 24 hours in system.',
|
||||
32: 'An account does not have the full rights.',
|
||||
33: 'Plugin needed a update.',
|
||||
34: 'No HTTPS allowed.',
|
||||
35: 'No HTTP allowed.',
|
||||
36: 'Source not allowed.',
|
||||
37: 'Transfer denied.',
|
||||
38: 'Incorrect answer without space',
|
||||
39: 'Incorrect answer with space',
|
||||
40: 'Incorrect answer with not only numbers',
|
||||
41: 'Incorrect answer with not only A-Z, a-z',
|
||||
42: 'Incorrect answer with not only 0-9, A-Z, a-z',
|
||||
43: 'Incorrect answer with not only [0-9,- ]',
|
||||
44: 'Incorrect answer with not only [0-9A-Za-z,- ]',
|
||||
45: 'Incorrect answer with not only coordinates',
|
||||
46: 'Incorrect answer with not only multiple coordinates',
|
||||
47: 'Incorrect answer with not only data',
|
||||
48: 'Incorrect answer with not only rotate number',
|
||||
49: 'Incorrect answer with not only text',
|
||||
50: 'Incorrect answer with not only text and too short',
|
||||
51: 'Incorrect answer with not enough chars',
|
||||
52: 'Incorrect answer with too many chars',
|
||||
53: 'Incorrect answer without no or yes',
|
||||
54: 'Assignment was not found.'
|
||||
}
|
||||
|
||||
if response.text.startswith('{'):
|
||||
if response.json().get('error'):
|
||||
raise reCaptcha_exceptions.reCaptcha_Error_From_API(error_codes.get(int(response.json().get('error'))))
|
||||
else:
|
||||
error_code = int(re.search(r'^00(?P<error_code>\d+)', response.text).groupdict().get('error_code', 0))
|
||||
if error_code:
|
||||
raise reCaptcha_exceptions.reCaptcha_Error_From_API(error_codes.get(error_code))
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def requestJob(self, jobID):
|
||||
if not jobID:
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
"9kw: Error bad job id to request reCaptcha against."
|
||||
)
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.ok and response.json().get('answer') != 'NO DATA':
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response)
|
||||
|
||||
return None
|
||||
|
||||
response = polling.poll(
|
||||
lambda: self.session.get(
|
||||
self.host,
|
||||
params={
|
||||
'apikey': self.api_key,
|
||||
'action': 'usercaptchacorrectdata',
|
||||
'id': jobID,
|
||||
'info': 1,
|
||||
'json': 1
|
||||
}
|
||||
),
|
||||
check_success=_checkRequest,
|
||||
step=10,
|
||||
timeout=(self.maxtimeout + 10)
|
||||
)
|
||||
|
||||
if response:
|
||||
return response.json().get('answer')
|
||||
else:
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout("9kw: Error failed to solve reCaptcha.")
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def requestSolve(self, site_url, site_key):
|
||||
def _checkRequest(response):
|
||||
if response.ok and response.text.startswith('{') and response.json().get('captchaid'):
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response)
|
||||
|
||||
return None
|
||||
|
||||
response = polling.poll(
|
||||
lambda: self.session.post(
|
||||
self.host,
|
||||
data={
|
||||
'apikey': self.api_key,
|
||||
'action': 'usercaptchaupload',
|
||||
'interactive': 1,
|
||||
'file-upload-01': site_key,
|
||||
'oldsource': 'recaptchav2',
|
||||
'pageurl': site_url,
|
||||
'maxtimeout': self.maxtimeout,
|
||||
'json': 1
|
||||
},
|
||||
allow_redirects=False
|
||||
),
|
||||
check_success=_checkRequest,
|
||||
step=5,
|
||||
timeout=(self.maxtimeout + 10)
|
||||
)
|
||||
|
||||
if response:
|
||||
return response.json().get('captchaid')
|
||||
else:
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID('9kw: Error no valid job id was returned.')
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def getCaptchaAnswer(self, site_url, site_key, reCaptchaParams):
|
||||
jobID = None
|
||||
|
||||
if not reCaptchaParams.get('api_key'):
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Parameter("9kw: Missing api_key parameter.")
|
||||
|
||||
self.api_key = reCaptchaParams.get('api_key')
|
||||
|
||||
if reCaptchaParams.get('maxtimeout'):
|
||||
self.maxtimeout = reCaptchaParams.get('maxtimeout')
|
||||
|
||||
if reCaptchaParams.get('proxy'):
|
||||
self.session.proxies = reCaptchaParams.get('proxies')
|
||||
|
||||
try:
|
||||
jobID = self.requestSolve(site_url, site_key)
|
||||
return self.requestJob(jobID)
|
||||
except polling.TimeoutException:
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"9kw: reCaptcha solve took to long to execute 'captchaid' {}, aborting.".format(jobID)
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
captchaSolver()
|
||||
@@ -1,12 +1,16 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import reCaptcha_exceptions
|
||||
|
||||
try:
|
||||
from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask
|
||||
except ImportError:
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Please install the python module 'python_anticaptcha' via pip or download it from https://github.com/ad-m/python-anticaptcha")
|
||||
raise reCaptcha_exceptions.reCaptcha_Import_Error(
|
||||
"Please install the python module 'python_anticaptcha' via pip or download it from "
|
||||
"https://github.com/ad-m/python-anticaptcha"
|
||||
)
|
||||
|
||||
from . import reCaptcha
|
||||
|
||||
@@ -16,9 +20,11 @@ class captchaSolver(reCaptcha):
|
||||
def __init__(self):
|
||||
super(captchaSolver, self).__init__('anticaptcha')
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def getCaptchaAnswer(self, site_url, site_key, reCaptchaParams):
|
||||
if not reCaptchaParams.get('api_key'):
|
||||
raise ValueError("reCaptcha provider 'anticaptcha' was not provided an 'api_key' parameter.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Parameter("anticaptcha: Missing api_key parameter.")
|
||||
|
||||
client = AnticaptchaClient(reCaptchaParams.get('api_key'))
|
||||
|
||||
@@ -29,10 +35,14 @@ class captchaSolver(reCaptcha):
|
||||
|
||||
if not hasattr(client, 'createTaskSmee'):
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Please upgrade 'python_anticaptcha' via pip or download it from https://github.com/ad-m/python-anticaptcha")
|
||||
raise reCaptcha_exceptions.reCaptcha_Import_Error(
|
||||
"Please upgrade 'python_anticaptcha' via pip or download it from https://github.com/ad-m/python-anticaptcha"
|
||||
)
|
||||
|
||||
job = client.createTaskSmee(task)
|
||||
return job.get_solution_response()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
captchaSolver()
|
||||
|
||||
@@ -2,13 +2,17 @@ from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
import requests
|
||||
import reCaptcha_exceptions
|
||||
|
||||
try:
|
||||
import polling
|
||||
except ImportError:
|
||||
import sys
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Please install the python module 'polling' via pip or download it from https://github.com/justiniso/polling/")
|
||||
raise reCaptcha_exceptions.reCaptcha_Import_Error(
|
||||
"Please install the python module 'polling' via pip or download it from "
|
||||
"https://github.com/justiniso/polling/"
|
||||
)
|
||||
|
||||
from . import reCaptcha
|
||||
|
||||
@@ -20,7 +24,7 @@ class captchaSolver(reCaptcha):
|
||||
self.host = 'http://api.dbcapi.me/api'
|
||||
self.session = requests.Session()
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@staticmethod
|
||||
def checkErrorStatus(response):
|
||||
@@ -34,21 +38,21 @@ class captchaSolver(reCaptcha):
|
||||
)
|
||||
|
||||
if response.status_code in errors:
|
||||
raise RuntimeError(errors.get(response.status_code))
|
||||
raise reCaptcha_exceptions.reCaptcha_Service_Unavailable(errors.get(response.status_code))
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def login(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.status_code == 200:
|
||||
if response.ok:
|
||||
if response.json().get('is_banned'):
|
||||
raise RuntimeError('DeathByCaptcha: Your account is banned.')
|
||||
raise reCaptcha_exceptions.reCaptcha_Account_Error('DeathByCaptcha: Your account is banned.')
|
||||
|
||||
if response.json().get('balanace') == 0:
|
||||
raise RuntimeError('DeathByCaptcha: insufficient credits.')
|
||||
raise reCaptcha_exceptions.reCaptcha_Account_Error('DeathByCaptcha: insufficient credits.')
|
||||
|
||||
return response
|
||||
|
||||
@@ -72,11 +76,13 @@ class captchaSolver(reCaptcha):
|
||||
|
||||
self.debugRequest(response)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def reportJob(self, jobID):
|
||||
if not jobID:
|
||||
raise RuntimeError("DeathByCaptcha: Error bad job id to report failed reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
"DeathByCaptcha: Error bad job id to report failed reCaptcha."
|
||||
)
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.status_code == 200:
|
||||
@@ -103,16 +109,20 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return True
|
||||
else:
|
||||
raise RuntimeError("DeathByCaptcha: Error report failed reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Report_Error(
|
||||
"DeathByCaptcha: Error report failed reCaptcha."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def requestJob(self, jobID):
|
||||
if not jobID:
|
||||
raise RuntimeError("DeathByCaptcha: Error bad job id to request reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
"DeathByCaptcha: Error bad job id to request reCaptcha."
|
||||
)
|
||||
|
||||
def _checkRequest(response):
|
||||
if response.status_code in [200, 303] and response.json().get('text'):
|
||||
if response.ok and response.json().get('text'):
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response)
|
||||
@@ -132,13 +142,15 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return response.json().get('text')
|
||||
else:
|
||||
raise RuntimeError("DeathByCaptcha: Error failed to solve reCaptcha.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"DeathByCaptcha: Error failed to solve reCaptcha."
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def requestSolve(self, site_url, site_key):
|
||||
def _checkRequest(response):
|
||||
if response.status_code in [200, 303] and response.json().get("is_correct") and response.json().get('captcha'):
|
||||
if response.ok and response.json().get("is_correct") and response.json().get('captcha'):
|
||||
return response
|
||||
|
||||
self.checkErrorStatus(response)
|
||||
@@ -168,16 +180,20 @@ class captchaSolver(reCaptcha):
|
||||
if response:
|
||||
return response.json().get('captcha')
|
||||
else:
|
||||
raise RuntimeError('DeathByCaptcha: Error no job id was returned.')
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Job_ID(
|
||||
'DeathByCaptcha: Error no job id was returned.'
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
def getCaptchaAnswer(self, site_url, site_key, reCaptchaParams):
|
||||
jobID = None
|
||||
|
||||
for param in ['username', 'password']:
|
||||
if not reCaptchaParams.get(param):
|
||||
raise ValueError("DeathByCaptcha: Missing '{}' parameter.".format(param))
|
||||
raise reCaptcha_exceptions.reCaptcha_Bad_Parameter(
|
||||
"DeathByCaptcha: Missing '{}' parameter.".format(param)
|
||||
)
|
||||
setattr(self, param, reCaptchaParams.get(param))
|
||||
|
||||
if reCaptchaParams.get('proxy'):
|
||||
@@ -191,9 +207,13 @@ class captchaSolver(reCaptcha):
|
||||
if jobID:
|
||||
self.reportJob(jobID)
|
||||
except polling.TimeoutException:
|
||||
raise RuntimeError("DeathByCaptcha: reCaptcha solve took to long and also failed reporting the job.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"DeathByCaptcha: reCaptcha solve took to long and also failed reporting the job id {}.".format(jobID)
|
||||
)
|
||||
|
||||
raise RuntimeError("DeathByCaptcha: reCaptcha solve took to long to execute, aborting.")
|
||||
raise reCaptcha_exceptions.reCaptcha_Timeout(
|
||||
"DeathByCaptcha: reCaptcha solve took to long to execute job id {}, aborting.".format(jobID)
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------- #
|
||||
|
||||
@@ -47,7 +47,7 @@ class User_Agent():
|
||||
for browser in user_agents:
|
||||
for release in user_agents[browser]['releases']:
|
||||
for platform in ['mobile', 'desktop']:
|
||||
if re.search(self.custom, ' '.join(user_agents[browser]['releases'][release]['User-Agent'][platform])):
|
||||
if re.search(re.escape(self.custom), ' '.join(user_agents[browser]['releases'][release]['User-Agent'][platform])):
|
||||
self.browser = browser
|
||||
self.loadHeaders(user_agents, release)
|
||||
self.headers['User-Agent'] = self.custom
|
||||
@@ -74,10 +74,11 @@ class User_Agent():
|
||||
sys.tracebacklimit = 0
|
||||
raise RuntimeError("Sorry you can't have mobile and desktop disabled at the same time.")
|
||||
|
||||
user_agents = json.load(
|
||||
open(os.path.join(os.path.dirname(__file__), 'browsers.json'), 'r'),
|
||||
object_pairs_hook=OrderedDict
|
||||
)
|
||||
with open(os.path.join(os.path.dirname(__file__), 'browsers.json'), 'r') as fp:
|
||||
user_agents = json.load(
|
||||
fp,
|
||||
object_pairs_hook=OrderedDict
|
||||
)
|
||||
|
||||
if self.custom:
|
||||
if not self.tryMatchCustom(user_agents):
|
||||
|
||||
@@ -13,9 +13,14 @@
|
||||
"TLS_CHACHA20_POLY1305_SHA256",
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
"ECDHE-RSA-AES256-GCM-SHA384",
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305",
|
||||
"ECDHE-RSA-CHACHA20-POLY1305",
|
||||
"ECDHE-RSA-AES256-SHA",
|
||||
"AES128-GCM-SHA256",
|
||||
"AES256-GCM-SHA384",
|
||||
"AES128-SHA",
|
||||
"AES256-SHA"
|
||||
],
|
||||
"releases": {
|
||||
@@ -12814,10 +12819,15 @@
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305",
|
||||
"ECDHE-RSA-CHACHA20-POLY1305",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
"ECDHE-RSA-AES256-GCM-SHA384",
|
||||
"ECDHE-ECDSA-AES256-SHA",
|
||||
"ECDHE-ECDSA-AES128-SHA",
|
||||
"ECDHE-RSA-AES256-SHA",
|
||||
"DHE-RSA-AES128-SHA",
|
||||
"DHE-RSA-AES256-SHA",
|
||||
"AES128-SHA",
|
||||
"AES256-SHA"
|
||||
],
|
||||
"releases": {
|
||||
|
||||
@@ -28,6 +28,7 @@ from __future__ import division
|
||||
from __future__ import absolute_import
|
||||
from future.builtins import range
|
||||
from future.builtins import bytes
|
||||
from future.builtins import str
|
||||
|
||||
__all__ = [
|
||||
'body_decode',
|
||||
|
||||
Regular → Executable
@@ -11,9 +11,9 @@ an application may want to handle an exception like a regular
|
||||
response.
|
||||
"""
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from ... import standard_library
|
||||
from future import standard_library
|
||||
|
||||
from . import response as urllib_response
|
||||
from future.backports.urllib import response as urllib_response
|
||||
|
||||
|
||||
__all__ = ['URLError', 'HTTPError', 'ContentTooShortError']
|
||||
|
||||
@@ -87,7 +87,7 @@ def clear_cache():
|
||||
# decoding and encoding. If valid use cases are
|
||||
# presented, we may relax this by using latin-1
|
||||
# decoding internally for 3.3
|
||||
_implicit_encoding = 'utf8'
|
||||
_implicit_encoding = 'ascii'
|
||||
_implicit_errors = 'strict'
|
||||
|
||||
def _noop(obj):
|
||||
@@ -122,7 +122,7 @@ class _ResultMixinStr(object):
|
||||
"""Standard approach to encoding parsed results from str to bytes"""
|
||||
__slots__ = ()
|
||||
|
||||
def encode(self, encoding='utf8', errors='strict'):
|
||||
def encode(self, encoding='ascii', errors='strict'):
|
||||
return self._encoded_counterpart(*(x.encode(encoding, errors) for x in self))
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ class _ResultMixinBytes(object):
|
||||
"""Standard approach to decoding parsed results from bytes to str"""
|
||||
__slots__ = ()
|
||||
|
||||
def decode(self, encoding='utf8', errors='strict'):
|
||||
def decode(self, encoding='ascii', errors='strict'):
|
||||
return self._decoded_counterpart(*(x.decode(encoding, errors) for x in self))
|
||||
|
||||
|
||||
@@ -730,7 +730,7 @@ def quote_from_bytes(bs, safe='/'):
|
||||
###
|
||||
if isinstance(safe, str):
|
||||
# Normalize 'safe' by converting to bytes and removing non-ASCII chars
|
||||
safe = str(safe).encode('utf8', 'ignore')
|
||||
safe = str(safe).encode('ascii', 'ignore')
|
||||
else:
|
||||
### For Python-Future:
|
||||
safe = bytes(safe)
|
||||
|
||||
@@ -827,7 +827,7 @@ class ProxyHandler(BaseHandler):
|
||||
if user and password:
|
||||
user_pass = '%s:%s' % (unquote(user),
|
||||
unquote(password))
|
||||
creds = base64.b64encode(user_pass.encode()).decode("utf8")
|
||||
creds = base64.b64encode(user_pass.encode()).decode("ascii")
|
||||
req.add_header('Proxy-authorization', 'Basic ' + creds)
|
||||
hostport = unquote(hostport)
|
||||
req.set_proxy(hostport, proxy_type)
|
||||
@@ -977,7 +977,7 @@ class AbstractBasicAuthHandler(object):
|
||||
user, pw = self.passwd.find_user_password(realm, host)
|
||||
if pw is not None:
|
||||
raw = "%s:%s" % (user, pw)
|
||||
auth = "Basic " + base64.b64encode(raw.encode()).decode("utf8")
|
||||
auth = "Basic " + base64.b64encode(raw.encode()).decode("ascii")
|
||||
if req.headers.get(self.auth_header, None) == auth:
|
||||
return None
|
||||
req.add_unredirected_header(self.auth_header, auth)
|
||||
@@ -1080,7 +1080,7 @@ class AbstractDigestAuthHandler(object):
|
||||
# authentication, and to provide some message integrity protection.
|
||||
# This isn't a fabulous effort, but it's probably Good Enough.
|
||||
s = "%s:%s:%s:" % (self.nonce_count, nonce, time.ctime())
|
||||
b = s.encode("utf8") + _randombytes(8)
|
||||
b = s.encode("ascii") + _randombytes(8)
|
||||
dig = hashlib.sha1(b).hexdigest()
|
||||
return dig[:16]
|
||||
|
||||
@@ -1147,9 +1147,9 @@ class AbstractDigestAuthHandler(object):
|
||||
def get_algorithm_impls(self, algorithm):
|
||||
# lambdas assume digest modules are imported at the top level
|
||||
if algorithm == 'MD5':
|
||||
H = lambda x: hashlib.md5(x.encode("utf8")).hexdigest()
|
||||
H = lambda x: hashlib.md5(x.encode("ascii")).hexdigest()
|
||||
elif algorithm == 'SHA':
|
||||
H = lambda x: hashlib.sha1(x.encode("utf8")).hexdigest()
|
||||
H = lambda x: hashlib.sha1(x.encode("ascii")).hexdigest()
|
||||
# XXX MD5-sess
|
||||
KD = lambda s, d: H("%s:%s" % (s, d))
|
||||
return H, KD
|
||||
@@ -1829,13 +1829,13 @@ class URLopener(object):
|
||||
|
||||
if proxy_passwd:
|
||||
proxy_passwd = unquote(proxy_passwd)
|
||||
proxy_auth = base64.b64encode(proxy_passwd.encode()).decode('utf8')
|
||||
proxy_auth = base64.b64encode(proxy_passwd.encode()).decode('ascii')
|
||||
else:
|
||||
proxy_auth = None
|
||||
|
||||
if user_passwd:
|
||||
user_passwd = unquote(user_passwd)
|
||||
auth = base64.b64encode(user_passwd.encode()).decode('utf8')
|
||||
auth = base64.b64encode(user_passwd.encode()).decode('ascii')
|
||||
else:
|
||||
auth = None
|
||||
http_conn = connection_factory(host)
|
||||
@@ -2040,7 +2040,7 @@ class URLopener(object):
|
||||
msg.append('Content-type: %s' % type)
|
||||
if encoding == 'base64':
|
||||
# XXX is this encoding/decoding ok?
|
||||
data = base64.decodebytes(data.encode('utf8')).decode('latin-1')
|
||||
data = base64.decodebytes(data.encode('ascii')).decode('latin-1')
|
||||
else:
|
||||
data = unquote(data)
|
||||
msg.append('Content-Length: %d' % len(data))
|
||||
@@ -2498,17 +2498,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings):
|
||||
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
try:
|
||||
from _scproxy import _get_proxy_settings, _get_proxies
|
||||
except:
|
||||
try:
|
||||
# By default use environment variables
|
||||
_get_proxy_settings = getproxies_environment
|
||||
_get_proxies = proxy_bypass_environment
|
||||
getproxies = getproxies_environment
|
||||
proxy_bypass = proxy_bypass_environment
|
||||
except:
|
||||
pass
|
||||
from _scproxy import _get_proxy_settings, _get_proxies
|
||||
|
||||
def proxy_bypass_macosx_sysconf(host):
|
||||
proxy_settings = _get_proxy_settings()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from ...builtins import str
|
||||
from future.builtins import str
|
||||
""" robotparser.py
|
||||
|
||||
Copyright (C) 2000 Bastian Kleineidam
|
||||
@@ -13,8 +13,8 @@ from ...builtins import str
|
||||
"""
|
||||
|
||||
# Was: import urllib.parse, urllib.request
|
||||
from .. import urllib
|
||||
from . import parse as _parse, request as _request
|
||||
from future.backports import urllib
|
||||
from future.backports.urllib import parse as _parse, request as _request
|
||||
urllib.parse = _parse
|
||||
urllib.request = _request
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
``python-future``: pure Python implementation of Python 3 round().
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from future.utils import PYPY, PY26, bind_method
|
||||
|
||||
# Use the decimal module for simplicity of implementation (and
|
||||
@@ -29,8 +30,6 @@ def newround(number, ndigits=None):
|
||||
if hasattr(number, '__round__'):
|
||||
return number.__round__(ndigits)
|
||||
|
||||
if ndigits < 0:
|
||||
raise NotImplementedError('negative ndigits not supported yet')
|
||||
exponent = Decimal('10') ** (-ndigits)
|
||||
|
||||
if PYPY:
|
||||
@@ -42,15 +41,19 @@ def newround(number, ndigits=None):
|
||||
d = number
|
||||
else:
|
||||
if not PY26:
|
||||
d = Decimal.from_float(number).quantize(exponent,
|
||||
rounding=ROUND_HALF_EVEN)
|
||||
d = Decimal.from_float(number)
|
||||
else:
|
||||
d = from_float_26(number).quantize(exponent, rounding=ROUND_HALF_EVEN)
|
||||
d = from_float_26(number)
|
||||
|
||||
if ndigits < 0:
|
||||
result = newround(d / exponent) * exponent
|
||||
else:
|
||||
result = d.quantize(exponent, rounding=ROUND_HALF_EVEN)
|
||||
|
||||
if return_int:
|
||||
return int(d)
|
||||
return int(result)
|
||||
else:
|
||||
return float(d)
|
||||
return float(result)
|
||||
|
||||
|
||||
### From Python 2.7's decimal.py. Only needed to support Py2.6:
|
||||
|
||||
@@ -10,3 +10,9 @@ else:
|
||||
except ImportError:
|
||||
raise ImportError('The FileDialog module is missing. Does your Py2 '
|
||||
'installation include tkinter?')
|
||||
|
||||
try:
|
||||
from tkFileDialog import *
|
||||
except ImportError:
|
||||
raise ImportError('The tkFileDialog module is missing. Does your Py2 '
|
||||
'installation include tkinter?')
|
||||
|
||||
@@ -450,63 +450,35 @@ def install_aliases():
|
||||
# if hasattr(install_aliases, 'run_already'):
|
||||
# return
|
||||
for (newmodname, newobjname, oldmodname, oldobjname) in MOVES:
|
||||
try:
|
||||
__import__(newmodname)
|
||||
# We look up the module in sys.modules because __import__ just returns the
|
||||
# top-level package:
|
||||
newmod = sys.modules[newmodname]
|
||||
# newmod.__future_module__ = True
|
||||
__import__(newmodname)
|
||||
# We look up the module in sys.modules because __import__ just returns the
|
||||
# top-level package:
|
||||
newmod = sys.modules[newmodname]
|
||||
# newmod.__future_module__ = True
|
||||
|
||||
__import__(oldmodname)
|
||||
oldmod = sys.modules[oldmodname]
|
||||
__import__(oldmodname)
|
||||
oldmod = sys.modules[oldmodname]
|
||||
|
||||
obj = getattr(oldmod, oldobjname)
|
||||
setattr(newmod, newobjname, obj)
|
||||
except:
|
||||
try:
|
||||
flog.warning('*** FUTURE ERROR in module %s %s ' % (str(oldmod), str(oldobjname)))
|
||||
except:
|
||||
pass
|
||||
obj = getattr(oldmod, oldobjname)
|
||||
setattr(newmod, newobjname, obj)
|
||||
|
||||
# Hack for urllib so it appears to have the same structure on Py2 as on Py3
|
||||
try:
|
||||
import urllib
|
||||
from future.backports.urllib import response
|
||||
urllib.response = response
|
||||
sys.modules['urllib.response'] = response
|
||||
from future.backports.urllib import parse
|
||||
urllib.parse = parse
|
||||
sys.modules['urllib.parse'] = parse
|
||||
from future.backports.urllib import error
|
||||
urllib.error = error
|
||||
sys.modules['urllib.error'] = error
|
||||
except ImportError:
|
||||
try:
|
||||
flog.warning('*** FUTURE ERROR importing URLLIB.response, parse, error')
|
||||
urllib.response = urllib
|
||||
sys.modules['urllib.response'] = urllib
|
||||
urllib.parse = urllib
|
||||
sys.modules['urllib.parse'] = urllib
|
||||
urllib.error = urllib
|
||||
sys.modules['urllib.error'] = urllib
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
from future.backports.urllib import request
|
||||
urllib.request = request
|
||||
sys.modules['urllib.request'] = request
|
||||
from future.backports.urllib import robotparser
|
||||
urllib.robotparser = robotparser
|
||||
sys.modules['urllib.robotparser'] = robotparser
|
||||
except ImportError:
|
||||
try:
|
||||
flog.warning('*** FUTURE ERROR importing URLLIB.Request')
|
||||
urllib.request = urllib
|
||||
sys.modules['urllib.request'] = urllib
|
||||
urllib.robotparser = urllib
|
||||
sys.modules['urllib.robotparser'] = urllib
|
||||
except:
|
||||
pass
|
||||
import urllib
|
||||
from future.backports.urllib import request
|
||||
from future.backports.urllib import response
|
||||
from future.backports.urllib import parse
|
||||
from future.backports.urllib import error
|
||||
from future.backports.urllib import robotparser
|
||||
urllib.request = request
|
||||
urllib.response = response
|
||||
urllib.parse = parse
|
||||
urllib.error = error
|
||||
urllib.robotparser = robotparser
|
||||
sys.modules['urllib.request'] = request
|
||||
sys.modules['urllib.response'] = response
|
||||
sys.modules['urllib.parse'] = parse
|
||||
sys.modules['urllib.error'] = error
|
||||
sys.modules['urllib.robotparser'] = robotparser
|
||||
|
||||
# Patch the test module so it appears to have the same structure on Py2 as on Py3
|
||||
try:
|
||||
@@ -518,11 +490,8 @@ def install_aliases():
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
test.support = support
|
||||
sys.modules['test.support'] = support
|
||||
except:
|
||||
pass
|
||||
test.support = support
|
||||
sys.modules['test.support'] = support
|
||||
|
||||
# Patch the dbm module so it appears to have the same structure on Py2 as on Py3
|
||||
try:
|
||||
@@ -530,26 +499,23 @@ def install_aliases():
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from future.moves.dbm import dumb
|
||||
dbm.dumb = dumb
|
||||
sys.modules['dbm.dumb'] = dumb
|
||||
try:
|
||||
from future.moves.dbm import dumb
|
||||
dbm.dumb = dumb
|
||||
sys.modules['dbm.dumb'] = dumb
|
||||
try:
|
||||
from future.moves.dbm import gnu
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
dbm.gnu = gnu
|
||||
sys.modules['dbm.gnu'] = gnu
|
||||
try:
|
||||
from future.moves.dbm import ndbm
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
dbm.ndbm = ndbm
|
||||
sys.modules['dbm.ndbm'] = ndbm
|
||||
except:
|
||||
flog.warning('*** FUTURE ERROR importing MOVES.dbm')
|
||||
from future.moves.dbm import gnu
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
dbm.gnu = gnu
|
||||
sys.modules['dbm.gnu'] = gnu
|
||||
try:
|
||||
from future.moves.dbm import ndbm
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
dbm.ndbm = ndbm
|
||||
sys.modules['dbm.ndbm'] = ndbm
|
||||
|
||||
# install_aliases.run_already = True
|
||||
|
||||
|
||||
@@ -527,9 +527,9 @@ def implements_iterator(cls):
|
||||
return cls
|
||||
|
||||
if PY3:
|
||||
get_next = lambda x: x.next
|
||||
else:
|
||||
get_next = lambda x: x.__next__
|
||||
else:
|
||||
get_next = lambda x: x.next
|
||||
|
||||
|
||||
def encode_filename(filename):
|
||||
|
||||
+6
-2
@@ -11,7 +11,10 @@ import re
|
||||
import os
|
||||
import sys
|
||||
import urllib
|
||||
import urlparse
|
||||
try:
|
||||
import urlparse
|
||||
except:
|
||||
import urllib.parse as urlparse
|
||||
import datetime
|
||||
import time
|
||||
import traceback
|
||||
@@ -161,7 +164,8 @@ def update_title(item):
|
||||
item.channel = new_item.channel #Restuaramos el nombre del canal, por si lo habíamos cambiado
|
||||
if item.tmdb_stat == True:
|
||||
if new_item.contentSerieName: #Si es serie...
|
||||
if config.get_setting("filter_languages", item.channel) >= 0:
|
||||
filter_languages = config.get_setting("filter_languages", item.channel)
|
||||
if filter_languages and filter_languages >= 0:
|
||||
item.title_from_channel = new_item.contentSerieName #Guardo el título incial para Filtertools
|
||||
item.contentSerieName = new_item.contentSerieName #Guardo el título incial para Filtertools
|
||||
else:
|
||||
|
||||
@@ -9,7 +9,6 @@ has been tested with Python2.7 and Python 3.4.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -238,7 +238,15 @@ class socksocket(socket.socket):
|
||||
headers - Additional or modified headers for the proxy connect
|
||||
request.
|
||||
"""
|
||||
self.__proxy = (proxytype, addr, port, rdns, username, password, headers)
|
||||
self.__proxy = (
|
||||
proxytype,
|
||||
addr,
|
||||
port,
|
||||
rdns,
|
||||
username.encode() if username else None,
|
||||
password.encode() if password else None,
|
||||
headers,
|
||||
)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
"""Utilities for certificate management."""
|
||||
|
||||
import os
|
||||
|
||||
certifi_available = False
|
||||
certifi_where = None
|
||||
try:
|
||||
from certifi import where as certifi_where
|
||||
certifi_available = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
custom_ca_locater_available = False
|
||||
custom_ca_locater_where = None
|
||||
try:
|
||||
from ca_certs_locater import get as custom_ca_locater_where
|
||||
custom_ca_locater_available = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
BUILTIN_CA_CERTS = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "cacerts.txt"
|
||||
)
|
||||
|
||||
|
||||
def where():
|
||||
env = os.environ.get("HTTPLIB2_CA_CERTS")
|
||||
if env is not None:
|
||||
if os.path.isfile(env):
|
||||
return env
|
||||
else:
|
||||
raise RuntimeError("Environment variable HTTPLIB2_CA_CERTS not a valid file")
|
||||
if custom_ca_locater_available:
|
||||
return custom_ca_locater_where()
|
||||
if certifi_available:
|
||||
return certifi_where()
|
||||
return BUILTIN_CA_CERTS
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(where())
|
||||
@@ -0,0 +1,124 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Converts an IRI to a URI."""
|
||||
|
||||
__author__ = "Joe Gregorio (joe@bitworking.org)"
|
||||
__copyright__ = "Copyright 2006, Joe Gregorio"
|
||||
__contributors__ = []
|
||||
__version__ = "1.0.0"
|
||||
__license__ = "MIT"
|
||||
|
||||
import urllib.parse
|
||||
|
||||
# Convert an IRI to a URI following the rules in RFC 3987
|
||||
#
|
||||
# The characters we need to enocde and escape are defined in the spec:
|
||||
#
|
||||
# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
|
||||
# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
|
||||
# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
|
||||
# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
|
||||
# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
|
||||
# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
|
||||
# / %xD0000-DFFFD / %xE1000-EFFFD
|
||||
|
||||
escape_range = [
|
||||
(0xA0, 0xD7FF),
|
||||
(0xE000, 0xF8FF),
|
||||
(0xF900, 0xFDCF),
|
||||
(0xFDF0, 0xFFEF),
|
||||
(0x10000, 0x1FFFD),
|
||||
(0x20000, 0x2FFFD),
|
||||
(0x30000, 0x3FFFD),
|
||||
(0x40000, 0x4FFFD),
|
||||
(0x50000, 0x5FFFD),
|
||||
(0x60000, 0x6FFFD),
|
||||
(0x70000, 0x7FFFD),
|
||||
(0x80000, 0x8FFFD),
|
||||
(0x90000, 0x9FFFD),
|
||||
(0xA0000, 0xAFFFD),
|
||||
(0xB0000, 0xBFFFD),
|
||||
(0xC0000, 0xCFFFD),
|
||||
(0xD0000, 0xDFFFD),
|
||||
(0xE1000, 0xEFFFD),
|
||||
(0xF0000, 0xFFFFD),
|
||||
(0x100000, 0x10FFFD),
|
||||
]
|
||||
|
||||
|
||||
def encode(c):
|
||||
retval = c
|
||||
i = ord(c)
|
||||
for low, high in escape_range:
|
||||
if i < low:
|
||||
break
|
||||
if i >= low and i <= high:
|
||||
retval = "".join(["%%%2X" % o for o in c.encode("utf-8")])
|
||||
break
|
||||
return retval
|
||||
|
||||
|
||||
def iri2uri(uri):
|
||||
"""Convert an IRI to a URI. Note that IRIs must be
|
||||
passed in a unicode strings. That is, do not utf-8 encode
|
||||
the IRI before passing it into the function."""
|
||||
if isinstance(uri, str):
|
||||
(scheme, authority, path, query, fragment) = urllib.parse.urlsplit(uri)
|
||||
authority = authority.encode("idna").decode("utf-8")
|
||||
# For each character in 'ucschar' or 'iprivate'
|
||||
# 1. encode as utf-8
|
||||
# 2. then %-encode each octet of that utf-8
|
||||
uri = urllib.parse.urlunsplit((scheme, authority, path, query, fragment))
|
||||
uri = "".join([encode(c) for c in uri])
|
||||
return uri
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
def test_uris(self):
|
||||
"""Test that URIs are invariant under the transformation."""
|
||||
invariant = [
|
||||
"ftp://ftp.is.co.za/rfc/rfc1808.txt",
|
||||
"http://www.ietf.org/rfc/rfc2396.txt",
|
||||
"ldap://[2001:db8::7]/c=GB?objectClass?one",
|
||||
"mailto:John.Doe@example.com",
|
||||
"news:comp.infosystems.www.servers.unix",
|
||||
"tel:+1-816-555-1212",
|
||||
"telnet://192.0.2.16:80/",
|
||||
"urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
|
||||
]
|
||||
for uri in invariant:
|
||||
self.assertEqual(uri, iri2uri(uri))
|
||||
|
||||
def test_iri(self):
|
||||
"""Test that the right type of escaping is done for each part of the URI."""
|
||||
self.assertEqual(
|
||||
"http://xn--o3h.com/%E2%98%84",
|
||||
iri2uri("http://\N{COMET}.com/\N{COMET}"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"http://bitworking.org/?fred=%E2%98%84",
|
||||
iri2uri("http://bitworking.org/?fred=\N{COMET}"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"http://bitworking.org/#%E2%98%84",
|
||||
iri2uri("http://bitworking.org/#\N{COMET}"),
|
||||
)
|
||||
self.assertEqual("#%E2%98%84", iri2uri("#\N{COMET}"))
|
||||
self.assertEqual(
|
||||
"/fred?bar=%E2%98%9A#%E2%98%84",
|
||||
iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"/fred?bar=%E2%98%9A#%E2%98%84",
|
||||
iri2uri(iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")),
|
||||
)
|
||||
self.assertNotEqual(
|
||||
"/fred?bar=%E2%98%9A#%E2%98%84",
|
||||
iri2uri(
|
||||
"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode("utf-8")
|
||||
),
|
||||
)
|
||||
|
||||
unittest.main()
|
||||
@@ -0,0 +1,518 @@
|
||||
"""SocksiPy - Python SOCKS module.
|
||||
|
||||
Version 1.00
|
||||
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
This module provides a standard socket-like interface for Python
|
||||
for tunneling connections through SOCKS proxies.
|
||||
|
||||
Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for
|
||||
use in PyLoris (http://pyloris.sourceforge.net/).
|
||||
|
||||
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||
mainly to merge bug fixes found in Sourceforge.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
if getattr(socket, "socket", None) is None:
|
||||
raise ImportError("socket.socket missing, proxy support unusable")
|
||||
|
||||
PROXY_TYPE_SOCKS4 = 1
|
||||
PROXY_TYPE_SOCKS5 = 2
|
||||
PROXY_TYPE_HTTP = 3
|
||||
PROXY_TYPE_HTTP_NO_TUNNEL = 4
|
||||
|
||||
_defaultproxy = None
|
||||
_orgsocket = socket.socket
|
||||
|
||||
|
||||
class ProxyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GeneralProxyError(ProxyError):
|
||||
pass
|
||||
|
||||
|
||||
class Socks5AuthError(ProxyError):
|
||||
pass
|
||||
|
||||
|
||||
class Socks5Error(ProxyError):
|
||||
pass
|
||||
|
||||
|
||||
class Socks4Error(ProxyError):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPError(ProxyError):
|
||||
pass
|
||||
|
||||
|
||||
_generalerrors = (
|
||||
"success",
|
||||
"invalid data",
|
||||
"not connected",
|
||||
"not available",
|
||||
"bad proxy type",
|
||||
"bad input",
|
||||
)
|
||||
|
||||
_socks5errors = (
|
||||
"succeeded",
|
||||
"general SOCKS server failure",
|
||||
"connection not allowed by ruleset",
|
||||
"Network unreachable",
|
||||
"Host unreachable",
|
||||
"Connection refused",
|
||||
"TTL expired",
|
||||
"Command not supported",
|
||||
"Address type not supported",
|
||||
"Unknown error",
|
||||
)
|
||||
|
||||
_socks5autherrors = (
|
||||
"succeeded",
|
||||
"authentication is required",
|
||||
"all offered authentication methods were rejected",
|
||||
"unknown username or invalid password",
|
||||
"unknown error",
|
||||
)
|
||||
|
||||
_socks4errors = (
|
||||
"request granted",
|
||||
"request rejected or failed",
|
||||
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||
"request rejected because the client program and identd report different "
|
||||
"user-ids",
|
||||
"unknown error",
|
||||
)
|
||||
|
||||
|
||||
def setdefaultproxy(
|
||||
proxytype=None, addr=None, port=None, rdns=True, username=None, password=None
|
||||
):
|
||||
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
Sets a default proxy which all further socksocket objects will use,
|
||||
unless explicitly changed.
|
||||
"""
|
||||
global _defaultproxy
|
||||
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||
|
||||
|
||||
def wrapmodule(module):
|
||||
"""wrapmodule(module)
|
||||
|
||||
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||
a default proxy using setdefaultproxy(...) first.
|
||||
This will only work on modules that import socket directly into the
|
||||
namespace;
|
||||
most of the Python Standard Library falls into this category.
|
||||
"""
|
||||
if _defaultproxy != None:
|
||||
module.socket.socket = socksocket
|
||||
else:
|
||||
raise GeneralProxyError((4, "no proxy specified"))
|
||||
|
||||
|
||||
class socksocket(socket.socket):
|
||||
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||
Open a SOCKS enabled socket. The parameters are the same as
|
||||
those of the standard socket init. In order for SOCKS to work,
|
||||
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None
|
||||
):
|
||||
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||
if _defaultproxy != None:
|
||||
self.__proxy = _defaultproxy
|
||||
else:
|
||||
self.__proxy = (None, None, None, None, None, None)
|
||||
self.__proxysockname = None
|
||||
self.__proxypeername = None
|
||||
self.__httptunnel = True
|
||||
|
||||
def __recvall(self, count):
|
||||
"""__recvall(count) -> data
|
||||
Receive EXACTLY the number of bytes requested from the socket.
|
||||
Blocks until the required number of bytes have been received.
|
||||
"""
|
||||
data = self.recv(count)
|
||||
while len(data) < count:
|
||||
d = self.recv(count - len(data))
|
||||
if not d:
|
||||
raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||
data = data + d
|
||||
return data
|
||||
|
||||
def sendall(self, content, *args):
|
||||
""" override socket.socket.sendall method to rewrite the header
|
||||
for non-tunneling proxies if needed
|
||||
"""
|
||||
if not self.__httptunnel:
|
||||
content = self.__rewriteproxy(content)
|
||||
return super(socksocket, self).sendall(content, *args)
|
||||
|
||||
def __rewriteproxy(self, header):
|
||||
""" rewrite HTTP request headers to support non-tunneling proxies
|
||||
(i.e. those which do not support the CONNECT method).
|
||||
This only works for HTTP (not HTTPS) since HTTPS requires tunneling.
|
||||
"""
|
||||
host, endpt = None, None
|
||||
hdrs = header.split("\r\n")
|
||||
for hdr in hdrs:
|
||||
if hdr.lower().startswith("host:"):
|
||||
host = hdr
|
||||
elif hdr.lower().startswith("get") or hdr.lower().startswith("post"):
|
||||
endpt = hdr
|
||||
if host and endpt:
|
||||
hdrs.remove(host)
|
||||
hdrs.remove(endpt)
|
||||
host = host.split(" ")[1]
|
||||
endpt = endpt.split(" ")
|
||||
if self.__proxy[4] != None and self.__proxy[5] != None:
|
||||
hdrs.insert(0, self.__getauthheader())
|
||||
hdrs.insert(0, "Host: %s" % host)
|
||||
hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2]))
|
||||
return "\r\n".join(hdrs)
|
||||
|
||||
def __getauthheader(self):
|
||||
auth = self.__proxy[4] + b":" + self.__proxy[5]
|
||||
return "Proxy-Authorization: Basic " + base64.b64encode(auth).decode()
|
||||
|
||||
def setproxy(
|
||||
self,
|
||||
proxytype=None,
|
||||
addr=None,
|
||||
port=None,
|
||||
rdns=True,
|
||||
username=None,
|
||||
password=None,
|
||||
headers=None,
|
||||
):
|
||||
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||
|
||||
Sets the proxy to be used.
|
||||
proxytype - The type of the proxy to be used. Three types
|
||||
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||
addr - The address of the server (IP or DNS).
|
||||
port - The port of the server. Defaults to 1080 for SOCKS
|
||||
servers and 8080 for HTTP proxy servers.
|
||||
rdns - Should DNS queries be preformed on the remote side
|
||||
(rather than the local side). The default is True.
|
||||
Note: This has no effect with SOCKS4 servers.
|
||||
username - Username to authenticate with to the server.
|
||||
The default is no authentication.
|
||||
password - Password to authenticate with to the server.
|
||||
Only relevant when username is also provided.
|
||||
headers - Additional or modified headers for the proxy connect
|
||||
request.
|
||||
"""
|
||||
self.__proxy = (
|
||||
proxytype,
|
||||
addr,
|
||||
port,
|
||||
rdns,
|
||||
username.encode() if username else None,
|
||||
password.encode() if password else None,
|
||||
headers,
|
||||
)
|
||||
|
||||
def __negotiatesocks5(self, destaddr, destport):
|
||||
"""__negotiatesocks5(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS5 server.
|
||||
"""
|
||||
# First we'll send the authentication packages we support.
|
||||
if (self.__proxy[4] != None) and (self.__proxy[5] != None):
|
||||
# The username/password details were supplied to the
|
||||
# setproxy method so we support the USERNAME/PASSWORD
|
||||
# authentication (in addition to the standard none).
|
||||
self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02))
|
||||
else:
|
||||
# No username/password were entered, therefore we
|
||||
# only support connections with no authentication.
|
||||
self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00))
|
||||
# We'll receive the server's response to determine which
|
||||
# method was selected
|
||||
chosenauth = self.__recvall(2)
|
||||
if chosenauth[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Check the chosen authentication method
|
||||
if chosenauth[1:2] == chr(0x00).encode():
|
||||
# No authentication is required
|
||||
pass
|
||||
elif chosenauth[1:2] == chr(0x02).encode():
|
||||
# Okay, we need to perform a basic username/password
|
||||
# authentication.
|
||||
packet = bytearray()
|
||||
packet.append(0x01)
|
||||
packet.append(len(self.__proxy[4]))
|
||||
packet.extend(self.__proxy[4])
|
||||
packet.append(len(self.__proxy[5]))
|
||||
packet.extend(self.__proxy[5])
|
||||
self.sendall(packet)
|
||||
authstat = self.__recvall(2)
|
||||
if authstat[0:1] != chr(0x01).encode():
|
||||
# Bad response
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if authstat[1:2] != chr(0x00).encode():
|
||||
# Authentication failed
|
||||
self.close()
|
||||
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||
# Authentication succeeded
|
||||
else:
|
||||
# Reaching here is always bad
|
||||
self.close()
|
||||
if chosenauth[1] == chr(0xFF).encode():
|
||||
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||
else:
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
# Now we can request the actual connection
|
||||
req = struct.pack("BBB", 0x05, 0x01, 0x00)
|
||||
# If the given destination address is an IP address, we'll
|
||||
# use the IPv4 address request even if remote resolving was specified.
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
except socket.error:
|
||||
# Well it's not an IP number, so it's probably a DNS name.
|
||||
if self.__proxy[3]:
|
||||
# Resolve remotely
|
||||
ipaddr = None
|
||||
req = (
|
||||
req
|
||||
+ chr(0x03).encode()
|
||||
+ chr(len(destaddr)).encode()
|
||||
+ destaddr.encode()
|
||||
)
|
||||
else:
|
||||
# Resolve locally
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
req = req + chr(0x01).encode() + ipaddr
|
||||
req = req + struct.pack(">H", destport)
|
||||
self.sendall(req)
|
||||
# Get the response
|
||||
resp = self.__recvall(4)
|
||||
if resp[0:1] != chr(0x05).encode():
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
elif resp[1:2] != chr(0x00).encode():
|
||||
# Connection failed
|
||||
self.close()
|
||||
if ord(resp[1:2]) <= 8:
|
||||
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||
else:
|
||||
raise Socks5Error((9, _socks5errors[9]))
|
||||
# Get the bound address/port
|
||||
elif resp[3:4] == chr(0x01).encode():
|
||||
boundaddr = self.__recvall(4)
|
||||
elif resp[3:4] == chr(0x03).encode():
|
||||
resp = resp + self.recv(1)
|
||||
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||
else:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||
self.__proxysockname = (boundaddr, boundport)
|
||||
if ipaddr != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def getproxysockname(self):
|
||||
"""getsockname() -> address info
|
||||
Returns the bound IP address and port number at the proxy.
|
||||
"""
|
||||
return self.__proxysockname
|
||||
|
||||
def getproxypeername(self):
|
||||
"""getproxypeername() -> address info
|
||||
Returns the IP and port number of the proxy.
|
||||
"""
|
||||
return _orgsocket.getpeername(self)
|
||||
|
||||
def getpeername(self):
|
||||
"""getpeername() -> address info
|
||||
Returns the IP address and port number of the destination
|
||||
machine (note: getproxypeername returns the proxy)
|
||||
"""
|
||||
return self.__proxypeername
|
||||
|
||||
def __negotiatesocks4(self, destaddr, destport):
|
||||
"""__negotiatesocks4(self,destaddr,destport)
|
||||
Negotiates a connection through a SOCKS4 server.
|
||||
"""
|
||||
# Check if the destination address provided is an IP address
|
||||
rmtrslv = False
|
||||
try:
|
||||
ipaddr = socket.inet_aton(destaddr)
|
||||
except socket.error:
|
||||
# It's a DNS name. Check where it should be resolved.
|
||||
if self.__proxy[3]:
|
||||
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||
rmtrslv = True
|
||||
else:
|
||||
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||
# Construct the request packet
|
||||
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||
# The username parameter is considered userid for SOCKS4
|
||||
if self.__proxy[4] != None:
|
||||
req = req + self.__proxy[4]
|
||||
req = req + chr(0x00).encode()
|
||||
# DNS name if remote resolving is required
|
||||
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||
# called SOCKS4A and may not be supported in all cases.
|
||||
if rmtrslv:
|
||||
req = req + destaddr + chr(0x00).encode()
|
||||
self.sendall(req)
|
||||
# Get the response from the server
|
||||
resp = self.__recvall(8)
|
||||
if resp[0:1] != chr(0x00).encode():
|
||||
# Bad data
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if resp[1:2] != chr(0x5A).encode():
|
||||
# Server returned an error
|
||||
self.close()
|
||||
if ord(resp[1:2]) in (91, 92, 93):
|
||||
self.close()
|
||||
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||
else:
|
||||
raise Socks4Error((94, _socks4errors[4]))
|
||||
# Get the bound address/port
|
||||
self.__proxysockname = (
|
||||
socket.inet_ntoa(resp[4:]),
|
||||
struct.unpack(">H", resp[2:4])[0],
|
||||
)
|
||||
if rmtrslv != None:
|
||||
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||
else:
|
||||
self.__proxypeername = (destaddr, destport)
|
||||
|
||||
def __negotiatehttp(self, destaddr, destport):
|
||||
"""__negotiatehttp(self,destaddr,destport)
|
||||
Negotiates a connection through an HTTP server.
|
||||
"""
|
||||
# If we need to resolve locally, we do this now
|
||||
if not self.__proxy[3]:
|
||||
addr = socket.gethostbyname(destaddr)
|
||||
else:
|
||||
addr = destaddr
|
||||
headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"]
|
||||
wrote_host_header = False
|
||||
wrote_auth_header = False
|
||||
if self.__proxy[6] != None:
|
||||
for key, val in self.__proxy[6].iteritems():
|
||||
headers += [key, ": ", val, "\r\n"]
|
||||
wrote_host_header = key.lower() == "host"
|
||||
wrote_auth_header = key.lower() == "proxy-authorization"
|
||||
if not wrote_host_header:
|
||||
headers += ["Host: ", destaddr, "\r\n"]
|
||||
if not wrote_auth_header:
|
||||
if self.__proxy[4] != None and self.__proxy[5] != None:
|
||||
headers += [self.__getauthheader(), "\r\n"]
|
||||
headers.append("\r\n")
|
||||
self.sendall("".join(headers).encode())
|
||||
# We read the response until we get the string "\r\n\r\n"
|
||||
resp = self.recv(1)
|
||||
while resp.find("\r\n\r\n".encode()) == -1:
|
||||
resp = resp + self.recv(1)
|
||||
# We just need the first line to check if the connection
|
||||
# was successful
|
||||
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
try:
|
||||
statuscode = int(statusline[1])
|
||||
except ValueError:
|
||||
self.close()
|
||||
raise GeneralProxyError((1, _generalerrors[1]))
|
||||
if statuscode != 200:
|
||||
self.close()
|
||||
raise HTTPError((statuscode, statusline[2]))
|
||||
self.__proxysockname = ("0.0.0.0", 0)
|
||||
self.__proxypeername = (addr, destport)
|
||||
|
||||
def connect(self, destpair):
|
||||
"""connect(self, despair)
|
||||
Connects to the specified destination through a proxy.
|
||||
destpar - A tuple of the IP/DNS address and the port number.
|
||||
(identical to socket's connect).
|
||||
To select the proxy server use setproxy().
|
||||
"""
|
||||
# Do a minimal input check first
|
||||
if (
|
||||
(not type(destpair) in (list, tuple))
|
||||
or (len(destpair) < 2)
|
||||
or (not isinstance(destpair[0], (str, bytes)))
|
||||
or (type(destpair[1]) != int)
|
||||
):
|
||||
raise GeneralProxyError((5, _generalerrors[5]))
|
||||
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 1080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL:
|
||||
if self.__proxy[2] != None:
|
||||
portnum = self.__proxy[2]
|
||||
else:
|
||||
portnum = 8080
|
||||
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||
if destpair[1] == 443:
|
||||
self.__negotiatehttp(destpair[0], destpair[1])
|
||||
else:
|
||||
self.__httptunnel = False
|
||||
elif self.__proxy[0] == None:
|
||||
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||
else:
|
||||
raise GeneralProxyError((4, _generalerrors[4]))
|
||||
@@ -0,0 +1,9 @@
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
from repr import *
|
||||
else:
|
||||
raise ImportError('This package should not be accessible on Python 3. '
|
||||
'Either you are trying to run from the python-future src folder '
|
||||
'or your installation of python-future is corrupted.')
|
||||
Reference in New Issue
Block a user