Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #912 from arucard21/noVerifySSL_midgetspy
modified URL openers so they do not check SSL certificates (fixes #2551)
  • Loading branch information
midgetspy committed Feb 24, 2015
2 parents ff36290 + f60fbe5 commit 79cb8f2
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 250 deletions.
8 changes: 7 additions & 1 deletion autoProcessTV/autoProcessTV.py
Expand Up @@ -21,7 +21,10 @@
from __future__ import with_statement

import os.path

import sys
if sys.version_info >= (2, 7, 9):
import ssl as sslModule

# Try importing Python 2 modules using new names
try:
Expand Down Expand Up @@ -131,7 +134,10 @@ def processEpisode(dir_to_process, org_NZB_name=None):
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, url, username, password)
handler = HTTPBasicAuthHandler(password_mgr)
opener = urllib2.build_opener(handler)
if sys.version_info >= (2, 7, 9):
opener = urllib2.build_opener(handler, urllib2.HTTPSHandler(context=sslModule._create_unverified_context()))
else:
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)

result = opener.open(url).readlines()
Expand Down
7 changes: 6 additions & 1 deletion lib/growl/gntp_bridge.py
@@ -1,5 +1,7 @@
from gntp import *
import urllib
if sys.version_info >= (2, 7, 9):
import ssl
import Growl

def register_send(self):
Expand Down Expand Up @@ -55,7 +57,10 @@ def get_resource(self,key):
return self.resources.get(resource[1])['Data']
elif resource.startswith('http'):
resource = resource.replace(' ', '%20')
icon = urllib.urlopen(resource)
if sys.version_info >= (2, 7, 9):
icon = urllib.urlopen(resource, context=ssl._create_unverified_context())
else:
icon = urllib.urlopen(resource)
return icon.read()
else:
return None
Expand Down
51 changes: 3 additions & 48 deletions sickbeard/classes.py
Expand Up @@ -16,55 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.



import sickbeard

import urllib
import datetime

from common import USER_AGENT, Quality

class SickBeardURLopener(urllib.FancyURLopener):
version = USER_AGENT

class AuthURLOpener(SickBeardURLopener):
"""
URLOpener class that supports http auth without needing interactive password entry.
If the provided username/password don't work it simply fails.
user: username to use for HTTP auth
pw: password to use for HTTP auth
"""
def __init__(self, user, pw):
self.username = user
self.password = pw

# remember if we've tried the username/password before
self.numTries = 0

# call the base class
urllib.FancyURLopener.__init__(self)

def prompt_user_passwd(self, host, realm):
"""
Override this function and instead of prompting just give the
username/password that were provided when the class was instantiated.
"""

# if this is the first try then provide a username/password
if self.numTries == 0:
self.numTries = 1
return (self.username, self.password)

# if we've tried before then return blank which cancels the request
else:
return ('', '')

# this is pretty much just a hack for convenience
def openit(self, url):
self.numTries = 0
return SickBeardURLopener.open(self, url)
from common import Quality

class SearchResult:
"""
Expand Down Expand Up @@ -178,11 +133,11 @@ def add(error):
def clear():
ErrorViewer.errors = []



class UIError():
"""
Represents an error to be displayed in the web UI.
"""
def __init__(self, message):
self.message = message
self.time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
152 changes: 109 additions & 43 deletions sickbeard/helpers.py
Expand Up @@ -28,10 +28,13 @@
import StringIO
import time
import traceback
import urllib
import urllib2
import cookielib
import sys
if sys.version_info >= (2, 7, 9):
import ssl
import zlib

from lib import MultipartPostHandler
from httplib import BadStatusLine

try:
Expand All @@ -46,20 +49,23 @@
except ImportError:
import elementtree.ElementTree as etree

import sickbeard

from sickbeard.exceptions import MultipleShowObjectsException, ex
from sickbeard import logger, classes
from sickbeard.common import USER_AGENT, mediaExtensions, XML_NSMAP
from sickbeard import logger
from sickbeard.common import USER_AGENT, mediaExtensions

from sickbeard import db
from sickbeard import encodingKludge as ek
from sickbeard import notifiers
from sickbeard.notifiers import synoindex_notifier

from lib.tvdb_api import tvdb_api, tvdb_exceptions

urllib._urlopener = classes.SickBeardURLopener()
# workaround for broken urllib2 in python 2.6.5: wrong credentials lead to an infinite recursion
if sys.version_info >= (2, 6, 5) and sys.version_info < (2, 6, 6):
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
def retry_http_basic_auth(self, host, req, realm):
# don't retry if auth failed
if req.get_header(self.auth_header, None) is not None:
return None

return urllib2.HTTPBasicAuthHandler.retry_http_basic_auth(self, host, req, realm)

def indentXML(elem, level=0):
'''
Expand Down Expand Up @@ -166,61 +172,121 @@ def sanitizeFileName(name):

return name


def getURL(url, post_data=None, headers=[]):
def getURL(url, validate=False, cookies = cookielib.CookieJar(), password_mgr=None, throw_exc=False):
"""
Returns a byte-string retrieved from the url provider.
Convenience method to directly retrieve the contents of a url
"""
obj = getURLFileLike(url, validate, cookies, password_mgr, throw_exc)
if obj:
return readURLFileLike(obj)
else:
return None

opener = urllib2.build_opener()
opener.addheaders = [('User-Agent', USER_AGENT), ('Accept-Encoding', 'gzip,deflate')]
for cur_header in headers:
opener.addheaders.append(cur_header)

def getURLFileLike(url, validate=False, cookies = cookielib.CookieJar(), password_mgr=None, throw_exc=False):
"""
Returns a file-like object same as returned by urllib2.urlopen but with Handlers configured for sickbeard.
It allows for the use of cookies, multipart/form-data, https without certificate validation and both basic
and digest HTTP authentication. In addition, the user-agent is set to the sickbeard default and accepts
gzip and deflate encoding (which can be automatically handled when using readURL() to retrieve the contents)
@param url: can be either a string or a Request object.
@param validate: defines if SSL certificates should be validated on HTTPS connections
@param cookies: is the cookielib.CookieJar in which cookies are stored.
@param password_mgr: if given, should be something that is compatible with HTTPPasswordMgr
@param throw_exc: throw the exception that was caught instead of None
@return: the file-like object retrieved from the URL or None (or the exception) if it could not be retrieved
"""
# configure the OpenerDirector appropriately
if not validate and sys.version_info >= (2, 7, 9):
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
MultipartPostHandler.MultipartPostHandler,
urllib2.HTTPSHandler(context=ssl._create_unverified_context()),
urllib2.HTTPDigestAuthHandler(password_mgr),
urllib2.HTTPBasicAuthHandler(password_mgr))
else:
# Before python 2.7.9, there was no built-in way to validate SSL certificates
# Since our default is not to validate, it is of low priority to make it available here
if validate and sys.version_info < (2, 7, 9):
logger.log(u"The SSL certificate will not be validated for " + url + "(python 2.7.9+ required)", logger.MESSAGE)

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
MultipartPostHandler.MultipartPostHandler,
urllib2.HTTPDigestAuthHandler(password_mgr),
urllib2.HTTPBasicAuthHandler(password_mgr))

# set the default headers for every request
opener.addheaders = [('User-Agent', USER_AGENT),
('Accept-Encoding', 'gzip,deflate')]

try:
usock = opener.open(url, post_data)
url = usock.geturl()
encoding = usock.info().get("Content-Encoding")

if encoding in ('gzip', 'x-gzip', 'deflate'):
content = usock.read()
if encoding == 'deflate':
data = StringIO.StringIO(zlib.decompress(content))
else:
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(content))
result = data.read()

else:
result = usock.read()

usock.close()
return opener.open(url)

except urllib2.HTTPError, e:
logger.log(u"HTTP error " + str(e.code) + " while loading URL " + url, logger.WARNING)
return None
if throw_exc:
raise
else:
return None

except urllib2.URLError, e:
logger.log(u"URL error " + str(e.reason) + " while loading URL " + url, logger.WARNING)
return None
if throw_exc:
raise
else:
return None

except BadStatusLine:
logger.log(u"BadStatusLine error while loading URL " + url, logger.WARNING)
return None
if throw_exc:
raise
else:
return None

except socket.timeout:
logger.log(u"Timed out while loading URL " + url, logger.WARNING)
return None
if throw_exc:
raise
else:
return None

except ValueError:
logger.log(u"Unknown error while loading URL " + url, logger.WARNING)
return None
if throw_exc:
raise
else:
return None

except Exception:
logger.log(u"Unknown exception while loading URL " + url + ": " + traceback.format_exc(), logger.WARNING)
return None
if throw_exc:
raise
else:
return None

def readURLFileLike(urlFileLike):
"""
Return the contents of the file like objects as string, performing decompression if necessary.
@param urlFileLike: is a file like objects same as returned by urllib2.urlopen() and getURL()
"""
encoding = urlFileLike.info().get("Content-Encoding")

return result
if encoding in ('gzip', 'x-gzip', 'deflate'):
content = urlFileLike.read()
if encoding == 'deflate':
data = StringIO.StringIO(zlib.decompress(content))
else:
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(content))
result = data.read()

else:
result = urlFileLike.read()

urlFileLike.close()

return result;

def is_hidden_folder(folder):
"""
Expand Down Expand Up @@ -311,7 +377,7 @@ def makeDir(path):
try:
ek.ek(os.makedirs, path)
# do the library update for synoindex
notifiers.synoindex_notifier.addFolder(path)
synoindex_notifier.addFolder(path)
except OSError:
return False
return True
Expand Down Expand Up @@ -444,7 +510,7 @@ def make_dirs(path):
# use normpath to remove end separator, otherwise checks permissions against itself
chmodAsParent(ek.ek(os.path.normpath, sofar))
# do the library update for synoindex
notifiers.synoindex_notifier.addFolder(sofar)
synoindex_notifier.addFolder(sofar)
except (OSError, IOError), e:
logger.log(u"Failed creating " + sofar + " : " + ex(e), logger.ERROR)
return False
Expand Down Expand Up @@ -514,7 +580,7 @@ def delete_empty_folders(check_empty_dir, keep_dir=None):
# need shutil.rmtree when ignore_items is really implemented
ek.ek(os.rmdir, check_empty_dir)
# do the library update for synoindex
notifiers.synoindex_notifier.deleteFolder(check_empty_dir)
synoindex_notifier.deleteFolder(check_empty_dir)
except OSError, e:
logger.log(u"Unable to delete " + check_empty_dir + ": " + repr(e) + " / " + str(e), logger.WARNING)
break
Expand Down
5 changes: 2 additions & 3 deletions sickbeard/notifiers/boxcar2.py
Expand Up @@ -20,7 +20,6 @@

import urllib
import urllib2

import sickbeard

from sickbeard import logger
Expand Down Expand Up @@ -56,8 +55,8 @@ def _sendBoxcar2(self, title, msg, accessToken, sound):

# send the request to boxcar2
try:
req = urllib2.Request(API_URL)
handle = urllib2.urlopen(req, data)
req = urllib2.Request(API_URL, data)
handle = sickbeard.helpers.getURLFileLike(req, throw_exc=True)
handle.close()

except urllib2.URLError, e:
Expand Down
4 changes: 2 additions & 2 deletions sickbeard/notifiers/nmj.py
Expand Up @@ -101,7 +101,7 @@ def _sendNMJ(self, host, database, mount=None):
try:
req = urllib2.Request(mount)
logger.log(u"NMJ: Try to mount network drive via url: %s" % (mount), logger.DEBUG)
handle = urllib2.urlopen(req)
sickbeard.helpers.getURLFileLike(req)
except IOError, e:
if hasattr(e, 'reason'):
logger.log(u"NMJ: Could not contact Popcorn Hour on host %s: %s" % (host, e.reason), logger.WARNING)
Expand All @@ -127,7 +127,7 @@ def _sendNMJ(self, host, database, mount=None):
try:
req = urllib2.Request(updateUrl)
logger.log(u"NMJ: Sending NMJ scan update command via url: %s" % (updateUrl), logger.DEBUG)
handle = urllib2.urlopen(req)
handle = sickbeard.helpers.getURLFileLike(req)
response = handle.read()
except IOError, e:
if hasattr(e, 'reason'):
Expand Down

7 comments on commit 79cb8f2

@aptalca
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this commit, xbmc/kodi and plex notifications no longer work

Clicking the test button shows "Test XBMC notice failed to 192.168.1.XX:XXXX" for xbmc and "Please fill out the necessary fields above." for plex

@pengc99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as aptalca - Plex does not work for me.

I get the error "Please fill out the necessary fields above":
plex_1

Manually trying to update Plex from the SickBeard homepage shows the following 500 Internal Server Error:
plex_2

@jordansaints
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here - Plex notifications no longer work in the the latest update. I get the same 500 Internal Server Error that pengc99 posted. Guess I'll attempt to rollback to a previous known good.

Edit: sweet, git checkout ff36290ba50a08d004b41cbcb8649645c324797a did the trick for me. Although now my git HEAD doesn't point to the repo, but I can deal with that later. If you do this make sure you make a backup of your SB directory first, just in case. :)

Edit 2: derp derp git reset --hard ff36290ba50a08d004b41cbcb8649645c324797a gets us to where we were before we updated to this commit, with SB correctly realizing it is 13 commits behind (so we can update as usual once this issue is resolved in a later commit).

@p88h
Copy link

@p88h p88h commented on 79cb8f2 Mar 2, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got the same 'Please fill out necessary fields above' message despite still being at
ff36290 (never updated from that).

Edit: note that the notifications do work if the config is saved, it's just the test that doesn't work correctly

@aptalca
Copy link

@aptalca aptalca commented on 79cb8f2 Mar 4, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jordansaints, thanks for the downgrade instructions. I am back to the last working commit and all is well.

I am never updating sickbeard again, lol

@pengc99
Copy link
Contributor

@pengc99 pengc99 commented on 79cb8f2 Mar 6, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. I stopped Sickbeard, then used:

git reset --hard ff36290ba50a08d004b41cbcb8649645c324797a

then I restart Sickbeard and setup Plex again. Now it's working.

@ppslim
Copy link
Contributor

@ppslim ppslim commented on 79cb8f2 Mar 6, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root cause is https://github.com/midgetspy/Sick-Beard/blob/development/sickbeard/helpers.py#L221

gzip encoding has been requested by getURLFileLike and Plex is honouring the request.

getURLFileLike does not support decoding and its use replaced urllib2.urlopen() without any encoding requests, it simply returns a file like object of the original source, including the compression from http.

sickbeard.helpers.readURLFileLike will however solve this and IS required anytime getURLFileLike is now called.

Infact, getURLFileLike should never have been called direct, instead sickbeard.helpers.getURL() should be used, which wraps the getURLFileLike and readURLFileLike into one.
#923 proposed

Please sign in to comment.