diff --git a/contributing.md b/contributing.md
index 572dd332..ef8546f0 100644
--- a/contributing.md
+++ b/contributing.md
@@ -12,4 +12,4 @@
* What hardware / OS are you using and what are the limits? NAS can be slow and maybe have a different python installed then when you use CP on OSX or Windows for example.
* I will mark issues with the "can't reproduce" tag. Don't go asking me "why closed" if it clearly says the issue in the tag ;)
-**If I don't get enough info, the change of the issue getting closed is a lot bigger ;)**
\ No newline at end of file
+**If I don't get enough info, the chance of the issue getting closed is a lot bigger ;)**
diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py
index 8dc691da..b8aa3ab9 100644
--- a/couchpotato/__init__.py
+++ b/couchpotato/__init__.py
@@ -1,31 +1,47 @@
from couchpotato.api import api_docs, api_docs_missing, api
-from couchpotato.core.auth import requires_auth
from couchpotato.core.event import fireEvent
-from couchpotato.core.helpers.variable import md5
+from couchpotato.core.helpers.variable import md5, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
-from sqlalchemy.engine import create_engine
-from sqlalchemy.orm import scoped_session
-from sqlalchemy.orm.session import sessionmaker
from tornado import template
-from tornado.web import RequestHandler
+from tornado.web import RequestHandler, authenticated
import os
import time
+import traceback
log = CPLog(__name__)
+
views = {}
template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates'))
+
+class BaseHandler(RequestHandler):
+
+ def get_current_user(self):
+ username = Env.setting('username')
+ password = Env.setting('password')
+
+ if username and password:
+ return self.get_secure_cookie('user')
+ else: # Login when no username or password are set
+ return True
+
# Main web handler
-@requires_auth
-class WebHandler(RequestHandler):
+class WebHandler(BaseHandler):
+
+ @authenticated
def get(self, route, *args, **kwargs):
route = route.strip('/')
if not views.get(route):
page_not_found(self)
return
- self.write(views[route]())
+
+ try:
+ self.write(views[route]())
+ except:
+ log.error("Failed doing web request '%s': %s", (route, traceback.format_exc()))
+ self.write({'success': False, 'error': 'Failed returning results'})
def addView(route, func, static = False):
views[route] = func
@@ -58,16 +74,54 @@ addView('docs', apiDocs)
class KeyHandler(RequestHandler):
def get(self, *args, **kwargs):
api = None
+
+ try:
+ username = Env.setting('username')
+ password = Env.setting('password')
+
+ if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
+ api = Env.setting('api_key')
+
+ self.write({
+ 'success': api is not None,
+ 'api_key': api
+ })
+ except:
+ log.error('Failed doing key request: %s', (traceback.format_exc()))
+ self.write({'success': False, 'error': 'Failed returning results'})
+
+
+class LoginHandler(BaseHandler):
+
+ def get(self, *args, **kwargs):
+
+ if self.get_current_user():
+ self.redirect(Env.get('web_base'))
+ else:
+ self.write(template_loader.load('login.html').generate(sep = os.sep, fireEvent = fireEvent, Env = Env))
+
+ def post(self, *args, **kwargs):
+
+ api = None
+
username = Env.setting('username')
password = Env.setting('password')
- if (self.get_argument('u') == md5(username) or not username) and (self.get_argument('p') == password or not password):
+ if (self.get_argument('username') == username or not username) and (md5(self.get_argument('password')) == password or not password):
api = Env.setting('api_key')
- self.write({
- 'success': api is not None,
- 'api_key': api
- })
+ if api:
+ remember_me = tryInt(self.get_argument('remember_me', default = 0))
+ self.set_secure_cookie('user', api, expires_days = 30 if remember_me > 0 else None)
+
+ self.redirect(Env.get('web_base'))
+
+class LogoutHandler(BaseHandler):
+
+ def get(self, *args, **kwargs):
+ self.clear_cookie('user')
+ self.redirect('%slogin/' % Env.get('web_base'))
+
def page_not_found(rh):
index_url = Env.get('web_base')
diff --git a/couchpotato/api.py b/couchpotato/api.py
index 029ebce2..b6135583 100644
--- a/couchpotato/api.py
+++ b/couchpotato/api.py
@@ -1,38 +1,64 @@
from couchpotato.core.helpers.request import getParams
+from couchpotato.core.logger import CPLog
+from functools import wraps
+from threading import Thread
+from tornado.gen import coroutine
from tornado.web import RequestHandler, asynchronous
import json
+import threading
+import tornado
+import traceback
import urllib
+log = CPLog(__name__)
+
+
api = {}
+api_locks = {}
api_nonblock = {}
api_docs = {}
api_docs_missing = []
+def run_async(func):
+ @wraps(func)
+ def async_func(*args, **kwargs):
+ func_hl = Thread(target = func, args = args, kwargs = kwargs)
+ func_hl.start()
+ return func_hl
+
+ return async_func
+
# NonBlock API handler
class NonBlockHandler(RequestHandler):
- stoppers = []
+ stopper = None
@asynchronous
def get(self, route, *args, **kwargs):
route = route.strip('/')
start, stop = api_nonblock[route]
- self.stoppers.append(stop)
+ self.stopper = stop
- start(self.onNewMessage, last_id = self.get_argument("last_id", None))
+ start(self.onNewMessage, last_id = self.get_argument('last_id', None))
def onNewMessage(self, response):
if self.request.connection.stream.closed():
return
- self.finish(response)
+
+ try:
+ self.finish(response)
+ except:
+ log.error('Failed doing nonblock request: %s', (traceback.format_exc()))
+ try: self.finish({'success': False, 'error': 'Failed returning results'})
+ except: pass
def on_connection_close(self):
- for stop in self.stoppers:
- stop(self.onNewMessage)
+ if self.stopper:
+ self.stopper(self.onNewMessage)
- self.stoppers = []
+ self.stopper = None
def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
api_nonblock[route] = func_tuple
@@ -45,38 +71,61 @@ def addNonBlockApiView(route, func_tuple, docs = None, **kwargs):
# Blocking API handler
class ApiHandler(RequestHandler):
+ @coroutine
def get(self, route, *args, **kwargs):
route = route.strip('/')
if not api.get(route):
self.write('API call doesn\'t seem to exist')
return
- kwargs = {}
- for x in self.request.arguments:
- kwargs[x] = urllib.unquote(self.get_argument(x))
+ api_locks[route].acquire()
- # Split array arguments
- kwargs = getParams(kwargs)
+ try:
- # Remove t random string
- try: del kwargs['t']
- except: pass
+ kwargs = {}
+ for x in self.request.arguments:
+ kwargs[x] = urllib.unquote(self.get_argument(x))
- # Check JSONP callback
- result = api[route](**kwargs)
- jsonp_callback = self.get_argument('callback_func', default = None)
+ # Split array arguments
+ kwargs = getParams(kwargs)
- if jsonp_callback:
- self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
- elif isinstance(result, (tuple)) and result[0] == 'redirect':
- self.redirect(result[1])
- else:
- self.write(result)
+ # Remove t random string
+ try: del kwargs['t']
+ except: pass
+
+ # Add async callback handler
+ @run_async
+ def run_handler(callback):
+ try:
+ result = api[route](**kwargs)
+ callback(result)
+ except:
+ log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
+ callback({'success': False, 'error': 'Failed returning results'})
+ result = yield tornado.gen.Task(run_handler)
+
+ # Check JSONP callback
+ jsonp_callback = self.get_argument('callback_func', default = None)
+
+ if jsonp_callback:
+ self.write(str(jsonp_callback) + '(' + json.dumps(result) + ')')
+ elif isinstance(result, tuple) and result[0] == 'redirect':
+ self.redirect(result[1])
+ else:
+ self.write(result)
+
+ except:
+ log.error('Failed doing api request "%s": %s', (route, traceback.format_exc()))
+ self.write({'success': False, 'error': 'Failed returning results'})
+
+ api_locks[route].release()
def addApiView(route, func, static = False, docs = None, **kwargs):
if static: func(route)
- else: api[route] = func
+ else:
+ api[route] = func
+ api_locks[route] = threading.Lock()
if docs:
api_docs[route[4:] if route[0:4] == 'api.' else route] = docs
diff --git a/couchpotato/core/_base/_core/main.py b/couchpotato/core/_base/_core/main.py
index 4ad37d6c..803ac5a3 100644
--- a/couchpotato/core/_base/_core/main.py
+++ b/couchpotato/core/_base/_core/main.py
@@ -56,7 +56,7 @@ class Core(Plugin):
self.signalHandler()
def md5Password(self, value):
- return md5(value.encode(Env.get('encoding'))) if value else ''
+ return md5(value) if value else ''
def checkApikey(self, value):
return value if value and len(value) > 3 else uuid4().hex
@@ -124,7 +124,7 @@ class Core(Plugin):
time.sleep(1)
- log.debug('Save to shutdown/restart')
+ log.debug('Safe to shutdown/restart')
try:
IOLoop.current().stop()
diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py
index fece6fa4..1b7f1636 100644
--- a/couchpotato/core/_base/clientscript/main.py
+++ b/couchpotato/core/_base/clientscript/main.py
@@ -6,6 +6,7 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from minify.cssmin import cssmin
from minify.jsmin import jsmin
+from tornado.web import StaticFileHandler
import os
import re
import traceback
@@ -80,7 +81,7 @@ class ClientScript(Plugin):
for static_type in self.core_static:
for rel_path in self.core_static.get(static_type):
file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path)
- core_url = 'api/%s/static/%s?%s' % (Env.setting('api_key'), rel_path, tryInt(os.path.getmtime(file_path)))
+ core_url = 'static/%s' % rel_path
if static_type == 'script':
self.registerScript(core_url, file_path, position = 'front')
@@ -90,6 +91,13 @@ class ClientScript(Plugin):
def minify(self):
+ # Create cache dir
+ cache = Env.get('cache_dir')
+ parent_dir = os.path.join(cache, 'minified')
+ self.makeDir(parent_dir)
+
+ Env.get('app').add_handlers(".*$", [(Env.get('web_base') + 'minified/(.*)', StaticFileHandler, {'path': parent_dir})])
+
for file_type in ['style', 'script']:
ext = 'js' if file_type is 'script' else 'css'
positions = self.paths.get(file_type, {})
@@ -100,8 +108,8 @@ class ClientScript(Plugin):
def _minify(self, file_type, files, position, out):
cache = Env.get('cache_dir')
- out_name = 'minified_' + out
- out = os.path.join(cache, out_name)
+ out_name = out
+ out = os.path.join(cache, 'minified', out_name)
raw = []
for file_path in files:
@@ -111,7 +119,7 @@ class ClientScript(Plugin):
data = jsmin(f)
else:
data = self.prefix(f)
- data = cssmin(f)
+ data = cssmin(data)
data = data.replace('../images/', '../static/images/')
data = data.replace('../fonts/', '../static/fonts/')
data = data.replace('../../static/', '../static/') # Replace inside plugins
@@ -131,7 +139,7 @@ class ClientScript(Plugin):
if not self.minified[file_type].get(position):
self.minified[file_type][position] = []
- minified_url = 'api/%s/file.cache/%s?%s' % (Env.setting('api_key'), out_name, tryInt(os.path.getmtime(out)))
+ minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out)))
self.minified[file_type][position].append(minified_url)
def getStyles(self, *args, **kwargs):
@@ -165,6 +173,8 @@ class ClientScript(Plugin):
def register(self, api_path, file_path, type, location):
+ api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path)))
+
if not self.urls[type].get(location):
self.urls[type][location] = []
self.urls[type][location].append(api_path)
diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py
index 38b7d36e..f3b4b19c 100644
--- a/couchpotato/core/_base/updater/main.py
+++ b/couchpotato/core/_base/updater/main.py
@@ -132,6 +132,7 @@ class BaseUpdater(Plugin):
update_failed = False
update_version = None
last_check = 0
+ auto_register_static = False
def doUpdate(self):
pass
diff --git a/couchpotato/core/auth.py b/couchpotato/core/auth.py
deleted file mode 100644
index e58016bd..00000000
--- a/couchpotato/core/auth.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from couchpotato.core.helpers.variable import md5
-from couchpotato.environment import Env
-import base64
-
-def check_auth(username, password):
- return username == Env.setting('username') and password == Env.setting('password')
-
-def requires_auth(handler_class):
-
- def wrap_execute(handler_execute):
-
- def require_basic_auth(handler, kwargs):
- if Env.setting('username') and Env.setting('password'):
-
- auth_header = handler.request.headers.get('Authorization')
- auth_decoded = base64.decodestring(auth_header[6:]) if auth_header else None
- if auth_decoded:
- username, password = auth_decoded.split(':', 2)
-
- if auth_header is None or not auth_header.startswith('Basic ') or (not check_auth(username.decode('latin'), md5(password.decode('latin')))):
- handler.set_status(401)
- handler.set_header('WWW-Authenticate', 'Basic realm="CouchPotato Login"')
- handler._transforms = []
- handler.finish()
-
- return False
-
- return True
-
- def _execute(self, transforms, *args, **kwargs):
-
- if not require_basic_auth(self, kwargs):
- return False
- return handler_execute(self, transforms, *args, **kwargs)
-
- return _execute
-
- handler_class._execute = wrap_execute(handler_class._execute)
-
- return handler_class
diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py
index 900fd8c0..08be4bd0 100644
--- a/couchpotato/core/downloaders/base.py
+++ b/couchpotato/core/downloaders/base.py
@@ -11,7 +11,7 @@ log = CPLog(__name__)
class Downloader(Provider):
- type = []
+ protocol = []
http_time_between_calls = 0
torrent_sources = [
@@ -36,18 +36,23 @@ class Downloader(Provider):
def __init__(self):
addEvent('download', self._download)
addEvent('download.enabled', self._isEnabled)
- addEvent('download.enabled_types', self.getEnabledDownloadType)
+ addEvent('download.enabled_protocols', self.getEnabledProtocol)
addEvent('download.status', self._getAllDownloadStatus)
addEvent('download.remove_failed', self._removeFailed)
+ addEvent('download.pause', self._pause)
+ addEvent('download.process_complete', self._processComplete)
- def getEnabledDownloadType(self):
- for download_type in self.type:
- if self.isEnabled(manual = True, data = {'type': download_type}):
- return self.type
+ def getEnabledProtocol(self):
+ for download_protocol in self.protocol:
+ if self.isEnabled(manual = True, data = {'protocol': download_protocol}):
+ return self.protocol
return []
- def _download(self, data = {}, movie = {}, manual = False, filedata = None):
+ def _download(self, data = None, movie = None, manual = False, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
+
if self.isDisabled(manual, data):
return
return self.download(data = data, movie = movie, filedata = filedata)
@@ -65,19 +70,35 @@ class Downloader(Provider):
if self.isDisabled(manual = True, data = {}):
return
- if self.conf('delete_failed', default = True):
- return self.removeFailed(item)
+ if item and item.get('downloader') == self.getName():
+ if self.conf('delete_failed'):
+ return self.removeFailed(item)
- return False
+ return False
+ return
def removeFailed(self, item):
return
- def isCorrectType(self, item_type):
- is_correct = item_type in self.type
+ def _processComplete(self, item):
+ if self.isDisabled(manual = True, data = {}):
+ return
+
+ if item and item.get('downloader') == self.getName():
+ if self.conf('remove_complete', default = False):
+ return self.processComplete(item = item, delete_files = self.conf('delete_files', default = False))
+
+ return False
+ return
+
+ def processComplete(self, item, delete_files):
+ return
+
+ def isCorrectProtocol(self, item_protocol):
+ is_correct = item_protocol in self.protocol
if not is_correct:
- log.debug("Downloader doesn't support this type")
+ log.debug("Downloader doesn't support this protocol")
return is_correct
@@ -101,7 +122,7 @@ class Downloader(Provider):
except:
log.debug('Torrent hash "%s" wasn\'t found on: %s', (torrent_hash, source))
- log.error('Failed converting magnet url to torrent: %s', (torrent_hash))
+ log.error('Failed converting magnet url to torrent: %s', torrent_hash)
return False
def downloadReturnId(self, download_id):
@@ -110,20 +131,38 @@ class Downloader(Provider):
'id': download_id
}
- def isDisabled(self, manual, data):
+ def isDisabled(self, manual = False, data = None):
+ if not data: data = {}
+
return not self.isEnabled(manual, data)
- def _isEnabled(self, manual, data = {}):
+ def _isEnabled(self, manual, data = None):
+ if not data: data = {}
+
if not self.isEnabled(manual, data):
return
return True
- def isEnabled(self, manual, data = {}):
+ def isEnabled(self, manual = False, data = None):
+ if not data: data = {}
+
d_manual = self.conf('manual', default = False)
return super(Downloader, self).isEnabled() and \
- ((d_manual and manual) or (d_manual is False)) and \
- (not data or self.isCorrectType(data.get('type')))
+ (d_manual and manual or d_manual is False) and \
+ (not data or self.isCorrectProtocol(data.get('protocol')))
+ def _pause(self, item, pause = True):
+ if self.isDisabled(manual = True, data = {}):
+ return
+
+ if item and item.get('downloader') == self.getName():
+ self.pause(item, pause)
+ return True
+
+ return False
+
+ def pause(self, item, pause):
+ return
class StatusList(list):
diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py
index aad9ea7f..9a5a6217 100644
--- a/couchpotato/core/downloaders/blackhole/main.py
+++ b/couchpotato/core/downloaders/blackhole/main.py
@@ -7,22 +7,25 @@ import traceback
log = CPLog(__name__)
+
class Blackhole(Downloader):
- type = ['nzb', 'torrent', 'torrent_magnet']
+ protocol = ['nzb', 'torrent', 'torrent_magnet']
- def download(self, data = {}, movie = {}, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
directory = self.conf('directory')
if not directory or not os.path.isdir(directory):
- log.error('No directory set for blackhole %s download.', data.get('type'))
+ log.error('No directory set for blackhole %s download.', data.get('protocol'))
else:
try:
if not filedata or len(filedata) < 50:
try:
- if data.get('type') == 'torrent_magnet':
+ if data.get('protocol') == 'torrent_magnet':
filedata = self.magnetToTorrent(data.get('url'))
- data['type'] = 'torrent'
+ data['protocol'] = 'torrent'
except:
log.error('Failed download torrent via magnet url: %s', traceback.format_exc())
@@ -34,7 +37,7 @@ class Blackhole(Downloader):
try:
if not os.path.isfile(fullPath):
- log.info('Downloading %s to %s.', (data.get('type'), fullPath))
+ log.info('Downloading %s to %s.', (data.get('protocol'), fullPath))
with open(fullPath, 'wb') as f:
f.write(filedata)
os.chmod(fullPath, Env.getPermission('file'))
@@ -53,20 +56,21 @@ class Blackhole(Downloader):
return False
- def getEnabledDownloadType(self):
+ def getEnabledProtocol(self):
if self.conf('use_for') == 'both':
- return super(Blackhole, self).getEnabledDownloadType()
+ return super(Blackhole, self).getEnabledProtocol()
elif self.conf('use_for') == 'torrent':
return ['torrent', 'torrent_magnet']
else:
return ['nzb']
- def isEnabled(self, manual, data = {}):
- for_type = ['both']
- if data and 'torrent' in data.get('type'):
- for_type.append('torrent')
+ def isEnabled(self, manual = False, data = None):
+ if not data: data = {}
+ for_protocol = ['both']
+ if data and 'torrent' in data.get('protocol'):
+ for_protocol.append('torrent')
elif data:
- for_type.append(data.get('type'))
+ for_protocol.append(data.get('protocol'))
return super(Blackhole, self).isEnabled(manual, data) and \
- ((self.conf('use_for') in for_type))
+ ((self.conf('use_for') in for_protocol))
diff --git a/couchpotato/core/downloaders/deluge/__init__.py b/couchpotato/core/downloaders/deluge/__init__.py
new file mode 100644
index 00000000..c7aa26e6
--- /dev/null
+++ b/couchpotato/core/downloaders/deluge/__init__.py
@@ -0,0 +1,90 @@
+from .main import Deluge
+
+def start():
+ return Deluge()
+
+config = [{
+ 'name': 'deluge',
+ 'groups': [
+ {
+ 'tab': 'downloaders',
+ 'list': 'download_providers',
+ 'name': 'deluge',
+ 'label': 'Deluge',
+ 'description': 'Use Deluge to download torrents.',
+ 'wizard': True,
+ 'options': [
+ {
+ 'name': 'enabled',
+ 'default': 0,
+ 'type': 'enabler',
+ 'radio_group': 'torrent',
+ },
+ {
+ 'name': 'host',
+ 'default': 'localhost:58846',
+ 'description': 'Hostname with port. Usually localhost:58846',
+ },
+ {
+ 'name': 'username',
+ },
+ {
+ 'name': 'password',
+ 'type': 'password',
+ },
+ {
+ 'name': 'directory',
+ 'type': 'directory',
+ 'description': 'Download to this directory. Keep empty for default Deluge download directory.',
+ },
+ {
+ 'name': 'completed_directory',
+ 'type': 'directory',
+ 'description': 'Move completed torrent to this directory. Keep empty for default Deluge options.',
+ 'advanced': True,
+ },
+ {
+ 'name': 'label',
+ 'description': 'Label to add to torrents in the Deluge UI.',
+ },
+ {
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'type': 'bool',
+ 'default': True,
+ 'advanced': True,
+ 'description': 'Remove the torrent from Deluge after it has finished seeding.',
+ },
+ {
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Also remove the leftover files.',
+ },
+ {
+ 'name': 'paused',
+ 'type': 'bool',
+ 'advanced': True,
+ 'default': False,
+ 'description': 'Add the torrent paused.',
+ },
+ {
+ 'name': 'manual',
+ 'default': 0,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
+ },
+ {
+ 'name': 'delete_failed',
+ 'default': True,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Delete a release after the download has failed.',
+ },
+ ],
+ }
+ ],
+}]
diff --git a/couchpotato/core/downloaders/deluge/main.py b/couchpotato/core/downloaders/deluge/main.py
new file mode 100644
index 00000000..580ed7ff
--- /dev/null
+++ b/couchpotato/core/downloaders/deluge/main.py
@@ -0,0 +1,244 @@
+from base64 import b64encode
+from couchpotato.core.downloaders.base import Downloader, StatusList
+from couchpotato.core.helpers.encoding import isInt, ss
+from couchpotato.core.helpers.variable import tryFloat
+from couchpotato.core.logger import CPLog
+from couchpotato.environment import Env
+from datetime import timedelta
+from synchronousdeluge import DelugeClient
+import os.path
+import traceback
+
+log = CPLog(__name__)
+
+
+class Deluge(Downloader):
+
+ protocol = ['torrent', 'torrent_magnet']
+ log = CPLog(__name__)
+ drpc = None
+
+ def connect(self):
+ # Load host from config and split out port.
+ host = self.conf('host').split(':')
+ if not isInt(host[1]):
+ log.error('Config properties are not filled in correctly, port is missing.')
+ return False
+
+ if not self.drpc:
+ self.drpc = DelugeRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+
+ return self.drpc
+
+ def download(self, data, movie, filedata = None):
+ log.info('Sending "%s" (%s) to Deluge.', (data.get('name'), data.get('protocol')))
+
+ if not self.connect():
+ return False
+
+ if not filedata and data.get('protocol') == 'torrent':
+ log.error('Failed sending torrent, no data')
+ return False
+
+ # Set parameters for Deluge
+ options = {
+ 'add_paused': self.conf('paused', default = 0),
+ 'label': self.conf('label')
+ }
+
+ if self.conf('directory'):
+ if os.path.isdir(self.conf('directory')):
+ options['download_location'] = self.conf('directory')
+ else:
+ log.error('Download directory from Deluge settings: %s doesn\'t exist', self.conf('directory'))
+
+ if self.conf('completed_directory'):
+ if os.path.isdir(self.conf('completed_directory')):
+ options['move_completed'] = 1
+ options['move_completed_path'] = self.conf('completed_directory')
+ else:
+ log.error('Download directory from Deluge settings: %s doesn\'t exist', self.conf('directory'))
+
+ if data.get('seed_ratio'):
+ options['stop_at_ratio'] = 1
+ options['stop_ratio'] = tryFloat(data.get('seed_ratio'))
+
+# Deluge only has seed time as a global option. Might be added in
+# in a future API release.
+# if data.get('seed_time'):
+
+ # Send request to Deluge
+ if data.get('protocol') == 'torrent_magnet':
+ remote_torrent = self.drpc.add_torrent_magnet(data.get('url'), options)
+ else:
+ filename = self.createFileName(data, filedata, movie)
+ remote_torrent = self.drpc.add_torrent_file(filename, b64encode(filedata), options)
+
+ if not remote_torrent:
+ log.error('Failed sending torrent to Deluge')
+ return False
+
+ log.info('Torrent sent to Deluge successfully.')
+ return self.downloadReturnId(remote_torrent)
+
+ def getAllDownloadStatus(self):
+
+ log.debug('Checking Deluge download status.')
+
+ if not os.path.isdir(Env.setting('from', 'renamer')):
+ log.error('Renamer "from" folder doesn\'t to exist.')
+ return
+
+ if not self.connect():
+ return False
+
+ statuses = StatusList(self)
+
+ queue = self.drpc.get_alltorrents()
+
+ if not queue:
+ log.debug('Nothing in queue or error')
+ return False
+
+ for torrent_id in queue:
+ item = queue[torrent_id]
+ log.debug('name=%s / id=%s / save_path=%s / move_completed_path=%s / hash=%s / progress=%s / state=%s / eta=%s / ratio=%s / stop_ratio=%s / is_seed=%s / is_finished=%s / paused=%s', (item['name'], item['hash'], item['save_path'], item['move_completed_path'], item['hash'], item['progress'], item['state'], item['eta'], item['ratio'], item['stop_ratio'], item['is_seed'], item['is_finished'], item['paused']))
+
+ # Deluge has no easy way to work out if a torrent is stalled or failing.
+ #status = 'failed'
+ status = 'busy'
+ if item['is_seed'] and tryFloat(item['ratio']) < tryFloat(item['stop_ratio']):
+ # We have item['seeding_time'] to work out what the seeding time is, but we do not
+ # have access to the downloader seed_time, as with deluge we have no way to pass it
+ # when the torrent is added. So Deluge will only look at the ratio.
+ # See above comment in download().
+ status = 'seeding'
+ elif item['is_seed'] and item['is_finished'] and item['paused'] and item['state'] == 'Paused':
+ status = 'completed'
+
+ download_dir = item['save_path']
+ if item['move_on_completed']:
+ download_dir = item['move_completed_path']
+
+ statuses.append({
+ 'id': item['hash'],
+ 'name': item['name'],
+ 'status': status,
+ 'original_status': item['state'],
+ 'seed_ratio': item['ratio'],
+ 'timeleft': str(timedelta(seconds = item['eta'])),
+ 'folder': ss(os.path.join(download_dir, item['name'])),
+ })
+
+ return statuses
+
+ def pause(self, item, pause = True):
+ if pause:
+ return self.drpc.pause_torrent([item['id']])
+ else:
+ return self.drpc.resume_torrent([item['id']])
+
+ def removeFailed(self, item):
+ log.info('%s failed downloading, deleting...', item['name'])
+ return self.drpc.remove_torrent(item['id'], True)
+
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting Deluge to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ return self.drpc.remove_torrent(item['id'], remove_local_data = delete_files)
+
+class DelugeRPC(object):
+
+ host = 'localhost'
+ port = 58846
+ username = None
+ password = None
+ client = None
+
+ def __init__(self, host = 'localhost', port = 58846, username = None, password = None):
+ super(DelugeRPC, self).__init__()
+
+ self.host = host
+ self.port = port
+ self.username = username
+ self.password = password
+
+ def connect(self):
+ self.client = DelugeClient()
+ self.client.connect(self.host, int(self.port), self.username, self.password)
+
+ def add_torrent_magnet(self, torrent, options):
+ torrent_id = False
+ try:
+ self.connect()
+ torrent_id = self.client.core.add_torrent_magnet(torrent, options).get()
+ if options['label']:
+ self.client.label.set_torrent(torrent_id, options['label']).get()
+ except Exception, err:
+ log.error('Failed to add torrent magnet %s: %s %s', (torrent, err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+
+ return torrent_id
+
+ def add_torrent_file(self, filename, torrent, options):
+ torrent_id = False
+ try:
+ self.connect()
+ torrent_id = self.client.core.add_torrent_file(filename, torrent, options).get()
+ if options['label']:
+ self.client.label.set_torrent(torrent_id, options['label']).get()
+ except Exception, err:
+ log.error('Failed to add torrent file %s: %s %s', (filename, err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+
+ return torrent_id
+
+ def get_alltorrents(self):
+ ret = False
+ try:
+ self.connect()
+ ret = self.client.core.get_torrents_status({}, {}).get()
+ except Exception, err:
+ log.error('Failed to get all torrents: %s %s', (err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+ return ret
+
+ def pause_torrent(self, torrent_ids):
+ try:
+ self.connect()
+ self.client.core.pause_torrent(torrent_ids).get()
+ except Exception, err:
+ log.error('Failed to pause torrent: %s %s', (err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+
+ def resume_torrent(self, torrent_ids):
+ try:
+ self.connect()
+ self.client.core.resume_torrent(torrent_ids).get()
+ except Exception, err:
+ log.error('Failed to resume torrent: %s %s', (err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+
+ def remove_torrent(self, torrent_id, remove_local_data):
+ ret = False
+ try:
+ self.connect()
+ ret = self.client.core.remove_torrent(torrent_id, remove_local_data).get()
+ except Exception, err:
+ log.error('Failed to remove torrent: %s %s', (err, traceback.format_exc()))
+ finally:
+ if self.client:
+ self.disconnect()
+ return ret
+
+ def disconnect(self):
+ self.client.disconnect()
diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py
index a17b2dc5..19483713 100644
--- a/couchpotato/core/downloaders/nzbget/__init__.py
+++ b/couchpotato/core/downloaders/nzbget/__init__.py
@@ -42,6 +42,7 @@ config = [{
},
{
'name': 'priority',
+ 'advanced': True,
'default': '0',
'type': 'dropdown',
'values': [('Very Low', -100), ('Low', -50), ('Normal', 0), ('High', 50), ('Very High', 100)],
@@ -57,6 +58,7 @@ config = [{
{
'name': 'delete_failed',
'default': True,
+ 'advanced': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py
index 43061e17..b7cf0263 100644
--- a/couchpotato/core/downloaders/nzbget/main.py
+++ b/couchpotato/core/downloaders/nzbget/main.py
@@ -12,13 +12,16 @@ import xmlrpclib
log = CPLog(__name__)
+
class NZBGet(Downloader):
- type = ['nzb']
+ protocol = ['nzb']
url = 'http://%(username)s:%(password)s@%(host)s/xmlrpc'
- def download(self, data = {}, movie = {}, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
if not filedata:
log.error('Unable to get NZB file: %s', traceback.format_exc())
@@ -32,7 +35,7 @@ class NZBGet(Downloader):
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to drop off %s.' % nzb_name):
- log.info('Successfully connected to NZBGet')
+ log.debug('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
@@ -73,7 +76,7 @@ class NZBGet(Downloader):
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to check status'):
- log.info('Successfully connected to NZBGet')
+ log.debug('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
@@ -139,10 +142,10 @@ class NZBGet(Downloader):
statuses.append({
'id': nzb_id,
'name': item['NZBFilename'],
- 'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed',
+ 'status': 'completed' if item['ParStatus'] in ['SUCCESS','NONE'] and item['ScriptStatus'] in ['SUCCESS','NONE'] else 'failed',
'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'],
'timeleft': str(timedelta(seconds = 0)),
- 'folder': item['DestDir']
+ 'folder': ss(item['DestDir'])
})
return statuses
@@ -151,12 +154,12 @@ class NZBGet(Downloader):
log.info('%s failed downloading, deleting...', item['name'])
- url = self.url % {'host': self.conf('host'), 'password': self.conf('password')}
+ url = self.url % {'host': self.conf('host'), 'username': self.conf('username'), 'password': self.conf('password')}
rpc = xmlrpclib.ServerProxy(url)
try:
if rpc.writelog('INFO', 'CouchPotato connected to delete some history'):
- log.info('Successfully connected to NZBGet')
+ log.debug('Successfully connected to NZBGet')
else:
log.info('Successfully connected to NZBGet, but unable to send a message')
except socket.error:
@@ -171,11 +174,16 @@ class NZBGet(Downloader):
try:
history = rpc.history()
+ nzb_id = None
+ path = None
+
for hist in history:
- if hist['Parameters'] and hist['Parameters']['couchpotato'] and hist['Parameters']['couchpotato'] == item['id']:
- nzb_id = hist['ID']
- path = hist['DestDir']
- if rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
+ for param in hist['Parameters']:
+ if param['Name'] == 'couchpotato' and param['Value'] == item['id']:
+ nzb_id = hist['ID']
+ path = hist['DestDir']
+
+ if nzb_id and path and rpc.editqueue('HistoryDelete', 0, "", [tryInt(nzb_id)]):
shutil.rmtree(path, True)
except:
log.error('Failed deleting: %s', traceback.format_exc(0))
diff --git a/couchpotato/core/downloaders/nzbvortex/__init__.py b/couchpotato/core/downloaders/nzbvortex/__init__.py
index f1604ea8..3b95698e 100644
--- a/couchpotato/core/downloaders/nzbvortex/__init__.py
+++ b/couchpotato/core/downloaders/nzbvortex/__init__.py
@@ -38,6 +38,7 @@ config = [{
{
'name': 'delete_failed',
'default': True,
+ 'advanced': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py
index f1f8acc6..a652f110 100644
--- a/couchpotato/core/downloaders/nzbvortex/main.py
+++ b/couchpotato/core/downloaders/nzbvortex/main.py
@@ -16,13 +16,16 @@ import urllib2
log = CPLog(__name__)
+
class NZBVortex(Downloader):
- type = ['nzb']
+ protocol = ['nzb']
api_level = None
session_id = None
- def download(self, data = {}, movie = {}, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
# Send the nzb
try:
@@ -55,8 +58,8 @@ class NZBVortex(Downloader):
'name': item['uiTitle'],
'status': status,
'original_status': item['state'],
- 'timeleft': -1,
- 'folder': item['destinationPath'],
+ 'timeleft':-1,
+ 'folder': ss(item['destinationPath']),
})
return statuses
@@ -96,9 +99,10 @@ class NZBVortex(Downloader):
return False
- def call(self, call, parameters = {}, repeat = False, auth = True, *args, **kwargs):
+ def call(self, call, parameters = None, repeat = False, auth = True, *args, **kwargs):
# Login first
+ if not parameters: parameters = {}
if not self.session_id and auth:
self.login()
@@ -121,7 +125,7 @@ class NZBVortex(Downloader):
# Try login and do again
if not repeat:
self.login()
- return self.call(call, parameters = parameters, repeat = True, *args, **kwargs)
+ return self.call(call, parameters = parameters, repeat = True, **kwargs)
log.error('Failed to parsing %s: %s', (self.getName(), traceback.format_exc()))
except:
@@ -147,7 +151,8 @@ class NZBVortex(Downloader):
return self.api_level
- def isEnabled(self, manual, data):
+ def isEnabled(self, manual = False, data = None):
+ if not data: data = {}
return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel()
diff --git a/couchpotato/core/downloaders/pneumatic/main.py b/couchpotato/core/downloaders/pneumatic/main.py
index 5e2b7854..643350e1 100644
--- a/couchpotato/core/downloaders/pneumatic/main.py
+++ b/couchpotato/core/downloaders/pneumatic/main.py
@@ -6,12 +6,15 @@ import traceback
log = CPLog(__name__)
+
class Pneumatic(Downloader):
- type = ['nzb']
+ protocol = ['nzb']
strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s'
- def download(self, data = {}, movie = {}, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
directory = self.conf('directory')
if not directory or not os.path.isdir(directory):
@@ -26,7 +29,7 @@ class Pneumatic(Downloader):
try:
if not os.path.isfile(fullPath):
- log.info('Downloading %s to %s.', (data.get('type'), fullPath))
+ log.info('Downloading %s to %s.', (data.get('protocol'), fullPath))
with open(fullPath, 'wb') as f:
f.write(filedata)
diff --git a/couchpotato/core/downloaders/rtorrent/__init__.py b/couchpotato/core/downloaders/rtorrent/__init__.py
new file mode 100755
index 00000000..efc2234b
--- /dev/null
+++ b/couchpotato/core/downloaders/rtorrent/__init__.py
@@ -0,0 +1,71 @@
+from .main import rTorrent
+
+def start():
+ return rTorrent()
+
+config = [{
+ 'name': 'rtorrent',
+ 'groups': [
+ {
+ 'tab': 'downloaders',
+ 'list': 'download_providers',
+ 'name': 'rtorrent',
+ 'label': 'rTorrent',
+ 'description': '',
+ 'wizard': True,
+ 'options': [
+ {
+ 'name': 'enabled',
+ 'default': 0,
+ 'type': 'enabler',
+ 'radio_group': 'torrent',
+ },
+ {
+ 'name': 'url',
+ 'default': 'http://localhost:80/RPC2',
+ },
+ {
+ 'name': 'username',
+ },
+ {
+ 'name': 'password',
+ 'type': 'password',
+ },
+ {
+ 'name': 'label',
+ 'description': 'Label to apply on added torrents.',
+ },
+ {
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'default': False,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Remove the torrent after it finishes seeding.',
+ },
+ {
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Also remove the leftover files.',
+ },
+ {
+ 'name': 'paused',
+ 'type': 'bool',
+ 'advanced': True,
+ 'default': False,
+ 'description': 'Add the torrent paused.',
+ },
+ {
+ 'name': 'manual',
+ 'default': 0,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
+ },
+ ],
+ }
+ ],
+}]
diff --git a/couchpotato/core/downloaders/rtorrent/main.py b/couchpotato/core/downloaders/rtorrent/main.py
new file mode 100755
index 00000000..161c671a
--- /dev/null
+++ b/couchpotato/core/downloaders/rtorrent/main.py
@@ -0,0 +1,201 @@
+from base64 import b16encode, b32decode
+from bencode import bencode, bdecode
+from couchpotato.core.downloaders.base import Downloader, StatusList
+from couchpotato.core.helpers.encoding import ss
+from couchpotato.core.logger import CPLog
+from datetime import timedelta
+from hashlib import sha1
+from rtorrent import RTorrent
+from rtorrent.err import MethodError
+import shutil
+
+log = CPLog(__name__)
+
+
+class rTorrent(Downloader):
+
+ protocol = ['torrent', 'torrent_magnet']
+ rt = None
+
+ def connect(self):
+ # Already connected?
+ if self.rt is not None:
+ return self.rt
+
+ # Ensure url is set
+ if not self.conf('url'):
+ log.error('Config properties are not filled in correctly, url is missing.')
+ return False
+
+ if self.conf('username') and self.conf('password'):
+ self.rt = RTorrent(
+ self.conf('url'),
+ self.conf('username'),
+ self.conf('password')
+ )
+ else:
+ self.rt = RTorrent(self.conf('url'))
+
+ return self.rt
+
+ def _update_provider_group(self, name, data):
+ if data.get('seed_time'):
+ log.info('seeding time ignored, not supported')
+
+ if not name:
+ return False
+
+ if not self.connect():
+ return False
+
+ views = self.rt.get_views()
+
+ if name not in views:
+ self.rt.create_group(name)
+
+ group = self.rt.get_group(name)
+
+ try:
+ if data.get('seed_ratio'):
+ ratio = int(float(data.get('seed_ratio')) * 100)
+ log.debug('Updating provider ratio to %s, group name: %s', (ratio, name))
+
+ # Explicitly set all group options to ensure it is setup correctly
+ group.set_upload('1M')
+ group.set_min(ratio)
+ group.set_max(ratio)
+ group.set_command('d.stop')
+ group.enable()
+ else:
+ # Reset group action and disable it
+ group.set_command()
+ group.disable()
+ except MethodError, err:
+ log.error('Unable to set group options: %s', err.message)
+ return False
+
+ return True
+
+
+ def download(self, data, movie, filedata = None):
+ log.debug('Sending "%s" to rTorrent.', (data.get('name')))
+
+ if not self.connect():
+ return False
+
+ group_name = 'cp_' + data.get('provider').lower()
+ if not self._update_provider_group(group_name, data):
+ return False
+
+ torrent_params = {}
+ if self.conf('label'):
+ torrent_params['label'] = self.conf('label')
+
+ if not filedata and data.get('protocol') == 'torrent':
+ log.error('Failed sending torrent, no data')
+ return False
+
+ # Try download magnet torrents
+ if data.get('protocol') == 'torrent_magnet':
+ filedata = self.magnetToTorrent(data.get('url'))
+
+ if filedata is False:
+ return False
+
+ data['protocol'] = 'torrent'
+
+ info = bdecode(filedata)["info"]
+ torrent_hash = sha1(bencode(info)).hexdigest().upper()
+
+ # Convert base 32 to hex
+ if len(torrent_hash) == 32:
+ torrent_hash = b16encode(b32decode(torrent_hash))
+
+ # Send request to rTorrent
+ try:
+ # Send torrent to rTorrent
+ torrent = self.rt.load_torrent(filedata)
+
+ # Set label
+ if self.conf('label'):
+ torrent.set_custom(1, self.conf('label'))
+
+ # Set Ratio Group
+ torrent.set_visible(group_name)
+
+ # Start torrent
+ if not self.conf('paused', default = 0):
+ torrent.start()
+
+ return self.downloadReturnId(torrent_hash)
+ except Exception, err:
+ log.error('Failed to send torrent to rTorrent: %s', err)
+ return False
+
+ def getAllDownloadStatus(self):
+ log.debug('Checking rTorrent download status.')
+
+ if not self.connect():
+ return False
+
+ try:
+ torrents = self.rt.get_torrents()
+
+ statuses = StatusList(self)
+
+ for item in torrents:
+ status = 'busy'
+ if item.complete:
+ if item.active:
+ status = 'seeding'
+ else:
+ status = 'completed'
+
+ statuses.append({
+ 'id': item.info_hash,
+ 'name': item.name,
+ 'status': status,
+ 'seed_ratio': item.ratio,
+ 'original_status': item.state,
+ 'timeleft': str(timedelta(seconds = float(item.left_bytes) / item.down_rate)) if item.down_rate > 0 else -1,
+ 'folder': ss(item.directory)
+ })
+
+ return statuses
+
+ except Exception, err:
+ log.error('Failed to get status from rTorrent: %s', err)
+ return False
+
+ def pause(self, download_info, pause = True):
+ if not self.connect():
+ return False
+
+ torrent = self.rt.find_torrent(download_info['id'])
+ if torrent is None:
+ return False
+
+ if pause:
+ return torrent.pause()
+ return torrent.resume()
+
+ def removeFailed(self, item):
+ log.info('%s failed downloading, deleting...', item['name'])
+ return self.processComplete(item, delete_files = True)
+
+ def processComplete(self, item, delete_files):
+ log.debug('Requesting rTorrent to remove the torrent %s%s.',
+ (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ if not self.connect():
+ return False
+
+ torrent = self.rt.find_torrent(item['id'])
+ if torrent is None:
+ return False
+
+ torrent.erase() # just removes the torrent, doesn't delete data
+
+ if delete_files:
+ shutil.rmtree(item['folder'], True)
+
+ return True
diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py
index f17db9c1..48692dae 100644
--- a/couchpotato/core/downloaders/sabnzbd/__init__.py
+++ b/couchpotato/core/downloaders/sabnzbd/__init__.py
@@ -34,6 +34,15 @@ config = [{
'label': 'Category',
'description': 'The category CP places the nzb in. Like movies or couchpotato',
},
+ {
+ 'name': 'priority',
+ 'label': 'Priority',
+ 'type': 'dropdown',
+ 'default': '0',
+ 'advanced': True,
+ 'values': [('Paused', -2), ('Low', -1), ('Normal', 0), ('High', 1), ('Forced', 2)],
+ 'description': 'Add to the queue with this priority.',
+ },
{
'name': 'manual',
'default': False,
@@ -41,9 +50,18 @@ config = [{
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
+ {
+ 'name': 'remove_complete',
+ 'advanced': True,
+ 'label': 'Remove NZB',
+ 'default': False,
+ 'type': 'bool',
+ 'description': 'Remove the NZB from history after it completed.',
+ },
{
'name': 'delete_failed',
'default': True,
+ 'advanced': True,
'type': 'bool',
'description': 'Delete a release after the download has failed.',
},
diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py
index f2f217a1..08ee409c 100644
--- a/couchpotato/core/downloaders/sabnzbd/main.py
+++ b/couchpotato/core/downloaders/sabnzbd/main.py
@@ -10,11 +10,14 @@ import traceback
log = CPLog(__name__)
+
class Sabnzbd(Downloader):
- type = ['nzb']
+ protocol = ['nzb']
- def download(self, data = {}, movie = {}, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
log.info('Sending "%s" to SABnzbd.', data.get('name'))
@@ -22,11 +25,13 @@ class Sabnzbd(Downloader):
'cat': self.conf('category'),
'mode': 'addurl',
'nzbname': self.createNzbName(data, movie),
+ 'priority': self.conf('priority'),
}
+ nzb_filename = None
if filedata:
if len(filedata) < 50:
- log.error('No proper nzb available: %s', (filedata))
+ log.error('No proper nzb available: %s', filedata)
return False
# If it's a .rar, it adds the .rar extension, otherwise it stays .nzb
@@ -36,7 +41,7 @@ class Sabnzbd(Downloader):
req_params['name'] = data.get('url')
try:
- if req_params.get('mode') is 'addfile':
+ if nzb_filename and req_params.get('mode') is 'addfile':
sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True)
else:
sab_data = self.call(req_params)
@@ -107,7 +112,7 @@ class Sabnzbd(Downloader):
'status': status,
'original_status': item['status'],
'timeleft': str(timedelta(seconds = 0)),
- 'folder': item['storage'],
+ 'folder': ss(item['storage']),
})
return statuses
@@ -129,6 +134,22 @@ class Sabnzbd(Downloader):
return True
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting SabNZBd to remove the NZB %s.', item['name'])
+
+ try:
+ self.call({
+ 'mode': 'history',
+ 'name': 'delete',
+ 'del_files': '0',
+ 'value': item['id']
+ }, use_json = False)
+ except:
+ log.error('Failed removing: %s', traceback.format_exc(0))
+ return False
+
+ return True
+
def call(self, request_params, use_json = True, **kwargs):
url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, {
diff --git a/couchpotato/core/downloaders/synology/main.py b/couchpotato/core/downloaders/synology/main.py
index 87212749..d5082c77 100644
--- a/couchpotato/core/downloaders/synology/main.py
+++ b/couchpotato/core/downloaders/synology/main.py
@@ -9,13 +9,15 @@ log = CPLog(__name__)
class Synology(Downloader):
- type = ['nzb', 'torrent', 'torrent_magnet']
+ protocol = ['nzb', 'torrent', 'torrent_magnet']
log = CPLog(__name__)
- def download(self, data, movie, filedata = None):
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
response = False
- log.error('Sending "%s" (%s) to Synology.', (data['name'], data['type']))
+ log.error('Sending "%s" (%s) to Synology.', (data['name'], data['protocol']))
# Load host from config and split out port.
host = self.conf('host').split(':')
@@ -26,42 +28,44 @@ class Synology(Downloader):
try:
# Send request to Synology
srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password'))
- if data['type'] == 'torrent_magnet':
+ if data['protocol'] == 'torrent_magnet':
log.info('Adding torrent URL %s', data['url'])
response = srpc.create_task(url = data['url'])
- elif data['type'] in ['nzb', 'torrent']:
- log.info('Adding %s' % data['type'])
+ elif data['protocol'] in ['nzb', 'torrent']:
+ log.info('Adding %s' % data['protocol'])
if not filedata:
- log.error('No %s data found' % data['type'])
+ log.error('No %s data found' % data['protocol'])
else:
- filename = data['name'] + '.' + data['type']
+ filename = data['name'] + '.' + data['protocol']
response = srpc.create_task(filename = filename, filedata = filedata)
except Exception, err:
log.error('Exception while adding torrent: %s', err)
finally:
return response
- def getEnabledDownloadType(self):
+ def getEnabledProtocol(self):
if self.conf('use_for') == 'both':
- return super(Synology, self).getEnabledDownloadType()
+ return super(Synology, self).getEnabledProtocol()
elif self.conf('use_for') == 'torrent':
return ['torrent', 'torrent_magnet']
else:
return ['nzb']
- def isEnabled(self, manual, data = {}):
- for_type = ['both']
- if data and 'torrent' in data.get('type'):
- for_type.append('torrent')
+ def isEnabled(self, manual = False, data = None):
+ if not data: data = {}
+
+ for_protocol = ['both']
+ if data and 'torrent' in data.get('protocol'):
+ for_protocol.append('torrent')
elif data:
- for_type.append(data.get('type'))
+ for_protocol.append(data.get('protocol'))
return super(Synology, self).isEnabled(manual, data) and\
- ((self.conf('use_for') in for_type))
+ ((self.conf('use_for') in for_protocol))
class SynologyRPC(object):
- '''SynologyRPC lite library'''
+ """SynologyRPC lite library"""
def __init__(self, host = 'localhost', port = 5000, username = None, password = None):
@@ -98,7 +102,7 @@ class SynologyRPC(object):
req = requests.post(url, data = args, files = files)
req.raise_for_status()
response = json.loads(req.text)
- if response['success'] == True:
+ if response['success']:
log.info('Synology action successfull')
return response
except requests.ConnectionError, err:
@@ -111,11 +115,11 @@ class SynologyRPC(object):
return response
def create_task(self, url = None, filename = None, filedata = None):
- ''' Creates new download task in Synology DownloadStation. Either specify
+ """ Creates new download task in Synology DownloadStation. Either specify
url or pair (filename, filedata).
Returns True if task was created, False otherwise
- '''
+ """
result = False
# login
if self._login():
diff --git a/couchpotato/core/downloaders/transmission/__init__.py b/couchpotato/core/downloaders/transmission/__init__.py
index 6dfbd3f9..f96e628e 100644
--- a/couchpotato/core/downloaders/transmission/__init__.py
+++ b/couchpotato/core/downloaders/transmission/__init__.py
@@ -25,6 +25,13 @@ config = [{
'default': 'localhost:9091',
'description': 'Hostname with port. Usually localhost:9091',
},
+ {
+ 'name': 'rpc_url',
+ 'type': 'string',
+ 'default': 'transmission',
+ 'advanced': True,
+ 'description': 'Change if you don\'t run Transmission RPC at the default url.',
+ },
{
'name': 'username',
},
@@ -32,30 +39,33 @@ config = [{
'name': 'password',
'type': 'password',
},
- {
- 'name': 'paused',
- 'type': 'bool',
- 'default': False,
- 'description': 'Add the torrent paused.',
- },
{
'name': 'directory',
'type': 'directory',
'description': 'Download to this directory. Keep empty for default Transmission download directory.',
},
{
- 'name': 'ratio',
- 'default': 10,
- 'type': 'float',
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'default': True,
'advanced': True,
- 'description': 'Stop transfer when reaching ratio',
+ 'type': 'bool',
+ 'description': 'Remove the torrent from Transmission after it finished seeding.',
},
{
- 'name': 'ratiomode',
- 'default': 0,
- 'type': 'int',
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
'advanced': True,
- 'description': '0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.',
+ 'description': 'Also remove the leftover files.',
+ },
+ {
+ 'name': 'paused',
+ 'type': 'bool',
+ 'advanced': True,
+ 'default': False,
+ 'description': 'Add the torrent paused.',
},
{
'name': 'manual',
@@ -64,6 +74,20 @@ config = [{
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
+ {
+ 'name': 'stalled_as_failed',
+ 'default': True,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Consider a stalled torrent as failed',
+ },
+ {
+ 'name': 'delete_failed',
+ 'default': True,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Delete a release after the download has failed.',
+ },
],
}
],
diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py
index 6094e89b..5ff33c05 100644
--- a/couchpotato/core/downloaders/transmission/main.py
+++ b/couchpotato/core/downloaders/transmission/main.py
@@ -1,6 +1,7 @@
from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader, StatusList
-from couchpotato.core.helpers.encoding import isInt
+from couchpotato.core.helpers.encoding import isInt, ss
+from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from datetime import timedelta
@@ -8,7 +9,6 @@ import httplib
import json
import os.path
import re
-import traceback
import urllib2
log = CPLog(__name__)
@@ -16,151 +16,140 @@ log = CPLog(__name__)
class Transmission(Downloader):
- type = ['torrent', 'torrent_magnet']
+ protocol = ['torrent', 'torrent_magnet']
log = CPLog(__name__)
+ trpc = None
- def download(self, data, movie, filedata = None):
-
- log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type')))
-
+ def connect(self):
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
- # Set parameters for Transmission
- params = {
- 'paused': self.conf('paused', default = 0),
- }
+ if not self.trpc:
+ self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url'), username = self.conf('username'), password = self.conf('password'))
- if len(self.conf('directory', default = '')) > 0:
- folder_name = self.createFileName(data, filedata, movie)[:-len(data.get('type')) - 1]
- params['download-dir'] = os.path.join(self.conf('directory', default = ''), folder_name).rstrip(os.path.sep)
+ return self.trpc
- torrent_params = {}
- if self.conf('ratio'):
- torrent_params = {
- 'seedRatioLimit': self.conf('ratio'),
- 'seedRatioMode': self.conf('ratiomode')
- }
+ def download(self, data, movie, filedata = None):
- if not filedata and data.get('type') == 'torrent':
+ log.info('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('protocol')))
+
+ if not self.connect():
+ return False
+
+ if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
- # Send request to Transmission
- try:
- trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- if data.get('type') == 'torrent_magnet':
- remote_torrent = trpc.add_torrent_uri(data.get('url'), arguments = params)
- torrent_params['trackerAdd'] = self.torrent_trackers
+ # Set parameters for adding torrent
+ params = {
+ 'paused': self.conf('paused', default = False)
+ }
+
+ if self.conf('directory'):
+ if os.path.isdir(self.conf('directory')):
+ params['download-dir'] = self.conf('directory')
else:
- remote_torrent = trpc.add_torrent_file(b64encode(filedata), arguments = params)
+ log.error('Download directory from Transmission settings: %s doesn\'t exist', self.conf('directory'))
- if not remote_torrent:
- return False
+ # Change parameters of torrent
+ torrent_params = {}
+ if data.get('seed_ratio'):
+ torrent_params['seedRatioLimit'] = tryFloat(data.get('seed_ratio'))
+ torrent_params['seedRatioMode'] = 1
- # Change settings of added torrents
- elif torrent_params:
- trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
+ if data.get('seed_time'):
+ torrent_params['seedIdleLimit'] = tryInt(data.get('seed_time')) * 60
+ torrent_params['seedIdleMode'] = 1
- log.info('Torrent sent to Transmission successfully.')
- return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
- except:
- log.error('Failed to change settings for transfer: %s', traceback.format_exc())
+ # Send request to Transmission
+ if data.get('protocol') == 'torrent_magnet':
+ remote_torrent = self.trpc.add_torrent_uri(data.get('url'), arguments = params)
+ torrent_params['trackerAdd'] = self.torrent_trackers
+ else:
+ remote_torrent = self.trpc.add_torrent_file(b64encode(filedata), arguments = params)
+
+ if not remote_torrent:
+ log.error('Failed sending torrent to Transmission')
return False
+ # Change settings of added torrents
+ if torrent_params:
+ self.trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params)
+
+ log.info('Torrent sent to Transmission successfully.')
+ return self.downloadReturnId(remote_torrent['torrent-added']['hashString'])
+
def getAllDownloadStatus(self):
log.debug('Checking Transmission download status.')
- # Load host from config and split out port.
- host = self.conf('host').split(':')
- if not isInt(host[1]):
- log.error('Config properties are not filled in correctly, port is missing.')
+ if not self.connect():
return False
- # Go through Queue
- try:
- trpc = TransmissionRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- return_params = {
- 'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isFinished', 'downloadDir', 'uploadRatio']
- }
- queue = trpc.get_alltorrents(return_params)
- except Exception, err:
- log.error('Failed getting queue: %s', err)
- return False
-
- if not queue:
- return []
-
statuses = StatusList(self)
- # Get torrents status
- # CouchPotato Status
- #status = 'busy'
- #status = 'failed'
- #status = 'completed'
- # Transmission Status
- #status = 0 => "Torrent is stopped"
- #status = 1 => "Queued to check files"
- #status = 2 => "Checking files"
- #status = 3 => "Queued to download"
- #status = 4 => "Downloading"
- #status = 4 => "Queued to seed"
- #status = 6 => "Seeding"
- #To do :
- # add checking file
- # manage no peer in a range time => fail
+ return_params = {
+ 'fields': ['id', 'name', 'hashString', 'percentDone', 'status', 'eta', 'isStalled', 'isFinished', 'downloadDir', 'uploadRatio', 'secondsSeeding', 'seedIdleLimit']
+ }
+
+ queue = self.trpc.get_alltorrents(return_params)
+ if not (queue and queue.get('torrents')):
+ log.debug('Nothing in queue or error')
+ return False
for item in queue['torrents']:
- log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / eta=%s / uploadRatio=%s / confRatio=%s / isFinished=%s', (item['name'], item['id'], item['downloadDir'], item['hashString'], item['percentDone'], item['status'], item['eta'], item['uploadRatio'], self.conf('ratio'), item['isFinished']))
+ log.debug('name=%s / id=%s / downloadDir=%s / hashString=%s / percentDone=%s / status=%s / eta=%s / uploadRatio=%s / isFinished=%s',
+ (item['name'], item['id'], item['downloadDir'], item['hashString'], item['percentDone'], item['status'], item['eta'], item['uploadRatio'], item['isFinished']))
if not os.path.isdir(Env.setting('from', 'renamer')):
log.error('Renamer "from" folder doesn\'t to exist.')
return
- if (item['percentDone'] * 100) >= 100 and (item['status'] == 6 or item['status'] == 0) and item['uploadRatio'] > self.conf('ratio'):
- try:
- trpc.stop_torrent(item['hashString'], {})
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'completed',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = 0)),
- 'folder': os.path.join(item['downloadDir'], item['name']),
- })
- except Exception, err:
- log.error('Failed to stop and remove torrent "%s" with error: %s', (item['name'], err))
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'failed',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = 0)),
- })
- else:
- statuses.append({
- 'id': item['hashString'],
- 'name': item['name'],
- 'status': 'busy',
- 'original_status': item['status'],
- 'timeleft': str(timedelta(seconds = item['eta'])), # Is ETA in seconds??
- })
+ status = 'busy'
+ if item['isStalled'] and self.conf('stalled_as_failed'):
+ status = 'failed'
+ elif item['status'] == 0 and item['percentDone'] == 1:
+ status = 'completed'
+ elif item['status'] in [5, 6]:
+ status = 'seeding'
+
+ statuses.append({
+ 'id': item['hashString'],
+ 'name': item['name'],
+ 'status': status,
+ 'original_status': item['status'],
+ 'seed_ratio': item['uploadRatio'],
+ 'timeleft': str(timedelta(seconds = item['eta'])),
+ 'folder': ss(os.path.join(item['downloadDir'], item['name'])),
+ })
return statuses
+ def pause(self, item, pause = True):
+ if pause:
+ return self.trpc.stop_torrent(item['id'])
+ else:
+ return self.trpc.start_torrent(item['id'])
+
+ def removeFailed(self, item):
+ log.info('%s failed downloading, deleting...', item['name'])
+ return self.trpc.remove_torrent(item['hashString'], True)
+
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting Transmission to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ return self.trpc.remove_torrent(item['hashString'], delete_files)
+
class TransmissionRPC(object):
"""TransmissionRPC lite library"""
-
- def __init__(self, host = 'localhost', port = 9091, username = None, password = None):
+ def __init__(self, host = 'localhost', port = 9091, rpc_url = 'transmission', username = None, password = None):
super(TransmissionRPC, self).__init__()
- self.url = 'http://' + host + ':' + str(port) + '/transmission/rpc'
+ self.url = 'http://' + host + ':' + str(port) + '/' + rpc_url + '/rpc'
self.tag = 0
self.session_id = 0
self.session = {}
@@ -184,7 +173,7 @@ class TransmissionRPC(object):
log.debug('request: %s', json.dumps(ojson))
log.debug('response: %s', json.dumps(response))
if response['result'] == 'success':
- log.debug('Transmission action successfull')
+ log.debug('Transmission action successful')
return response['arguments']
else:
log.debug('Unknown failure sending command to Transmission. Return text is: %s', response['result'])
@@ -236,13 +225,15 @@ class TransmissionRPC(object):
post_data = {'arguments': arguments, 'method': 'torrent-get', 'tag': self.tag}
return self._request(post_data)
- def stop_torrent(self, torrent_id, arguments):
- arguments['ids'] = torrent_id
- post_data = {'arguments': arguments, 'method': 'torrent-stop', 'tag': self.tag}
+ def stop_torrent(self, torrent_id):
+ post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-stop', 'tag': self.tag}
return self._request(post_data)
- def remove_torrent(self, torrent_id, remove_local_data, arguments):
- arguments['ids'] = torrent_id
- arguments['delete-local-data'] = remove_local_data
- post_data = {'arguments': arguments, 'method': 'torrent-remove', 'tag': self.tag}
+ def start_torrent(self, torrent_id):
+ post_data = {'arguments': {'ids': torrent_id}, 'method': 'torrent-start', 'tag': self.tag}
return self._request(post_data)
+
+ def remove_torrent(self, torrent_id, delete_local_data):
+ post_data = {'arguments': {'ids': torrent_id, 'delete-local-data': delete_local_data}, 'method': 'torrent-remove', 'tag': self.tag}
+ return self._request(post_data)
+
diff --git a/couchpotato/core/downloaders/utorrent/__init__.py b/couchpotato/core/downloaders/utorrent/__init__.py
index 2c494eb2..d45e2e6c 100644
--- a/couchpotato/core/downloaders/utorrent/__init__.py
+++ b/couchpotato/core/downloaders/utorrent/__init__.py
@@ -11,7 +11,7 @@ config = [{
'list': 'download_providers',
'name': 'utorrent',
'label': 'uTorrent',
- 'description': 'Use uTorrent to download torrents.',
+ 'description': 'Use uTorrent (3.0+) to download torrents.',
'wizard': True,
'options': [
{
@@ -36,9 +36,26 @@ config = [{
'name': 'label',
'description': 'Label to add torrent as.',
},
+ {
+ 'name': 'remove_complete',
+ 'label': 'Remove torrent',
+ 'default': True,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Remove the torrent from uTorrent after it finished seeding.',
+ },
+ {
+ 'name': 'delete_files',
+ 'label': 'Remove files',
+ 'default': True,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Also remove the leftover files.',
+ },
{
'name': 'paused',
'type': 'bool',
+ 'advanced': True,
'default': False,
'description': 'Add the torrent paused.',
},
@@ -49,6 +66,13 @@ config = [{
'advanced': True,
'description': 'Disable this downloader for automated searches, but use it when I manually send a release.',
},
+ {
+ 'name': 'delete_failed',
+ 'default': True,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Delete a release after the download has failed.',
+ },
],
}
],
diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py
index f49859ab..ce82c8c2 100644
--- a/couchpotato/core/downloaders/utorrent/main.py
+++ b/couchpotato/core/downloaders/utorrent/main.py
@@ -1,7 +1,8 @@
from base64 import b16encode, b32decode
-from bencode import bencode, bdecode
+from bencode import bencode as benc, bdecode
from couchpotato.core.downloaders.base import Downloader, StatusList
from couchpotato.core.helpers.encoding import isInt, ss
+from couchpotato.core.helpers.variable import tryInt, tryFloat
from couchpotato.core.logger import CPLog
from datetime import timedelta
from hashlib import sha1
@@ -9,123 +10,203 @@ from multipartpost import MultipartPostHandler
import cookielib
import httplib
import json
+import os
import re
+import stat
import time
import urllib
import urllib2
-
log = CPLog(__name__)
class uTorrent(Downloader):
- type = ['torrent', 'torrent_magnet']
+ protocol = ['torrent', 'torrent_magnet']
utorrent_api = None
- def download(self, data, movie, filedata = None):
-
- log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type')))
-
+ def connect(self):
# Load host from config and split out port.
host = self.conf('host').split(':')
if not isInt(host[1]):
log.error('Config properties are not filled in correctly, port is missing.')
return False
+ self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+
+ return self.utorrent_api
+
+ def download(self, data = None, movie = None, filedata = None):
+ if not movie: movie = {}
+ if not data: data = {}
+
+ log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('protocol')))
+
+ if not self.connect():
+ return False
+
+ settings = self.utorrent_api.get_settings()
+ if not settings:
+ return False
+
+ #Fix settings in case they are not set for CPS compatibility
+ new_settings = {}
+ if not (settings.get('seed_prio_limitul') == 0 and settings['seed_prio_limitul_flag']):
+ new_settings['seed_prio_limitul'] = 0
+ new_settings['seed_prio_limitul_flag'] = True
+ log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.')
+
+ if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function
+ new_settings['bt.read_only_on_complete'] = False
+ log.info('Updated uTorrent settings to not set the files to read only after completing.')
+
+ if new_settings:
+ self.utorrent_api.set_settings(new_settings)
+
torrent_params = {}
if self.conf('label'):
torrent_params['label'] = self.conf('label')
- if not filedata and data.get('type') == 'torrent':
+ if not filedata and data.get('protocol') == 'torrent':
log.error('Failed sending torrent, no data')
return False
- if data.get('type') == 'torrent_magnet':
+ if data.get('protocol') == 'torrent_magnet':
torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers)
else:
info = bdecode(filedata)["info"]
- torrent_hash = sha1(bencode(info)).hexdigest().upper()
- torrent_filename = self.createFileName(data, filedata, movie)
+ torrent_hash = sha1(benc(info)).hexdigest().upper()
+ torrent_filename = self.createFileName(data, filedata, movie)
+
+ if data.get('seed_ratio'):
+ torrent_params['seed_override'] = 1
+ torrent_params['seed_ratio'] = tryInt(tryFloat(data['seed_ratio']) * 1000)
+
+ if data.get('seed_time'):
+ torrent_params['seed_override'] = 1
+ torrent_params['seed_time'] = tryInt(data['seed_time']) * 3600
# Convert base 32 to hex
if len(torrent_hash) == 32:
torrent_hash = b16encode(b32decode(torrent_hash))
# Send request to uTorrent
- try:
- if not self.utorrent_api:
- self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
+ if data.get('protocol') == 'torrent_magnet':
+ self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'))
+ else:
+ self.utorrent_api.add_torrent_file(torrent_filename, filedata)
- if data.get('type') == 'torrent_magnet':
- self.utorrent_api.add_torrent_uri(data.get('url'))
+ # Change settings of added torrent
+ self.utorrent_api.set_torrent(torrent_hash, torrent_params)
+ if self.conf('paused', default = 0):
+ self.utorrent_api.pause_torrent(torrent_hash)
+
+ count = 0
+ while True:
+
+ count += 1
+ # Check if torrent is saved in subfolder of torrent name
+ data = self.utorrent_api.get_files(torrent_hash)
+
+ torrent_files = json.loads(data)
+ if torrent_files.get('error'):
+ log.error('Error getting data from uTorrent: %s', torrent_files.get('error'))
+ return False
+
+ if (torrent_files.get('files') and len(torrent_files['files'][1]) > 0) or count > 60:
+ break
+
+ time.sleep(1)
+
+ # Torrent has only one file, so uTorrent wont create a folder for it
+ if len(torrent_files['files'][1]) == 1:
+ # Remove torrent and try again
+ self.utorrent_api.remove_torrent(torrent_hash, remove_data = True)
+
+ # Send request to uTorrent
+ if data.get('protocol') == 'torrent_magnet':
+ self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url'), add_folder = True)
else:
- self.utorrent_api.add_torrent_file(torrent_filename, filedata)
+ self.utorrent_api.add_torrent_file(torrent_filename, filedata, add_folder = True)
- # Change settings of added torrents
+ # Change settings of added torrent
self.utorrent_api.set_torrent(torrent_hash, torrent_params)
if self.conf('paused', default = 0):
self.utorrent_api.pause_torrent(torrent_hash)
- return self.downloadReturnId(torrent_hash)
- except Exception, err:
- log.error('Failed to send torrent to uTorrent: %s', err)
- return False
+
+ return self.downloadReturnId(torrent_hash)
def getAllDownloadStatus(self):
log.debug('Checking uTorrent download status.')
- # Load host from config and split out port.
- host = self.conf('host').split(':')
- if not isInt(host[1]):
- log.error('Config properties are not filled in correctly, port is missing.')
- return False
-
- try:
- self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password'))
- except Exception, err:
- log.error('Failed to get uTorrent object: %s', err)
- return False
-
- data = ''
- try:
- data = self.utorrent_api.get_status()
- queue = json.loads(data)
- if queue.get('error'):
- log.error('Error getting data from uTorrent: %s', queue.get('error'))
- return False
-
- except Exception, err:
- log.error('Failed to get status from uTorrent: %s', err)
- return False
-
- if queue.get('torrents', []) == []:
- log.debug('Nothing in queue')
+ if not self.connect():
return False
statuses = StatusList(self)
+ data = self.utorrent_api.get_status()
+ if not data:
+ log.error('Error getting data from uTorrent')
+ return False
+
+ queue = json.loads(data)
+ if queue.get('error'):
+ log.error('Error getting data from uTorrent: %s', queue.get('error'))
+ return False
+
+ if not queue.get('torrents'):
+ log.debug('Nothing in queue')
+ return False
+
# Get torrents
- for item in queue.get('torrents', []):
+ for item in queue['torrents']:
# item[21] = Paused | Downloading | Seeding | Finished
status = 'busy'
- if item[21] == 'Finished' or item[21] == 'Seeding':
+ if 'Finished' in item[21]:
status = 'completed'
+ self.removeReadOnly(item[26])
+ elif 'Seeding' in item[21]:
+ status = 'seeding'
+ self.removeReadOnly(item[26])
statuses.append({
'id': item[0],
'name': item[2],
'status': status,
+ 'seed_ratio': float(item[7]) / 1000,
'original_status': item[1],
'timeleft': str(timedelta(seconds = item[10])),
- 'folder': item[26],
+ 'folder': ss(item[26]),
})
return statuses
+ def pause(self, item, pause = True):
+ if not self.connect():
+ return False
+ return self.utorrent_api.pause_torrent(item['id'], pause)
+ def removeFailed(self, item):
+ log.info('%s failed downloading, deleting...', item['name'])
+ if not self.connect():
+ return False
+ return self.utorrent_api.remove_torrent(item['id'], remove_data = True)
+
+ def processComplete(self, item, delete_files = False):
+ log.debug('Requesting uTorrent to remove the torrent %s%s.', (item['name'], ' and cleanup the downloaded files' if delete_files else ''))
+ if not self.connect():
+ return False
+ return self.utorrent_api.remove_torrent(item['id'], remove_data = delete_files)
+
+ def removeReadOnly(self, folder):
+ #Removes all read-only flags in a folder
+ if folder and os.path.isdir(folder):
+ for root, folders, filenames in os.walk(folder):
+ for filename in filenames:
+ os.chmod(os.path.join(root, filename), stat.S_IWRITE)
class uTorrentAPI(object):
@@ -176,12 +257,16 @@ class uTorrentAPI(object):
token = re.findall("
(.*?)", request.read())[0]
return token
- def add_torrent_uri(self, torrent):
+ def add_torrent_uri(self, filename, torrent, add_folder = False):
action = "action=add-url&s=%s" % urllib.quote(torrent)
+ if add_folder:
+ action += "&path=%s" % urllib.quote(filename)
return self._request(action)
- def add_torrent_file(self, filename, filedata):
+ def add_torrent_file(self, filename, filedata, add_folder = False):
action = "action=add-file"
+ if add_folder:
+ action += "&path=%s" % urllib.quote(filename)
return self._request(action, {"torrent_file": (ss(filename), filedata)})
def set_torrent(self, hash, params):
@@ -190,8 +275,22 @@ class uTorrentAPI(object):
action += "&s=%s&v=%s" % (k, v)
return self._request(action)
- def pause_torrent(self, hash):
- action = "action=pause&hash=%s" % hash
+ def pause_torrent(self, hash, pause = True):
+ if pause:
+ action = "action=pause&hash=%s" % hash
+ else:
+ action = "action=unpause&hash=%s" % hash
+ return self._request(action)
+
+ def stop_torrent(self, hash):
+ action = "action=stop&hash=%s" % hash
+ return self._request(action)
+
+ def remove_torrent(self, hash, remove_data = False):
+ if remove_data:
+ action = "action=removedata&hash=%s" % hash
+ else:
+ action = "action=remove&hash=%s" % hash
return self._request(action)
def get_status(self):
@@ -219,3 +318,17 @@ class uTorrentAPI(object):
log.error('Failed to get settings from uTorrent: %s', err)
return settings_dict
+
+ def set_settings(self, settings_dict = None):
+ if not settings_dict: settings_dict = {}
+
+ for key in settings_dict:
+ if isinstance(settings_dict[key], bool):
+ settings_dict[key] = 1 if settings_dict[key] else 0
+
+ action = 'action=setsetting' + ''.join(['&s=%s&v=%s' % (key, value) for (key, value) in settings_dict.items()])
+ return self._request(action)
+
+ def get_files(self, hash):
+ action = "action=getfiles&hash=%s" % hash
+ return self._request(action)
diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py
index 0e0b4a70..7b01fbd8 100644
--- a/couchpotato/core/event.py
+++ b/couchpotato/core/event.py
@@ -21,9 +21,11 @@ def addEvent(name, handler, priority = 100):
def createHandle(*args, **kwargs):
+ h = None
try:
# Open handler
has_parent = hasattr(handler, 'im_self')
+ parent = None
if has_parent:
parent = handler.im_self
bc = hasattr(parent, 'beforeCall')
@@ -33,7 +35,7 @@ def addEvent(name, handler, priority = 100):
h = runHandler(name, handler, *args, **kwargs)
# Close handler
- if has_parent:
+ if parent and has_parent:
ac = hasattr(parent, 'afterCall')
if ac: parent.afterCall(handler)
except:
@@ -53,11 +55,6 @@ def removeEvent(name, handler):
def fireEvent(name, *args, **kwargs):
if not events.has_key(name): return
- e = Event(name = name, threads = 10, asynch = kwargs.get('async', False), exc_info = True, traceback = True, lock = threading.RLock())
-
- for event in events[name]:
- e.handle(event['handler'], priority = event['priority'])
-
#log.debug('Firing event %s', name)
try:
@@ -67,7 +64,6 @@ def fireEvent(name, *args, **kwargs):
'single': False, # Return single handler
'merge': False, # Merge items
'in_order': False, # Fire them in specific order, waits for the other to finish
- 'async': False
}
# Do options
@@ -78,12 +74,32 @@ def fireEvent(name, *args, **kwargs):
options[x] = val
except: pass
- # Make sure only 1 event is fired at a time when order is wanted
- kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
- kwargs['event_return_on_result'] = options['single']
+ if len(events[name]) == 1:
- # Fire
- result = e(*args, **kwargs)
+ single = None
+ try:
+ single = events[name][0]['handler'](*args, **kwargs)
+ except:
+ log.error('Failed running single event: %s', traceback.format_exc())
+
+ # Don't load thread for single event
+ result = {
+ 'single': (single is not None, single),
+ }
+
+ else:
+
+ e = Event(name = name, threads = 10, exc_info = True, traceback = True, lock = threading.RLock())
+
+ for event in events[name]:
+ e.handle(event['handler'], priority = event['priority'])
+
+ # Make sure only 1 event is fired at a time when order is wanted
+ kwargs['event_order_lock'] = threading.RLock() if options['in_order'] or options['single'] else None
+ kwargs['event_return_on_result'] = options['single']
+
+ # Fire
+ result = e(*args, **kwargs)
if options['single'] and not options['merge']:
results = None
diff --git a/couchpotato/core/helpers/encoding.py b/couchpotato/core/helpers/encoding.py
index a11dd88b..6e864446 100644
--- a/couchpotato/core/helpers/encoding.py
+++ b/couchpotato/core/helpers/encoding.py
@@ -11,7 +11,8 @@ log = CPLog(__name__)
def toSafeString(original):
valid_chars = "-_.() %s%s" % (ascii_letters, digits)
cleanedFilename = unicodedata.normalize('NFKD', toUnicode(original)).encode('ASCII', 'ignore')
- return ''.join(c for c in cleanedFilename if c in valid_chars)
+ valid_string = ''.join(c for c in cleanedFilename if c in valid_chars)
+ return ' '.join(valid_string.split())
def simplifyString(original):
string = stripAccents(original.lower())
@@ -62,7 +63,7 @@ def stripAccents(s):
def tryUrlencode(s):
new = u''
- if isinstance(s, (dict)):
+ if isinstance(s, dict):
for key, value in s.iteritems():
new += u'&%s=%s' % (key, tryUrlencode(value))
diff --git a/couchpotato/core/helpers/request.py b/couchpotato/core/helpers/request.py
index c2249796..888e63fd 100644
--- a/couchpotato/core/helpers/request.py
+++ b/couchpotato/core/helpers/request.py
@@ -8,7 +8,7 @@ def getParams(params):
reg = re.compile('^[a-z0-9_\.]+$')
- current = temp = {}
+ temp = {}
for param, value in sorted(params.iteritems()):
nest = re.split("([\[\]]+)", param)
diff --git a/couchpotato/core/helpers/rss.py b/couchpotato/core/helpers/rss.py
index d88fdb53..b840d862 100644
--- a/couchpotato/core/helpers/rss.py
+++ b/couchpotato/core/helpers/rss.py
@@ -6,7 +6,7 @@ log = CPLog(__name__)
class RSS(object):
def getTextElements(self, xml, path):
- ''' Find elements and return tree'''
+ """ Find elements and return tree"""
textelements = []
try:
@@ -28,7 +28,7 @@ class RSS(object):
return elements
def getElement(self, xml, path):
- ''' Find element and return text'''
+ """ Find element and return text"""
try:
return xml.find(path)
@@ -36,7 +36,7 @@ class RSS(object):
return
def getTextElement(self, xml, path):
- ''' Find element and return text'''
+ """ Find element and return text"""
try:
return xml.find(path).text
diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py
index fa8a8b51..1e7fc837 100644
--- a/couchpotato/core/helpers/variable.py
+++ b/couchpotato/core/helpers/variable.py
@@ -1,4 +1,4 @@
-from couchpotato.core.helpers.encoding import simplifyString, toSafeString
+from couchpotato.core.helpers.encoding import simplifyString, toSafeString, ss
from couchpotato.core.logger import CPLog
import hashlib
import os.path
@@ -101,11 +101,16 @@ def flattenList(l):
return l
def md5(text):
- return hashlib.md5(text).hexdigest()
+ return hashlib.md5(ss(text)).hexdigest()
def sha1(text):
return hashlib.sha1(text).hexdigest()
+def isLocalIP(ip):
+ ip = ip.lstrip('htps:/')
+ regex = '/(^127\.)|(^192\.168\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^::1)$/'
+ return re.search(regex, ip) is not None or 'localhost' in ip or ip[:4] == '127.'
+
def getExt(filename):
return os.path.splitext(filename)[1][1:]
@@ -113,13 +118,15 @@ def cleanHost(host):
if not host.startswith(('http://', 'https://')):
host = 'http://' + host
- if not host.endswith('/'):
- host += '/'
+ host = host.rstrip('/')
+ host += '/'
return host
def getImdb(txt, check_inside = True, multiple = False):
+ txt = ss(txt)
+
if check_inside and os.path.isfile(txt):
output = open(txt, 'r')
txt = output.read()
@@ -128,7 +135,7 @@ def getImdb(txt, check_inside = True, multiple = False):
try:
ids = re.findall('(tt\d{7})', txt)
if multiple:
- return ids if len(ids) > 0 else []
+ return list(set(ids)) if len(ids) > 0 else []
return ids[0]
except IndexError:
pass
@@ -140,7 +147,11 @@ def tryInt(s):
except: return 0
def tryFloat(s):
- try: return float(s) if '.' in s else tryInt(s)
+ try:
+ if isinstance(s, str):
+ return float(s) if '.' in s else tryInt(s)
+ else:
+ return float(s)
except: return 0
def natsortKey(s):
@@ -159,8 +170,11 @@ def getTitle(library_dict):
if title.default:
return title.title
except:
- log.error('Could not get title for %s', library_dict.identifier)
- return None
+ try:
+ return library_dict['info']['titles'][0]
+ except:
+ log.error('Could not get title for %s', library_dict.identifier)
+ return None
log.error('Could not get title for %s', library_dict['identifier'])
return None
@@ -170,11 +184,15 @@ def getTitle(library_dict):
def possibleTitles(raw_title):
- titles = []
+ titles = [
+ toSafeString(raw_title).lower(),
+ raw_title.lower(),
+ simplifyString(raw_title)
+ ]
- titles.append(toSafeString(raw_title).lower())
- titles.append(raw_title.lower())
- titles.append(simplifyString(raw_title))
+ # replace some chars
+ new_title = raw_title.replace('&', 'and')
+ titles.append(simplifyString(new_title))
return list(set(titles))
diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py
index a97437a2..2016d287 100644
--- a/couchpotato/core/loader.py
+++ b/couchpotato/core/loader.py
@@ -6,15 +6,24 @@ import traceback
log = CPLog(__name__)
-class Loader(object):
+class Loader(object):
plugins = {}
providers = {}
-
modules = {}
- def preload(self, root = ''):
+ def addPath(self, root, base_path, priority, recursive = False):
+ for filename in os.listdir(os.path.join(root, *base_path)):
+ path = os.path.join(os.path.join(root, *base_path), filename)
+ if os.path.isdir(path) and filename[:2] != '__':
+ if u'__init__.py' in os.listdir(path):
+ new_base_path = ''.join(s + '.' for s in base_path) + filename
+ self.paths[new_base_path.replace('.', '_')] = (priority, new_base_path, path)
+ if recursive:
+ self.addPath(root, base_path + [filename], priority, recursive = True)
+
+ def preload(self, root = ''):
core = os.path.join(root, 'couchpotato', 'core')
self.paths = {
@@ -25,12 +34,10 @@ class Loader(object):
}
# Add providers to loader
- provider_dir = os.path.join(root, 'couchpotato', 'core', 'providers')
- for provider in os.listdir(provider_dir):
- path = os.path.join(provider_dir, provider)
- if os.path.isdir(path):
- self.paths[provider + '_provider'] = (25, 'couchpotato.core.providers.' + provider, path)
+ self.addPath(root, ['couchpotato', 'core', 'providers'], 25, recursive = False)
+ # Add media to loader
+ self.addPath(root, ['couchpotato', 'core', 'media'], 25, recursive = True)
for plugin_type, plugin_tuple in self.paths.iteritems():
priority, module, dir_name = plugin_tuple
@@ -43,7 +50,13 @@ class Loader(object):
for module_name, plugin in sorted(self.modules[priority].iteritems()):
# Load module
try:
- m = getattr(self.loadModule(module_name), plugin.get('name'))
+ if plugin.get('name')[:2] == '__':
+ continue
+
+ m = self.loadModule(module_name)
+ if m is None:
+ continue
+ m = getattr(m, plugin.get('name'))
log.info('Loading %s: %s', (plugin['type'], plugin['name']))
@@ -53,7 +66,7 @@ class Loader(object):
self.loadPlugins(m, plugin.get('name'))
except ImportError as e:
# todo:: subclass ImportError for missing requirements.
- if (e.message.lower().startswith("missing")):
+ if e.message.lower().startswith("missing"):
log.error(e.message)
pass
# todo:: this needs to be more descriptive.
@@ -73,19 +86,21 @@ class Loader(object):
splitted = module.split('.')
for sub in splitted[1:]:
m = getattr(m, sub)
-
- if hasattr(m, 'config'):
- fireEvent('settings.options', splitted[-1] + '_config', getattr(m, 'config'))
except:
raise
for cur_file in glob.glob(os.path.join(dir_name, '*')):
name = os.path.basename(cur_file)
- if os.path.isdir(os.path.join(dir_name, name)):
+ if os.path.isdir(os.path.join(dir_name, name)) and name != 'static' and os.path.isfile(os.path.join(cur_file, '__init__.py')):
module_name = '%s.%s' % (module, name)
self.addModule(priority, plugin_type, module_name, name)
def loadSettings(self, module, name, save = True):
+
+ if not hasattr(module, 'config'):
+ log.debug('Skip loading settings for plugin %s as it has no config section' % module.__file__)
+ return False
+
try:
for section in module.config:
fireEvent('settings.options', section['name'], section)
@@ -100,15 +115,14 @@ class Loader(object):
return False
def loadPlugins(self, module, name):
+
+ if not hasattr(module, 'start'):
+ log.debug('Skip startup for plugin %s as it has no start section' % module.__file__)
+ return False
try:
- klass = module.start()
- klass.registerPlugin()
-
- if klass and getattr(klass, 'auto_register_static'):
- klass.registerStatic(module.__file__)
-
+ module.start()
return True
- except Exception, e:
+ except:
log.error('Failed loading plugin "%s": %s', (module.__file__, traceback.format_exc()))
return False
@@ -131,5 +145,8 @@ class Loader(object):
for sub in splitted[1:-1]:
m = getattr(m, sub)
return m
+ except ImportError:
+ log.debug('Skip loading module plugin %s: %s', (name, traceback.format_exc()))
+ return None
except:
raise
diff --git a/couchpotato/core/media/__init__.py b/couchpotato/core/media/__init__.py
new file mode 100644
index 00000000..1cef967b
--- /dev/null
+++ b/couchpotato/core/media/__init__.py
@@ -0,0 +1,13 @@
+from couchpotato.core.event import addEvent
+from couchpotato.core.plugins.base import Plugin
+
+
+class MediaBase(Plugin):
+
+ _type = None
+
+ def initType(self):
+ addEvent('media.types', self.getType)
+
+ def getType(self):
+ return self._type
diff --git a/couchpotato/core/providers/movie/__init__.py b/couchpotato/core/media/_base/__init__.py
similarity index 100%
rename from couchpotato/core/providers/movie/__init__.py
rename to couchpotato/core/media/_base/__init__.py
diff --git a/couchpotato/core/media/_base/library/__init__.py b/couchpotato/core/media/_base/library/__init__.py
new file mode 100644
index 00000000..553eff5a
--- /dev/null
+++ b/couchpotato/core/media/_base/library/__init__.py
@@ -0,0 +1,13 @@
+from couchpotato.core.event import addEvent
+from couchpotato.core.plugins.base import Plugin
+
+
+class LibraryBase(Plugin):
+
+ _type = None
+
+ def initType(self):
+ addEvent('library.types', self.getType)
+
+ def getType(self):
+ return self._type
diff --git a/couchpotato/core/media/_base/searcher/__init__.py b/couchpotato/core/media/_base/searcher/__init__.py
new file mode 100644
index 00000000..0fb6cc09
--- /dev/null
+++ b/couchpotato/core/media/_base/searcher/__init__.py
@@ -0,0 +1,75 @@
+from .main import Searcher
+
+def start():
+ return Searcher()
+
+config = [{
+ 'name': 'searcher',
+ 'order': 20,
+ 'groups': [
+ {
+ 'tab': 'searcher',
+ 'name': 'searcher',
+ 'label': 'Basics',
+ 'description': 'General search options',
+ 'options': [
+ {
+ 'name': 'preferred_method',
+ 'label': 'First search',
+ 'description': 'Which of the methods do you prefer',
+ 'default': 'both',
+ 'type': 'dropdown',
+ 'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
+ },
+ ],
+ }, {
+ 'tab': 'searcher',
+ 'subtab': 'category',
+ 'subtab_label': 'Categories',
+ 'name': 'filter',
+ 'label': 'Global filters',
+ 'description': 'Prefer, ignore & required words in release names',
+ 'options': [
+ {
+ 'name': 'preferred_words',
+ 'label': 'Preferred',
+ 'default': '',
+ 'placeholder': 'Example: CtrlHD, Amiable, Wiki',
+ 'description': 'Words that give the releases a higher score.'
+ },
+ {
+ 'name': 'required_words',
+ 'label': 'Required',
+ 'default': '',
+ 'placeholder': 'Example: DTS, AC3 & English',
+ 'description': 'Release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
+ },
+ {
+ 'name': 'ignored_words',
+ 'label': 'Ignored',
+ 'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
+ 'description': 'Ignores releases that match any of these sets. (Works like explained above)'
+ },
+ ],
+ },
+ ],
+}, {
+ 'name': 'nzb',
+ 'groups': [
+ {
+ 'tab': 'searcher',
+ 'name': 'searcher',
+ 'label': 'NZB',
+ 'wizard': True,
+ 'options': [
+ {
+ 'name': 'retention',
+ 'label': 'Usenet Retention',
+ 'default': 1500,
+ 'type': 'int',
+ 'unit': 'days'
+ },
+ ],
+ },
+ ],
+}]
diff --git a/couchpotato/core/media/_base/searcher/base.py b/couchpotato/core/media/_base/searcher/base.py
new file mode 100644
index 00000000..368c6e2d
--- /dev/null
+++ b/couchpotato/core/media/_base/searcher/base.py
@@ -0,0 +1,45 @@
+from couchpotato.core.event import addEvent, fireEvent
+from couchpotato.core.logger import CPLog
+from couchpotato.core.plugins.base import Plugin
+
+log = CPLog(__name__)
+
+
+class SearcherBase(Plugin):
+
+ in_progress = False
+
+ def __init__(self):
+ super(SearcherBase, self).__init__()
+
+
+ addEvent('searcher.progress', self.getProgress)
+ addEvent('%s.searcher.progress' % self.getType(), self.getProgress)
+
+ self.initCron()
+
+ def initCron(self):
+ """ Set the searcher cronjob
+ Make sure to reset cronjob after setting has changed
+ """
+
+ _type = self.getType()
+
+ def setCrons():
+
+ fireEvent('schedule.cron', '%s.searcher.all' % _type, self.searchAll,
+ day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
+
+ addEvent('app.load', setCrons)
+ addEvent('setting.save.%s_searcher.cron_day.after' % _type, setCrons)
+ addEvent('setting.save.%s_searcher.cron_hour.after' % _type, setCrons)
+ addEvent('setting.save.%s_searcher.cron_minute.after' % _type, setCrons)
+
+ def getProgress(self, **kwargs):
+ """ Return progress of current searcher"""
+
+ progress = {
+ self.getType(): self.in_progress
+ }
+
+ return progress
diff --git a/couchpotato/core/media/_base/searcher/main.py b/couchpotato/core/media/_base/searcher/main.py
new file mode 100644
index 00000000..f09be64b
--- /dev/null
+++ b/couchpotato/core/media/_base/searcher/main.py
@@ -0,0 +1,238 @@
+from couchpotato import get_session
+from couchpotato.api import addApiView
+from couchpotato.core.event import addEvent, fireEvent
+from couchpotato.core.helpers.encoding import simplifyString, toUnicode
+from couchpotato.core.helpers.variable import md5, getTitle
+from couchpotato.core.logger import CPLog
+from couchpotato.core.media._base.searcher.base import SearcherBase
+from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
+from couchpotato.environment import Env
+from inspect import ismethod, isfunction
+import datetime
+import re
+import time
+import traceback
+
+log = CPLog(__name__)
+
+
+class Searcher(SearcherBase):
+
+ def __init__(self):
+ addEvent('searcher.protocols', self.getSearchProtocols)
+ addEvent('searcher.contains_other_quality', self.containsOtherQuality)
+ addEvent('searcher.correct_year', self.correctYear)
+ addEvent('searcher.correct_name', self.correctName)
+ addEvent('searcher.download', self.download)
+
+ addApiView('searcher.full_search', self.searchAllView, docs = {
+ 'desc': 'Starts a full search for all media',
+ })
+
+ addApiView('searcher.progress', self.getProgressForAll, docs = {
+ 'desc': 'Get the progress of all media searches',
+ 'return': {'type': 'object', 'example': """{
+ 'movie': False || object, total & to_go,
+ 'show': False || object, total & to_go,
+}"""},
+ })
+
+ def searchAllView(self):
+
+ results = {}
+ for _type in fireEvent('media.types'):
+ results[_type] = fireEvent('%s.searcher.all_view' % _type)
+
+ return results
+
+ def getProgressForAll(self):
+ progress = fireEvent('searcher.progress', merge = True)
+ return progress
+
+ def download(self, data, movie, manual = False):
+
+ if not data.get('protocol'):
+ data['protocol'] = data['type']
+ data['type'] = 'movie'
+
+ # Test to see if any downloaders are enabled for this type
+ downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
+
+ if downloader_enabled:
+
+ snatched_status = fireEvent('status.get', 'snatched', single = True)
+
+ # Download movie to temp
+ filedata = None
+ if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
+ filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
+ if filedata == 'try_next':
+ return filedata
+
+ download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
+ log.debug('Downloader result: %s', download_result)
+
+ if download_result:
+ try:
+ # Mark release as snatched
+ db = get_session()
+ rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
+ if rls:
+ renamer_enabled = Env.setting('enabled', 'renamer')
+
+ done_status = fireEvent('status.get', 'done', single = True)
+ rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
+
+ # Save download-id info if returned
+ if isinstance(download_result, dict):
+ for key in download_result:
+ rls_info = ReleaseInfo(
+ identifier = 'download_%s' % key,
+ value = toUnicode(download_result.get(key))
+ )
+ rls.info.append(rls_info)
+ db.commit()
+
+ log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
+ snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
+ log.info(snatch_message)
+ fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
+
+ # If renamer isn't used, mark movie done
+ if not renamer_enabled:
+ active_status = fireEvent('status.get', 'active', single = True)
+ done_status = fireEvent('status.get', 'done', single = True)
+ try:
+ if movie['status_id'] == active_status.get('id'):
+ for profile_type in movie['profile']['types']:
+ if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
+ log.info('Renamer disabled, marking movie as finished: %s', log_movie)
+
+ # Mark release done
+ rls.status_id = done_status.get('id')
+ rls.last_edit = int(time.time())
+ db.commit()
+
+ # Mark movie done
+ mvie = db.query(Movie).filter_by(id = movie['id']).first()
+ mvie.status_id = done_status.get('id')
+ mvie.last_edit = int(time.time())
+ db.commit()
+ except:
+ log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
+
+ except:
+ log.error('Failed marking movie finished: %s', traceback.format_exc())
+
+ return True
+
+ log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('protocol')))
+
+ return False
+
+ def getSearchProtocols(self):
+
+ download_protocols = fireEvent('download.enabled_protocols', merge = True)
+ provider_protocols = fireEvent('provider.enabled_protocols', merge = True)
+
+ if download_protocols and len(list(set(provider_protocols) & set(download_protocols))) == 0:
+ log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_protocols))
+ return []
+
+ for useless_provider in list(set(provider_protocols) - set(download_protocols)):
+ log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
+
+ search_protocols = download_protocols
+
+ if len(search_protocols) == 0:
+ log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
+ return []
+
+ return search_protocols
+
+ def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = None):
+ if not preferred_quality: preferred_quality = {}
+
+ name = nzb['name']
+ size = nzb.get('size', 0)
+ nzb_words = re.split('\W+', simplifyString(name))
+
+ qualities = fireEvent('quality.all', single = True)
+
+ found = {}
+ for quality in qualities:
+ # Main in words
+ if quality['identifier'] in nzb_words:
+ found[quality['identifier']] = True
+
+ # Alt in words
+ if list(set(nzb_words) & set(quality['alternative'])):
+ found[quality['identifier']] = True
+
+ # Try guessing via quality tags
+ guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
+ if guess:
+ found[guess['identifier']] = True
+
+ # Hack for older movies that don't contain quality tag
+ year_name = fireEvent('scanner.name_year', name, single = True)
+ if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
+ if size > 3000: # Assume dvdr
+ log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', size)
+ found['dvdr'] = True
+ else: # Assume dvdrip
+ log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', size)
+ found['dvdrip'] = True
+
+ # Allow other qualities
+ for allowed in preferred_quality.get('allow'):
+ if found.get(allowed):
+ del found[allowed]
+
+ return not (found.get(preferred_quality['identifier']) and len(found) == 1)
+
+ def correctYear(self, haystack, year, year_range):
+
+ if not isinstance(haystack, (list, tuple, set)):
+ haystack = [haystack]
+
+ year_name = {}
+ for string in haystack:
+
+ year_name = fireEvent('scanner.name_year', string, single = True)
+
+ if year_name and ((year - year_range) <= year_name.get('year') <= (year + year_range)):
+ log.debug('Movie year matches range: %s looking for %s', (year_name.get('year'), year))
+ return True
+
+ log.debug('Movie year doesn\'t matche range: %s looking for %s', (year_name.get('year'), year))
+ return False
+
+ def correctName(self, check_name, movie_name):
+
+ check_names = [check_name]
+
+ # Match names between "
+ try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
+ except: pass
+
+ # Match longest name between []
+ try: check_names.append(max(check_name.split('['), key = len))
+ except: pass
+
+ for check_name in list(set(check_names)):
+ check_movie = fireEvent('scanner.name_year', check_name, single = True)
+
+ try:
+ check_words = filter(None, re.split('\W+', check_movie.get('name', '')))
+ movie_words = filter(None, re.split('\W+', simplifyString(movie_name)))
+
+ if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0:
+ return True
+ except:
+ pass
+
+ return False
+
+class SearchSetupError(Exception):
+ pass
diff --git a/couchpotato/core/media/movie/__init__.py b/couchpotato/core/media/movie/__init__.py
new file mode 100644
index 00000000..898529c1
--- /dev/null
+++ b/couchpotato/core/media/movie/__init__.py
@@ -0,0 +1,6 @@
+from couchpotato.core.media import MediaBase
+
+
+class MovieTypeBase(MediaBase):
+
+ _type = 'movie'
diff --git a/couchpotato/core/media/movie/_base/__init__.py b/couchpotato/core/media/movie/_base/__init__.py
new file mode 100644
index 00000000..4be3b127
--- /dev/null
+++ b/couchpotato/core/media/movie/_base/__init__.py
@@ -0,0 +1,6 @@
+from .main import MovieBase
+
+def start():
+ return MovieBase()
+
+config = []
diff --git a/couchpotato/core/plugins/movie/main.py b/couchpotato/core/media/movie/_base/main.py
similarity index 77%
rename from couchpotato/core/plugins/movie/main.py
rename to couchpotato/core/media/movie/_base/main.py
index 0cc98fd3..310b4e92 100644
--- a/couchpotato/core/plugins/movie/main.py
+++ b/couchpotato/core/media/movie/_base/main.py
@@ -2,9 +2,10 @@ from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent, fireEventAsync, addEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
-from couchpotato.core.helpers.variable import getImdb, splitString
+from couchpotato.core.helpers.variable import getImdb, splitString, tryInt, \
+ mergeDicts
from couchpotato.core.logger import CPLog
-from couchpotato.core.plugins.base import Plugin
+from couchpotato.core.media.movie import MovieTypeBase
from couchpotato.core.settings.model import Library, LibraryTitle, Movie, \
Release
from couchpotato.environment import Env
@@ -16,17 +17,23 @@ import time
log = CPLog(__name__)
-class MoviePlugin(Plugin):
+class MovieBase(MovieTypeBase):
default_dict = {
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}, 'files':{}, 'info': {}},
'library': {'titles': {}, 'files':{}},
'files': {},
- 'status': {}
+ 'status': {},
+ 'category': {},
}
def __init__(self):
+
+ # Initialize this type
+ super(MovieBase, self).__init__()
+ self.initType()
+
addApiView('movie.search', self.search, docs = {
'desc': 'Search the movie providers for a movie',
'params': {
@@ -139,7 +146,7 @@ class MoviePlugin(Plugin):
imdb_id = getImdb(str(movie_id))
- if(imdb_id):
+ if imdb_id:
m = db.query(Movie).filter(Movie.library.has(identifier = imdb_id)).first()
else:
m = db.query(Movie).filter_by(id = movie_id).first()
@@ -161,19 +168,33 @@ class MoviePlugin(Plugin):
if release_status and not isinstance(release_status, (list, tuple)):
release_status = [release_status]
+ # query movie ids
q = db.query(Movie) \
- .outerjoin(Movie.releases, Movie.library, Library.titles) \
- .filter(LibraryTitle.default == True) \
+ .with_entities(Movie.id) \
.group_by(Movie.id)
# Filter on movie status
if status and len(status) > 0:
- q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
+ statuses = fireEvent('status.get', status, single = len(status) > 1)
+ statuses = [s.get('id') for s in statuses]
+
+ q = q.filter(Movie.status_id.in_(statuses))
# Filter on release status
if release_status and len(release_status) > 0:
- q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
+ q = q.join(Movie.releases)
+ statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
+ statuses = [s.get('id') for s in statuses]
+
+ q = q.filter(Release.status_id.in_(statuses))
+
+ # Only join when searching / ordering
+ if starts_with or search or order != 'release_order':
+ q = q.join(Movie.library, Library.titles) \
+ .filter(LibraryTitle.default == True)
+
+ # Add search filters
filter_or = []
if starts_with:
starts_with = toUnicode(starts_with.lower())
@@ -188,47 +209,79 @@ class MoviePlugin(Plugin):
if search:
filter_or.append(LibraryTitle.simple_title.like('%%' + search + '%%'))
- if filter_or:
+ if len(filter_or) > 0:
q = q.filter(or_(*filter_or))
total_count = q.count()
+ if total_count == 0:
+ return 0, []
if order == 'release_order':
q = q.order_by(desc(Release.last_edit))
else:
q = q.order_by(asc(LibraryTitle.simple_title))
- q = q.subquery()
- q2 = db.query(Movie).join((q, q.c.id == Movie.id)) \
- .options(joinedload_all('releases')) \
- .options(joinedload_all('profile.types')) \
+ if limit_offset:
+ splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
+ limit = splt[0]
+ offset = 0 if len(splt) is 1 else splt[1]
+ q = q.limit(limit).offset(offset)
+
+ # Get all movie_ids in sorted order
+ movie_ids = [m.id for m in q.all()]
+
+ # List release statuses
+ releases = db.query(Release) \
+ .filter(Release.movie_id.in_(movie_ids)) \
+ .all()
+
+ release_statuses = dict((m, set()) for m in movie_ids)
+ releases_count = dict((m, 0) for m in movie_ids)
+ for release in releases:
+ release_statuses[release.movie_id].add('%d,%d' % (release.status_id, release.quality_id))
+ releases_count[release.movie_id] += 1
+
+ # Get main movie data
+ q2 = db.query(Movie) \
.options(joinedload_all('library.titles')) \
.options(joinedload_all('library.files')) \
.options(joinedload_all('status')) \
.options(joinedload_all('files'))
- if limit_offset:
- splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
- limit = splt[0]
- offset = 0 if len(splt) is 1 else splt[1]
- q2 = q2.limit(limit).offset(offset)
+ q2 = q2.filter(Movie.id.in_(movie_ids))
results = q2.all()
- movies = []
+
+ # Create dict by movie id
+ movie_dict = {}
for movie in results:
- movies.append(movie.to_dict({
- 'profile': {'types': {}},
- 'releases': {'files':{}, 'info': {}},
+ movie_dict[movie.id] = movie
+
+ # List movies based on movie_ids order
+ movies = []
+ for movie_id in movie_ids:
+
+ releases = []
+ for r in release_statuses.get(movie_id):
+ x = splitString(r)
+ releases.append({'status_id': x[0], 'quality_id': x[1]})
+
+ # Merge releases with movie dict
+ movies.append(mergeDicts(movie_dict[movie_id].to_dict({
'library': {'titles': {}, 'files':{}},
'files': {},
+ }), {
+ 'releases': releases,
+ 'releases_count': releases_count.get(movie_id),
}))
db.expire_all()
- return (total_count, movies)
+ return total_count, movies
def availableChars(self, status = None, release_status = None):
- chars = ''
+ status = status or []
+ release_status = release_status or []
db = get_session()
@@ -238,37 +291,53 @@ class MoviePlugin(Plugin):
if release_status and not isinstance(release_status, (list, tuple)):
release_status = [release_status]
- q = db.query(Movie) \
- .outerjoin(Movie.releases, Movie.library, Library.titles, Movie.status) \
- .options(joinedload_all('library.titles'))
+ q = db.query(Movie)
# Filter on movie status
if status and len(status) > 0:
- q = q.filter(or_(*[Movie.status.has(identifier = s) for s in status]))
+ statuses = fireEvent('status.get', status, single = len(release_status) > 1)
+ statuses = [s.get('id') for s in statuses]
+
+ q = q.filter(Movie.status_id.in_(statuses))
# Filter on release status
if release_status and len(release_status) > 0:
- q = q.filter(or_(*[Release.status.has(identifier = s) for s in release_status]))
- results = q.all()
+ statuses = fireEvent('status.get', release_status, single = len(release_status) > 1)
+ statuses = [s.get('id') for s in statuses]
- for movie in results:
- char = movie.library.titles[0].simple_title[0]
- char = char if char in ascii_lowercase else '#'
- if char not in chars:
- chars += str(char)
+ q = q.join(Movie.releases) \
+ .filter(Release.status_id.in_(statuses))
+
+ q = q.join(Library, LibraryTitle) \
+ .with_entities(LibraryTitle.simple_title) \
+ .filter(LibraryTitle.default == True)
+
+ titles = q.all()
+
+ chars = set()
+ for title in titles:
+ try:
+ char = title[0][0]
+ char = char if char in ascii_lowercase else '#'
+ chars.add(str(char))
+ except:
+ log.error('Failed getting title for %s', title.libraries_id)
+
+ if len(chars) == 25:
+ break
db.expire_all()
- return ''.join(sorted(chars, key = str.lower))
+ return ''.join(sorted(chars))
def listView(self, **kwargs):
- status = splitString(kwargs.get('status', None))
- release_status = splitString(kwargs.get('release_status', None))
- limit_offset = kwargs.get('limit_offset', None)
- starts_with = kwargs.get('starts_with', None)
- search = kwargs.get('search', None)
- order = kwargs.get('order', None)
+ status = splitString(kwargs.get('status'))
+ release_status = splitString(kwargs.get('release_status'))
+ limit_offset = kwargs.get('limit_offset')
+ starts_with = kwargs.get('starts_with')
+ search = kwargs.get('search')
+ order = kwargs.get('order')
total_movies, movies = self.list(
status = status,
@@ -313,7 +382,7 @@ class MoviePlugin(Plugin):
if title.default: default_title = title.title
fireEvent('notify.frontend', type = 'movie.busy.%s' % x, data = True)
- fireEventAsync('library.update', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(x))
+ fireEventAsync('library.update.movie', identifier = movie.library.identifier, default_title = default_title, force = True, on_complete = self.createOnComplete(x))
db.expire_all()
return {
@@ -339,7 +408,8 @@ class MoviePlugin(Plugin):
'movies': movies,
}
- def add(self, params = {}, force_readd = True, search_after = True, update_library = False, status_id = None):
+ def add(self, params = None, force_readd = True, search_after = True, update_library = False, status_id = None):
+ if not params: params = {}
if not params.get('identifier'):
msg = 'Can\'t add movie without imdb identifier.'
@@ -358,23 +428,26 @@ class MoviePlugin(Plugin):
pass
- library = fireEvent('library.add', single = True, attrs = params, update_after = update_library)
+ library = fireEvent('library.add.movie', single = True, attrs = params, update_after = update_library)
# Status
status_active, snatched_status, ignored_status, done_status, downloaded_status = \
fireEvent('status.get', ['active', 'snatched', 'ignored', 'done', 'downloaded'], single = True)
default_profile = fireEvent('profile.default', single = True)
+ cat_id = params.get('category_id')
db = get_session()
m = db.query(Movie).filter_by(library_id = library.get('id')).first()
added = True
do_search = False
+ search_after = search_after and self.conf('search_on_add', section = 'moviesearcher')
if not m:
m = Movie(
library_id = library.get('id'),
profile_id = params.get('profile_id', default_profile.get('id')),
status_id = status_id if status_id else status_active.get('id'),
+ category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else None,
)
db.add(m)
db.commit()
@@ -383,7 +456,7 @@ class MoviePlugin(Plugin):
if search_after:
onComplete = self.createOnComplete(m.id)
- fireEventAsync('library.update', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
+ fireEventAsync('library.update.movie', params.get('identifier'), default_title = params.get('title', ''), on_complete = onComplete)
search_after = False
elif force_readd:
@@ -396,6 +469,7 @@ class MoviePlugin(Plugin):
fireEvent('release.delete', release.id, single = True)
m.profile_id = params.get('profile_id', default_profile.get('id'))
+ m.category_id = tryInt(cat_id) if cat_id is not None and tryInt(cat_id) > 0 else (m.category_id or None)
else:
log.debug('Movie already exists, not updating: %s', params)
added = False
@@ -452,6 +526,10 @@ class MoviePlugin(Plugin):
m.profile_id = kwargs.get('profile_id')
+ cat_id = kwargs.get('category_id')
+ if cat_id is not None:
+ m.category_id = tryInt(cat_id) if tryInt(cat_id) > 0 else None
+
# Remove releases
for rel in m.releases:
if rel.status_id is available_status.get('id'):
@@ -468,7 +546,7 @@ class MoviePlugin(Plugin):
fireEvent('movie.restatus', m.id)
movie_dict = m.to_dict(self.default_dict)
- fireEventAsync('searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
+ fireEventAsync('movie.searcher.single', movie_dict, on_complete = self.createNotifyFront(movie_id))
db.expire_all()
return {
@@ -503,7 +581,7 @@ class MoviePlugin(Plugin):
total_deleted = 0
new_movie_status = None
for release in movie.releases:
- if delete_from in ['wanted', 'snatched']:
+ if delete_from in ['wanted', 'snatched', 'late']:
if release.status_id != done_status.get('id'):
db.delete(release)
total_deleted += 1
@@ -544,7 +622,7 @@ class MoviePlugin(Plugin):
log.debug('Can\'t restatus movie, doesn\'t seem to exist.')
return False
- log.debug('Changing status for %s', (m.library.titles[0].title))
+ log.debug('Changing status for %s', m.library.titles[0].title)
if not m.profile:
m.status_id = done_status.get('id')
else:
@@ -566,7 +644,7 @@ class MoviePlugin(Plugin):
def onComplete():
db = get_session()
movie = db.query(Movie).filter_by(id = movie_id).first()
- fireEventAsync('searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
+ fireEventAsync('movie.searcher.single', movie.to_dict(self.default_dict), on_complete = self.createNotifyFront(movie_id))
db.expire_all()
return onComplete
diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/media/movie/_base/static/list.js
similarity index 96%
rename from couchpotato/core/plugins/movie/static/list.js
rename to couchpotato/core/media/movie/_base/static/list.js
index 1b11fab5..341d2348 100644
--- a/couchpotato/core/plugins/movie/static/list.js
+++ b/couchpotato/core/media/movie/_base/static/list.js
@@ -273,8 +273,25 @@ var MovieList = new Class({
})
).addClass('search');
+ var available_chars;
self.filter_menu.addEvent('open', function(){
self.navigation_search_input.focus();
+
+ // Get available chars and highlight
+ if(!available_chars && (self.navigation.isDisplayed() || self.navigation.isVisible()))
+ Api.request('movie.available_chars', {
+ 'data': Object.merge({
+ 'status': self.options.status
+ }, self.filter),
+ 'onSuccess': function(json){
+ available_chars = json.chars
+
+ json.chars.split('').each(function(c){
+ self.letters[c.capitalize()].addClass('available')
+ })
+
+ }
+ });
});
self.filter_menu.addLink(
@@ -311,21 +328,6 @@ var MovieList = new Class({
}).inject(self.navigation_alpha);
});
- // Get available chars and highlight
- if(self.navigation.isDisplayed() || self.navigation.isVisible())
- Api.request('movie.available_chars', {
- 'data': Object.merge({
- 'status': self.options.status
- }, self.filter),
- 'onSuccess': function(json){
-
- json.chars.split('').each(function(c){
- self.letters[c.capitalize()].addClass('available')
- })
-
- }
- });
-
// Add menu or hide
if (self.options.menu.length > 0)
self.options.menu.each(function(menu_item){
@@ -566,7 +568,7 @@ var MovieList = new Class({
}
self.store(json.movies);
- self.addMovies(json.movies, json.total);
+ self.addMovies(json.movies, json.total || json.movies.length);
if(self.scrollspy) {
self.load_more.set('text', 'load more movies');
self.scrollspy.start();
diff --git a/couchpotato/core/plugins/movie/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js
similarity index 78%
rename from couchpotato/core/plugins/movie/static/movie.actions.js
rename to couchpotato/core/media/movie/_base/static/movie.actions.js
index a0f7bad5..24bf6260 100644
--- a/couchpotato/core/plugins/movie/static/movie.actions.js
+++ b/couchpotato/core/media/movie/_base/static/movie.actions.js
@@ -1,5 +1,5 @@
var MovieAction = new Class({
-
+
Implements: [Options],
class_name: 'action icon2',
@@ -124,6 +124,46 @@ MA.Release = new Class({
else
self.showHelper();
+ App.addEvent('movie.searcher.ended.'+self.movie.data.id, function(notification){
+ self.releases = null;
+ if(self.options_container){
+ self.options_container.destroy();
+ self.options_container = null;
+ }
+ });
+
+ },
+
+ show: function(e){
+ var self = this;
+ if(e)
+ (e).preventDefault();
+
+ if(self.releases)
+ self.createReleases();
+ else {
+
+ self.movie.busy(true);
+
+ Api.request('release.for_movie', {
+ 'data': {
+ 'id': self.movie.data.id
+ },
+ 'onComplete': function(json){
+ self.movie.busy(false, 1);
+
+ if(json && json.releases){
+ self.releases = json.releases;
+ self.createReleases();
+ }
+ else
+ alert('Something went wrong, check the logs.');
+ }
+ });
+
+ }
+
+
},
createReleases: function(){
@@ -145,7 +185,7 @@ MA.Release = new Class({
new Element('span.provider', {'text': 'Provider'})
).inject(self.release_container)
- self.movie.data.releases.sortBy('-info.score').each(function(release){
+ self.releases.each(function(release){
var status = Status.get(release.status_id),
quality = Quality.getProfile(release.quality_id) || {},
@@ -211,35 +251,40 @@ MA.Release = new Class({
}
});
- if(self.last_release){
+ if(self.last_release)
self.release_container.getElement('#release_'+self.last_release.id).addClass('last_release');
- }
- if(self.next_release){
+ if(self.next_release)
self.release_container.getElement('#release_'+self.next_release.id).addClass('next_release');
- }
if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
self.trynext_container = new Element('div.buttons.try_container').inject(self.release_container, 'top');
+
+ var nr = self.next_release,
+ lr = self.last_release;
self.trynext_container.adopt(
new Element('span.or', {
'text': 'This movie is snatched, if anything went wrong, download'
}),
- self.last_release ? new Element('a.button.orange', {
+ lr ? new Element('a.button.orange', {
'text': 'the same release again',
'events': {
- 'click': self.trySameRelease.bind(self)
+ 'click': function(){
+ self.download(lr);
+ }
}
}) : null,
- self.next_release && self.last_release ? new Element('span.or', {
+ nr && lr ? new Element('span.or', {
'text': ','
}) : null,
- self.next_release ? [new Element('a.button.green', {
- 'text': self.last_release ? 'another release' : 'the best release',
+ nr ? [new Element('a.button.green', {
+ 'text': lr ? 'another release' : 'the best release',
'events': {
- 'click': self.tryNextRelease.bind(self)
+ 'click': function(){
+ self.download(nr);
+ }
}
}),
new Element('span.or', {
@@ -248,18 +293,15 @@ MA.Release = new Class({
)
}
+ self.last_release = null;
+ self.next_release = null;
+
}
- },
-
- show: function(e){
- var self = this;
- if(e)
- (e).preventDefault();
-
- self.createReleases();
+ // Show it
self.options_container.inject(self.movie, 'top');
self.movie.slide('in', self.options_container);
+
},
showHelper: function(e){
@@ -267,15 +309,29 @@ MA.Release = new Class({
if(e)
(e).preventDefault();
- self.createReleases();
+ var has_available = false,
+ has_snatched = false;
- if(self.next_release || (self.last_release && ['ignored', 'failed'].indexOf(self.last_release.status.identifier) === false)){
+ self.movie.data.releases.each(function(release){
+ if(has_available && has_snatched) return;
+
+ var status = Status.get(release.status_id);
+
+ if(['snatched', 'downloaded', 'seeding'].contains(status.identifier))
+ has_snatched = true;
+
+ if(['available'].contains(status.identifier))
+ has_available = true;
+
+ });
+
+ if(has_available || has_snatched){
self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container);
self.trynext_container.adopt(
- self.next_release ? [new Element('a.icon2.readd', {
- 'text': self.last_release ? 'Download another release' : 'Download the best release',
+ has_available ? [new Element('a.icon2.readd', {
+ 'text': has_snatched ? 'Download another release' : 'Download the best release',
'events': {
'click': self.tryNextRelease.bind(self)
}
@@ -291,24 +347,7 @@ MA.Release = new Class({
new Element('a.icon2.completed', {
'text': 'mark this movie done',
'events': {
- 'click': function(){
- Api.request('movie.delete', {
- 'data': {
- 'id': self.movie.get('id'),
- 'delete_from': 'wanted'
- },
- 'onComplete': function(){
- var movie = $(self.movie);
- movie.set('tween', {
- 'duration': 300,
- 'onComplete': function(){
- self.movie.destroy()
- }
- });
- movie.tween('height', 0);
- }
- });
- }
+ 'click': self.markMovieDone.bind(self)
}
})
)
@@ -326,17 +365,19 @@ MA.Release = new Class({
var release_el = self.release_container.getElement('#release_'+release.id),
icon = release_el.getElement('.download.icon2');
- self.movie.busy(true);
+ icon.addClass('icon spinner').removeClass('download');
Api.request('release.download', {
'data': {
'id': release.id
},
'onComplete': function(json){
- self.movie.busy(false);
+ icon.removeClass('icon spinner');
- if(json.success)
+ if(json.success){
icon.addClass('completed');
+ release_el.getElement('.release_status').set('text', 'snatched');
+ }
else
icon.addClass('attention').set('title', 'Something went wrong when downloading, please check logs.');
}
@@ -365,24 +406,36 @@ MA.Release = new Class({
},
- tryNextRelease: function(movie_id){
+ markMovieDone: function(){
var self = this;
- self.createReleases();
-
- if(self.last_release)
- self.ignore(self.last_release);
-
- if(self.next_release)
- self.download(self.next_release);
+ Api.request('movie.delete', {
+ 'data': {
+ 'id': self.movie.get('id'),
+ 'delete_from': 'wanted'
+ },
+ 'onComplete': function(){
+ var movie = $(self.movie);
+ movie.set('tween', {
+ 'duration': 300,
+ 'onComplete': function(){
+ self.movie.destroy()
+ }
+ });
+ movie.tween('height', 0);
+ }
+ });
},
- trySameRelease: function(movie_id){
+ tryNextRelease: function(movie_id){
var self = this;
- if(self.last_release)
- self.download(self.last_release);
+ Api.request('movie.searcher.try_next', {
+ 'data': {
+ 'id': self.movie.get('id')
+ }
+ });
}
@@ -408,7 +461,7 @@ MA.Trailer = new Class({
watch: function(offset){
var self = this;
- var data_url = 'http://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
+ var data_url = 'https://gdata.youtube.com/feeds/videos?vq="{title}" {year} trailer&max-results=1&alt=json-in-script&orderby=relevance&sortorder=descending&format=5&fmt=18'
var url = data_url.substitute({
'title': encodeURI(self.getTitle()),
'year': self.get('year'),
@@ -521,6 +574,11 @@ MA.Edit = new Class({
self.profile_select = new Element('select', {
'name': 'profile'
}),
+ self.category_select = new Element('select', {
+ 'name': 'category'
+ }).grab(
+ new Element('option', {'value': -1, 'text': 'None'})
+ ),
new Element('a.button.edit', {
'text': 'Save & Search',
'events': {
@@ -540,7 +598,34 @@ MA.Edit = new Class({
});
- Quality.getActiveProfiles().each(function(profile){
+ // Fill categories
+ var categories = CategoryList.getAll();
+
+ if(categories.length == 0)
+ self.category_select.hide();
+ else {
+ self.category_select.show();
+ categories.each(function(category){
+
+ var category_id = category.data.id;
+
+ new Element('option', {
+ 'value': category_id,
+ 'text': category.data.label
+ }).inject(self.category_select);
+
+ if(self.movie.category && self.movie.category.data && self.movie.category.data.id == category_id)
+ self.category_select.set('value', category_id);
+
+ });
+ }
+
+ // Fill profiles
+ var profiles = Quality.getActiveProfiles();
+ if(profiles.length == 1)
+ self.profile_select.hide();
+
+ profiles.each(function(profile){
var profile_id = profile.id ? profile.id : profile.data.id;
@@ -549,8 +634,9 @@ MA.Edit = new Class({
'text': profile.label ? profile.label : profile.data.label
}).inject(self.profile_select);
- if(self.movie.profile && self.movie.profile.data && self.movie.profile.data.id == profile_id)
+ if(self.movie.get('profile_id') == profile_id)
self.profile_select.set('value', profile_id);
+
});
}
@@ -566,7 +652,8 @@ MA.Edit = new Class({
'data': {
'id': self.movie.get('id'),
'default_title': self.title_select.get('value'),
- 'profile_id': self.profile_select.get('value')
+ 'profile_id': self.profile_select.get('value'),
+ 'category_id': self.category_select.get('value')
},
'useSpinner': true,
'spinnerTarget': $(self.movie),
@@ -697,6 +784,7 @@ MA.Delete = new Class({
var self = this;
(e).preventDefault();
+ self.movie.removeView();
self.movie.slide('out');
},
@@ -745,16 +833,45 @@ MA.Files = new Class({
self.el = new Element('a.directory', {
'title': 'Available files',
'events': {
- 'click': self.showFiles.bind(self)
+ 'click': self.show.bind(self)
}
});
},
- showFiles: function(e){
+ show: function(e){
var self = this;
(e).preventDefault();
+ if(self.releases)
+ self.showFiles();
+ else {
+
+ self.movie.busy(true);
+
+ Api.request('release.for_movie', {
+ 'data': {
+ 'id': self.movie.data.id
+ },
+ 'onComplete': function(json){
+ self.movie.busy(false, 1);
+
+ if(json && json.releases){
+ self.releases = json.releases;
+ self.showFiles();
+ }
+ else
+ alert('Something went wrong, check the logs.');
+ }
+ });
+
+ }
+
+ },
+
+ showFiles: function(){
+ var self = this;
+
if(!self.options_container){
self.options_container = new Element('div.options').adopt(
self.files_container = new Element('div.files.table')
@@ -767,7 +884,7 @@ MA.Files = new Class({
new Element('span.is_available', {'text': 'Available'})
).inject(self.files_container)
- Array.each(self.movie.data.releases, function(release){
+ Array.each(self.releases, function(release){
var rel = new Element('div.release').inject(self.files_container);
diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/media/movie/_base/static/movie.css
similarity index 98%
rename from couchpotato/core/plugins/movie/static/movie.css
rename to couchpotato/core/media/movie/_base/static/movie.css
index 60ab96b2..0200417c 100644
--- a/couchpotato/core/plugins/movie/static/movie.css
+++ b/couchpotato/core/media/movie/_base/static/movie.css
@@ -425,7 +425,9 @@
}
.movies .data .quality .available { background-color: #578bc3; }
- .movies .data .quality .snatched { background-color: #369545; }
+ .movies .data .quality .failed { background-color: #a43d34; }
+ .movies .data .quality .snatched { background-color: #a2a232; }
+ .movies .data .quality .seeding { background-color: #0a6819; }
.movies .data .quality .done {
background-color: #369545;
opacity: 1;
@@ -639,6 +641,12 @@
position: absolute;
z-index: 10;
}
+ @media only screen and (device-width: 768px) {
+ .trailer_container iframe {
+ margin-top: 25px;
+ }
+ }
+
.trailer_container.hide {
height: 0 !important;
}
diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js
similarity index 85%
rename from couchpotato/core/plugins/movie/static/movie.js
rename to couchpotato/core/media/movie/_base/static/movie.js
index 5ca36c9d..363d860c 100644
--- a/couchpotato/core/plugins/movie/static/movie.js
+++ b/couchpotato/core/media/movie/_base/static/movie.js
@@ -14,6 +14,7 @@ var Movie = new Class({
self.el = new Element('div.movie');
self.profile = Quality.getProfile(data.profile_id) || {};
+ self.category = CategoryList.getCategory(data.category_id) || {};
self.parent(self, options);
self.addEvents();
@@ -28,14 +29,14 @@ var Movie = new Class({
self.update.delay(2000, self, notification);
});
- ['movie.busy', 'searcher.started'].each(function(listener){
+ ['movie.busy', 'movie.searcher.started'].each(function(listener){
App.addEvent(listener+'.'+self.data.id, function(notification){
if(notification.data)
self.busy(true)
});
})
- App.addEvent('searcher.ended.'+self.data.id, function(notification){
+ App.addEvent('movie.searcher.ended.'+self.data.id, function(notification){
if(notification.data)
self.busy(false)
});
@@ -52,12 +53,12 @@ var Movie = new Class({
// Remove events
App.removeEvents('movie.update.'+self.data.id);
- ['movie.busy', 'searcher.started'].each(function(listener){
+ ['movie.busy', 'movie.searcher.started'].each(function(listener){
App.removeEvents(listener+'.'+self.data.id);
})
},
- busy: function(set_busy){
+ busy: function(set_busy, timeout){
var self = this;
if(!set_busy){
@@ -71,9 +72,9 @@ var Movie = new Class({
self.spinner.el.destroy();
self.spinner = null;
self.mask = null;
- }, 400);
+ }, timeout || 400);
}
- }, 1000)
+ }, timeout || 1000)
}
else if(!self.spinner) {
self.createMask();
@@ -111,6 +112,7 @@ var Movie = new Class({
self.removeView();
self.profile = Quality.getProfile(self.data.profile_id) || {};
+ self.category = CategoryList.getCategory(self.data.category_id) || {};
self.create();
self.busy(false);
@@ -177,20 +179,21 @@ var Movie = new Class({
});
// Add releases
- self.data.releases.each(function(release){
-
- var q = self.quality.getElement('.q_id'+ release.quality_id),
- status = Status.get(release.status_id);
-
- if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
- var q = self.addQuality(release.quality_id)
-
- if (status && q && !q.hasClass(status.identifier)){
- q.addClass(status.identifier);
- q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
- }
-
- });
+ if(self.data.releases)
+ self.data.releases.each(function(release){
+
+ var q = self.quality.getElement('.q_id'+ release.quality_id),
+ status = Status.get(release.status_id);
+
+ if(!q && (status.identifier == 'snatched' || status.identifier == 'done'))
+ var q = self.addQuality(release.quality_id)
+
+ if (status && q && !q.hasClass(status.identifier)){
+ q.addClass(status.identifier);
+ q.set('title', (q.get('title') ? q.get('title') : '') + ' status: '+ status.label)
+ }
+
+ });
Object.each(self.options.actions, function(action, key){
self.action[key.toLowerCase()] = action = new self.options.actions[key](self)
diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/media/movie/_base/static/search.css
similarity index 84%
rename from couchpotato/core/plugins/movie/static/search.css
rename to couchpotato/core/media/movie/_base/static/search.css
index e2aa0a47..dc747346 100644
--- a/couchpotato/core/plugins/movie/static/search.css
+++ b/couchpotato/core/media/movie/_base/static/search.css
@@ -159,13 +159,15 @@
display: inline-block;
margin-right: 10px;
}
- .movie_result .options select[name=title] { width: 180px; }
+ .movie_result .options select[name=title] { width: 170px; }
.movie_result .options select[name=profile] { width: 90px; }
+ .movie_result .options select[name=category] { width: 80px; }
@media all and (max-width: 480px) {
.movie_result .options select[name=title] { width: 90px; }
- .movie_result .options select[name=profile] { width: 60px; }
+ .movie_result .options select[name=profile] { width: 50px; }
+ .movie_result .options select[name=category] { width: 50px; }
}
@@ -217,26 +219,51 @@
position: absolute;
top: 20%;
left: 15px;
- right: 60px;
+ right: 7px;
vertical-align: middle;
}
-
+
.movie_result .info h2 {
+ margin: 0;
font-weight: normal;
font-size: 20px;
+ padding: 0;
+ }
+
+ .search_form .info h2 {
+ position: absolute;
+ width: 100%;
+ }
+
+ .movie_result .info h2 .title {
display: block;
margin: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
- width: 100%;
- }
-
- .movie_result .info h2 span {
- padding: 0 5px;
- position: absolute;
- right: -60px;
}
+
+ .search_form .info h2 .title {
+ position: absolute;
+ width: 88%;
+ }
+
+ .movie_result .info h2 .year {
+ padding: 0 5px;
+ text-align: center;
+ position: absolute;
+ width: 12%;
+ right: 0;
+ }
+
+ @media all and (max-width: 480px) {
+
+ .search_form .info h2 .year {
+ font-size: 12px;
+ margin-top: 7px;
+ }
+
+ }
.search_form .mask,
.movie_result .mask {
diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/media/movie/_base/static/search.js
similarity index 82%
rename from couchpotato/core/plugins/movie/static/search.js
rename to couchpotato/core/media/movie/_base/static/search.js
index 5530505f..7332381b 100644
--- a/couchpotato/core/plugins/movie/static/search.js
+++ b/couchpotato/core/media/movie/_base/static/search.js
@@ -215,10 +215,11 @@ Block.Search.Item = new Class({
'click': self.showOptions.bind(self)
}
}).adopt(
- new Element('div.info').adopt(
- self.title = new Element('h2', {
- 'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
- }).adopt(
+ self.info_container = new Element('div.info').adopt(
+ new Element('h2').adopt(
+ self.title = new Element('span.title', {
+ 'text': info.titles && info.titles.length > 0 ? info.titles[0] : 'Unknown'
+ }),
self.year = info.year ? new Element('span.year', {
'text': info.year
}) : null
@@ -274,7 +275,9 @@ Block.Search.Item = new Class({
add: function(e){
var self = this;
- (e).preventDefault();
+
+ if(e)
+ (e).preventDefault();
self.loadingMask();
@@ -282,7 +285,8 @@ Block.Search.Item = new Class({
'data': {
'identifier': self.info.imdb,
'title': self.title_select.get('value'),
- 'profile_id': self.profile_select.get('value')
+ 'profile_id': self.profile_select.get('value'),
+ 'category_id': self.category_select.get('value')
},
'onComplete': function(json){
self.options_el.empty();
@@ -322,10 +326,10 @@ Block.Search.Item = new Class({
self.options_el.grab(
new Element('div', {
- 'class': self.info.in_wanted && self.info.in_wanted.profile || in_library ? 'in_library_wanted' : ''
+ 'class': self.info.in_wanted && self.info.in_wanted.profile_id || in_library ? 'in_library_wanted' : ''
}).adopt(
- self.info.in_wanted && self.info.in_wanted.profile ? new Element('span.in_wanted', {
- 'text': 'Already in wanted list: ' + self.info.in_wanted.profile.label
+ self.info.in_wanted && self.info.in_wanted.profile_id ? new Element('span.in_wanted', {
+ 'text': 'Already in wanted list: ' + Quality.getProfile(self.info.in_wanted.profile_id).get('label')
}) : (in_library ? new Element('span.in_library', {
'text': 'Already in library: ' + in_library.join(', ')
}) : null),
@@ -335,7 +339,12 @@ Block.Search.Item = new Class({
self.profile_select = new Element('select', {
'name': 'profile'
}),
- new Element('a.button', {
+ self.category_select = new Element('select', {
+ 'name': 'category'
+ }).grab(
+ new Element('option', {'value': -1, 'text': 'None'})
+ ),
+ self.add_button = new Element('a.button', {
'text': 'Add',
'events': {
'click': self.add.bind(self)
@@ -350,7 +359,28 @@ Block.Search.Item = new Class({
}).inject(self.title_select)
})
- Quality.getActiveProfiles().each(function(profile){
+
+ // Fill categories
+ var categories = CategoryList.getAll();
+
+ if(categories.length == 0)
+ self.category_select.hide();
+ else {
+ self.category_select.show();
+ categories.each(function(category){
+ new Element('option', {
+ 'value': category.data.id,
+ 'text': category.data.label
+ }).inject(self.category_select);
+ });
+ }
+
+ // Fill profiles
+ var profiles = Quality.getActiveProfiles();
+ if(profiles.length == 1)
+ self.profile_select.hide();
+
+ profiles.each(function(profile){
new Element('option', {
'value': profile.id ? profile.id : profile.data.id,
'text': profile.label ? profile.label : profile.data.label
@@ -358,6 +388,11 @@ Block.Search.Item = new Class({
});
self.options_el.addClass('set');
+
+ if(categories.length == 0 && self.title_select.getElements('option').length == 1 && profiles.length == 1 &&
+ !(self.info.in_wanted && self.info.in_wanted.profile_id || in_library))
+ self.add();
+
}
},
diff --git a/libs/themoviedb/__init__.py b/couchpotato/core/media/movie/library/__init__.py
similarity index 100%
rename from libs/themoviedb/__init__.py
rename to couchpotato/core/media/movie/library/__init__.py
diff --git a/couchpotato/core/media/movie/library/movie/__init__.py b/couchpotato/core/media/movie/library/movie/__init__.py
new file mode 100644
index 00000000..03494a11
--- /dev/null
+++ b/couchpotato/core/media/movie/library/movie/__init__.py
@@ -0,0 +1,6 @@
+from .main import MovieLibraryPlugin
+
+def start():
+ return MovieLibraryPlugin()
+
+config = []
diff --git a/couchpotato/core/plugins/library/main.py b/couchpotato/core/media/movie/library/movie/main.py
similarity index 82%
rename from couchpotato/core/plugins/library/main.py
rename to couchpotato/core/media/movie/library/movie/main.py
index b463abfd..718e7390 100644
--- a/couchpotato/core/plugins/library/main.py
+++ b/couchpotato/core/media/movie/library/movie/main.py
@@ -2,7 +2,7 @@ from couchpotato import get_session
from couchpotato.core.event import addEvent, fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode, simplifyString
from couchpotato.core.logger import CPLog
-from couchpotato.core.plugins.base import Plugin
+from couchpotato.core.media._base.library import LibraryBase
from couchpotato.core.settings.model import Library, LibraryTitle, File
from string import ascii_letters
import time
@@ -10,16 +10,20 @@ import traceback
log = CPLog(__name__)
-class LibraryPlugin(Plugin):
+
+class MovieLibraryPlugin(LibraryBase):
default_dict = {'titles': {}, 'files':{}}
def __init__(self):
- addEvent('library.add', self.add)
- addEvent('library.update', self.update)
- addEvent('library.update_release_date', self.updateReleaseDate)
+ addEvent('library.add.movie', self.add)
+ addEvent('library.update.movie', self.update)
+ addEvent('library.update.movie.release_date', self.updateReleaseDate)
- def add(self, attrs = {}, update_after = True):
+ def add(self, attrs = None, update_after = True):
+ if not attrs: attrs = {}
+
+ primary_provider = attrs.get('primary_provider', 'imdb')
db = get_session()
@@ -32,7 +36,7 @@ class LibraryPlugin(Plugin):
plot = toUnicode(attrs.get('plot')),
tagline = toUnicode(attrs.get('tagline')),
status_id = status.get('id'),
- info = {},
+ info = {}
)
title = LibraryTitle(
@@ -48,7 +52,7 @@ class LibraryPlugin(Plugin):
# Update library info
if update_after is not False:
handle = fireEventAsync if update_after is 'async' else fireEvent
- handle('library.update', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
+ handle('library.update.movie', identifier = l.identifier, default_title = toUnicode(attrs.get('title', '')))
library_dict = l.to_dict(self.default_dict)
@@ -57,29 +61,30 @@ class LibraryPlugin(Plugin):
def update(self, identifier, default_title = '', force = False):
+ if self.shuttingDown():
+ return
+
db = get_session()
library = db.query(Library).filter_by(identifier = identifier).first()
done_status = fireEvent('status.get', 'done', single = True)
+ library_dict = None
if library:
library_dict = library.to_dict(self.default_dict)
do_update = True
- if library.status_id == done_status.get('id') and not force:
- do_update = False
- else:
- info = fireEvent('movie.info', merge = True, identifier = identifier)
+ info = fireEvent('movie.info', merge = True, identifier = identifier)
- # Don't need those here
- try: del info['in_wanted']
- except: pass
- try: del info['in_library']
- except: pass
+ # Don't need those here
+ try: del info['in_wanted']
+ except: pass
+ try: del info['in_library']
+ except: pass
- if not info or len(info) == 0:
- log.error('Could not update, no movie info to work with: %s', identifier)
- return False
+ if not info or len(info) == 0:
+ log.error('Could not update, no movie info to work with: %s', identifier)
+ return False
# Main info
if do_update:
diff --git a/couchpotato/core/media/movie/searcher/__init__.py b/couchpotato/core/media/movie/searcher/__init__.py
new file mode 100644
index 00000000..bae18902
--- /dev/null
+++ b/couchpotato/core/media/movie/searcher/__init__.py
@@ -0,0 +1,73 @@
+from .main import MovieSearcher
+import random
+
+def start():
+ return MovieSearcher()
+
+config = [{
+ 'name': 'moviesearcher',
+ 'order': 20,
+ 'groups': [
+ {
+ 'tab': 'searcher',
+ 'name': 'movie_searcher',
+ 'label': 'Movie search',
+ 'description': 'Search options for movies',
+ 'advanced': True,
+ 'options': [
+ {
+ 'name': 'always_search',
+ 'default': False,
+ 'migrate_from': 'searcher',
+ 'type': 'bool',
+ 'label': 'Always search',
+ 'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
+ },
+ {
+ 'name': 'run_on_launch',
+ 'migrate_from': 'searcher',
+ 'label': 'Run on launch',
+ 'advanced': True,
+ 'default': 0,
+ 'type': 'bool',
+ 'description': 'Force run the searcher after (re)start.',
+ },
+ {
+ 'name': 'search_on_add',
+ 'label': 'Search after add',
+ 'advanced': True,
+ 'default': 1,
+ 'type': 'bool',
+ 'description': 'Disable this to only search for movies on cron.',
+ },
+ {
+ 'name': 'cron_day',
+ 'migrate_from': 'searcher',
+ 'label': 'Day',
+ 'advanced': True,
+ 'default': '*',
+ 'type': 'string',
+ 'description': '*: Every day, */2: Every 2 days, 1: Every first of the month. See APScheduler for details.',
+ },
+ {
+ 'name': 'cron_hour',
+ 'migrate_from': 'searcher',
+ 'label': 'Hour',
+ 'advanced': True,
+ 'default': random.randint(0, 23),
+ 'type': 'string',
+ 'description': '*: Every hour, */8: Every 8 hours, 3: At 3, midnight.',
+ },
+ {
+ 'name': 'cron_minute',
+ 'migrate_from': 'searcher',
+ 'label': 'Minute',
+ 'advanced': True,
+ 'default': random.randint(0, 59),
+ 'type': 'string',
+ 'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour."
+ },
+ ],
+ },
+ ],
+}]
diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/media/movie/searcher/main.py
similarity index 51%
rename from couchpotato/core/plugins/searcher/main.py
rename to couchpotato/core/media/movie/searcher/main.py
index cc2774ac..b08e7532 100644
--- a/couchpotato/core/plugins/searcher/main.py
+++ b/couchpotato/core/media/movie/searcher/main.py
@@ -1,17 +1,16 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent, fireEventAsync
-from couchpotato.core.helpers.encoding import simplifyString, toUnicode
+from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss
from couchpotato.core.helpers.variable import md5, getTitle, splitString, \
- possibleTitles
+ possibleTitles, getImdb
from couchpotato.core.logger import CPLog
-from couchpotato.core.plugins.base import Plugin
+from couchpotato.core.media._base.searcher.base import SearcherBase
+from couchpotato.core.media.movie import MovieTypeBase
from couchpotato.core.settings.model import Movie, Release, ReleaseInfo
from couchpotato.environment import Env
from datetime import date
-from inspect import ismethod, isfunction
from sqlalchemy.exc import InterfaceError
-import datetime
import random
import re
import time
@@ -20,30 +19,32 @@ import traceback
log = CPLog(__name__)
-class Searcher(Plugin):
+class MovieSearcher(SearcherBase, MovieTypeBase):
in_progress = False
def __init__(self):
- addEvent('searcher.all', self.allMovies)
- addEvent('searcher.single', self.single)
- addEvent('searcher.correct_movie', self.correctMovie)
- addEvent('searcher.download', self.download)
- addEvent('searcher.try_next_release', self.tryNextRelease)
- addEvent('searcher.could_be_released', self.couldBeReleased)
+ super(MovieSearcher, self).__init__()
- addApiView('searcher.try_next', self.tryNextReleaseView, docs = {
+ addEvent('movie.searcher.all', self.searchAll)
+ addEvent('movie.searcher.all_view', self.searchAllView)
+ addEvent('movie.searcher.single', self.single)
+ addEvent('movie.searcher.correct_movie', self.correctMovie)
+ addEvent('movie.searcher.try_next_release', self.tryNextRelease)
+ addEvent('movie.searcher.could_be_released', self.couldBeReleased)
+
+ addApiView('movie.searcher.try_next', self.tryNextReleaseView, docs = {
'desc': 'Marks the snatched results as ignored and try the next best release',
'params': {
'id': {'desc': 'The id of the movie'},
},
})
- addApiView('searcher.full_search', self.allMoviesView, docs = {
+ addApiView('movie.searcher.full_search', self.searchAllView, docs = {
'desc': 'Starts a full search for all wanted movies',
})
- addApiView('searcher.progress', self.getProgress, docs = {
+ addApiView('movie.searcher.progress', self.getProgress, docs = {
'desc': 'Get the progress of current full search',
'return': {'type': 'object', 'example': """{
'progress': False || object, total & to_go,
@@ -51,42 +52,25 @@ class Searcher(Plugin):
})
if self.conf('run_on_launch'):
- addEvent('app.load', self.allMovies)
+ addEvent('app.load', self.searchAll)
- addEvent('app.load', self.setCrons)
- addEvent('setting.save.searcher.cron_day.after', self.setCrons)
- addEvent('setting.save.searcher.cron_hour.after', self.setCrons)
- addEvent('setting.save.searcher.cron_minute.after', self.setCrons)
+ def searchAllView(self, **kwargs):
- def setCrons(self):
- fireEvent('schedule.cron', 'searcher.all', self.allMovies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute'))
-
- def allMoviesView(self, **kwargs):
-
- in_progress = self.in_progress
- if not in_progress:
- fireEventAsync('searcher.all')
- fireEvent('notify.frontend', type = 'searcher.started', data = True, message = 'Full search started')
- else:
- fireEvent('notify.frontend', type = 'searcher.already_started', data = True, message = 'Full search already in progress')
+ fireEventAsync('movie.searcher.all')
return {
- 'success': not in_progress
+ 'success': not self.in_progress
}
- def getProgress(self, **kwargs):
-
- return {
- 'progress': self.in_progress
- }
-
- def allMovies(self):
+ def searchAll(self):
if self.in_progress:
log.info('Search already in progress')
+ fireEvent('notify.frontend', type = 'movie.searcher.already_started', data = True, message = 'Full search already in progress')
return
self.in_progress = True
+ fireEvent('notify.frontend', type = 'movie.searcher.started', data = True, message = 'Full search started')
db = get_session()
@@ -101,21 +85,22 @@ class Searcher(Plugin):
}
try:
- search_types = self.getSearchTypes()
+ search_protocols = fireEvent('searcher.protocols', single = True)
for movie in movies:
movie_dict = movie.to_dict({
+ 'category': {},
'profile': {'types': {'quality': {}}},
'releases': {'status': {}, 'quality': {}},
'library': {'titles': {}, 'files':{}},
- 'files': {}
+ 'files': {},
})
try:
- self.single(movie_dict, search_types)
+ self.single(movie_dict, search_protocols)
except IndexError:
log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
- fireEvent('library.update', movie_dict['library']['identifier'], force = True)
+ fireEvent('library.update.movie', movie_dict['library']['identifier'], force = True)
except:
log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc()))
@@ -130,25 +115,25 @@ class Searcher(Plugin):
self.in_progress = False
- def single(self, movie, search_types = None):
+ def single(self, movie, search_protocols = None, manual = False):
# Find out search type
try:
- if not search_types:
- search_types = self.getSearchTypes()
+ if not search_protocols:
+ search_protocols = fireEvent('searcher.protocols', single = True)
except SearchSetupError:
return
done_status = fireEvent('status.get', 'done', single = True)
- if not movie['profile'] or movie['status_id'] == done_status.get('id'):
+ if not movie['profile'] or (movie['status_id'] == done_status.get('id') and not manual):
log.debug('Movie doesn\'t have a profile or already done, assuming in manage tab.')
return
db = get_session()
pre_releases = fireEvent('quality.pre_releases', single = True)
- release_dates = fireEvent('library.update_release_date', identifier = movie['library']['identifier'], merge = True)
+ release_dates = fireEvent('library.update.movie.release_date', identifier = movie['library']['identifier'], merge = True)
available_status, ignored_status, failed_status = fireEvent('status.get', ['available', 'ignored', 'failed'], single = True)
found_releases = []
@@ -160,7 +145,7 @@ class Searcher(Plugin):
fireEvent('movie.delete', movie['id'], single = True)
return
- fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
+ fireEvent('notify.frontend', type = 'movie.searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title)
ret = False
@@ -183,18 +168,18 @@ class Searcher(Plugin):
quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True)
results = []
- for search_type in search_types:
- type_results = fireEvent('%s.search' % search_type, movie, quality, merge = True)
- if type_results:
- results += type_results
+ for search_protocol in search_protocols:
+ protocol_results = fireEvent('provider.search.%s.movie' % search_protocol, movie, quality, merge = True)
+ if protocol_results:
+ results += protocol_results
sorted_results = sorted(results, key = lambda k: k['score'], reverse = True)
if len(sorted_results) == 0:
log.debug('Nothing found for %s in %s', (default_title, quality_type['quality']['label']))
- download_preference = self.conf('preferred_method')
+ download_preference = self.conf('preferred_method', section = 'searcher')
if download_preference != 'both':
- sorted_results = sorted(sorted_results, key = lambda k: k['type'], reverse = (download_preference == 'torrent'))
+ sorted_results = sorted(sorted_results, key = lambda k: k['protocol'][:3], reverse = (download_preference == 'torrent'))
# Check if movie isn't deleted while searching
if not db.query(Movie).filter_by(id = movie.get('id')).first():
@@ -252,7 +237,7 @@ class Searcher(Plugin):
log.info('Ignored, score to low: %s', nzb['name'])
continue
- downloaded = self.download(data = nzb, movie = movie)
+ downloaded = fireEvent('searcher.download', data = nzb, movie = movie, manual = manual, single = True)
if downloaded is True:
ret = True
break
@@ -276,107 +261,10 @@ class Searcher(Plugin):
if len(too_early_to_search) > 0:
log.info2('Too early to search for %s, %s', (too_early_to_search, default_title))
- fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True)
+ fireEvent('notify.frontend', type = 'movie.searcher.ended.%s' % movie['id'], data = True)
return ret
- def download(self, data, movie, manual = False):
-
- # Test to see if any downloaders are enabled for this type
- downloader_enabled = fireEvent('download.enabled', manual, data, single = True)
-
- if downloader_enabled:
-
- snatched_status = fireEvent('status.get', 'snatched', single = True)
-
- # Download movie to temp
- filedata = None
- if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))):
- filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
- if filedata == 'try_next':
- return filedata
-
- download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True)
- log.debug('Downloader result: %s', download_result)
-
- if download_result:
- try:
- # Mark release as snatched
- db = get_session()
- rls = db.query(Release).filter_by(identifier = md5(data['url'])).first()
- if rls:
- renamer_enabled = Env.setting('enabled', 'renamer')
-
- done_status = fireEvent('status.get', 'done', single = True)
- rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id')
-
- # Save download-id info if returned
- if isinstance(download_result, dict):
- for key in download_result:
- rls_info = ReleaseInfo(
- identifier = 'download_%s' % key,
- value = toUnicode(download_result.get(key))
- )
- rls.info.append(rls_info)
- db.commit()
-
- log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label)
- snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie)
- log.info(snatch_message)
- fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict())
-
- # If renamer isn't used, mark movie done
- if not renamer_enabled:
- active_status = fireEvent('status.get', 'active', single = True)
- done_status = fireEvent('status.get', 'done', single = True)
- try:
- if movie['status_id'] == active_status.get('id'):
- for profile_type in movie['profile']['types']:
- if profile_type['quality_id'] == rls.quality.id and profile_type['finish']:
- log.info('Renamer disabled, marking movie as finished: %s', log_movie)
-
- # Mark release done
- rls.status_id = done_status.get('id')
- rls.last_edit = int(time.time())
- db.commit()
-
- # Mark movie done
- mvie = db.query(Movie).filter_by(id = movie['id']).first()
- mvie.status_id = done_status.get('id')
- mvie.last_edit = int(time.time())
- db.commit()
- except:
- log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc())
-
- except:
- log.error('Failed marking movie finished: %s', traceback.format_exc())
-
- return True
-
- log.info('Tried to download, but none of the "%s" downloaders are enabled or gave an error', (data.get('type', '')))
-
- return False
-
- def getSearchTypes(self):
-
- download_types = fireEvent('download.enabled_types', merge = True)
- provider_types = fireEvent('provider.enabled_types', merge = True)
-
- if download_types and len(list(set(provider_types) & set(download_types))) == 0:
- log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types))
- raise NoProviders
-
- for useless_provider in list(set(provider_types) - set(download_types)):
- log.debug('Provider for "%s" enabled, but no downloader.', useless_provider)
-
- search_types = download_types
-
- if len(search_types) == 0:
- log.error('There aren\'t any downloaders enabled. Please pick one in settings.')
- raise NoDownloaders
-
- return search_types
-
def correctMovie(self, nzb = None, movie = None, quality = None, **kwargs):
imdb_results = kwargs.get('imdb_results', False)
@@ -392,29 +280,35 @@ class Searcher(Plugin):
nzb_words = re.split('\W+', nzb_name)
# Make sure it has required words
- required_words = splitString(self.conf('required_words').lower())
+ required_words = splitString(self.conf('required_words', section = 'searcher').lower())
+ try: required_words = list(set(required_words + splitString(movie['category']['required'].lower())))
+ except: pass
+
req_match = 0
for req_set in required_words:
req = splitString(req_set, '&')
req_match += len(list(set(nzb_words) & set(req))) == len(req)
- if self.conf('required_words') and req_match == 0:
+ if len(required_words) > 0 and req_match == 0:
log.info2('Wrong: Required word missing: %s', nzb['name'])
return False
# Ignore releases
- ignored_words = splitString(self.conf('ignored_words').lower())
+ ignored_words = splitString(self.conf('ignored_words', section = 'searcher').lower())
+ try: ignored_words = list(set(ignored_words + splitString(movie['category']['ignored'].lower())))
+ except: pass
+
ignored_match = 0
for ignored_set in ignored_words:
ignored = splitString(ignored_set, '&')
ignored_match += len(list(set(nzb_words) & set(ignored))) == len(ignored)
- if self.conf('ignored_words') and ignored_match:
+ if len(ignored_words) > 0 and ignored_match:
log.info2("Wrong: '%s' contains 'ignored words'", (nzb['name']))
return False
# Ignore porn stuff
- pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic']
+ pron_tags = ['xxx', 'sex', 'anal', 'tits', 'fuck', 'porn', 'orgy', 'milf', 'boobs', 'erotica', 'erotic', 'cock', 'dick']
pron_words = list(set(nzb_words) & set(pron_tags) - set(movie_words))
if pron_words:
log.info('Wrong: %s, probably pr0n', (nzb['name']))
@@ -423,7 +317,7 @@ class Searcher(Plugin):
preferred_quality = fireEvent('quality.single', identifier = quality['identifier'], single = True)
# Contains lower quality string
- if self.containsOtherQuality(nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality):
+ if fireEvent('searcher.contains_other_quality', nzb, movie_year = movie['library']['year'], preferred_quality = preferred_quality, single = True):
log.info2('Wrong: %s, looking for %s', (nzb['name'], quality['label']))
return False
@@ -453,112 +347,25 @@ class Searcher(Plugin):
return True
# Check if nzb contains imdb link
- if self.checkIMDB([nzb.get('description', '')], movie['library']['identifier']):
+ if getImdb(nzb.get('description', '')) == movie['library']['identifier']:
return True
for raw_title in movie['library']['titles']:
for movie_title in possibleTitles(raw_title['title']):
movie_words = re.split('\W+', simplifyString(movie_title))
- if self.correctName(nzb['name'], movie_title):
+ if fireEvent('searcher.correct_name', nzb['name'], movie_title, single = True):
# if no IMDB link, at least check year range 1
- if len(movie_words) > 2 and self.correctYear([nzb['name']], movie['library']['year'], 1):
+ if len(movie_words) > 2 and fireEvent('searcher.correct_year', nzb['name'], movie['library']['year'], 1, single = True):
return True
# if no IMDB link, at least check year
- if len(movie_words) <= 2 and self.correctYear([nzb['name']], movie['library']['year'], 0):
+ if len(movie_words) <= 2 and fireEvent('searcher.correct_year', nzb['name'], movie['library']['year'], 0, single = True):
return True
log.info("Wrong: %s, undetermined naming. Looking for '%s (%s)'", (nzb['name'], movie_name, movie['library']['year']))
return False
- def containsOtherQuality(self, nzb, movie_year = None, preferred_quality = {}):
-
- name = nzb['name']
- size = nzb.get('size', 0)
- nzb_words = re.split('\W+', simplifyString(name))
-
- qualities = fireEvent('quality.all', single = True)
-
- found = {}
- for quality in qualities:
- # Main in words
- if quality['identifier'] in nzb_words:
- found[quality['identifier']] = True
-
- # Alt in words
- if list(set(nzb_words) & set(quality['alternative'])):
- found[quality['identifier']] = True
-
- # Try guessing via quality tags
- guess = fireEvent('quality.guess', [nzb.get('name')], single = True)
- if guess:
- found[guess['identifier']] = True
-
- # Hack for older movies that don't contain quality tag
- year_name = fireEvent('scanner.name_year', name, single = True)
- if len(found) == 0 and movie_year < datetime.datetime.now().year - 3 and not year_name.get('year', None):
- if size > 3000: # Assume dvdr
- log.info('Quality was missing in name, assuming it\'s a DVD-R based on the size: %s', (size))
- found['dvdr'] = True
- else: # Assume dvdrip
- log.info('Quality was missing in name, assuming it\'s a DVD-Rip based on the size: %s', (size))
- found['dvdrip'] = True
-
- # Allow other qualities
- for allowed in preferred_quality.get('allow'):
- if found.get(allowed):
- del found[allowed]
-
- return not (found.get(preferred_quality['identifier']) and len(found) == 1)
-
- def checkIMDB(self, haystack, imdbId):
-
- for string in haystack:
- if 'imdb.com/title/' + imdbId in string:
- return True
-
- return False
-
- def correctYear(self, haystack, year, year_range):
-
- for string in haystack:
-
- year_name = fireEvent('scanner.name_year', string, single = True)
-
- if year_name and ((year - year_range) <= year_name.get('year') <= (year + year_range)):
- log.debug('Movie year matches range: %s looking for %s', (year_name.get('year'), year))
- return True
-
- log.debug('Movie year doesn\'t matche range: %s looking for %s', (year_name.get('year'), year))
- return False
-
- def correctName(self, check_name, movie_name):
-
- check_names = [check_name]
-
- # Match names between "
- try: check_names.append(re.search(r'([\'"])[^\1]*\1', check_name).group(0))
- except: pass
-
- # Match longest name between []
- try: check_names.append(max(check_name.split('['), key = len))
- except: pass
-
- for check_name in list(set(check_names)):
- check_movie = fireEvent('scanner.name_year', check_name, single = True)
-
- try:
- check_words = filter(None, re.split('\W+', check_movie.get('name', '')))
- movie_words = filter(None, re.split('\W+', simplifyString(movie_name)))
-
- if len(check_words) > 0 and len(movie_words) > 0 and len(list(set(check_words) - set(movie_words))) == 0:
- return True
- except:
- pass
-
- return False
-
def couldBeReleased(self, is_pre_release, dates, year = None):
now = int(time.time())
@@ -569,7 +376,7 @@ class Searcher(Plugin):
else:
# For movies before 1972
- if dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
+ if not dates or dates.get('theater', 0) < 0 or dates.get('dvd', 0) < 0:
return True
if is_pre_release:
@@ -596,7 +403,7 @@ class Searcher(Plugin):
def tryNextReleaseView(self, id = None, **kwargs):
- trynext = self.tryNextRelease(id)
+ trynext = self.tryNextRelease(id, manual = True)
return {
'success': trynext
@@ -604,14 +411,14 @@ class Searcher(Plugin):
def tryNextRelease(self, movie_id, manual = False):
- snatched_status, ignored_status = fireEvent('status.get', ['snatched', 'ignored'], single = True)
+ snatched_status, done_status, ignored_status = fireEvent('status.get', ['snatched', 'done', 'ignored'], single = True)
try:
db = get_session()
- rels = db.query(Release).filter_by(
- status_id = snatched_status.get('id'),
- movie_id = movie_id
- ).all()
+ rels = db.query(Release) \
+ .filter_by(movie_id = movie_id) \
+ .filter(Release.status_id.in_([snatched_status.get('id'), done_status.get('id')])) \
+ .all()
for rel in rels:
rel.status_id = ignored_status.get('id')
@@ -619,7 +426,7 @@ class Searcher(Plugin):
movie_dict = fireEvent('movie.get', movie_id, single = True)
log.info('Trying next release for: %s', getTitle(movie_dict['library']))
- fireEvent('searcher.single', movie_dict)
+ fireEvent('movie.searcher.single', movie_dict, manual = manual)
return True
@@ -629,9 +436,3 @@ class Searcher(Plugin):
class SearchSetupError(Exception):
pass
-
-class NoDownloaders(SearchSetupError):
- pass
-
-class NoProviders(SearchSetupError):
- pass
diff --git a/couchpotato/core/migration/versions/002_Movie_category.py b/couchpotato/core/migration/versions/002_Movie_category.py
new file mode 100644
index 00000000..234e1136
--- /dev/null
+++ b/couchpotato/core/migration/versions/002_Movie_category.py
@@ -0,0 +1,17 @@
+from migrate.changeset.schema import create_column
+from sqlalchemy.schema import MetaData, Column, Table, Index
+from sqlalchemy.types import Integer
+
+meta = MetaData()
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ category_column = Column('category_id', Integer)
+ movie = Table('movie', meta, category_column)
+ create_column(category_column, movie)
+ Index('ix_movie_category_id', movie.c.category_id).create()
+
+def downgrade(migrate_engine):
+ pass
diff --git a/couchpotato/core/notifications/base.py b/couchpotato/core/notifications/base.py
index 7418e1a4..4c0d0992 100644
--- a/couchpotato/core/notifications/base.py
+++ b/couchpotato/core/notifications/base.py
@@ -32,7 +32,9 @@ class Notification(Provider):
addEvent(listener, self.createNotifyHandler(listener))
def createNotifyHandler(self, listener):
- def notify(message = None, group = {}, data = None):
+ def notify(message = None, group = None, data = None):
+ if not group: group = {}
+
if not self.conf('on_snatch', default = True) and listener == 'movie.snatched':
return
return self._notify(message = message, data = data if data else group, listener = listener)
@@ -45,9 +47,10 @@ class Notification(Provider):
def _notify(self, *args, **kwargs):
if self.isEnabled():
return self.notify(*args, **kwargs)
+ return False
- def notify(self, message = '', data = {}, listener = None):
- pass
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
def test(self, **kwargs):
diff --git a/couchpotato/core/notifications/boxcar/main.py b/couchpotato/core/notifications/boxcar/main.py
index b30d487a..0fca749f 100644
--- a/couchpotato/core/notifications/boxcar/main.py
+++ b/couchpotato/core/notifications/boxcar/main.py
@@ -10,7 +10,8 @@ class Boxcar(Notification):
url = 'https://boxcar.io/devices/providers/7MNNXY3UIzVBwvzkKwkC/notifications'
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
try:
message = message.strip()
diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py
index b6c07f58..a9a20b0a 100644
--- a/couchpotato/core/notifications/core/main.py
+++ b/couchpotato/core/notifications/core/main.py
@@ -7,6 +7,7 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from couchpotato.core.settings.model import Notification as Notif
from couchpotato.environment import Env
+from operator import itemgetter
from sqlalchemy.sql.expression import or_
import threading
import time
@@ -18,9 +19,7 @@ log = CPLog(__name__)
class CoreNotifier(Notification):
- m_lock = threading.Lock()
- messages = []
- listeners = []
+ m_lock = None
def __init__(self):
super(CoreNotifier, self).__init__()
@@ -51,10 +50,15 @@ class CoreNotifier(Notification):
addApiView('notification.listener', self.listener)
fireEvent('schedule.interval', 'core.check_messages', self.checkMessages, hours = 12, single = True)
+ fireEvent('schedule.interval', 'core.clean_messages', self.cleanMessages, seconds = 15, single = True)
addEvent('app.load', self.clean)
addEvent('app.load', self.checkMessages)
+ self.messages = []
+ self.listeners = []
+ self.m_lock = threading.Lock()
+
def clean(self):
db = get_session()
@@ -113,7 +117,7 @@ class CoreNotifier(Notification):
prop_name = 'messages.last_check'
last_check = tryInt(Env.prop(prop_name, default = 0))
- messages = fireEvent('cp.messages', last_check = last_check, single = True)
+ messages = fireEvent('cp.messages', last_check = last_check, single = True) or []
for message in messages:
if message.get('time') > last_check:
@@ -124,7 +128,8 @@ class CoreNotifier(Notification):
Env.prop(prop_name, value = last_check)
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
db = get_session()
@@ -145,7 +150,8 @@ class CoreNotifier(Notification):
return True
- def frontend(self, type = 'notification', data = {}, message = None):
+ def frontend(self, type = 'notification', data = None, message = None):
+ if not data: data = {}
log.debug('Notifying frontend')
@@ -169,8 +175,8 @@ class CoreNotifier(Notification):
except:
log.debug('Failed sending to listener: %s', traceback.format_exc())
+ self.listeners = []
self.m_lock.release()
- self.cleanMessages()
log.debug('Done notifying frontend')
@@ -184,11 +190,14 @@ class CoreNotifier(Notification):
'result': messages,
})
+ self.m_lock.acquire()
self.listeners.append((callback, last_id))
+ self.m_lock.release()
def removeListener(self, callback):
+ self.m_lock.acquire()
for list_tuple in self.listeners:
try:
listener, last_id = list_tuple
@@ -196,15 +205,18 @@ class CoreNotifier(Notification):
self.listeners.remove(list_tuple)
except:
log.debug('Failed removing listener: %s', traceback.format_exc())
+ self.m_lock.release()
def cleanMessages(self):
+ if len(self.messages) == 0:
+ return
+
log.debug('Cleaning messages')
self.m_lock.acquire()
- for message in self.messages:
- if message['time'] < (time.time() - 15):
- self.messages.remove(message)
+ time_ago = (time.time() - 15)
+ self.messages[:] = [m for m in self.messages if (m['time'] > time_ago)]
self.m_lock.release()
log.debug('Done cleaning messages')
@@ -215,16 +227,16 @@ class CoreNotifier(Notification):
self.m_lock.acquire()
recent = []
- index = 0
- for i in xrange(len(self.messages)):
- index = len(self.messages) - i - 1
- if self.messages[index]["message_id"] == last_id: break
- recent = self.messages[index:]
+ try:
+ index = map(itemgetter('message_id'), self.messages).index(last_id)
+ recent = self.messages[index + 1:]
+ except:
+ pass
self.m_lock.release()
- log.debug('Returning for %s %s messages', (last_id, len(recent or [])))
+ log.debug('Returning for %s %s messages', (last_id, len(recent)))
- return recent or []
+ return recent
def listener(self, init = False, **kwargs):
@@ -237,6 +249,7 @@ class CoreNotifier(Notification):
notifications = db.query(Notif) \
.filter(or_(Notif.read == False, Notif.added > (time.time() - 259200))) \
.all()
+
for n in notifications:
ndict = n.to_dict()
ndict['type'] = 'notification'
diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js
index b05eb1a3..e485976e 100644
--- a/couchpotato/core/notifications/core/static/notification.js
+++ b/couchpotato/core/notifications/core/static/notification.js
@@ -157,7 +157,7 @@ var NotificationBase = new Class({
}
// Restart poll
- self.startPoll()
+ self.startPoll.delay(1500, self);
},
showMessage: function(message, sticky, data){
diff --git a/couchpotato/core/notifications/email/main.py b/couchpotato/core/notifications/email/main.py
index 21fcf157..f94688d5 100644
--- a/couchpotato/core/notifications/email/main.py
+++ b/couchpotato/core/notifications/email/main.py
@@ -11,7 +11,8 @@ log = CPLog(__name__)
class Email(Notification):
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
# Extract all the settings from settings
from_address = self.conf('from')
diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py
index caad661b..dabeea01 100644
--- a/couchpotato/core/notifications/growl/main.py
+++ b/couchpotato/core/notifications/growl/main.py
@@ -43,7 +43,8 @@ class Growl(Notification):
else:
log.error('Failed register of growl: %s', traceback.format_exc())
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
self.register()
diff --git a/couchpotato/core/notifications/nmj/main.py b/couchpotato/core/notifications/nmj/main.py
index 695f53be..1479fb1b 100644
--- a/couchpotato/core/notifications/nmj/main.py
+++ b/couchpotato/core/notifications/nmj/main.py
@@ -23,16 +23,15 @@ class NMJ(Notification):
def autoConfig(self, host = 'localhost', **kwargs):
- database = ''
mount = ''
try:
terminal = telnetlib.Telnet(host)
except Exception:
- log.error('Warning: unable to get a telnet session to %s', (host))
+ log.error('Warning: unable to get a telnet session to %s', host)
return self.failed()
- log.debug('Connected to %s via telnet', (host))
+ log.debug('Connected to %s via telnet', host)
terminal.read_until('sh-3.00# ')
terminal.write('cat /tmp/source\n')
terminal.write('cat /tmp/netshare\n')
@@ -46,7 +45,7 @@ class NMJ(Notification):
device = match.group(2)
log.info('Found NMJ database %s on device %s', (database, device))
else:
- log.error('Could not get current NMJ database on %s, NMJ is probably not running!', (host))
+ log.error('Could not get current NMJ database on %s, NMJ is probably not running!', host)
return self.failed()
if device.startswith('NETWORK_SHARE/'):
@@ -54,7 +53,7 @@ class NMJ(Notification):
if match:
mount = match.group().replace('127.0.0.1', host)
- log.info('Found mounting url on the Popcorn Hour in configuration: %s', (mount))
+ log.info('Found mounting url on the Popcorn Hour in configuration: %s', mount)
else:
log.error('Detected a network share on the Popcorn Hour, but could not get the mounting url')
return self.failed()
@@ -65,17 +64,18 @@ class NMJ(Notification):
'mount': mount,
}
- def addToLibrary(self, message = None, group = {}):
+ def addToLibrary(self, message = None, group = None):
if self.isDisabled(): return
+ if not group: group = {}
host = self.conf('host')
mount = self.conf('mount')
database = self.conf('database')
if mount:
- log.debug('Try to mount network drive via url: %s', (mount))
+ log.debug('Try to mount network drive via url: %s', mount)
try:
- data = self.urlopen(mount)
+ self.urlopen(mount)
except:
return False
@@ -98,11 +98,11 @@ class NMJ(Notification):
et = etree.fromstring(response)
result = et.findtext('returnValue')
except SyntaxError, e:
- log.error('Unable to parse XML returned from the Popcorn Hour: %s', (e))
+ log.error('Unable to parse XML returned from the Popcorn Hour: %s', e)
return False
if int(result) > 0:
- log.error('Popcorn Hour returned an errorcode: %s', (result))
+ log.error('Popcorn Hour returned an errorcode: %s', result)
return False
else:
log.info('NMJ started background scan')
diff --git a/couchpotato/core/notifications/notifo/main.py b/couchpotato/core/notifications/notifo/main.py
index 6e4d7adf..2d56ed71 100644
--- a/couchpotato/core/notifications/notifo/main.py
+++ b/couchpotato/core/notifications/notifo/main.py
@@ -12,7 +12,8 @@ class Notifo(Notification):
url = 'https://api.notifo.com/v1/send_notification'
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
try:
params = {
diff --git a/couchpotato/core/notifications/notifymyandroid/main.py b/couchpotato/core/notifications/notifymyandroid/main.py
index 2c4ac90c..92e59562 100644
--- a/couchpotato/core/notifications/notifymyandroid/main.py
+++ b/couchpotato/core/notifications/notifymyandroid/main.py
@@ -8,19 +8,17 @@ log = CPLog(__name__)
class NotifyMyAndroid(Notification):
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
nma = pynma.PyNMA()
keys = splitString(self.conf('api_key'))
nma.addkey(keys)
nma.developerkey(self.conf('dev_key'))
- # hacky fix for the event type
- # as it seems to be part of the message now
- self.event = message.split(' ')[0]
response = nma.push(
application = self.default_title,
- event = self.event,
+ event = message.split(' ')[0],
description = message,
priority = self.conf('priority'),
batch_mode = len(keys) > 1
diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py
index 86da9cd5..f6088f5b 100644
--- a/couchpotato/core/notifications/plex/main.py
+++ b/couchpotato/core/notifications/plex/main.py
@@ -1,9 +1,10 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import tryUrlencode
-from couchpotato.core.helpers.variable import cleanHost
+from couchpotato.core.helpers.variable import cleanHost, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from urllib2 import URLError
+from urlparse import urlparse
from xml.dom import minidom
import traceback
@@ -16,16 +17,17 @@ class Plex(Notification):
super(Plex, self).__init__()
addEvent('renamer.after', self.addToLibrary)
- def addToLibrary(self, message = None, group = {}):
+ def addToLibrary(self, message = None, group = None):
if self.isDisabled(): return
+ if not group: group = {}
log.info('Sending notification to Plex')
- hosts = [cleanHost(x.strip() + ':32400') for x in self.conf('host').split(",")]
+ hosts = self.getHosts(port = 32400)
for host in hosts:
source_type = ['movie']
- base_url = '%slibrary/sections' % host
+ base_url = '%s/library/sections' % host
refresh_url = '%s/%%s/refresh' % base_url
try:
@@ -36,7 +38,7 @@ class Plex(Notification):
for s in sections:
if s.getAttribute('type') in source_type:
url = refresh_url % s.getAttribute('key')
- x = self.urlopen(url)
+ self.urlopen(url)
except:
log.error('Plex library update failed for %s, Media Server not running: %s', (host, traceback.format_exc(1)))
@@ -44,9 +46,10 @@ class Plex(Notification):
return True
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
- hosts = [x.strip() + ':3000' for x in self.conf('host').split(",")]
+ hosts = self.getHosts(port = 3000)
successful = 0
for host in hosts:
if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host):
@@ -56,8 +59,7 @@ class Plex(Notification):
def send(self, command, host):
- url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, tryUrlencode(command))
-
+ url = '%s/xbmcCmds/xbmcHttp/?%s' % (host, tryUrlencode(command))
headers = {}
try:
@@ -88,3 +90,18 @@ class Plex(Notification):
return {
'success': success or success2
}
+
+ def getHosts(self, port = None):
+
+ raw_hosts = splitString(self.conf('host'))
+ hosts = []
+
+ for h in raw_hosts:
+ h = cleanHost(h)
+ p = urlparse(h)
+ h = h.rstrip('/')
+ if port and not p.port:
+ h += ':%s' % port
+ hosts.append(h)
+
+ return hosts
diff --git a/couchpotato/core/notifications/prowl/main.py b/couchpotato/core/notifications/prowl/main.py
index e5c4678b..a8a3dda2 100644
--- a/couchpotato/core/notifications/prowl/main.py
+++ b/couchpotato/core/notifications/prowl/main.py
@@ -12,7 +12,8 @@ class Prowl(Notification):
'api': 'https://api.prowlapp.com/publicapi/add'
}
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
data = {
'apikey': self.conf('api_key'),
diff --git a/couchpotato/core/notifications/pushalot/main.py b/couchpotato/core/notifications/pushalot/main.py
index 4c5e76c3..4e3b6e76 100644
--- a/couchpotato/core/notifications/pushalot/main.py
+++ b/couchpotato/core/notifications/pushalot/main.py
@@ -11,7 +11,8 @@ class Pushalot(Notification):
'api': 'https://pushalot.com/api/sendmessage'
}
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
data = {
'AuthorizationToken': self.conf('auth_token'),
diff --git a/couchpotato/core/notifications/pushover/main.py b/couchpotato/core/notifications/pushover/main.py
index ea5e7748..76f730b6 100644
--- a/couchpotato/core/notifications/pushover/main.py
+++ b/couchpotato/core/notifications/pushover/main.py
@@ -11,7 +11,8 @@ class Pushover(Notification):
app_token = 'YkxHMYDZp285L265L3IwH3LmzkTaCy'
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
http_handler = HTTPSConnection("api.pushover.net:443")
diff --git a/couchpotato/core/notifications/synoindex/main.py b/couchpotato/core/notifications/synoindex/main.py
index 315520ef..0f7775d6 100644
--- a/couchpotato/core/notifications/synoindex/main.py
+++ b/couchpotato/core/notifications/synoindex/main.py
@@ -15,8 +15,9 @@ class Synoindex(Notification):
super(Synoindex, self).__init__()
addEvent('renamer.after', self.addToLibrary)
- def addToLibrary(self, message = None, group = {}):
+ def addToLibrary(self, message = None, group = None):
if self.isDisabled(): return
+ if not group: group = {}
command = [self.index_path, '-A', group.get('destination_dir')]
log.info('Executing synoindex command: %s ', command)
@@ -27,9 +28,8 @@ class Synoindex(Notification):
return True
except OSError, e:
log.error('Unable to run synoindex: %s', e)
- return False
- return True
+ return False
def test(self, **kwargs):
return {
diff --git a/couchpotato/core/notifications/toasty/main.py b/couchpotato/core/notifications/toasty/main.py
index 79b021e2..c65b6b42 100644
--- a/couchpotato/core/notifications/toasty/main.py
+++ b/couchpotato/core/notifications/toasty/main.py
@@ -11,7 +11,8 @@ class Toasty(Notification):
'api': 'http://api.supertoasty.com/notify/%s?%s'
}
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
data = {
'title': self.default_title,
diff --git a/couchpotato/core/notifications/trakt/main.py b/couchpotato/core/notifications/trakt/main.py
index 86d47086..99d55530 100644
--- a/couchpotato/core/notifications/trakt/main.py
+++ b/couchpotato/core/notifications/trakt/main.py
@@ -13,7 +13,8 @@ class Trakt(Notification):
listen_to = ['movie.downloaded']
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
post_data = {
'username': self.conf('automation_username'),
diff --git a/couchpotato/core/notifications/twitter/main.py b/couchpotato/core/notifications/twitter/main.py
index 59fbb3a1..ad4fc315 100644
--- a/couchpotato/core/notifications/twitter/main.py
+++ b/couchpotato/core/notifications/twitter/main.py
@@ -4,7 +4,8 @@ from couchpotato.core.helpers.variable import cleanHost
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
from couchpotato.environment import Env
-from pytwitter import Api, parse_qsl
+from pytwitter import Api
+from urlparse import parse_qsl
import oauth2
log = CPLog(__name__)
@@ -29,7 +30,8 @@ class Twitter(Notification):
addApiView('notify.%s.auth_url' % self.getName().lower(), self.getAuthorizationUrl)
addApiView('notify.%s.credentials' % self.getName().lower(), self.getCredentials)
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
api = Api(self.consumer_key, self.consumer_secret, self.conf('access_token_key'), self.conf('access_token_secret'))
@@ -50,7 +52,7 @@ class Twitter(Notification):
try:
if direct_message:
for user in direct_message_users.split():
- api.PostDirectMessage(user, '[%s] %s' % (self.default_title, message))
+ api.PostDirectMessage('[%s] %s' % (self.default_title, message), screen_name = user)
else:
update_message = '[%s] %s' % (self.default_title, message)
if len(update_message) > 140:
diff --git a/couchpotato/core/notifications/xbmc/__init__.py b/couchpotato/core/notifications/xbmc/__init__.py
index e3c467ce..dafa0f63 100644
--- a/couchpotato/core/notifications/xbmc/__init__.py
+++ b/couchpotato/core/notifications/xbmc/__init__.py
@@ -38,6 +38,14 @@ config = [{
'advanced': True,
'description': 'Only update the first host when movie snatched, useful for synced XBMC',
},
+ {
+ 'name': 'remote_dir_scan',
+ 'label': 'Remote Folder Scan',
+ 'default': 0,
+ 'type': 'bool',
+ 'advanced': True,
+ 'description': 'Only scan new movie folder at remote XBMC servers. Works if movie location is the same.',
+ },
{
'name': 'on_snatch',
'default': 0,
diff --git a/couchpotato/core/notifications/xbmc/main.py b/couchpotato/core/notifications/xbmc/main.py
index ad6fa605..dc185c41 100755
--- a/couchpotato/core/notifications/xbmc/main.py
+++ b/couchpotato/core/notifications/xbmc/main.py
@@ -13,11 +13,12 @@ log = CPLog(__name__)
class XBMC(Notification):
- listen_to = ['renamer.after']
+ listen_to = ['renamer.after', 'movie.snatched']
use_json_notifications = {}
http_time_between_calls = 0
- def notify(self, message = '', data = {}, listener = None):
+ def notify(self, message = '', data = None, listener = None):
+ if not data: data = {}
hosts = splitString(self.conf('host'))
@@ -33,15 +34,19 @@ class XBMC(Notification):
('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}),
]
- if not self.conf('only_first') or hosts.index(host) == 0:
- calls.append(('VideoLibrary.Scan', {}))
+ if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
+ param = {}
+ if self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0]):
+ param = {'directory': data['destination_dir']}
+
+ calls.append(('VideoLibrary.Scan', param))
max_successful += len(calls)
response = self.request(host, calls)
else:
response = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
- if not self.conf('only_first') or hosts.index(host) == 0:
+ if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0):
response += self.request(host, [('VideoLibrary.Scan', {})])
max_successful += 1
@@ -49,9 +54,9 @@ class XBMC(Notification):
try:
for result in response:
- if (result.get('result') and result['result'] == 'OK'):
+ if result.get('result') and result['result'] == 'OK':
successful += 1
- elif (result.get('error')):
+ elif result.get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
except:
@@ -68,7 +73,7 @@ class XBMC(Notification):
('JSONRPC.Version', {})
])
for result in response:
- if (result.get('result') and type(result['result']['version']).__name__ == 'int'):
+ if result.get('result') and type(result['result']['version']).__name__ == 'int':
# only v2 and v4 return an int object
# v6 (as of XBMC v12(Frodo)) is required to send notifications
xbmc_rpc_version = str(result['result']['version'])
@@ -81,15 +86,15 @@ class XBMC(Notification):
# send the text message
resp = self.notifyXBMCnoJSON(host, {'title':self.default_title, 'message':message})
for result in resp:
- if (result.get('result') and result['result'] == 'OK'):
+ if result.get('result') and result['result'] == 'OK':
log.debug('Message delivered successfully!')
success = True
break
- elif (result.get('error')):
+ elif result.get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
break
- elif (result.get('result') and type(result['result']['version']).__name__ == 'dict'):
+ elif result.get('result') and type(result['result']['version']).__name__ == 'dict':
# XBMC JSON-RPC v6 returns an array object containing
# major, minor and patch number
xbmc_rpc_version = str(result['result']['version']['major'])
@@ -104,16 +109,16 @@ class XBMC(Notification):
# send the text message
resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})])
for result in resp:
- if (result.get('result') and result['result'] == 'OK'):
+ if result.get('result') and result['result'] == 'OK':
log.debug('Message delivered successfully!')
success = True
break
- elif (result.get('error')):
+ elif result.get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
break
# error getting version info (we do have contact with XBMC though)
- elif (result.get('error')):
+ elif result.get('error'):
log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code']))
log.debug('Use JSON notifications: %s ', self.use_json_notifications)
diff --git a/couchpotato/core/plugins/automation/main.py b/couchpotato/core/plugins/automation/main.py
index 67bae1d1..92547cb0 100644
--- a/couchpotato/core/plugins/automation/main.py
+++ b/couchpotato/core/plugins/automation/main.py
@@ -26,6 +26,10 @@ class Automation(Plugin):
movie_ids = []
for imdb_id in movies:
+
+ if self.shuttingDown():
+ break
+
prop_name = 'automation.added.%s' % imdb_id
added = Env.prop(prop_name, default = False)
if not added:
@@ -35,5 +39,11 @@ class Automation(Plugin):
Env.prop(prop_name, True)
for movie_id in movie_ids:
+
+ if self.shuttingDown():
+ break
+
movie_dict = fireEvent('movie.get', movie_id, single = True)
- fireEvent('searcher.single', movie_dict)
+ fireEvent('movie.searcher.single', movie_dict)
+
+ return True
\ No newline at end of file
diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py
index aa2a99e9..ce7c1b49 100644
--- a/couchpotato/core/plugins/base.py
+++ b/couchpotato/core/plugins/base.py
@@ -2,7 +2,7 @@ from StringIO import StringIO
from couchpotato.core.event import fireEvent, addEvent
from couchpotato.core.helpers.encoding import tryUrlencode, ss, toSafeString, \
toUnicode
-from couchpotato.core.helpers.variable import getExt, md5
+from couchpotato.core.helpers.variable import getExt, md5, isLocalIP
from couchpotato.core.logger import CPLog
from couchpotato.environment import Env
from multipartpost import MultipartPostHandler
@@ -12,6 +12,7 @@ from urlparse import urlparse
import cookielib
import glob
import gzip
+import inspect
import math
import os.path
import re
@@ -24,10 +25,14 @@ log = CPLog(__name__)
class Plugin(object):
+ _class_name = None
+ plugin_path = None
+
enabled_option = 'enabled'
auto_register_static = True
_needs_shutdown = False
+ _running = None
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20130519 Firefox/24.0'
http_last_use = {}
@@ -35,16 +40,29 @@ class Plugin(object):
http_failed_request = {}
http_failed_disabled = {}
+ def __new__(typ, *args, **kwargs):
+ new_plugin = super(Plugin, typ).__new__(typ)
+ new_plugin.registerPlugin()
+
+ return new_plugin
+
def registerPlugin(self):
addEvent('app.do_shutdown', self.doShutdown)
addEvent('plugin.running', self.isRunning)
self._running = []
- def conf(self, attr, value = None, default = None):
- return Env.setting(attr, self.getName().lower(), value = value, default = default)
+ if self.auto_register_static:
+ self.registerStatic(inspect.getfile(self.__class__))
+
+ def conf(self, attr, value = None, default = None, section = None):
+ class_name = self.getName().lower().split(':')
+ return Env.setting(attr, section = section if section else class_name[0].lower(), value = value, default = default)
def getName(self):
- return self.__class__.__name__
+ return self._class_name or self.__class__.__name__
+
+ def setName(self, name):
+ self._class_name = name
def renderTemplate(self, parent_file, templ, **params):
@@ -65,7 +83,7 @@ class Plugin(object):
class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
# View path
- path = 'api/%s/static/%s/' % (Env.setting('api_key'), class_name)
+ path = 'static/plugin/%s/' % (class_name)
# Add handler to Tornado
Env.get('app').add_handlers(".*$", [(Env.get('web_base') + path + '(.*)', StaticFileHandler, {'path': static_folder})])
@@ -124,7 +142,7 @@ class Plugin(object):
if self.http_failed_disabled[host] > (time.time() - 900):
log.info2('Disabled calls to %s for 15 minutes because so many failed requests.', host)
if not show_error:
- raise
+ raise Exception('Disabled calls to %s for 15 minutes because so many failed requests')
else:
return ''
else:
@@ -187,7 +205,7 @@ class Plugin(object):
self.http_failed_request[host] += 1
# Disable temporarily
- if self.http_failed_request[host] > 5:
+ if self.http_failed_request[host] > 5 and not isLocalIP(host):
self.http_failed_disabled[host] = time.time()
except:
@@ -241,8 +259,8 @@ class Plugin(object):
def getCache(self, cache_key, url = None, **kwargs):
- cache_key = md5(ss(cache_key))
- cache = Env.get('cache').get(cache_key)
+ cache_key_md5 = md5(cache_key)
+ cache = Env.get('cache').get(cache_key_md5)
if cache:
if not Env.get('dev'): log.debug('Getting cache %s', cache_key)
return cache
@@ -266,8 +284,9 @@ class Plugin(object):
return ''
def setCache(self, cache_key, value, timeout = 300):
+ cache_key_md5 = md5(cache_key)
log.debug('Setting cache %s', cache_key)
- Env.get('cache').set(cache_key, value, timeout)
+ Env.get('cache').set(cache_key_md5, value, timeout)
return value
def createNzbName(self, data, movie):
@@ -276,9 +295,9 @@ class Plugin(object):
def createFileName(self, data, filedata, movie):
name = os.path.join(self.createNzbName(data, movie))
- if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata:
+ if data.get('protocol') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata:
return '%s.%s' % (name, 'rar')
- return '%s.%s' % (name, data.get('type'))
+ return '%s.%s' % (name, data.get('protocol'))
def cpTag(self, movie):
if Env.setting('enabled', 'renamer'):
@@ -290,4 +309,4 @@ class Plugin(object):
return not self.isEnabled()
def isEnabled(self):
- return self.conf(self.enabled_option) or self.conf(self.enabled_option) == None
+ return self.conf(self.enabled_option) or self.conf(self.enabled_option) is None
diff --git a/couchpotato/core/plugins/browser/main.py b/couchpotato/core/plugins/browser/main.py
index 6b989a08..380e6826 100644
--- a/couchpotato/core/plugins/browser/main.py
+++ b/couchpotato/core/plugins/browser/main.py
@@ -12,7 +12,7 @@ if os.name == 'nt':
except:
# todo:: subclass ImportError for missing dependencies, vs. broken plugins?
raise ImportError("Missing the win32file module, which is a part of the prerequisite \
- pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/");
+ pywin32 package. You can get it from http://sourceforge.net/projects/pywin32/files/pywin32/")
else:
import win32file #@UnresolvedImport
diff --git a/couchpotato/core/plugins/category/__init__.py b/couchpotato/core/plugins/category/__init__.py
new file mode 100644
index 00000000..6dc41df7
--- /dev/null
+++ b/couchpotato/core/plugins/category/__init__.py
@@ -0,0 +1,6 @@
+from .main import CategoryPlugin
+
+def start():
+ return CategoryPlugin()
+
+config = []
diff --git a/couchpotato/core/plugins/category/main.py b/couchpotato/core/plugins/category/main.py
new file mode 100644
index 00000000..d13a74a3
--- /dev/null
+++ b/couchpotato/core/plugins/category/main.py
@@ -0,0 +1,121 @@
+from couchpotato import get_session
+from couchpotato.api import addApiView
+from couchpotato.core.event import addEvent
+from couchpotato.core.helpers.encoding import toUnicode
+from couchpotato.core.logger import CPLog
+from couchpotato.core.plugins.base import Plugin
+from couchpotato.core.settings.model import Movie, Category
+
+log = CPLog(__name__)
+
+
+class CategoryPlugin(Plugin):
+
+ def __init__(self):
+ addEvent('category.all', self.all)
+
+ addApiView('category.save', self.save)
+ addApiView('category.save_order', self.saveOrder)
+ addApiView('category.delete', self.delete)
+ addApiView('category.list', self.allView, docs = {
+ 'desc': 'List all available categories',
+ 'return': {'type': 'object', 'example': """{
+ 'success': True,
+ 'list': array, categories
+}"""}
+ })
+
+ def allView(self, **kwargs):
+
+ return {
+ 'success': True,
+ 'list': self.all()
+ }
+
+ def all(self):
+
+ db = get_session()
+ categories = db.query(Category).all()
+
+ temp = []
+ for category in categories:
+ temp.append(category.to_dict())
+
+ db.expire_all()
+ return temp
+
+ def save(self, **kwargs):
+
+ db = get_session()
+
+ c = db.query(Category).filter_by(id = kwargs.get('id')).first()
+ if not c:
+ c = Category()
+ db.add(c)
+
+ c.order = kwargs.get('order', c.order if c.order else 0)
+ c.label = toUnicode(kwargs.get('label', ''))
+ c.ignored = toUnicode(kwargs.get('ignored', ''))
+ c.preferred = toUnicode(kwargs.get('preferred', ''))
+ c.required = toUnicode(kwargs.get('required', ''))
+ c.destination = toUnicode(kwargs.get('destination', ''))
+
+ db.commit()
+
+ category_dict = c.to_dict()
+
+ return {
+ 'success': True,
+ 'category': category_dict
+ }
+
+ def saveOrder(self, **kwargs):
+
+ db = get_session()
+
+ order = 0
+ for category_id in kwargs.get('ids', []):
+ c = db.query(Category).filter_by(id = category_id).first()
+ c.order = order
+
+ order += 1
+
+ db.commit()
+
+ return {
+ 'success': True
+ }
+
+ def delete(self, id = None, **kwargs):
+
+ db = get_session()
+
+ success = False
+ message = ''
+ try:
+ c = db.query(Category).filter_by(id = id).first()
+ db.delete(c)
+ db.commit()
+
+ # Force defaults on all empty category movies
+ self.removeFromMovie(id)
+
+ success = True
+ except Exception, e:
+ message = log.error('Failed deleting category: %s', e)
+
+ db.expire_all()
+ return {
+ 'success': success,
+ 'message': message
+ }
+
+ def removeFromMovie(self, category_id):
+
+ db = get_session()
+ movies = db.query(Movie).filter(Movie.category_id == category_id).all()
+
+ if len(movies) > 0:
+ for movie in movies:
+ movie.category_id = None
+ db.commit()
diff --git a/couchpotato/core/plugins/category/static/category.css b/couchpotato/core/plugins/category/static/category.css
new file mode 100644
index 00000000..0987c197
--- /dev/null
+++ b/couchpotato/core/plugins/category/static/category.css
@@ -0,0 +1,82 @@
+.add_new_category {
+ padding: 20px;
+ display: block;
+ text-align: center;
+ font-size: 20px;
+ border-bottom: 1px solid rgba(255,255,255,0.2);
+}
+
+.category {
+ border-bottom: 1px solid rgba(255,255,255,0.2);
+ position: relative;
+}
+
+ .category > .delete {
+ position: absolute;
+ padding: 16px;
+ right: 0;
+ cursor: pointer;
+ opacity: 0.6;
+ color: #fd5353;
+ }
+ .category > .delete:hover {
+ opacity: 1;
+ }
+
+ .category .ctrlHolder:hover {
+ background: none;
+ }
+
+ .category .formHint {
+ width: 250px !important;
+ margin: 0 !important;
+ opacity: 0.1;
+ }
+ .category:hover .formHint {
+ opacity: 1;
+ }
+
+#category_ordering {
+
+}
+
+ #category_ordering ul {
+ float: left;
+ margin: 0;
+ width: 275px;
+ padding: 0;
+ }
+
+ #category_ordering li {
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+ border-bottom: 1px solid rgba(255,255,255,0.2);
+ padding: 0 5px;
+ }
+ #category_ordering li:last-child { border: 0; }
+
+ #category_ordering li .check {
+ margin: 2px 10px 0 0;
+ vertical-align: top;
+ }
+
+ #category_ordering li > span {
+ display: inline-block;
+ height: 20px;
+ vertical-align: top;
+ line-height: 20px;
+ }
+
+ #category_ordering li .handle {
+ background: url('../../static/profile_plugin/handle.png') center;
+ width: 20px;
+ float: right;
+ }
+
+ #category_ordering .formHint {
+ clear: none;
+ float: right;
+ width: 250px;
+ margin: 0;
+ }
\ No newline at end of file
diff --git a/couchpotato/core/plugins/category/static/category.js b/couchpotato/core/plugins/category/static/category.js
new file mode 100644
index 00000000..168b70de
--- /dev/null
+++ b/couchpotato/core/plugins/category/static/category.js
@@ -0,0 +1,332 @@
+var CategoryListBase = new Class({
+
+ initialize: function(){
+ var self = this;
+
+ App.addEvent('load', self.addSettings.bind(self));
+ },
+
+ setup: function(categories){
+ var self = this;
+
+ self.categories = []
+ Array.each(categories, self.createCategory.bind(self));
+
+ },
+
+ addSettings: function(){
+ var self = this;
+
+ self.settings = App.getPage('Settings')
+ self.settings.addEvent('create', function(){
+ var tab = self.settings.createSubTab('category', {
+ 'label': 'Categories',
+ 'name': 'category',
+ 'subtab_label': 'Category & filtering'
+ }, self.settings.tabs.searcher ,'searcher');
+
+ self.tab = tab.tab;
+ self.content = tab.content;
+
+ self.createList();
+ self.createOrdering();
+
+ })
+
+ // Add categories in renamer
+ self.settings.addEvent('create', function(){
+ var renamer_group = self.settings.tabs.renamer.groups.renamer;
+
+ self.categories.each(function(category){
+
+ var input = new Option.Directory('section_name', 'option.name', category.get('destination'), {
+ 'name': category.get('label')
+ });
+ input.inject(renamer_group.getElement('.renamer_to'));
+ input.fireEvent('injected');
+
+ input.save = function(){
+ category.data.destination = input.getValue();
+ category.save();
+ };
+
+ });
+
+ })
+
+ },
+
+ createList: function(){
+ var self = this;
+
+ var count = self.categories.length;
+
+ self.settings.createGroup({
+ 'label': 'Categories',
+ 'description': 'Create categories, each one extending global filters. (Needs refresh \'' +(App.isMac() ? 'CMD+R' : 'F5')+ '\' after editing)'
+ }).inject(self.content).adopt(
+ self.category_container = new Element('div.container'),
+ new Element('a.add_new_category', {
+ 'text': count > 0 ? 'Create another category' : 'Click here to create a category.',
+ 'events': {
+ 'click': function(){
+ var category = self.createCategory();
+ $(category).inject(self.category_container)
+ }
+ }
+ })
+ );
+
+ // Add categories, that aren't part of the core (for editing)
+ Array.each(self.categories, function(category){
+ $(category).inject(self.category_container)
+ });
+
+ },
+
+ getCategory: function(id){
+ return this.categories.filter(function(category){
+ return category.data.id == id
+ }).pick()
+ },
+
+ getAll: function(){
+ return this.categories;
+ },
+
+ createCategory: function(data){
+ var self = this;
+
+ var data = data || {'id': randomString()}
+ var category = new Category(data)
+ self.categories.include(category)
+
+ return category;
+ },
+
+ createOrdering: function(){
+ var self = this;
+
+ var category_list;
+ var group = self.settings.createGroup({
+ 'label': 'Category ordering'
+ }).adopt(
+ new Element('.ctrlHolder#category_ordering').adopt(
+ new Element('label[text=Order]'),
+ category_list = new Element('ul'),
+ new Element('p.formHint', {
+ 'html': 'Change the order the categories are in the dropdown list.
First one will be default.'
+ })
+ )
+ ).inject(self.content)
+
+ Array.each(self.categories, function(category){
+ new Element('li', {'data-id': category.data.id}).adopt(
+ new Element('span.category_label', {
+ 'text': category.data.label
+ }),
+ new Element('span.handle')
+ ).inject(category_list);
+
+ });
+
+ // Sortable
+ self.category_sortable = new Sortables(category_list, {
+ 'revert': true,
+ 'handle': '',
+ 'opacity': 0.5,
+ 'onComplete': self.saveOrdering.bind(self)
+ });
+
+ },
+
+ saveOrdering: function(){
+ var self = this;
+
+ var ids = [];
+
+ self.category_sortable.list.getElements('li').each(function(el, nr){
+ ids.include(el.get('data-id'));
+ });
+
+ Api.request('category.save_order', {
+ 'data': {
+ 'ids': ids
+ }
+ });
+
+ }
+
+})
+
+window.CategoryList = new CategoryListBase();
+
+var Category = new Class({
+
+ data: {},
+
+ initialize: function(data){
+ var self = this;
+
+ self.data = data;
+
+ self.create();
+
+ self.el.addEvents({
+ 'change:relay(select)': self.save.bind(self, 0),
+ 'keyup:relay(input[type=text])': self.save.bind(self, [300])
+ });
+
+ },
+
+ create: function(){
+ var self = this;
+
+ var data = self.data;
+
+ self.el = new Element('div.category').adopt(
+ self.delete_button = new Element('span.delete.icon2', {
+ 'events': {
+ 'click': self.del.bind(self)
+ }
+ }),
+ new Element('.category_label.ctrlHolder').adopt(
+ new Element('label', {'text':'Name'}),
+ new Element('input.inlay', {
+ 'type':'text',
+ 'value': data.label,
+ 'placeholder': 'Example: Kids, Horror or His'
+ }),
+ new Element('p.formHint', {'text': 'See global filters for explanation.'})
+ ),
+ new Element('.category_preferred.ctrlHolder').adopt(
+ new Element('label', {'text':'Preferred'}),
+ new Element('input.inlay', {
+ 'type':'text',
+ 'value': data.preferred,
+ 'placeholder': 'Blu-ray, DTS'
+ })
+ ),
+ new Element('.category_required.ctrlHolder').adopt(
+ new Element('label', {'text':'Required'}),
+ new Element('input.inlay', {
+ 'type':'text',
+ 'value': data.required,
+ 'placeholder': 'Example: DTS, AC3 & English'
+ })
+ ),
+ new Element('.category_ignored.ctrlHolder').adopt(
+ new Element('label', {'text':'Ignored'}),
+ new Element('input.inlay', {
+ 'type':'text',
+ 'value': data.ignored,
+ 'placeholder': 'Example: dubbed, swesub, french'
+ })
+ )
+ );
+
+ self.makeSortable()
+
+ },
+
+ save: function(delay){
+ var self = this;
+
+ if(self.save_timer) clearTimeout(self.save_timer);
+ self.save_timer = (function(){
+
+ var data = self.getData();
+
+ Api.request('category.save', {
+ 'data': self.getData(),
+ 'useSpinner': true,
+ 'spinnerOptions': {
+ 'target': self.el
+ },
+ 'onComplete': function(json){
+ if(json.success){
+ self.data = json.category;
+ }
+ }
+ });
+
+ }).delay(delay || 0, self)
+
+ },
+
+ getData: function(){
+ var self = this;
+
+ var data = {
+ 'id' : self.data.id,
+ 'label' : self.el.getElement('.category_label input').get('value'),
+ 'required' : self.el.getElement('.category_required input').get('value'),
+ 'preferred' : self.el.getElement('.category_preferred input').get('value'),
+ 'ignored' : self.el.getElement('.category_ignored input').get('value'),
+ 'destination': self.data.destination
+ }
+
+ return data
+ },
+
+ del: function(){
+ var self = this;
+
+ if(self.data.label == undefined){
+ self.el.destroy();
+ return;
+ }
+
+ var label = self.el.getElement('.category_label input').get('value');
+ var qObj = new Question('Are you sure you want to delete "'+label+'"?', '', [{
+ 'text': 'Delete "'+label+'"',
+ 'class': 'delete',
+ 'events': {
+ 'click': function(e){
+ (e).preventDefault();
+ Api.request('category.delete', {
+ 'data': {
+ 'id': self.data.id
+ },
+ 'useSpinner': true,
+ 'spinnerOptions': {
+ 'target': self.el
+ },
+ 'onComplete': function(json){
+ if(json.success) {
+ qObj.close();
+ self.el.destroy();
+ } else {
+ alert(json.message);
+ }
+ }
+ });
+ }
+ }
+ }, {
+ 'text': 'Cancel',
+ 'cancel': true
+ }]);
+
+ },
+
+ makeSortable: function(){
+ var self = this;
+
+ self.sortable = new Sortables(self.category_container, {
+ 'revert': true,
+ 'handle': '.handle',
+ 'opacity': 0.5,
+ 'onComplete': self.save.bind(self, 300)
+ });
+ },
+
+ get: function(attr){
+ return this.data[attr]
+ },
+
+ toElement: function(){
+ return this.el
+ }
+
+});
\ No newline at end of file
diff --git a/couchpotato/core/plugins/category/static/handle.png b/couchpotato/core/plugins/category/static/handle.png
new file mode 100644
index 00000000..adff5b29
Binary files /dev/null and b/couchpotato/core/plugins/category/static/handle.png differ
diff --git a/couchpotato/core/plugins/dashboard/main.py b/couchpotato/core/plugins/dashboard/main.py
index df21fefa..2da4d8cc 100644
--- a/couchpotato/core/plugins/dashboard/main.py
+++ b/couchpotato/core/plugins/dashboard/main.py
@@ -4,8 +4,9 @@ from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import splitString, tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
-from couchpotato.core.settings.model import Movie
+from couchpotato.core.settings.model import Movie, Library, LibraryTitle
from sqlalchemy.orm import joinedload_all
+from sqlalchemy.sql.expression import asc
import random as rndm
import time
@@ -40,67 +41,81 @@ class Dashboard(Plugin):
profile_pre[profile.get('id')] = contains
- # Get all active movies
- active_status, snatched_status, downloaded_status, available_status = fireEvent('status.get', ['active', 'snatched', 'downloaded', 'available'], single = True)
- subq = db.query(Movie).filter(Movie.status_id == active_status.get('id')).subquery()
-
- q = db.query(Movie).join((subq, subq.c.id == Movie.id)) \
- .options(joinedload_all('releases')) \
- .options(joinedload_all('profile.types')) \
- .options(joinedload_all('library.titles')) \
- .options(joinedload_all('library.files')) \
- .options(joinedload_all('status')) \
- .options(joinedload_all('files'))
-
# Add limit
limit = 12
if limit_offset:
splt = splitString(limit_offset) if isinstance(limit_offset, (str, unicode)) else limit_offset
limit = tryInt(splt[0])
- all_movies = q.all()
+ # Get all active movies
+ active_status = fireEvent('status.get', ['active'], single = True)
+ q = db.query(Movie) \
+ .join(Library) \
+ .filter(Movie.status_id == active_status.get('id')) \
+ .with_entities(Movie.id, Movie.profile_id, Library.info, Library.year) \
+ .group_by(Movie.id)
- if random:
- rndm.shuffle(all_movies)
+ if not random:
+ q = q.join(LibraryTitle) \
+ .filter(LibraryTitle.default == True) \
+ .order_by(asc(LibraryTitle.simple_title))
+ active = q.all()
movies = []
- for movie in all_movies:
- pp = profile_pre.get(movie.profile.id)
- eta = movie.library.info.get('release_date', {}) or {}
- coming_soon = False
- # Theater quality
- if pp.get('theater') and fireEvent('searcher.could_be_released', True, eta, movie.library.year, single = True):
- coming_soon = True
- if pp.get('dvd') and fireEvent('searcher.could_be_released', False, eta, movie.library.year, single = True):
- coming_soon = True
+ if len(active) > 0:
- # Skip if movie is snatched/downloaded/available
- skip = False
- for release in movie.releases:
- if release.status_id in [snatched_status.get('id'), downloaded_status.get('id'), available_status.get('id')]:
- skip = True
- break
- if skip:
- continue
+ # Do the shuffle
+ if random:
+ rndm.shuffle(active)
- if coming_soon:
- temp = movie.to_dict({
- 'profile': {'types': {}},
- 'releases': {'files':{}, 'info': {}},
- 'library': {'titles': {}, 'files':{}},
- 'files': {},
- })
+ movie_ids = []
+ for movie in active:
+ movie_id, profile_id, info, year = movie
- # Don't list older movies
- if ((not late and ((not eta.get('dvd') and not eta.get('theater')) or (eta.get('dvd') and eta.get('dvd') > (now - 2419200)))) or \
- (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
- movies.append(temp)
+ pp = profile_pre.get(profile_id)
+ if not pp: continue
- if len(movies) >= limit:
- break
+ eta = info.get('release_date', {}) or {}
+ coming_soon = False
+
+ # Theater quality
+ if pp.get('theater') and fireEvent('movie.searcher.could_be_released', True, eta, year, single = True):
+ coming_soon = True
+ elif pp.get('dvd') and fireEvent('movie.searcher.could_be_released', False, eta, year, single = True):
+ coming_soon = True
+
+ if coming_soon:
+
+ # Don't list older movies
+ if ((not late and (not eta.get('dvd') and not eta.get('theater') or eta.get('dvd') and eta.get('dvd') > (now - 2419200))) or
+ (late and (eta.get('dvd', 0) > 0 or eta.get('theater')) and eta.get('dvd') < (now - 2419200))):
+ movie_ids.append(movie_id)
+
+ if len(movie_ids) >= limit:
+ break
+
+ if len(movie_ids) > 0:
+
+ # Get all movie information
+ movies_raw = db.query(Movie) \
+ .options(joinedload_all('library.titles')) \
+ .options(joinedload_all('library.files')) \
+ .options(joinedload_all('files')) \
+ .filter(Movie.id.in_(movie_ids)) \
+ .all()
+
+ # Create dict by movie id
+ movie_dict = {}
+ for movie in movies_raw:
+ movie_dict[movie.id] = movie
+
+ for movie_id in movie_ids:
+ movies.append(movie_dict[movie_id].to_dict({
+ 'library': {'titles': {}, 'files':{}},
+ 'files': {},
+ }))
- db.expire_all()
return {
'success': True,
'empty': len(movies) == 0,
diff --git a/couchpotato/core/plugins/file/main.py b/couchpotato/core/plugins/file/main.py
index cdd67f5c..fc63aca8 100644
--- a/couchpotato/core/plugins/file/main.py
+++ b/couchpotato/core/plugins/file/main.py
@@ -71,11 +71,11 @@ class FileManager(Plugin):
db = get_session()
for root, dirs, walk_files in os.walk(Env.get('cache_dir')):
for filename in walk_files:
- if root == python_cache or 'minified' in filename or 'version' in filename or 'temp_updater' in root: continue
- file_path = os.path.join(root, filename)
- f = db.query(File).filter(File.path == toUnicode(file_path)).first()
- if not f:
- os.remove(file_path)
+ if os.path.splitext(filename)[1] in ['.png', '.jpg', '.jpeg']:
+ file_path = os.path.join(root, filename)
+ f = db.query(File).filter(File.path == toUnicode(file_path)).first()
+ if not f:
+ os.remove(file_path)
except:
log.error('Failed removing unused file: %s', traceback.format_exc())
@@ -83,7 +83,8 @@ class FileManager(Plugin):
Env.get('app').add_handlers(".*$", [('%s%s' % (Env.get('api_base'), route), StaticFileHandler, {'path': Env.get('cache_dir')})])
- def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = {}):
+ def download(self, url = '', dest = None, overwrite = False, urlopen_kwargs = None):
+ if not urlopen_kwargs: urlopen_kwargs = {}
if not dest: # to Cache
dest = os.path.join(Env.get('cache_dir'), '%s.%s' % (md5(url), getExt(url)))
@@ -100,7 +101,9 @@ class FileManager(Plugin):
self.createFile(dest, filedata, binary = True)
return dest
- def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = {}):
+ def add(self, path = '', part = 1, type_tuple = (), available = 1, properties = None):
+ if not properties: properties = {}
+
type_id = self.getType(type_tuple).get('id')
db = get_session()
diff --git a/couchpotato/core/plugins/library/__init__.py b/couchpotato/core/plugins/library/__init__.py
deleted file mode 100644
index f5970329..00000000
--- a/couchpotato/core/plugins/library/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from .main import LibraryPlugin
-
-def start():
- return LibraryPlugin()
-
-config = []
diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py
index ee88b8d4..dc8f740f 100644
--- a/couchpotato/core/plugins/log/main.py
+++ b/couchpotato/core/plugins/log/main.py
@@ -90,7 +90,6 @@ class Logging(Plugin):
if not os.path.isfile(path):
break
- reversed_lines = []
f = open(path, 'r')
reversed_lines = toUnicode(f.read()).split('[0m\n')
reversed_lines.reverse()
@@ -120,7 +119,7 @@ class Logging(Plugin):
path = '%s%s' % (Env.get('log_path'), '.%s' % x if x > 0 else '')
if not os.path.isfile(path):
- break
+ continue
try:
diff --git a/couchpotato/core/plugins/manage/__init__.py b/couchpotato/core/plugins/manage/__init__.py
index 46eee210..912296b4 100644
--- a/couchpotato/core/plugins/manage/__init__.py
+++ b/couchpotato/core/plugins/manage/__init__.py
@@ -28,6 +28,14 @@ config = [{
'description': 'Remove movie from db if it can\'t be found after re-scan.',
'default': True,
},
+ {
+ 'label': 'Scan at startup',
+ 'name': 'startup_scan',
+ 'type': 'bool',
+ 'default': True,
+ 'advanced': True,
+ 'description': 'Do a quick scan on startup. On slow systems better disable this.',
+ },
],
},
],
diff --git a/couchpotato/core/plugins/manage/main.py b/couchpotato/core/plugins/manage/main.py
index 454e765c..702b1293 100644
--- a/couchpotato/core/plugins/manage/main.py
+++ b/couchpotato/core/plugins/manage/main.py
@@ -26,7 +26,8 @@ class Manage(Plugin):
addEvent('manage.diskspace', self.getDiskSpace)
# Add files after renaming
- def after_rename(message = None, group = {}):
+ def after_rename(message = None, group = None):
+ if not group: group = {}
return self.scanFilesToLibrary(folder = group['destination_dir'], files = group['renamed_files'])
addEvent('renamer.after', after_rename, priority = 110)
@@ -44,7 +45,7 @@ class Manage(Plugin):
}"""},
})
- if not Env.get('dev'):
+ if not Env.get('dev') and self.conf('startup_scan'):
addEvent('app.load', self.updateLibraryQuick)
def getProgress(self, **kwargs):
@@ -117,7 +118,9 @@ class Manage(Plugin):
fireEvent('movie.delete', movie_id = done_movie['id'], delete_from = 'all')
else:
- for release in done_movie.get('releases', []):
+ releases = fireEvent('release.for_movie', id = done_movie.get('id'), single = True)
+
+ for release in releases:
if len(release.get('files', [])) == 0:
fireEvent('release.delete', release['id'])
else:
@@ -128,9 +131,9 @@ class Manage(Plugin):
break
# Check if there are duplicate releases (different quality) use the last one, delete the rest
- if len(done_movie.get('releases', [])) > 1:
+ if len(releases) > 1:
used_files = {}
- for release in done_movie.get('releases', []):
+ for release in releases:
for release_file in release.get('files', []):
already_used = used_files.get(release_file['path'])
@@ -169,6 +172,7 @@ class Manage(Plugin):
self.in_progress = False
def createAddToLibrary(self, folder, added_identifiers = []):
+
def addToLibrary(group, total_found, to_go):
if self.in_progress[folder]['total'] is None:
self.in_progress[folder] = {
@@ -182,9 +186,9 @@ class Manage(Plugin):
# Add it to release and update the info
fireEvent('release.add', group = group)
- fireEventAsync('library.update', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
+ fireEventAsync('library.update.movie', identifier = identifier, on_complete = self.createAfterUpdate(folder, identifier))
else:
- self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
+ self.in_progress[folder]['to_go'] -= 1
return addToLibrary
@@ -192,7 +196,10 @@ class Manage(Plugin):
# Notify frontend
def afterUpdate():
- self.in_progress[folder]['to_go'] = self.in_progress[folder]['to_go'] - 1
+ if not self.in_progress or self.shuttingDown():
+ return
+
+ self.in_progress[folder]['to_go'] -= 1
total = self.in_progress[folder]['total']
movie_dict = fireEvent('movie.get', identifier, single = True)
diff --git a/couchpotato/core/plugins/movie/__init__.py b/couchpotato/core/plugins/movie/__init__.py
deleted file mode 100644
index 4df29ad8..00000000
--- a/couchpotato/core/plugins/movie/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from .main import MoviePlugin
-
-def start():
- return MoviePlugin()
-
-config = []
diff --git a/couchpotato/core/plugins/profile/main.py b/couchpotato/core/plugins/profile/main.py
index c70d7c99..68ab9360 100644
--- a/couchpotato/core/plugins/profile/main.py
+++ b/couchpotato/core/plugins/profile/main.py
@@ -5,6 +5,7 @@ from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Profile, ProfileType, Movie
+from sqlalchemy.orm import joinedload_all
log = CPLog(__name__)
@@ -55,7 +56,9 @@ class ProfilePlugin(Plugin):
def all(self):
db = get_session()
- profiles = db.query(Profile).all()
+ profiles = db.query(Profile) \
+ .options(joinedload_all('types')) \
+ .all()
temp = []
for profile in profiles:
@@ -104,7 +107,9 @@ class ProfilePlugin(Plugin):
def default(self):
db = get_session()
- default = db.query(Profile).first()
+ default = db.query(Profile) \
+ .options(joinedload_all('types')) \
+ .first()
default_dict = default.to_dict(self.to_dict)
db.expire_all()
@@ -155,7 +160,7 @@ class ProfilePlugin(Plugin):
def fill(self):
- db = get_session();
+ db = get_session()
profiles = [{
'label': 'Best',
diff --git a/couchpotato/core/plugins/quality/main.py b/couchpotato/core/plugins/quality/main.py
index 6ac1c6b6..1149c036 100644
--- a/couchpotato/core/plugins/quality/main.py
+++ b/couchpotato/core/plugins/quality/main.py
@@ -19,10 +19,10 @@ class QualityPlugin(Plugin):
{'identifier': 'bd50', 'hd': True, 'size': (15000, 60000), 'label': 'BR-Disk', 'alternative': ['bd25'], 'allow': ['1080p'], 'ext':[], 'tags': ['bdmv', 'certificate', ('complete', 'bluray')]},
{'identifier': '1080p', 'hd': True, 'size': (4000, 20000), 'label': '1080p', 'width': 1920, 'height': 1080, 'alternative': [], 'allow': [], 'ext':['mkv', 'm2ts'], 'tags': ['m2ts']},
{'identifier': '720p', 'hd': True, 'size': (3000, 10000), 'label': '720p', 'width': 1280, 'height': 720, 'alternative': [], 'allow': [], 'ext':['mkv', 'ts']},
- {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi']},
+ {'identifier': 'brrip', 'hd': True, 'size': (700, 7000), 'label': 'BR-Rip', 'alternative': ['bdrip'], 'allow': ['720p', '1080p'], 'ext':['avi'], 'tags': ['hdtv', 'hdrip', 'webdl', ('web', 'dl')]},
{'identifier': 'dvdr', 'size': (3000, 10000), 'label': 'DVD-R', 'alternative': [], 'allow': [], 'ext':['iso', 'img'], 'tags': ['pal', 'ntsc', 'video_ts', 'audio_ts']},
- {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': ['dvdrip'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
- {'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg']},
+ {'identifier': 'dvdrip', 'size': (600, 2400), 'label': 'DVD-Rip', 'width': 720, 'alternative': [], 'allow': [], 'ext':['avi', 'mpg', 'mpeg'], 'tags': [('dvd', 'rip'), ('dvd', 'xvid'), ('dvd', 'divx')]},
+ {'identifier': 'scr', 'size': (600, 1600), 'label': 'Screener', 'alternative': ['screener', 'dvdscr', 'ppvrip', 'dvdscreener', 'hdscr'], 'allow': ['dvdr', 'dvd'], 'ext':['avi', 'mpg', 'mpeg'], 'tags': ['webrip', ('web', 'rip')]},
{'identifier': 'r5', 'size': (600, 1000), 'label': 'R5', 'alternative': ['r6'], 'allow': ['dvdr'], 'ext':['avi', 'mpg', 'mpeg']},
{'identifier': 'tc', 'size': (600, 1000), 'label': 'TeleCine', 'alternative': ['telecine'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
{'identifier': 'ts', 'size': (600, 1000), 'label': 'TeleSync', 'alternative': ['telesync', 'hdts'], 'allow': [], 'ext':['avi', 'mpg', 'mpeg']},
@@ -102,7 +102,7 @@ class QualityPlugin(Plugin):
def fill(self):
- db = get_session();
+ db = get_session()
order = 0
for q in self.qualities:
@@ -152,45 +152,61 @@ class QualityPlugin(Plugin):
return True
- def guess(self, files, extra = {}):
+ def guess(self, files, extra = None):
+ if not extra: extra = {}
# Create hash for cache
- hash = md5(str([f.replace('.' + getExt(f), '') for f in files]))
- cached = self.getCache(hash)
- if cached and extra is {}: return cached
+ cache_key = md5(str([f.replace('.' + getExt(f), '') for f in files]))
+ cached = self.getCache(cache_key)
+ if cached and len(extra) == 0: return cached
+ qualities = self.all()
for cur_file in files:
words = re.split('\W+', cur_file.lower())
- for quality in self.all():
+ found = {}
+ for quality in qualities:
+ contains = self.containsTag(quality, words, cur_file)
+ if contains:
+ found[quality['identifier']] = True
- # Check tags
+ for quality in qualities:
+
+ # Check identifier
if quality['identifier'] in words:
- log.debug('Found via identifier "%s" in %s', (quality['identifier'], cur_file))
- return self.setCache(hash, quality)
+ if len(found) == 0 or len(found) == 1 and found.get(quality['identifier']):
+ log.debug('Found via identifier "%s" in %s', (quality['identifier'], cur_file))
+ return self.setCache(cache_key, quality)
- if list(set(quality.get('alternative', [])) & set(words)):
- log.debug('Found %s via alt %s in %s', (quality['identifier'], quality.get('alternative'), cur_file))
- return self.setCache(hash, quality)
-
- for tag in quality.get('tags', []):
- if isinstance(tag, tuple) and '.'.join(tag) in '.'.join(words):
- log.debug('Found %s via tag %s in %s', (quality['identifier'], quality.get('tags'), cur_file))
- return self.setCache(hash, quality)
-
- if list(set(quality.get('tags', [])) & set(words)):
- log.debug('Found %s via tag %s in %s', (quality['identifier'], quality.get('tags'), cur_file))
- return self.setCache(hash, quality)
+ # Check alt and tags
+ contains = self.containsTag(quality, words, cur_file)
+ if contains:
+ return self.setCache(cache_key, quality)
# Try again with loose testing
- quality = self.guessLoose(hash, files = files, extra = extra)
+ quality = self.guessLoose(cache_key, files = files, extra = extra)
if quality:
- return self.setCache(hash, quality)
+ return self.setCache(cache_key, quality)
log.debug('Could not identify quality for: %s', files)
return None
- def guessLoose(self, hash, files = None, extra = None):
+ def containsTag(self, quality, words, cur_file = ''):
+
+ # Check alt and tags
+ for tag_type in ['alternative', 'tags']:
+ for alt in quality.get(tag_type, []):
+ if isinstance(alt, tuple) and '.'.join(alt) in '.'.join(words):
+ log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
+ return True
+
+ if list(set(quality.get(tag_type, [])) & set(words)):
+ log.debug('Found %s via %s %s in %s', (quality['identifier'], tag_type, quality.get(tag_type), cur_file))
+ return True
+
+ return
+
+ def guessLoose(self, cache_key, files = None, extra = None):
if extra:
for quality in self.all():
@@ -198,15 +214,15 @@ class QualityPlugin(Plugin):
# Check width resolution, range 20
if quality.get('width') and (quality.get('width') - 20) <= extra.get('resolution_width', 0) <= (quality.get('width') + 20):
log.debug('Found %s via resolution_width: %s == %s', (quality['identifier'], quality.get('width'), extra.get('resolution_width', 0)))
- return self.setCache(hash, quality)
+ return self.setCache(cache_key, quality)
# Check height resolution, range 20
if quality.get('height') and (quality.get('height') - 20) <= extra.get('resolution_height', 0) <= (quality.get('height') + 20):
log.debug('Found %s via resolution_height: %s == %s', (quality['identifier'], quality.get('height'), extra.get('resolution_height', 0)))
- return self.setCache(hash, quality)
+ return self.setCache(cache_key, quality)
if 480 <= extra.get('resolution_width', 0) <= 720:
log.debug('Found as dvdrip')
- return self.setCache(hash, self.single('dvdrip'))
+ return self.setCache(cache_key, self.single('dvdrip'))
return None
diff --git a/couchpotato/core/plugins/quality/static/quality.js b/couchpotato/core/plugins/quality/static/quality.js
index bd2ff2ac..ead9a904 100644
--- a/couchpotato/core/plugins/quality/static/quality.js
+++ b/couchpotato/core/plugins/quality/static/quality.js
@@ -41,7 +41,8 @@ var QualityBase = new Class({
self.settings.addEvent('create', function(){
var tab = self.settings.createSubTab('profile', {
'label': 'Quality',
- 'name': 'profile'
+ 'name': 'profile',
+ 'subtab_label': 'Qualities'
}, self.settings.tabs.searcher ,'searcher');
self.tab = tab.tab;
@@ -102,7 +103,8 @@ var QualityBase = new Class({
var profile_list;
var group = self.settings.createGroup({
- 'label': 'Profile Defaults'
+ 'label': 'Profile Defaults',
+ 'description': '(Needs refresh \'' +(App.isMac() ? 'CMD+R' : 'F5')+ '\' after editing)'
}).adopt(
new Element('.ctrlHolder#profile_ordering').adopt(
new Element('label[text=Order]'),
diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py
index bd66b1aa..46857adf 100644
--- a/couchpotato/core/plugins/release/main.py
+++ b/couchpotato/core/plugins/release/main.py
@@ -6,8 +6,10 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.plugins.scanner.main import Scanner
from couchpotato.core.settings.model import File, Release as Relea, Movie
+from sqlalchemy.orm import joinedload_all
from sqlalchemy.sql.expression import and_, or_
import os
+import traceback
log = CPLog(__name__)
@@ -35,7 +37,14 @@ class Release(Plugin):
'id': {'type': 'id', 'desc': 'ID of the release object in release-table'}
}
})
+ addApiView('release.for_movie', self.forMovieView, docs = {
+ 'desc': 'Returns all releases for a movie. Ordered by score(desc)',
+ 'params': {
+ 'id': {'type': 'id', 'desc': 'ID of the movie'}
+ }
+ })
+ addEvent('release.for_movie', self.forMovie)
addEvent('release.delete', self.delete)
addEvent('release.clean', self.clean)
@@ -88,8 +97,8 @@ class Release(Plugin):
added_files = db.query(File).filter(or_(*[File.id == x for x in added_files])).all()
rel.files.extend(added_files)
db.commit()
- except Exception, e:
- log.debug('Failed to attach "%s" to release: %s', (cur_file, e))
+ except:
+ log.debug('Failed to attach "%s" to release: %s', (added_files, traceback.format_exc()))
fireEvent('movie.restatus', movie.id)
@@ -174,7 +183,11 @@ class Release(Plugin):
# Get matching provider
provider = fireEvent('provider.belongs_to', item['url'], provider = item.get('provider'), single = True)
- if item['type'] != 'torrent_magnet':
+ if not item.get('protocol'):
+ item['protocol'] = item['type']
+ item['type'] = 'movie'
+
+ if item.get('protocol') != 'torrent_magnet':
item['download'] = provider.loginDownload if provider.urls.get('login') else provider.download
success = fireEvent('searcher.download', data = item, movie = rel.movie.to_dict({
@@ -203,3 +216,28 @@ class Release(Plugin):
return {
'success': False
}
+
+ def forMovie(self, id = None):
+
+ db = get_session()
+
+ releases_raw = db.query(Relea) \
+ .options(joinedload_all('info')) \
+ .options(joinedload_all('files')) \
+ .filter(Relea.movie_id == id) \
+ .all()
+
+ releases = [r.to_dict({'info':{}, 'files':{}}) for r in releases_raw]
+ releases = sorted(releases, key = lambda k: k['info'].get('score', 0), reverse = True)
+
+ return releases
+
+ def forMovieView(self, id = None, **kwargs):
+
+ releases = self.forMovie(id)
+
+ return {
+ 'releases': releases,
+ 'success': True
+ }
+
diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py
old mode 100644
new mode 100755
index 155a939b..921b3e1e
--- a/couchpotato/core/plugins/renamer/__init__.py
+++ b/couchpotato/core/plugins/renamer/__init__.py
@@ -27,6 +27,7 @@ rename_options = {
'imdb_id': 'IMDB id (tt0123456)',
'cd': 'CD number (cd1)',
'cd_nr': 'Just the cd nr. (1)',
+ 'mpaa': 'MPAA Rating',
},
}
@@ -54,7 +55,7 @@ config = [{
{
'name': 'to',
'type': 'directory',
- 'description': 'Folder where the movies should be moved to.',
+ 'description': 'Default folder where the movies are moved to.',
},
{
'name': 'folder_name',
@@ -72,6 +73,12 @@ config = [{
'type': 'choice',
'options': rename_options
},
+ {
+ 'name': 'unrar',
+ 'type': 'bool',
+ 'description': 'Extract rar files if found.',
+ 'default': False,
+ },
{
'name': 'cleanup',
'type': 'bool',
@@ -113,16 +120,22 @@ config = [{
{
'advanced': True,
'name': 'separator',
- 'label': 'Separator',
+ 'label': 'File-Separator',
+ 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
+ },
+ {
+ 'advanced': True,
+ 'name': 'foldersep',
+ 'label': 'Folder-Separator',
'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.',
},
{
'name': 'file_action',
'label': 'Torrent File Action',
- 'default': 'move',
+ 'default': 'link',
'type': 'dropdown',
- 'values': [('Move', 'move'), ('Copy', 'copy'), ('Hard link', 'hardlink'), ('Sym link', 'symlink'), ('Move & Sym link', 'move_symlink')],
- 'description': 'Define which kind of file operation you want to use for torrents. Before you start using hard links or sym links, PLEASE read about their possible drawbacks.',
+ 'values': [('Link', 'link'), ('Copy', 'copy'), ('Move', 'move')],
+ 'description': 'Link or Copy after downloading completed (and allow for seeding), or Move after seeding completed. Link first tries hard link, then sym link and falls back to Copy.',
'advanced': True,
},
{
diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py
old mode 100644
new mode 100755
index a2fb5dc5..ad7df1cf
--- a/couchpotato/core/plugins/renamer/main.py
+++ b/couchpotato/core/plugins/renamer/main.py
@@ -9,7 +9,9 @@ from couchpotato.core.plugins.base import Plugin
from couchpotato.core.settings.model import Library, File, Profile, Release, \
ReleaseInfo
from couchpotato.environment import Env
+from unrar2 import RarFile
import errno
+import fnmatch
import os
import re
import shutil
@@ -38,7 +40,6 @@ class Renamer(Plugin):
addEvent('renamer.check_snatched', self.checkSnatched)
addEvent('app.load', self.scan)
- addEvent('app.load', self.checkSnatched)
addEvent('app.load', self.setCrons)
# Enable / disable interval
@@ -60,23 +61,24 @@ class Renamer(Plugin):
def scanView(self, **kwargs):
- async = tryInt(kwargs.get('async', None))
- movie_folder = kwargs.get('movie_folder', None)
- downloader = kwargs.get('downloader', None)
- download_id = kwargs.get('download_id', None)
+ async = tryInt(kwargs.get('async', 0))
+ movie_folder = kwargs.get('movie_folder')
+ downloader = kwargs.get('downloader')
+ download_id = kwargs.get('download_id')
+
+ download_info = {'folder': movie_folder} if movie_folder else None
+ if download_info:
+ download_info.update({'id': download_id, 'downloader': downloader} if download_id else {})
fire_handle = fireEvent if not async else fireEventAsync
- fire_handle('renamer.scan',
- movie_folder = movie_folder,
- download_info = {'id': download_id, 'downloader': downloader} if download_id else None
- )
+ fire_handle('renamer.scan', download_info)
return {
'success': True
}
- def scan(self, movie_folder = None, download_info = None):
+ def scan(self, download_info = None):
if self.isDisabled():
return
@@ -85,6 +87,8 @@ class Renamer(Plugin):
log.info('Renamer is already running, if you see this often, check the logs above for errors.')
return
+ movie_folder = download_info and download_info.get('folder')
+
# Check to see if the "to" folder is inside the "from" folder.
if movie_folder and not os.path.isdir(movie_folder) or not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')):
l = log.debug if movie_folder else log.error
@@ -93,10 +97,14 @@ class Renamer(Plugin):
elif self.conf('from') in self.conf('to'):
log.error('The "to" can\'t be inside of the "from" folder. You\'ll get an infinite loop.')
return
- elif (movie_folder and movie_folder in [self.conf('to'), self.conf('from')]):
+ elif movie_folder and movie_folder in [self.conf('to'), self.conf('from')]:
log.error('The "to" and "from" folders can\'t be inside of or the same as the provided movie folder.')
return
+ # Make sure a checkSnatched marked all downloads/seeds as such
+ if not download_info and self.conf('run_every') > 0:
+ fireEvent('renamer.check_snatched')
+
self.renaming_started = True
# make sure the movie folder name is included in the search
@@ -119,10 +127,15 @@ class Renamer(Plugin):
# Extend the download info with info stored in the downloaded release
download_info = self.extendDownloadInfo(download_info)
+ # Unpack any archives
+ extr_files = None
+ if self.conf('unrar'):
+ folder, movie_folder, files, extr_files = self.extractFiles(folder = folder, movie_folder = movie_folder, files = files,
+ cleanup = self.conf('cleanup') and not self.downloadIsTorrent(download_info))
+
groups = fireEvent('scanner.scan', folder = folder if folder else self.conf('from'),
files = files, download_info = download_info, return_ignored = False, single = True)
- destination = self.conf('to')
folder_name = self.conf('folder_name')
file_name = self.conf('file_name')
trailer_name = self.conf('trailer_name')
@@ -148,17 +161,35 @@ class Renamer(Plugin):
continue
# Rename the files using the library data
else:
- group['library'] = fireEvent('library.update', identifier = group['library']['identifier'], single = True)
+ group['library'] = fireEvent('library.update.movie', identifier = group['library']['identifier'], single = True)
if not group['library']:
log.error('Could not rename, no library item to work with: %s', group_identifier)
continue
library = group['library']
+ library_ent = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
+
movie_title = getTitle(library)
+ # Overwrite destination when set in category
+ destination = self.conf('to')
+ for movie in library_ent.movies:
+ if movie.category and movie.category.destination and len(movie.category.destination) > 0 and movie.category.destination != 'None':
+ destination = movie.category.destination
+ log.debug('Setting category destination for "%s": %s' % (movie_title, destination))
+ else:
+ log.debug('No category destination found for "%s"' % movie_title)
+
+ break
+
# Find subtitle for renaming
+ group['before_rename'] = []
fireEvent('renamer.before', group)
+ # Add extracted files to the before_rename list
+ if extr_files:
+ group['before_rename'].extend(extr_files)
+
# Remove weird chars from moviename
movie_name = re.sub(r"[\x00\/\\:\*\?\"<>\|]", '', movie_title)
@@ -185,6 +216,7 @@ class Renamer(Plugin):
'imdb_id': library['identifier'],
'cd': '',
'cd_nr': '',
+ 'mpaa': library['info'].get('mpaa', ''),
}
for file_type in group['files']:
@@ -192,8 +224,8 @@ class Renamer(Plugin):
# Move nfo depending on settings
if file_type is 'nfo' and not self.conf('rename_nfo'):
log.debug('Skipping, renaming of %s disabled', file_type)
- if self.conf('cleanup'):
- for current_file in group['files'][file_type]:
+ for current_file in group['files'][file_type]:
+ if self.conf('cleanup') and (not self.downloadIsTorrent(download_info) or self.fileIsAdded(current_file, group)):
remove_files.append(current_file)
continue
@@ -220,7 +252,7 @@ class Renamer(Plugin):
replacements['cd_nr'] = cd if multiple else ''
# Naming
- final_folder_name = self.doReplace(folder_name, replacements)
+ final_folder_name = self.doReplace(folder_name, replacements, folder = True)
final_file_name = self.doReplace(file_name, replacements)
replacements['filename'] = final_file_name[:-(len(getExt(final_file_name)) + 1)]
@@ -307,19 +339,18 @@ class Renamer(Plugin):
cd += 1
# Before renaming, remove the lower quality files
- library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
remove_leftovers = True
# Add it to the wanted list before we continue
- if len(library.movies) == 0:
+ if len(library_ent.movies) == 0:
profile = db.query(Profile).filter_by(core = True, label = group['meta_data']['quality']['label']).first()
fireEvent('movie.add', params = {'identifier': group['library']['identifier'], 'profile_id': profile.id}, search_after = False)
db.expire_all()
- library = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
+ library_ent = db.query(Library).filter_by(identifier = group['library']['identifier']).first()
- for movie in library.movies:
+ for movie in library_ent.movies:
- # Mark movie "done" onces it found the quality with the finish check
+ # Mark movie "done" once it's found the quality with the finish check
try:
if movie.status_id == active_status.get('id') and movie.profile:
for profile_type in movie.profile.types:
@@ -357,7 +388,7 @@ class Renamer(Plugin):
self.tagDir(group, 'exists')
# Notify on rename fail
- download_message = 'Renaming of %s (%s) canceled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
+ download_message = 'Renaming of %s (%s) cancelled, exists in %s already.' % (movie.library.titles[0].title, group['meta_data']['quality']['label'], release.quality.label)
fireEvent('movie.renaming.canceled', message = download_message, data = group)
remove_leftovers = False
@@ -374,14 +405,15 @@ class Renamer(Plugin):
db.commit()
# Remove leftover files
- if self.conf('cleanup') and not self.conf('move_leftover') and remove_leftovers and \
- not (self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info)):
- log.debug('Removing leftover files')
- for current_file in group['files']['leftover']:
- remove_files.append(current_file)
- elif not remove_leftovers: # Don't remove anything
+ if not remove_leftovers: # Don't remove anything
break
+ log.debug('Removing leftover files')
+ for current_file in group['files']['leftover']:
+ if self.conf('cleanup') and not self.conf('move_leftover') and \
+ (not self.downloadIsTorrent(download_info) or self.fileIsAdded(current_file, group)):
+ remove_files.append(current_file)
+
# Remove files
delete_folders = []
for src in remove_files:
@@ -425,14 +457,15 @@ class Renamer(Plugin):
self.makeDir(os.path.dirname(dst))
try:
- self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info))
+ self.moveFile(src, dst, forcemove = not self.downloadIsTorrent(download_info) or self.fileIsAdded(src, group))
group['renamed_files'].append(dst)
except:
log.error('Failed moving the file "%s" : %s', (os.path.basename(src), traceback.format_exc()))
self.tagDir(group, 'failed_rename')
- if self.conf('file_action') != 'move' and self.downloadIsTorrent(download_info):
- self.tagDir(group, 'renamed already')
+ # Tag folder if it is in the 'from' folder and it will not be removed because it is a torrent
+ if self.movieInFromFolder(movie_folder) and self.downloadIsTorrent(download_info):
+ self.tagDir(group, 'renamed_already')
# Remove matching releases
for release in remove_releases:
@@ -442,7 +475,7 @@ class Renamer(Plugin):
except:
log.error('Failed removing %s: %s', (release.identifier, traceback.format_exc()))
- if group['dirname'] and group['parentdir']:
+ if group['dirname'] and group['parentdir'] and not self.downloadIsTorrent(download_info):
try:
log.info('Deleting folder: %s', group['parentdir'])
self.deleteEmptyFolder(group['parentdir'])
@@ -462,7 +495,9 @@ class Renamer(Plugin):
self.renaming_started = False
- def getRenameExtras(self, extra_type = '', replacements = {}, folder_name = '', file_name = '', destination = '', group = {}, current_file = '', remove_multiple = False):
+ def getRenameExtras(self, extra_type = '', replacements = None, folder_name = '', file_name = '', destination = '', group = None, current_file = '', remove_multiple = False):
+ if not group: group = {}
+ if not replacements: replacements = {}
replacements = replacements.copy()
rename_files = {}
@@ -473,7 +508,7 @@ class Renamer(Plugin):
for extra in set(filter(test, group['files'][extra_type])):
replacements['ext'] = getExt(extra)
- final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple)
+ final_folder_name = self.doReplace(folder_name, replacements, remove_multiple = remove_multiple, folder = True)
final_file_name = self.doReplace(file_name, replacements, remove_multiple = remove_multiple)
rename_files[extra] = os.path.join(destination, final_folder_name, final_file_name)
@@ -483,9 +518,15 @@ class Renamer(Plugin):
def tagDir(self, group, tag):
ignore_file = None
- for movie_file in sorted(list(group['files']['movie'])):
- ignore_file = '%s.ignore' % os.path.splitext(movie_file)[0]
- break
+ if isinstance(group, dict):
+ for movie_file in sorted(list(group['files']['movie'])):
+ ignore_file = '%s.%s.ignore' % (os.path.splitext(movie_file)[0], tag)
+ break
+ else:
+ if not os.path.isdir(group) or not tag:
+ return
+ ignore_file = os.path.join(group, '%s.ignore' % tag)
+
text = """This file is from CouchPotato
It has marked this release as "%s"
@@ -496,21 +537,48 @@ Remove it if you want it to be renamed (again, or at least let it try again)
if ignore_file:
self.createFile(ignore_file, text)
+ def untagDir(self, folder, tag = ''):
+ if not os.path.isdir(folder):
+ return
+
+ # Remove any .ignore files
+ for root, dirnames, filenames in os.walk(folder):
+ for filename in fnmatch.filter(filenames, '*%s.ignore' % tag):
+ os.remove((os.path.join(root, filename)))
+
+ def hastagDir(self, folder, tag = ''):
+ if not os.path.isdir(folder):
+ return False
+
+ # Find any .ignore files
+ for root, dirnames, filenames in os.walk(folder):
+ if fnmatch.filter(filenames, '*%s.ignore' % tag):
+ return True
+
+ return False
def moveFile(self, old, dest, forcemove = False):
dest = ss(dest)
try:
if forcemove:
shutil.move(old, dest)
- elif self.conf('file_action') == 'hardlink':
- link(old, dest)
- elif self.conf('file_action') == 'symlink':
- symlink(old, dest)
elif self.conf('file_action') == 'copy':
shutil.copy(old, dest)
- elif self.conf('file_action') == 'move_symlink':
- shutil.move(old, dest)
- symlink(dest, old)
+ elif self.conf('file_action') == 'link':
+ # First try to hardlink
+ try:
+ log.debug('Hardlinking file "%s" to "%s"...', (old, dest))
+ link(old, dest)
+ except:
+ # Try to simlink next
+ log.debug('Couldn\'t hardlink file "%s" to "%s". Simlinking instead. Error: %s. ', (old, dest, traceback.format_exc()))
+ shutil.copy(old, dest)
+ try:
+ symlink(dest, old + '.link')
+ os.unlink(old)
+ os.rename(old + '.link', old)
+ except:
+ log.error('Couldn\'t symlink file "%s" to "%s". Copied instead. Error: %s. ', (old, dest, traceback.format_exc()))
else:
shutil.move(old, dest)
@@ -535,10 +603,10 @@ Remove it if you want it to be renamed (again, or at least let it try again)
return True
- def doReplace(self, string, replacements, remove_multiple = False):
- '''
+ def doReplace(self, string, replacements, remove_multiple = False, folder = False):
+ """
replace confignames with the real thing
- '''
+ """
replacements = replacements.copy()
if remove_multiple:
@@ -555,7 +623,7 @@ Remove it if you want it to be renamed (again, or at least let it try again)
replaced = re.sub(r"[\x00:\*\?\"<>\|]", '', replaced)
- sep = self.conf('separator')
+ sep = self.conf('foldersep') if folder else self.conf('separator')
return self.replaceDoubles(replaced.lstrip('. ')).replace(' ', ' ' if not sep else sep)
def replaceDoubles(self, string):
@@ -584,19 +652,21 @@ Remove it if you want it to be renamed (again, or at least let it try again)
if self.checking_snatched:
log.debug('Already checking snatched')
+ return False
self.checking_snatched = True
- snatched_status, ignored_status, failed_status, done_status = \
- fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done'], single = True)
+ snatched_status, ignored_status, failed_status, done_status, seeding_status, downloaded_status = \
+ fireEvent('status.get', ['snatched', 'ignored', 'failed', 'done', 'seeding', 'downloaded'], single = True)
db = get_session()
rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all()
+ rels.extend(db.query(Release).filter_by(status_id = seeding_status.get('id')).all())
+ scan_items = []
scan_required = False
if rels:
- self.checking_snatched = True
log.debug('Checking status snatched releases...')
statuses = fireEvent('download.status', merge = True)
@@ -608,17 +678,6 @@ Remove it if you want it to be renamed (again, or at least let it try again)
for rel in rels:
rel_dict = rel.to_dict({'info': {}})
- # Get current selected title
- default_title = getTitle(rel.movie.library)
-
- # Check if movie has already completed and is manage tab (legacy db correction)
- if rel.movie.status_id == done_status.get('id'):
- log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title)
- rel.status_id = ignored_status.get('id')
- rel.last_edit = int(time.time())
- db.commit()
- continue
-
movie_dict = fireEvent('movie.get', rel.movie_id, single = True)
# check status
@@ -640,7 +699,34 @@ Remove it if you want it to be renamed (again, or at least let it try again)
log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft))
if item['status'] == 'busy':
- pass
+ # Tag folder if it is in the 'from' folder and it will not be processed because it is still downloading
+ if item['folder'] and self.conf('from') in item['folder']:
+ self.tagDir(item['folder'], 'downloading')
+
+ elif item['status'] == 'seeding':
+
+ #If linking setting is enabled, process release
+ if self.conf('file_action') != 'move' and not rel.movie.status_id == done_status.get('id') and self.statusInfoComplete(item):
+ log.info('Download of %s completed! It is now being processed while leaving the original files alone for seeding. Current ratio: %s.', (item['name'], item['seed_ratio']))
+
+ # Remove the downloading tag
+ self.untagDir(item['folder'], 'downloading')
+
+ rel.status_id = seeding_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Scan and set the torrent to paused if required
+ item.update({'pause': True, 'scan': True, 'process_complete': False})
+ scan_items.append(item)
+ else:
+ if rel.status_id != seeding_status.get('id'):
+ rel.status_id = seeding_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ #let it seed
+ log.debug('%s is seeding with ratio: %s', (item['name'], item['seed_ratio']))
elif item['status'] == 'failed':
fireEvent('download.remove_failed', item, single = True)
rel.status_id = failed_status.get('id')
@@ -648,11 +734,39 @@ Remove it if you want it to be renamed (again, or at least let it try again)
db.commit()
if self.conf('next_on_failed'):
- fireEvent('searcher.try_next_release', movie_id = rel.movie_id)
+ fireEvent('movie.searcher.try_next_release', movie_id = rel.movie_id)
elif item['status'] == 'completed':
log.info('Download of %s completed!', item['name'])
- if item['id'] and item['downloader'] and item['folder']:
- fireEventAsync('renamer.scan', movie_folder = item['folder'], download_info = item)
+ if self.statusInfoComplete(item):
+
+ # If the release has been seeding, process now the seeding is done
+ if rel.status_id == seeding_status.get('id'):
+ if rel.movie.status_id == done_status.get('id'):
+ # Set the release to done as the movie has already been renamed
+ rel.status_id = downloaded_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': False, 'process_complete': True})
+ scan_items.append(item)
+ else:
+ # Set the release to snatched so that the renamer can process the release as if it was never seeding
+ rel.status_id = snatched_status.get('id')
+ rel.last_edit = int(time.time())
+ db.commit()
+
+ # Scan and Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': True, 'process_complete': True})
+ scan_items.append(item)
+
+ else:
+ # Remove the downloading tag
+ self.untagDir(item['folder'], 'downloading')
+
+ # Scan and Allow the downloader to clean-up
+ item.update({'pause': False, 'scan': True, 'process_complete': True})
+ scan_items.append(item)
else:
scan_required = True
@@ -665,6 +779,23 @@ Remove it if you want it to be renamed (again, or at least let it try again)
except:
log.error('Failed checking for release in downloader: %s', traceback.format_exc())
+ # The following can either be done here, or inside the scanner if we pass it scan_items in one go
+ for item in scan_items:
+ # Ask the renamer to scan the item
+ if item['scan']:
+ if item['pause'] and self.conf('file_action') == 'link':
+ fireEvent('download.pause', item = item, pause = True, single = True)
+ fireEvent('renamer.scan', download_info = item)
+ if item['pause'] and self.conf('file_action') == 'link':
+ fireEvent('download.pause', item = item, pause = False, single = True)
+ if item['process_complete']:
+ #First make sure the files were succesfully processed
+ if not self.hastagDir(item['folder'], 'failed_rename'):
+ # Remove the seeding tag if it exists
+ self.untagDir(item['folder'], 'renamed_already')
+ # Ask the downloader to process the item
+ fireEvent('download.process_complete', item = item, single = True)
+
if scan_required:
fireEvent('renamer.scan')
@@ -699,10 +830,146 @@ Remove it if you want it to be renamed (again, or at least let it try again)
download_info.update({
'imdb_id': rls.movie.library.identifier,
'quality': rls.quality.identifier,
- 'type': rls_dict.get('info', {}).get('type')
+ 'protocol': rls_dict.get('info', {}).get('protocol') or rls_dict.get('info', {}).get('type'),
})
return download_info
def downloadIsTorrent(self, download_info):
- return download_info and download_info.get('type') in ['torrent', 'torrent_magnet']
+ return download_info and download_info.get('protocol') in ['torrent', 'torrent_magnet']
+
+ def fileIsAdded(self, src, group):
+ if not group or not group.get('before_rename'):
+ return False
+ return src in group['before_rename']
+
+ def statusInfoComplete(self, item):
+ return item['id'] and item['downloader'] and item['folder']
+
+ def movieInFromFolder(self, movie_folder):
+ return movie_folder and self.conf('from') in movie_folder or not movie_folder
+
+ def extractFiles(self, folder = None, movie_folder = None, files = None, cleanup = False):
+ if not files: files = []
+
+ # RegEx for finding rar files
+ archive_regex = '(?P^(?P(?:(?!\.part\d+\.rar$).)*)\.(?:(?:part0*1\.)?rar)$)'
+ restfile_regex = '(^%s\.(?:part(?!0*1\.rar$)\d+\.rar$|[rstuvw]\d+$))'
+ extr_files = []
+
+ # Check input variables
+ if not folder:
+ folder = self.conf('from')
+
+ check_file_date = True
+ if movie_folder:
+ check_file_date = False
+
+ if not files:
+ for root, folders, names in os.walk(folder):
+ files.extend([os.path.join(root, name) for name in names])
+
+ # Find all archive files
+ archives = [re.search(archive_regex, name).groupdict() for name in files if re.search(archive_regex, name)]
+
+ #Extract all found archives
+ for archive in archives:
+ # Check if it has already been processed by CPS
+ if self.hastagDir(os.path.dirname(archive['file'])):
+ continue
+
+ # Find all related archive files
+ archive['files'] = [name for name in files if re.search(restfile_regex % re.escape(archive['base']), name)]
+ archive['files'].append(archive['file'])
+
+ # Check if archive is fresh and maybe still copying/moving/downloading, ignore files newer than 1 minute
+ if check_file_date:
+ file_too_new = False
+ for cur_file in archive['files']:
+ if not os.path.isfile(cur_file):
+ file_too_new = time.time()
+ break
+ file_time = [os.path.getmtime(cur_file), os.path.getctime(cur_file)]
+ for t in file_time:
+ if t > time.time() - 60:
+ file_too_new = tryInt(time.time() - t)
+ break
+
+ if file_too_new:
+ break
+
+ if file_too_new:
+ try:
+ time_string = time.ctime(file_time[0])
+ except:
+ try:
+ time_string = time.ctime(file_time[1])
+ except:
+ time_string = 'unknown'
+
+ log.info('Archive seems to be still copying/moving/downloading or just copied/moved/downloaded (created on %s), ignoring for now: %s', (time_string, os.path.basename(archive['file'])))
+ continue
+
+ log.info('Archive %s found. Extracting...', os.path.basename(archive['file']))
+ try:
+ rar_handle = RarFile(archive['file'])
+ extr_path = os.path.join(self.conf('from'), os.path.relpath(os.path.dirname(archive['file']), folder))
+ self.makeDir(extr_path)
+ for packedinfo in rar_handle.infolist():
+ if not packedinfo.isdir and not os.path.isfile(os.path.join(extr_path, os.path.basename(packedinfo.filename))):
+ log.debug('Extracting %s...', packedinfo.filename)
+ rar_handle.extract(condition = [packedinfo.index], path = extr_path, withSubpath = False, overwrite = False)
+ extr_files.append(os.path.join(extr_path, os.path.basename(packedinfo.filename)))
+ del rar_handle
+ except Exception, e:
+ log.error('Failed to extract %s: %s %s', (archive['file'], e, traceback.format_exc()))
+ continue
+
+ # Delete the archive files
+ for filename in archive['files']:
+ if cleanup:
+ try:
+ os.remove(filename)
+ except Exception, e:
+ log.error('Failed to remove %s: %s %s', (filename, e, traceback.format_exc()))
+ continue
+ files.remove(filename)
+
+ # Move the rest of the files and folders if any files are extracted to the from folder (only if folder was provided)
+ if extr_files and os.path.normpath(os.path.normcase(folder)) != os.path.normpath(os.path.normcase(self.conf('from'))):
+ for leftoverfile in list(files):
+ move_to = os.path.join(self.conf('from'), os.path.relpath(leftoverfile, folder))
+
+ try:
+ self.makeDir(os.path.dirname(move_to))
+ self.moveFile(leftoverfile, move_to, cleanup)
+ except Exception, e:
+ log.error('Failed moving left over file %s to %s: %s %s', (leftoverfile, move_to, e, traceback.format_exc()))
+ # As we probably tried to overwrite the nfo file, check if it exists and then remove the original
+ if os.path.isfile(move_to):
+ if cleanup:
+ log.info('Deleting left over file %s instead...', leftoverfile)
+ os.unlink(leftoverfile)
+ else:
+ continue
+
+ files.remove(leftoverfile)
+ extr_files.append(move_to)
+
+ if cleanup:
+ # Remove all left over folders
+ log.debug('Removing old movie folder %s...', movie_folder)
+ self.deleteEmptyFolder(movie_folder)
+
+ movie_folder = os.path.join(self.conf('from'), os.path.relpath(movie_folder, folder))
+ folder = self.conf('from')
+
+ if extr_files:
+ files.extend(extr_files)
+
+ # Cleanup files and folder if movie_folder was not provided
+ if not movie_folder:
+ files = []
+ folder = None
+
+ return folder, movie_folder, files, extr_files
diff --git a/couchpotato/core/plugins/scanner/main.py b/couchpotato/core/plugins/scanner/main.py
index a400b159..ff17b647 100644
--- a/couchpotato/core/plugins/scanner/main.py
+++ b/couchpotato/core/plugins/scanner/main.py
@@ -120,13 +120,17 @@ class Scanner(Plugin):
files = []
for root, dirs, walk_files in os.walk(folder):
files.extend(os.path.join(root, filename) for filename in walk_files)
+
+ # Break if CP wants to shut down
+ if self.shuttingDown():
+ break
+
except:
log.error('Failed getting files from %s: %s', (folder, traceback.format_exc()))
else:
check_file_date = False
files = [ss(x) for x in files]
- db = get_session()
for file_path in files:
@@ -225,6 +229,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack
leftovers = leftovers - set(found_files)
+ exts = [getExt(ff) for ff in found_files]
+ if 'ignore' in exts:
+ ignored_identifiers.append(identifier)
+
# Break if CP wants to shut down
if self.shuttingDown():
break
@@ -251,6 +259,10 @@ class Scanner(Plugin):
# Remove the found files from the leftover stack
leftovers = leftovers - set([ff])
+ ext = getExt(ff)
+ if ext == 'ignore':
+ ignored_identifiers.append(new_identifier)
+
# Break if CP wants to shut down
if self.shuttingDown():
break
@@ -269,7 +281,7 @@ class Scanner(Plugin):
except:
break
- # Check if movie is fresh and maybe still unpacking, ignore files new then 1 minute
+ # Check if movie is fresh and maybe still unpacking, ignore files newer than 1 minute
file_too_new = False
for cur_file in group['unsorted_files']:
if not os.path.isfile(cur_file):
@@ -321,14 +333,18 @@ class Scanner(Plugin):
del movie_files
+ total_found = len(valid_files)
+
# Make sure only one movie was found if a download ID is provided
- if download_info and not len(valid_files) == 1:
+ if download_info and total_found == 0:
+ log.info('Download ID provided (%s), but no groups found! Make sure the download contains valid media files (fully extracted).', download_info.get('imdb_id'))
+ elif download_info and total_found > 1:
log.info('Download ID provided (%s), but more than one group found (%s). Ignoring Download ID...', (download_info.get('imdb_id'), len(valid_files)))
download_info = None
# Determine file types
+ db = get_session()
processed_movies = {}
- total_found = len(valid_files)
while True and not self.shuttingDown():
try:
identifier, group = valid_files.popitem()
@@ -413,7 +429,7 @@ class Scanner(Plugin):
if len(processed_movies) > 0:
log.info('Found %s movies in the folder %s', (len(processed_movies), folder))
else:
- log.debug('Found no movies in the folder %s', (folder))
+ log.debug('Found no movies in the folder %s', folder)
return processed_movies
@@ -492,6 +508,7 @@ class Scanner(Plugin):
detected_languages = {}
# Subliminal scanner
+ paths = None
try:
paths = group['files']['movie']
scan_result = []
@@ -544,12 +561,14 @@ class Scanner(Plugin):
break
# Check and see if nfo contains the imdb-id
+ nfo_file = None
if not imdb_id:
try:
- for nfo_file in files['nfo']:
- imdb_id = getImdb(nfo_file)
+ for nf in files['nfo']:
+ imdb_id = getImdb(nf)
if imdb_id:
- log.debug('Found movie via nfo file: %s', nfo_file)
+ log.debug('Found movie via nfo file: %s', nf)
+ nfo_file = nf
break
except:
pass
@@ -569,26 +588,16 @@ class Scanner(Plugin):
# Check if path is already in db
if not imdb_id:
db = get_session()
- for cur_file in files['movie']:
- f = db.query(File).filter_by(path = toUnicode(cur_file)).first()
+ for cf in files['movie']:
+ f = db.query(File).filter_by(path = toUnicode(cf)).first()
try:
imdb_id = f.library[0].identifier
- log.debug('Found movie via database: %s', cur_file)
+ log.debug('Found movie via database: %s', cf)
+ cur_file = cf
break
except:
pass
- # Search based on OpenSubtitleHash
- if not imdb_id and not group['is_dvd']:
- for cur_file in files['movie']:
- movie = fireEvent('movie.by_hash', file = cur_file, merge = True)
-
- if len(movie) > 0:
- imdb_id = movie[0].get('imdb')
- if imdb_id:
- log.debug('Found movie via OpenSubtitleHash: %s', cur_file)
- break
-
# Search based on identifiers
if not imdb_id:
for identifier in group['identifiers']:
@@ -609,7 +618,7 @@ class Scanner(Plugin):
log.debug('Identifier to short to use for search: %s', identifier)
if imdb_id:
- return fireEvent('library.add', attrs = {
+ return fireEvent('library.add.movie', attrs = {
'identifier': imdb_id
}, update_after = False, single = True)
@@ -675,10 +684,9 @@ class Scanner(Plugin):
return getExt(s.lower()) in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tbn']
files = set(filter(test, files))
- images = {}
-
- # Fanart
- images['backdrop'] = set(filter(lambda s: re.search('(^|[\W_])fanart|backdrop\d*[\W_]', s.lower()) and self.filesizeBetween(s, 0, 5), files))
+ images = {
+ 'backdrop': set(filter(lambda s: re.search('(^|[\W_])fanart|backdrop\d*[\W_]', s.lower()) and self.filesizeBetween(s, 0, 5), files))
+ }
# Rest
images['rest'] = files - images['backdrop']
@@ -750,7 +758,7 @@ class Scanner(Plugin):
# Year
year = self.findYear(identifier)
- if year:
+ if year and identifier[:4] != year:
identifier = '%s %s' % (identifier.split(year)[0].strip(), year)
else:
identifier = identifier.split('::')[0]
@@ -811,6 +819,13 @@ class Scanner(Plugin):
return None
def findYear(self, text):
+
+ # Search year inside () or [] first
+ matches = re.search('(\(|\[)(?P19[0-9]{2}|20[0-9]{2})(\]|\))', text)
+ if matches:
+ return matches.group('year')
+
+ # Search normal
matches = re.search('(?P19[0-9]{2}|20[0-9]{2})', text)
if matches:
return matches.group('year')
@@ -823,11 +838,11 @@ class Scanner(Plugin):
guess = {}
if file_name:
try:
- guess = guess_movie_info(toUnicode(file_name))
- if guess.get('title') and guess.get('year'):
+ guessit = guess_movie_info(toUnicode(file_name))
+ if guessit.get('title') and guessit.get('year'):
guess = {
- 'name': guess.get('title'),
- 'year': guess.get('year'),
+ 'name': guessit.get('title'),
+ 'year': guessit.get('year'),
}
except:
log.debug('Could not detect via guessit "%s": %s', (file_name, traceback.format_exc()))
@@ -835,7 +850,13 @@ class Scanner(Plugin):
# Backup to simple
cleaned = ' '.join(re.split('\W+', simplifyString(release_name)))
cleaned = re.sub(self.clean, ' ', cleaned)
- year = self.findYear(cleaned)
+
+ for year_str in [file_name, cleaned]:
+ if not year_str: continue
+ year = self.findYear(year_str)
+ if year:
+ break
+
cp_guess = {}
if year: # Split name on year
diff --git a/couchpotato/core/plugins/score/main.py b/couchpotato/core/plugins/score/main.py
index 4f16539d..5f9da1a1 100644
--- a/couchpotato/core/plugins/score/main.py
+++ b/couchpotato/core/plugins/score/main.py
@@ -1,11 +1,12 @@
from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import toUnicode
-from couchpotato.core.helpers.variable import getTitle
+from couchpotato.core.helpers.variable import getTitle, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.core.plugins.score.scores import nameScore, nameRatioScore, \
sizeScore, providerScore, duplicateScore, partialIgnoredScore, namePositionScore, \
halfMultipartScore
+from couchpotato.environment import Env
log = CPLog(__name__)
@@ -16,9 +17,14 @@ class Score(Plugin):
addEvent('score.calculate', self.calculate)
def calculate(self, nzb, movie):
- ''' Calculate the score of a NZB, used for sorting later '''
+ """ Calculate the score of a NZB, used for sorting later """
- score = nameScore(toUnicode(nzb['name'] + ' ' + nzb.get('name_extra', '')), movie['library']['year'])
+ # Merge global and category
+ preferred_words = splitString(Env.setting('preferred_words', section = 'searcher').lower())
+ try: preferred_words = list(set(preferred_words + splitString(movie['category']['preferred'].lower())))
+ except: pass
+
+ score = nameScore(toUnicode(nzb['name']), movie['library']['year'], preferred_words)
for movie_title in movie['library']['titles']:
score += nameRatioScore(toUnicode(nzb['name']), toUnicode(movie_title['title']))
@@ -40,8 +46,13 @@ class Score(Plugin):
# Duplicates in name
score += duplicateScore(nzb['name'], getTitle(movie['library']))
+ # Merge global and category
+ ignored_words = splitString(Env.setting('ignored_words', section = 'searcher').lower())
+ try: ignored_words = list(set(ignored_words + splitString(movie['category']['ignored'].lower())))
+ except: pass
+
# Partial ignored words
- score += partialIgnoredScore(nzb['name'], getTitle(movie['library']))
+ score += partialIgnoredScore(nzb['name'], getTitle(movie['library']), ignored_words)
# Ignore single downloads from multipart
score += halfMultipartScore(nzb['name'])
diff --git a/couchpotato/core/plugins/score/scores.py b/couchpotato/core/plugins/score/scores.py
index 95ef9b97..6aa0b465 100644
--- a/couchpotato/core/plugins/score/scores.py
+++ b/couchpotato/core/plugins/score/scores.py
@@ -1,6 +1,6 @@
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import simplifyString
-from couchpotato.core.helpers.variable import tryInt, splitString
+from couchpotato.core.helpers.variable import tryInt
from couchpotato.environment import Env
import re
@@ -23,8 +23,8 @@ name_scores = [
]
-def nameScore(name, year):
- ''' Calculate score for words in the NZB name '''
+def nameScore(name, year, preferred_words):
+ """ Calculate score for words in the NZB name """
score = 0
name = name.lower()
@@ -34,20 +34,18 @@ def nameScore(name, year):
v = value.split(':')
add = int(v.pop())
if v.pop() in name:
- score = score + add
+ score += add
# points if the year is correct
if str(year) in name:
- score = score + 5
+ score += 5
# Contains preferred word
nzb_words = re.split('\W+', simplifyString(name))
- preferred_words = splitString(Env.setting('preferred_words', section = 'searcher'))
score += 100 * len(list(set(nzb_words) & set(preferred_words)))
return score
-
def nameRatioScore(nzb_name, movie_name):
nzb_words = re.split('\W+', fireEvent('scanner.create_file_identifier', nzb_name, single = True))
movie_words = re.split('\W+', simplifyString(movie_name))
@@ -70,9 +68,12 @@ def namePositionScore(nzb_name, movie_name):
name_year = fireEvent('scanner.name_year', nzb_name, single = True)
# Give points for movies beginning with the correct name
- name_split = simplifyString(nzb_name).split(simplifyString(movie_name))
- if name_split[0].strip() == '':
- score += 10
+ split_by = simplifyString(movie_name)
+ name_split = []
+ if len(split_by) > 0:
+ name_split = simplifyString(nzb_name).split(split_by)
+ if name_split[0].strip() == '':
+ score += 10
# If year is second in line, give more points
if len(name_split) > 1 and name_year:
@@ -134,13 +135,11 @@ def duplicateScore(nzb_name, movie_name):
return len(list(set(duplicates) - set(movie_words))) * -4
-def partialIgnoredScore(nzb_name, movie_name):
+def partialIgnoredScore(nzb_name, movie_name, ignored_words):
nzb_name = nzb_name.lower()
movie_name = movie_name.lower()
- ignored_words = [x.strip().lower() for x in Env.setting('ignored_words', section = 'searcher').split(',')]
-
score = 0
for ignored_word in ignored_words:
if ignored_word in nzb_name and ignored_word not in movie_name:
@@ -148,6 +147,7 @@ def partialIgnoredScore(nzb_name, movie_name):
return score
+
def halfMultipartScore(nzb_name):
wrong_found = 0
diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py
deleted file mode 100644
index bed90eb2..00000000
--- a/couchpotato/core/plugins/searcher/__init__.py
+++ /dev/null
@@ -1,113 +0,0 @@
-from .main import Searcher
-import random
-
-def start():
- return Searcher()
-
-config = [{
- 'name': 'searcher',
- 'order': 20,
- 'groups': [
- {
- 'tab': 'searcher',
- 'name': 'searcher',
- 'label': 'Search',
- 'description': 'Options for the searchers',
- 'options': [
- {
- 'name': 'preferred_words',
- 'label': 'Preferred words',
- 'default': '',
- 'description': 'These words will give the releases a higher score.'
- },
- {
- 'name': 'required_words',
- 'label': 'Required words',
- 'default': '',
- 'placeholder': 'Example: DTS, AC3 & English',
- 'description': 'A release should contain at least one set of words. Sets are separated by "," and each word within a set must be separated with "&"'
- },
- {
- 'name': 'ignored_words',
- 'label': 'Ignored words',
- 'default': 'german, dutch, french, truefrench, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub, dksubs',
- 'description': 'Ignores releases that match any of these sets. (Works like explained above)'
- },
- {
- 'name': 'preferred_method',
- 'label': 'First search',
- 'description': 'Which of the methods do you prefer',
- 'default': 'both',
- 'type': 'dropdown',
- 'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')],
- },
- {
- 'name': 'always_search',
- 'default': False,
- 'advanced': True,
- 'type': 'bool',
- 'label': 'Always search',
- 'description': 'Search for movies even before there is a ETA. Enabling this will probably get you a lot of fakes.',
- },
- ],
- }, {
- 'tab': 'searcher',
- 'name': 'cronjob',
- 'label': 'Cronjob',
- 'advanced': True,
- 'description': 'Cron settings for the searcher see: APScheduler for details.',
- 'options': [
- {
- 'name': 'run_on_launch',
- 'label': 'Run on launch',
- 'advanced': True,
- 'default': 0,
- 'type': 'bool',
- 'description': 'Force run the searcher after (re)start.',
- },
- {
- 'name': 'cron_day',
- 'label': 'Day',
- 'advanced': True,
- 'default': '*',
- 'type': 'string',
- 'description': '*: Every day, */2: Every 2 days, 1: Every first of the month.',
- },
- {
- 'name': 'cron_hour',
- 'label': 'Hour',
- 'advanced': True,
- 'default': random.randint(0, 23),
- 'type': 'string',
- 'description': '*: Every hour, */8: Every 8 hours, 3: At 3, midnight.',
- },
- {
- 'name': 'cron_minute',
- 'label': 'Minute',
- 'advanced': True,
- 'default': random.randint(0, 59),
- 'type': 'string',
- 'description': "Just keep it random, so the providers don't get DDOSed by every CP user on a 'full' hour."
- },
- ],
- },
- ],
-}, {
- 'name': 'nzb',
- 'groups': [
- {
- 'tab': 'searcher',
- 'name': 'nzb',
- 'label': 'NZB',
- 'wizard': True,
- 'options': [
- {
- 'name': 'retention',
- 'default': 1000,
- 'type': 'int',
- 'unit': 'days'
- },
- ],
- },
- ],
-}]
diff --git a/couchpotato/core/plugins/status/main.py b/couchpotato/core/plugins/status/main.py
index c8c8f666..7546c651 100644
--- a/couchpotato/core/plugins/status/main.py
+++ b/couchpotato/core/plugins/status/main.py
@@ -23,6 +23,7 @@ class StatusPlugin(Plugin):
'ignored': 'Ignored',
'available': 'Available',
'suggest': 'Suggest',
+ 'seeding': 'Seeding',
}
status_cached = {}
@@ -74,7 +75,7 @@ class StatusPlugin(Plugin):
def get(self, identifiers):
- if not isinstance(identifiers, (list)):
+ if not isinstance(identifiers, list):
identifiers = [identifiers]
db = get_session()
diff --git a/couchpotato/core/plugins/subtitle/main.py b/couchpotato/core/plugins/subtitle/main.py
index c6bef6ae..e447dacd 100644
--- a/couchpotato/core/plugins/subtitle/main.py
+++ b/couchpotato/core/plugins/subtitle/main.py
@@ -36,13 +36,12 @@ class Subtitle(Plugin):
files = []
for file in release.files.filter(FileType.status.has(identifier = 'movie')).all():
- files.append(file.path);
+ files.append(file.path)
# get subtitles for those files
subliminal.list_subtitles(files, cache_dir = Env.get('cache_dir'), multi = True, languages = self.getLanguages(), services = self.services)
def searchSingle(self, group):
-
if self.isDisabled(): return
try:
@@ -60,6 +59,7 @@ class Subtitle(Plugin):
for d_sub in downloaded:
log.info('Found subtitle (%s): %s', (d_sub.language.alpha2, files))
group['files']['subtitle'].append(d_sub.path)
+ group['before_rename'].append(d_sub.path)
group['subtitle_language'][d_sub.path] = [d_sub.language.alpha2]
return True
diff --git a/couchpotato/core/plugins/suggestion/main.py b/couchpotato/core/plugins/suggestion/main.py
index 0e7d701a..a7d0f82a 100644
--- a/couchpotato/core/plugins/suggestion/main.py
+++ b/couchpotato/core/plugins/suggestion/main.py
@@ -1,13 +1,14 @@
from couchpotato import get_session
from couchpotato.api import addApiView
from couchpotato.core.event import fireEvent
-from couchpotato.core.helpers.encoding import ss
-from couchpotato.core.helpers.variable import splitString, md5
+from couchpotato.core.helpers.variable import splitString
from couchpotato.core.plugins.base import Plugin
-from couchpotato.core.settings.model import Movie
+from couchpotato.core.settings.model import Movie, Library
from couchpotato.environment import Env
+from sqlalchemy.orm import joinedload_all
from sqlalchemy.sql.expression import or_
+
class Suggestion(Plugin):
def __init__(self):
@@ -15,44 +16,53 @@ class Suggestion(Plugin):
addApiView('suggestion.view', self.suggestView)
addApiView('suggestion.ignore', self.ignoreView)
- def suggestView(self, **kwargs):
+ def suggestView(self, limit = 6, **kwargs):
movies = splitString(kwargs.get('movies', ''))
ignored = splitString(kwargs.get('ignored', ''))
- limit = kwargs.get('limit', 6)
-
- if not movies or len(movies) == 0:
- db = get_session()
- active_movies = db.query(Movie) \
- .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all()
- movies = [x.library.identifier for x in active_movies]
-
- if not ignored or len(ignored) == 0:
- ignored = splitString(Env.prop('suggest_ignore', default = ''))
+ seen = splitString(kwargs.get('seen', ''))
cached_suggestion = self.getCache('suggestion_cached')
if cached_suggestion:
suggestions = cached_suggestion
else:
+
+ if not movies or len(movies) == 0:
+ db = get_session()
+ active_movies = db.query(Movie) \
+ .options(joinedload_all('library')) \
+ .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all()
+ movies = [x.library.identifier for x in active_movies]
+
+ if not ignored or len(ignored) == 0:
+ ignored = splitString(Env.prop('suggest_ignore', default = ''))
+ if not seen or len(seen) == 0:
+ movies.extend(splitString(Env.prop('suggest_seen', default = '')))
+
suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True)
- self.setCache(md5(ss('suggestion_cached')), suggestions, timeout = 6048000) # Cache for 10 weeks
+ self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks
return {
'success': True,
'count': len(suggestions),
- 'suggestions': suggestions[:limit]
+ 'suggestions': suggestions[:int(limit)]
}
- def ignoreView(self, imdb = None, limit = 6, remove_only = False, **kwargs):
+ def ignoreView(self, imdb = None, limit = 6, remove_only = False, mark_seen = False, **kwargs):
ignored = splitString(Env.prop('suggest_ignore', default = ''))
+ seen = splitString(Env.prop('suggest_seen', default = ''))
+ new_suggestions = []
if imdb:
- if not remove_only:
+ if mark_seen:
+ seen.append(imdb)
+ Env.prop('suggest_seen', ','.join(set(seen)))
+ elif not remove_only:
ignored.append(imdb)
Env.prop('suggest_ignore', ','.join(set(ignored)))
- new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored)
+ new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen)
return {
'result': True,
@@ -60,12 +70,13 @@ class Suggestion(Plugin):
'suggestions': new_suggestions[limit - 1:limit]
}
- def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None):
+ def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None):
# Combine with previous suggestion_cache
- cached_suggestion = self.getCache('suggestion_cached')
+ cached_suggestion = self.getCache('suggestion_cached') or []
new_suggestions = []
ignored = [] if not ignored else ignored
+ seen = [] if not seen else seen
if ignore_imdb:
for cs in cached_suggestion:
@@ -75,10 +86,15 @@ class Suggestion(Plugin):
# Get new results and add them
if len(new_suggestions) - 1 < limit:
+ active_status, done_status = fireEvent('status.get', ['active', 'done'], single = True)
+
db = get_session()
active_movies = db.query(Movie) \
- .filter(or_(*[Movie.status.has(identifier = s) for s in ['active', 'done']])).all()
- movies = [x.library.identifier for x in active_movies]
+ .join(Library) \
+ .with_entities(Library.identifier) \
+ .filter(Movie.status_id.in_([active_status.get('id'), done_status.get('id')])).all()
+ movies = [x[0] for x in active_movies]
+ movies.extend(seen)
ignored.extend([x.get('imdb') for x in cached_suggestion])
suggestions = fireEvent('movie.suggest', movies = movies, ignore = list(set(ignored)), single = True)
@@ -86,6 +102,6 @@ class Suggestion(Plugin):
if suggestions:
new_suggestions.extend(suggestions)
- self.setCache(md5(ss('suggestion_cached')), new_suggestions, timeout = 6048000)
+ self.setCache('suggestion_cached', new_suggestions, timeout = 6048000)
return new_suggestions
diff --git a/couchpotato/core/plugins/suggestion/static/suggest.css b/couchpotato/core/plugins/suggestion/static/suggest.css
index 95e12b9e..c321ca28 100644
--- a/couchpotato/core/plugins/suggestion/static/suggest.css
+++ b/couchpotato/core/plugins/suggestion/static/suggest.css
@@ -16,58 +16,96 @@
width: 50%;
}
}
-
+
@media all and (max-width: 600px) {
.suggestions .movie_result {
width: 100%;
}
}
-
+
.suggestions .movie_result .data {
left: 100px;
background: #4e5969;
border: none;
}
-
+
.suggestions .movie_result .data .info {
top: 15px;
left: 15px;
right: 15px;
+ bottom: 15px;
+ overflow: hidden;
}
-
+
.suggestions .movie_result .data .info h2 {
white-space: normal;
max-height: 120px;
font-size: 18px;
line-height: 18px;
}
-
+
+ .suggestions .movie_result .data .info .rating,
+ .suggestions .movie_result .data .info .genres,
.suggestions .movie_result .data .info .year {
position: static;
display: block;
- margin: 5px 0 0;
padding: 0;
opacity: .6;
}
-
+
+ .suggestions .movie_result .data .info .year {
+ margin: 10px 0 0;
+ }
+
+ .suggestions .movie_result .data .info .rating {
+ font-size: 20px;
+ float: right;
+ margin-top: -20px;
+ }
+ .suggestions .movie_result .data .info .rating:before {
+ content: "\e031";
+ font-family: 'Elusive-Icons';
+ font-size: 14px;
+ margin: 0 5px 0 0;
+ vertical-align: bottom;
+ }
+
+ .suggestions .movie_result .data .info .genres {
+ font-size: 11px;
+ font-style: italic;
+ text-align: right;
+
+ }
+
.suggestions .movie_result .data {
- cursor: default;
+ cursor: default;
}
-
+
.suggestions .movie_result .options {
left: 100px;
}
-
+ .suggestions .movie_result .options select[name=title] { width: 100%; }
+ .suggestions .movie_result .options select[name=profile] { width: 100%; }
+ .suggestions .movie_result .options select[name=category] { width: 100%; }
+
+ .suggestions .movie_result .button {
+ position: absolute;
+ margin: 2px 0 0 0;
+ right: 15px;
+ bottom: 15px;
+ }
+
+
.suggestions .movie_result .thumbnail {
width: 100px;
}
-
+
.suggestions .movie_result .actions {
position: absolute;
bottom: 10px;
right: 10px;
display: none;
- width: 120px;
+ width: 140px;
}
.suggestions .movie_result:hover .actions {
display: block;
@@ -75,10 +113,9 @@
.suggestions .movie_result .data.open .actions {
display: none;
}
-
+
.suggestions .movie_result .actions a {
margin-left: 10px;
vertical-align: middle;
}
-
-
\ No newline at end of file
+
diff --git a/couchpotato/core/plugins/suggestion/static/suggest.js b/couchpotato/core/plugins/suggestion/static/suggest.js
index 5be7d139..e6226711 100644
--- a/couchpotato/core/plugins/suggestion/static/suggest.js
+++ b/couchpotato/core/plugins/suggestion/static/suggest.js
@@ -26,6 +26,20 @@ var SuggestList = new Class({
'onComplete': self.fill.bind(self)
});
+ },
+ 'click:relay(a.eye-open)': function(e, el){
+ (e).stop();
+
+ $(el).getParent('.movie_result').destroy();
+
+ Api.request('suggestion.ignore', {
+ 'data': {
+ 'imdb': el.get('data-seen'),
+ 'mark_seen': 1
+ },
+ 'onComplete': self.fill.bind(self)
+ });
+
}
}
}).grab(
@@ -44,6 +58,8 @@ var SuggestList = new Class({
var self = this;
+ if(!json) return;
+
Object.each(json.suggestions, function(movie){
var m = new Block.Search.Item(movie, {
@@ -67,14 +83,32 @@ var SuggestList = new Class({
new Element('a.delete.icon2', {
'title': 'Don\'t suggest this movie again',
'data-ignore': movie.imdb
+ }),
+ new Element('a.eye-open.icon2', {
+ 'title': 'Seen it, like it, don\'t add',
+ 'data-seen': movie.imdb
})
)
);
m.data_container.removeEvents('click');
+
+ // Add rating
+ m.info_container.adopt(
+ m.rating = m.info.rating && m.info.rating.imdb.length == 2 && parseFloat(m.info.rating.imdb[0]) > 0 ? new Element('span.rating', {
+ 'text': parseFloat(m.info.rating.imdb[0]),
+ 'title': parseInt(m.info.rating.imdb[1]) + ' votes'
+ }) : null,
+ m.genre = m.info.genres && m.info.genres.length > 0 ? new Element('span.genres', {
+ 'text': m.info.genres.slice(0, 3).join(', ')
+ }) : null
+ )
+
$(m).inject(self.el);
});
+ self.fireEvent('loaded');
+
},
afterAdded: function(m, movie){
diff --git a/couchpotato/core/plugins/trailer/main.py b/couchpotato/core/plugins/trailer/main.py
index 1a8955fb..e27e3f9f 100644
--- a/couchpotato/core/plugins/trailer/main.py
+++ b/couchpotato/core/plugins/trailer/main.py
@@ -12,8 +12,8 @@ class Trailer(Plugin):
def __init__(self):
addEvent('renamer.after', self.searchSingle)
- def searchSingle(self, message = None, group = {}):
-
+ def searchSingle(self, message = None, group = None):
+ if not group: group = {}
if self.isDisabled() or len(group['files']['trailer']) > 0: return
trailers = fireEvent('trailer.search', group = group, merge = True)
@@ -40,4 +40,3 @@ class Trailer(Plugin):
break
return True
-
diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py
index a76cf58c..1b3cfe3d 100644
--- a/couchpotato/core/plugins/userscript/main.py
+++ b/couchpotato/core/plugins/userscript/main.py
@@ -55,7 +55,7 @@ class Userscript(Plugin):
'excludes': fireEvent('userscript.get_excludes', merge = True),
'version': klass.getVersion(),
'api': '%suserscript/' % Env.get('api_base'),
- 'host': '%s://%s' % (self.request.protocol, self.request.host),
+ 'host': '%s://%s' % (self.request.protocol, self.request.headers.get('X-Forwarded-Host') or self.request.headers.get('host')),
}
script = klass.renderTemplate(__file__, 'template.js', **params)
@@ -81,8 +81,6 @@ class Userscript(Plugin):
def getViaUrl(self, url = None, **kwargs):
- print url
-
params = {
'url': url,
'movie': fireEvent('userscript.get_movie_via_url', url = url, single = True)
diff --git a/couchpotato/core/providers/automation/bluray/__init__.py b/couchpotato/core/providers/automation/bluray/__init__.py
index b916b0af..e0675247 100644
--- a/couchpotato/core/providers/automation/bluray/__init__.py
+++ b/couchpotato/core/providers/automation/bluray/__init__.py
@@ -11,7 +11,7 @@ config = [{
'list': 'automation_providers',
'name': 'bluray_automation',
'label': 'Blu-ray.com',
- 'description': 'Imports movies from blu-ray.com. (uses minimal requirements)',
+ 'description': 'Imports movies from blu-ray.com.',
'options': [
{
'name': 'automation_enabled',
diff --git a/couchpotato/core/providers/automation/imdb/__init__.py b/couchpotato/core/providers/automation/imdb/__init__.py
index a0013c4a..546cba97 100644
--- a/couchpotato/core/providers/automation/imdb/__init__.py
+++ b/couchpotato/core/providers/automation/imdb/__init__.py
@@ -9,7 +9,7 @@ config = [{
{
'tab': 'automation',
'list': 'watchlist_providers',
- 'name': 'imdb_automation',
+ 'name': 'imdb_automation_watchlist',
'label': 'IMDB',
'description': 'From any public IMDB watchlists. Url should be the CSV link.',
'options': [
@@ -30,5 +30,33 @@ config = [{
},
],
},
+ {
+ 'tab': 'automation',
+ 'list': 'automation_providers',
+ 'name': 'imdb_automation_charts',
+ 'label': 'IMDB',
+ 'description': 'Import movies from IMDB Charts',
+ 'options': [
+ {
+ 'name': 'automation_providers_enabled',
+ 'default': False,
+ 'type': 'enabler',
+ },
+ {
+ 'name': 'automation_charts_theater',
+ 'type': 'bool',
+ 'label': 'In Theaters',
+ 'description': 'New Movies In-Theaters chart',
+ 'default': True,
+ },
+ {
+ 'name': 'automation_charts_top250',
+ 'type': 'bool',
+ 'label': 'TOP 250',
+ 'description': 'IMDB TOP 250 chart',
+ 'default': True,
+ },
+ ],
+ },
],
}]
diff --git a/couchpotato/core/providers/automation/imdb/main.py b/couchpotato/core/providers/automation/imdb/main.py
index 75a2d75c..e9d14b5a 100644
--- a/couchpotato/core/providers/automation/imdb/main.py
+++ b/couchpotato/core/providers/automation/imdb/main.py
@@ -1,38 +1,100 @@
+import traceback
+
+from bs4 import BeautifulSoup
+from couchpotato import fireEvent
from couchpotato.core.helpers.rss import RSS
from couchpotato.core.helpers.variable import getImdb, splitString, tryInt
+
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
-import traceback
+
+from couchpotato.core.providers.base import MultiProvider
+
log = CPLog(__name__)
-class IMDB(Automation, RSS):
+class IMDB(MultiProvider):
+
+ def getTypes(self):
+ return [IMDBWatchlist, IMDBAutomation]
+
+
+class IMDBBase(Automation, RSS):
interval = 1800
+ def getInfo(self, imdb_id):
+ return fireEvent('movie.info', identifier = imdb_id, merge = True)
+
+
+class IMDBWatchlist(IMDBBase):
+
+ enabled_option = 'automation_enabled'
+
def getIMDBids(self):
movies = []
- enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
- urls = splitString(self.conf('automation_urls'))
+ watchlist_enablers = [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]
+ watchlist_urls = splitString(self.conf('automation_urls'))
index = -1
- for url in urls:
+ for watchlist_url in watchlist_urls:
index += 1
- if not enablers[index]:
+ if not watchlist_enablers[index]:
continue
try:
- rss_data = self.getHTMLData(url)
+ log.debug('Started IMDB watchlists: %s', watchlist_url)
+ rss_data = self.getHTMLData(watchlist_url)
imdbs = getImdb(rss_data, multiple = True) if rss_data else []
for imdb in imdbs:
movies.append(imdb)
+ if self.shuttingDown():
+ break
+
except:
- log.error('Failed loading IMDB watchlist: %s %s', (url, traceback.format_exc()))
+ log.error('Failed loading IMDB watchlist: %s %s', (watchlist_url, traceback.format_exc()))
+
+ return movies
+
+
+class IMDBAutomation(IMDBBase):
+
+ enabled_option = 'automation_providers_enabled'
+
+ chart_urls = {
+ 'theater': 'http://www.imdb.com/movies-in-theaters/',
+ 'top250': 'http://www.imdb.com/chart/top',
+ }
+
+ def getIMDBids(self):
+
+ movies = []
+
+ for url in self.chart_urls:
+ if self.conf('automation_charts_%s' % url):
+ data = self.getHTMLData(self.chart_urls[url])
+ if data:
+ html = BeautifulSoup(data)
+
+ try:
+ result_div = html.find('div', attrs = {'id': 'main'})
+ imdb_ids = getImdb(str(result_div), multiple = True)
+
+ for imdb_id in imdb_ids:
+ info = self.getInfo(imdb_id)
+ if info and self.isMinimalMovie(info):
+ movies.append(imdb_id)
+
+ if self.shuttingDown():
+ break
+
+ except:
+ log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc()))
return movies
diff --git a/couchpotato/core/providers/automation/itunes/__init__.py b/couchpotato/core/providers/automation/itunes/__init__.py
index b5c565f6..cc5dddc7 100644
--- a/couchpotato/core/providers/automation/itunes/__init__.py
+++ b/couchpotato/core/providers/automation/itunes/__init__.py
@@ -11,7 +11,7 @@ config = [{
'list': 'automation_providers',
'name': 'itunes_automation',
'label': 'iTunes',
- 'description': 'From any iTunes Store feed. Url should be the RSS link. (uses minimal requirements)',
+ 'description': 'From any iTunes Store feed. Url should be the RSS link.',
'options': [
{
'name': 'automation_enabled',
diff --git a/couchpotato/core/providers/automation/itunes/main.py b/couchpotato/core/providers/automation/itunes/main.py
index 14ca2a82..8e352370 100644
--- a/couchpotato/core/providers/automation/itunes/main.py
+++ b/couchpotato/core/providers/automation/itunes/main.py
@@ -31,7 +31,7 @@ class ITunes(Automation, RSS):
for url in urls:
index += 1
- if not enablers[index]:
+ if len(enablers) == 0 or len(enablers) < index or not enablers[index]:
continue
try:
diff --git a/couchpotato/core/providers/automation/kinepolis/__init__.py b/couchpotato/core/providers/automation/kinepolis/__init__.py
index d3b8e898..24bd4ebb 100644
--- a/couchpotato/core/providers/automation/kinepolis/__init__.py
+++ b/couchpotato/core/providers/automation/kinepolis/__init__.py
@@ -11,7 +11,7 @@ config = [{
'list': 'automation_providers',
'name': 'kinepolis_automation',
'label': 'Kinepolis',
- 'description': 'Imports movies from the current top 10 of kinepolis. (uses minimal requirements)',
+ 'description': 'Imports movies from the current top 10 of kinepolis.',
'options': [
{
'name': 'automation_enabled',
diff --git a/couchpotato/core/providers/automation/moviemeter/__init__.py b/couchpotato/core/providers/automation/moviemeter/__init__.py
index 773bed45..aff5d09d 100644
--- a/couchpotato/core/providers/automation/moviemeter/__init__.py
+++ b/couchpotato/core/providers/automation/moviemeter/__init__.py
@@ -11,7 +11,7 @@ config = [{
'list': 'automation_providers',
'name': 'moviemeter_automation',
'label': 'Moviemeter',
- 'description': 'Imports movies from the current top 10 of moviemeter.nl. (uses minimal requirements)',
+ 'description': 'Imports movies from the current top 10 of moviemeter.nl.',
'options': [
{
'name': 'automation_enabled',
diff --git a/couchpotato/core/providers/automation/rottentomatoes/__init__.py b/couchpotato/core/providers/automation/rottentomatoes/__init__.py
index dd96fe45..4675fac2 100644
--- a/couchpotato/core/providers/automation/rottentomatoes/__init__.py
+++ b/couchpotato/core/providers/automation/rottentomatoes/__init__.py
@@ -11,18 +11,31 @@ config = [{
'list': 'automation_providers',
'name': 'rottentomatoes_automation',
'label': 'Rottentomatoes',
- 'description': 'Imports movies from the rottentomatoes "in theaters"-feed.',
+ 'description': 'Imports movies from rottentomatoes rss feeds specified below.',
'options': [
{
'name': 'automation_enabled',
'default': False,
'type': 'enabler',
},
+ {
+ 'name': 'automation_urls_use',
+ 'label': 'Use',
+ 'default': '1',
+ },
+ {
+ 'name': 'automation_urls',
+ 'label': 'url',
+ 'type': 'combined',
+ 'combine': ['automation_urls_use', 'automation_urls'],
+ 'default': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
+ },
{
'name': 'tomatometer_percent',
'default': '80',
- 'label': 'Tomatometer'
- }
+ 'label': 'Tomatometer',
+ 'description': 'Use as extra scoring requirement',
+ },
],
},
],
diff --git a/couchpotato/core/providers/automation/rottentomatoes/main.py b/couchpotato/core/providers/automation/rottentomatoes/main.py
index b4482023..69611705 100644
--- a/couchpotato/core/providers/automation/rottentomatoes/main.py
+++ b/couchpotato/core/providers/automation/rottentomatoes/main.py
@@ -1,5 +1,5 @@
from couchpotato.core.helpers.rss import RSS
-from couchpotato.core.helpers.variable import tryInt
+from couchpotato.core.helpers.variable import tryInt, splitString
from couchpotato.core.logger import CPLog
from couchpotato.core.providers.automation.base import Automation
from xml.etree.ElementTree import QName
@@ -11,38 +11,42 @@ log = CPLog(__name__)
class Rottentomatoes(Automation, RSS):
interval = 1800
- urls = {
- 'namespace': 'http://www.rottentomatoes.com/xmlns/rtmovie/',
- 'theater': 'http://www.rottentomatoes.com/syndication/rss/in_theaters.xml',
- }
def getIMDBids(self):
movies = []
- rss_movies = self.getRSSData(self.urls['theater'])
- rating_tag = str(QName(self.urls['namespace'], 'tomatometer_percent'))
+ rotten_tomatoes_namespace = 'http://www.rottentomatoes.com/xmlns/rtmovie/'
+ urls = dict(zip(splitString(self.conf('automation_urls')), [tryInt(x) for x in splitString(self.conf('automation_urls_use'))]))
- for movie in rss_movies:
+ for url in urls:
- value = self.getTextElement(movie, "title")
- result = re.search('(?<=%\s).*', value)
+ if not urls[url]:
+ continue
- if result:
+ rss_movies = self.getRSSData(url)
+ rating_tag = str(QName(rotten_tomatoes_namespace, 'tomatometer_percent'))
- log.info2('Something smells...')
- rating = tryInt(self.getTextElement(movie, rating_tag))
- name = result.group(0)
+ for movie in rss_movies:
- if rating < tryInt(self.conf('tomatometer_percent')):
- log.info2('%s seems to be rotten...', name)
- else:
+ value = self.getTextElement(movie, "title")
+ result = re.search('(?<=%\s).*', value)
- log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
- year = datetime.datetime.now().strftime("%Y")
- imdb = self.search(name, year)
+ if result:
- if imdb:
- movies.append(imdb['imdb'])
+ log.info2('Something smells...')
+ rating = tryInt(self.getTextElement(movie, rating_tag))
+ name = result.group(0)
+
+ if rating < tryInt(self.conf('tomatometer_percent')):
+ log.info2('%s seems to be rotten...', name)
+ else:
+
+ log.info2('Found %s fresh enough movies, enqueuing: %s', (rating, name))
+ year = datetime.datetime.now().strftime("%Y")
+ imdb = self.search(name, year)
+
+ if imdb and self.isMinimalMovie(imdb):
+ movies.append(imdb['imdb'])
return movies
diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py
index 182a0310..e6a9cb00 100644
--- a/couchpotato/core/providers/base.py
+++ b/couchpotato/core/providers/base.py
@@ -13,13 +13,32 @@ import traceback
import urllib2
import xml.etree.ElementTree as XMLTree
-
log = CPLog(__name__)
+class MultiProvider(Plugin):
+
+ def __init__(self):
+ self._classes = []
+
+ for Type in self.getTypes():
+ klass = Type()
+
+ # Overwrite name so logger knows what we're talking about
+ klass.setName('%s:%s' % (self.getName(), klass.getName()))
+
+ self._classes.append(klass)
+
+ def getTypes(self):
+ return []
+
+ def getClasses(self):
+ return self._classes
+
+
class Provider(Plugin):
- type = None # movie, nzb, torrent, subtitle, trailer
+ type = None # movie, show, subtitle, trailer, ...
http_time_between_calls = 10 # Default timeout for url requests
last_available_check = {}
@@ -79,7 +98,11 @@ class Provider(Plugin):
class YarrProvider(Provider):
- cat_ids = []
+ protocol = None # nzb, torrent, torrent_magnet
+ type = 'movie'
+
+ cat_ids = {}
+ cat_backup_id = None
sizeGb = ['gb', 'gib']
sizeMb = ['mb', 'mib']
@@ -89,14 +112,13 @@ class YarrProvider(Provider):
last_login_check = 0
def __init__(self):
- addEvent('provider.enabled_types', self.getEnabledProviderType)
+ addEvent('provider.enabled_protocols', self.getEnabledProtocol)
addEvent('provider.belongs_to', self.belongsTo)
- addEvent('yarr.search', self.search)
- addEvent('%s.search' % self.type, self.search)
+ addEvent('provider.search.%s.%s' % (self.protocol, self.type), self.search)
- def getEnabledProviderType(self):
+ def getEnabledProtocol(self):
if self.isEnabled():
- return self.type
+ return self.protocol
else:
return []
@@ -257,7 +279,7 @@ class ResultList(list):
new_result = self.fillResult(result)
- is_correct_movie = fireEvent('searcher.correct_movie',
+ is_correct_movie = fireEvent('movie.searcher.correct_movie',
nzb = new_result, movie = self.movie, quality = self.quality,
imdb_results = self.kwargs.get('imdb_results', False), single = True)
@@ -273,9 +295,12 @@ class ResultList(list):
defaults = {
'id': 0,
+ 'protocol': self.provider.protocol,
'type': self.provider.type,
'provider': self.provider.getName(),
'download': self.provider.loginDownload if self.provider.urls.get('login') else self.provider.download,
+ 'seed_ratio': Env.setting('seed_ratio', section = self.provider.getName().lower(), default = ''),
+ 'seed_time': Env.setting('seed_time', section = self.provider.getName().lower(), default = ''),
'url': '',
'name': '',
'age': 0,
diff --git a/couchpotato/core/providers/info/__init__.py b/couchpotato/core/providers/info/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/couchpotato/core/providers/movie/_modifier/__init__.py b/couchpotato/core/providers/info/_modifier/__init__.py
similarity index 100%
rename from couchpotato/core/providers/movie/_modifier/__init__.py
rename to couchpotato/core/providers/info/_modifier/__init__.py
diff --git a/couchpotato/core/providers/movie/_modifier/main.py b/couchpotato/core/providers/info/_modifier/main.py
similarity index 99%
rename from couchpotato/core/providers/movie/_modifier/main.py
rename to couchpotato/core/providers/info/_modifier/main.py
index e4d70221..835cce04 100644
--- a/couchpotato/core/providers/movie/_modifier/main.py
+++ b/couchpotato/core/providers/info/_modifier/main.py
@@ -28,6 +28,7 @@ class MovieResultModifier(Plugin):
'tagline': '',
'imdb': '',
'genres': [],
+ 'mpaa': None
}
def __init__(self):
diff --git a/couchpotato/core/providers/movie/base.py b/couchpotato/core/providers/info/base.py
similarity index 100%
rename from couchpotato/core/providers/movie/base.py
rename to couchpotato/core/providers/info/base.py
diff --git a/couchpotato/core/providers/movie/couchpotatoapi/__init__.py b/couchpotato/core/providers/info/couchpotatoapi/__init__.py
similarity index 100%
rename from couchpotato/core/providers/movie/couchpotatoapi/__init__.py
rename to couchpotato/core/providers/info/couchpotatoapi/__init__.py
diff --git a/couchpotato/core/providers/movie/couchpotatoapi/main.py b/couchpotato/core/providers/info/couchpotatoapi/main.py
similarity index 94%
rename from couchpotato/core/providers/movie/couchpotatoapi/main.py
rename to couchpotato/core/providers/info/couchpotatoapi/main.py
index 9f76381a..ef7db1f9 100644
--- a/couchpotato/core/providers/movie/couchpotatoapi/main.py
+++ b/couchpotato/core/providers/info/couchpotatoapi/main.py
@@ -1,7 +1,7 @@
from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.movie.base import MovieProvider
+from couchpotato.core.providers.info.base import MovieProvider
from couchpotato.environment import Env
import time
@@ -80,7 +80,10 @@ class CouchPotatoApi(MovieProvider):
return dates
- def getSuggestions(self, movies = [], ignore = []):
+ def getSuggestions(self, movies = None, ignore = None):
+ if not ignore: ignore = []
+ if not movies: movies = []
+
suggestions = self.getJsonData(self.urls['suggest'], params = {
'movies': ','.join(movies),
'ignore': ','.join(ignore),
diff --git a/couchpotato/core/providers/movie/omdbapi/__init__.py b/couchpotato/core/providers/info/omdbapi/__init__.py
similarity index 100%
rename from couchpotato/core/providers/movie/omdbapi/__init__.py
rename to couchpotato/core/providers/info/omdbapi/__init__.py
diff --git a/couchpotato/core/providers/movie/omdbapi/main.py b/couchpotato/core/providers/info/omdbapi/main.py
old mode 100644
new mode 100755
similarity index 95%
rename from couchpotato/core/providers/movie/omdbapi/main.py
rename to couchpotato/core/providers/info/omdbapi/main.py
index 89990747..87bb0a73
--- a/couchpotato/core/providers/movie/omdbapi/main.py
+++ b/couchpotato/core/providers/info/omdbapi/main.py
@@ -2,7 +2,7 @@ from couchpotato.core.event import addEvent, fireEvent
from couchpotato.core.helpers.encoding import tryUrlencode
from couchpotato.core.helpers.variable import tryInt, tryFloat, splitString
from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.movie.base import MovieProvider
+from couchpotato.core.providers.info.base import MovieProvider
import json
import re
import traceback
@@ -95,9 +95,10 @@ class OMDBAPI(MovieProvider):
#'rotten': (tryFloat(movie.get('tomatoRating', 0)), tryInt(movie.get('tomatoReviews', '').replace(',', ''))),
},
'imdb': str(movie.get('imdbID', '')),
+ 'mpaa': str(movie.get('Rated', '')),
'runtime': self.runtimeToMinutes(movie.get('Runtime', '')),
'released': movie.get('Released'),
- 'year': year if isinstance(year, (int)) else None,
+ 'year': year if isinstance(year, int) else None,
'plot': movie.get('Plot'),
'genres': splitString(movie.get('Genre', '')),
'directors': splitString(movie.get('Director', '')),
diff --git a/couchpotato/core/providers/movie/themoviedb/__init__.py b/couchpotato/core/providers/info/themoviedb/__init__.py
similarity index 100%
rename from couchpotato/core/providers/movie/themoviedb/__init__.py
rename to couchpotato/core/providers/info/themoviedb/__init__.py
diff --git a/couchpotato/core/providers/info/themoviedb/main.py b/couchpotato/core/providers/info/themoviedb/main.py
new file mode 100644
index 00000000..376ddad0
--- /dev/null
+++ b/couchpotato/core/providers/info/themoviedb/main.py
@@ -0,0 +1,153 @@
+from couchpotato.core.event import addEvent
+from couchpotato.core.helpers.encoding import simplifyString, toUnicode
+from couchpotato.core.logger import CPLog
+from couchpotato.core.providers.info.base import MovieProvider
+import tmdb3
+import traceback
+
+log = CPLog(__name__)
+
+
+class TheMovieDb(MovieProvider):
+
+ def __init__(self):
+ addEvent('movie.search', self.search, priority = 2)
+ addEvent('movie.info', self.getInfo, priority = 2)
+ addEvent('movie.info_by_tmdb', self.getInfo)
+
+ # Configure TMDB settings
+ tmdb3.set_key(self.conf('api_key'))
+ tmdb3.set_cache('null')
+
+ def search(self, q, limit = 12):
+ """ Find movie by name """
+
+ if self.isDisabled():
+ return False
+
+ search_string = simplifyString(q)
+ cache_key = 'tmdb.cache.%s.%s' % (search_string, limit)
+ results = self.getCache(cache_key)
+
+ if not results:
+ log.debug('Searching for movie: %s', q)
+
+ raw = None
+ try:
+ raw = tmdb3.searchMovie(search_string)
+ except:
+ log.error('Failed searching TMDB for "%s": %s', (search_string, traceback.format_exc()))
+
+ results = []
+ if raw:
+ try:
+ nr = 0
+
+ for movie in raw:
+ results.append(self.parseMovie(movie, with_titles = False))
+
+ nr += 1
+ if nr == limit:
+ break
+
+ log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results])
+
+ self.setCache(cache_key, results)
+ return results
+ except SyntaxError, e:
+ log.error('Failed to parse XML response: %s', e)
+ return False
+
+ return results
+
+ def getInfo(self, identifier = None):
+
+ if not identifier:
+ return {}
+
+ cache_key = 'tmdb.cache.%s' % identifier
+ result = self.getCache(cache_key)
+
+ if not result:
+ try:
+ log.debug('Getting info: %s', cache_key)
+ movie = tmdb3.Movie(identifier)
+ result = self.parseMovie(movie)
+ self.setCache(cache_key, result)
+ except:
+ pass
+
+ return result
+
+ def parseMovie(self, movie, with_titles = True):
+
+ cache_key = 'tmdb.cache.%s' % movie.id
+ movie_data = self.getCache(cache_key)
+
+ if not movie_data:
+
+ # Images
+ poster = self.getImage(movie, type = 'poster', size = 'poster')
+ poster_original = self.getImage(movie, type = 'poster', size = 'original')
+ backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
+
+ # Genres
+ try:
+ genres = [genre.name for genre in movie.genres]
+ except:
+ genres = []
+
+ # 1900 is the same as None
+ year = str(movie.releasedate or '')[:4]
+ if not movie.releasedate or year == '1900' or year.lower() == 'none':
+ year = None
+
+ movie_data = {
+ 'via_tmdb': True,
+ 'tmdb_id': movie.id,
+ 'titles': [toUnicode(movie.title)],
+ 'original_title': movie.originaltitle,
+ 'images': {
+ 'poster': [poster] if poster else [],
+ #'backdrop': [backdrop] if backdrop else [],
+ 'poster_original': [poster_original] if poster_original else [],
+ 'backdrop_original': [backdrop_original] if backdrop_original else [],
+ },
+ 'imdb': movie.imdb,
+ 'runtime': movie.runtime,
+ 'released': str(movie.releasedate),
+ 'year': year,
+ 'plot': movie.overview,
+ 'genres': genres,
+ }
+
+ movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
+
+ # Add alternative names
+ if with_titles:
+ movie_data['titles'].append(movie.originaltitle)
+ for alt in movie.alternate_titles:
+ alt_name = alt.title
+ if alt_name and alt_name not in movie_data['titles'] and alt_name.lower() != 'none' and alt_name is not None:
+ movie_data['titles'].append(alt_name)
+
+ # Cache movie parsed
+ self.setCache(cache_key, movie_data)
+
+ return movie_data
+
+ def getImage(self, movie, type = 'poster', size = 'poster'):
+
+ image_url = ''
+ try:
+ image_url = getattr(movie, type).geturl(size = 'original')
+ except:
+ log.debug('Failed getting %s.%s for "%s"', (type, size, movie.title))
+
+ return image_url
+
+ def isDisabled(self):
+ if self.conf('api_key') == '':
+ log.error('No API key provided.')
+ return True
+ return False
diff --git a/couchpotato/core/providers/metadata/base.py b/couchpotato/core/providers/metadata/base.py
index b41960a0..f5610030 100644
--- a/couchpotato/core/providers/metadata/base.py
+++ b/couchpotato/core/providers/metadata/base.py
@@ -17,14 +17,15 @@ class MetaDataBase(Plugin):
def __init__(self):
addEvent('renamer.after', self.create)
- def create(self, message = None, group = {}):
+ def create(self, message = None, group = None):
if self.isDisabled(): return
+ if not group: group = {}
log.info('Creating %s metadata.', self.getName())
# Update library to get latest info
try:
- updated_library = fireEvent('library.update', group['library']['identifier'], force = True, single = True)
+ updated_library = fireEvent('library.update.movie', group['library']['identifier'], force = True, single = True)
group['library'] = mergeDicts(group['library'], updated_library)
except:
log.error('Failed to update movie, before creating metadata: %s', traceback.format_exc())
@@ -40,7 +41,7 @@ class MetaDataBase(Plugin):
# Get file path
name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root)
- if name and self.conf('meta_' + file_type):
+ if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None):
# Get file content
content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = group)
@@ -48,6 +49,11 @@ class MetaDataBase(Plugin):
log.debug('Creating %s file: %s', (file_type, name))
if os.path.isfile(content):
shutil.copy2(content, name)
+ shutil.copyfile(content, name)
+
+ # Try and copy stats seperately
+ try: shutil.copystat(content, name)
+ except: pass
else:
self.createFile(name, content)
group['renamed_files'].append(name)
@@ -60,8 +66,9 @@ class MetaDataBase(Plugin):
except:
log.error('Unable to create %s file: %s', (file_type, traceback.format_exc()))
- def getRootName(self, data):
- return
+ def getRootName(self, data = None):
+ if not data: data = {}
+ return os.path.join(data['destination_dir'], data['filename'])
def getFanartName(self, name, root):
return
@@ -72,13 +79,19 @@ class MetaDataBase(Plugin):
def getNfoName(self, name, root):
return
- def getNfo(self, movie_info = {}, data = {}):
- return
+ def getNfo(self, movie_info = None, data = None):
+ if not data: data = {}
+ if not movie_info: movie_info = {}
- def getThumbnail(self, movie_info = {}, data = {}, wanted_file_type = 'poster_original'):
+ def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original'):
+ if not data: data = {}
+ if not movie_info: movie_info = {}
file_types = fireEvent('file.types', single = True)
- for file_type in file_types:
- if file_type.get('identifier') == wanted_file_type:
+ file_type = {}
+
+ for ft in file_types:
+ if ft.get('identifier') == wanted_file_type:
+ file_type = ft
break
# See if it is in current files
@@ -94,5 +107,7 @@ class MetaDataBase(Plugin):
except:
pass
- def getFanart(self, movie_info = {}, data = {}):
+ def getFanart(self, movie_info = None, data = None):
+ if not data: data = {}
+ if not movie_info: movie_info = {}
return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original')
diff --git a/couchpotato/core/providers/metadata/wmc/__init__.py b/couchpotato/core/providers/metadata/wmc/__init__.py
new file mode 100644
index 00000000..290436c6
--- /dev/null
+++ b/couchpotato/core/providers/metadata/wmc/__init__.py
@@ -0,0 +1,24 @@
+from .main import WindowsMediaCenter
+
+def start():
+ return WindowsMediaCenter()
+
+config = [{
+ 'name': 'windowsmediacenter',
+ 'groups': [
+ {
+ 'tab': 'renamer',
+ 'subtab': 'metadata',
+ 'name': 'windowsmediacenter_metadata',
+ 'label': 'Windows Explorer / Media Center',
+ 'description': 'Generate folder.jpg',
+ 'options': [
+ {
+ 'name': 'meta_enabled',
+ 'default': False,
+ 'type': 'enabler',
+ },
+ ],
+ },
+ ],
+}]
diff --git a/couchpotato/core/providers/metadata/wmc/main.py b/couchpotato/core/providers/metadata/wmc/main.py
new file mode 100644
index 00000000..89258918
--- /dev/null
+++ b/couchpotato/core/providers/metadata/wmc/main.py
@@ -0,0 +1,7 @@
+from couchpotato.core.providers.metadata.base import MetaDataBase
+import os
+
+class WindowsMediaCenter(MetaDataBase):
+
+ def getThumbnailName(self, name, root):
+ return os.path.join(root, 'folder.jpg')
diff --git a/couchpotato/core/providers/metadata/xbmc/main.py b/couchpotato/core/providers/metadata/xbmc/main.py
index 1fd95846..e865e2d4 100644
--- a/couchpotato/core/providers/metadata/xbmc/main.py
+++ b/couchpotato/core/providers/metadata/xbmc/main.py
@@ -12,9 +12,6 @@ log = CPLog(__name__)
class XBMC(MetaDataBase):
- def getRootName(self, data = {}):
- return os.path.join(data['destination_dir'], data['filename'])
-
def getFanartName(self, name, root):
return self.createMetaName(self.conf('meta_fanart_name'), name, root)
@@ -27,7 +24,9 @@ class XBMC(MetaDataBase):
def createMetaName(self, basename, name, root):
return os.path.join(root, basename.replace('%s', name))
- def getNfo(self, movie_info = {}, data = {}):
+ def getNfo(self, movie_info = None, data = None):
+ if not data: data = {}
+ if not movie_info: movie_info = {}
# return imdb url only
if self.conf('meta_url_only'):
diff --git a/couchpotato/core/providers/movie/themoviedb/main.py b/couchpotato/core/providers/movie/themoviedb/main.py
deleted file mode 100644
index 735419c3..00000000
--- a/couchpotato/core/providers/movie/themoviedb/main.py
+++ /dev/null
@@ -1,215 +0,0 @@
-from couchpotato.core.event import addEvent
-from couchpotato.core.helpers.encoding import simplifyString, toUnicode
-from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.movie.base import MovieProvider
-from themoviedb import tmdb
-import traceback
-
-log = CPLog(__name__)
-
-
-class TheMovieDb(MovieProvider):
-
- def __init__(self):
- addEvent('movie.by_hash', self.byHash)
- addEvent('movie.search', self.search, priority = 2)
- addEvent('movie.info', self.getInfo, priority = 2)
- addEvent('movie.info_by_tmdb', self.getInfoByTMDBId)
-
- # Use base wrapper
- tmdb.configure(self.conf('api_key'))
-
- def byHash(self, file):
- ''' Find movie by hash '''
-
- if self.isDisabled():
- return False
-
- cache_key = 'tmdb.cache.%s' % simplifyString(file)
- results = self.getCache(cache_key)
-
- if not results:
- log.debug('Searching for movie by hash: %s', file)
- try:
- raw = tmdb.searchByHashingFile(file)
-
- results = []
- if raw:
- try:
- results = self.parseMovie(raw)
- log.info('Found: %s', results['titles'][0] + ' (' + str(results.get('year', 0)) + ')')
-
- self.setCache(cache_key, results)
- return results
- except SyntaxError, e:
- log.error('Failed to parse XML response: %s', e)
- return False
- except:
- log.debug('No movies known by hash for: %s', file)
- pass
-
- return results
-
- def search(self, q, limit = 12):
- ''' Find movie by name '''
-
- if self.isDisabled():
- return False
-
- search_string = simplifyString(q)
- cache_key = 'tmdb.cache.%s.%s' % (search_string, limit)
- results = self.getCache(cache_key)
-
- if not results:
- log.debug('Searching for movie: %s', q)
-
- raw = None
- try:
- raw = tmdb.search(search_string)
- except:
- log.error('Failed searching TMDB for "%s": %s', (search_string, traceback.format_exc()))
-
- results = []
- if raw:
- try:
- nr = 0
-
- for movie in raw:
- results.append(self.parseMovie(movie))
-
- nr += 1
- if nr == limit:
- break
-
- log.info('Found: %s', [result['titles'][0] + ' (' + str(result.get('year', 0)) + ')' for result in results])
-
- self.setCache(cache_key, results)
- return results
- except SyntaxError, e:
- log.error('Failed to parse XML response: %s', e)
- return False
-
- return results
-
- def getInfo(self, identifier = None):
-
- if not identifier:
- return {}
-
- cache_key = 'tmdb.cache.%s' % identifier
- result = self.getCache(cache_key)
-
- if not result:
- result = {}
- movie = None
-
- try:
- log.debug('Getting info: %s', cache_key)
- movie = tmdb.imdbLookup(id = identifier)
- except:
- pass
-
- if movie:
- result = self.parseMovie(movie[0])
- self.setCache(cache_key, result)
-
- return result
-
- def getInfoByTMDBId(self, id = None):
-
- cache_key = 'tmdb.cache.%s' % id
- result = self.getCache(cache_key)
-
- if not result:
- result = {}
- movie = None
-
- try:
- log.debug('Getting info: %s', cache_key)
- movie = tmdb.getMovieInfo(id = id)
- except:
- pass
-
- if movie:
- result = self.parseMovie(movie)
- self.setCache(cache_key, result)
-
- return result
-
- def parseMovie(self, movie):
-
- # Images
- poster = self.getImage(movie, type = 'poster', size = 'cover')
- #backdrop = self.getImage(movie, type = 'backdrop', size = 'w1280')
- poster_original = self.getImage(movie, type = 'poster', size = 'original')
- backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original')
-
- # Genres
- try:
- genres = self.getCategory(movie, 'genre')
- except:
- genres = []
-
- # 1900 is the same as None
- year = str(movie.get('released', 'none'))[:4]
- if year == '1900' or year.lower() == 'none':
- year = None
-
- movie_data = {
- 'via_tmdb': True,
- 'tmdb_id': int(movie.get('id', 0)),
- 'titles': [toUnicode(movie.get('name'))],
- 'original_title': movie.get('original_name'),
- 'images': {
- 'poster': [poster] if poster else [],
- #'backdrop': [backdrop] if backdrop else [],
- 'poster_original': [poster_original] if poster_original else [],
- 'backdrop_original': [backdrop_original] if backdrop_original else [],
- },
- 'imdb': movie.get('imdb_id'),
- 'runtime': movie.get('runtime'),
- 'released': movie.get('released'),
- 'year': year,
- 'plot': movie.get('overview'),
- 'genres': genres,
- }
-
- movie_data = dict((k, v) for k, v in movie_data.iteritems() if v)
-
- # Add alternative names
- for alt in ['original_name', 'alternative_name']:
- alt_name = toUnicode(movie.get(alt))
- if alt_name and not alt_name in movie_data['titles'] and alt_name.lower() != 'none' and alt_name != None:
- movie_data['titles'].append(alt_name)
-
- return movie_data
-
- def getImage(self, movie, type = 'poster', size = 'cover'):
-
- image_url = ''
- for image in movie.get('images', []):
- if(image.get('type') == type) and image.get(size):
- image_url = image.get(size)
- break
-
- return image_url
-
- def getCategory(self, movie, type = 'genre'):
-
- cats = movie.get('categories', {}).get(type)
-
- categories = []
- for category in cats:
- try:
- categories.append(category)
- except:
- pass
-
- return categories
-
- def isDisabled(self):
- if self.conf('api_key') == '':
- log.error('No API key provided.')
- True
- else:
- False
diff --git a/couchpotato/core/providers/nzb/__init__.py b/couchpotato/core/providers/nzb/__init__.py
index 651ae8b9..36098bb3 100644
--- a/couchpotato/core/providers/nzb/__init__.py
+++ b/couchpotato/core/providers/nzb/__init__.py
@@ -2,13 +2,12 @@ config = {
'name': 'nzb_providers',
'groups': [
{
- 'label': 'Usenet',
+ 'label': 'Usenet Providers',
'description': 'Providers searching usenet for new releases',
'wizard': True,
'type': 'list',
'name': 'nzb_providers',
'tab': 'searcher',
- 'subtab': 'providers',
'options': [],
},
],
diff --git a/couchpotato/core/providers/nzb/base.py b/couchpotato/core/providers/nzb/base.py
index f11382ba..53c73af0 100644
--- a/couchpotato/core/providers/nzb/base.py
+++ b/couchpotato/core/providers/nzb/base.py
@@ -3,7 +3,8 @@ import time
class NZBProvider(YarrProvider):
- type = 'nzb'
+
+ protocol = 'nzb'
def calculateAge(self, unix):
return int(time.time() - unix) / 24 / 60 / 60
diff --git a/couchpotato/core/providers/nzb/binsearch/__init__.py b/couchpotato/core/providers/nzb/binsearch/__init__.py
index d3288604..1cfb0b73 100644
--- a/couchpotato/core/providers/nzb/binsearch/__init__.py
+++ b/couchpotato/core/providers/nzb/binsearch/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'binsearch',
'description': 'Free provider, less accurate. See BinSearch',
diff --git a/couchpotato/core/providers/nzb/binsearch/main.py b/couchpotato/core/providers/nzb/binsearch/main.py
index 1d863002..dee5fc78 100644
--- a/couchpotato/core/providers/nzb/binsearch/main.py
+++ b/couchpotato/core/providers/nzb/binsearch/main.py
@@ -86,8 +86,10 @@ class BinSearch(NZBProvider):
def download(self, url = '', nzb_id = ''):
- params = {'action': 'nzb'}
- params[nzb_id] = 'on'
+ params = {
+ 'action': 'nzb',
+ nzb_id: 'on'
+ }
try:
return self.urlopen(url, params = params, show_error = False)
diff --git a/couchpotato/core/providers/nzb/ftdworld/__init__.py b/couchpotato/core/providers/nzb/ftdworld/__init__.py
index 37aedb2a..5a004a70 100644
--- a/couchpotato/core/providers/nzb/ftdworld/__init__.py
+++ b/couchpotato/core/providers/nzb/ftdworld/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'FTDWorld',
'description': 'Free provider, less accurate. See FTDWorld',
diff --git a/couchpotato/core/providers/nzb/newznab/__init__.py b/couchpotato/core/providers/nzb/newznab/__init__.py
index 90f81cfc..3902ab13 100644
--- a/couchpotato/core/providers/nzb/newznab/__init__.py
+++ b/couchpotato/core/providers/nzb/newznab/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'newznab',
'order': 10,
diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py
index 8eb3e84e..02ffcfdc 100644
--- a/couchpotato/core/providers/nzb/newznab/main.py
+++ b/couchpotato/core/providers/nzb/newznab/main.py
@@ -118,7 +118,7 @@ class Newznab(NZBProvider, RSS):
return list
- def belongsTo(self, url, provider = None):
+ def belongsTo(self, url, provider = None, host = None):
hosts = self.getHosts()
diff --git a/couchpotato/core/providers/nzb/nzbclub/__init__.py b/couchpotato/core/providers/nzb/nzbclub/__init__.py
index 9955462c..95eeea13 100644
--- a/couchpotato/core/providers/nzb/nzbclub/__init__.py
+++ b/couchpotato/core/providers/nzb/nzbclub/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'NZBClub',
'description': 'Free provider, less accurate. See NZBClub',
diff --git a/couchpotato/core/providers/nzb/nzbindex/__init__.py b/couchpotato/core/providers/nzb/nzbindex/__init__.py
index aa8de4dd..47461e63 100644
--- a/couchpotato/core/providers/nzb/nzbindex/__init__.py
+++ b/couchpotato/core/providers/nzb/nzbindex/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'nzbindex',
'description': 'Free provider, less accurate. See NZBIndex',
diff --git a/couchpotato/core/providers/nzb/nzbsrus/__init__.py b/couchpotato/core/providers/nzb/nzbsrus/__init__.py
deleted file mode 100644
index 863e6a37..00000000
--- a/couchpotato/core/providers/nzb/nzbsrus/__init__.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from .main import Nzbsrus
-
-def start():
- return Nzbsrus()
-
-config = [{
- 'name': 'nzbsrus',
- 'groups': [
- {
- 'tab': 'searcher',
- 'subtab': 'providers',
- 'list': 'nzb_providers',
- 'name': 'nzbsrus',
- 'label': 'Nzbsrus',
- 'description': 'See NZBsRus. You need a VIP account!',
- 'wizard': True,
- 'options': [
- {
- 'name': 'enabled',
- 'type': 'enabler',
- },
- {
- 'name': 'userid',
- 'label': 'User ID',
- },
- {
- 'name': 'api_key',
- 'default': '',
- 'label': 'Api Key',
- },
- {
- 'name': 'english_only',
- 'default': 1,
- 'type': 'bool',
- 'label': 'English only',
- 'description': 'Only search for English spoken movies on Nzbsrus',
- },
- {
- 'name': 'extra_score',
- 'advanced': True,
- 'label': 'Extra Score',
- 'type': 'int',
- 'default': 0,
- 'description': 'Starting score for each release found via this provider.',
- }
- ],
- },
- ],
-}]
diff --git a/couchpotato/core/providers/nzb/nzbsrus/main.py b/couchpotato/core/providers/nzb/nzbsrus/main.py
deleted file mode 100644
index d52212a7..00000000
--- a/couchpotato/core/providers/nzb/nzbsrus/main.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from couchpotato.core.helpers.encoding import tryUrlencode
-from couchpotato.core.helpers.rss import RSS
-from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.nzb.base import NZBProvider
-from couchpotato.environment import Env
-import time
-
-log = CPLog(__name__)
-
-class Nzbsrus(NZBProvider, RSS):
-
- urls = {
- 'download': 'https://www.nzbsrus.com/nzbdownload_rss.php/%s',
- 'detail': 'https://www.nzbsrus.com/nzbdetails.php?id=%s',
- 'search': 'https://www.nzbsrus.com/api.php?extended=1&xml=1&listname={date,grabs}',
- }
-
- cat_ids = [
- ([90, 45, 51], ['720p', '1080p', 'brrip', 'bd50', 'dvdr']),
- ([48, 51], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
- ]
- cat_backup_id = 240
-
- def _search(self, movie, quality, results):
-
- cat_id_string = '&'.join(['c%s=1' % x for x in self.getCatId(quality.get('identifier'))])
- arguments = tryUrlencode({
- 'searchtext': 'imdb:' + movie['library']['identifier'][2:],
- 'uid': self.conf('userid'),
- 'key': self.conf('api_key'),
- 'age': Env.setting('retention', section = 'nzb'),
-
- })
-
- # check for english_only
- if self.conf('english_only'):
- arguments += '&lang0=1&lang3=1&lang1=1'
-
- url = '%s&%s&%s' % (self.urls['search'], arguments , cat_id_string)
- nzbs = self.getRSSData(url, item_path = 'results/result', cache_timeout = 1800, headers = {'User-Agent': Env.getIdentifier()})
-
- for nzb in nzbs:
-
- title = self.getTextElement(nzb, 'name')
- if 'error' in title.lower(): continue
-
- nzb_id = self.getTextElement(nzb, 'id')
- size = int(round(int(self.getTextElement(nzb, 'size')) / 1048576))
- age = int(round((time.time() - int(self.getTextElement(nzb, 'postdate'))) / 86400))
-
- results.append({
- 'id': nzb_id,
- 'name': title,
- 'age': age,
- 'size': size,
- 'url': self.urls['download'] % nzb_id + self.getApiExt() + self.getTextElement(nzb, 'key'),
- 'detail_url': self.urls['detail'] % nzb_id,
- 'description': self.getTextElement(nzb, 'addtext'),
- })
-
- def getApiExt(self):
- return '/%s/' % (self.conf('userid'))
diff --git a/couchpotato/core/providers/nzb/nzbx/__init__.py b/couchpotato/core/providers/nzb/nzbx/__init__.py
deleted file mode 100644
index 9cf80638..00000000
--- a/couchpotato/core/providers/nzb/nzbx/__init__.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from .main import Nzbx
-
-def start():
- return Nzbx()
-
-config = [{
- 'name': 'nzbx',
- 'groups': [
- {
- 'tab': 'searcher',
- 'subtab': 'providers',
- 'list': 'nzb_providers',
- 'name': 'nzbX',
- 'description': 'Free provider. See nzbX',
- 'wizard': True,
- 'options': [
- {
- 'name': 'enabled',
- 'type': 'enabler',
- 'default': True,
- },
- {
- 'name': 'extra_score',
- 'advanced': True,
- 'label': 'Extra Score',
- 'type': 'int',
- 'default': 0,
- 'description': 'Starting score for each release found via this provider.',
- }
- ],
- },
- ],
-}]
diff --git a/couchpotato/core/providers/nzb/nzbx/main.py b/couchpotato/core/providers/nzb/nzbx/main.py
deleted file mode 100644
index ec7fbfe2..00000000
--- a/couchpotato/core/providers/nzb/nzbx/main.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from couchpotato.core.helpers.encoding import tryUrlencode
-from couchpotato.core.helpers.variable import tryInt
-from couchpotato.core.logger import CPLog
-from couchpotato.core.providers.nzb.base import NZBProvider
-from couchpotato.environment import Env
-
-log = CPLog(__name__)
-
-
-class Nzbx(NZBProvider):
-
- urls = {
- 'search': 'https://nzbx.co/api/search?%s',
- 'details': 'https://nzbx.co/api/details?guid=%s',
- }
-
- http_time_between_calls = 1 # Seconds
-
- def _search(self, movie, quality, results):
-
- # Get nbzs
- arguments = tryUrlencode({
- 'q': movie['library']['identifier'].replace('tt', ''),
- 'sf': quality.get('size_min'),
- })
- nzbs = self.getJsonData(self.urls['search'] % arguments, headers = {'User-Agent': Env.getIdentifier()})
-
- for nzb in nzbs:
-
- results.append({
- 'id': nzb['guid'],
- 'url': nzb['nzb'],
- 'detail_url': self.urls['details'] % nzb['guid'],
- 'name': nzb['name'],
- 'age': self.calculateAge(int(nzb['postdate'])),
- 'size': tryInt(nzb['size']) / 1024 / 1024,
- 'score': 5 if nzb['votes']['upvotes'] > nzb['votes']['downvotes'] else 0
- })
diff --git a/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py b/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py
index fe80518c..933aff3e 100644
--- a/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py
+++ b/couchpotato/core/providers/nzb/omgwtfnzbs/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'nzb_providers',
'name': 'OMGWTFNZBs',
'description': 'See OMGWTFNZBs',
diff --git a/couchpotato/core/providers/torrent/__init__.py b/couchpotato/core/providers/torrent/__init__.py
index 191e132e..250bcead 100644
--- a/couchpotato/core/providers/torrent/__init__.py
+++ b/couchpotato/core/providers/torrent/__init__.py
@@ -2,13 +2,12 @@ config = {
'name': 'torrent_providers',
'groups': [
{
- 'label': 'Torrent',
+ 'label': 'Torrent Providers',
'description': 'Providers searching torrent sites for new releases',
'wizard': True,
'type': 'list',
'name': 'torrent_providers',
'tab': 'searcher',
- 'subtab': 'providers',
'options': [],
},
],
diff --git a/couchpotato/core/providers/torrent/awesomehd/__init__.py b/couchpotato/core/providers/torrent/awesomehd/__init__.py
index 5c8c9794..de6a2144 100644
--- a/couchpotato/core/providers/torrent/awesomehd/__init__.py
+++ b/couchpotato/core/providers/torrent/awesomehd/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'Awesome-HD',
'description': 'See AHD',
@@ -23,6 +22,20 @@ config = [{
'name': 'passkey',
'default': '',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'only_internal',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/awesomehd/main.py b/couchpotato/core/providers/torrent/awesomehd/main.py
index fed12519..79482f2a 100644
--- a/couchpotato/core/providers/torrent/awesomehd/main.py
+++ b/couchpotato/core/providers/torrent/awesomehd/main.py
@@ -25,6 +25,11 @@ class AwesomeHD(TorrentProvider):
if data:
try:
soup = BeautifulSoup(data)
+
+ if soup.find('error'):
+ log.error(soup.find('error').get_text())
+ return
+
authkey = soup.find('authkey').get_text()
entries = soup.find_all('torrent')
diff --git a/couchpotato/core/providers/torrent/base.py b/couchpotato/core/providers/torrent/base.py
index 453954c9..3e7ddde8 100644
--- a/couchpotato/core/providers/torrent/base.py
+++ b/couchpotato/core/providers/torrent/base.py
@@ -7,7 +7,7 @@ log = CPLog(__name__)
class TorrentProvider(YarrProvider):
- type = 'torrent'
+ protocol = 'torrent'
def imdbMatch(self, url, imdbId):
if getImdb(url) == imdbId:
@@ -27,6 +27,6 @@ class TorrentProvider(YarrProvider):
class TorrentMagnetProvider(TorrentProvider):
- type = 'torrent_magnet'
+ protocol = 'torrent_magnet'
download = None
diff --git a/couchpotato/core/providers/torrent/bitsoup/__init__.py b/couchpotato/core/providers/torrent/bitsoup/__init__.py
new file mode 100644
index 00000000..a36ab08f
--- /dev/null
+++ b/couchpotato/core/providers/torrent/bitsoup/__init__.py
@@ -0,0 +1,55 @@
+from .main import Bitsoup
+
+def start():
+ return Bitsoup()
+
+config = [{
+ 'name': 'bitsoup',
+ 'groups': [
+ {
+ 'tab': 'searcher',
+ 'list': 'torrent_providers',
+ 'name': 'Bitsoup',
+ 'description': 'See Bitsoup',
+ 'wizard': True,
+ 'options': [
+ {
+ 'name': 'enabled',
+ 'type': 'enabler',
+ 'default': False,
+ },
+ {
+ 'name': 'username',
+ 'default': '',
+ },
+ {
+ 'name': 'password',
+ 'default': '',
+ 'type': 'password',
+ },
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
+ {
+ 'name': 'extra_score',
+ 'advanced': True,
+ 'label': 'Extra Score',
+ 'type': 'int',
+ 'default': 20,
+ 'description': 'Starting score for each release found via this provider.',
+ }
+ ],
+ },
+ ],
+}]
diff --git a/couchpotato/core/providers/torrent/bitsoup/main.py b/couchpotato/core/providers/torrent/bitsoup/main.py
new file mode 100644
index 00000000..539ba43d
--- /dev/null
+++ b/couchpotato/core/providers/torrent/bitsoup/main.py
@@ -0,0 +1,84 @@
+from bs4 import BeautifulSoup
+from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode
+from couchpotato.core.helpers.variable import tryInt
+from couchpotato.core.logger import CPLog
+from couchpotato.core.providers.torrent.base import TorrentProvider
+import traceback
+
+log = CPLog(__name__)
+
+
+class Bitsoup(TorrentProvider):
+
+ urls = {
+ 'test': 'https://www.bitsoup.me/',
+ 'login' : 'https://www.bitsoup.me/takelogin.php',
+ 'login_check': 'https://www.bitsoup.me/my.php',
+ 'search': 'https://www.bitsoup.me/browse.php?',
+ 'baseurl': 'https://www.bitsoup.me/%s',
+ }
+
+ http_time_between_calls = 1 #seconds
+
+ def _searchOnTitle(self, title, movie, quality, results):
+
+ q = '"%s" %s' % (simplifyString(title), movie['library']['year'])
+ arguments = tryUrlencode({
+ 'search': q,
+ })
+ url = "%s&%s" % (self.urls['search'], arguments)
+
+ data = self.getHTMLData(url, opener = self.login_opener)
+
+ if data:
+ html = BeautifulSoup(data)
+
+ try:
+ result_table = html.find('table', attrs = {'class': 'koptekst'})
+ entries = result_table.find_all('tr')
+ for result in entries[1:]:
+
+ all_cells = result.find_all('td')
+
+ torrent = all_cells[1].find('a')
+ download = all_cells[3].find('a')
+
+ torrent_id = torrent['href']
+ torrent_id = torrent_id.replace('details.php?id=', '')
+ torrent_id = torrent_id.replace('&hit=1', '')
+
+ torrent_name = torrent.getText()
+
+ torrent_size = self.parseSize(all_cells[7].getText())
+ torrent_seeders = tryInt(all_cells[9].getText())
+ torrent_leechers = tryInt(all_cells[10].getText())
+ torrent_url = self.urls['baseurl'] % download['href']
+ torrent_detail_url = self.urls['baseurl'] % torrent['href']
+
+ results.append({
+ 'id': torrent_id,
+ 'name': torrent_name,
+ 'size': torrent_size,
+ 'seeders': torrent_seeders,
+ 'leechers': torrent_leechers,
+ 'url': torrent_url,
+ 'detail_url': torrent_detail_url,
+ })
+
+ except:
+ log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
+
+
+ def getLoginParams(self):
+ return tryUrlencode({
+ 'username': self.conf('username'),
+ 'password': self.conf('password'),
+ 'ssl': 'yes',
+ })
+
+
+ def loginSuccess(self, output):
+ return 'logout.php' in output.lower()
+
+ loginCheckSuccess = loginSuccess
+
diff --git a/couchpotato/core/providers/torrent/hdbits/__init__.py b/couchpotato/core/providers/torrent/hdbits/__init__.py
index 8a9fc80e..07ea95d6 100644
--- a/couchpotato/core/providers/torrent/hdbits/__init__.py
+++ b/couchpotato/core/providers/torrent/hdbits/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'HDBits',
'description': 'See HDBits',
@@ -31,6 +30,20 @@ config = [{
'name': 'passkey',
'default': '',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/iptorrents/__init__.py b/couchpotato/core/providers/torrent/iptorrents/__init__.py
index 24f9772b..6cb2dead 100644
--- a/couchpotato/core/providers/torrent/iptorrents/__init__.py
+++ b/couchpotato/core/providers/torrent/iptorrents/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'IPTorrents',
'description': 'See IPTorrents',
@@ -34,6 +33,20 @@ config = [{
'type': 'bool',
'description': 'Only search for [FreeLeech] torrents.',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
index 999dbb1b..b095a97d 100644
--- a/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
+++ b/couchpotato/core/providers/torrent/kickasstorrents/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'KickAssTorrents',
'description': 'See KickAssTorrents',
@@ -19,6 +18,20 @@ config = [{
'type': 'enabler',
'default': True,
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
index a535034a..66b3ea76 100644
--- a/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
+++ b/couchpotato/core/providers/torrent/passthepopcorn/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'PassThePopcorn',
'description': 'See PassThePopcorn.me',
@@ -62,6 +61,20 @@ config = [{
'default': 0,
'description': 'Require staff-approval for releases to be accepted.'
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
@@ -71,6 +84,6 @@ config = [{
'description': 'Starting score for each release found via this provider.',
}
],
-}
+ }
]
}]
diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py
index 3c27cf48..ace12880 100644
--- a/couchpotato/core/providers/torrent/publichd/__init__.py
+++ b/couchpotato/core/providers/torrent/publichd/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'PublicHD',
'description': 'Public Torrent site with only HD content. See PublicHD',
@@ -19,6 +18,20 @@ config = [{
'type': 'enabler',
'default': True,
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py
index 2043f8c4..7b497fd9 100644
--- a/couchpotato/core/providers/torrent/publichd/main.py
+++ b/couchpotato/core/providers/torrent/publichd/main.py
@@ -67,10 +67,22 @@ class PublicHD(TorrentMagnetProvider):
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
def getMoreInfo(self, item):
- full_description = self.getCache('publichd.%s' % item['id'], item['detail_url'], cache_timeout = 25920000)
- html = BeautifulSoup(full_description)
- nfo_pre = html.find('div', attrs = {'id':'torrmain'})
- description = toUnicode(nfo_pre.text) if nfo_pre else ''
+
+ cache_key = 'publichd.%s' % item['id']
+ description = self.getCache(cache_key)
+
+ if not description:
+
+ try:
+ full_description = self.urlopen(item['detail_url'])
+ html = BeautifulSoup(full_description)
+ nfo_pre = html.find('div', attrs = {'id':'torrmain'})
+ description = toUnicode(nfo_pre.text) if nfo_pre else ''
+ except:
+ log.error('Failed getting more info for %s', item['name'])
+ description = ''
+
+ self.setCache(cache_key, description, timeout = 25920000)
item['description'] = description
return item
diff --git a/couchpotato/core/providers/torrent/sceneaccess/__init__.py b/couchpotato/core/providers/torrent/sceneaccess/__init__.py
index baad57f6..4b675573 100644
--- a/couchpotato/core/providers/torrent/sceneaccess/__init__.py
+++ b/couchpotato/core/providers/torrent/sceneaccess/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'SceneAccess',
'description': 'See SceneAccess',
@@ -28,6 +27,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/sceneaccess/main.py b/couchpotato/core/providers/torrent/sceneaccess/main.py
index b2d3dd68..7e9ab896 100644
--- a/couchpotato/core/providers/torrent/sceneaccess/main.py
+++ b/couchpotato/core/providers/torrent/sceneaccess/main.py
@@ -29,9 +29,13 @@ class SceneAccess(TorrentProvider):
def _search(self, movie, quality, results):
+ cat = self.getCatId(quality['identifier'])
+ if not cat:
+ return
+
url = self.urls['search'] % (
- self.getCatId(quality['identifier'])[0],
- self.getCatId(quality['identifier'])[0]
+ cat[0],
+ cat[0]
)
arguments = tryUrlencode({
diff --git a/couchpotato/core/providers/torrent/scenehd/__init__.py b/couchpotato/core/providers/torrent/scenehd/__init__.py
index 3cd2132e..c0a82ae7 100644
--- a/couchpotato/core/providers/torrent/scenehd/__init__.py
+++ b/couchpotato/core/providers/torrent/scenehd/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'SceneHD',
'description': 'See SceneHD',
@@ -28,6 +27,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/scenehd/main.py b/couchpotato/core/providers/torrent/scenehd/main.py
index f471ec0c..2b76e43d 100644
--- a/couchpotato/core/providers/torrent/scenehd/main.py
+++ b/couchpotato/core/providers/torrent/scenehd/main.py
@@ -65,7 +65,7 @@ class SceneHD(TorrentProvider):
log.error('Failed getting results from %s: %s', (self.getName(), traceback.format_exc()))
- def getLoginParams(self, params):
+ def getLoginParams(self):
return tryUrlencode({
'username': self.conf('username'),
'password': self.conf('password'),
diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py
index f2394dd6..83de7a94 100644
--- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py
+++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'ThePirateBay',
'description': 'The world\'s largest bittorrent tracker. See ThePirateBay',
@@ -25,6 +24,20 @@ config = [{
'label': 'Proxy server',
'description': 'Domain for requests, keep empty to let CouchPotato pick.',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py
index 10608157..6aa22167 100644
--- a/couchpotato/core/providers/torrent/thepiratebay/main.py
+++ b/couchpotato/core/providers/torrent/thepiratebay/main.py
@@ -15,12 +15,13 @@ class ThePirateBay(TorrentMagnetProvider):
urls = {
'detail': '%s/torrent/%s',
- 'search': '%s/search/%s/%s/7/%d'
+ 'search': '%s/search/%s/%s/7/%s'
}
cat_ids = [
([207], ['720p', '1080p']),
- ([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']),
+ ([201], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr']),
+ ([201, 207], ['brrip']),
([202], ['dvdr'])
]
@@ -50,10 +51,11 @@ class ThePirateBay(TorrentMagnetProvider):
page = 0
total_pages = 1
+ cats = self.getCatId(quality['identifier'])
while page < total_pages:
- search_url = self.urls['search'] % (self.getDomain(), tryUrlencode('"%s" %s' % (title, movie['library']['year'])), page, self.getCatId(quality['identifier'])[0])
+ search_url = self.urls['search'] % (self.getDomain(), tryUrlencode('"%s" %s' % (title, movie['library']['year'])), page, ','.join(str(x) for x in cats))
page += 1
data = self.getHTMLData(search_url)
@@ -84,10 +86,10 @@ class ThePirateBay(TorrentMagnetProvider):
if link and download:
def extra_score(item):
- trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) != None]
- vip = (0, 20)[result.find('img', alt = re.compile('VIP')) != None]
- confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) != None]
- moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) != None]
+ trusted = (0, 10)[result.find('img', alt = re.compile('Trusted')) is not None]
+ vip = (0, 20)[result.find('img', alt = re.compile('VIP')) is not None]
+ confirmed = (0, 30)[result.find('img', alt = re.compile('Helpers')) is not None]
+ moderated = (0, 50)[result.find('img', alt = re.compile('Moderator')) is not None]
return confirmed + trusted + vip + moderated
diff --git a/couchpotato/core/providers/torrent/torrentbytes/__init__.py b/couchpotato/core/providers/torrent/torrentbytes/__init__.py
index 10e581a6..712eac85 100644
--- a/couchpotato/core/providers/torrent/torrentbytes/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentbytes/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentBytes',
'description': 'See TorrentBytes',
@@ -28,6 +27,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/torrentday/__init__.py b/couchpotato/core/providers/torrent/torrentday/__init__.py
index de715b53..d98bb917 100644
--- a/couchpotato/core/providers/torrent/torrentday/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentday/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentDay',
'description': 'See TorrentDay',
@@ -28,6 +27,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/torrentleech/__init__.py b/couchpotato/core/providers/torrent/torrentleech/__init__.py
index fa048d50..c788477f 100644
--- a/couchpotato/core/providers/torrent/torrentleech/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentleech/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentLeech',
'description': 'See TorrentLeech',
@@ -28,6 +27,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/torrent/torrentshack/__init__.py b/couchpotato/core/providers/torrent/torrentshack/__init__.py
index 203e0996..4171fc49 100644
--- a/couchpotato/core/providers/torrent/torrentshack/__init__.py
+++ b/couchpotato/core/providers/torrent/torrentshack/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'TorrentShack',
'description': 'See TorrentShack',
@@ -27,6 +26,20 @@ config = [{
'default': '',
'type': 'password',
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'scene_only',
'type': 'bool',
diff --git a/couchpotato/core/providers/torrent/torrentshack/main.py b/couchpotato/core/providers/torrent/torrentshack/main.py
index 1b9ee197..353b606e 100644
--- a/couchpotato/core/providers/torrent/torrentshack/main.py
+++ b/couchpotato/core/providers/torrent/torrentshack/main.py
@@ -11,12 +11,12 @@ log = CPLog(__name__)
class TorrentShack(TorrentProvider):
urls = {
- 'test' : 'http://www.torrentshack.net/',
- 'login' : 'http://www.torrentshack.net/login.php',
- 'login_check': 'http://www.torrentshack.net/inbox.php',
- 'detail' : 'http://www.torrentshack.net/torrent/%s',
- 'search' : 'http://www.torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1',
- 'download' : 'http://www.torrentshack.net/%s',
+ 'test' : 'https://torrentshack.net/',
+ 'login' : 'https://torrentshack.net/login.php',
+ 'login_check': 'https://torrentshack.net/inbox.php',
+ 'detail' : 'https://torrentshack.net/torrent/%s',
+ 'search' : 'https://torrentshack.net/torrents.php?searchstr=%s&filter_cat[%d]=1',
+ 'download' : 'https://torrentshack.net/%s',
}
cat_ids = [
@@ -27,7 +27,7 @@ class TorrentShack(TorrentProvider):
]
http_time_between_calls = 1 #seconds
- cat_backup_id = None
+ cat_backup_id = 400
def _searchOnTitle(self, title, movie, quality, results):
diff --git a/couchpotato/core/providers/torrent/yify/__init__.py b/couchpotato/core/providers/torrent/yify/__init__.py
index 70d65687..775ecdbe 100644
--- a/couchpotato/core/providers/torrent/yify/__init__.py
+++ b/couchpotato/core/providers/torrent/yify/__init__.py
@@ -8,7 +8,6 @@ config = [{
'groups': [
{
'tab': 'searcher',
- 'subtab': 'providers',
'list': 'torrent_providers',
'name': 'Yify',
'description': 'Free provider, less accurate. Small HD movies, encoded by Yify.',
@@ -19,6 +18,20 @@ config = [{
'type': 'enabler',
'default': 0
},
+ {
+ 'name': 'seed_ratio',
+ 'label': 'Seed ratio',
+ 'type': 'float',
+ 'default': 1,
+ 'description': 'Will not be (re)moved until this seed ratio is met.',
+ },
+ {
+ 'name': 'seed_time',
+ 'label': 'Seed time',
+ 'type': 'int',
+ 'default': 40,
+ 'description': 'Will not be (re)moved until this seed time (in hours) is met.',
+ },
{
'name': 'extra_score',
'advanced': True,
diff --git a/couchpotato/core/providers/trailer/hdtrailers/main.py b/couchpotato/core/providers/trailer/hdtrailers/main.py
index 320a5835..abb91658 100644
--- a/couchpotato/core/providers/trailer/hdtrailers/main.py
+++ b/couchpotato/core/providers/trailer/hdtrailers/main.py
@@ -90,21 +90,18 @@ class HDTrailers(TrailerProvider):
html = BeautifulSoup(data, parse_only = tables)
result_table = html.find('table', attrs = {'class':'bottomTable'})
-
for tr in result_table.find_all('tr'):
trtext = str(tr).lower()
if 'clips' in trtext:
break
- if 'trailer' in trtext and not 'clip' in trtext and provider in trtext:
- nr = 0
+
+ if 'trailer' in trtext and not 'clip' in trtext and provider in trtext and not '3d' in trtext:
if 'trailer' not in tr.find('span', 'standardTrailerName').text.lower():
continue
resolutions = tr.find_all('td', attrs = {'class':'bottomTableResolution'})
for res in resolutions:
- results[str(res.a.contents[0])].insert(0, res.a['href'])
- nr += 1
-
- return results
+ if res.a:
+ results[str(res.a.contents[0])].insert(0, res.a['href'])
except AttributeError:
log.debug('No trailers found in provider %s.', provider)
diff --git a/couchpotato/core/providers/userscript/allocine/main.py b/couchpotato/core/providers/userscript/allocine/main.py
index 8cc889ee..f8ca630d 100644
--- a/couchpotato/core/providers/userscript/allocine/main.py
+++ b/couchpotato/core/providers/userscript/allocine/main.py
@@ -19,9 +19,6 @@ class AlloCine(UserscriptBase):
except:
return
- name = None
- year = None
-
try:
start = data.find('')
end = data.find('', start)
diff --git a/couchpotato/core/providers/userscript/tmdb/main.py b/couchpotato/core/providers/userscript/tmdb/main.py
index 6205851e..cab38fc6 100644
--- a/couchpotato/core/providers/userscript/tmdb/main.py
+++ b/couchpotato/core/providers/userscript/tmdb/main.py
@@ -9,7 +9,7 @@ class TMDB(UserscriptBase):
def getMovie(self, url):
match = re.search('(?P\d+)', url)
- movie = fireEvent('movie.info_by_tmdb', id = match.group('id'), merge = True)
+ movie = fireEvent('movie.info_by_tmdb', identifier = match.group('id'), merge = True)
if movie['imdb']:
return self.getInfo(movie['imdb'])
diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py
index cdf58aa2..61d982f2 100644
--- a/couchpotato/core/settings/__init__.py
+++ b/couchpotato/core/settings/__init__.py
@@ -1,13 +1,10 @@
from __future__ import with_statement
from couchpotato.api import addApiView
from couchpotato.core.event import addEvent, fireEvent
-from couchpotato.core.helpers.encoding import isInt, toUnicode
-from couchpotato.core.helpers.variable import mergeDicts, tryInt
+from couchpotato.core.helpers.encoding import toUnicode
+from couchpotato.core.helpers.variable import mergeDicts, tryInt, tryFloat
from couchpotato.core.settings.model import Properties
import ConfigParser
-import os.path
-import time
-import traceback
class Settings(object):
@@ -75,16 +72,26 @@ class Settings(object):
addEvent('settings.register', self.registerDefaults)
addEvent('settings.save', self.save)
- def registerDefaults(self, section_name, options = {}, save = True):
+ def registerDefaults(self, section_name, options = None, save = True):
+ if not options: options = {}
+
self.addSection(section_name)
+
for option_name, option in options.iteritems():
self.setDefault(section_name, option_name, option.get('default', ''))
+ # Migrate old settings from old location to the new location
+ if option.get('migrate_from'):
+ if self.p.has_option(option.get('migrate_from'), option_name):
+ previous_value = self.p.get(option.get('migrate_from'), option_name)
+ self.p.set(section_name, option_name, previous_value)
+ self.p.remove_option(option.get('migrate_from'), option_name)
+
if option.get('type'):
self.setType(section_name, option_name, option.get('type'))
if save:
- self.save(self)
+ self.save()
def set(self, section, option, value):
return self.p.set(section, option, value)
@@ -122,7 +129,7 @@ class Settings(object):
try:
return self.p.getfloat(section, option)
except:
- return tryInt(self.p.get(section, option))
+ return tryFloat(self.p.get(section, option))
def getUnicode(self, section, option):
value = self.p.get(section, option).decode('unicode_escape')
diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py
index 00ac34e5..f39544bc 100644
--- a/couchpotato/core/settings/model.py
+++ b/couchpotato/core/settings/model.py
@@ -82,6 +82,7 @@ class Movie(Entity):
library = ManyToOne('Library', cascade = 'delete, delete-orphan', single_parent = True)
status = ManyToOne('Status')
profile = ManyToOne('Profile')
+ category = ManyToOne('Category')
releases = OneToMany('Release', cascade = 'all, delete-orphan')
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
@@ -136,7 +137,10 @@ class Release(Entity):
files = ManyToMany('File')
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
- def to_dict(self, deep = {}, exclude = []):
+ def to_dict(self, deep = None, exclude = None):
+ if not exclude: exclude = []
+ if not deep: deep = {}
+
orig_dict = super(Release, self).to_dict(deep = deep, exclude = exclude)
new_info = {}
@@ -199,13 +203,30 @@ class Profile(Entity):
movie = OneToMany('Movie')
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
- def to_dict(self, deep = {}, exclude = []):
+ def to_dict(self, deep = None, exclude = None):
+ if not exclude: exclude = []
+ if not deep: deep = {}
+
orig_dict = super(Profile, self).to_dict(deep = deep, exclude = exclude)
orig_dict['core'] = orig_dict.get('core') or False
orig_dict['hide'] = orig_dict.get('hide') or False
return orig_dict
+class Category(Entity):
+ """"""
+ using_options(order_by = 'order')
+
+ label = Field(Unicode(50))
+ order = Field(Integer, default = 0, index = True)
+ required = Field(Unicode(255))
+ preferred = Field(Unicode(255))
+ ignored = Field(Unicode(255))
+ destination = Field(Unicode(255))
+
+ movie = OneToMany('Movie')
+
+
class ProfileType(Entity):
""""""
using_options(order_by = 'order')
@@ -271,13 +292,6 @@ class Notification(Entity):
data = Field(JsonType)
-class Folder(Entity):
- """Renamer destination folders."""
-
- path = Field(Unicode(255))
- label = Field(Unicode(255))
-
-
class Properties(Entity):
identifier = Field(String(50), index = True)
diff --git a/couchpotato/environment.py b/couchpotato/environment.py
index ac0f729e..0f04d838 100644
--- a/couchpotato/environment.py
+++ b/couchpotato/environment.py
@@ -74,7 +74,7 @@ class Env(object):
s = Env.get('settings')
# Return setting
- if value == None:
+ if value is None:
return s.get(attr, default = default, section = section, type = type)
# Set setting
@@ -86,7 +86,7 @@ class Env(object):
@staticmethod
def prop(identifier, value = None, default = None):
s = Env.get('settings')
- if value == None:
+ if value is None:
v = s.getProperty(identifier)
return v if v else default
diff --git a/couchpotato/runner.py b/couchpotato/runner.py
index 0c0127fa..571023ea 100644
--- a/couchpotato/runner.py
+++ b/couchpotato/runner.py
@@ -1,6 +1,6 @@
from argparse import ArgumentParser
from cache import FileSystemCache
-from couchpotato import KeyHandler
+from couchpotato import KeyHandler, LoginHandler, LogoutHandler
from couchpotato.api import NonBlockHandler, ApiHandler
from couchpotato.core.event import fireEventAsync, fireEvent
from couchpotato.core.helpers.encoding import toUnicode
@@ -83,13 +83,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# Backup before start and cleanup old databases
new_backup = toUnicode(os.path.join(data_dir, 'db_backup', str(int(time.time()))))
-
- # Create path and copy
if not os.path.isdir(new_backup): os.makedirs(new_backup)
- src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
- for src_file in src_files:
- if os.path.isfile(src_file):
- shutil.copy2(src_file, toUnicode(os.path.join(new_backup, os.path.basename(src_file))))
# Remove older backups, keep backups 3 days or at least 3
backups = []
@@ -98,14 +92,31 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
if os.path.isdir(backup):
backups.append(backup)
+ latest_backup = tryInt(os.path.basename(sorted(backups)[-1])) if len(backups) > 0 else 0
+ if latest_backup < time.time() - 3600:
+ # Create path and copy
+ src_files = [options.config_file, db_path, db_path + '-shm', db_path + '-wal']
+ for src_file in src_files:
+ if os.path.isfile(src_file):
+ dst_file = toUnicode(os.path.join(new_backup, os.path.basename(src_file)))
+ shutil.copyfile(src_file, dst_file)
+
+ # Try and copy stats seperately
+ try: shutil.copystat(src_file, dst_file)
+ except: pass
+
total_backups = len(backups)
for backup in backups:
if total_backups > 3:
if tryInt(os.path.basename(backup)) < time.time() - 259200:
- for src_file in src_files:
- b_file = toUnicode(os.path.join(backup, os.path.basename(src_file)))
- if os.path.isfile(b_file):
- os.remove(b_file)
+ for the_file in os.listdir(backup):
+ file_path = os.path.join(backup, the_file)
+ try:
+ if os.path.isfile(file_path):
+ os.remove(file_path)
+ except:
+ raise
+
os.rmdir(backup)
total_backups -= 1
@@ -212,7 +223,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
# app.debug = development
config = {
'use_reloader': reloader,
- 'port': tryInt(Env.setting('port', default = 5000)),
+ 'port': tryInt(Env.setting('port', default = 5050)),
'host': host if host and len(host) > 0 else '0.0.0.0',
'ssl_cert': Env.setting('ssl_cert', default = None),
'ssl_key': Env.setting('ssl_key', default = None),
@@ -224,10 +235,11 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
log_function = lambda x : None,
debug = config['use_reloader'],
gzip = True,
+ cookie_secret = api_key,
+ login_url = '%slogin/' % web_base,
)
Env.set('app', application)
-
# Request handlers
application.add_handlers(".*$", [
(r'%snonblock/(.*)(/?)' % api_base, NonBlockHandler),
@@ -237,18 +249,22 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
(r'%sgetkey(/?)' % web_base, KeyHandler), # Get API key
(r'%s' % api_base, RedirectHandler, {"url": web_base + 'docs/'}), # API docs
+ # Login handlers
+ (r'%slogin(/?)' % web_base, LoginHandler),
+ (r'%slogout(/?)' % web_base, LogoutHandler),
+
# Catch all webhandlers
(r'%s(.*)(/?)' % web_base, WebHandler),
(r'(.*)', WebHandler),
])
# Static paths
- static_path = '%sstatic/' % api_base
+ static_path = '%sstatic/' % web_base
for dir_name in ['fonts', 'images', 'scripts', 'style']:
application.add_handlers(".*$", [
('%s%s/(.*)' % (static_path, dir_name), StaticFileHandler, {'path': toUnicode(os.path.join(base_path, 'couchpotato', 'static', dir_name))})
])
- Env.set('static_path', static_path);
+ Env.set('static_path', static_path)
# Load configs & plugins
diff --git a/couchpotato/static/fonts/Lobster-webfont.eot b/couchpotato/static/fonts/Lobster-webfont.eot
new file mode 100755
index 00000000..56f66aae
Binary files /dev/null and b/couchpotato/static/fonts/Lobster-webfont.eot differ
diff --git a/couchpotato/static/fonts/Lobster-webfont.svg b/couchpotato/static/fonts/Lobster-webfont.svg
new file mode 100755
index 00000000..e4455833
--- /dev/null
+++ b/couchpotato/static/fonts/Lobster-webfont.svg
@@ -0,0 +1,244 @@
+
+
+
\ No newline at end of file
diff --git a/couchpotato/static/fonts/Lobster-webfont.ttf b/couchpotato/static/fonts/Lobster-webfont.ttf
new file mode 100755
index 00000000..4c46e93f
Binary files /dev/null and b/couchpotato/static/fonts/Lobster-webfont.ttf differ
diff --git a/couchpotato/static/fonts/Lobster-webfont.woff b/couchpotato/static/fonts/Lobster-webfont.woff
new file mode 100755
index 00000000..af59caad
Binary files /dev/null and b/couchpotato/static/fonts/Lobster-webfont.woff differ
diff --git a/couchpotato/static/scripts/api.js b/couchpotato/static/scripts/api.js
index 5e507bc1..38d18740 100644
--- a/couchpotato/static/scripts/api.js
+++ b/couchpotato/static/scripts/api.js
@@ -1,7 +1,7 @@
var ApiClass = new Class({
setup: function(options){
- var self = this
+ var self = this;
self.options = options;
},
@@ -13,7 +13,7 @@ var ApiClass = new Class({
return new Request[r_type](Object.merge({
'callbackKey': 'callback_func',
'method': 'get',
- 'url': self.createUrl(type, {'t': randomString()}),
+ 'url': self.createUrl(type, {'t': randomString()})
}, options)).send()
},
@@ -26,4 +26,4 @@ var ApiClass = new Class({
}
});
-window.Api = new ApiClass()
\ No newline at end of file
+window.Api = new ApiClass();
\ No newline at end of file
diff --git a/couchpotato/static/scripts/block.js b/couchpotato/static/scripts/block.js
index 82193ca5..7407b7fe 100644
--- a/couchpotato/static/scripts/block.js
+++ b/couchpotato/static/scripts/block.js
@@ -36,4 +36,4 @@ var BlockBase = new Class({
});
-var Block = BlockBase
\ No newline at end of file
+var Block = BlockBase;
\ No newline at end of file
diff --git a/couchpotato/static/scripts/block/menu.js b/couchpotato/static/scripts/block/menu.js
index 8d315f59..91e29a23 100644
--- a/couchpotato/static/scripts/block/menu.js
+++ b/couchpotato/static/scripts/block/menu.js
@@ -18,11 +18,11 @@ Block.Menu = new Class({
self.button = new Element('a.button' + (self.options.button_class ? '.' + self.options.button_class : ''), {
'events': {
'click': function(){
- self.el.toggleClass('show')
- self.fireEvent(self.el.hasClass('show') ? 'open' : 'close')
+ self.el.toggleClass('show');
+ self.fireEvent(self.el.hasClass('show') ? 'open' : 'close');
if(self.el.hasClass('show')){
- self.el.addEvent('outerClick', self.removeOuterClick.bind(self))
+ self.el.addEvent('outerClick', self.removeOuterClick.bind(self));
this.addEvent('outerClick', function(e){
if(e.target.get('tag') != 'input')
self.removeOuterClick()
@@ -41,7 +41,7 @@ Block.Menu = new Class({
removeOuterClick: function(){
var self = this;
- self.el.removeClass('show')
+ self.el.removeClass('show');
self.el.removeEvents('outerClick');
self.button.removeEvents('outerClick');
@@ -49,8 +49,7 @@ Block.Menu = new Class({
addLink: function(tab, position){
var self = this;
- var el = new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom');
- return el;
+ return new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom');
}
});
\ No newline at end of file
diff --git a/couchpotato/static/scripts/block/navigation.js b/couchpotato/static/scripts/block/navigation.js
index 8389ff9f..f5642df2 100644
--- a/couchpotato/static/scripts/block/navigation.js
+++ b/couchpotato/static/scripts/block/navigation.js
@@ -5,7 +5,6 @@ Block.Navigation = new Class({
create: function(){
var self = this;
- var settings_added = false;
self.el = new Element('div.navigation').adopt(
self.foldout = new Element('a.foldout.icon2.menu', {
'events': {
@@ -28,7 +27,7 @@ Block.Navigation = new Class({
'duration': 100
}
})
- )
+ );
new ScrollSpy({
min: 400,
@@ -58,7 +57,7 @@ Block.Navigation = new Class({
},
- toggleMenu: function(e){
+ toggleMenu: function(){
var self = this,
body = $(document.body),
html = body.getParent();
diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js
index 8e6ccede..dcd0f7bd 100644
--- a/couchpotato/static/scripts/couchpotato.js
+++ b/couchpotato/static/scripts/couchpotato.js
@@ -1,4 +1,4 @@
-var CouchPotato = new Class({
+var CouchPotato = new Class({
Implements: [Events, Options],
@@ -15,7 +15,7 @@ var CouchPotato = new Class({
var self = this;
self.setOptions(options);
- self.c = $(document.body)
+ self.c = $(document.body);
self.route = new Route(self.defaults);
@@ -48,7 +48,6 @@ var CouchPotato = new Class({
},
pushState: function(e){
- var self = this;
if((!e.meta && Browser.Platform.mac) || (!e.control && !Browser.Platform.mac)){
(e).preventDefault();
var url = e.target.get('href');
@@ -56,6 +55,10 @@ var CouchPotato = new Class({
History.push(url);
}
},
+
+ isMac: function(){
+ return Browser.Platform.mac
+ },
createLayout: function(){
var self = this;
@@ -107,11 +110,11 @@ var CouchPotato = new Class({
'click': self.shutdownQA.bind(self)
}
})
- ]
+ ];
setting_links.each(function(a){
self.block.more.addLink(a)
- })
+ });
new ScrollSpy({
@@ -129,7 +132,7 @@ var CouchPotato = new Class({
var self = this;
Object.each(Page, function(page_class, class_name){
- pg = new Page[class_name](self, {});
+ var pg = new Page[class_name](self, {});
self.pages[class_name] = pg;
$(pg).inject(self.content);
@@ -152,7 +155,7 @@ var CouchPotato = new Class({
return;
if(self.current_page)
- self.current_page.hide()
+ self.current_page.hide();
try {
var page = self.pages[page_name] || self.pages.Home;
@@ -179,14 +182,14 @@ var CouchPotato = new Class({
shutdown: function(){
var self = this;
- self.blockPage('You have shutdown. This is what suppose to happen ;)');
+ self.blockPage('You have shutdown. This is what is supposed to happen ;)');
Api.request('app.shutdown', {
'onComplete': self.blockPage.bind(self)
});
self.checkAvailable(1000);
},
- shutdownQA: function(e){
+ shutdownQA: function(){
var self = this;
var q = new Question('Are you sure you want to shutdown CouchPotato?', '', [{
@@ -235,7 +238,7 @@ var CouchPotato = new Class({
checkForUpdate: function(onComplete){
var self = this;
- Updater.check(onComplete)
+ Updater.check(onComplete);
self.blockPage('Please wait. If this takes too long, something must have gone wrong.', 'Checking for updates');
self.checkAvailable(3000);
@@ -253,7 +256,7 @@ var CouchPotato = new Class({
},
'onSuccess': function(){
if(onAvailable)
- onAvailable()
+ onAvailable();
self.unBlockPage();
self.fireEvent('reload');
}
@@ -267,7 +270,6 @@ var CouchPotato = new Class({
self.unBlockPage();
- var body = $(document.body);
self.mask = new Element('div.mask').adopt(
new Element('div').adopt(
new Element('h1', {'text': title || 'Unavailable'}),
@@ -324,7 +326,7 @@ var CouchPotato = new Class({
'target': '',
'events': {
'click': function(e){
- (e).stop()
+ (e).stop();
alert('Drag it to your bookmark ;)')
}
}
@@ -347,35 +349,35 @@ var Route = new Class({
params: {},
initialize: function(defaults){
- var self = this
+ var self = this;
self.defaults = defaults
},
parse: function(){
var self = this;
- var rep = function(pa){
+ var rep = function (pa) {
return pa.replace(Api.getOption('url'), '/').replace(App.getOption('base_url'), '/')
- }
+ };
- var path = rep(History.getPath())
+ var path = rep(History.getPath());
if(path == '/' && location.hash){
path = rep(location.hash.replace('#', '/'))
}
- self.current = path.replace(/^\/+|\/+$/g, '')
- var url = self.current.split('/')
+ self.current = path.replace(/^\/+|\/+$/g, '');
+ var url = self.current.split('/');
- self.page = (url.length > 0) ? url.shift() : self.defaults.page
- self.action = (url.length > 0) ? url.shift() : self.defaults.action
+ self.page = (url.length > 0) ? url.shift() : self.defaults.page;
+ self.action = (url.length > 0) ? url.shift() : self.defaults.action;
self.params = Object.merge({}, self.defaults.params);
if(url.length > 1){
- var key
+ var key;
url.each(function(el, nr){
if(nr%2 == 0)
- key = el
+ key = el;
else if(key) {
- self.params[key] = el
+ self.params[key] = el;
key = null
}
})
@@ -483,8 +485,8 @@ function randomString(length, extra) {
var comparer = function(a, b) {
for (var i = 0, l = keyPaths.length; i < l; i++) {
- aVal = valueOf(a, keyPaths[i].path);
- bVal = valueOf(b, keyPaths[i].path);
+ var aVal = valueOf(a, keyPaths[i].path),
+ bVal = valueOf(b, keyPaths[i].path);
if (aVal > bVal) return keyPaths[i].sign;
if (aVal < bVal) return -keyPaths[i].sign;
}
@@ -525,4 +527,4 @@ var createSpinner = function(target, options){
}, options);
return new Spinner(opts).spin(target);
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js
index 1af800e8..58ba5acd 100644
--- a/couchpotato/static/scripts/page.js
+++ b/couchpotato/static/scripts/page.js
@@ -12,7 +12,7 @@ var PageBase = new Class({
initialize: function(options) {
var self = this;
- self.setOptions(options)
+ self.setOptions(options);
// Create main page container
self.el = new Element('div.page.'+self.name);
@@ -74,4 +74,4 @@ var PageBase = new Class({
}
});
-var Page = {}
+var Page = {};
diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js
index 71270de7..3efa3933 100644
--- a/couchpotato/static/scripts/page/about.js
+++ b/couchpotato/static/scripts/page/about.js
@@ -13,7 +13,7 @@ var AboutSettingTab = new Class({
addSettings: function(){
var self = this;
- self.settings = App.getPage('Settings')
+ self.settings = App.getPage('Settings');
self.settings.addEvent('create', function(){
var tab = self.settings.createTab('about', {
'label': 'About',
@@ -72,7 +72,7 @@ var AboutSettingTab = new Class({
);
if(!self.fillVersion(Updater.getInfo()))
- Updater.addEvent('loaded', self.fillVersion.bind(self))
+ Updater.addEvent('loaded', self.fillVersion.bind(self));
self.settings.createGroup({
'name': 'Help Support CouchPotato'
diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js
index 01344ad8..b93db5bd 100644
--- a/couchpotato/static/scripts/page/home.js
+++ b/couchpotato/static/scripts/page/home.js
@@ -5,7 +5,7 @@ Page.Home = new Class({
name: 'home',
title: 'Manage new stuff for things and such',
- indexAction: function(param){
+ indexAction: function () {
var self = this;
if(self.soon_list){
@@ -14,10 +14,24 @@ Page.Home = new Class({
self.available_list.update();
self.late_list.update();
- return
+ return;
}
- // Snatched
+ self.chain = new Chain();
+ self.chain.chain(
+ self.createAvailable.bind(self),
+ self.createSoon.bind(self),
+ self.createSuggestions.bind(self),
+ self.createLate.bind(self)
+ );
+
+ self.chain.callChain();
+
+ },
+
+ createAvailable: function(){
+ var self = this;
+
self.available_list = new MovieList({
'navigation': false,
'identifier': 'snatched',
@@ -40,9 +54,19 @@ Page.Home = new Class({
'filter': {
'release_status': 'snatched,available'
},
- 'limit': null
+ 'limit': null,
+ 'onLoaded': function(){
+ self.chain.callChain();
+ }
});
+ $(self.available_list).inject(self.el);
+
+ },
+
+ createSoon: function(){
+ var self = this;
+
// Coming Soon
self.soon_list = new MovieList({
'navigation': false,
@@ -50,10 +74,6 @@ Page.Home = new Class({
'limit': 12,
'title': 'Available soon',
'description': 'These are being searched for and should be available soon as they will be released on DVD in the next few weeks.',
- 'on_empty_element': new Element('div').adopt(
- new Element('h2', {'text': 'Available soon'}),
- new Element('span', {'text': 'There are no movies available soon. Add some movies, so you have something to watch later.'})
- ),
'filter': {
'random': true
},
@@ -61,7 +81,10 @@ Page.Home = new Class({
'load_more': false,
'view': 'thumbs',
'force_view': true,
- 'api_call': 'dashboard.soon'
+ 'api_call': 'dashboard.soon',
+ 'onLoaded': function(){
+ self.chain.callChain();
+ }
});
// Make all thumbnails the same size
@@ -99,10 +122,30 @@ Page.Home = new Class({
images.setStyle('height', highest);
}).delay(300);
});
+
});
+ $(self.soon_list).inject(self.el);
+
+ },
+
+ createSuggestions: function(){
+ var self = this;
+
// Suggest
- self.suggestion_list = new SuggestList();
+ self.suggestion_list = new SuggestList({
+ 'onLoaded': function(){
+ self.chain.callChain();
+ }
+ });
+
+ $(self.suggestion_list).inject(self.el);
+
+
+ },
+
+ createLate: function(){
+ var self = this;
// Still not available
self.late_list = new MovieList({
@@ -110,7 +153,7 @@ Page.Home = new Class({
'identifier': 'late',
'limit': 50,
'title': 'Still not available',
- 'description': 'Try another quality profile or maybe add more providers in Settings.',
+ 'description': 'Try another quality profile or maybe add more providers in Settings.',
'filter': {
'late': true
},
@@ -118,25 +161,14 @@ Page.Home = new Class({
'load_more': false,
'view': 'list',
'actions': [MA.IMDB, MA.Trailer, MA.Edit, MA.Refresh, MA.Delete],
- 'api_call': 'dashboard.soon'
+ 'api_call': 'dashboard.soon',
+ 'onLoaded': function(){
+ self.chain.callChain();
+ }
});
- self.el.adopt(
- $(self.available_list),
- $(self.soon_list),
- $(self.suggestion_list),
- $(self.late_list)
- );
-
- // Recent
- // Snatched
- // Renamed
- // Added
-
- // Free space
-
- // Shortcuts
+ $(self.late_list).inject(self.el);
}
-})
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/couchpotato/static/scripts/page/manage.js b/couchpotato/static/scripts/page/manage.js
index aef1f3c7..4827f51d 100644
--- a/couchpotato/static/scripts/page/manage.js
+++ b/couchpotato/static/scripts/page/manage.js
@@ -5,7 +5,7 @@ Page.Manage = new Class({
name: 'manage',
title: 'Do stuff to your existing movies!',
- indexAction: function(param){
+ indexAction: function(){
var self = this;
if(!self.list){
@@ -73,7 +73,7 @@ Page.Manage = new Class({
'data': {
'full': +full
}
- })
+ });
self.startProgressInterval();
@@ -86,9 +86,12 @@ Page.Manage = new Class({
self.progress_interval = setInterval(function(){
- Api.request('manage.progress', {
+ if(self.progress_request && self.progress_request.running)
+ return;
+
+ self.update_in_progress = true;
+ self.progress_request = Api.request('manage.progress', {
'onComplete': function(json){
- self.update_in_progress = true;
if(!json || !json.progress){
clearInterval(self.progress_interval);
@@ -99,8 +102,13 @@ Page.Manage = new Class({
}
}
else {
+
+ // Don't add loader when page is loading still
+ if(!self.list.navigation)
+ return;
+
if(!self.progress_container)
- self.progress_container = new Element('div.progress').inject(self.list.navigation, 'after')
+ self.progress_container = new Element('div.progress').inject(self.list.navigation, 'after');
self.progress_container.empty();
diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js
index 0d7ebe7f..68b41d0a 100644
--- a/couchpotato/static/scripts/page/settings.js
+++ b/couchpotato/static/scripts/page/settings.js
@@ -46,16 +46,16 @@ Page.Settings = new Class({
var t = self.tabs[tab_name] || self.tabs[self.action] || self.tabs.general;
// Subtab
- var subtab = null
+ var subtab = null;
Object.each(self.params, function(param, subtab_name){
subtab = subtab_name;
- })
+ });
self.el.getElements('li.'+c+' , .tab_content.'+c).each(function(active){
active.removeClass(c);
});
- if (t.subtabs[subtab]){
+ if(t.subtabs[subtab]){
t.tab[a](c);
t.subtabs[subtab].tab[a](c);
t.subtabs[subtab].content[a](c);
@@ -87,7 +87,7 @@ Page.Settings = new Class({
self.data = json;
onComplete(json);
}
- })
+ });
return self.data;
},
@@ -139,7 +139,7 @@ Page.Settings = new Class({
Object.each(json.options, function(section, section_name){
section['section_name'] = section_name;
options.include(section);
- })
+ });
options.sort(function(a, b){
return (a.order || 100) - (b.order || 100)
@@ -156,13 +156,13 @@ Page.Settings = new Class({
// Create tab
if(!self.tabs[group.tab] || !self.tabs[group.tab].groups)
self.createTab(group.tab, {});
- var content_container = self.tabs[group.tab].content
+ var content_container = self.tabs[group.tab].content;
// Create subtab
if(group.subtab){
- if (!self.tabs[group.tab].subtabs[group.subtab])
- self.createSubTab(group.subtab, {}, self.tabs[group.tab], group.tab);
- var content_container = self.tabs[group.tab].subtabs[group.subtab].content
+ if(!self.tabs[group.tab].subtabs[group.subtab])
+ self.createSubTab(group.subtab, group, self.tabs[group.tab], group.tab);
+ content_container = self.tabs[group.tab].subtabs[group.subtab].content
}
if(group.list && !self.lists[group.list]){
@@ -170,12 +170,10 @@ Page.Settings = new Class({
}
// Create the group
- if(!self.tabs[group.tab].groups[group.name]){
- var group_el = self.createGroup(group)
+ if(!self.tabs[group.tab].groups[group.name])
+ self.tabs[group.tab].groups[group.name] = self.createGroup(group)
.inject(group.list ? self.lists[group.list] : content_container)
.addClass('section_'+section_name);
- self.tabs[group.tab].groups[group.name] = group_el;
- }
// Create list if needed
if(group.type && group.type == 'list'){
@@ -208,9 +206,9 @@ Page.Settings = new Class({
var self = this;
if(self.tabs[tab_name] && self.tabs[tab_name].tab)
- return self.tabs[tab_name].tab
+ return self.tabs[tab_name].tab;
- var label = tab.label || (tab.name || tab_name).capitalize()
+ var label = tab.label || (tab.name || tab_name).capitalize();
var tab_el = new Element('li.t_'+tab_name).adopt(
new Element('a', {
'href': App.createUrl(self.name+'/'+tab_name),
@@ -221,14 +219,14 @@ Page.Settings = new Class({
if(!self.tabs[tab_name])
self.tabs[tab_name] = {
'label': label
- }
+ };
self.tabs[tab_name] = Object.merge(self.tabs[tab_name], {
'tab': tab_el,
'subtabs': {},
- 'content': new Element('div.tab_content.tab_'+tab_name).inject(self.containers),
+ 'content': new Element('div.tab_content.tab_' + tab_name).inject(self.containers),
'groups': {}
- })
+ });
return self.tabs[tab_name]
@@ -238,12 +236,12 @@ Page.Settings = new Class({
var self = this;
if(parent_tab.subtabs[tab_name])
- return parent_tab.subtabs[tab_name]
+ return parent_tab.subtabs[tab_name];
if(!parent_tab.subtabs_el)
parent_tab.subtabs_el = new Element('ul.subtabs').inject(parent_tab.tab);
- var label = tab.label || (tab.name || tab_name.replace('_', ' ')).capitalize()
+ var label = tab.subtab_label || tab_name.replace('_', ' ').capitalize();
var tab_el = new Element('li.t_'+tab_name).adopt(
new Element('a', {
'href': App.createUrl(self.name+'/'+parent_tab_name+'/'+tab_name),
@@ -254,7 +252,7 @@ Page.Settings = new Class({
if(!parent_tab.subtabs[tab_name])
parent_tab.subtabs[tab_name] = {
'label': label
- }
+ };
parent_tab.subtabs[tab_name] = Object.merge(parent_tab.subtabs[tab_name], {
'tab': tab_el,
@@ -267,21 +265,17 @@ Page.Settings = new Class({
},
createGroup: function(group){
- var self = this;
-
- var group_el = new Element('fieldset', {
+ return new Element('fieldset', {
'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '')
}).adopt(
- new Element('h2', {
- 'text': group.label || (group.name).capitalize()
- }).adopt(
- new Element('span.hint', {
- 'html': group.description || ''
- })
- )
- )
-
- return group_el
+ new Element('h2', {
+ 'text': group.label || (group.name).capitalize()
+ }).adopt(
+ new Element('span.hint', {
+ 'html': group.description || ''
+ })
+ )
+ );
},
createList: function(content_container){
@@ -299,12 +293,12 @@ var OptionBase = new Class({
Implements: [Options, Events],
klass: 'textInput',
- focused_class : 'focused',
+ focused_class: 'focused',
save_on_change: true,
initialize: function(section, name, value, options){
- var self = this
- self.setOptions(options)
+ var self = this;
+ self.setOptions(options);
self.section = section;
self.name = name;
@@ -329,11 +323,12 @@ var OptionBase = new Class({
* Create the element
*/
createBase: function(){
- var self = this
- self.el = new Element('div.ctrlHolder')
+ var self = this;
+ self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name)
},
- create: function(){},
+ create: function(){
+ },
createLabel: function(){
var self = this;
@@ -343,7 +338,7 @@ var OptionBase = new Class({
},
setAdvanced: function(){
- this.el.addClass(this.options.advanced ? 'advanced': '')
+ this.el.addClass(this.options.advanced ? 'advanced' : '')
},
createHint: function(){
@@ -354,7 +349,8 @@ var OptionBase = new Class({
}).inject(self.el);
},
- afterInject: function(){},
+ afterInject: function(){
+ },
// Element has changed, do something
changed: function(){
@@ -407,7 +403,7 @@ var OptionBase = new Class({
postName: function(){
var self = this;
- return self.section +'['+self.name+']';
+ return self.section + '[' + self.name + ']';
},
getValue: function(){
@@ -427,16 +423,16 @@ var OptionBase = new Class({
toElement: function(){
return this.el;
}
-})
+});
-var Option = {}
+var Option = {};
Option.String = new Class({
Extends: OptionBase,
type: 'string',
create: function(){
- var self = this
+ var self = this;
self.el.adopt(
self.createLabel(),
@@ -458,21 +454,21 @@ Option.Dropdown = new Class({
Extends: OptionBase,
create: function(){
- var self = this
+ var self = this;
self.el.adopt(
self.createLabel(),
self.input = new Element('select', {
'name': self.postName()
})
- )
+ );
Object.each(self.options.values, function(value){
new Element('option', {
'text': value[0],
'value': value[1]
}).inject(self.input)
- })
+ });
self.input.set('value', self.getSettingValue());
@@ -491,7 +487,7 @@ Option.Checkbox = new Class({
create: function(){
var self = this;
- var randomId = 'r-'+randomString()
+ var randomId = 'r-' + randomString();
self.el.adopt(
self.createLabel().set('for', randomId),
@@ -520,8 +516,8 @@ Option.Password = new Class({
create: function(){
var self = this;
- self.parent()
- self.input.set('type', 'password')
+ self.parent();
+ self.input.set('type', 'password');
self.input.addEvent('focus', function(){
self.input.set('value', '')
@@ -570,9 +566,9 @@ Option.Enabler = new Class({
afterInject: function(){
var self = this;
- self.parentFieldset = self.el.getParent('fieldset').addClass('enabler')
+ self.parentFieldset = self.el.getParent('fieldset').addClass('enabler');
self.parentList = self.parentFieldset.getParent('.option_list');
- self.el.inject(self.parentFieldset, 'top')
+ self.el.inject(self.parentFieldset, 'top');
self.checkState()
}
@@ -622,7 +618,7 @@ Option.Directory = new Class({
self.getDirs()
},
- previousDirectory: function(e){
+ previousDirectory: function(){
var self = this;
self.selectDirectory(self.getParentDir())
@@ -697,8 +693,8 @@ Option.Directory = new Class({
self.initial_directory = self.input.get('text');
- self.getDirs()
- self.browser.show()
+ self.getDirs();
+ self.browser.show();
self.el.addEvent('outerClick', self.hideBrowser.bind(self))
},
@@ -707,11 +703,11 @@ Option.Directory = new Class({
(e).preventDefault();
if(save)
- self.save()
+ self.save();
else
self.input.set('text', self.initial_directory);
- self.browser.hide()
+ self.browser.hide();
self.el.removeEvents('outerClick')
},
@@ -732,11 +728,11 @@ Option.Directory = new Class({
var prev_dirname = self.getCurrentDirname(previous_dir);
if(previous_dir == json.home)
prev_dirname = 'Home';
- else if (previous_dir == '/' && json.platform == 'nt')
+ else if(previous_dir == '/' && json.platform == 'nt')
prev_dirname = 'Computer';
- self.back_button.set('data-value', previous_dir)
- self.back_button.set('html', '« '+prev_dirname)
+ self.back_button.set('data-value', previous_dir);
+ self.back_button.set('html', '« ' + prev_dirname);
self.back_button.show()
}
else {
@@ -798,8 +794,6 @@ Option.Directory = new Class({
},
getCurrentDirname: function(dir){
- var self = this;
-
var dir_split = dir.split(Api.getOption('path_sep'));
return dir_split[dir_split.length-2] || Api.getOption('path_sep')
@@ -848,7 +842,7 @@ Option.Directories = new Class({
var parent = self.el.getParent('fieldset');
var dirs = parent.getElements('.multi_directory');
if(dirs.length == 0)
- $(dir).inject(parent)
+ $(dir).inject(parent);
else
$(dir).inject(dirs.getLast(), 'after');
@@ -885,7 +879,7 @@ Option.Directories = new Class({
saveItems: function(){
var self = this;
- var dirs = []
+ var dirs = [];
self.directories.each(function(dir){
if(dir.getValue()){
$(dir).removeClass('is_empty');
@@ -957,7 +951,7 @@ Option.Choice = new Class({
}).inject(self.input, 'after');
self.el.addClass('tag_input');
- var mtches = []
+ var mtches = [];
if(matches)
matches.each(function(match, mnr){
var pos = value.indexOf(match),
@@ -1037,7 +1031,7 @@ Option.Choice = new Class({
var prev_index = self.tags.indexOf(from_tag)-1;
if(prev_index >= 0)
- self.tags[prev_index].selectFrom('right')
+ self.tags[prev_index].selectFrom('right');
else
from_tag.focus();
@@ -1049,7 +1043,7 @@ Option.Choice = new Class({
var next_index = self.tags.indexOf(from_tag)+1;
if(next_index < self.tags.length)
- self.tags[next_index].selectFrom('left')
+ self.tags[next_index].selectFrom('left');
else
from_tag.focus();
},
@@ -1139,7 +1133,7 @@ Option.Choice.Tag = new Class({
if(e.key == 'left' && current_caret_pos == self.last_caret_pos){
self.fireEvent('goLeft');
}
- else if (e.key == 'right' && self.last_caret_pos === current_caret_pos){
+ else if(e.key == 'right' && self.last_caret_pos === current_caret_pos){
self.fireEvent('goRight');
}
self.last_caret_pos = self.input.getCaretPosition();
@@ -1195,11 +1189,11 @@ Option.Choice.Tag = new Class({
self.fireEvent('goRight');
this.destroy();
}
- else if (e.key == 'left'){
+ else if(e.key == 'left'){
self.fireEvent('goLeft');
this.destroy();
}
- else if (e.key == 'backspace'){
+ else if(e.key == 'backspace'){
self.del();
this.destroy();
self.fireEvent('goLeft');
@@ -1213,7 +1207,7 @@ Option.Choice.Tag = new Class({
'top': -200
}
});
- self.el.adopt(temp_input)
+ self.el.adopt(temp_input);
temp_input.focus();
}
},
@@ -1266,10 +1260,10 @@ Option.Combined = new Class({
self.fieldset = self.input.getParent('fieldset');
self.combined_list = new Element('div.combined_table').inject(self.fieldset.getElement('h2'), 'after');
- self.values = {}
- self.inputs = {}
- self.items = []
- self.labels = {}
+ self.values = {};
+ self.inputs = {};
+ self.items = [];
+ self.labels = {};
self.options.combine.each(function(name){
@@ -1277,7 +1271,7 @@ Option.Combined = new Class({
var values = self.inputs[name].get('value').split(',');
values.each(function(value, nr){
- if (!self.values[nr]) self.values[nr] = {};
+ if(!self.values[nr]) self.values[nr] = {};
self.values[nr][name] = value.trim();
});
@@ -1286,19 +1280,18 @@ Option.Combined = new Class({
});
- var head = new Element('div.head').inject(self.combined_list)
+ var head = new Element('div.head').inject(self.combined_list);
Object.each(self.inputs, function(input, name){
- self.labels[name] = input.getPrevious().get('text')
+ self.labels[name] = input.getPrevious().get('text');
new Element('abbr', {
'class': name,
- 'text': self.labels[name],
- //'title': input.getNext().get('text')
+ 'text': self.labels[name]
}).inject(head)
- })
+ });
- Object.each(self.values, function(item, nr){
+ Object.each(self.values, function(item){
self.createItem(item);
});
@@ -1316,7 +1309,7 @@ Option.Combined = new Class({
self.items.each(function(ctrl_holder){
var empty_count = 0;
self.options.combine.each(function(name){
- var input = ctrl_holder.getElement('input.'+name)
+ var input = ctrl_holder.getElement('input.' + name);
if(input.get('value') == '' || input.get('type') == 'checkbox')
empty_count++
});
@@ -1338,7 +1331,7 @@ Option.Combined = new Class({
value_empty = 0;
self.options.combine.each(function(name){
- var value = values[name] || ''
+ var value = values[name] || '';
if(name.indexOf('use') != -1){
var checkbox = new Element('input[type=checkbox].inlay.'+name, {
@@ -1375,7 +1368,7 @@ Option.Combined = new Class({
'events': {
'click': self.deleteCombinedItem.bind(self)
}
- }).inject(item)
+ }).inject(item);
self.items.include(item);
@@ -1386,7 +1379,7 @@ Option.Combined = new Class({
var self = this;
- var temp = {}
+ var temp = {};
self.items.each(function(item, nr){
self.options.combine.each(function(name){
var input = item.getElement('input.'+name);
diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js
index eabd1465..98a676c8 100644
--- a/couchpotato/static/scripts/page/wanted.js
+++ b/couchpotato/static/scripts/page/wanted.js
@@ -5,7 +5,7 @@ Page.Wanted = new Class({
name: 'wanted',
title: 'Gimmy gimmy gimmy!',
- indexAction: function(param){
+ indexAction: function(){
var self = this;
if(!self.wanted){
@@ -35,12 +35,12 @@ Page.Wanted = new Class({
},
- doFullSearch: function(full){
+ doFullSearch: function(){
var self = this;
if(!self.search_in_progress){
- Api.request('searcher.full_search');
+ Api.request('movie.searcher.full_search');
self.startProgressInterval();
}
@@ -53,16 +53,16 @@ Page.Wanted = new Class({
var start_text = self.manual_search.get('text');
self.progress_interval = setInterval(function(){
if(self.search_progress && self.search_progress.running) return;
- self.search_progress = Api.request('searcher.progress', {
+ self.search_progress = Api.request('movie.searcher.progress', {
'onComplete': function(json){
self.search_in_progress = true;
- if(!json.progress){
+ if(!json.movie){
clearInterval(self.progress_interval);
self.search_in_progress = false;
self.manual_search.set('text', start_text);
}
else {
- var progress = json.progress;
+ var progress = json.movie;
self.manual_search.set('text', 'Searching.. (' + (((progress.total-progress.to_go)/progress.total)*100).round() + '%)');
}
}
diff --git a/couchpotato/static/style/api.css b/couchpotato/static/style/api.css
index c6354098..0c9f0f08 100644
--- a/couchpotato/static/style/api.css
+++ b/couchpotato/static/style/api.css
@@ -1,6 +1,5 @@
html {
- font-size: 12px;
- line-height: 1.5;
+ line-height: 1.5;
font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
font-size: 14px;
}
diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css
index ae461c43..0ade5182 100644
--- a/couchpotato/static/style/main.css
+++ b/couchpotato/static/style/main.css
@@ -127,6 +127,8 @@ body > .spinner, .mask{
line-height: 1;
border-radius: 2px;
cursor: pointer;
+ border: none;
+ -webkit-appearance: none;
}
.button.red { background-color: #ff0000; }
.button.green { background-color: #2aa300; }
@@ -142,7 +144,7 @@ body > .spinner, .mask{
.icon.download { background-image: url('../images/icon.download.png'); }
.icon.edit { background-image: url('../images/icon.edit.png'); }
.icon.completed { background-image: url('../images/icon.check.png'); }
-.icon.folder { background-image: url('../images/icon.folder.png'); }
+.icon.folder { background-image: url('../images/icon.folder.gif'); }
.icon.imdb { background-image: url('../images/icon.imdb.png'); }
.icon.refresh { background-image: url('../images/icon.refresh.png'); }
.icon.readd { background-image: url('../images/icon.readd.png'); }
@@ -199,14 +201,22 @@ body > .spinner, .mask{
top: -3px;
}
.icon2.menu:before {
- content: "\e076 \e076 \e076";
+ content: "\e076\00a0 \e076\00a0 \e076\00a0";
line-height: 6px;
transform: scaleX(2);
width: 20px;
font-size: 10px;
display: inline-block;
vertical-align: middle;
+ word-wrap: break-word;
+ text-align:center;
+ margin-left: 5px;
}
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
+ .icon2.menu:before {
+ margin-top: -7px;
+ }
+ }
/*** Navigation ***/
.header {
@@ -257,14 +267,14 @@ body > .spinner, .mask{
.header .logo {
display: inline-block;
- font-size: 1.75em;
- padding: 15px 30px 0 15px;
+ font-size: 3em;
+ padding: 4px 30px 0 15px;
height: 100%;
- vertical-align: middle;
- border-right: 1px solid rgba(255,255,255,.07);
+ border-right: 1px solid rgba(255,255,255,.07);
color: #FFF;
font-weight: normal;
vertical-align: top;
+ font-family: Lobster;
}
@media all and (max-width: 480px) {
@@ -275,6 +285,7 @@ body > .spinner, .mask{
.header .logo {
padding-top: 7px;
border: 0;
+ font-size: 1.7em;
}
}
@@ -489,7 +500,6 @@ body > .spinner, .mask{
display: block;
font-size: .85em;
color: #aaa;
- text-align: ;
}
.header .notification_menu li .more {
@@ -606,7 +616,7 @@ body > .spinner, .mask{
.onlay, .inlay .selected, .inlay:not(.reversed) > li:hover, .inlay > li.active, .inlay.reversed > li {
border-radius:3px;
border: 1px solid #252930;
- box-shadow: inset 0 1px 0px rgba(255,255,255,0.20);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.20);
background: rgb(55,62,74);
background-image: linear-gradient(
0,
@@ -729,7 +739,7 @@ body > .spinner, .mask{
.more_menu .wrapper li .separator {
border-bottom: 1px solid rgba(0,0,0,.1);
display: block;
- height: 1;
+ height: 1px;
margin: 5px 0;
}
@@ -790,6 +800,73 @@ body > .spinner, .mask{
right: 0;
color: #FFF;
}
+
+/*** Login ***/
+.page.login {
+ display: block;
+}
+
+ .login h1 {
+ padding: 0 0 10px;
+ font-size: 60px;
+ font-family: Lobster;
+ font-weight: normal;
+ }
+
+ .login form {
+ padding: 0;
+ height: 300px;
+ width: 400px;
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ margin: -200px 0 0 -200px;
+ }
+ @media all and (max-width: 480px) {
+
+ .login form {
+ padding: 0;
+ height: 300px;
+ width: 90%;
+ position: absolute;
+ left: 5%;
+ top: 10px;
+ margin: 0;
+ }
+
+ }
+
+ .page.login .ctrlHolder {
+ padding: 0;
+ margin: 0 0 20px;
+ }
+ .page.login .ctrlHolder:hover {
+ background: none;
+ }
+
+ .page.login input[type=text],
+ .page.login input[type=password] {
+ width: 100% !important;
+ font-size: 25px;
+ padding: 14px !important;
+ }
+
+ .page.login .remember_me {
+ font-size: 15px;
+ float: left;
+ width: 150px;
+ padding: 20px 0;
+ }
+
+ .page.login .remember_me .check {
+ margin: 5px 5px 0 0;
+ }
+
+ .page.login .button {
+ font-size: 25px;
+ padding: 20px;
+ float: right;
+ }
/* Fonts */
@font-face {
@@ -848,5 +925,15 @@ body > .spinner, .mask{
url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg');
font-weight: bold;
font-style: italic;
+}
+@font-face {
+ font-family: 'Lobster';
+ src: url('../fonts/Lobster-webfont.eot');
+ src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fonts/Lobster-webfont.woff') format('woff'),
+ url('../fonts/Lobster-webfont.ttf') format('truetype'),
+ url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg');
+ font-weight: normal;
+ font-style: normal;
}
\ No newline at end of file
diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css
index 132d9c53..61d5239f 100644
--- a/couchpotato/static/style/settings.css
+++ b/couchpotato/static/style/settings.css
@@ -90,7 +90,7 @@
padding: 0 9px 10px 30px;
margin: 0;
border-bottom: 1px solid #333;
- box-shadow: 0 1px 0px rgba(255,255,255, 0.15);
+ box-shadow: 0 1px 0 rgba(255,255,255, 0.15);
}
.page fieldset h2 .hint {
font-size: 12px;
@@ -107,10 +107,8 @@
.page fieldset > .ctrlHolder:first-child {
display: block;
padding: 0;
- width: auto;
- margin: 0;
position: relative;
- margin-bottom: -23px;
+ margin: 0 0 -23px;
border: none;
width: 20px;
}
@@ -132,12 +130,11 @@
.page .ctrlHolder .formHint {
width: 47%;
margin: -18px 0;
- padding: 0;
- color: #fff !important;
+ color: #fff !important;
display: inline-block;
vertical-align: middle;
- padding-left: 2%;
- line-height: 14px;
+ padding: 0 0 0 2%;
+ line-height: 14px;
}
.page .check {
@@ -219,7 +216,7 @@
font-weight: bold;
border: none;
border-top: 1px solid rgba(255,255,255, 0.15);
- box-shadow: 0 -1px 0px #333;
+ box-shadow: 0 -1px 0 #333;
margin: 0;
padding: 10px 0 5px 25px;
}
@@ -308,7 +305,7 @@
border-bottom: 6px solid #5c697b;
display: block;
position: absolute;
- width: 0px;
+ width: 0;
margin: -6px 0 0 45%;
}
@@ -688,7 +685,6 @@
}
.group_userscript .bookmarklet {
- display: block;
display: block;
float: left;
padding: 20px 15px 0 25px;
diff --git a/couchpotato/static/style/uniform.generic.css b/couchpotato/static/style/uniform.generic.css
index e70a9158..8ac41363 100644
--- a/couchpotato/static/style/uniform.generic.css
+++ b/couchpotato/static/style/uniform.generic.css
@@ -92,9 +92,8 @@
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
- -o-border-radius: 4px;
- -khtml-border-radius: 4px;
- }
+
+ }
.uniForm #errorMsg h3{} /* Feel free to use a heading level suitable to your page structure */
.uniForm #errorMsg ol{ margin: 0 0 1.5em 0; padding: 0; }
.uniForm #errorMsg ol li{ margin: 0 0 3px 1.5em; padding: 7px; background: #f6bec1; position: relative; font-size: .85em;
@@ -102,9 +101,8 @@
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
- -o-border-radius: 4px;
- -khtml-border-radius: 4px;
- }
+
+ }
.uniForm .ctrlHolder.error,
.uniForm .ctrlHolder.focused.error{ background: #ffdfdf; border: 1px solid #f3afb5;
@@ -112,9 +110,8 @@
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
- -o-border-radius: 4px;
- -khtml-border-radius: 4px;
- }
+
+ }
.uniForm .ctrlHolder.error input.error,
.uniForm .ctrlHolder.error select.error,
.uniForm .ctrlHolder.error textarea.error{ color: #af4c4c; margin: 0 0 6px 0; padding: 4px; }
@@ -125,9 +122,8 @@
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
- -o-border-radius: 4px;
- -khtml-border-radius: 4px;
- }
+
+ }
.uniForm #OKMsg p{ margin: 0; }
/* ----------------------------------------------------------------------------- */
diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html
index 5f16ef46..d45dcb9b 100644
--- a/couchpotato/templates/index.html
+++ b/couchpotato/templates/index.html
@@ -2,7 +2,7 @@
-
+
{% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %}
@@ -22,17 +22,18 @@
{% end %}
+
+
+
+
+
+
+ CouchPotato
+
+
+
+
+
\ No newline at end of file
diff --git a/init/ubuntu b/init/ubuntu
index dac2076c..7f770a67 100644
--- a/init/ubuntu
+++ b/init/ubuntu
@@ -26,48 +26,89 @@ NAME=couchpotato
# App name
DESC=CouchPotato
-# Path to app root
-CP_APP_PATH=${APP_PATH-/usr/local/sbin/CouchPotatoServer/}
+## Don't edit this file
+## Edit user configuation in /etc/default/couchpotato to change
+##
+## CP_USER= #$RUN_AS, username to run couchpotato under, the default is couchpotato
+## CP_HOME= #$APP_PATH, the location of couchpotato.py, the default is /opt/couchpotato
+## CP_DATA= #$DATA_DIR, the location of couchpotato.db, cache, logs, the default is /var/couchpotato
+## CP_PIDFILE= #$PID_FILE, the location of couchpotato.pid, the default is /var/run/couchpotato.pid
+## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
+## CP_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for couchpotato, i.e. " --config_file=/home/couchpotato/couchpotato.ini"
+## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
+##
+## EXAMPLE if want to run as different user
+## add CP_USER=username to /etc/default/couchpotato
+## otherwise default couchpotato is used
-# User to run CP as
-CP_RUN_AS=${RUN_AS-root}
+# Run CP as username
+RUN_AS=${CP_USER-couchpotato}
-# Path to python bin
-CP_DAEMON=${DAEMON_PATH-/usr/bin/python}
+# Path to app
+# CP_HOME=path_to_app_CouchPotato.py
+APP_PATH=${CP_HOME-/opt/couchpotato/}
+
+# Data directory where couchpotato.db, cache and logs are stored
+DATA_DIR=${CP_DATA-/var/couchpotato}
# Path to store PID file
-CP_PID_FILE=${PID_FILE-/var/run/couchpotato.pid}
+PID_FILE=${CP_PIDFILE-/var/run/couchpotato.pid}
-# Other startup args
-CP_DAEMON_OPTS=" CouchPotato.py --daemon --pid_file=${CP_PID_FILE}"
+# path to python bin
+DAEMON=${PYTHON_BIN-/usr/bin/python}
-test -x $CP_DAEMON || exit 0
+# Extra daemon option like: CP_OPTS=" --config=/home/couchpotato/couchpotato.ini"
+EXTRA_DAEMON_OPTS=${CP_OPTS-}
+
+# Extra start-stop-daemon option like START_OPTS=" --group=users"
+EXTRA_SSD_OPTS=${SSD_OPTS-}
+
+
+PID_PATH=`dirname $PID_FILE`
+DAEMON_OPTS=" CouchPotato.py --quiet --daemon --pid_file=${PID_FILE} --data_dir=${DATA_DIR} ${EXTRA_DAEMON_OPTS}"
+
+
+test -x $DAEMON || exit 0
set -e
-. /lib/lsb/init-functions
+# Create PID directory if not exist and ensure the CouchPotato user can write to it
+if [ ! -d $PID_PATH ]; then
+ mkdir -p $PID_PATH
+ chown $RUN_AS $PID_PATH
+fi
+
+if [ ! -d $DATA_DIR ]; then
+ mkdir -p $DATA_DIR
+ chown $RUN_AS $DATA_DIR
+fi
+
+if [ -e $PID_FILE ]; then
+ PID=`cat $PID_FILE`
+ if ! kill -0 $PID > /dev/null 2>&1; then
+ echo "Removing stale $PID_FILE"
+ rm $PID_FILE
+ fi
+fi
case "$1" in
start)
echo "Starting $DESC"
- rm -rf $CP_PID_FILE || return 1
- touch $CP_PID_FILE
- chown $CP_RUN_AS $CP_PID_FILE
- start-stop-daemon -d $CP_APP_PATH -c $CP_RUN_AS --start --background --pidfile $CP_PID_FILE --exec $CP_DAEMON -- $CP_DAEMON_OPTS
+ start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
;;
stop)
echo "Stopping $DESC"
- start-stop-daemon --stop --pidfile $CP_PID_FILE --retry 15
+ start-stop-daemon --stop --pidfile $PID_FILE --retry 15
;;
restart|force-reload)
echo "Restarting $DESC"
- start-stop-daemon --stop --pidfile $CP_PID_FILE --retry 15
- start-stop-daemon -d $CP_APP_PATH -c $CP_RUN_AS --start --background --pidfile $CP_PID_FILE --exec $CP_DAEMON -- $CP_DAEMON_OPTS
+ start-stop-daemon --stop --pidfile $PID_FILE --retry 15
+ start-stop-daemon -d $APP_PATH -c $RUN_AS $EXTRA_SSD_OPTS --start --pidfile $PID_FILE --exec $DAEMON -- $DAEMON_OPTS
;;
status)
- status_of_proc -p $CP_PID_FILE "$CP_DAEMON" "$NAME"
+ status_of_proc -p $PID_FILE "$DAEMON" "$NAME"
;;
*)
N=/etc/init.d/$NAME
diff --git a/init/ubuntu.default b/init/ubuntu.default
index 0d1e7128..00c523e7 100644
--- a/init/ubuntu.default
+++ b/init/ubuntu.default
@@ -1,5 +1,5 @@
-# COPY THIS FILE TO /etc/default/couchpotato
-# OPTIONS: APP_PATH, RUN_AS, DAEMON_PATH, CP_PID_FILE
+# COPY THIS FILE TO /etc/default/couchpotato
+# OPTIONS: CP_HOME, CP_USER, CP_DATA, CP_PIDFILE, PYTHON_BIN, CP_OPTS, SSD_OPTS
-APP_PATH=
-RUN_AS=root
\ No newline at end of file
+CP_HOME=
+CP_USER=root
\ No newline at end of file
diff --git a/libs/enzyme/mp4.py b/libs/enzyme/mp4.py
index c53f30d3..a66d30ad 100644
--- a/libs/enzyme/mp4.py
+++ b/libs/enzyme/mp4.py
@@ -284,6 +284,10 @@ class MPEG4(core.AVContainer):
while datasize:
mdia = struct.unpack('>I4s', atomdata[pos:pos + 8])
+
+ if mdia[0] == 0:
+ break
+
if mdia[1] == 'mdhd':
# Parse based on version of mdhd header. See
# http://wiki.multimedia.cx/index.php?title=QuickTime_container#mdhd
diff --git a/libs/guessit/__init__.py b/libs/guessit/__init__.py
index 386aa7f4..ce140248 100755
--- a/libs/guessit/__init__.py
+++ b/libs/guessit/__init__.py
@@ -20,7 +20,7 @@
from __future__ import unicode_literals
-__version__ = '0.6-dev'
+__version__ = '0.7-dev'
__all__ = ['Guess', 'Language',
'guess_file_info', 'guess_video_info',
'guess_movie_info', 'guess_episode_info']
@@ -91,7 +91,28 @@ log.addHandler(h)
def _guess_filename(filename, filetype):
+ def find_nodes(tree, props):
+ """Yields all nodes containing any of the given props."""
+ if isinstance(props, base_text_type):
+ props = [props]
+ for node in tree.nodes():
+ if any(prop in node.guess for prop in props):
+ yield node
+
+ def warning(title):
+ log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string()))
+ return m
+
mtree = IterativeMatcher(filename, filetype=filetype)
+
+ # if there are multiple possible years found, we assume the first one is
+ # part of the title, reparse the tree taking this into account
+ years = set(n.value for n in find_nodes(mtree.match_tree, 'year'))
+ if len(years) >= 2:
+ mtree = IterativeMatcher(filename, filetype=filetype,
+ opts=['skip_first_year'])
+
+
m = mtree.matched()
if 'language' not in m and 'subtitleLanguage' not in m:
@@ -102,20 +123,10 @@ def _guess_filename(filename, filetype):
opts=['nolanguage', 'nocountry'])
m2 = mtree2.matched()
- def find_nodes(tree, props):
- """Yields all nodes containing any of the given props."""
- if isinstance(props, base_text_type):
- props = [props]
- for node in tree.nodes():
- if any(prop in node.guess for prop in props):
- yield node
-
- def warning(title):
- log.warning('%s, guesses: %s - %s' % (title, m.nice_string(), m2.nice_string()))
+ if m.get('title') is None:
return m
-
if m.get('title') != m2.get('title'):
title = next(find_nodes(mtree.match_tree, 'title'))
title2 = next(find_nodes(mtree2.match_tree, 'title'))
diff --git a/libs/guessit/fileutils.py b/libs/guessit/fileutils.py
index 45f07e87..dc077e64 100755
--- a/libs/guessit/fileutils.py
+++ b/libs/guessit/fileutils.py
@@ -77,12 +77,12 @@ def file_in_same_dir(ref_file, desired_file):
def load_file_in_same_dir(ref_file, filename):
"""Load a given file. Works even when the file is contained inside a zip."""
- path = split_path(ref_file)[:-1] + [str(filename)]
+ path = split_path(ref_file)[:-1] + [filename]
for i, p in enumerate(path):
- if p[-4:] == '.zip':
+ if p.endswith('.zip'):
zfilename = os.path.join(*path[:i + 1])
zfile = zipfile.ZipFile(zfilename)
return zfile.read('/'.join(path[i + 1:]))
- return u(io.open(os.path.join(*path), encoding = 'utf-8').read())
+ return u(io.open(os.path.join(*path), encoding='utf-8').read())
diff --git a/libs/guessit/guess.py b/libs/guessit/guess.py
index 62385e8c..33d36517 100755
--- a/libs/guessit/guess.py
+++ b/libs/guessit/guess.py
@@ -295,7 +295,7 @@ def merge_all(guesses, append=None):
# then merge the remaining ones
dups = set(result) & set(g)
if dups:
- log.warning('duplicate properties %s in merged result...' % dups)
+ log.warning('duplicate properties %s in merged result...' % [ (result[p], g[p]) for p in dups] )
result.update_highest_confidence(g)
diff --git a/libs/guessit/language.py b/libs/guessit/language.py
index 3b3a86a5..2714c6e0 100755
--- a/libs/guessit/language.py
+++ b/libs/guessit/language.py
@@ -326,7 +326,7 @@ def search_language(string, lang_filter=None):
'la', 'el', 'del', 'por', 'mar',
# other
'ind', 'arw', 'ts', 'ii', 'bin', 'chan', 'ss', 'san', 'oss', 'iii',
- 'vi', 'ben', 'da'
+ 'vi', 'ben', 'da', 'lt'
])
sep = r'[](){} \._-+'
diff --git a/libs/guessit/matcher.py b/libs/guessit/matcher.py
index cc77b817..43378192 100755
--- a/libs/guessit/matcher.py
+++ b/libs/guessit/matcher.py
@@ -128,12 +128,14 @@ class IterativeMatcher(object):
apply_transfo(name)
# more guessers for both movies and episodes
- for name in ['guess_bonus_features', 'guess_year']:
- apply_transfo(name)
+ apply_transfo('guess_bonus_features')
+ apply_transfo('guess_year', skip_first_year=('skip_first_year' in opts))
if 'nocountry' not in opts:
apply_transfo('guess_country')
+ apply_transfo('guess_idnumber')
+
# split into '-' separated subgroups (with required separator chars
# around the dash)
diff --git a/libs/guessit/matchtree.py b/libs/guessit/matchtree.py
index 2853c3a0..0725e835 100755
--- a/libs/guessit/matchtree.py
+++ b/libs/guessit/matchtree.py
@@ -275,7 +275,7 @@ class MatchTree(BaseMatchTree):
for string_part in ('title', 'series', 'container', 'format',
'releaseGroup', 'website', 'audioCodec',
'videoCodec', 'screenSize', 'episodeFormat',
- 'audioChannels'):
+ 'audioChannels', 'idNumber'):
merge_similar_guesses(parts, string_part, choose_string)
# 2- merge the rest, potentially discarding information not properly
diff --git a/libs/guessit/patterns.py b/libs/guessit/patterns.py
index a8a0607c..ed3982b9 100755
--- a/libs/guessit/patterns.py
+++ b/libs/guessit/patterns.py
@@ -43,13 +43,13 @@ episode_rexps = [ # ... Season 2 ...
(r'saison (?P[0-9]+)', 1.0, (0, 0)),
# ... s02e13 ...
- (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Ee-][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
+ (r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[eE-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)),
- # ... s03-x02 ...
- (r'[Ss](?P[0-9]{1,2}).?(?P(?:[Xx][0-9]{1,2})+)[^0-9]', 1.0, (0, -1)),
+ # ... s03-x02 ... # FIXME: redundant? remove it?
+ #(r'[Ss](?P[0-9]{1,3})[^0-9]?(?P(?:-?[xX-][0-9]{1,3})+)[^0-9]', 1.0, (0, -1)),
# ... 2x13 ...
- (r'[^0-9](?P[0-9]{1,2}).?(?P(?:[xX][0-9]{1,2})+)[^0-9]', 0.8, (1, -1)),
+ (r'[^0-9](?P[0-9]{1,2})[^0-9]?(?P(?:-?[xX][0-9]{1,3})+)[^0-9]', 1.0, (1, -1)),
# ... s02 ...
#(sep + r's(?P[0-9]{1,2})' + sep, 0.6, (1, -1)),
@@ -122,20 +122,25 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ],
'VHS': [ 'VHS' ],
'WEB-DL': [ 'WEB-DL' ] },
- 'screenSize': { '480p': [ '480p?' ],
- '720p': [ '720p?' ],
- '1080p': [ '1080p?' ] },
+ 'screenSize': { '480p': [ '480[pi]?' ],
+ '720p': [ '720[pi]?' ],
+ '1080p': [ '1080[pi]?' ] },
'videoCodec': { 'XviD': [ 'Xvid' ],
'DivX': [ 'DVDivX', 'DivX' ],
'h264': [ '[hx]-264' ],
- 'Rv10': [ 'Rv10' ] },
+ 'Rv10': [ 'Rv10' ],
+ 'Mpeg2': [ 'Mpeg2' ] },
+
+ # has nothing to do here (or on filenames for that matter), but some
+ # releases use it and it helps to identify release groups, so we adapt
+ 'videoApi': { 'DXVA': [ 'DXVA' ] },
'audioCodec': { 'AC3': [ 'AC3' ],
'DTS': [ 'DTS' ],
'AAC': [ 'He-AAC', 'AAC-He', 'AAC' ] },
- 'audioChannels': { '5.1': [ r'5\.1', 'DD5\.1', '5ch' ] },
+ 'audioChannels': { '5.1': [ r'5\.1', 'DD5[\._ ]1', '5ch' ] },
'episodeFormat': { 'Minisode': [ 'Minisodes?' ] }
@@ -143,14 +148,21 @@ prop_multi = { 'format': { 'DVD': [ 'DVD', 'DVD-Rip', 'VIDEO-TS', 'DVDivX' ],
# prop_single dict of { property_name: [ canonical_form ] }
prop_single = { 'releaseGroup': [ 'ESiR', 'WAF', 'SEPTiC', r'\[XCT\]', 'iNT', 'PUKKA',
- 'CHD', 'ViTE', 'TLF', 'DEiTY', 'FLAiTE',
- 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS', 'FiNaLe',
- 'UnSeeN', 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL',
- 'SiNNERS', 'DiRTY', 'REWARD', 'ECI', 'KiNGS', 'CLUE',
- 'CtrlHD', 'POD', 'WiKi', 'DIMENSION', 'IMMERSE', 'FQM',
- '2HD', 'REPTiLE', 'CTU', 'HALCYON', 'EbP', 'SiTV',
- 'SAiNTS', 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV',
- 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3' ],
+ 'CHD', 'ViTE', 'TLF', 'FLAiTE',
+ 'MDX', 'GM4F', 'DVL', 'SVD', 'iLUMiNADOS',
+ 'aXXo', 'KLAXXON', 'NoTV', 'ZeaL', 'LOL',
+ 'CtrlHD', 'POD', 'WiKi','IMMERSE', 'FQM',
+ '2HD', 'CTU', 'HALCYON', 'EbP', 'SiTV',
+ 'HDBRiSe', 'AlFleNi-TeaM', 'EVOLVE', '0TV',
+ 'TLA', 'NTB', 'ASAP', 'MOMENTUM', 'FoV', 'D-Z0N3',
+ 'TrollHD', 'ECI'
+ ],
+
+ # potentially confusing release group names (they are words)
+ 'weakReleaseGroup': [ 'DEiTY', 'FiNaLe', 'UnSeeN', 'KiNGS', 'CLUE', 'DIMENSION',
+ 'SAiNTS', 'ARROW', 'EuReKA', 'SiNNERS', 'DiRTY', 'REWARD',
+ 'REPTiLE',
+ ],
'other': [ 'PROPER', 'REPACK', 'LIMITED', 'DualAudio', 'Audiofixed', 'R5',
'complete', 'classic', # not so sure about these ones, could appear in a title
@@ -179,6 +191,10 @@ properties_rexps.update(dict((type, dict((canonical_form, [ _to_rexp(canonical_f
def find_properties(string):
result = []
for property_name, props in properties_rexps.items():
+ # FIXME: this should be done in a more flexible way...
+ if property_name in ['weakReleaseGroup']:
+ continue
+
for canonical_form, rexps in props.items():
for value_rexp in rexps:
match = value_rexp.search(string)
diff --git a/libs/guessit/transfo/guess_episodes_rexps.py b/libs/guessit/transfo/guess_episodes_rexps.py
index 4ebfb547..29562be2 100755
--- a/libs/guessit/transfo/guess_episodes_rexps.py
+++ b/libs/guessit/transfo/guess_episodes_rexps.py
@@ -28,7 +28,13 @@ import logging
log = logging.getLogger(__name__)
def number_list(s):
- return list(re.sub('[^0-9]+', ' ', s).split())
+ l = [ int(n) for n in re.sub('[^0-9]+', ' ', s).split() ]
+
+ if len(l) == 2:
+ # it is an episode interval, return all numbers in between
+ return range(l[0], l[1]+1)
+
+ return l
def guess_episodes_rexps(string):
for rexp, confidence, span_adjust in episode_rexps:
@@ -38,23 +44,23 @@ def guess_episodes_rexps(string):
span = (match.start() + span_adjust[0],
match.end() + span_adjust[1])
- # episodes which have a season > 25 are most likely errors
+ # episodes which have a season > 30 are most likely errors
# (Simpsons is at 24!)
- if int(guess.get('season', 0)) > 25:
+ if int(guess.get('season', 0)) > 30:
continue
# decide whether we have only a single episode number or an
# episode list
if guess.get('episodeNumber'):
eplist = number_list(guess['episodeNumber'])
- guess.set('episodeNumber', int(eplist[0]), confidence=confidence)
+ guess.set('episodeNumber', eplist[0], confidence=confidence)
if len(eplist) > 1:
- guess.set('episodeList', list(map(int, eplist)), confidence=confidence)
+ guess.set('episodeList', eplist, confidence=confidence)
if guess.get('bonusNumber'):
eplist = number_list(guess['bonusNumber'])
- guess.set('bonusNumber', int(eplist[0]), confidence=confidence)
+ guess.set('bonusNumber', eplist[0], confidence=confidence)
return guess, span
diff --git a/libs/guessit/transfo/guess_idnumber.py b/libs/guessit/transfo/guess_idnumber.py
new file mode 100755
index 00000000..0e15af5c
--- /dev/null
+++ b/libs/guessit/transfo/guess_idnumber.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# GuessIt - A library for guessing information from filenames
+# Copyright (c) 2013 Nicolas Wack
+#
+# GuessIt is free software; you can redistribute it and/or modify it under
+# the terms of the Lesser GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GuessIt is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Lesser GNU General Public License for more details.
+#
+# You should have received a copy of the Lesser GNU General Public License
+# along with this program. If not, see .
+#
+
+from __future__ import unicode_literals
+from guessit.transfo import SingleNodeGuesser
+from guessit.patterns import find_properties
+import re
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def guess_properties(string):
+ try:
+ prop, value, pos, end = find_properties(string)[0]
+ return { prop: value }, (pos, end)
+ except IndexError:
+ return None, None
+
+_idnum = re.compile(r'(?P[a-zA-Z0-9-]{10,})') # 1.0, (0, 0))
+
+def guess_idnumber(string):
+ match = _idnum.search(string)
+ if match is not None:
+ result = match.groupdict()
+ switch_count = 0
+ DIGIT = 0
+ LETTER = 1
+ OTHER = 2
+ last = LETTER
+ for c in result['idNumber']:
+ if c in '0123456789':
+ ci = DIGIT
+ elif c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
+ ci = LETTER
+ else:
+ ci = OTHER
+
+ if ci != last:
+ switch_count += 1
+
+ last = ci
+
+ switch_ratio = float(switch_count) / len(result['idNumber'])
+
+ # only return the result as probable if we alternate often between
+ # char type (more likely for hash values than for common words)
+ if switch_ratio > 0.4:
+ return result, match.span()
+
+ return None, None
+
+def process(mtree):
+ SingleNodeGuesser(guess_idnumber, 0.4, log).process(mtree)
diff --git a/libs/guessit/transfo/guess_release_group.py b/libs/guessit/transfo/guess_release_group.py
index 2ff237d8..b72c7368 100755
--- a/libs/guessit/transfo/guess_release_group.py
+++ b/libs/guessit/transfo/guess_release_group.py
@@ -31,16 +31,22 @@ def get_patterns(property_name):
CODECS = get_patterns('videoCodec')
FORMATS = get_patterns('format')
+VAPIS = get_patterns('videoApi')
-GROUP_NAMES = [ r'(?P' + codec + r')-?(?P.*?)[ \.]'
+# RG names following a codec or format, with a potential space or dash inside the name
+GROUP_NAMES = [ r'(?P' + codec + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
for codec in CODECS ]
-GROUP_NAMES += [ r'(?P' + fmt + r')-?(?P.*?)[ \.]'
+GROUP_NAMES += [ r'(?P' + fmt + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
for fmt in FORMATS ]
+GROUP_NAMES += [ r'(?P' + api + r')[ \.-](?P.+?([- \.].*?)??)[ \.]'
+ for api in VAPIS ]
GROUP_NAMES2 = [ r'\.(?P' + codec + r')-(?P.*?)(-(.*?))?[ \.]'
for codec in CODECS ]
-GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]'
+GROUP_NAMES2 += [ r'\.(?P' + fmt + r')-(?P.*?)(-(.*?))?[ \.]'
for fmt in FORMATS ]
+GROUP_NAMES2 += [ r'\.(?P' + vapi + r')-(?P.*?)(-(.*?))?[ \.]'
+ for vapi in VAPIS ]
GROUP_NAMES = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES ]
GROUP_NAMES2 = [ re.compile(r, re.IGNORECASE) for r in GROUP_NAMES2 ]
@@ -54,12 +60,17 @@ def guess_release_group(string):
# first try to see whether we have both a known codec and a known release group
for rexp in GROUP_NAMES:
match = rexp.search(string)
- if match:
+ while match:
metadata = match.groupdict()
- release_group = compute_canonical_form('releaseGroup', metadata['releaseGroup'])
+ # make sure this is an actual release group we caught
+ release_group = (compute_canonical_form('releaseGroup', metadata['releaseGroup']) or
+ compute_canonical_form('weakReleaseGroup', metadata['releaseGroup']))
if release_group:
return adjust_metadata(metadata), (match.start(1), match.end(2))
+ # we didn't find anything conclusive, keep searching
+ match = rexp.search(string, match.span()[0]+1)
+
# pick anything as releaseGroup as long as we have a codec in front
# this doesn't include a potential dash ('-') ending the release group
# eg: [...].X264-HiS@SiLUHD-English.[...]
diff --git a/libs/guessit/transfo/guess_year.py b/libs/guessit/transfo/guess_year.py
index 4bc9b867..c193af7a 100755
--- a/libs/guessit/transfo/guess_year.py
+++ b/libs/guessit/transfo/guess_year.py
@@ -33,6 +33,18 @@ def guess_year(string):
else:
return None, None
+def guess_year_skip_first(string):
+ year, span = search_year(string)
+ if year:
+ year2, span2 = guess_year(string[span[1]:])
+ if year2:
+ return year2, (span2[0]+span[1], span2[1]+span[1])
-def process(mtree):
- SingleNodeGuesser(guess_year, 1.0, log).process(mtree)
+ return None, None
+
+
+def process(mtree, skip_first_year=False):
+ if skip_first_year:
+ SingleNodeGuesser(guess_year_skip_first, 1.0, log).process(mtree)
+ else:
+ SingleNodeGuesser(guess_year, 1.0, log).process(mtree)
diff --git a/libs/rtorrent/__init__.py b/libs/rtorrent/__init__.py
new file mode 100755
index 00000000..d19c78b4
--- /dev/null
+++ b/libs/rtorrent/__init__.py
@@ -0,0 +1,588 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from rtorrent.common import find_torrent, \
+ is_valid_port, convert_version_tuple_to_str
+from rtorrent.lib.torrentparser import TorrentParser
+from rtorrent.lib.xmlrpc.http import HTTPServerProxy
+from rtorrent.rpc import Method, BasicAuthTransport
+from rtorrent.torrent import Torrent
+from rtorrent.group import Group
+import os.path
+import rtorrent.rpc # @UnresolvedImport
+import time
+import xmlrpclib
+
+__version__ = "0.2.9"
+__author__ = "Chris Lucas"
+__contact__ = "chris@chrisjlucas.com"
+__license__ = "MIT"
+
+MIN_RTORRENT_VERSION = (0, 8, 1)
+MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION)
+
+
+class RTorrent:
+ """ Create a new rTorrent connection """
+ rpc_prefix = None
+
+ def __init__(self, url, username=None, password=None,
+ verify=False, sp=HTTPServerProxy, sp_kwargs={}):
+ self.url = url # : From X{__init__(self, url)}
+ self.username = username
+ self.password = password
+ self.sp = sp
+ self.sp_kwargs = sp_kwargs
+
+ self.torrents = [] # : List of L{Torrent} instances
+ self._rpc_methods = [] # : List of rTorrent RPC methods
+ self._torrent_cache = []
+ self._client_version_tuple = ()
+
+ if verify is True:
+ self._verify_conn()
+
+ def _get_conn(self):
+ """Get ServerProxy instance"""
+ if self.username is not None and self.password is not None:
+ return self.sp(
+ self.url,
+ transport=BasicAuthTransport(self.username, self.password),
+ **self.sp_kwargs
+ )
+ return self.sp(self.url, **self.sp_kwargs)
+
+ def _verify_conn(self):
+ # check for rpc methods that should be available
+ assert {"system.client_version",
+ "system.library_version"}.issubset(set(self._get_rpc_methods())),\
+ "Required RPC methods not available."
+
+ # minimum rTorrent version check
+
+ assert self._meets_version_requirement() is True,\
+ "Error: Minimum rTorrent version required is {0}".format(
+ MIN_RTORRENT_VERSION_STR)
+
+ def _meets_version_requirement(self):
+ return self._get_client_version_tuple() >= MIN_RTORRENT_VERSION
+
+ def _get_client_version_tuple(self):
+ conn = self._get_conn()
+
+ if not self._client_version_tuple:
+ if not hasattr(self, "client_version"):
+ setattr(self, "client_version",
+ conn.system.client_version())
+
+ rtver = getattr(self, "client_version")
+ self._client_version_tuple = tuple([int(i) for i in
+ rtver.split(".")])
+
+ return self._client_version_tuple
+
+ def _get_rpc_methods(self):
+ """ Get list of raw RPC commands
+
+ @return: raw RPC commands
+ @rtype: list
+ """
+
+ if self._rpc_methods == []:
+ self._rpc_methods = self._get_conn().system.listMethods()
+
+ return(self._rpc_methods)
+
+ def get_torrents(self, view="main"):
+ """Get list of all torrents in specified view
+
+ @return: list of L{Torrent} instances
+
+ @rtype: list
+
+ @todo: add validity check for specified view
+ """
+ self.torrents = []
+ methods = rtorrent.torrent.methods
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self)]
+
+ m = rtorrent.rpc.Multicall(self)
+ m.add("d.multicall", view, "d.get_hash=",
+ *[method.rpc_call + "=" for method in retriever_methods])
+
+ results = m.call()[0] # only sent one call, only need first result
+
+ for result in results:
+ results_dict = {}
+ # build results_dict
+ for m, r in zip(retriever_methods, result[1:]): # result[0] is the info_hash
+ results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
+
+ self.torrents.append(
+ Torrent(self, info_hash=result[0], **results_dict)
+ )
+
+ self._manage_torrent_cache()
+ return(self.torrents)
+
+ def _manage_torrent_cache(self):
+ """Carry tracker/peer/file lists over to new torrent list"""
+ for torrent in self._torrent_cache:
+ new_torrent = rtorrent.common.find_torrent(torrent.info_hash,
+ self.torrents)
+ if new_torrent is not None:
+ new_torrent.files = torrent.files
+ new_torrent.peers = torrent.peers
+ new_torrent.trackers = torrent.trackers
+
+ self._torrent_cache = self.torrents
+
+ def _get_load_function(self, file_type, start, verbose):
+ """Determine correct "load torrent" RPC method"""
+ func_name = None
+ if file_type == "url":
+ # url strings can be input directly
+ if start and verbose:
+ func_name = "load_start_verbose"
+ elif start:
+ func_name = "load_start"
+ elif verbose:
+ func_name = "load_verbose"
+ else:
+ func_name = "load"
+ elif file_type in ["file", "raw"]:
+ if start and verbose:
+ func_name = "load_raw_start_verbose"
+ elif start:
+ func_name = "load_raw_start"
+ elif verbose:
+ func_name = "load_raw_verbose"
+ else:
+ func_name = "load_raw"
+
+ return(func_name)
+
+ def load_torrent(self, torrent, start=False, verbose=False, verify_load=True):
+ """
+ Loads torrent into rTorrent (with various enhancements)
+
+ @param torrent: can be a url, a path to a local file, or the raw data
+ of a torrent file
+ @type torrent: str
+
+ @param start: start torrent when loaded
+ @type start: bool
+
+ @param verbose: print error messages to rTorrent log
+ @type verbose: bool
+
+ @param verify_load: verify that torrent was added to rTorrent successfully
+ @type verify_load: bool
+
+ @return: Depends on verify_load:
+ - if verify_load is True, (and the torrent was
+ loaded successfully), it'll return a L{Torrent} instance
+ - if verify_load is False, it'll return None
+
+ @rtype: L{Torrent} instance or None
+
+ @raise AssertionError: If the torrent wasn't successfully added to rTorrent
+ - Check L{TorrentParser} for the AssertionError's
+ it raises
+
+
+ @note: Because this function includes url verification (if a url was input)
+ as well as verification as to whether the torrent was successfully added,
+ this function doesn't execute instantaneously. If that's what you're
+ looking for, use load_torrent_simple() instead.
+ """
+ p = self._get_conn()
+ tp = TorrentParser(torrent)
+ torrent = xmlrpclib.Binary(tp._raw_torrent)
+ info_hash = tp.info_hash
+
+ func_name = self._get_load_function("raw", start, verbose)
+
+ # load torrent
+ getattr(p, func_name)(torrent)
+
+ if verify_load:
+ MAX_RETRIES = 3
+ i = 0
+ while i < MAX_RETRIES:
+ self.get_torrents()
+ if info_hash in [t.info_hash for t in self.torrents]:
+ break
+
+ # was still getting AssertionErrors, delay should help
+ time.sleep(1)
+ i += 1
+
+ assert info_hash in [t.info_hash for t in self.torrents],\
+ "Adding torrent was unsuccessful."
+
+ return(find_torrent(info_hash, self.torrents))
+
+ def load_torrent_simple(self, torrent, file_type,
+ start=False, verbose=False):
+ """Loads torrent into rTorrent
+
+ @param torrent: can be a url, a path to a local file, or the raw data
+ of a torrent file
+ @type torrent: str
+
+ @param file_type: valid options: "url", "file", or "raw"
+ @type file_type: str
+
+ @param start: start torrent when loaded
+ @type start: bool
+
+ @param verbose: print error messages to rTorrent log
+ @type verbose: bool
+
+ @return: None
+
+ @raise AssertionError: if incorrect file_type is specified
+
+ @note: This function was written for speed, it includes no enhancements.
+ If you input a url, it won't check if it's valid. You also can't get
+ verification that the torrent was successfully added to rTorrent.
+ Use load_torrent() if you would like these features.
+ """
+ p = self._get_conn()
+
+ assert file_type in ["raw", "file", "url"], \
+ "Invalid file_type, options are: 'url', 'file', 'raw'."
+ func_name = self._get_load_function(file_type, start, verbose)
+
+ if file_type == "file":
+ # since we have to assume we're connected to a remote rTorrent
+ # client, we have to read the file and send it to rT as raw
+ assert os.path.isfile(torrent), \
+ "Invalid path: \"{0}\"".format(torrent)
+ torrent = open(torrent, "rb").read()
+
+ if file_type in ["raw", "file"]:
+ finput = xmlrpclib.Binary(torrent)
+ elif file_type == "url":
+ finput = torrent
+
+ getattr(p, func_name)(finput)
+
+ def get_views(self):
+ p = self._get_conn()
+ return p.view_list()
+
+ def create_group(self, name, persistent=True, view=None):
+ p = self._get_conn()
+
+ if persistent is True:
+ p.group.insert_persistent_view('', name)
+ else:
+ assert view is not None, "view parameter required on non-persistent groups"
+ p.group.insert('', name, view)
+
+ def get_group(self, name):
+ assert name is not None, "group name required"
+
+ group = Group(self, name)
+ group.update()
+ return group
+
+ def set_dht_port(self, port):
+ """Set DHT port
+
+ @param port: port
+ @type port: int
+
+ @raise AssertionError: if invalid port is given
+ """
+ assert is_valid_port(port), "Valid port range is 0-65535"
+ self.dht_port = self._p.set_dht_port(port)
+
+ def enable_check_hash(self):
+ """Alias for set_check_hash(True)"""
+ self.set_check_hash(True)
+
+ def disable_check_hash(self):
+ """Alias for set_check_hash(False)"""
+ self.set_check_hash(False)
+
+ def find_torrent(self, info_hash):
+ """Frontend for rtorrent.common.find_torrent"""
+ return(rtorrent.common.find_torrent(info_hash, self.get_torrents()))
+
+ def poll(self):
+ """ poll rTorrent to get latest torrent/peer/tracker/file information
+
+ @note: This essentially refreshes every aspect of the rTorrent
+ connection, so it can be very slow if working with a remote
+ connection that has a lot of torrents loaded.
+
+ @return: None
+ """
+ self.update()
+ torrents = self.get_torrents()
+ for t in torrents:
+ t.poll()
+
+ def update(self):
+ """Refresh rTorrent client info
+
+ @note: All fields are stored as attributes to self.
+
+ @return: None
+ """
+ multicall = rtorrent.rpc.Multicall(self)
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self)]
+ for method in retriever_methods:
+ multicall.add(method)
+
+ multicall.call()
+
+
+def _build_class_methods(class_obj):
+ # multicall add class
+ caller = lambda self, multicall, method, *args:\
+ multicall.add(method, self.rpc_id, *args)
+
+ caller.__doc__ = """Same as Multicall.add(), but with automatic inclusion
+ of the rpc_id
+
+ @param multicall: A L{Multicall} instance
+ @type: multicall: Multicall
+
+ @param method: L{Method} instance or raw rpc method
+ @type: Method or str
+
+ @param args: optional arguments to pass
+ """
+ setattr(class_obj, "multicall_add", caller)
+
+
+def __compare_rpc_methods(rt_new, rt_old):
+ from pprint import pprint
+ rt_new_methods = set(rt_new._get_rpc_methods())
+ rt_old_methods = set(rt_old._get_rpc_methods())
+ print("New Methods:")
+ pprint(rt_new_methods - rt_old_methods)
+ print("Methods not in new rTorrent:")
+ pprint(rt_old_methods - rt_new_methods)
+
+
+def __check_supported_methods(rt):
+ from pprint import pprint
+ supported_methods = set([m.rpc_call for m in
+ methods +
+ rtorrent.file.methods +
+ rtorrent.torrent.methods +
+ rtorrent.tracker.methods +
+ rtorrent.peer.methods])
+ all_methods = set(rt._get_rpc_methods())
+
+ print("Methods NOT in supported methods")
+ pprint(all_methods - supported_methods)
+ print("Supported methods NOT in all methods")
+ pprint(supported_methods - all_methods)
+
+methods = [
+ # RETRIEVERS
+ Method(RTorrent, 'get_xmlrpc_size_limit', 'get_xmlrpc_size_limit'),
+ Method(RTorrent, 'get_proxy_address', 'get_proxy_address'),
+ Method(RTorrent, 'get_split_suffix', 'get_split_suffix'),
+ Method(RTorrent, 'get_up_limit', 'get_upload_rate'),
+ Method(RTorrent, 'get_max_memory_usage', 'get_max_memory_usage'),
+ Method(RTorrent, 'get_max_open_files', 'get_max_open_files'),
+ Method(RTorrent, 'get_min_peers_seed', 'get_min_peers_seed'),
+ Method(RTorrent, 'get_use_udp_trackers', 'get_use_udp_trackers'),
+ Method(RTorrent, 'get_preload_min_size', 'get_preload_min_size'),
+ Method(RTorrent, 'get_max_uploads', 'get_max_uploads'),
+ Method(RTorrent, 'get_max_peers', 'get_max_peers'),
+ Method(RTorrent, 'get_timeout_sync', 'get_timeout_sync'),
+ Method(RTorrent, 'get_receive_buffer_size', 'get_receive_buffer_size'),
+ Method(RTorrent, 'get_split_file_size', 'get_split_file_size'),
+ Method(RTorrent, 'get_dht_throttle', 'get_dht_throttle'),
+ Method(RTorrent, 'get_max_peers_seed', 'get_max_peers_seed'),
+ Method(RTorrent, 'get_min_peers', 'get_min_peers'),
+ Method(RTorrent, 'get_tracker_numwant', 'get_tracker_numwant'),
+ Method(RTorrent, 'get_max_open_sockets', 'get_max_open_sockets'),
+ Method(RTorrent, 'get_session', 'get_session'),
+ Method(RTorrent, 'get_ip', 'get_ip'),
+ Method(RTorrent, 'get_scgi_dont_route', 'get_scgi_dont_route'),
+ Method(RTorrent, 'get_hash_read_ahead', 'get_hash_read_ahead'),
+ Method(RTorrent, 'get_http_cacert', 'get_http_cacert'),
+ Method(RTorrent, 'get_dht_port', 'get_dht_port'),
+ Method(RTorrent, 'get_handshake_log', 'get_handshake_log'),
+ Method(RTorrent, 'get_preload_type', 'get_preload_type'),
+ Method(RTorrent, 'get_max_open_http', 'get_max_open_http'),
+ Method(RTorrent, 'get_http_capath', 'get_http_capath'),
+ Method(RTorrent, 'get_max_downloads_global', 'get_max_downloads_global'),
+ Method(RTorrent, 'get_name', 'get_name'),
+ Method(RTorrent, 'get_session_on_completion', 'get_session_on_completion'),
+ Method(RTorrent, 'get_down_limit', 'get_download_rate'),
+ Method(RTorrent, 'get_down_total', 'get_down_total'),
+ Method(RTorrent, 'get_up_rate', 'get_up_rate'),
+ Method(RTorrent, 'get_hash_max_tries', 'get_hash_max_tries'),
+ Method(RTorrent, 'get_peer_exchange', 'get_peer_exchange'),
+ Method(RTorrent, 'get_down_rate', 'get_down_rate'),
+ Method(RTorrent, 'get_connection_seed', 'get_connection_seed'),
+ Method(RTorrent, 'get_http_proxy', 'get_http_proxy'),
+ Method(RTorrent, 'get_stats_preloaded', 'get_stats_preloaded'),
+ Method(RTorrent, 'get_timeout_safe_sync', 'get_timeout_safe_sync'),
+ Method(RTorrent, 'get_hash_interval', 'get_hash_interval'),
+ Method(RTorrent, 'get_port_random', 'get_port_random'),
+ Method(RTorrent, 'get_directory', 'get_directory'),
+ Method(RTorrent, 'get_port_open', 'get_port_open'),
+ Method(RTorrent, 'get_max_file_size', 'get_max_file_size'),
+ Method(RTorrent, 'get_stats_not_preloaded', 'get_stats_not_preloaded'),
+ Method(RTorrent, 'get_memory_usage', 'get_memory_usage'),
+ Method(RTorrent, 'get_connection_leech', 'get_connection_leech'),
+ Method(RTorrent, 'get_check_hash', 'get_check_hash',
+ boolean=True,
+ ),
+ Method(RTorrent, 'get_session_lock', 'get_session_lock'),
+ Method(RTorrent, 'get_preload_required_rate', 'get_preload_required_rate'),
+ Method(RTorrent, 'get_max_uploads_global', 'get_max_uploads_global'),
+ Method(RTorrent, 'get_send_buffer_size', 'get_send_buffer_size'),
+ Method(RTorrent, 'get_port_range', 'get_port_range'),
+ Method(RTorrent, 'get_max_downloads_div', 'get_max_downloads_div'),
+ Method(RTorrent, 'get_max_uploads_div', 'get_max_uploads_div'),
+ Method(RTorrent, 'get_safe_sync', 'get_safe_sync'),
+ Method(RTorrent, 'get_bind', 'get_bind'),
+ Method(RTorrent, 'get_up_total', 'get_up_total'),
+ Method(RTorrent, 'get_client_version', 'system.client_version'),
+ Method(RTorrent, 'get_library_version', 'system.library_version'),
+ Method(RTorrent, 'get_api_version', 'system.api_version',
+ min_version=(0, 9, 1)
+ ),
+ Method(RTorrent, "get_system_time", "system.time",
+ docstring="""Get the current time of the system rTorrent is running on
+
+ @return: time (posix)
+ @rtype: int""",
+ ),
+
+ # MODIFIERS
+ Method(RTorrent, 'set_http_proxy', 'set_http_proxy'),
+ Method(RTorrent, 'set_max_memory_usage', 'set_max_memory_usage'),
+ Method(RTorrent, 'set_max_file_size', 'set_max_file_size'),
+ Method(RTorrent, 'set_bind', 'set_bind',
+ docstring="""Set address bind
+
+ @param arg: ip address
+ @type arg: str
+ """,
+ ),
+ Method(RTorrent, 'set_up_limit', 'set_upload_rate',
+ docstring="""Set global upload limit (in bytes)
+
+ @param arg: speed limit
+ @type arg: int
+ """,
+ ),
+ Method(RTorrent, 'set_port_random', 'set_port_random'),
+ Method(RTorrent, 'set_connection_leech', 'set_connection_leech'),
+ Method(RTorrent, 'set_tracker_numwant', 'set_tracker_numwant'),
+ Method(RTorrent, 'set_max_peers', 'set_max_peers'),
+ Method(RTorrent, 'set_min_peers', 'set_min_peers'),
+ Method(RTorrent, 'set_max_uploads_div', 'set_max_uploads_div'),
+ Method(RTorrent, 'set_max_open_files', 'set_max_open_files'),
+ Method(RTorrent, 'set_max_downloads_global', 'set_max_downloads_global'),
+ Method(RTorrent, 'set_session_lock', 'set_session_lock'),
+ Method(RTorrent, 'set_session', 'set_session'),
+ Method(RTorrent, 'set_split_suffix', 'set_split_suffix'),
+ Method(RTorrent, 'set_hash_interval', 'set_hash_interval'),
+ Method(RTorrent, 'set_handshake_log', 'set_handshake_log'),
+ Method(RTorrent, 'set_port_range', 'set_port_range'),
+ Method(RTorrent, 'set_min_peers_seed', 'set_min_peers_seed'),
+ Method(RTorrent, 'set_scgi_dont_route', 'set_scgi_dont_route'),
+ Method(RTorrent, 'set_preload_min_size', 'set_preload_min_size'),
+ Method(RTorrent, 'set_log.tracker', 'set_log.tracker'),
+ Method(RTorrent, 'set_max_uploads_global', 'set_max_uploads_global'),
+ Method(RTorrent, 'set_down_limit', 'set_download_rate',
+ docstring="""Set global download limit (in bytes)
+
+ @param arg: speed limit
+ @type arg: int
+ """,
+ ),
+ Method(RTorrent, 'set_preload_required_rate', 'set_preload_required_rate'),
+ Method(RTorrent, 'set_hash_read_ahead', 'set_hash_read_ahead'),
+ Method(RTorrent, 'set_max_peers_seed', 'set_max_peers_seed'),
+ Method(RTorrent, 'set_max_uploads', 'set_max_uploads'),
+ Method(RTorrent, 'set_session_on_completion', 'set_session_on_completion'),
+ Method(RTorrent, 'set_max_open_http', 'set_max_open_http'),
+ Method(RTorrent, 'set_directory', 'set_directory'),
+ Method(RTorrent, 'set_http_cacert', 'set_http_cacert'),
+ Method(RTorrent, 'set_dht_throttle', 'set_dht_throttle'),
+ Method(RTorrent, 'set_hash_max_tries', 'set_hash_max_tries'),
+ Method(RTorrent, 'set_proxy_address', 'set_proxy_address'),
+ Method(RTorrent, 'set_split_file_size', 'set_split_file_size'),
+ Method(RTorrent, 'set_receive_buffer_size', 'set_receive_buffer_size'),
+ Method(RTorrent, 'set_use_udp_trackers', 'set_use_udp_trackers'),
+ Method(RTorrent, 'set_connection_seed', 'set_connection_seed'),
+ Method(RTorrent, 'set_xmlrpc_size_limit', 'set_xmlrpc_size_limit'),
+ Method(RTorrent, 'set_xmlrpc_dialect', 'set_xmlrpc_dialect'),
+ Method(RTorrent, 'set_safe_sync', 'set_safe_sync'),
+ Method(RTorrent, 'set_http_capath', 'set_http_capath'),
+ Method(RTorrent, 'set_send_buffer_size', 'set_send_buffer_size'),
+ Method(RTorrent, 'set_max_downloads_div', 'set_max_downloads_div'),
+ Method(RTorrent, 'set_name', 'set_name'),
+ Method(RTorrent, 'set_port_open', 'set_port_open'),
+ Method(RTorrent, 'set_timeout_sync', 'set_timeout_sync'),
+ Method(RTorrent, 'set_peer_exchange', 'set_peer_exchange'),
+ Method(RTorrent, 'set_ip', 'set_ip',
+ docstring="""Set IP
+
+ @param arg: ip address
+ @type arg: str
+ """,
+ ),
+ Method(RTorrent, 'set_timeout_safe_sync', 'set_timeout_safe_sync'),
+ Method(RTorrent, 'set_preload_type', 'set_preload_type'),
+ Method(RTorrent, 'set_check_hash', 'set_check_hash',
+ docstring="""Enable/Disable hash checking on finished torrents
+
+ @param arg: True to enable, False to disable
+ @type arg: bool
+ """,
+ boolean=True,
+ ),
+]
+
+_all_methods_list = [methods,
+ rtorrent.file.methods,
+ rtorrent.torrent.methods,
+ rtorrent.tracker.methods,
+ rtorrent.peer.methods,
+ ]
+
+class_methods_pair = {
+ RTorrent: methods,
+ rtorrent.file.File: rtorrent.file.methods,
+ rtorrent.torrent.Torrent: rtorrent.torrent.methods,
+ rtorrent.tracker.Tracker: rtorrent.tracker.methods,
+ rtorrent.peer.Peer: rtorrent.peer.methods,
+}
+for c in class_methods_pair.keys():
+ rtorrent.rpc._build_rpc_methods(c, class_methods_pair[c])
+ _build_class_methods(c)
diff --git a/libs/rtorrent/common.py b/libs/rtorrent/common.py
new file mode 100755
index 00000000..371c71c3
--- /dev/null
+++ b/libs/rtorrent/common.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+from rtorrent.compat import is_py3
+
+
+def bool_to_int(value):
+ """Translates python booleans to RPC-safe integers"""
+ if value is True:
+ return("1")
+ elif value is False:
+ return("0")
+ else:
+ return(value)
+
+
+def cmd_exists(cmds_list, cmd):
+ """Check if given command is in list of available commands
+
+ @param cmds_list: see L{RTorrent._rpc_methods}
+ @type cmds_list: list
+
+ @param cmd: name of command to be checked
+ @type cmd: str
+
+ @return: bool
+ """
+
+ return(cmd in cmds_list)
+
+
+def find_torrent(info_hash, torrent_list):
+ """Find torrent file in given list of Torrent classes
+
+ @param info_hash: info hash of torrent
+ @type info_hash: str
+
+ @param torrent_list: list of L{Torrent} instances (see L{RTorrent.get_torrents})
+ @type torrent_list: list
+
+ @return: L{Torrent} instance, or -1 if not found
+ """
+ for t in torrent_list:
+ if t.info_hash == info_hash:
+ return t
+
+ return None
+
+
+def is_valid_port(port):
+ """Check if given port is valid"""
+ return(0 <= int(port) <= 65535)
+
+
+def convert_version_tuple_to_str(t):
+ return(".".join([str(n) for n in t]))
+
+
+def safe_repr(fmt, *args, **kwargs):
+ """ Formatter that handles unicode arguments """
+
+ if not is_py3():
+ # unicode fmt can take str args, str fmt cannot take unicode args
+ fmt = fmt.decode("utf-8")
+ out = fmt.format(*args, **kwargs)
+ return out.encode("utf-8")
+ else:
+ return fmt.format(*args, **kwargs)
diff --git a/libs/rtorrent/compat.py b/libs/rtorrent/compat.py
new file mode 100755
index 00000000..1778818b
--- /dev/null
+++ b/libs/rtorrent/compat.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import sys
+
+
+def is_py3():
+ return sys.version_info[0] == 3
+
+if is_py3():
+ import xmlrpc.client as xmlrpclib
+else:
+ import xmlrpclib
diff --git a/libs/rtorrent/err.py b/libs/rtorrent/err.py
new file mode 100755
index 00000000..920b8385
--- /dev/null
+++ b/libs/rtorrent/err.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from rtorrent.common import convert_version_tuple_to_str
+
+
+class RTorrentVersionError(Exception):
+ def __init__(self, min_version, cur_version):
+ self.min_version = min_version
+ self.cur_version = cur_version
+ self.msg = "Minimum version required: {0}".format(
+ convert_version_tuple_to_str(min_version))
+
+ def __str__(self):
+ return(self.msg)
+
+
+class MethodError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+ def __str__(self):
+ return(self.msg)
diff --git a/libs/rtorrent/file.py b/libs/rtorrent/file.py
new file mode 100755
index 00000000..a3db35cf
--- /dev/null
+++ b/libs/rtorrent/file.py
@@ -0,0 +1,91 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# from rtorrent.rpc import Method
+import rtorrent.rpc
+
+from rtorrent.common import safe_repr
+
+Method = rtorrent.rpc.Method
+
+
+class File:
+ """Represents an individual file within a L{Torrent} instance."""
+
+ def __init__(self, _rt_obj, info_hash, index, **kwargs):
+ self._rt_obj = _rt_obj
+ self.info_hash = info_hash # : info hash for the torrent the file is associated with
+ self.index = index # : The position of the file within the file list
+ for k in kwargs.keys():
+ setattr(self, k, kwargs.get(k, None))
+
+ self.rpc_id = "{0}:f{1}".format(
+ self.info_hash, self.index) # : unique id to pass to rTorrent
+
+ def update(self):
+ """Refresh file data
+
+ @note: All fields are stored as attributes to self.
+
+ @return: None
+ """
+ multicall = rtorrent.rpc.Multicall(self)
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ for method in retriever_methods:
+ multicall.add(method, self.rpc_id)
+
+ multicall.call()
+
+ def __repr__(self):
+ return safe_repr("File(index={0} path=\"{1}\")", self.index, self.path)
+
+methods = [
+ # RETRIEVERS
+ Method(File, 'get_last_touched', 'f.get_last_touched'),
+ Method(File, 'get_range_second', 'f.get_range_second'),
+ Method(File, 'get_size_bytes', 'f.get_size_bytes'),
+ Method(File, 'get_priority', 'f.get_priority'),
+ Method(File, 'get_match_depth_next', 'f.get_match_depth_next'),
+ Method(File, 'is_resize_queued', 'f.is_resize_queued',
+ boolean=True,
+ ),
+ Method(File, 'get_range_first', 'f.get_range_first'),
+ Method(File, 'get_match_depth_prev', 'f.get_match_depth_prev'),
+ Method(File, 'get_path', 'f.get_path'),
+ Method(File, 'get_completed_chunks', 'f.get_completed_chunks'),
+ Method(File, 'get_path_components', 'f.get_path_components'),
+ Method(File, 'is_created', 'f.is_created',
+ boolean=True,
+ ),
+ Method(File, 'is_open', 'f.is_open',
+ boolean=True,
+ ),
+ Method(File, 'get_size_chunks', 'f.get_size_chunks'),
+ Method(File, 'get_offset', 'f.get_offset'),
+ Method(File, 'get_frozen_path', 'f.get_frozen_path'),
+ Method(File, 'get_path_depth', 'f.get_path_depth'),
+ Method(File, 'is_create_queued', 'f.is_create_queued',
+ boolean=True,
+ ),
+
+
+ # MODIFIERS
+]
diff --git a/libs/rtorrent/group.py b/libs/rtorrent/group.py
new file mode 100755
index 00000000..e8246aa8
--- /dev/null
+++ b/libs/rtorrent/group.py
@@ -0,0 +1,84 @@
+# Copyright (c) 2013 Dean Gardiner,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import rtorrent.rpc
+
+Method = rtorrent.rpc.Method
+
+
+class Group:
+ __name__ = 'Group'
+
+ def __init__(self, _rt_obj, name):
+ self._rt_obj = _rt_obj
+ self.name = name
+
+ self.methods = [
+ # RETRIEVERS
+ Method(Group, 'get_max', 'group.' + self.name + '.ratio.max', varname='max'),
+ Method(Group, 'get_min', 'group.' + self.name + '.ratio.min', varname='min'),
+ Method(Group, 'get_upload', 'group.' + self.name + '.ratio.upload', varname='upload'),
+
+ # MODIFIERS
+ Method(Group, 'set_max', 'group.' + self.name + '.ratio.max.set', varname='max'),
+ Method(Group, 'set_min', 'group.' + self.name + '.ratio.min.set', varname='min'),
+ Method(Group, 'set_upload', 'group.' + self.name + '.ratio.upload.set', varname='upload')
+ ]
+
+ rtorrent.rpc._build_rpc_methods(self, self.methods)
+
+ # Setup multicall_add method
+ caller = lambda multicall, method, *args: \
+ multicall.add(method, *args)
+ setattr(self, "multicall_add", caller)
+
+ def _get_prefix(self):
+ return 'group.' + self.name + '.ratio.'
+
+ def update(self):
+ multicall = rtorrent.rpc.Multicall(self)
+
+ retriever_methods = [m for m in self.methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+
+ for method in retriever_methods:
+ multicall.add(method)
+
+ multicall.call()
+
+ def enable(self):
+ p = self._rt_obj._get_conn()
+ return getattr(p, self._get_prefix() + 'enable')()
+
+ def disable(self):
+ p = self._rt_obj._get_conn()
+ return getattr(p, self._get_prefix() + 'disable')()
+
+ def set_command(self, *methods):
+ methods = [m + '=' for m in methods]
+
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(
+ m, 'system.method.set',
+ self._get_prefix() + 'command',
+ *methods
+ )
+
+ return(m.call()[-1])
diff --git a/libs/rtorrent/lib/__init__.py b/libs/rtorrent/lib/__init__.py
new file mode 100755
index 00000000..e69de29b
diff --git a/libs/rtorrent/lib/bencode.py b/libs/rtorrent/lib/bencode.py
new file mode 100755
index 00000000..97bd2f0e
--- /dev/null
+++ b/libs/rtorrent/lib/bencode.py
@@ -0,0 +1,281 @@
+# Copyright (C) 2011 by clueless
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# Version: 20111107
+#
+# Changelog
+# ---------
+# 2011-11-07 - Added support for Python2 (tested on 2.6)
+# 2011-10-03 - Fixed: moved check for end of list at the top of the while loop
+# in _decode_list (in case the list is empty) (Chris Lucas)
+# - Converted dictionary keys to str
+# 2011-04-24 - Changed date format to YYYY-MM-DD for versioning, bigger
+# integer denotes a newer version
+# - Fixed a bug that would treat False as an integral type but
+# encode it using the 'False' string, attempting to encode a
+# boolean now results in an error
+# - Fixed a bug where an integer value of 0 in a list or
+# dictionary resulted in a parse error while decoding
+#
+# 2011-04-03 - Original release
+
+import sys
+
+_py3 = sys.version_info[0] == 3
+
+if _py3:
+ _VALID_STRING_TYPES = (str,)
+else:
+ _VALID_STRING_TYPES = (str, unicode) # @UndefinedVariable
+
+_TYPE_INT = 1
+_TYPE_STRING = 2
+_TYPE_LIST = 3
+_TYPE_DICTIONARY = 4
+_TYPE_END = 5
+_TYPE_INVALID = 6
+
+# Function to determine the type of he next value/item
+# Arguments:
+# char First character of the string that is to be decoded
+# Return value:
+# Returns an integer that describes what type the next value/item is
+
+
+def _gettype(char):
+ if not isinstance(char, int):
+ char = ord(char)
+ if char == 0x6C: # 'l'
+ return _TYPE_LIST
+ elif char == 0x64: # 'd'
+ return _TYPE_DICTIONARY
+ elif char == 0x69: # 'i'
+ return _TYPE_INT
+ elif char == 0x65: # 'e'
+ return _TYPE_END
+ elif char >= 0x30 and char <= 0x39: # '0' '9'
+ return _TYPE_STRING
+ else:
+ return _TYPE_INVALID
+
+# Function to parse a string from the bendcoded data
+# Arguments:
+# data bencoded data, must be guaranteed to be a string
+# Return Value:
+# Returns a tuple, the first member of the tuple is the parsed string
+# The second member is whatever remains of the bencoded data so it can
+# be used to parse the next part of the data
+
+
+def _decode_string(data):
+ end = 1
+ # if py3, data[end] is going to be an int
+ # if py2, data[end] will be a string
+ if _py3:
+ char = 0x3A
+ else:
+ char = chr(0x3A)
+
+ while data[end] != char: # ':'
+ end = end + 1
+ strlen = int(data[:end])
+ return (data[end + 1:strlen + end + 1], data[strlen + end + 1:])
+
+# Function to parse an integer from the bencoded data
+# Arguments:
+# data bencoded data, must be guaranteed to be an integer
+# Return Value:
+# Returns a tuple, the first member of the tuple is the parsed string
+# The second member is whatever remains of the bencoded data so it can
+# be used to parse the next part of the data
+
+
+def _decode_int(data):
+ end = 1
+ # if py3, data[end] is going to be an int
+ # if py2, data[end] will be a string
+ if _py3:
+ char = 0x65
+ else:
+ char = chr(0x65)
+
+ while data[end] != char: # 'e'
+ end = end + 1
+ return (int(data[1:end]), data[end + 1:])
+
+# Function to parse a bencoded list
+# Arguments:
+# data bencoded data, must be guaranted to be the start of a list
+# Return Value:
+# Returns a tuple, the first member of the tuple is the parsed list
+# The second member is whatever remains of the bencoded data so it can
+# be used to parse the next part of the data
+
+
+def _decode_list(data):
+ x = []
+ overflow = data[1:]
+ while True: # Loop over the data
+ if _gettype(overflow[0]) == _TYPE_END: # - Break if we reach the end of the list
+ return (x, overflow[1:]) # and return the list and overflow
+
+ value, overflow = _decode(overflow) #
+ if isinstance(value, bool) or overflow == '': # - if we have a parse error
+ return (False, False) # Die with error
+ else: # - Otherwise
+ x.append(value) # add the value to the list
+
+
+# Function to parse a bencoded list
+# Arguments:
+# data bencoded data, must be guaranted to be the start of a list
+# Return Value:
+# Returns a tuple, the first member of the tuple is the parsed dictionary
+# The second member is whatever remains of the bencoded data so it can
+# be used to parse the next part of the data
+def _decode_dict(data):
+ x = {}
+ overflow = data[1:]
+ while True: # Loop over the data
+ if _gettype(overflow[0]) != _TYPE_STRING: # - If the key is not a string
+ return (False, False) # Die with error
+ key, overflow = _decode(overflow) #
+ if key == False or overflow == '': # - If parse error
+ return (False, False) # Die with error
+ value, overflow = _decode(overflow) #
+ if isinstance(value, bool) or overflow == '': # - If parse error
+ print("Error parsing value")
+ print(value)
+ print(overflow)
+ return (False, False) # Die with error
+ else:
+ # don't use bytes for the key
+ key = key.decode()
+ x[key] = value
+ if _gettype(overflow[0]) == _TYPE_END:
+ return (x, overflow[1:])
+
+# Arguments:
+# data bencoded data in bytes format
+# Return Values:
+# Returns a tuple, the first member is the parsed data, could be a string,
+# an integer, a list or a dictionary, or a combination of those
+# The second member is the leftover of parsing, if everything parses correctly this
+# should be an empty byte string
+
+
+def _decode(data):
+ btype = _gettype(data[0])
+ if btype == _TYPE_INT:
+ return _decode_int(data)
+ elif btype == _TYPE_STRING:
+ return _decode_string(data)
+ elif btype == _TYPE_LIST:
+ return _decode_list(data)
+ elif btype == _TYPE_DICTIONARY:
+ return _decode_dict(data)
+ else:
+ return (False, False)
+
+# Function to decode bencoded data
+# Arguments:
+# data bencoded data, can be str or bytes
+# Return Values:
+# Returns the decoded data on success, this coud be bytes, int, dict or list
+# or a combinatin of those
+# If an error occurs the return value is False
+
+
+def decode(data):
+ # if isinstance(data, str):
+ # data = data.encode()
+ decoded, overflow = _decode(data)
+ return decoded
+
+# Args: data as integer
+# return: encoded byte string
+
+
+def _encode_int(data):
+ return b'i' + str(data).encode() + b'e'
+
+# Args: data as string or bytes
+# Return: encoded byte string
+
+
+def _encode_string(data):
+ return str(len(data)).encode() + b':' + data
+
+# Args: data as list
+# Return: Encoded byte string, false on error
+
+
+def _encode_list(data):
+ elist = b'l'
+ for item in data:
+ eitem = encode(item)
+ if eitem == False:
+ return False
+ elist += eitem
+ return elist + b'e'
+
+# Args: data as dict
+# Return: encoded byte string, false on error
+
+
+def _encode_dict(data):
+ edict = b'd'
+ keys = []
+ for key in data:
+ if not isinstance(key, _VALID_STRING_TYPES) and not isinstance(key, bytes):
+ return False
+ keys.append(key)
+ keys.sort()
+ for key in keys:
+ ekey = encode(key)
+ eitem = encode(data[key])
+ if ekey == False or eitem == False:
+ return False
+ edict += ekey + eitem
+ return edict + b'e'
+
+# Function to encode a variable in bencoding
+# Arguments:
+# data Variable to be encoded, can be a list, dict, str, bytes, int or a combination of those
+# Return Values:
+# Returns the encoded data as a byte string when successful
+# If an error occurs the return value is False
+
+
+def encode(data):
+ if isinstance(data, bool):
+ return False
+ elif isinstance(data, int):
+ return _encode_int(data)
+ elif isinstance(data, bytes):
+ return _encode_string(data)
+ elif isinstance(data, _VALID_STRING_TYPES):
+ return _encode_string(data.encode())
+ elif isinstance(data, list):
+ return _encode_list(data)
+ elif isinstance(data, dict):
+ return _encode_dict(data)
+ else:
+ return False
diff --git a/libs/rtorrent/lib/torrentparser.py b/libs/rtorrent/lib/torrentparser.py
new file mode 100755
index 00000000..19dd12aa
--- /dev/null
+++ b/libs/rtorrent/lib/torrentparser.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from rtorrent.compat import is_py3
+import os.path
+import re
+import rtorrent.lib.bencode as bencode
+import hashlib
+
+if is_py3():
+ from urllib.request import urlopen # @UnresolvedImport @UnusedImport
+else:
+ from urllib2 import urlopen # @UnresolvedImport @Reimport
+
+
+class TorrentParser():
+ def __init__(self, torrent):
+ """Decode and parse given torrent
+
+ @param torrent: handles: urls, file paths, string of torrent data
+ @type torrent: str
+
+ @raise AssertionError: Can be raised for a couple reasons:
+ - If _get_raw_torrent() couldn't figure out
+ what X{torrent} is
+ - if X{torrent} isn't a valid bencoded torrent file
+ """
+ self.torrent = torrent
+ self._raw_torrent = None # : testing yo
+ self._torrent_decoded = None # : what up
+ self.file_type = None
+
+ self._get_raw_torrent()
+ assert self._raw_torrent is not None, "Couldn't get raw_torrent."
+ if self._torrent_decoded is None:
+ self._decode_torrent()
+ assert isinstance(self._torrent_decoded, dict), "Invalid torrent file."
+ self._parse_torrent()
+
+ def _is_raw(self):
+ raw = False
+ if isinstance(self.torrent, (str, bytes)):
+ if isinstance(self._decode_torrent(self.torrent), dict):
+ raw = True
+ else:
+ # reset self._torrent_decoded (currently equals False)
+ self._torrent_decoded = None
+
+ return(raw)
+
+ def _get_raw_torrent(self):
+ """Get raw torrent data by determining what self.torrent is"""
+ # already raw?
+ if self._is_raw():
+ self.file_type = "raw"
+ self._raw_torrent = self.torrent
+ return
+ # local file?
+ if os.path.isfile(self.torrent):
+ self.file_type = "file"
+ self._raw_torrent = open(self.torrent, "rb").read()
+ # url?
+ elif re.search("^(http|ftp):\/\/", self.torrent, re.I):
+ self.file_type = "url"
+ self._raw_torrent = urlopen(self.torrent).read()
+
+ def _decode_torrent(self, raw_torrent=None):
+ if raw_torrent is None:
+ raw_torrent = self._raw_torrent
+ self._torrent_decoded = bencode.decode(raw_torrent)
+ return(self._torrent_decoded)
+
+ def _calc_info_hash(self):
+ self.info_hash = None
+ if "info" in self._torrent_decoded.keys():
+ info_dict = self._torrent_decoded["info"]
+ self.info_hash = hashlib.sha1(bencode.encode(
+ info_dict)).hexdigest().upper()
+
+ return(self.info_hash)
+
+ def _parse_torrent(self):
+ for k in self._torrent_decoded:
+ key = k.replace(" ", "_").lower()
+ setattr(self, key, self._torrent_decoded[k])
+
+ self._calc_info_hash()
+
+
+class NewTorrentParser(object):
+ @staticmethod
+ def _read_file(fp):
+ return fp.read()
+
+ @staticmethod
+ def _write_file(fp):
+ fp.write()
+ return fp
+
+ @staticmethod
+ def _decode_torrent(data):
+ return bencode.decode(data)
+
+ def __init__(self, input):
+ self.input = input
+ self._raw_torrent = None
+ self._decoded_torrent = None
+ self._hash_outdated = False
+
+ if isinstance(self.input, (str, bytes)):
+ # path to file?
+ if os.path.isfile(self.input):
+ self._raw_torrent = self._read_file(open(self.input, "rb"))
+ else:
+ # assume input was the raw torrent data (do we really want
+ # this?)
+ self._raw_torrent = self.input
+
+ # file-like object?
+ elif self.input.hasattr("read"):
+ self._raw_torrent = self._read_file(self.input)
+
+ assert self._raw_torrent is not None, "Invalid input: input must be a path or a file-like object"
+
+ self._decoded_torrent = self._decode_torrent(self._raw_torrent)
+
+ assert isinstance(
+ self._decoded_torrent, dict), "File could not be decoded"
+
+ def _calc_info_hash(self):
+ self.info_hash = None
+ info_dict = self._torrent_decoded["info"]
+ self.info_hash = hashlib.sha1(bencode.encode(
+ info_dict)).hexdigest().upper()
+
+ return(self.info_hash)
+
+ def set_tracker(self, tracker):
+ self._decoded_torrent["announce"] = tracker
+
+ def get_tracker(self):
+ return self._decoded_torrent.get("announce")
diff --git a/libs/rtorrent/lib/xmlrpc/__init__.py b/libs/rtorrent/lib/xmlrpc/__init__.py
new file mode 100755
index 00000000..e69de29b
diff --git a/libs/rtorrent/lib/xmlrpc/http.py b/libs/rtorrent/lib/xmlrpc/http.py
new file mode 100755
index 00000000..3eb85210
--- /dev/null
+++ b/libs/rtorrent/lib/xmlrpc/http.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from rtorrent.compat import xmlrpclib
+
+HTTPServerProxy = xmlrpclib.ServerProxy
diff --git a/libs/rtorrent/peer.py b/libs/rtorrent/peer.py
new file mode 100755
index 00000000..61ca0941
--- /dev/null
+++ b/libs/rtorrent/peer.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# from rtorrent.rpc import Method
+import rtorrent.rpc
+
+from rtorrent.common import safe_repr
+
+Method = rtorrent.rpc.Method
+
+
+class Peer:
+ """Represents an individual peer within a L{Torrent} instance."""
+ def __init__(self, _rt_obj, info_hash, **kwargs):
+ self._rt_obj = _rt_obj
+ self.info_hash = info_hash # : info hash for the torrent the peer is associated with
+ for k in kwargs.keys():
+ setattr(self, k, kwargs.get(k, None))
+
+ self.rpc_id = "{0}:p{1}".format(
+ self.info_hash, self.id) # : unique id to pass to rTorrent
+
+ def __repr__(self):
+ return safe_repr("Peer(id={0})", self.id)
+
+ def update(self):
+ """Refresh peer data
+
+ @note: All fields are stored as attributes to self.
+
+ @return: None
+ """
+ multicall = rtorrent.rpc.Multicall(self)
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ for method in retriever_methods:
+ multicall.add(method, self.rpc_id)
+
+ multicall.call()
+
+methods = [
+ # RETRIEVERS
+ Method(Peer, 'is_preferred', 'p.is_preferred',
+ boolean=True,
+ ),
+ Method(Peer, 'get_down_rate', 'p.get_down_rate'),
+ Method(Peer, 'is_unwanted', 'p.is_unwanted',
+ boolean=True,
+ ),
+ Method(Peer, 'get_peer_total', 'p.get_peer_total'),
+ Method(Peer, 'get_peer_rate', 'p.get_peer_rate'),
+ Method(Peer, 'get_port', 'p.get_port'),
+ Method(Peer, 'is_snubbed', 'p.is_snubbed',
+ boolean=True,
+ ),
+ Method(Peer, 'get_id_html', 'p.get_id_html'),
+ Method(Peer, 'get_up_rate', 'p.get_up_rate'),
+ Method(Peer, 'is_banned', 'p.banned',
+ boolean=True,
+ ),
+ Method(Peer, 'get_completed_percent', 'p.get_completed_percent'),
+ Method(Peer, 'completed_percent', 'p.completed_percent'),
+ Method(Peer, 'get_id', 'p.get_id'),
+ Method(Peer, 'is_obfuscated', 'p.is_obfuscated',
+ boolean=True,
+ ),
+ Method(Peer, 'get_down_total', 'p.get_down_total'),
+ Method(Peer, 'get_client_version', 'p.get_client_version'),
+ Method(Peer, 'get_address', 'p.get_address'),
+ Method(Peer, 'is_incoming', 'p.is_incoming',
+ boolean=True,
+ ),
+ Method(Peer, 'is_encrypted', 'p.is_encrypted',
+ boolean=True,
+ ),
+ Method(Peer, 'get_options_str', 'p.get_options_str'),
+ Method(Peer, 'get_client_version', 'p.client_version'),
+ Method(Peer, 'get_up_total', 'p.get_up_total'),
+
+ # MODIFIERS
+]
diff --git a/libs/rtorrent/rpc/__init__.py b/libs/rtorrent/rpc/__init__.py
new file mode 100755
index 00000000..034f4eef
--- /dev/null
+++ b/libs/rtorrent/rpc/__init__.py
@@ -0,0 +1,369 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from base64 import encodestring
+import httplib
+import inspect
+import string
+
+import rtorrent
+import re
+from rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
+ safe_repr
+from rtorrent.err import RTorrentVersionError, MethodError
+from rtorrent.compat import xmlrpclib
+
+
+class BasicAuthTransport(xmlrpclib.Transport):
+ def __init__(self, username=None, password=None):
+ xmlrpclib.Transport.__init__(self)
+ self.username = username
+ self.password = password
+
+ def send_auth(self, h):
+ if self.username is not None and self.password is not None:
+ h.putheader('AUTHORIZATION', "Basic %s" % string.replace(
+ encodestring("%s:%s" % (self.username, self.password)),
+ "\012", ""
+ ))
+
+ def single_request(self, host, handler, request_body, verbose=0):
+ # issue XML-RPC request
+
+ h = self.make_connection(host)
+ if verbose:
+ h.set_debuglevel(1)
+
+ try:
+ self.send_request(h, handler, request_body)
+ self.send_host(h, host)
+ self.send_user_agent(h)
+ self.send_auth(h)
+ self.send_content(h, request_body)
+
+ response = h.getresponse(buffering=True)
+ if response.status == 200:
+ self.verbose = verbose
+ return self.parse_response(response)
+ except xmlrpclib.Fault:
+ raise
+ except Exception:
+ self.close()
+ raise
+
+ #discard any response data and raise exception
+ if (response.getheader("content-length", 0)):
+ response.read()
+ raise xmlrpclib.ProtocolError(
+ host + handler,
+ response.status, response.reason,
+ response.msg,
+ )
+
+
+def get_varname(rpc_call):
+ """Transform rpc method into variable name.
+
+ @newfield example: Example
+ @example: if the name of the rpc method is 'p.get_down_rate', the variable
+ name will be 'down_rate'
+ """
+ # extract variable name from xmlrpc func name
+ r = re.search(
+ "([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I)
+ if r:
+ return(r.groups()[-1])
+ else:
+ return(None)
+
+
+def _handle_unavailable_rpc_method(method, rt_obj):
+ msg = "Method isn't available."
+ if rt_obj._get_client_version_tuple() < method.min_version:
+ msg = "This method is only available in " \
+ "RTorrent version v{0} or later".format(
+ convert_version_tuple_to_str(method.min_version))
+
+ raise MethodError(msg)
+
+
+class DummyClass:
+ def __init__(self):
+ pass
+
+
+class Method:
+ """Represents an individual RPC method"""
+
+ def __init__(self, _class, method_name,
+ rpc_call, docstring=None, varname=None, **kwargs):
+ self._class = _class # : Class this method is associated with
+ self.class_name = _class.__name__
+ self.method_name = method_name # : name of public-facing method
+ self.rpc_call = rpc_call # : name of rpc method
+ self.docstring = docstring # : docstring for rpc method (optional)
+ self.varname = varname # : variable for the result of the method call, usually set to self.varname
+ self.min_version = kwargs.get("min_version", (
+ 0, 0, 0)) # : Minimum version of rTorrent required
+ self.boolean = kwargs.get("boolean", False) # : returns boolean value?
+ self.post_process_func = kwargs.get(
+ "post_process_func", None) # : custom post process function
+ self.aliases = kwargs.get(
+ "aliases", []) # : aliases for method (optional)
+ self.required_args = []
+ #: Arguments required when calling the method (not utilized)
+
+ self.method_type = self._get_method_type()
+
+ if self.varname is None:
+ self.varname = get_varname(self.rpc_call)
+ assert self.varname is not None, "Couldn't get variable name."
+
+ def __repr__(self):
+ return safe_repr("Method(method_name='{0}', rpc_call='{1}')",
+ self.method_name, self.rpc_call)
+
+ def _get_method_type(self):
+ """Determine whether method is a modifier or a retriever"""
+ if self.method_name[:4] == "set_": return('m') # modifier
+ else:
+ return('r') # retriever
+
+ def is_modifier(self):
+ if self.method_type == 'm':
+ return(True)
+ else:
+ return(False)
+
+ def is_retriever(self):
+ if self.method_type == 'r':
+ return(True)
+ else:
+ return(False)
+
+ def is_available(self, rt_obj):
+ if rt_obj._get_client_version_tuple() < self.min_version or \
+ self.rpc_call not in rt_obj._get_rpc_methods():
+ return(False)
+ else:
+ return(True)
+
+
+class Multicall:
+ def __init__(self, class_obj, **kwargs):
+ self.class_obj = class_obj
+ if class_obj.__class__.__name__ == "RTorrent":
+ self.rt_obj = class_obj
+ else:
+ self.rt_obj = class_obj._rt_obj
+ self.calls = []
+
+ def add(self, method, *args):
+ """Add call to multicall
+
+ @param method: L{Method} instance or name of raw RPC method
+ @type method: Method or str
+
+ @param args: call arguments
+ """
+ # if a raw rpc method was given instead of a Method instance,
+ # try and find the instance for it. And if all else fails, create a
+ # dummy Method instance
+ if isinstance(method, str):
+ result = find_method(method)
+ # if result not found
+ if result == -1:
+ method = Method(DummyClass, method, method)
+ else:
+ method = result
+
+ # ensure method is available before adding
+ if not method.is_available(self.rt_obj):
+ _handle_unavailable_rpc_method(method, self.rt_obj)
+
+ self.calls.append((method, args))
+
+ def list_calls(self):
+ for c in self.calls:
+ print(c)
+
+ def call(self):
+ """Execute added multicall calls
+
+ @return: the results (post-processed), in the order they were added
+ @rtype: tuple
+ """
+ m = xmlrpclib.MultiCall(self.rt_obj._get_conn())
+ for call in self.calls:
+ method, args = call
+ rpc_call = getattr(method, "rpc_call")
+ getattr(m, rpc_call)(*args)
+
+ results = m()
+ results = tuple(results)
+ results_processed = []
+
+ for r, c in zip(results, self.calls):
+ method = c[0] # Method instance
+ result = process_result(method, r)
+ results_processed.append(result)
+ # assign result to class_obj
+ exists = hasattr(self.class_obj, method.varname)
+ if not exists or not inspect.ismethod(getattr(self.class_obj, method.varname)):
+ setattr(self.class_obj, method.varname, result)
+
+ return(tuple(results_processed))
+
+
+def call_method(class_obj, method, *args):
+ """Handles single RPC calls
+
+ @param class_obj: Peer/File/Torrent/Tracker/RTorrent instance
+ @type class_obj: object
+
+ @param method: L{Method} instance or name of raw RPC method
+ @type method: Method or str
+ """
+ if method.is_retriever():
+ args = args[:-1]
+ else:
+ assert args[-1] is not None, "No argument given."
+
+ if class_obj.__class__.__name__ == "RTorrent":
+ rt_obj = class_obj
+ else:
+ rt_obj = class_obj._rt_obj
+
+ # check if rpc method is even available
+ if not method.is_available(rt_obj):
+ _handle_unavailable_rpc_method(method, rt_obj)
+
+ m = Multicall(class_obj)
+ m.add(method, *args)
+ # only added one method, only getting one result back
+ ret_value = m.call()[0]
+
+ ####### OBSOLETE ##########################################################
+ # if method.is_retriever():
+ # #value = process_result(method, ret_value)
+ # value = ret_value #MultiCall already processed the result
+ # else:
+ # # we're setting the user's input to method.varname
+ # # but we'll return the value that xmlrpc gives us
+ # value = process_result(method, args[-1])
+ ##########################################################################
+
+ return(ret_value)
+
+
+def find_method(rpc_call):
+ """Return L{Method} instance associated with given RPC call"""
+ method_lists = [
+ rtorrent.methods,
+ rtorrent.file.methods,
+ rtorrent.tracker.methods,
+ rtorrent.peer.methods,
+ rtorrent.torrent.methods,
+ ]
+
+ for l in method_lists:
+ for m in l:
+ if m.rpc_call.lower() == rpc_call.lower():
+ return(m)
+
+ return(-1)
+
+
+def process_result(method, result):
+ """Process given C{B{result}} based on flags set in C{B{method}}
+
+ @param method: L{Method} instance
+ @type method: Method
+
+ @param result: result to be processed (the result of given L{Method} instance)
+
+ @note: Supported Processing:
+ - boolean - convert ones and zeros returned by rTorrent and
+ convert to python boolean values
+ """
+ # handle custom post processing function
+ if method.post_process_func is not None:
+ result = method.post_process_func(result)
+
+ # is boolean?
+ if method.boolean:
+ if result in [1, '1']:
+ result = True
+ elif result in [0, '0']:
+ result = False
+
+ return(result)
+
+
+def _build_rpc_methods(class_, method_list):
+ """Build glorified aliases to raw RPC methods"""
+ instance = None
+ if not inspect.isclass(class_):
+ instance = class_
+ class_ = instance.__class__
+
+ for m in method_list:
+ class_name = m.class_name
+ if class_name != class_.__name__:
+ continue
+
+ if class_name == "RTorrent":
+ caller = lambda self, arg = None, method = m:\
+ call_method(self, method, bool_to_int(arg))
+ elif class_name == "Torrent":
+ caller = lambda self, arg = None, method = m:\
+ call_method(self, method, self.rpc_id,
+ bool_to_int(arg))
+ elif class_name in ["Tracker", "File"]:
+ caller = lambda self, arg = None, method = m:\
+ call_method(self, method, self.rpc_id,
+ bool_to_int(arg))
+
+ elif class_name == "Peer":
+ caller = lambda self, arg = None, method = m:\
+ call_method(self, method, self.rpc_id,
+ bool_to_int(arg))
+
+ elif class_name == "Group":
+ caller = lambda arg = None, method = m: \
+ call_method(instance, method, bool_to_int(arg))
+
+ if m.docstring is None:
+ m.docstring = ""
+
+ # print(m)
+ docstring = """{0}
+
+ @note: Variable where the result for this method is stored: {1}.{2}""".format(
+ m.docstring,
+ class_name,
+ m.varname)
+
+ caller.__doc__ = docstring
+
+ for method_name in [m.method_name] + list(m.aliases):
+ if instance is None:
+ setattr(class_, method_name, caller)
+ else:
+ setattr(instance, method_name, caller)
diff --git a/libs/rtorrent/torrent.py b/libs/rtorrent/torrent.py
new file mode 100755
index 00000000..c610e368
--- /dev/null
+++ b/libs/rtorrent/torrent.py
@@ -0,0 +1,506 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import rtorrent.rpc
+# from rtorrent.rpc import Method
+import rtorrent.peer
+import rtorrent.tracker
+import rtorrent.file
+import rtorrent.compat
+
+from rtorrent.common import safe_repr
+
+Peer = rtorrent.peer.Peer
+Tracker = rtorrent.tracker.Tracker
+File = rtorrent.file.File
+Method = rtorrent.rpc.Method
+
+
+class Torrent:
+ """Represents an individual torrent within a L{RTorrent} instance."""
+
+ def __init__(self, _rt_obj, info_hash, **kwargs):
+ self._rt_obj = _rt_obj
+ self.info_hash = info_hash # : info hash for the torrent
+ self.rpc_id = self.info_hash # : unique id to pass to rTorrent
+ for k in kwargs.keys():
+ setattr(self, k, kwargs.get(k, None))
+
+ self.peers = []
+ self.trackers = []
+ self.files = []
+
+ self._call_custom_methods()
+
+ def __repr__(self):
+ return safe_repr("Torrent(info_hash=\"{0}\" name=\"{1}\")",
+ self.info_hash, self.name)
+
+ def _call_custom_methods(self):
+ """only calls methods that check instance variables."""
+ self._is_hash_checking_queued()
+ self._is_started()
+ self._is_paused()
+
+ def get_peers(self):
+ """Get list of Peer instances for given torrent.
+
+ @return: L{Peer} instances
+ @rtype: list
+
+ @note: also assigns return value to self.peers
+ """
+ self.peers = []
+ retriever_methods = [m for m in rtorrent.peer.methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ # need to leave 2nd arg empty (dunno why)
+ m = rtorrent.rpc.Multicall(self)
+ m.add("p.multicall", self.info_hash, "",
+ *[method.rpc_call + "=" for method in retriever_methods])
+
+ results = m.call()[0] # only sent one call, only need first result
+
+ for result in results:
+ results_dict = {}
+ # build results_dict
+ for m, r in zip(retriever_methods, result):
+ results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
+
+ self.peers.append(Peer(
+ self._rt_obj, self.info_hash, **results_dict))
+
+ return(self.peers)
+
+ def get_trackers(self):
+ """Get list of Tracker instances for given torrent.
+
+ @return: L{Tracker} instances
+ @rtype: list
+
+ @note: also assigns return value to self.trackers
+ """
+ self.trackers = []
+ retriever_methods = [m for m in rtorrent.tracker.methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+
+ # need to leave 2nd arg empty (dunno why)
+ m = rtorrent.rpc.Multicall(self)
+ m.add("t.multicall", self.info_hash, "",
+ *[method.rpc_call + "=" for method in retriever_methods])
+
+ results = m.call()[0] # only sent one call, only need first result
+
+ for result in results:
+ results_dict = {}
+ # build results_dict
+ for m, r in zip(retriever_methods, result):
+ results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
+
+ self.trackers.append(Tracker(
+ self._rt_obj, self.info_hash, **results_dict))
+
+ return(self.trackers)
+
+ def get_files(self):
+ """Get list of File instances for given torrent.
+
+ @return: L{File} instances
+ @rtype: list
+
+ @note: also assigns return value to self.files
+ """
+
+ self.files = []
+ retriever_methods = [m for m in rtorrent.file.methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ # 2nd arg can be anything, but it'll return all files in torrent
+ # regardless
+ m = rtorrent.rpc.Multicall(self)
+ m.add("f.multicall", self.info_hash, "",
+ *[method.rpc_call + "=" for method in retriever_methods])
+
+ results = m.call()[0] # only sent one call, only need first result
+
+ offset_method_index = retriever_methods.index(
+ rtorrent.rpc.find_method("f.get_offset"))
+
+ # make a list of the offsets of all the files, sort appropriately
+ offset_list = sorted([r[offset_method_index] for r in results])
+
+ for result in results:
+ results_dict = {}
+ # build results_dict
+ for m, r in zip(retriever_methods, result):
+ results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
+
+ # get proper index positions for each file (based on the file
+ # offset)
+ f_index = offset_list.index(results_dict["offset"])
+
+ self.files.append(File(self._rt_obj, self.info_hash,
+ f_index, **results_dict))
+
+ return(self.files)
+
+ def set_directory(self, d):
+ """Modify download directory
+
+ @note: Needs to stop torrent in order to change the directory.
+ Also doesn't restart after directory is set, that must be called
+ separately.
+ """
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.try_stop")
+ self.multicall_add(m, "d.set_directory", d)
+
+ self.directory = m.call()[-1]
+
+ def start(self):
+ """Start the torrent"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.try_start")
+ self.multicall_add(m, "d.is_active")
+
+ self.active = m.call()[-1]
+ return(self.active)
+
+ def stop(self):
+ """"Stop the torrent"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.try_stop")
+ self.multicall_add(m, "d.is_active")
+
+ self.active = m.call()[-1]
+ return(self.active)
+
+ def pause(self):
+ """Pause the torrent"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.pause")
+
+ return(m.call()[-1])
+
+ def resume(self):
+ """Resume the torrent"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.resume")
+
+ return(m.call()[-1])
+
+ def close(self):
+ """Close the torrent and it's files"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.close")
+
+ return(m.call()[-1])
+
+ def erase(self):
+ """Delete the torrent
+
+ @note: doesn't delete the downloaded files"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.erase")
+
+ return(m.call()[-1])
+
+ def check_hash(self):
+ """(Re)hash check the torrent"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.check_hash")
+
+ return(m.call()[-1])
+
+ def poll(self):
+ """poll rTorrent to get latest peer/tracker/file information"""
+ self.get_peers()
+ self.get_trackers()
+ self.get_files()
+
+ def update(self):
+ """Refresh torrent data
+
+ @note: All fields are stored as attributes to self.
+
+ @return: None
+ """
+ multicall = rtorrent.rpc.Multicall(self)
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ for method in retriever_methods:
+ multicall.add(method, self.rpc_id)
+
+ multicall.call()
+
+ # custom functions (only call private methods, since they only check
+ # local variables and are therefore faster)
+ self._call_custom_methods()
+
+ def accept_seeders(self, accept_seeds):
+ """Enable/disable whether the torrent connects to seeders
+
+ @param accept_seeds: enable/disable accepting seeders
+ @type accept_seeds: bool"""
+ if accept_seeds:
+ call = "d.accepting_seeders.enable"
+ else:
+ call = "d.accepting_seeders.disable"
+
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, call)
+
+ return(m.call()[-1])
+
+ def announce(self):
+ """Announce torrent info to tracker(s)"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.tracker_announce")
+
+ return(m.call()[-1])
+
+ @staticmethod
+ def _assert_custom_key_valid(key):
+ assert type(key) == int and key > 0 and key < 6, \
+ "key must be an integer between 1-5"
+
+ def get_custom(self, key):
+ """
+ Get custom value
+
+ @param key: the index for the custom field (between 1-5)
+ @type key: int
+
+ @rtype: str
+ """
+
+ self._assert_custom_key_valid(key)
+ m = rtorrent.rpc.Multicall(self)
+
+ field = "custom{0}".format(key)
+ self.multicall_add(m, "d.get_{0}".format(field))
+ setattr(self, field, m.call()[-1])
+
+ return (getattr(self, field))
+
+ def set_custom(self, key, value):
+ """
+ Set custom value
+
+ @param key: the index for the custom field (between 1-5)
+ @type key: int
+
+ @param value: the value to be stored
+ @type value: str
+
+ @return: if successful, value will be returned
+ @rtype: str
+ """
+
+ self._assert_custom_key_valid(key)
+ m = rtorrent.rpc.Multicall(self)
+
+ self.multicall_add(m, "d.set_custom{0}".format(key), value)
+
+ return(m.call()[-1])
+
+ def set_visible(self, view, visible=True):
+ p = self._rt_obj._get_conn()
+
+ if visible:
+ return p.view.set_visible(self.info_hash, view)
+ else:
+ return p.view.set_not_visible(self.info_hash, view)
+
+ ############################################################################
+ # CUSTOM METHODS (Not part of the official rTorrent API)
+ ##########################################################################
+ def _is_hash_checking_queued(self):
+ """Only checks instance variables, shouldn't be called directly"""
+ # if hashing == 3, then torrent is marked for hash checking
+ # if hash_checking == False, then torrent is waiting to be checked
+ self.hash_checking_queued = (self.hashing == 3 and
+ self.hash_checking is False)
+
+ return(self.hash_checking_queued)
+
+ def is_hash_checking_queued(self):
+ """Check if torrent is waiting to be hash checked
+
+ @note: Variable where the result for this method is stored Torrent.hash_checking_queued"""
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.get_hashing")
+ self.multicall_add(m, "d.is_hash_checking")
+ results = m.call()
+
+ setattr(self, "hashing", results[0])
+ setattr(self, "hash_checking", results[1])
+
+ return(self._is_hash_checking_queued())
+
+ def _is_paused(self):
+ """Only checks instance variables, shouldn't be called directly"""
+ self.paused = (self.state == 0)
+ return(self.paused)
+
+ def is_paused(self):
+ """Check if torrent is paused
+
+ @note: Variable where the result for this method is stored: Torrent.paused"""
+ self.get_state()
+ return(self._is_paused())
+
+ def _is_started(self):
+ """Only checks instance variables, shouldn't be called directly"""
+ self.started = (self.state == 1)
+ return(self.started)
+
+ def is_started(self):
+ """Check if torrent is started
+
+ @note: Variable where the result for this method is stored: Torrent.started"""
+ self.get_state()
+ return(self._is_started())
+
+
+methods = [
+ # RETRIEVERS
+ Method(Torrent, 'is_hash_checked', 'd.is_hash_checked',
+ boolean=True,
+ ),
+ Method(Torrent, 'is_hash_checking', 'd.is_hash_checking',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_peers_max', 'd.get_peers_max'),
+ Method(Torrent, 'get_tracker_focus', 'd.get_tracker_focus'),
+ Method(Torrent, 'get_skip_total', 'd.get_skip_total'),
+ Method(Torrent, 'get_state', 'd.get_state'),
+ Method(Torrent, 'get_peer_exchange', 'd.get_peer_exchange'),
+ Method(Torrent, 'get_down_rate', 'd.get_down_rate'),
+ Method(Torrent, 'get_connection_seed', 'd.get_connection_seed'),
+ Method(Torrent, 'get_uploads_max', 'd.get_uploads_max'),
+ Method(Torrent, 'get_priority_str', 'd.get_priority_str'),
+ Method(Torrent, 'is_open', 'd.is_open',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_peers_min', 'd.get_peers_min'),
+ Method(Torrent, 'get_peers_complete', 'd.get_peers_complete'),
+ Method(Torrent, 'get_tracker_numwant', 'd.get_tracker_numwant'),
+ Method(Torrent, 'get_connection_current', 'd.get_connection_current'),
+ Method(Torrent, 'is_complete', 'd.get_complete',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_peers_connected', 'd.get_peers_connected'),
+ Method(Torrent, 'get_chunk_size', 'd.get_chunk_size'),
+ Method(Torrent, 'get_state_counter', 'd.get_state_counter'),
+ Method(Torrent, 'get_base_filename', 'd.get_base_filename'),
+ Method(Torrent, 'get_state_changed', 'd.get_state_changed'),
+ Method(Torrent, 'get_peers_not_connected', 'd.get_peers_not_connected'),
+ Method(Torrent, 'get_directory', 'd.get_directory'),
+ Method(Torrent, 'is_incomplete', 'd.incomplete',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_tracker_size', 'd.get_tracker_size'),
+ Method(Torrent, 'is_multi_file', 'd.is_multi_file',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_local_id', 'd.get_local_id'),
+ Method(Torrent, 'get_ratio', 'd.get_ratio',
+ post_process_func=lambda x: x / 1000.0,
+ ),
+ Method(Torrent, 'get_loaded_file', 'd.get_loaded_file'),
+ Method(Torrent, 'get_max_file_size', 'd.get_max_file_size'),
+ Method(Torrent, 'get_size_chunks', 'd.get_size_chunks'),
+ Method(Torrent, 'is_pex_active', 'd.is_pex_active',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_hashing', 'd.get_hashing'),
+ Method(Torrent, 'get_bitfield', 'd.get_bitfield'),
+ Method(Torrent, 'get_local_id_html', 'd.get_local_id_html'),
+ Method(Torrent, 'get_connection_leech', 'd.get_connection_leech'),
+ Method(Torrent, 'get_peers_accounted', 'd.get_peers_accounted'),
+ Method(Torrent, 'get_message', 'd.get_message'),
+ Method(Torrent, 'is_active', 'd.is_active',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_size_bytes', 'd.get_size_bytes'),
+ Method(Torrent, 'get_ignore_commands', 'd.get_ignore_commands'),
+ Method(Torrent, 'get_creation_date', 'd.get_creation_date'),
+ Method(Torrent, 'get_base_path', 'd.get_base_path'),
+ Method(Torrent, 'get_left_bytes', 'd.get_left_bytes'),
+ Method(Torrent, 'get_size_files', 'd.get_size_files'),
+ Method(Torrent, 'get_size_pex', 'd.get_size_pex'),
+ Method(Torrent, 'is_private', 'd.is_private',
+ boolean=True,
+ ),
+ Method(Torrent, 'get_max_size_pex', 'd.get_max_size_pex'),
+ Method(Torrent, 'get_num_chunks_hashed', 'd.get_chunks_hashed',
+ aliases=("get_chunks_hashed",)),
+ Method(Torrent, 'get_num_chunks_wanted', 'd.wanted_chunks'),
+ Method(Torrent, 'get_priority', 'd.get_priority'),
+ Method(Torrent, 'get_skip_rate', 'd.get_skip_rate'),
+ Method(Torrent, 'get_completed_bytes', 'd.get_completed_bytes'),
+ Method(Torrent, 'get_name', 'd.get_name'),
+ Method(Torrent, 'get_completed_chunks', 'd.get_completed_chunks'),
+ Method(Torrent, 'get_throttle_name', 'd.get_throttle_name'),
+ Method(Torrent, 'get_free_diskspace', 'd.get_free_diskspace'),
+ Method(Torrent, 'get_directory_base', 'd.get_directory_base'),
+ Method(Torrent, 'get_hashing_failed', 'd.get_hashing_failed'),
+ Method(Torrent, 'get_tied_to_file', 'd.get_tied_to_file'),
+ Method(Torrent, 'get_down_total', 'd.get_down_total'),
+ Method(Torrent, 'get_bytes_done', 'd.get_bytes_done'),
+ Method(Torrent, 'get_up_rate', 'd.get_up_rate'),
+ Method(Torrent, 'get_up_total', 'd.get_up_total'),
+ Method(Torrent, 'is_accepting_seeders', 'd.accepting_seeders',
+ boolean=True,
+ ),
+ Method(Torrent, "get_chunks_seen", "d.chunks_seen",
+ min_version=(0, 9, 1),
+ ),
+ Method(Torrent, "is_partially_done", "d.is_partially_done",
+ boolean=True,
+ ),
+ Method(Torrent, "is_not_partially_done", "d.is_not_partially_done",
+ boolean=True,
+ ),
+ Method(Torrent, "get_time_started", "d.timestamp.started"),
+ Method(Torrent, "get_custom1", "d.get_custom1"),
+ Method(Torrent, "get_custom2", "d.get_custom2"),
+ Method(Torrent, "get_custom3", "d.get_custom3"),
+ Method(Torrent, "get_custom4", "d.get_custom4"),
+ Method(Torrent, "get_custom5", "d.get_custom5"),
+
+ # MODIFIERS
+ Method(Torrent, 'set_uploads_max', 'd.set_uploads_max'),
+ Method(Torrent, 'set_tied_to_file', 'd.set_tied_to_file'),
+ Method(Torrent, 'set_tracker_numwant', 'd.set_tracker_numwant'),
+ Method(Torrent, 'set_priority', 'd.set_priority'),
+ Method(Torrent, 'set_peers_max', 'd.set_peers_max'),
+ Method(Torrent, 'set_hashing_failed', 'd.set_hashing_failed'),
+ Method(Torrent, 'set_message', 'd.set_message'),
+ Method(Torrent, 'set_throttle_name', 'd.set_throttle_name'),
+ Method(Torrent, 'set_peers_min', 'd.set_peers_min'),
+ Method(Torrent, 'set_ignore_commands', 'd.set_ignore_commands'),
+ Method(Torrent, 'set_max_file_size', 'd.set_max_file_size'),
+ Method(Torrent, 'set_custom5', 'd.set_custom5'),
+ Method(Torrent, 'set_custom4', 'd.set_custom4'),
+ Method(Torrent, 'set_custom2', 'd.set_custom2'),
+ Method(Torrent, 'set_custom1', 'd.set_custom1'),
+ Method(Torrent, 'set_custom3', 'd.set_custom3'),
+ Method(Torrent, 'set_connection_current', 'd.set_connection_current'),
+]
diff --git a/libs/rtorrent/tracker.py b/libs/rtorrent/tracker.py
new file mode 100755
index 00000000..81af2e49
--- /dev/null
+++ b/libs/rtorrent/tracker.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2013 Chris Lucas,
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# from rtorrent.rpc import Method
+import rtorrent.rpc
+
+from rtorrent.common import safe_repr
+
+Method = rtorrent.rpc.Method
+
+
+class Tracker:
+ """Represents an individual tracker within a L{Torrent} instance."""
+
+ def __init__(self, _rt_obj, info_hash, **kwargs):
+ self._rt_obj = _rt_obj
+ self.info_hash = info_hash # : info hash for the torrent using this tracker
+ for k in kwargs.keys():
+ setattr(self, k, kwargs.get(k, None))
+
+ # for clarity's sake...
+ self.index = self.group # : position of tracker within the torrent's tracker list
+ self.rpc_id = "{0}:t{1}".format(
+ self.info_hash, self.index) # : unique id to pass to rTorrent
+
+ def __repr__(self):
+ return safe_repr("Tracker(index={0}, url=\"{1}\")",
+ self.index, self.url)
+
+ def enable(self):
+ """Alias for set_enabled("yes")"""
+ self.set_enabled("yes")
+
+ def disable(self):
+ """Alias for set_enabled("no")"""
+ self.set_enabled("no")
+
+ def update(self):
+ """Refresh tracker data
+
+ @note: All fields are stored as attributes to self.
+
+ @return: None
+ """
+ multicall = rtorrent.rpc.Multicall(self)
+ retriever_methods = [m for m in methods
+ if m.is_retriever() and m.is_available(self._rt_obj)]
+ for method in retriever_methods:
+ multicall.add(method, self.rpc_id)
+
+ multicall.call()
+
+methods = [
+ # RETRIEVERS
+ Method(Tracker, 'is_enabled', 't.is_enabled', boolean=True),
+ Method(Tracker, 'get_id', 't.get_id'),
+ Method(Tracker, 'get_scrape_incomplete', 't.get_scrape_incomplete'),
+ Method(Tracker, 'is_open', 't.is_open', boolean=True),
+ Method(Tracker, 'get_min_interval', 't.get_min_interval'),
+ Method(Tracker, 'get_scrape_downloaded', 't.get_scrape_downloaded'),
+ Method(Tracker, 'get_group', 't.get_group'),
+ Method(Tracker, 'get_scrape_time_last', 't.get_scrape_time_last'),
+ Method(Tracker, 'get_type', 't.get_type'),
+ Method(Tracker, 'get_normal_interval', 't.get_normal_interval'),
+ Method(Tracker, 'get_url', 't.get_url'),
+ Method(Tracker, 'get_scrape_complete', 't.get_scrape_complete',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_activity_time_last', 't.activity_time_last',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_activity_time_next', 't.activity_time_next',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_failed_time_last', 't.failed_time_last',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_failed_time_next', 't.failed_time_next',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_success_time_last', 't.success_time_last',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'get_success_time_next', 't.success_time_next',
+ min_version=(0, 8, 9),
+ ),
+ Method(Tracker, 'can_scrape', 't.can_scrape',
+ min_version=(0, 9, 1),
+ boolean=True
+ ),
+ Method(Tracker, 'get_failed_counter', 't.failed_counter',
+ min_version=(0, 8, 9)
+ ),
+ Method(Tracker, 'get_scrape_counter', 't.scrape_counter',
+ min_version=(0, 8, 9)
+ ),
+ Method(Tracker, 'get_success_counter', 't.success_counter',
+ min_version=(0, 8, 9)
+ ),
+ Method(Tracker, 'is_usable', 't.is_usable',
+ min_version=(0, 9, 1),
+ boolean=True
+ ),
+ Method(Tracker, 'is_busy', 't.is_busy',
+ min_version=(0, 9, 1),
+ boolean=True
+ ),
+ Method(Tracker, 'is_extra_tracker', 't.is_extra_tracker',
+ min_version=(0, 9, 1),
+ boolean=True,
+ ),
+ Method(Tracker, "get_latest_sum_peers", "t.latest_sum_peers",
+ min_version=(0, 9, 0)
+ ),
+ Method(Tracker, "get_latest_new_peers", "t.latest_new_peers",
+ min_version=(0, 9, 0)
+ ),
+
+ # MODIFIERS
+ Method(Tracker, 'set_enabled', 't.set_enabled'),
+]
diff --git a/libs/synchronousdeluge/__init__.py b/libs/synchronousdeluge/__init__.py
new file mode 100644
index 00000000..bf5b20fe
--- /dev/null
+++ b/libs/synchronousdeluge/__init__.py
@@ -0,0 +1,24 @@
+"""A synchronous implementation of the Deluge RPC protocol
+ based on gevent-deluge by Christopher Rosell.
+
+ https://github.com/chrippa/gevent-deluge
+
+Example usage:
+
+ from synchronousdeluge import DelgueClient
+
+ client = DelugeClient()
+ client.connect()
+
+ # Wait for value
+ download_location = client.core.get_config_value("download_location").get()
+"""
+
+
+__title__ = "synchronous-deluge"
+__version__ = "0.1"
+__author__ = "Christian Dale"
+
+from synchronousdeluge.client import DelugeClient
+from synchronousdeluge.exceptions import DelugeRPCError
+
diff --git a/libs/synchronousdeluge/client.py b/libs/synchronousdeluge/client.py
new file mode 100644
index 00000000..98a80848
--- /dev/null
+++ b/libs/synchronousdeluge/client.py
@@ -0,0 +1,135 @@
+import os
+
+from collections import defaultdict
+from itertools import imap
+
+from synchronousdeluge.exceptions import DelugeRPCError
+from synchronousdeluge.protocol import DelugeRPCRequest, DelugeRPCResponse
+from synchronousdeluge.transfer import DelugeTransfer
+
+__all__ = ["DelugeClient"]
+
+
+RPC_RESPONSE = 1
+RPC_ERROR = 2
+RPC_EVENT = 3
+
+
+class DelugeClient(object):
+ def __init__(self):
+ """A deluge client session."""
+ self.transfer = DelugeTransfer()
+ self.modules = []
+ self._request_counter = 0
+
+ def _get_local_auth(self):
+ xdg_config = os.path.expanduser(os.environ.get("XDG_CONFIG_HOME", "~/.config"))
+ config_home = os.path.join(xdg_config, "deluge")
+ auth_file = os.path.join(config_home, "auth")
+
+ username = password = ""
+ with open(auth_file) as fd:
+ for line in fd:
+ if line.startswith("#"):
+ continue
+
+ auth = line.split(":")
+ if len(auth) >= 2 and auth[0] == "localclient":
+ username, password = auth[0], auth[1]
+ break
+
+ return username, password
+
+ def _create_module_method(self, module, method):
+ fullname = "{0}.{1}".format(module, method)
+
+ def func(obj, *args, **kwargs):
+ return self.remote_call(fullname, *args, **kwargs)
+
+ func.__name__ = method
+
+ return func
+
+ def _introspect(self):
+ self.modules = []
+
+ methods = self.remote_call("daemon.get_method_list").get()
+ methodmap = defaultdict(dict)
+ splitter = lambda v: v.split(".")
+
+ for module, method in imap(splitter, methods):
+ methodmap[module][method] = self._create_module_method(module, method)
+
+ for module, methods in methodmap.items():
+ clsname = "DelugeModule{0}".format(module.capitalize())
+ cls = type(clsname, (), methods)
+ setattr(self, module, cls())
+ self.modules.append(module)
+
+ def remote_call(self, method, *args, **kwargs):
+ req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs)
+ message = next(self.transfer.send_request(req))
+
+ response = DelugeRPCResponse()
+
+ if not isinstance(message, tuple):
+ return
+
+ if len(message) < 3:
+ return
+
+ message_type = message[0]
+
+# if message_type == RPC_EVENT:
+# event = message[1]
+# values = message[2]
+#
+# if event in self._event_handlers:
+# for handler in self._event_handlers[event]:
+# gevent.spawn(handler, *values)
+#
+# elif message_type in (RPC_RESPONSE, RPC_ERROR):
+ if message_type in (RPC_RESPONSE, RPC_ERROR):
+ request_id = message[1]
+ value = message[2]
+
+ if request_id == self._request_counter :
+ if message_type == RPC_RESPONSE:
+ response.set(value)
+ elif message_type == RPC_ERROR:
+ err = DelugeRPCError(*value)
+ response.set_exception(err)
+
+ self._request_counter += 1
+ return response
+
+ def connect(self, host="127.0.0.1", port=58846, username="", password=""):
+ """Connects to a daemon process.
+
+ :param host: str, the hostname of the daemon
+ :param port: int, the port of the daemon
+ :param username: str, the username to login with
+ :param password: str, the password to login with
+ """
+
+ # Connect transport
+ self.transfer.connect((host, port))
+
+ # Attempt to fetch local auth info if needed
+ if not username and host in ("127.0.0.1", "localhost"):
+ username, password = self._get_local_auth()
+
+ # Authenticate
+ self.remote_call("daemon.login", username, password).get()
+
+ # Introspect available methods
+ self._introspect()
+
+ @property
+ def connected(self):
+ return self.transfer.connected
+
+ def disconnect(self):
+ """Disconnects from the daemon."""
+ self.transfer.disconnect()
+
diff --git a/libs/synchronousdeluge/exceptions.py b/libs/synchronousdeluge/exceptions.py
new file mode 100644
index 00000000..da6cf022
--- /dev/null
+++ b/libs/synchronousdeluge/exceptions.py
@@ -0,0 +1,11 @@
+__all__ = ["DelugeRPCError"]
+
+class DelugeRPCError(Exception):
+ def __init__(self, name, msg, traceback):
+ self.name = name
+ self.msg = msg
+ self.traceback = traceback
+
+ def __str__(self):
+ return "{0}: {1}: {2}".format(self.__class__.__name__, self.name, self.msg)
+
diff --git a/libs/synchronousdeluge/protocol.py b/libs/synchronousdeluge/protocol.py
new file mode 100644
index 00000000..756d4dfc
--- /dev/null
+++ b/libs/synchronousdeluge/protocol.py
@@ -0,0 +1,38 @@
+__all__ = ["DelugeRPCRequest", "DelugeRPCResponse"]
+
+class DelugeRPCRequest(object):
+ def __init__(self, request_id, method, *args, **kwargs):
+ self.request_id = request_id
+ self.method = method
+ self.args = args
+ self.kwargs = kwargs
+
+ def format(self):
+ return (self.request_id, self.method, self.args, self.kwargs)
+
+class DelugeRPCResponse(object):
+ def __init__(self):
+ self.value = None
+ self._exception = None
+
+ def successful(self):
+ return self._exception is None
+
+ @property
+ def exception(self):
+ if self._exception is not None:
+ return self._exception
+
+ def set(self, value=None):
+ self.value = value
+ self._exception = None
+
+ def set_exception(self, exception):
+ self._exception = exception
+
+ def get(self):
+ if self._exception is None:
+ return self.value
+ else:
+ raise self._exception
+
diff --git a/libs/synchronousdeluge/rencode.py b/libs/synchronousdeluge/rencode.py
new file mode 100644
index 00000000..e58c7154
--- /dev/null
+++ b/libs/synchronousdeluge/rencode.py
@@ -0,0 +1,433 @@
+
+"""
+rencode -- Web safe object pickling/unpickling.
+
+Public domain, Connelly Barnes 2006-2007.
+
+The rencode module is a modified version of bencode from the
+BitTorrent project. For complex, heterogeneous data structures with
+many small elements, r-encodings take up significantly less space than
+b-encodings:
+
+ >>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
+ 13
+ >>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
+ 26
+
+The rencode format is not standardized, and may change with different
+rencode module versions, so you should check that you are using the
+same rencode version throughout your project.
+"""
+
+__version__ = '1.0.1'
+__all__ = ['dumps', 'loads']
+
+# Original bencode module by Petru Paler, et al.
+#
+# Modifications by Connelly Barnes:
+#
+# - Added support for floats (sent as 32-bit or 64-bit in network
+# order), bools, None.
+# - Allowed dict keys to be of any serializable type.
+# - Lists/tuples are always decoded as tuples (thus, tuples can be
+# used as dict keys).
+# - Embedded extra information in the 'typecodes' to save some space.
+# - Added a restriction on integer length, so that malicious hosts
+# cannot pass us large integers which take a long time to decode.
+#
+# Licensed by Bram Cohen under the "MIT license":
+#
+# "Copyright (C) 2001-2002 Bram Cohen
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# The Software is provided "AS IS", without warranty of any kind,
+# express or implied, including but not limited to the warranties of
+# merchantability, fitness for a particular purpose and
+# noninfringement. In no event shall the authors or copyright holders
+# be liable for any claim, damages or other liability, whether in an
+# action of contract, tort or otherwise, arising from, out of or in
+# connection with the Software or the use or other dealings in the
+# Software."
+#
+# (The rencode module is licensed under the above license as well).
+#
+
+import struct
+import string
+from threading import Lock
+
+# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
+DEFAULT_FLOAT_BITS = 32
+
+# Maximum length of integer when written as base 10 string.
+MAX_INT_LENGTH = 64
+
+# The bencode 'typecodes' such as i, d, etc have been extended and
+# relocated on the base-256 character set.
+CHR_LIST = chr(59)
+CHR_DICT = chr(60)
+CHR_INT = chr(61)
+CHR_INT1 = chr(62)
+CHR_INT2 = chr(63)
+CHR_INT4 = chr(64)
+CHR_INT8 = chr(65)
+CHR_FLOAT32 = chr(66)
+CHR_FLOAT64 = chr(44)
+CHR_TRUE = chr(67)
+CHR_FALSE = chr(68)
+CHR_NONE = chr(69)
+CHR_TERM = chr(127)
+
+# Positive integers with value embedded in typecode.
+INT_POS_FIXED_START = 0
+INT_POS_FIXED_COUNT = 44
+
+# Dictionaries with length embedded in typecode.
+DICT_FIXED_START = 102
+DICT_FIXED_COUNT = 25
+
+# Negative integers with value embedded in typecode.
+INT_NEG_FIXED_START = 70
+INT_NEG_FIXED_COUNT = 32
+
+# Strings with length embedded in typecode.
+STR_FIXED_START = 128
+STR_FIXED_COUNT = 64
+
+# Lists with length embedded in typecode.
+LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
+LIST_FIXED_COUNT = 64
+
+def decode_int(x, f):
+ f += 1
+ newf = x.index(CHR_TERM, f)
+ if newf - f >= MAX_INT_LENGTH:
+ raise ValueError('overflow')
+ try:
+ n = int(x[f:newf])
+ except (OverflowError, ValueError):
+ n = long(x[f:newf])
+ if x[f] == '-':
+ if x[f + 1] == '0':
+ raise ValueError
+ elif x[f] == '0' and newf != f+1:
+ raise ValueError
+ return (n, newf+1)
+
+def decode_intb(x, f):
+ f += 1
+ return (struct.unpack('!b', x[f:f+1])[0], f+1)
+
+def decode_inth(x, f):
+ f += 1
+ return (struct.unpack('!h', x[f:f+2])[0], f+2)
+
+def decode_intl(x, f):
+ f += 1
+ return (struct.unpack('!l', x[f:f+4])[0], f+4)
+
+def decode_intq(x, f):
+ f += 1
+ return (struct.unpack('!q', x[f:f+8])[0], f+8)
+
+def decode_float32(x, f):
+ f += 1
+ n = struct.unpack('!f', x[f:f+4])[0]
+ return (n, f+4)
+
+def decode_float64(x, f):
+ f += 1
+ n = struct.unpack('!d', x[f:f+8])[0]
+ return (n, f+8)
+
+def decode_string(x, f):
+ colon = x.index(':', f)
+ try:
+ n = int(x[f:colon])
+ except (OverflowError, ValueError):
+ n = long(x[f:colon])
+ if x[f] == '0' and colon != f+1:
+ raise ValueError
+ colon += 1
+ s = x[colon:colon+n]
+ try:
+ t = s.decode("utf8")
+ if len(t) != len(s):
+ s = t
+ except UnicodeDecodeError:
+ pass
+ return (s, colon+n)
+
+def decode_list(x, f):
+ r, f = [], f+1
+ while x[f] != CHR_TERM:
+ v, f = decode_func[x[f]](x, f)
+ r.append(v)
+ return (tuple(r), f + 1)
+
+def decode_dict(x, f):
+ r, f = {}, f+1
+ while x[f] != CHR_TERM:
+ k, f = decode_func[x[f]](x, f)
+ r[k], f = decode_func[x[f]](x, f)
+ return (r, f + 1)
+
+def decode_true(x, f):
+ return (True, f+1)
+
+def decode_false(x, f):
+ return (False, f+1)
+
+def decode_none(x, f):
+ return (None, f+1)
+
+decode_func = {}
+decode_func['0'] = decode_string
+decode_func['1'] = decode_string
+decode_func['2'] = decode_string
+decode_func['3'] = decode_string
+decode_func['4'] = decode_string
+decode_func['5'] = decode_string
+decode_func['6'] = decode_string
+decode_func['7'] = decode_string
+decode_func['8'] = decode_string
+decode_func['9'] = decode_string
+decode_func[CHR_LIST ] = decode_list
+decode_func[CHR_DICT ] = decode_dict
+decode_func[CHR_INT ] = decode_int
+decode_func[CHR_INT1 ] = decode_intb
+decode_func[CHR_INT2 ] = decode_inth
+decode_func[CHR_INT4 ] = decode_intl
+decode_func[CHR_INT8 ] = decode_intq
+decode_func[CHR_FLOAT32] = decode_float32
+decode_func[CHR_FLOAT64] = decode_float64
+decode_func[CHR_TRUE ] = decode_true
+decode_func[CHR_FALSE ] = decode_false
+decode_func[CHR_NONE ] = decode_none
+
+def make_fixed_length_string_decoders():
+ def make_decoder(slen):
+ def f(x, f):
+ s = x[f+1:f+1+slen]
+ try:
+ t = s.decode("utf8")
+ if len(t) != len(s):
+ s = t
+ except UnicodeDecodeError:
+ pass
+ return (s, f+1+slen)
+ return f
+ for i in range(STR_FIXED_COUNT):
+ decode_func[chr(STR_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_string_decoders()
+
+def make_fixed_length_list_decoders():
+ def make_decoder(slen):
+ def f(x, f):
+ r, f = [], f+1
+ for i in range(slen):
+ v, f = decode_func[x[f]](x, f)
+ r.append(v)
+ return (tuple(r), f)
+ return f
+ for i in range(LIST_FIXED_COUNT):
+ decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_list_decoders()
+
+def make_fixed_length_int_decoders():
+ def make_decoder(j):
+ def f(x, f):
+ return (j, f+1)
+ return f
+ for i in range(INT_POS_FIXED_COUNT):
+ decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i)
+ for i in range(INT_NEG_FIXED_COUNT):
+ decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i)
+
+make_fixed_length_int_decoders()
+
+def make_fixed_length_dict_decoders():
+ def make_decoder(slen):
+ def f(x, f):
+ r, f = {}, f+1
+ for j in range(slen):
+ k, f = decode_func[x[f]](x, f)
+ r[k], f = decode_func[x[f]](x, f)
+ return (r, f)
+ return f
+ for i in range(DICT_FIXED_COUNT):
+ decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_dict_decoders()
+
+def encode_dict(x,r):
+ r.append(CHR_DICT)
+ for k, v in x.items():
+ encode_func[type(k)](k, r)
+ encode_func[type(v)](v, r)
+ r.append(CHR_TERM)
+
+
+def loads(x):
+ try:
+ r, l = decode_func[x[0]](x, 0)
+ except (IndexError, KeyError):
+ raise ValueError
+ if l != len(x):
+ raise ValueError
+ return r
+
+from types import StringType, IntType, LongType, DictType, ListType, TupleType, FloatType, NoneType, UnicodeType
+
+def encode_int(x, r):
+ if 0 <= x < INT_POS_FIXED_COUNT:
+ r.append(chr(INT_POS_FIXED_START+x))
+ elif -INT_NEG_FIXED_COUNT <= x < 0:
+ r.append(chr(INT_NEG_FIXED_START-1-x))
+ elif -128 <= x < 128:
+ r.extend((CHR_INT1, struct.pack('!b', x)))
+ elif -32768 <= x < 32768:
+ r.extend((CHR_INT2, struct.pack('!h', x)))
+ elif -2147483648 <= x < 2147483648:
+ r.extend((CHR_INT4, struct.pack('!l', x)))
+ elif -9223372036854775808 <= x < 9223372036854775808:
+ r.extend((CHR_INT8, struct.pack('!q', x)))
+ else:
+ s = str(x)
+ if len(s) >= MAX_INT_LENGTH:
+ raise ValueError('overflow')
+ r.extend((CHR_INT, s, CHR_TERM))
+
+def encode_float32(x, r):
+ r.extend((CHR_FLOAT32, struct.pack('!f', x)))
+
+def encode_float64(x, r):
+ r.extend((CHR_FLOAT64, struct.pack('!d', x)))
+
+def encode_bool(x, r):
+ r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])
+
+def encode_none(x, r):
+ r.extend(CHR_NONE)
+
+def encode_string(x, r):
+ if len(x) < STR_FIXED_COUNT:
+ r.extend((chr(STR_FIXED_START + len(x)), x))
+ else:
+ r.extend((str(len(x)), ':', x))
+
+def encode_unicode(x, r):
+ encode_string(x.encode("utf8"), r)
+
+def encode_list(x, r):
+ if len(x) < LIST_FIXED_COUNT:
+ r.append(chr(LIST_FIXED_START + len(x)))
+ for i in x:
+ encode_func[type(i)](i, r)
+ else:
+ r.append(CHR_LIST)
+ for i in x:
+ encode_func[type(i)](i, r)
+ r.append(CHR_TERM)
+
+def encode_dict(x,r):
+ if len(x) < DICT_FIXED_COUNT:
+ r.append(chr(DICT_FIXED_START + len(x)))
+ for k, v in x.items():
+ encode_func[type(k)](k, r)
+ encode_func[type(v)](v, r)
+ else:
+ r.append(CHR_DICT)
+ for k, v in x.items():
+ encode_func[type(k)](k, r)
+ encode_func[type(v)](v, r)
+ r.append(CHR_TERM)
+
+encode_func = {}
+encode_func[IntType] = encode_int
+encode_func[LongType] = encode_int
+encode_func[StringType] = encode_string
+encode_func[ListType] = encode_list
+encode_func[TupleType] = encode_list
+encode_func[DictType] = encode_dict
+encode_func[NoneType] = encode_none
+encode_func[UnicodeType] = encode_unicode
+
+lock = Lock()
+
+try:
+ from types import BooleanType
+ encode_func[BooleanType] = encode_bool
+except ImportError:
+ pass
+
+def dumps(x, float_bits=DEFAULT_FLOAT_BITS):
+ """
+ Dump data structure to str.
+
+ Here float_bits is either 32 or 64.
+ """
+ lock.acquire()
+ try:
+ if float_bits == 32:
+ encode_func[FloatType] = encode_float32
+ elif float_bits == 64:
+ encode_func[FloatType] = encode_float64
+ else:
+ raise ValueError('Float bits (%d) is not 32 or 64' % float_bits)
+ r = []
+ encode_func[type(x)](x, r)
+ finally:
+ lock.release()
+ return ''.join(r)
+
+def test():
+ f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
+ f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
+ f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
+ L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),)
+ assert loads(dumps(L)) == L
+ d = dict(zip(range(-100000,100000),range(-100000,100000)))
+ d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False})
+ L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''})
+ assert loads(dumps(L)) == L
+ L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000)
+ assert loads(dumps(L)) == L
+ L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',)
+ assert loads(dumps(L)) == L
+ L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',)
+ assert loads(dumps(L)) == L
+ L = tuple([tuple(range(n)) for n in range(100)]) + ('b',)
+ assert loads(dumps(L)) == L
+ L = tuple(['a'*n for n in range(1000)]) + ('b',)
+ assert loads(dumps(L)) == L
+ L = tuple(['a'*n for n in range(1000)]) + (None,True,None)
+ assert loads(dumps(L)) == L
+ assert loads(dumps(None)) == None
+ assert loads(dumps({None:None})) == {None:None}
+ assert 1e-10 In natural language it calculates: size + 64bit chksum of the first and
- > last 64k (even if they overlap because the file is smaller than 128k).
- A slightly more Pythonic version of the Python solution on..
- http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
- """
- longlongformat = 'q'
- bytesize = struct.calcsize(longlongformat)
-
- f = open(name, "rb")
-
- filesize = os.path.getsize(name)
- fhash = filesize
-
- if filesize < 65536 * 2:
- raise ValueError("File size must be larger than %s bytes (is %s)" % (65536 * 2, filesize))
-
- for x in range(65536 / bytesize):
- buf = f.read(bytesize)
- (l_value,) = struct.unpack(longlongformat, buf)
- fhash += l_value
- fhash = fhash & 0xFFFFFFFFFFFFFFFF # to remain as 64bit number
-
- f.seek(max(0, filesize - 65536), 0)
- for x in range(65536 / bytesize):
- buf = f.read(bytesize)
- (l_value,) = struct.unpack(longlongformat, buf)
- fhash += l_value
- fhash = fhash & 0xFFFFFFFFFFFFFFFF
-
- f.close()
- return "%016x" % fhash
-
-class XmlHandler:
- """Deals with retrieval of XML files from API"""
- def __init__(self, url):
- self.url = url
-
- def _grabUrl(self, url):
- try:
- urlhandle = urllib2.urlopen(url)
- except IOError, errormsg:
- raise TmdHttpError(errormsg)
- if urlhandle.code >= 400:
- raise TmdHttpError("HTTP status code was %d" % urlhandle.code)
- return urlhandle.read()
-
- def getEt(self):
- xml = self._grabUrl(self.url)
- try:
- et = ElementTree.fromstring(xml)
- except SyntaxError, errormsg:
- raise TmdXmlError(errormsg)
- return et
-
-class SearchResults(list):
- """Stores a list of Movie's that matched the search"""
- def __repr__(self):
- return "" % (list.__repr__(self))
-
-class MovieResult(dict):
- """A dict containing the information about a specific search result"""
- def __repr__(self):
- return "" % (self.get("name"), self.get("released"))
-
- def info(self):
- """Performs a MovieDb.getMovieInfo search on the current id, returns
- a Movie object
- """
- cur_id = self['id']
- info = MovieDb().getMovieInfo(cur_id)
- return info
-
-class Movie(dict):
- """A dict containing the information about the film"""
- def __repr__(self):
- return "" % (self.get("name"), self.get("released"))
-
-class Categories(dict):
- """Stores category information"""
- def set(self, category_et):
- """Takes an elementtree Element ('category') and stores the url,
- using the type and name as the dict key.
- For example:
-
- ..becomes:
- categories['genre']['Crime'] = 'http://themoviedb.org/encyclopedia/category/80'
- """
- _type = category_et.get("type")
- name = category_et.get("name")
- url = category_et.get("url")
- self.setdefault(_type, {})[name] = url
- self[_type][name] = url
-
-class Studios(dict):
- """Stores category information"""
- def set(self, studio_et):
- """Takes an elementtree Element ('studio') and stores the url,
- using the name as the dict key.
- For example:
-
- ..becomes:
- studios['name'] = 'http://www.themoviedb.org/encyclopedia/company/20'
- """
- name = studio_et.get("name")
- url = studio_et.get("url")
- self[name] = url
-
-class Countries(dict):
- """Stores country information"""
- def set(self, country_et):
- """Takes an elementtree Element ('country') and stores the url,
- using the name and code as the dict key.
- For example:
-
- ..becomes:
- countries['code']['name'] = 'http://www.themoviedb.org/encyclopedia/country/223'
- """
- code = country_et.get("code")
- name = country_et.get("name")
- url = country_et.get("url")
- self.setdefault(code, {})[name] = url
-
-class Image(dict):
- """Stores image information for a single poster/backdrop (includes
- multiple sizes)
- """
- def __init__(self, _id, _type, size, url):
- self['id'] = _id
- self['type'] = _type
-
- def largest(self):
- for csize in ["original", "mid", "cover", "thumb"]:
- if csize in self:
- return csize
-
- def __repr__(self):
- return "" % (self['type'], self['id'])
-
-class ImagesList(list):
- """Stores a list of Images, and functions to filter "only posters" etc"""
- def set(self, image_et):
- """Takes an elementtree Element ('image') and stores the url,
- along with the type, id and size.
- Is a list containing each image as a dictionary (which includes the
- various sizes)
- For example:
-
- ..becomes:
- images[0] = {'id':4181', 'type': 'poster', 'original': 'http://images.themov...'}
- """
- _type = image_et.get("type")
- _id = image_et.get("id")
- size = image_et.get("size")
- url = image_et.get("url")
- cur = self.find_by('id', _id)
- if len(cur) == 0:
- nimg = Image(_id = _id, _type = _type, size = size, url = url)
- self.append(nimg)
- elif len(cur) == 1:
- cur[0][size] = url
- else:
- raise ValueError("Found more than one poster with id %s, this should never happen" % (_id))
-
- def find_by(self, key, value):
- ret = []
- for cur in self:
- if cur[key] == value:
- ret.append(cur)
- return ret
-
- @property
- def posters(self):
- return self.find_by('type', 'poster')
-
- @property
- def backdrops(self):
- return self.find_by('type', 'backdrop')
-
-class CrewRoleList(dict):
- """Stores a list of roles, such as director, actor etc
- >>> import tmdb
- >>> tmdb.getMovieInfo(550)['cast'].keys()[:5]
- ['casting', 'producer', 'author', 'sound editor', 'actor']
- """
- pass
-
-class CrewList(list):
- """Stores list of crew in specific role
- >>> import tmdb
- >>> tmdb.getMovieInfo(550)['cast']['author']
- [, ]
- """
- pass
-
-class Person(dict):
- """Stores information about a specific member of cast"""
- def __init__(self, job, _id, name, character, url):
- self['job'] = job
- self['id'] = _id
- self['name'] = name
- self['character'] = character
- self['url'] = url
-
- def __repr__(self):
- if self['character'] is None or self['character'] == "":
- return "<%(job)s (id %(id)s): %(name)s>" % self
- else:
- return "<%(job)s (id %(id)s): %(name)s (as %(character)s)>" % self
-
-class MovieDb:
- """Main interface to www.themoviedb.com
- The search() method searches for the film by title.
- The getMovieInfo() method retrieves information about a specific movie using themoviedb id.
- """
- def _parseSearchResults(self, movie_element):
- cur_movie = MovieResult()
- cur_images = ImagesList()
- for item in movie_element.getchildren():
- if item.tag.lower() == "images":
- for subitem in item.getchildren():
- cur_images.set(subitem)
- else:
- cur_movie[item.tag] = item.text
- cur_movie['images'] = cur_images
- return cur_movie
-
- def _parseMovie(self, movie_element):
- cur_movie = Movie()
- cur_categories = Categories()
- cur_studios = Studios()
- cur_countries = Countries()
- cur_images = ImagesList()
- cur_cast = CrewRoleList()
- for item in movie_element.getchildren():
- if item.tag.lower() == "categories":
- for subitem in item.getchildren():
- cur_categories.set(subitem)
- elif item.tag.lower() == "studios":
- for subitem in item.getchildren():
- cur_studios.set(subitem)
- elif item.tag.lower() == "countries":
- for subitem in item.getchildren():
- cur_countries.set(subitem)
- elif item.tag.lower() == "images":
- for subitem in item.getchildren():
- cur_images.set(subitem)
- elif item.tag.lower() == "cast":
- for subitem in item.getchildren():
- job = subitem.get("job").lower()
- p = Person(
- job = job,
- _id = subitem.get("id"),
- name = subitem.get("name"),
- character = subitem.get("character"),
- url = subitem.get("url"),
- )
- cur_cast.setdefault(job, CrewList()).append(p)
- else:
- cur_movie[item.tag] = item.text
-
- cur_movie['categories'] = cur_categories
- cur_movie['studios'] = cur_studios
- cur_movie['countries'] = cur_countries
- cur_movie['images'] = cur_images
- cur_movie['cast'] = cur_cast
- return cur_movie
-
- def search(self, title):
- """Searches for a film by its title.
- Returns SearchResults (a list) containing all matches (Movie instances)
- """
- title = urllib.quote(title.encode("utf-8"))
- url = config['urls']['movie.search'] % (title)
- etree = XmlHandler(url).getEt()
- search_results = SearchResults()
- for cur_result in etree.find("movies").findall("movie"):
- cur_movie = self._parseSearchResults(cur_result)
- search_results.append(cur_movie)
- return search_results
-
- def getMovieInfo(self, id):
- """Returns movie info by it's TheMovieDb ID.
- Returns a Movie instance
- """
- url = config['urls']['movie.getInfo'] % (id)
- etree = XmlHandler(url).getEt()
- moviesTree = etree.find("movies").findall("movie")
-
- if len(moviesTree) == 0:
- raise TmdNoResults("No results for id %s" % id)
- return self._parseMovie(moviesTree[0])
-
- def mediaGetInfo(self, hash, size):
- """Used to retrieve specific information about a movie but instead of
- passing a TMDb ID, you pass a file hash and filesize in bytes
- """
- url = config['urls']['media.getInfo'] % (hash, size)
- etree = XmlHandler(url).getEt()
- moviesTree = etree.find("movies").findall("movie")
- if len(moviesTree) == 0:
- raise TmdNoResults("No results for hash %s" % hash)
- return [self._parseMovie(x) for x in moviesTree]
-
- def imdbLookup(self, id = 0, title = False):
- if not config.get('apikey'):
- raise TmdConfigError("API Key not set")
- if id > 0:
- url = config['urls']['imdb.lookUp'] % (id)
- else:
- _imdb_id = self.search(title)[0]["imdb_id"]
- url = config['urls']['imdb.lookUp'] % (_imdb_id)
- etree = XmlHandler(url).getEt()
- lookup_results = SearchResults()
- for cur_lookup in etree.find("movies").findall("movie"):
- cur_movie = self._parseSearchResults(cur_lookup)
- lookup_results.append(cur_movie)
- return lookup_results
-
-class Browse:
-
- def __init__(self, params = {}):
- """
- tmdb.Browse(params)
- default params = {"order_by":"release","order":"desc"}
- params = {"query":"some query","release_max":"1991",...}
- all posible parameters = http://api.themoviedb.org/2.1/methods/Movie.browse
- """
- if "order_by" not in params:
- params.update({"order_by":"release"})
- if "order" not in params:
- params.update({"order":"desc"})
-
- self.params = urllib.urlencode(params)
- self.movie = self.look(self.params)
-
- def look(self, look_for):
- url = config['urls']['movie.browse'] % (look_for)
- etree = XmlHandler(url).getEt()
- look_results = SearchResults()
- for cur_lookup in etree.find("movies").findall("movie"):
- cur_movie = self._parseSearchResults(cur_lookup)
- look_results.append(cur_movie)
- return look_results
-
- def _parseSearchResults(self, movie_element):
- cur_movie = MovieResult()
- cur_images = ImagesList()
- for item in movie_element.getchildren():
- if item.tag.lower() == "images":
- for subitem in item.getchildren():
- cur_images.set(subitem)
- else:
- cur_movie[item.tag] = item.text
- cur_movie['images'] = cur_images
- return cur_movie
-
- def getTotal(self):
- return len(self.movie)
-
- def getRating(self, i):
- return self.movie[i]["rating"]
-
- def getVotes(self, i):
- return self.movie[i]["votes"]
-
- def getName(self, i):
- return self.movie[i]["name"]
-
- def getLanguage(self, i):
- return self.movie[i]["language"]
-
- def getCertification(self, i):
- return self.movie[i]["certification"]
-
- def getUrl(self, i):
- return self.movie[i]["url"]
-
- def getOverview(self, i):
- return self.movie[i]["overview"]
-
- def getPopularity(self, i):
- return self.movie[i]["popularity"]
-
- def getOriginalName(self, i):
- return self.movie[i]["original_name"]
-
- def getLastModified(self, i):
- return self.movie[i]["last_modified_at"]
-
- def getImdbId(self, i):
- return self.movie[i]["imdb_id"]
-
- def getReleased(self, i):
- return self.movie[i]["released"]
-
- def getScore(self, i):
- return self.movie[i]["score"]
-
- def getAdult(self, i):
- return self.movie[i]["adult"]
-
- def getVersion(self, i):
- return self.movie[i]["version"]
-
- def getTranslated(self, i):
- return self.movie[i]["translated"]
-
- def getType(self, i):
- return self.movie[i]["type"]
-
- def getId(self, i):
- return self.movie[i]["id"]
-
- def getAlternativeName(self, i):
- return self.movie[i]["alternative_name"]
-
- def getPoster(self, i, size):
- if size == "thumb" or size == "t":
- return self.movie[i]["images"][0]["thumb"]
- elif size == "cover" or size == "c":
- return self.movie[i]["images"][0]["cover"]
- else:
- return self.movie[i]["images"][0]["mid"]
-
- def getBackdrop(self, i, size):
- if size == "poster" or size == "p":
- return self.movie[i]["images"][1]["poster"]
- else:
- return self.movie[i]["images"][1]["thumb"]
-
-
-
-# Shortcuts for tmdb search method
-# using:
-# movie = tmdb.tmdb("Sin City")
-# print movie.getRating -> 7.0
-class tmdb:
-
- def __init__(self, name):
- """Convenience wrapper for MovieDb.search - so you can do..
- >>> import tmdb
- >>> movie = tmdb.tmdb("Fight Club")
- >>> ranking = movie.getRanking() or votes = movie.getVotes()
- ]>
- """
- mdb = MovieDb()
- self.movie = mdb.search(name)
-
- def getTotal(self):
- return len(self.movie)
-
- def getRating(self, i):
- return self.movie[i]["rating"]
-
- def getVotes(self, i):
- return self.movie[i]["votes"]
-
- def getName(self, i):
- return self.movie[i]["name"]
-
- def getLanguage(self, i):
- return self.movie[i]["language"]
-
- def getCertification(self, i):
- return self.movie[i]["certification"]
-
- def getUrl(self, i):
- return self.movie[i]["url"]
-
- def getOverview(self, i):
- return self.movie[i]["overview"]
-
- def getPopularity(self, i):
- return self.movie[i]["popularity"]
-
- def getOriginalName(self, i):
- return self.movie[i]["original_name"]
-
- def getLastModified(self, i):
- return self.movie[i]["last_modified_at"]
-
- def getImdbId(self, i):
- return self.movie[i]["imdb_id"]
-
- def getReleased(self, i):
- return self.movie[i]["released"]
-
- def getScore(self, i):
- return self.movie[i]["score"]
-
- def getAdult(self, i):
- return self.movie[i]["adult"]
-
- def getVersion(self, i):
- return self.movie[i]["version"]
-
- def getTranslated(self, i):
- return self.movie[i]["translated"]
-
- def getType(self, i):
- return self.movie[i]["type"]
-
- def getId(self, i):
- return self.movie[i]["id"]
-
- def getAlternativeName(self, i):
- return self.movie[i]["alternative_name"]
-
- def getPoster(self, i, size):
- if size == "thumb" or size == "t":
- return self.movie[i]["images"][0]["thumb"]
- elif size == "cover" or size == "c":
- return self.movie[i]["images"][0]["cover"]
- else:
- return self.movie[i]["images"][0]["mid"]
-
- def getBackdrop(self, i, size):
- if size == "poster" or size == "p":
- return self.movie[i]["images"][1]["poster"]
- else:
- return self.movie[i]["images"][1]["thumb"]
-
-# Shortcuts for imdb lookup method
-# using:
-# movie = tmdb.imdb("Sin City")
-# print movie.getRating -> 7.0
-class imdb:
-
- def __init__(self, id = 0, title = False):
- # get first movie if result=0
- """Convenience wrapper for MovieDb.search - so you can do..
- >>> import tmdb
- >>> movie = tmdb.imdb(title="Fight Club") # or movie = tmdb.imdb(id=imdb_id)
- >>> ranking = movie.getRanking() or votes = movie.getVotes()
- ]>
- """
- self.id = id
- self.title = title
- self.mdb = MovieDb()
- self.movie = self.mdb.imdbLookup(self.id, self.title)
-
- def getTotal(self):
- return len(self.movie)
-
- def getRuntime(self, i):
- return self.movie[i]["runtime"]
-
- def getCategories(self):
- from xml.dom.minidom import parse
- adres = config['urls']['imdb.lookUp'] % self.getImdbId()
- d = parse(urllib2.urlopen(adres))
- s = d.getElementsByTagName("categories")
- ds = []
- for i in range(len(s[0].childNodes)):
- if i % 2 > 0:
- ds.append(s[0].childNodes[i].getAttribute("name"))
- return ds
-
- def getRating(self, i):
- return self.movie[i]["rating"]
-
- def getVotes(self, i):
- return self.movie[i]["votes"]
-
- def getName(self, i):
- return self.movie[i]["name"]
-
- def getLanguage(self, i):
- return self.movie[i]["language"]
-
- def getCertification(self, i):
- return self.movie[i]["certification"]
-
- def getUrl(self, i):
- return self.movie[i]["url"]
-
- def getOverview(self, i):
- return self.movie[i]["overview"]
-
- def getPopularity(self, i):
- return self.movie[i]["popularity"]
-
- def getOriginalName(self, i):
- return self.movie[i]["original_name"]
-
- def getLastModified(self, i):
- return self.movie[i]["last_modified_at"]
-
- def getImdbId(self, i):
- return self.movie[i]["imdb_id"]
-
- def getReleased(self, i):
- return self.movie[i]["released"]
-
- def getAdult(self, i):
- return self.movie[i]["adult"]
-
- def getVersion(self, i):
- return self.movie[i]["version"]
-
- def getTranslated(self, i):
- return self.movie[i]["translated"]
-
- def getType(self, i):
- return self.movie[i]["type"]
-
- def getId(self, i):
- return self.movie[i]["id"]
-
- def getAlternativeName(self, i):
- return self.movie[i]["alternative_name"]
-
- def getPoster(self, i, size):
- poster = []
- if size == "thumb" or size == "t":
- _size = "thumb"
- elif size == "cover" or size == "c":
- _size = "cover"
- else:
- _size = "mid"
- for a in self.movie[i]["images"]:
- if a["type"] == "poster":
- poster.append(a[_size])
- return poster
- del poster
-
- def getBackdrop(self, i, size):
- backdrop = []
- if size == "thumb" or size == "t":
- _size = "thumb"
- elif size == "cover" or size == "c":
- _size = "cover"
- else:
- _size = "mid"
- for a in self.movie[i]["images"]:
- if a["type"] == "backdrop":
- backdrop.append(a[_size])
- return backdrop
- del backdrop
-
-def imdbLookup(id = 0, title = False):
- """Convenience wrapper for Imdb.Lookup - so you can do..
- >>> import tmdb
- >>> tmdb.imdbLookup("Fight Club")
- ]>
- """
- mdb = MovieDb()
- return mdb.imdbLookup(id, title)
-
-def search(name):
- """Convenience wrapper for MovieDb.search - so you can do..
- >>> import tmdb
- >>> tmdb.search("Fight Club")
- ]>
- """
- mdb = MovieDb()
- return mdb.search(name)
-
-def getMovieInfo(id):
- """Convenience wrapper for MovieDb.search - so you can do..
- >>> import tmdb
- >>> tmdb.getMovieInfo(187)
-
- """
- mdb = MovieDb()
- return mdb.getMovieInfo(id)
-
-def mediaGetInfo(hash, size):
- """Convenience wrapper for MovieDb.mediaGetInfo - so you can do..
-
- >>> import tmdb
- >>> tmdb.mediaGetInfo('907172e7fe51ba57', size = 742086656)[0]
-
- """
- mdb = MovieDb()
- return mdb.mediaGetInfo(hash, size)
-
-def searchByHashingFile(filename):
- """Searches for the specified file using the OpenSubtitle hashing method
- """
- return mediaGetInfo(opensubtitleHashFile(filename), os.path.size(filename))
-
-def main():
- results = search("Fight Club")
- searchResult = results[0]
- movie = getMovieInfo(searchResult['id'])
- print movie['name']
-
- print "Producers:"
- for prodr in movie['cast']['producer']:
- print " " * 4, prodr['name']
- print movie['images']
- for genreName in movie['categories']['genre']:
- print "%s (%s)" % (genreName, movie['categories']['genre'][genreName])
-
-if __name__ == '__main__':
- main()
diff --git a/libs/tmdb3/__init__.py b/libs/tmdb3/__init__.py
new file mode 100755
index 00000000..92ca5510
--- /dev/null
+++ b/libs/tmdb3/__init__.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+from tmdb_api import Configuration, searchMovie, searchMovieWithYear, \
+ searchPerson, searchStudio, searchList, searchCollection, \
+ Person, Movie, Collection, Genre, List, __version__
+from request import set_key, set_cache
+from locales import get_locale, set_locale
+from tmdb_auth import get_session, set_session
+from cache_engine import CacheEngine
+from tmdb_exceptions import *
+
diff --git a/libs/tmdb3/cache.py b/libs/tmdb3/cache.py
new file mode 100755
index 00000000..3b106774
--- /dev/null
+++ b/libs/tmdb3/cache.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: cache.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Caching framework to store TMDb API results
+#-----------------------
+
+from tmdb_exceptions import *
+from cache_engine import Engines
+
+import cache_null
+import cache_file
+
+class Cache( object ):
+ """
+ This class implements a persistent cache, backed in a file specified in
+ the object creation. The file is protected for safe, concurrent access
+ by multiple instances using flock.
+ This cache uses JSON for speed and storage efficiency, so only simple
+ data types are supported.
+ Data is stored in a simple format {key:(expiretimestamp, data)}
+ """
+ def __init__(self, engine=None, *args, **kwargs):
+ self._engine = None
+ self._data = {}
+ self._age = 0
+ self.configure(engine, *args, **kwargs)
+
+ def _import(self, data=None):
+ if data is None:
+ data = self._engine.get(self._age)
+ for obj in sorted(data, key=lambda x: x.creation):
+ if not obj.expired:
+ self._data[obj.key] = obj
+ self._age = max(self._age, obj.creation)
+
+ def _expire(self):
+ for k,v in self._data.items():
+ if v.expired:
+ del self._data[k]
+
+ def configure(self, engine, *args, **kwargs):
+ if engine is None:
+ engine = 'file'
+ elif engine not in Engines:
+ raise TMDBCacheError("Invalid cache engine specified: "+engine)
+ self._engine = Engines[engine](self)
+ self._engine.configure(*args, **kwargs)
+
+ def put(self, key, data, lifetime=60*60*12):
+ # pull existing data, so cache will be fresh when written back out
+ if self._engine is None:
+ raise TMDBCacheError("No cache engine configured")
+ self._expire()
+ self._import(self._engine.put(key, data, lifetime))
+
+ def get(self, key):
+ if self._engine is None:
+ raise TMDBCacheError("No cache engine configured")
+ self._expire()
+ if key not in self._data:
+ self._import()
+ try:
+ return self._data[key].data
+ except:
+ return None
+
+ def cached(self, callback):
+ """
+ Returns a decorator that uses a callback to specify the key to use
+ for caching the responses from the decorated function.
+ """
+ return self.Cached(self, callback)
+
+ class Cached( object ):
+ def __init__(self, cache, callback, func=None, inst=None):
+ self.cache = cache
+ self.callback = callback
+ self.func = func
+ self.inst = inst
+
+ if func:
+ self.__module__ = func.__module__
+ self.__name__ = func.__name__
+ self.__doc__ = func.__doc__
+
+ def __call__(self, *args, **kwargs):
+ if self.func is None: # decorator is waiting to be given a function
+ if len(kwargs) or (len(args) != 1):
+ raise TMDBCacheError('Cache.Cached decorator must be called '+\
+ 'a single callable argument before it '+\
+ 'be used.')
+ elif args[0] is None:
+ raise TMDBCacheError('Cache.Cached decorator called before '+\
+ 'being given a function to wrap.')
+ elif not callable(args[0]):
+ raise TMDBCacheError('Cache.Cached must be provided a '+\
+ 'callable object.')
+ return self.__class__(self.cache, self.callback, args[0])
+ elif self.inst.lifetime == 0:
+ return self.func(*args, **kwargs)
+ else:
+ key = self.callback()
+ data = self.cache.get(key)
+ if data is None:
+ data = self.func(*args, **kwargs)
+ if hasattr(self.inst, 'lifetime'):
+ self.cache.put(key, data, self.inst.lifetime)
+ else:
+ self.cache.put(key, data)
+ return data
+
+ def __get__(self, inst, owner):
+ if inst is None:
+ return self
+ func = self.func.__get__(inst, owner)
+ callback = self.callback.__get__(inst, owner)
+ return self.__class__(self.cache, callback, func, inst)
+
diff --git a/libs/tmdb3/cache_engine.py b/libs/tmdb3/cache_engine.py
new file mode 100755
index 00000000..99ad4cda
--- /dev/null
+++ b/libs/tmdb3/cache_engine.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: cache_engine.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Base cache engine class for collecting registered engines
+#-----------------------
+
+import time
+from weakref import ref
+
+class Engines( object ):
+ def __init__(self):
+ self._engines = {}
+ def register(self, engine):
+ self._engines[engine.__name__] = engine
+ self._engines[engine.name] = engine
+ def __getitem__(self, key):
+ return self._engines[key]
+ def __contains__(self, key):
+ return self._engines.__contains__(key)
+Engines = Engines()
+
+class CacheEngineType( type ):
+ """
+ Cache Engine Metaclass that registers new engines against the cache
+ for named selection and use.
+ """
+ def __init__(mcs, name, bases, attrs):
+ super(CacheEngineType, mcs).__init__(name, bases, attrs)
+ if name != 'CacheEngine':
+ # skip base class
+ Engines.register(mcs)
+
+class CacheEngine( object ):
+ __metaclass__ = CacheEngineType
+
+ name = 'unspecified'
+ def __init__(self, parent):
+ self.parent = ref(parent)
+ def configure(self):
+ raise RuntimeError
+ def get(self, date):
+ raise RuntimeError
+ def put(self, key, value, lifetime):
+ raise RuntimeError
+ def expire(self, key):
+ raise RuntimeError
+
+class CacheObject( object ):
+ """
+ Cache object class, containing one stored record.
+ """
+
+ def __init__(self, key, data, lifetime=0, creation=None):
+ self.key = key
+ self.data = data
+ self.lifetime = lifetime
+ self.creation = creation if creation is not None else time.time()
+
+ def __len__(self):
+ return len(self.data)
+
+ @property
+ def expired(self):
+ return (self.remaining == 0)
+
+ @property
+ def remaining(self):
+ return max((self.creation + self.lifetime) - time.time(), 0)
+
diff --git a/libs/tmdb3/cache_file.py b/libs/tmdb3/cache_file.py
new file mode 100755
index 00000000..5918071a
--- /dev/null
+++ b/libs/tmdb3/cache_file.py
@@ -0,0 +1,391 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: cache_file.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Persistant file-backed cache using /tmp/ to share data
+# using flock or msvcrt.locking to allow safe concurrent
+# access.
+#-----------------------
+
+import struct
+import errno
+import json
+import os
+import io
+
+from cStringIO import StringIO
+
+from tmdb_exceptions import *
+from cache_engine import CacheEngine, CacheObject
+
+####################
+# Cache File Format
+#------------------
+# cache version (2) unsigned short
+# slot count (2) unsigned short
+# slot 0: timestamp (8) double
+# slot 0: lifetime (4) unsigned int
+# slot 0: seek point (4) unsigned int
+# slot 1: timestamp
+# slot 1: lifetime index slots are IDd by their query date and
+# slot 1: seek point are filled incrementally forwards. lifetime
+# .... is how long after query date before the item
+# .... expires, and seek point is the location of the
+# slot N-2: timestamp start of data for that entry. 256 empty slots
+# slot N-2: lifetime are pre-allocated, allowing fast updates.
+# slot N-2: seek point when all slots are filled, the cache file is
+# slot N-1: timestamp rewritten from scrach to add more slots.
+# slot N-1: lifetime
+# slot N-1: seek point
+# block 1 (?) ASCII
+# block 2
+# .... blocks are just simple ASCII text, generated
+# .... as independent objects by the JSON encoder
+# block N-2
+# block N-1
+#
+####################
+
+
+def _donothing(*args, **kwargs):
+ pass
+
+try:
+ import fcntl
+ class Flock( object ):
+ """
+ Context manager to flock file for the duration the object exists.
+ Referenced file will be automatically unflocked as the interpreter
+ exits the context.
+ Supports an optional callback to process the error and optionally
+ suppress it.
+ """
+ LOCK_EX = fcntl.LOCK_EX
+ LOCK_SH = fcntl.LOCK_SH
+
+ def __init__(self, fileobj, operation, callback=None):
+ self.fileobj = fileobj
+ self.operation = operation
+ self.callback = callback
+ def __enter__(self):
+ fcntl.flock(self.fileobj, self.operation)
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ suppress = False
+ if callable(self.callback):
+ suppress = self.callback(exc_type, exc_value, exc_tb)
+ fcntl.flock(self.fileobj, fcntl.LOCK_UN)
+ return suppress
+
+ def parse_filename(filename):
+ if '$' in filename:
+ # replace any environmental variables
+ filename = os.path.expandvars(filename)
+ if filename.startswith('~'):
+ # check for home directory
+ return os.path.expanduser(filename)
+ elif filename.startswith('/'):
+ # check for absolute path
+ return filename
+ # return path with temp directory prepended
+ return '/tmp/' + filename
+
+except ImportError:
+ import msvcrt
+ class Flock( object ):
+ LOCK_EX = msvcrt.LK_LOCK
+ LOCK_SH = msvcrt.LK_LOCK
+
+ def __init__(self, fileobj, operation, callback=None):
+ self.fileobj = fileobj
+ self.operation = operation
+ self.callback = callback
+ def __enter__(self):
+ self.size = os.path.getsize(self.fileobj.name)
+ msvcrt.locking(self.fileobj.fileno(), self.operation, self.size)
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ suppress = False
+ if callable(self.callback):
+ suppress = self.callback(exc_type, exc_value, exc_tb)
+ msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_UNLCK, self.size)
+ return suppress
+
+ def parse_filename(filename):
+ if '%' in filename:
+ # replace any environmental variables
+ filename = os.path.expandvars(filename)
+ if filename.startswith('~'):
+ # check for home directory
+ return os.path.expanduser(filename)
+ elif (ord(filename[0]) in (range(65,91)+range(99,123))) \
+ and (filename[1:3] == ':\\'):
+ # check for absolute drive path (e.g. C:\...)
+ return filename
+ elif (filename.count('\\') >= 3) and (filename.startswith('\\\\')):
+ # check for absolute UNC path (e.g. \\server\...)
+ return filename
+ # return path with temp directory prepended
+ return os.path.expandvars(os.path.join('%TEMP%',filename))
+
+
+class FileCacheObject( CacheObject ):
+ _struct = struct.Struct('dII') # double and two ints
+ # timestamp, lifetime, position
+
+ @classmethod
+ def fromFile(cls, fd):
+ dat = cls._struct.unpack(fd.read(cls._struct.size))
+ obj = cls(None, None, dat[1], dat[0])
+ obj.position = dat[2]
+ return obj
+
+ def __init__(self, *args, **kwargs):
+ self._key = None
+ self._data = None
+ self._size = None
+ self._buff = StringIO()
+ super(FileCacheObject, self).__init__(*args, **kwargs)
+
+ @property
+ def size(self):
+ if self._size is None:
+ self._buff.seek(0,2)
+ size = self._buff.tell()
+ if size == 0:
+ if (self._key is None) or (self._data is None):
+ raise RuntimeError
+ json.dump([self.key, self.data], self._buff)
+ self._size = self._buff.tell()
+ self._size = size
+ return self._size
+ @size.setter
+ def size(self, value): self._size = value
+
+ @property
+ def key(self):
+ if self._key is None:
+ try:
+ self._key, self._data = json.loads(self._buff.getvalue())
+ except:
+ pass
+ return self._key
+ @key.setter
+ def key(self, value): self._key = value
+
+ @property
+ def data(self):
+ if self._data is None:
+ self._key, self._data = json.loads(self._buff.getvalue())
+ return self._data
+ @data.setter
+ def data(self, value): self._data = value
+
+ def load(self, fd):
+ fd.seek(self.position)
+ self._buff.seek(0)
+ self._buff.write(fd.read(self.size))
+
+ def dumpslot(self, fd):
+ pos = fd.tell()
+ fd.write(self._struct.pack(self.creation, self.lifetime, self.position))
+
+ def dumpdata(self, fd):
+ self.size
+ fd.seek(self.position)
+ fd.write(self._buff.getvalue())
+
+
+class FileEngine( CacheEngine ):
+ """Simple file-backed engine."""
+ name = 'file'
+ _struct = struct.Struct('HH') # two shorts for version and count
+ _version = 2
+
+ def __init__(self, parent):
+ super(FileEngine, self).__init__(parent)
+ self.configure(None)
+
+ def configure(self, filename, preallocate=256):
+ self.preallocate = preallocate
+ self.cachefile = filename
+ self.size = 0
+ self.free = 0
+ self.age = 0
+
+ def _init_cache(self):
+ # only run this once
+ self._init_cache = _donothing
+
+ if self.cachefile is None:
+ raise TMDBCacheError("No cache filename given.")
+
+ self.cachefile = parse_filename(self.cachefile)
+
+ try:
+ # attempt to read existing cache at filename
+ # handle any errors that occur
+ self._open('r+b')
+ # seems to have read fine, make sure we have write access
+ if not os.access(self.cachefile, os.W_OK):
+ raise TMDBCacheWriteError(self.cachefile)
+
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ # file does not exist, create a new one
+ try:
+ self._open('w+b')
+ self._write([])
+ except IOError as e:
+ if e.errno == errno.ENOENT:
+ # directory does not exist
+ raise TMDBCacheDirectoryError(self.cachefile)
+ elif e.errno == errno.EACCES:
+ # user does not have rights to create new file
+ raise TMDBCacheWriteError(self.cachefile)
+ else:
+ # let the unhandled error continue through
+ raise
+ elif e.errno == errno.EACCESS:
+ # file exists, but we do not have permission to access it
+ raise TMDBCacheReadError(self.cachefile)
+ else:
+ # let the unhandled error continue through
+ raise
+
+ def get(self, date):
+ self._init_cache()
+ self._open('r+b')
+
+ with Flock(self.cachefd, Flock.LOCK_SH): # lock for shared access
+ # return any new objects in the cache
+ return self._read(date)
+
+ def put(self, key, value, lifetime):
+ self._init_cache()
+ self._open('r+b')
+
+ with Flock(self.cachefd, Flock.LOCK_EX): # lock for exclusive access
+ newobjs = self._read(self.age)
+ newobjs.append(FileCacheObject(key, value, lifetime))
+
+ # this will cause a new file object to be opened with the proper
+ # access mode, however the Flock should keep the old object open
+ # and properly locked
+ self._open('r+b')
+ self._write(newobjs)
+ return newobjs
+
+ def _open(self, mode='r+b'):
+ # enforce binary operation
+ try:
+ if self.cachefd.mode == mode:
+ # already opened in requested mode, nothing to do
+ self.cachefd.seek(0)
+ return
+ except: pass # catch issue of no cachefile yet opened
+ self.cachefd = io.open(self.cachefile, mode)
+
+ def _read(self, date):
+ try:
+ self.cachefd.seek(0)
+ version, count = self._struct.unpack(\
+ self.cachefd.read(self._struct.size))
+ if version != self._version:
+ # old version, break out and well rewrite when finished
+ raise Exception
+
+ self.size = count
+ cache = []
+ while count:
+ # loop through storage definitions
+ obj = FileCacheObject.fromFile(self.cachefd)
+ cache.append(obj)
+ count -= 1
+
+ except:
+ # failed to read information, so just discard it and return empty
+ self.size = 0
+ self.free = 0
+ return []
+
+ # get end of file
+ self.cachefd.seek(0,2)
+ position = self.cachefd.tell()
+ newobjs = []
+ emptycount = 0
+
+ # walk backward through all, collecting new content and populating size
+ while len(cache):
+ obj = cache.pop()
+ if obj.creation == 0:
+ # unused slot, skip
+ emptycount += 1
+ elif obj.expired:
+ # object has passed expiration date, no sense processing
+ continue
+ elif obj.creation > date:
+ # used slot with new data, process
+ obj.size, position = position - obj.position, obj.position
+ newobjs.append(obj)
+ # update age
+ self.age = max(self.age, obj.creation)
+ elif len(newobjs):
+ # end of new data, break
+ break
+
+ # walk forward and load new content
+ for obj in newobjs:
+ obj.load(self.cachefd)
+
+ self.free = emptycount
+ return newobjs
+
+ def _write(self, data):
+ if self.free and (self.size != self.free):
+ # we only care about the last data point, since the rest are
+ # already stored in the file
+ data = data[-1]
+
+ # determine write position of data in cache
+ self.cachefd.seek(0,2)
+ end = self.cachefd.tell()
+ data.position = end
+
+ # write incremental update to free slot
+ self.cachefd.seek(4 + 16*(self.size-self.free))
+ data.dumpslot(self.cachefd)
+ data.dumpdata(self.cachefd)
+
+ else:
+ # rewrite cache file from scratch
+ # pull data from parent cache
+ data.extend(self.parent()._data.values())
+ data.sort(key=lambda x: x.creation)
+ # write header
+ size = len(data) + self.preallocate
+ self.cachefd.seek(0)
+ self.cachefd.truncate()
+ self.cachefd.write(self._struct.pack(self._version, size))
+ # write storage slot definitions
+ prev = None
+ for d in data:
+ if prev == None:
+ d.position = 4 + 16*size
+ else:
+ d.position = prev.position + prev.size
+ d.dumpslot(self.cachefd)
+ prev = d
+ # fill in allocated slots
+ for i in range(2**8):
+ self.cachefd.write(FileCacheObject._struct.pack(0, 0, 0))
+ # write stored data
+ for d in data:
+ d.dumpdata(self.cachefd)
+
+ self.cachefd.flush()
+
+ def expire(self, key):
+ pass
+
+
diff --git a/libs/tmdb3/cache_null.py b/libs/tmdb3/cache_null.py
new file mode 100755
index 00000000..a59741c4
--- /dev/null
+++ b/libs/tmdb3/cache_null.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: cache_null.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Null caching engine for debugging purposes
+#-----------------------
+
+from cache_engine import CacheEngine
+
+class NullEngine( CacheEngine ):
+ """Non-caching engine for debugging."""
+ name = 'null'
+ def configure(self): pass
+ def get(self, date): return []
+ def put(self, key, value, lifetime): return []
+ def expire(self, key): pass
+
diff --git a/libs/tmdb3/locales.py b/libs/tmdb3/locales.py
new file mode 100755
index 00000000..97efec72
--- /dev/null
+++ b/libs/tmdb3/locales.py
@@ -0,0 +1,634 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: locales.py Stores locale information for filtering results
+# Python Library
+# Author: Raymond Wagner
+#-----------------------
+
+from tmdb_exceptions import *
+import locale
+
+syslocale = None
+
+class LocaleBase( object ):
+ __slots__ = ['__immutable']
+ _stored = {}
+ fallthrough = False
+
+ def __init__(self, *keys):
+ for key in keys:
+ self._stored[key.lower()] = self
+ self.__immutable = True
+
+ def __setattr__(self, key, value):
+ if getattr(self, '__immutable', False):
+ raise NotImplementedError(self.__class__.__name__ +
+ ' does not support modification.')
+ super(LocaleBase, self).__setattr__(key, value)
+
+ def __delattr__(self, key):
+ if getattr(self, '__immutable', False):
+ raise NotImplementedError(self.__class__.__name__ +
+ ' does not support modification.')
+ super(LocaleBase, self).__delattr__(key)
+
+ def __lt__(self, other):
+ return (id(self) != id(other)) and (str(self) > str(other))
+ def __gt__(self, other):
+ return (id(self) != id(other)) and (str(self) < str(other))
+ def __eq__(self, other):
+ return (id(self) == id(other)) or (str(self) == str(other))
+
+ @classmethod
+ def getstored(cls, key):
+ if key is None:
+ return None
+ try:
+ return cls._stored[key.lower()]
+ except:
+ raise TMDBLocaleError("'{0}' is not a known valid {1} code."\
+ .format(key, cls.__name__))
+
+class Language( LocaleBase ):
+ __slots__ = ['ISO639_1', 'ISO639_2', 'ISO639_2B', 'englishname',
+ 'nativename']
+ _stored = {}
+
+ def __init__(self, iso1, iso2, ename):
+ self.ISO639_1 = iso1
+ self.ISO639_2 = iso2
+# self.ISO639_2B = iso2b
+ self.englishname = ename
+# self.nativename = nname
+ super(Language, self).__init__(iso1, iso2)
+
+ def __str__(self):
+ return self.ISO639_1
+
+ def __repr__(self):
+ return u"".format(self)
+
+class Country( LocaleBase ):
+ __slots__ = ['alpha2', 'name']
+ _stored = {}
+
+ def __init__(self, alpha2, name):
+ self.alpha2 = alpha2
+ self.name = name
+ super(Country, self).__init__(alpha2)
+
+ def __str__(self):
+ return self.alpha2
+
+ def __repr__(self):
+ return u"".format(self)
+
+class Locale( LocaleBase ):
+ __slots__ = ['language', 'country', 'encoding']
+
+ def __init__(self, language, country, encoding):
+ self.language = Language.getstored(language)
+ self.country = Country.getstored(country)
+ self.encoding = encoding if encoding else 'latin-1'
+
+ def __str__(self):
+ return u"{0}_{1}".format(self.language, self.country)
+
+ def __repr__(self):
+ return u"".format(self)
+
+ def encode(self, dat):
+ """Encode using system default encoding for network/file output."""
+ try:
+ return dat.encode(self.encoding)
+ except AttributeError:
+ # not a string type, pass along
+ return dat
+ except UnicodeDecodeError:
+ # just return unmodified and hope for the best
+ return dat
+
+ def decode(self, dat):
+ """Decode to system default encoding for internal use."""
+ try:
+ return dat.decode(self.encoding)
+ except AttributeError:
+ # not a string type, pass along
+ return dat
+ except UnicodeEncodeError:
+ # just return unmodified and hope for the best
+ return dat
+
+def set_locale(language=None, country=None, fallthrough=False):
+ global syslocale
+ LocaleBase.fallthrough = fallthrough
+
+ sysloc, sysenc = locale.getdefaultlocale()
+
+ if (not language) or (not country):
+ dat = None
+ if syslocale is not None:
+ dat = (str(syslocale.language), str(syslocale.country))
+ else:
+ if (sysloc is None) or ('_' not in sysloc):
+ dat = ('en', 'US')
+ else:
+ dat = sysloc.split('_')
+ if language is None:
+ language = dat[0]
+ if country is None:
+ country = dat[1]
+
+ syslocale = Locale(language, country, sysenc)
+
+def get_locale(language=-1, country=-1):
+ """Output locale using provided attributes, or return system locale."""
+ global syslocale
+ # pull existing stored values
+ if syslocale is None:
+ loc = Locale(None, None, locale.getdefaultlocale()[1])
+ else:
+ loc = syslocale
+
+ # both options are default, return stored values
+ if language == country == -1:
+ return loc
+
+ # supplement default option with stored values
+ if language == -1:
+ language = loc.language
+ elif country == -1:
+ country = loc.country
+ return Locale(language, country, loc.encoding)
+
+######## AUTOGENERATED LANGUAGE AND COUNTRY DATA BELOW HERE #########
+
+Language("ab", "abk", u"Abkhazian")
+Language("aa", "aar", u"Afar")
+Language("af", "afr", u"Afrikaans")
+Language("ak", "aka", u"Akan")
+Language("sq", "alb/sqi", u"Albanian")
+Language("am", "amh", u"Amharic")
+Language("ar", "ara", u"Arabic")
+Language("an", "arg", u"Aragonese")
+Language("hy", "arm/hye", u"Armenian")
+Language("as", "asm", u"Assamese")
+Language("av", "ava", u"Avaric")
+Language("ae", "ave", u"Avestan")
+Language("ay", "aym", u"Aymara")
+Language("az", "aze", u"Azerbaijani")
+Language("bm", "bam", u"Bambara")
+Language("ba", "bak", u"Bashkir")
+Language("eu", "baq/eus", u"Basque")
+Language("be", "bel", u"Belarusian")
+Language("bn", "ben", u"Bengali")
+Language("bh", "bih", u"Bihari languages")
+Language("bi", "bis", u"Bislama")
+Language("nb", "nob", u"Bokmål, Norwegian")
+Language("bs", "bos", u"Bosnian")
+Language("br", "bre", u"Breton")
+Language("bg", "bul", u"Bulgarian")
+Language("my", "bur/mya", u"Burmese")
+Language("es", "spa", u"Castilian")
+Language("ca", "cat", u"Catalan")
+Language("km", "khm", u"Central Khmer")
+Language("ch", "cha", u"Chamorro")
+Language("ce", "che", u"Chechen")
+Language("ny", "nya", u"Chewa")
+Language("ny", "nya", u"Chichewa")
+Language("zh", "chi/zho", u"Chinese")
+Language("za", "zha", u"Chuang")
+Language("cu", "chu", u"Church Slavic")
+Language("cu", "chu", u"Church Slavonic")
+Language("cv", "chv", u"Chuvash")
+Language("kw", "cor", u"Cornish")
+Language("co", "cos", u"Corsican")
+Language("cr", "cre", u"Cree")
+Language("hr", "hrv", u"Croatian")
+Language("cs", "cze/ces", u"Czech")
+Language("da", "dan", u"Danish")
+Language("dv", "div", u"Dhivehi")
+Language("dv", "div", u"Divehi")
+Language("nl", "dut/nld", u"Dutch")
+Language("dz", "dzo", u"Dzongkha")
+Language("en", "eng", u"English")
+Language("eo", "epo", u"Esperanto")
+Language("et", "est", u"Estonian")
+Language("ee", "ewe", u"Ewe")
+Language("fo", "fao", u"Faroese")
+Language("fj", "fij", u"Fijian")
+Language("fi", "fin", u"Finnish")
+Language("nl", "dut/nld", u"Flemish")
+Language("fr", "fre/fra", u"French")
+Language("ff", "ful", u"Fulah")
+Language("gd", "gla", u"Gaelic")
+Language("gl", "glg", u"Galician")
+Language("lg", "lug", u"Ganda")
+Language("ka", "geo/kat", u"Georgian")
+Language("de", "ger/deu", u"German")
+Language("ki", "kik", u"Gikuyu")
+Language("el", "gre/ell", u"Greek, Modern (1453-)")
+Language("kl", "kal", u"Greenlandic")
+Language("gn", "grn", u"Guarani")
+Language("gu", "guj", u"Gujarati")
+Language("ht", "hat", u"Haitian")
+Language("ht", "hat", u"Haitian Creole")
+Language("ha", "hau", u"Hausa")
+Language("he", "heb", u"Hebrew")
+Language("hz", "her", u"Herero")
+Language("hi", "hin", u"Hindi")
+Language("ho", "hmo", u"Hiri Motu")
+Language("hu", "hun", u"Hungarian")
+Language("is", "ice/isl", u"Icelandic")
+Language("io", "ido", u"Ido")
+Language("ig", "ibo", u"Igbo")
+Language("id", "ind", u"Indonesian")
+Language("ia", "ina", u"Interlingua (International Auxiliary Language Association)")
+Language("ie", "ile", u"Interlingue")
+Language("iu", "iku", u"Inuktitut")
+Language("ik", "ipk", u"Inupiaq")
+Language("ga", "gle", u"Irish")
+Language("it", "ita", u"Italian")
+Language("ja", "jpn", u"Japanese")
+Language("jv", "jav", u"Javanese")
+Language("kl", "kal", u"Kalaallisut")
+Language("kn", "kan", u"Kannada")
+Language("kr", "kau", u"Kanuri")
+Language("ks", "kas", u"Kashmiri")
+Language("kk", "kaz", u"Kazakh")
+Language("ki", "kik", u"Kikuyu")
+Language("rw", "kin", u"Kinyarwanda")
+Language("ky", "kir", u"Kirghiz")
+Language("kv", "kom", u"Komi")
+Language("kg", "kon", u"Kongo")
+Language("ko", "kor", u"Korean")
+Language("kj", "kua", u"Kuanyama")
+Language("ku", "kur", u"Kurdish")
+Language("kj", "kua", u"Kwanyama")
+Language("ky", "kir", u"Kyrgyz")
+Language("lo", "lao", u"Lao")
+Language("la", "lat", u"Latin")
+Language("lv", "lav", u"Latvian")
+Language("lb", "ltz", u"Letzeburgesch")
+Language("li", "lim", u"Limburgan")
+Language("li", "lim", u"Limburger")
+Language("li", "lim", u"Limburgish")
+Language("ln", "lin", u"Lingala")
+Language("lt", "lit", u"Lithuanian")
+Language("lu", "lub", u"Luba-Katanga")
+Language("lb", "ltz", u"Luxembourgish")
+Language("mk", "mac/mkd", u"Macedonian")
+Language("mg", "mlg", u"Malagasy")
+Language("ms", "may/msa", u"Malay")
+Language("ml", "mal", u"Malayalam")
+Language("dv", "div", u"Maldivian")
+Language("mt", "mlt", u"Maltese")
+Language("gv", "glv", u"Manx")
+Language("mi", "mao/mri", u"Maori")
+Language("mr", "mar", u"Marathi")
+Language("mh", "mah", u"Marshallese")
+Language("ro", "rum/ron", u"Moldavian")
+Language("ro", "rum/ron", u"Moldovan")
+Language("mn", "mon", u"Mongolian")
+Language("na", "nau", u"Nauru")
+Language("nv", "nav", u"Navaho")
+Language("nv", "nav", u"Navajo")
+Language("nd", "nde", u"Ndebele, North")
+Language("nr", "nbl", u"Ndebele, South")
+Language("ng", "ndo", u"Ndonga")
+Language("ne", "nep", u"Nepali")
+Language("nd", "nde", u"North Ndebele")
+Language("se", "sme", u"Northern Sami")
+Language("no", "nor", u"Norwegian")
+Language("nb", "nob", u"Norwegian Bokmål")
+Language("nn", "nno", u"Norwegian Nynorsk")
+Language("ii", "iii", u"Nuosu")
+Language("ny", "nya", u"Nyanja")
+Language("nn", "nno", u"Nynorsk, Norwegian")
+Language("ie", "ile", u"Occidental")
+Language("oc", "oci", u"Occitan (post 1500)")
+Language("oj", "oji", u"Ojibwa")
+Language("cu", "chu", u"Old Bulgarian")
+Language("cu", "chu", u"Old Church Slavonic")
+Language("cu", "chu", u"Old Slavonic")
+Language("or", "ori", u"Oriya")
+Language("om", "orm", u"Oromo")
+Language("os", "oss", u"Ossetian")
+Language("os", "oss", u"Ossetic")
+Language("pi", "pli", u"Pali")
+Language("pa", "pan", u"Panjabi")
+Language("ps", "pus", u"Pashto")
+Language("fa", "per/fas", u"Persian")
+Language("pl", "pol", u"Polish")
+Language("pt", "por", u"Portuguese")
+Language("pa", "pan", u"Punjabi")
+Language("ps", "pus", u"Pushto")
+Language("qu", "que", u"Quechua")
+Language("ro", "rum/ron", u"Romanian")
+Language("rm", "roh", u"Romansh")
+Language("rn", "run", u"Rundi")
+Language("ru", "rus", u"Russian")
+Language("sm", "smo", u"Samoan")
+Language("sg", "sag", u"Sango")
+Language("sa", "san", u"Sanskrit")
+Language("sc", "srd", u"Sardinian")
+Language("gd", "gla", u"Scottish Gaelic")
+Language("sr", "srp", u"Serbian")
+Language("sn", "sna", u"Shona")
+Language("ii", "iii", u"Sichuan Yi")
+Language("sd", "snd", u"Sindhi")
+Language("si", "sin", u"Sinhala")
+Language("si", "sin", u"Sinhalese")
+Language("sk", "slo/slk", u"Slovak")
+Language("sl", "slv", u"Slovenian")
+Language("so", "som", u"Somali")
+Language("st", "sot", u"Sotho, Southern")
+Language("nr", "nbl", u"South Ndebele")
+Language("es", "spa", u"Spanish")
+Language("su", "sun", u"Sundanese")
+Language("sw", "swa", u"Swahili")
+Language("ss", "ssw", u"Swati")
+Language("sv", "swe", u"Swedish")
+Language("tl", "tgl", u"Tagalog")
+Language("ty", "tah", u"Tahitian")
+Language("tg", "tgk", u"Tajik")
+Language("ta", "tam", u"Tamil")
+Language("tt", "tat", u"Tatar")
+Language("te", "tel", u"Telugu")
+Language("th", "tha", u"Thai")
+Language("bo", "tib/bod", u"Tibetan")
+Language("ti", "tir", u"Tigrinya")
+Language("to", "ton", u"Tonga (Tonga Islands)")
+Language("ts", "tso", u"Tsonga")
+Language("tn", "tsn", u"Tswana")
+Language("tr", "tur", u"Turkish")
+Language("tk", "tuk", u"Turkmen")
+Language("tw", "twi", u"Twi")
+Language("ug", "uig", u"Uighur")
+Language("uk", "ukr", u"Ukrainian")
+Language("ur", "urd", u"Urdu")
+Language("ug", "uig", u"Uyghur")
+Language("uz", "uzb", u"Uzbek")
+Language("ca", "cat", u"Valencian")
+Language("ve", "ven", u"Venda")
+Language("vi", "vie", u"Vietnamese")
+Language("vo", "vol", u"Volapük")
+Language("wa", "wln", u"Walloon")
+Language("cy", "wel/cym", u"Welsh")
+Language("fy", "fry", u"Western Frisian")
+Language("wo", "wol", u"Wolof")
+Language("xh", "xho", u"Xhosa")
+Language("yi", "yid", u"Yiddish")
+Language("yo", "yor", u"Yoruba")
+Language("za", "zha", u"Zhuang")
+Language("zu", "zul", u"Zulu")
+Country("AF", u"AFGHANISTAN")
+Country("AX", u"Ã…LAND ISLANDS")
+Country("AL", u"ALBANIA")
+Country("DZ", u"ALGERIA")
+Country("AS", u"AMERICAN SAMOA")
+Country("AD", u"ANDORRA")
+Country("AO", u"ANGOLA")
+Country("AI", u"ANGUILLA")
+Country("AQ", u"ANTARCTICA")
+Country("AG", u"ANTIGUA AND BARBUDA")
+Country("AR", u"ARGENTINA")
+Country("AM", u"ARMENIA")
+Country("AW", u"ARUBA")
+Country("AU", u"AUSTRALIA")
+Country("AT", u"AUSTRIA")
+Country("AZ", u"AZERBAIJAN")
+Country("BS", u"BAHAMAS")
+Country("BH", u"BAHRAIN")
+Country("BD", u"BANGLADESH")
+Country("BB", u"BARBADOS")
+Country("BY", u"BELARUS")
+Country("BE", u"BELGIUM")
+Country("BZ", u"BELIZE")
+Country("BJ", u"BENIN")
+Country("BM", u"BERMUDA")
+Country("BT", u"BHUTAN")
+Country("BO", u"BOLIVIA, PLURINATIONAL STATE OF")
+Country("BQ", u"BONAIRE, SINT EUSTATIUS AND SABA")
+Country("BA", u"BOSNIA AND HERZEGOVINA")
+Country("BW", u"BOTSWANA")
+Country("BV", u"BOUVET ISLAND")
+Country("BR", u"BRAZIL")
+Country("IO", u"BRITISH INDIAN OCEAN TERRITORY")
+Country("BN", u"BRUNEI DARUSSALAM")
+Country("BG", u"BULGARIA")
+Country("BF", u"BURKINA FASO")
+Country("BI", u"BURUNDI")
+Country("KH", u"CAMBODIA")
+Country("CM", u"CAMEROON")
+Country("CA", u"CANADA")
+Country("CV", u"CAPE VERDE")
+Country("KY", u"CAYMAN ISLANDS")
+Country("CF", u"CENTRAL AFRICAN REPUBLIC")
+Country("TD", u"CHAD")
+Country("CL", u"CHILE")
+Country("CN", u"CHINA")
+Country("CX", u"CHRISTMAS ISLAND")
+Country("CC", u"COCOS (KEELING) ISLANDS")
+Country("CO", u"COLOMBIA")
+Country("KM", u"COMOROS")
+Country("CG", u"CONGO")
+Country("CD", u"CONGO, THE DEMOCRATIC REPUBLIC OF THE")
+Country("CK", u"COOK ISLANDS")
+Country("CR", u"COSTA RICA")
+Country("CI", u"CÔTE D'IVOIRE")
+Country("HR", u"CROATIA")
+Country("CU", u"CUBA")
+Country("CW", u"CURAÇAO")
+Country("CY", u"CYPRUS")
+Country("CZ", u"CZECH REPUBLIC")
+Country("DK", u"DENMARK")
+Country("DJ", u"DJIBOUTI")
+Country("DM", u"DOMINICA")
+Country("DO", u"DOMINICAN REPUBLIC")
+Country("EC", u"ECUADOR")
+Country("EG", u"EGYPT")
+Country("SV", u"EL SALVADOR")
+Country("GQ", u"EQUATORIAL GUINEA")
+Country("ER", u"ERITREA")
+Country("EE", u"ESTONIA")
+Country("ET", u"ETHIOPIA")
+Country("FK", u"FALKLAND ISLANDS (MALVINAS)")
+Country("FO", u"FAROE ISLANDS")
+Country("FJ", u"FIJI")
+Country("FI", u"FINLAND")
+Country("FR", u"FRANCE")
+Country("GF", u"FRENCH GUIANA")
+Country("PF", u"FRENCH POLYNESIA")
+Country("TF", u"FRENCH SOUTHERN TERRITORIES")
+Country("GA", u"GABON")
+Country("GM", u"GAMBIA")
+Country("GE", u"GEORGIA")
+Country("DE", u"GERMANY")
+Country("GH", u"GHANA")
+Country("GI", u"GIBRALTAR")
+Country("GR", u"GREECE")
+Country("GL", u"GREENLAND")
+Country("GD", u"GRENADA")
+Country("GP", u"GUADELOUPE")
+Country("GU", u"GUAM")
+Country("GT", u"GUATEMALA")
+Country("GG", u"GUERNSEY")
+Country("GN", u"GUINEA")
+Country("GW", u"GUINEA-BISSAU")
+Country("GY", u"GUYANA")
+Country("HT", u"HAITI")
+Country("HM", u"HEARD ISLAND AND MCDONALD ISLANDS")
+Country("VA", u"HOLY SEE (VATICAN CITY STATE)")
+Country("HN", u"HONDURAS")
+Country("HK", u"HONG KONG")
+Country("HU", u"HUNGARY")
+Country("IS", u"ICELAND")
+Country("IN", u"INDIA")
+Country("ID", u"INDONESIA")
+Country("IR", u"IRAN, ISLAMIC REPUBLIC OF")
+Country("IQ", u"IRAQ")
+Country("IE", u"IRELAND")
+Country("IM", u"ISLE OF MAN")
+Country("IL", u"ISRAEL")
+Country("IT", u"ITALY")
+Country("JM", u"JAMAICA")
+Country("JP", u"JAPAN")
+Country("JE", u"JERSEY")
+Country("JO", u"JORDAN")
+Country("KZ", u"KAZAKHSTAN")
+Country("KE", u"KENYA")
+Country("KI", u"KIRIBATI")
+Country("KP", u"KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF")
+Country("KR", u"KOREA, REPUBLIC OF")
+Country("KW", u"KUWAIT")
+Country("KG", u"KYRGYZSTAN")
+Country("LA", u"LAO PEOPLE'S DEMOCRATIC REPUBLIC")
+Country("LV", u"LATVIA")
+Country("LB", u"LEBANON")
+Country("LS", u"LESOTHO")
+Country("LR", u"LIBERIA")
+Country("LY", u"LIBYA")
+Country("LI", u"LIECHTENSTEIN")
+Country("LT", u"LITHUANIA")
+Country("LU", u"LUXEMBOURG")
+Country("MO", u"MACAO")
+Country("MK", u"MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF")
+Country("MG", u"MADAGASCAR")
+Country("MW", u"MALAWI")
+Country("MY", u"MALAYSIA")
+Country("MV", u"MALDIVES")
+Country("ML", u"MALI")
+Country("MT", u"MALTA")
+Country("MH", u"MARSHALL ISLANDS")
+Country("MQ", u"MARTINIQUE")
+Country("MR", u"MAURITANIA")
+Country("MU", u"MAURITIUS")
+Country("YT", u"MAYOTTE")
+Country("MX", u"MEXICO")
+Country("FM", u"MICRONESIA, FEDERATED STATES OF")
+Country("MD", u"MOLDOVA, REPUBLIC OF")
+Country("MC", u"MONACO")
+Country("MN", u"MONGOLIA")
+Country("ME", u"MONTENEGRO")
+Country("MS", u"MONTSERRAT")
+Country("MA", u"MOROCCO")
+Country("MZ", u"MOZAMBIQUE")
+Country("MM", u"MYANMAR")
+Country("NA", u"NAMIBIA")
+Country("NR", u"NAURU")
+Country("NP", u"NEPAL")
+Country("NL", u"NETHERLANDS")
+Country("NC", u"NEW CALEDONIA")
+Country("NZ", u"NEW ZEALAND")
+Country("NI", u"NICARAGUA")
+Country("NE", u"NIGER")
+Country("NG", u"NIGERIA")
+Country("NU", u"NIUE")
+Country("NF", u"NORFOLK ISLAND")
+Country("MP", u"NORTHERN MARIANA ISLANDS")
+Country("NO", u"NORWAY")
+Country("OM", u"OMAN")
+Country("PK", u"PAKISTAN")
+Country("PW", u"PALAU")
+Country("PS", u"PALESTINIAN TERRITORY, OCCUPIED")
+Country("PA", u"PANAMA")
+Country("PG", u"PAPUA NEW GUINEA")
+Country("PY", u"PARAGUAY")
+Country("PE", u"PERU")
+Country("PH", u"PHILIPPINES")
+Country("PN", u"PITCAIRN")
+Country("PL", u"POLAND")
+Country("PT", u"PORTUGAL")
+Country("PR", u"PUERTO RICO")
+Country("QA", u"QATAR")
+Country("RE", u"RÉUNION")
+Country("RO", u"ROMANIA")
+Country("RU", u"RUSSIAN FEDERATION")
+Country("RW", u"RWANDA")
+Country("BL", u"SAINT BARTHÉLEMY")
+Country("SH", u"SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA")
+Country("KN", u"SAINT KITTS AND NEVIS")
+Country("LC", u"SAINT LUCIA")
+Country("MF", u"SAINT MARTIN (FRENCH PART)")
+Country("PM", u"SAINT PIERRE AND MIQUELON")
+Country("VC", u"SAINT VINCENT AND THE GRENADINES")
+Country("WS", u"SAMOA")
+Country("SM", u"SAN MARINO")
+Country("ST", u"SAO TOME AND PRINCIPE")
+Country("SA", u"SAUDI ARABIA")
+Country("SN", u"SENEGAL")
+Country("RS", u"SERBIA")
+Country("SC", u"SEYCHELLES")
+Country("SL", u"SIERRA LEONE")
+Country("SG", u"SINGAPORE")
+Country("SX", u"SINT MAARTEN (DUTCH PART)")
+Country("SK", u"SLOVAKIA")
+Country("SI", u"SLOVENIA")
+Country("SB", u"SOLOMON ISLANDS")
+Country("SO", u"SOMALIA")
+Country("ZA", u"SOUTH AFRICA")
+Country("GS", u"SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS")
+Country("SS", u"SOUTH SUDAN")
+Country("ES", u"SPAIN")
+Country("LK", u"SRI LANKA")
+Country("SD", u"SUDAN")
+Country("SR", u"SURINAME")
+Country("SJ", u"SVALBARD AND JAN MAYEN")
+Country("SZ", u"SWAZILAND")
+Country("SE", u"SWEDEN")
+Country("CH", u"SWITZERLAND")
+Country("SY", u"SYRIAN ARAB REPUBLIC")
+Country("TW", u"TAIWAN, PROVINCE OF CHINA")
+Country("TJ", u"TAJIKISTAN")
+Country("TZ", u"TANZANIA, UNITED REPUBLIC OF")
+Country("TH", u"THAILAND")
+Country("TL", u"TIMOR-LESTE")
+Country("TG", u"TOGO")
+Country("TK", u"TOKELAU")
+Country("TO", u"TONGA")
+Country("TT", u"TRINIDAD AND TOBAGO")
+Country("TN", u"TUNISIA")
+Country("TR", u"TURKEY")
+Country("TM", u"TURKMENISTAN")
+Country("TC", u"TURKS AND CAICOS ISLANDS")
+Country("TV", u"TUVALU")
+Country("UG", u"UGANDA")
+Country("UA", u"UKRAINE")
+Country("AE", u"UNITED ARAB EMIRATES")
+Country("GB", u"UNITED KINGDOM")
+Country("US", u"UNITED STATES")
+Country("UM", u"UNITED STATES MINOR OUTLYING ISLANDS")
+Country("UY", u"URUGUAY")
+Country("UZ", u"UZBEKISTAN")
+Country("VU", u"VANUATU")
+Country("VE", u"VENEZUELA, BOLIVARIAN REPUBLIC OF")
+Country("VN", u"VIET NAM")
+Country("VG", u"VIRGIN ISLANDS, BRITISH")
+Country("VI", u"VIRGIN ISLANDS, U.S.")
+Country("WF", u"WALLIS AND FUTUNA")
+Country("EH", u"WESTERN SAHARA")
+Country("YE", u"YEMEN")
+Country("ZM", u"ZAMBIA")
+Country("ZW", u"ZIMBABWE")
diff --git a/libs/tmdb3/pager.py b/libs/tmdb3/pager.py
new file mode 100755
index 00000000..6cb874c0
--- /dev/null
+++ b/libs/tmdb3/pager.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: pager.py List-like structure designed for handling paged results
+# Python Library
+# Author: Raymond Wagner
+#-----------------------
+
+from collections import Sequence, Iterator
+
+class PagedIterator( Iterator ):
+ def __init__(self, parent):
+ self._parent = parent
+ self._index = -1
+ self._len = len(parent)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ self._index += 1
+ if self._index == self._len:
+ raise StopIteration
+ return self._parent[self._index]
+
+class UnpagedData( object ):
+ def copy(self):
+ return self.__class__()
+
+ def __mul__(self, other):
+ return (self.copy() for a in range(other))
+
+ def __rmul__(self, other):
+ return (self.copy() for a in range(other))
+
+class PagedList( Sequence ):
+ """
+ List-like object, with support for automatically grabbing additional
+ pages from a data source.
+ """
+ _iter_class = None
+
+ def __iter__(self):
+ if self._iter_class is None:
+ self._iter_class = type(self.__class__.__name__ + 'Iterator',
+ (PagedIterator,), {})
+ return self._iter_class(self)
+
+ def __len__(self):
+ try:
+ return self._len
+ except:
+ return len(self._data)
+
+ def __init__(self, iterable, pagesize=20):
+ self._data = list(iterable)
+ self._pagesize = pagesize
+
+ def __getitem__(self, index):
+ if isinstance(index, slice):
+ return [self[x] for x in xrange(*index.indices(len(self)))]
+ if index >= len(self):
+ raise IndexError("list index outside range")
+ if (index >= len(self._data)) \
+ or isinstance(self._data[index], UnpagedData):
+ self._populatepage(index/self._pagesize + 1)
+ return self._data[index]
+
+ def __setitem__(self, index, value):
+ raise NotImplementedError
+
+ def __delitem__(self, index):
+ raise NotImplementedError
+
+ def __contains__(self, item):
+ raise NotImplementedError
+
+ def _populatepage(self, page):
+ pagestart = (page-1) * self._pagesize
+ if len(self._data) < pagestart:
+ self._data.extend(UnpagedData()*(pagestart-len(self._data)))
+ if len(self._data) == pagestart:
+ self._data.extend(self._getpage(page))
+ else:
+ for data in self._getpage(page):
+ self._data[pagestart] = data
+ pagestart += 1
+
+ def _getpage(self, page):
+ raise NotImplementedError("PagedList._getpage() must be provided "+\
+ "by subclass")
+
+class PagedRequest( PagedList ):
+ """
+ Derived PageList that provides a list-like object with automatic paging
+ intended for use with search requests.
+ """
+ def __init__(self, request, handler=None):
+ self._request = request
+ if handler: self._handler = handler
+ super(PagedRequest, self).__init__(self._getpage(1), 20)
+
+ def _getpage(self, page):
+ req = self._request.new(page=page)
+ res = req.readJSON()
+ self._len = res['total_results']
+ for item in res['results']:
+ yield self._handler(item)
+
diff --git a/libs/tmdb3/request.py b/libs/tmdb3/request.py
new file mode 100755
index 00000000..109630d4
--- /dev/null
+++ b/libs/tmdb3/request.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: tmdb_request.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Wrapped urllib2.Request class pre-configured for accessing the
+# TMDb v3 API
+#-----------------------
+
+from tmdb_exceptions import *
+from locales import get_locale
+from cache import Cache
+
+from urllib import urlencode
+import urllib2
+import json
+
+DEBUG = False
+cache = Cache(filename='pytmdb3.cache')
+
+#DEBUG = True
+#cache = Cache(engine='null')
+
+def set_key(key):
+ """
+ Specify the API key to use retrieving data from themoviedb.org. This
+ key must be set before any calls will function.
+ """
+ if len(key) != 32:
+ raise TMDBKeyInvalid("Specified API key must be 128-bit hex")
+ try:
+ int(key, 16)
+ except:
+ raise TMDBKeyInvalid("Specified API key must be 128-bit hex")
+ Request._api_key = key
+
+def set_cache(engine=None, *args, **kwargs):
+ """Specify caching engine and properties."""
+ cache.configure(engine, *args, **kwargs)
+
+class Request( urllib2.Request ):
+ _api_key = None
+ _base_url = "http://api.themoviedb.org/3/"
+
+ @property
+ def api_key(self):
+ if self._api_key is None:
+ raise TMDBKeyMissing("API key must be specified before "+\
+ "requests can be made")
+ return self._api_key
+
+ def __init__(self, url, **kwargs):
+ """Return a request object, using specified API path and arguments."""
+ kwargs['api_key'] = self.api_key
+ self._url = url.lstrip('/')
+ self._kwargs = dict([(kwa,kwv) for kwa,kwv in kwargs.items()
+ if kwv is not None])
+
+ locale = get_locale()
+ kwargs = {}
+ for k,v in self._kwargs.items():
+ kwargs[k] = locale.encode(v)
+ url = '{0}{1}?{2}'.format(self._base_url, self._url, urlencode(kwargs))
+
+ urllib2.Request.__init__(self, url)
+ self.add_header('Accept', 'application/json')
+ self.lifetime = 3600 # 1hr
+
+ def new(self, **kwargs):
+ """Create a new instance of the request, with tweaked arguments."""
+ args = dict(self._kwargs)
+ for k,v in kwargs.items():
+ if v is None:
+ if k in args:
+ del args[k]
+ else:
+ args[k] = v
+ obj = self.__class__(self._url, **args)
+ obj.lifetime = self.lifetime
+ return obj
+
+ def add_data(self, data):
+ """Provide data to be sent with POST."""
+ urllib2.Request.add_data(self, urlencode(data))
+
+ def open(self):
+ """Open a file object to the specified URL."""
+ try:
+ if DEBUG:
+ print 'loading '+self.get_full_url()
+ if self.has_data():
+ print ' '+self.get_data()
+ return urllib2.urlopen(self)
+ except urllib2.HTTPError, e:
+ raise TMDBHTTPError(e)
+
+ def read(self):
+ """Return result from specified URL as a string."""
+ return self.open().read()
+
+ @cache.cached(urllib2.Request.get_full_url)
+ def readJSON(self):
+ """Parse result from specified URL as JSON data."""
+ url = self.get_full_url()
+ try:
+ # catch HTTP error from open()
+ data = json.load(self.open())
+ except TMDBHTTPError, e:
+ try:
+ # try to load whatever was returned
+ data = json.loads(e.response)
+ except:
+ # cannot parse json, just raise existing error
+ raise e
+ else:
+ # response parsed, try to raise error from TMDB
+ handle_status(data, url)
+ # no error from TMDB, just raise existing error
+ raise e
+ handle_status(data, url)
+ #if DEBUG:
+ # import pprint
+ # pprint.PrettyPrinter().pprint(data)
+ return data
+
+status_handlers = {
+ 1: None,
+ 2: TMDBRequestInvalid('Invalid service - This service does not exist.'),
+ 3: TMDBRequestError('Authentication Failed - You do not have '+\
+ 'permissions to access this service.'),
+ 4: TMDBRequestInvalid("Invalid format - This service doesn't exist "+\
+ 'in that format.'),
+ 5: TMDBRequestInvalid('Invalid parameters - Your request parameters '+\
+ 'are incorrect.'),
+ 6: TMDBRequestInvalid('Invalid id - The pre-requisite id is invalid '+\
+ 'or not found.'),
+ 7: TMDBKeyInvalid('Invalid API key - You must be granted a valid key.'),
+ 8: TMDBRequestError('Duplicate entry - The data you tried to submit '+\
+ 'already exists.'),
+ 9: TMDBOffline('This service is tempirarily offline. Try again later.'),
+ 10: TMDBKeyRevoked('Suspended API key - Access to your account has been '+\
+ 'suspended, contact TMDB.'),
+ 11: TMDBError('Internal error - Something went wrong. Contact TMDb.'),
+ 12: None,
+ 13: None,
+ 14: TMDBRequestError('Authentication Failed.'),
+ 15: TMDBError('Failed'),
+ 16: TMDBError('Device Denied'),
+ 17: TMDBError('Session Denied')}
+
+def handle_status(data, query):
+ status = status_handlers[data.get('status_code', 1)]
+ if status is not None:
+ status.tmdberrno = data['status_code']
+ status.query = query
+ raise status
diff --git a/libs/tmdb3/tmdb_api.py b/libs/tmdb3/tmdb_api.py
new file mode 100755
index 00000000..b5cb0a90
--- /dev/null
+++ b/libs/tmdb3/tmdb_api.py
@@ -0,0 +1,689 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: tmdb_api.py Simple-to-use Python interface to TMDB's API v3
+# Python Library
+# Author: Raymond Wagner
+# Purpose: This Python library is intended to provide a series of classes
+# and methods for search and retrieval of text metadata and image
+# URLs from TMDB.
+# Preliminary API specifications can be found at
+# http://help.themoviedb.org/kb/api/about-3
+# License: Creative Commons GNU GPL v2
+# (http://creativecommons.org/licenses/GPL/2.0/)
+#-----------------------
+
+__title__ = "tmdb_api - Simple-to-use Python interface to TMDB's API v3 "+\
+ "(www.themoviedb.org)"
+__author__ = "Raymond Wagner"
+__purpose__ = """
+This Python library is intended to provide a series of classes and methods
+for search and retrieval of text metadata and image URLs from TMDB.
+Preliminary API specifications can be found at
+http://help.themoviedb.org/kb/api/about-3"""
+
+__version__="v0.6.17"
+# 0.1.0 Initial development
+# 0.2.0 Add caching mechanism for API queries
+# 0.2.1 Temporary work around for broken search paging
+# 0.3.0 Rework backend machinery for managing OO interface to results
+# 0.3.1 Add collection support
+# 0.3.2 Remove MythTV key from results.py
+# 0.3.3 Add functional language support
+# 0.3.4 Re-enable search paging
+# 0.3.5 Add methods for grabbing current, popular, and top rated movies
+# 0.3.6 Rework paging mechanism
+# 0.3.7 Generalize caching mechanism, and allow controllability
+# 0.4.0 Add full locale support (language and country) and optional fall through
+# 0.4.1 Add custom classmethod for dealing with IMDB movie IDs
+# 0.4.2 Improve cache file selection for Windows systems
+# 0.4.3 Add a few missed Person properties
+# 0.4.4 Add support for additional Studio information
+# 0.4.5 Add locale fallthrough for images and alternate titles
+# 0.4.6 Add slice support for search results
+# 0.5.0 Rework cache framework and improve file cache performance
+# 0.6.0 Add user authentication support
+# 0.6.1 Add adult filtering for people searches
+# 0.6.2 Add similar movie search for Movie objects
+# 0.6.3 Add Studio search
+# 0.6.4 Add Genre list and associated Movie search
+# 0.6.5 Prevent data from being blanked out by subsequent queries
+# 0.6.6 Turn date processing errors into mutable warnings
+# 0.6.7 Add support for searching by year
+# 0.6.8 Add support for collection images
+# 0.6.9 Correct Movie image language filtering
+# 0.6.10 Add upcoming movie classmethod
+# 0.6.11 Fix URL for top rated Movie query
+# 0.6.12 Add support for Movie watchlist query and editing
+# 0.6.13 Fix URL for rating Movies
+# 0.6.14 Add support for Lists
+# 0.6.15 Add ability to search Collections
+# 0.6.16 Make absent primary images return None (previously u'')
+# 0.6.17 Add userrating/votes to Image, add overview to Collection, remove
+# releasedate sorting from Collection Movies
+
+from request import set_key, Request
+from util import Datapoint, Datalist, Datadict, Element, NameRepr, SearchRepr
+from pager import PagedRequest
+from locales import get_locale, set_locale
+from tmdb_auth import get_session, set_session
+from tmdb_exceptions import *
+
+import datetime
+
+DEBUG = False
+
+def process_date(datestr):
+ try:
+ return datetime.date(*[int(x) for x in datestr.split('-')])
+ except (TypeError, ValueError):
+ import sys
+ import warnings
+ import traceback
+ _,_,tb = sys.exc_info()
+ f,l,_,_ = traceback.extract_tb(tb)[-1]
+ warnings.warn_explicit(('"{0}" is not a supported date format. '
+ 'Please fix upstream data at http://www.themoviedb.org.')\
+ .format(datestr), Warning, f, l)
+ return None
+
+class Configuration( Element ):
+ images = Datapoint('images')
+ def _populate(self):
+ return Request('configuration')
+Configuration = Configuration()
+
+class Account( NameRepr, Element ):
+ def _populate(self):
+ return Request('account', session_id=self._session.sessionid)
+
+ id = Datapoint('id')
+ adult = Datapoint('include_adult')
+ country = Datapoint('iso_3166_1')
+ language = Datapoint('iso_639_1')
+ name = Datapoint('name')
+ username = Datapoint('username')
+
+ @property
+ def locale(self):
+ return get_locale(self.language, self.country)
+
+def searchMovie(query, locale=None, adult=False, year=None):
+ kwargs = {'query':query, 'include_adult':adult}
+ if year is not None:
+ try:
+ kwargs['year'] = year.year
+ except AttributeError:
+ kwargs['year'] = year
+ return MovieSearchResult(Request('search/movie', **kwargs), locale=locale)
+
+def searchMovieWithYear(query, locale=None, adult=False):
+ year = None
+ if (len(query) > 6) and (query[-1] == ')') and (query[-6] == '('):
+ # simple syntax check, no need for regular expression
+ try:
+ year = int(query[-5:-1])
+ except ValueError:
+ pass
+ else:
+ if 1885 < year < 2050:
+ # strip out year from search
+ query = query[:-7]
+ else:
+ # sanity check on resolved year failed, pass through
+ year = None
+ return searchMovie(query, locale, adult, year)
+
+class MovieSearchResult( SearchRepr, PagedRequest ):
+ """Stores a list of search matches."""
+ _name = None
+ def __init__(self, request, locale=None):
+ if locale is None:
+ locale = get_locale()
+ super(MovieSearchResult, self).__init__(
+ request.new(language=locale.language),
+ lambda x: Movie(raw=x, locale=locale))
+
+def searchPerson(query, adult=False):
+ return PeopleSearchResult(Request('search/person', query=query,
+ include_adult=adult))
+
+class PeopleSearchResult( SearchRepr, PagedRequest ):
+ """Stores a list of search matches."""
+ _name = None
+ def __init__(self, request):
+ super(PeopleSearchResult, self).__init__(request,
+ lambda x: Person(raw=x))
+
+def searchStudio(query):
+ return StudioSearchResult(Request('search/company', query=query))
+
+class StudioSearchResult( SearchRepr, PagedRequest ):
+ """Stores a list of search matches."""
+ _name = None
+ def __init__(self, request):
+ super(StudioSearchResult, self).__init__(request,
+ lambda x: Studio(raw=x))
+
+def searchList(query, adult=False):
+ ListSearchResult(Request('search/list', query=query, include_adult=adult))
+
+class ListSearchResult( SearchRepr, PagedRequest ):
+ """Stores a list of search matches."""
+ _name = None
+ def __init__(self, request):
+ super(ListSearchResult, self).__init__(request,
+ lambda x: List(raw=x))
+
+def searchCollection(query, locale=None):
+ return CollectionSearchResult(Request('search/collection', query=query),
+ locale=locale)
+
+class CollectionSearchResult( SearchRepr, PagedRequest ):
+ """Stores a list of search matches."""
+ _name=None
+ def __init__(self, request, locale=None):
+ if locale is None:
+ locale = get_locale()
+ super(CollectionSearchResult, self).__init__(
+ request.new(language=locale.language),
+ lambda x: Collection(raw=x, locale=locale))
+
+class Image( Element ):
+ filename = Datapoint('file_path', initarg=1,
+ handler=lambda x: x.lstrip('/'))
+ aspectratio = Datapoint('aspect_ratio')
+ height = Datapoint('height')
+ width = Datapoint('width')
+ language = Datapoint('iso_639_1')
+ userrating = Datapoint('vote_average')
+ votes = Datapoint('vote_count')
+
+ def sizes(self):
+ return ['original']
+
+ def geturl(self, size='original'):
+ if size not in self.sizes():
+ raise TMDBImageSizeError
+ url = Configuration.images['base_url'].rstrip('/')
+ return url+'/{0}/{1}'.format(size, self.filename)
+
+ # sort preferring locale's language, but keep remaining ordering consistent
+ def __lt__(self, other):
+ return (self.language == self._locale.language) \
+ and (self.language != other.language)
+ def __gt__(self, other):
+ return (self.language != other.language) \
+ and (other.language == self._locale.language)
+ # direct match for comparison
+ def __eq__(self, other):
+ return self.filename == other.filename
+ # special handling for boolean to see if exists
+ def __nonzero__(self):
+ if len(self.filename) == 0:
+ return False
+ return True
+
+ def __repr__(self):
+ # BASE62 encoded filename, no need to worry about unicode
+ return u"<{0.__class__.__name__} '{0.filename}'>".format(self)
+
+class Backdrop( Image ):
+ def sizes(self):
+ return Configuration.images['backdrop_sizes']
+class Poster( Image ):
+ def sizes(self):
+ return Configuration.images['poster_sizes']
+class Profile( Image ):
+ def sizes(self):
+ return Configuration.images['profile_sizes']
+class Logo( Image ):
+ def sizes(self):
+ return Configuration.images['logo_sizes']
+
+class AlternateTitle( Element ):
+ country = Datapoint('iso_3166_1')
+ title = Datapoint('title')
+
+ # sort preferring locale's country, but keep remaining ordering consistent
+ def __lt__(self, other):
+ return (self.country == self._locale.country) \
+ and (self.country != other.country)
+ def __gt__(self, other):
+ return (self.country != other.country) \
+ and (other.country == self._locale.country)
+ def __eq__(self, other):
+ return self.country == other.country
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.title}' ({0.country})>"\
+ .format(self).encode('utf-8')
+
+class Person( Element ):
+ id = Datapoint('id', initarg=1)
+ name = Datapoint('name')
+ biography = Datapoint('biography')
+ dayofbirth = Datapoint('birthday', default=None, handler=process_date)
+ dayofdeath = Datapoint('deathday', default=None, handler=process_date)
+ homepage = Datapoint('homepage')
+ birthplace = Datapoint('place_of_birth')
+ profile = Datapoint('profile_path', handler=Profile, \
+ raw=False, default=None)
+ adult = Datapoint('adult')
+ aliases = Datalist('also_known_as')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}'>"\
+ .format(self).encode('utf-8')
+
+ def _populate(self):
+ return Request('person/{0}'.format(self.id))
+ def _populate_credits(self):
+ return Request('person/{0}/credits'.format(self.id), \
+ language=self._locale.language)
+ def _populate_images(self):
+ return Request('person/{0}/images'.format(self.id))
+
+ roles = Datalist('cast', handler=lambda x: ReverseCast(raw=x), \
+ poller=_populate_credits)
+ crew = Datalist('crew', handler=lambda x: ReverseCrew(raw=x), \
+ poller=_populate_credits)
+ profiles = Datalist('profiles', handler=Profile, poller=_populate_images)
+
+class Cast( Person ):
+ character = Datapoint('character')
+ order = Datapoint('order')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}' as '{0.character}'>"\
+ .format(self).encode('utf-8')
+
+class Crew( Person ):
+ job = Datapoint('job')
+ department = Datapoint('department')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}','{0.job}'>"\
+ .format(self).encode('utf-8')
+
+class Keyword( Element ):
+ id = Datapoint('id')
+ name = Datapoint('name')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} {0.name}>".format(self).encode('utf-8')
+
+class Release( Element ):
+ certification = Datapoint('certification')
+ country = Datapoint('iso_3166_1')
+ releasedate = Datapoint('release_date', handler=process_date)
+ def __repr__(self):
+ return u"<{0.__class__.__name__} {0.country}, {0.releasedate}>"\
+ .format(self).encode('utf-8')
+
+class Trailer( Element ):
+ name = Datapoint('name')
+ size = Datapoint('size')
+ source = Datapoint('source')
+
+class YoutubeTrailer( Trailer ):
+ def geturl(self):
+ return "http://www.youtube.com/watch?v={0}".format(self.source)
+
+ def __repr__(self):
+ # modified BASE64 encoding, no need to worry about unicode
+ return u"<{0.__class__.__name__} '{0.name}'>".format(self)
+
+class AppleTrailer( Element ):
+ name = Datapoint('name')
+ sources = Datadict('sources', handler=Trailer, attr='size')
+
+ def sizes(self):
+ return self.sources.keys()
+
+ def geturl(self, size=None):
+ if size is None:
+ # sort assuming ###p format for now, take largest resolution
+ size = str(sorted([int(size[:-1]) for size in self.sources])[-1])+'p'
+ return self.sources[size].source
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}'>".format(self)
+
+class Translation( Element ):
+ name = Datapoint('name')
+ language = Datapoint('iso_639_1')
+ englishname = Datapoint('english_name')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}' ({0.language})>"\
+ .format(self).encode('utf-8')
+
+class Genre( NameRepr, Element ):
+ id = Datapoint('id')
+ name = Datapoint('name')
+
+ def _populate_movies(self):
+ return Request('genre/{0}/movies'.format(self.id), \
+ language=self._locale.language)
+
+ @property
+ def movies(self):
+ if 'movies' not in self._data:
+ search = MovieSearchResult(self._populate_movies(), \
+ locale=self._locale)
+ search._name = "{0.name} Movies".format(self)
+ self._data['movies'] = search
+ return self._data['movies']
+
+ @classmethod
+ def getAll(cls, locale=None):
+ class GenreList( Element ):
+ genres = Datalist('genres', handler=Genre)
+ def _populate(self):
+ return Request('genre/list', language=self._locale.language)
+ return GenreList(locale=locale).genres
+
+
+class Studio( NameRepr, Element ):
+ id = Datapoint('id', initarg=1)
+ name = Datapoint('name')
+ description = Datapoint('description')
+ headquarters = Datapoint('headquarters')
+ logo = Datapoint('logo_path', handler=Logo, \
+ raw=False, default=None)
+ # FIXME: manage not-yet-defined handlers in a way that will propogate
+ # locale information properly
+ parent = Datapoint('parent_company', \
+ handler=lambda x: Studio(raw=x))
+
+ def _populate(self):
+ return Request('company/{0}'.format(self.id))
+ def _populate_movies(self):
+ return Request('company/{0}/movies'.format(self.id), \
+ language=self._locale.language)
+
+ # FIXME: add a cleaner way of adding types with no additional processing
+ @property
+ def movies(self):
+ if 'movies' not in self._data:
+ search = MovieSearchResult(self._populate_movies(), \
+ locale=self._locale)
+ search._name = "{0.name} Movies".format(self)
+ self._data['movies'] = search
+ return self._data['movies']
+
+class Country( NameRepr, Element ):
+ code = Datapoint('iso_3166_1')
+ name = Datapoint('name')
+
+class Language( NameRepr, Element ):
+ code = Datapoint('iso_639_1')
+ name = Datapoint('name')
+
+class Movie( Element ):
+ @classmethod
+ def latest(cls):
+ req = Request('latest/movie')
+ req.lifetime = 600
+ return cls(raw=req.readJSON())
+
+ @classmethod
+ def nowplaying(cls, locale=None):
+ res = MovieSearchResult(Request('movie/now-playing'), locale=locale)
+ res._name = 'Now Playing'
+ return res
+
+ @classmethod
+ def mostpopular(cls, locale=None):
+ res = MovieSearchResult(Request('movie/popular'), locale=locale)
+ res._name = 'Popular'
+ return res
+
+ @classmethod
+ def toprated(cls, locale=None):
+ res = MovieSearchResult(Request('movie/top_rated'), locale=locale)
+ res._name = 'Top Rated'
+ return res
+
+ @classmethod
+ def upcoming(cls, locale=None):
+ res = MovieSearchResult(Request('movie/upcoming'), locale=locale)
+ res._name = 'Upcoming'
+ return res
+
+ @classmethod
+ def favorites(cls, session=None):
+ if session is None:
+ session = get_session()
+ account = Account(session=session)
+ res = MovieSearchResult(
+ Request('account/{0}/favorite_movies'.format(account.id),
+ session_id=session.sessionid))
+ res._name = "Favorites"
+ return res
+
+ @classmethod
+ def ratedmovies(cls, session=None):
+ if session is None:
+ session = get_session()
+ account = Account(session=session)
+ res = MovieSearchResult(
+ Request('account/{0}/rated_movies'.format(account.id),
+ session_id=session.sessionid))
+ res._name = "Movies You Rated"
+ return res
+
+ @classmethod
+ def watchlist(cls, session=None):
+ if session is None:
+ session = get_session()
+ account = Account(session=session)
+ res = MovieSearchResult(
+ Request('account/{0}/movie_watchlist'.format(account.id),
+ session_id=session.sessionid))
+ res._name = "Movies You're Watching"
+ return res
+
+ @classmethod
+ def fromIMDB(cls, imdbid, locale=None):
+ try:
+ # assume string
+ if not imdbid.startswith('tt'):
+ imdbid = "tt{0:0>7}".format(imdbid)
+ except AttributeError:
+ # assume integer
+ imdbid = "tt{0:0>7}".format(imdbid)
+ if locale is None:
+ locale = get_locale()
+ movie = cls(imdbid, locale=locale)
+ movie._populate()
+ return movie
+
+ id = Datapoint('id', initarg=1)
+ title = Datapoint('title')
+ originaltitle = Datapoint('original_title')
+ tagline = Datapoint('tagline')
+ overview = Datapoint('overview')
+ runtime = Datapoint('runtime')
+ budget = Datapoint('budget')
+ revenue = Datapoint('revenue')
+ releasedate = Datapoint('release_date', handler=process_date)
+ homepage = Datapoint('homepage')
+ imdb = Datapoint('imdb_id')
+
+ backdrop = Datapoint('backdrop_path', handler=Backdrop, \
+ raw=False, default=None)
+ poster = Datapoint('poster_path', handler=Poster, \
+ raw=False, default=None)
+
+ popularity = Datapoint('popularity')
+ userrating = Datapoint('vote_average')
+ votes = Datapoint('vote_count')
+
+ adult = Datapoint('adult')
+ collection = Datapoint('belongs_to_collection', handler=lambda x: \
+ Collection(raw=x))
+ genres = Datalist('genres', handler=Genre)
+ studios = Datalist('production_companies', handler=Studio)
+ countries = Datalist('production_countries', handler=Country)
+ languages = Datalist('spoken_languages', handler=Language)
+
+ def _populate(self):
+ return Request('movie/{0}'.format(self.id), \
+ language=self._locale.language)
+ def _populate_titles(self):
+ kwargs = {}
+ if not self._locale.fallthrough:
+ kwargs['country'] = self._locale.country
+ return Request('movie/{0}/alternative_titles'.format(self.id), **kwargs)
+ def _populate_cast(self):
+ return Request('movie/{0}/casts'.format(self.id))
+ def _populate_images(self):
+ kwargs = {}
+ if not self._locale.fallthrough:
+ kwargs['language'] = self._locale.language
+ return Request('movie/{0}/images'.format(self.id), **kwargs)
+ def _populate_keywords(self):
+ return Request('movie/{0}/keywords'.format(self.id))
+ def _populate_releases(self):
+ return Request('movie/{0}/releases'.format(self.id))
+ def _populate_trailers(self):
+ return Request('movie/{0}/trailers'.format(self.id), \
+ language=self._locale.language)
+ def _populate_translations(self):
+ return Request('movie/{0}/translations'.format(self.id))
+
+ alternate_titles = Datalist('titles', handler=AlternateTitle, \
+ poller=_populate_titles, sort=True)
+ cast = Datalist('cast', handler=Cast, \
+ poller=_populate_cast, sort='order')
+ crew = Datalist('crew', handler=Crew, poller=_populate_cast)
+ backdrops = Datalist('backdrops', handler=Backdrop, \
+ poller=_populate_images, sort=True)
+ posters = Datalist('posters', handler=Poster, \
+ poller=_populate_images, sort=True)
+ keywords = Datalist('keywords', handler=Keyword, \
+ poller=_populate_keywords)
+ releases = Datadict('countries', handler=Release, \
+ poller=_populate_releases, attr='country')
+ youtube_trailers = Datalist('youtube', handler=YoutubeTrailer, \
+ poller=_populate_trailers)
+ apple_trailers = Datalist('quicktime', handler=AppleTrailer, \
+ poller=_populate_trailers)
+ translations = Datalist('translations', handler=Translation, \
+ poller=_populate_translations)
+
+ def setFavorite(self, value):
+ req = Request('account/{0}/favorite'.format(\
+ Account(session=self._session).id),
+ session_id=self._session.sessionid)
+ req.add_data({'movie_id':self.id, 'favorite':str(bool(value)).lower()})
+ req.lifetime = 0
+ req.readJSON()
+
+ def setRating(self, value):
+ if not (0 <= value <= 10):
+ raise TMDBError("Ratings must be between '0' and '10'.")
+ req = Request('movie/{0}/rating'.format(self.id), \
+ session_id=self._session.sessionid)
+ req.lifetime = 0
+ req.add_data({'value':value})
+ req.readJSON()
+
+ def setWatchlist(self, value):
+ req = Request('account/{0}/movie_watchlist'.format(\
+ Account(session=self._session).id),
+ session_id=self._session.sessionid)
+ req.lifetime = 0
+ req.add_data({'movie_id':self.id,
+ 'movie_watchlist':str(bool(value)).lower()})
+ req.readJSON()
+
+ def getSimilar(self):
+ return self.similar
+
+ @property
+ def similar(self):
+ res = MovieSearchResult(Request('movie/{0}/similar_movies'\
+ .format(self.id)),
+ locale=self._locale)
+ res._name = 'Similar to {0}'.format(self._printable_name())
+ return res
+
+ @property
+ def lists(self):
+ res = ListSearchResult(Request('movie/{0}/lists'.format(self.id)))
+ res._name = "Lists containing {0}".format(self._printable_name())
+ return res
+
+ def _printable_name(self):
+ if self.title is not None:
+ s = u"'{0}'".format(self.title)
+ elif self.originaltitle is not None:
+ s = u"'{0}'".format(self.originaltitle)
+ else:
+ s = u"'No Title'"
+ if self.releasedate:
+ s = u"{0} ({1})".format(s, self.releasedate.year)
+ return s
+
+ def __repr__(self):
+ return u"<{0} {1}>".format(self.__class__.__name__,\
+ self._printable_name()).encode('utf-8')
+
+class ReverseCast( Movie ):
+ character = Datapoint('character')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.character}' on {1}>"\
+ .format(self, self._printable_name()).encode('utf-8')
+
+class ReverseCrew( Movie ):
+ department = Datapoint('department')
+ job = Datapoint('job')
+
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.job}' for {1}>"\
+ .format(self, self._printable_name()).encode('utf-8')
+
+class Collection( NameRepr, Element ):
+ id = Datapoint('id', initarg=1)
+ name = Datapoint('name')
+ backdrop = Datapoint('backdrop_path', handler=Backdrop, \
+ raw=False, default=None)
+ poster = Datapoint('poster_path', handler=Poster, \
+ raw=False, default=None)
+ members = Datalist('parts', handler=Movie)
+ overview = Datapoint('overview')
+
+ def _populate(self):
+ return Request('collection/{0}'.format(self.id), \
+ language=self._locale.language)
+ def _populate_images(self):
+ kwargs = {}
+ if not self._locale.fallthrough:
+ kwargs['language'] = self._locale.language
+ return Request('collection/{0}/images'.format(self.id), **kwargs)
+
+ backdrops = Datalist('backdrops', handler=Backdrop, \
+ poller=_populate_images, sort=True)
+ posters = Datalist('posters', handler=Poster, \
+ poller=_populate_images, sort=True)
+
+class List( NameRepr, Element ):
+ id = Datapoint('id', initarg=1)
+ name = Datapoint('name')
+ author = Datapoint('created_by')
+ description = Datapoint('description')
+ favorites = Datapoint('favorite_count')
+ language = Datapoint('iso_639_1')
+ count = Datapoint('item_count')
+ poster = Datapoint('poster_path', handler=Poster, \
+ raw=False, default=None)
+
+ members = Datalist('items', handler=Movie)
+
+ def _populate(self):
+ return Request('list/{0}'.format(self.id))
+
diff --git a/libs/tmdb3/tmdb_auth.py b/libs/tmdb3/tmdb_auth.py
new file mode 100755
index 00000000..8583b990
--- /dev/null
+++ b/libs/tmdb3/tmdb_auth.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: tmdb_auth.py
+# Python Library
+# Author: Raymond Wagner
+# Purpose: Provide authentication and session services for
+# calls against the TMDB v3 API
+#-----------------------
+
+from datetime import datetime as _pydatetime, \
+ tzinfo as _pytzinfo
+import re
+class datetime( _pydatetime ):
+ """Customized datetime class with ISO format parsing."""
+ _reiso = re.compile('(?P[0-9]{4})'
+ '-(?P[0-9]{1,2})'
+ '-(?P[0-9]{1,2})'
+ '.'
+ '(?P[0-9]{2})'
+ ':(?P[0-9]{2})'
+ '(:(?P[0-9]{2}))?'
+ '(?PZ|'
+ '(?P[-+])'
+ '(?P[0-9]{1,2})'
+ '(:)?'
+ '(?P[0-9]{2})?'
+ ')?')
+
+ class _tzinfo( _pytzinfo):
+ def __init__(self, direc='+', hr=0, min=0):
+ if direc == '-':
+ hr = -1*int(hr)
+ self._offset = timedelta(hours=int(hr), minutes=int(min))
+ def utcoffset(self, dt): return self._offset
+ def tzname(self, dt): return ''
+ def dst(self, dt): return timedelta(0)
+
+ @classmethod
+ def fromIso(cls, isotime, sep='T'):
+ match = cls._reiso.match(isotime)
+ if match is None:
+ raise TypeError("time data '%s' does not match ISO 8601 format" \
+ % isotime)
+
+ dt = [int(a) for a in match.groups()[:5]]
+ if match.group('sec') is not None:
+ dt.append(int(match.group('sec')))
+ else:
+ dt.append(0)
+ if match.group('tz'):
+ if match.group('tz') == 'Z':
+ tz = cls._tzinfo()
+ elif match.group('tzmin'):
+ tz = cls._tzinfo(*match.group('tzdirec','tzhour','tzmin'))
+ else:
+ tz = cls._tzinfo(*match.group('tzdirec','tzhour'))
+ dt.append(0)
+ dt.append(tz)
+ return cls(*dt)
+
+from request import Request
+from tmdb_exceptions import *
+
+syssession = None
+
+def set_session(sessionid):
+ global syssession
+ syssession = Session(sessionid)
+
+def get_session(sessionid=None):
+ global syssession
+ if sessionid:
+ return Session(sessionid)
+ elif syssession is not None:
+ return syssession
+ else:
+ return Session.new()
+
+class Session( object ):
+
+ @classmethod
+ def new(cls):
+ return cls(None)
+
+ def __init__(self, sessionid):
+ self.sessionid = sessionid
+
+ @property
+ def sessionid(self):
+ if self._sessionid is None:
+ if self._authtoken is None:
+ raise TMDBError("No Auth Token to produce Session for")
+ # TODO: check authtokenexpiration against current time
+ req = Request('authentication/session/new', \
+ request_token=self._authtoken)
+ req.lifetime = 0
+ dat = req.readJSON()
+ if not dat['success']:
+ raise TMDBError("Session generation failed")
+ self._sessionid = dat['session_id']
+ return self._sessionid
+
+ @sessionid.setter
+ def sessionid(self, value):
+ self._sessionid = value
+ self._authtoken = None
+ self._authtokenexpiration = None
+ if value is None:
+ self.authenticated = False
+ else:
+ self.authenticated = True
+
+ @property
+ def authtoken(self):
+ if self.authenticated:
+ raise TMDBError("Session is already authenticated")
+ if self._authtoken is None:
+ req = Request('authentication/token/new')
+ req.lifetime = 0
+ dat = req.readJSON()
+ if not dat['success']:
+ raise TMDBError("Auth Token request failed")
+ self._authtoken = dat['request_token']
+ self._authtokenexpiration = datetime.fromIso(dat['expires_at'])
+ return self._authtoken
+
+ @property
+ def callbackurl(self):
+ return "http://www.themoviedb.org/authenticate/"+self._authtoken
+
diff --git a/libs/tmdb3/tmdb_exceptions.py b/libs/tmdb3/tmdb_exceptions.py
new file mode 100755
index 00000000..35e0364b
--- /dev/null
+++ b/libs/tmdb3/tmdb_exceptions.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: tmdb_exceptions.py Common exceptions used in tmdbv3 API library
+# Python Library
+# Author: Raymond Wagner
+#-----------------------
+
+class TMDBError( Exception ):
+ Error = 0
+ KeyError = 10
+ KeyMissing = 20
+ KeyInvalid = 30
+ KeyRevoked = 40
+ RequestError = 50
+ RequestInvalid = 51
+ PagingIssue = 60
+ CacheError = 70
+ CacheReadError = 71
+ CacheWriteError = 72
+ CacheDirectoryError = 73
+ ImageSizeError = 80
+ HTTPError = 90
+ Offline = 100
+ LocaleError = 110
+
+ def __init__(self, msg=None, errno=0):
+ self.errno = errno
+ if errno == 0:
+ self.errno = getattr(self, 'TMDB'+self.__class__.__name__, errno)
+ self.args = (msg,)
+
+class TMDBKeyError( TMDBError ):
+ pass
+
+class TMDBKeyMissing( TMDBKeyError ):
+ pass
+
+class TMDBKeyInvalid( TMDBKeyError ):
+ pass
+
+class TMDBKeyRevoked( TMDBKeyInvalid ):
+ pass
+
+class TMDBRequestError( TMDBError ):
+ pass
+
+class TMDBRequestInvalid( TMDBRequestError ):
+ pass
+
+class TMDBPagingIssue( TMDBRequestError ):
+ pass
+
+class TMDBCacheError( TMDBRequestError ):
+ pass
+
+class TMDBCacheReadError( TMDBCacheError ):
+ def __init__(self, filename):
+ super(TMDBCacheReadError, self).__init__(
+ "User does not have permission to access cache file: {0}.".format(filename))
+ self.filename = filename
+
+class TMDBCacheWriteError( TMDBCacheError ):
+ def __init__(self, filename):
+ super(TMDBCacheWriteError, self).__init__(
+ "User does not have permission to write cache file: {0}.".format(filename))
+ self.filename = filename
+
+class TMDBCacheDirectoryError( TMDBCacheError ):
+ def __init__(self, filename):
+ super(TMDBCacheDirectoryError, self).__init__(
+ "Directory containing cache file does not exist: {0}.".format(filename))
+ self.filename = filename
+
+class TMDBImageSizeError( TMDBError ):
+ pass
+
+class TMDBHTTPError( TMDBError ):
+ def __init__(self, err):
+ self.httperrno = err.code
+ self.response = err.fp.read()
+ super(TMDBHTTPError, self).__init__(str(err))
+
+class TMDBOffline( TMDBError ):
+ pass
+
+class TMDBLocaleError( TMDBError ):
+ pass
+
diff --git a/libs/tmdb3/util.py b/libs/tmdb3/util.py
new file mode 100755
index 00000000..bba9fcc7
--- /dev/null
+++ b/libs/tmdb3/util.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-----------------------
+# Name: util.py Assorted utilities used in tmdb_api
+# Python Library
+# Author: Raymond Wagner
+#-----------------------
+
+from copy import copy
+from locales import get_locale
+from tmdb_auth import get_session
+
+class NameRepr( object ):
+ """Mixin for __repr__ methods using 'name' attribute."""
+ def __repr__(self):
+ return u"<{0.__class__.__name__} '{0.name}'>"\
+ .format(self).encode('utf-8')
+
+class SearchRepr( object ):
+ """
+ Mixin for __repr__ methods for classes with '_name' and
+ '_request' attributes.
+ """
+ def __repr__(self):
+ name = self._name if self._name else self._request._kwargs['query']
+ return u"".format(name).encode('utf-8')
+
+class Poller( object ):
+ """
+ Wrapper for an optional callable to populate an Element derived class
+ with raw data, or data from a Request.
+ """
+ def __init__(self, func, lookup, inst=None):
+ self.func = func
+ self.lookup = lookup
+ self.inst = inst
+ if func:
+ # with function, this allows polling data from the API
+ self.__doc__ = func.__doc__
+ self.__name__ = func.__name__
+ self.__module__ = func.__module__
+ else:
+ # without function, this is just a dummy poller used for applying
+ # raw data to a new Element class with the lookup table
+ self.__name__ = '_populate'
+
+ def __get__(self, inst, owner):
+ # normal decorator stuff
+ # return self for a class
+ # return instantiated copy of self for an object
+ if inst is None:
+ return self
+ func = None
+ if self.func:
+ func = self.func.__get__(inst, owner)
+ return self.__class__(func, self.lookup, inst)
+
+ def __call__(self):
+ # retrieve data from callable function, and apply
+ if not callable(self.func):
+ raise RuntimeError('Poller object called without a source function')
+ req = self.func()
+ if (('language' in req._kwargs) or ('country' in req._kwargs)) \
+ and self.inst._locale.fallthrough:
+ # request specifies a locale filter, and fallthrough is enabled
+ # run a first pass with specified filter
+ if not self.apply(req.readJSON(), False):
+ return
+ # if first pass results in missed data, run a second pass to
+ # fill in the gaps
+ self.apply(req.new(language=None, country=None).readJSON())
+ # re-apply the filtered first pass data over top the second
+ # unfiltered set. this is to work around the issue that the
+ # properties have no way of knowing when they should or
+ # should not overwrite existing data. the cache engine will
+ # take care of the duplicate query
+ self.apply(req.readJSON())
+
+ def apply(self, data, set_nones=True):
+ # apply data directly, bypassing callable function
+ unfilled = False
+ for k,v in self.lookup.items():
+ if (k in data) and \
+ ((data[k] is not None) if callable(self.func) else True):
+ # argument received data, populate it
+ setattr(self.inst, v, data[k])
+ elif v in self.inst._data:
+ # argument did not receive data, but Element already contains
+ # some value, so skip this
+ continue
+ elif set_nones:
+ # argument did not receive data, so fill it with None
+ # to indicate such and prevent a repeat scan
+ setattr(self.inst, v, None)
+ else:
+ # argument does not need data, so ignore it allowing it to
+ # trigger a later poll. this is intended for use when
+ # initializing a class with raw data, or when performing a
+ # first pass through when performing locale fall through
+ unfilled = True
+ return unfilled
+
+class Data( object ):
+ """
+ Basic response definition class
+ This maps to a single key in a JSON dictionary received from the API
+ """
+ def __init__(self, field, initarg=None, handler=None, poller=None,
+ raw=True, default=u'', lang=False):
+ """
+ This defines how the dictionary value is to be processed by the poller
+ field -- defines the dictionary key that filters what data this uses
+ initarg -- (optional) specifies that this field must be supplied
+ when creating a new instance of the Element class this
+ definition is mapped to. Takes an integer for the order
+ it should be used in the input arguments
+ handler -- (optional) callable used to process the received value
+ before being stored in the Element object.
+ poller -- (optional) callable to be used if data is requested and
+ this value has not yet been defined. the callable should
+ return a dictionary of data from a JSON query. many
+ definitions may share a single poller, which will be
+ and the data used to populate all referenced definitions
+ based off their defined field
+ raw -- (optional) if the specified handler is an Element class,
+ the data will be passed into it using the 'raw' keyword
+ attribute. setting this to false will force the data to
+ instead be passed in as the first argument
+ """
+ self.field = field
+ self.initarg = initarg
+ self.poller = poller
+ self.raw = raw
+ self.default = default
+ self.sethandler(handler)
+
+ def __get__(self, inst, owner):
+ if inst is None:
+ return self
+ if self.field not in inst._data:
+ if self.poller is None:
+ return None
+ self.poller.__get__(inst, owner)()
+ return inst._data[self.field]
+
+ def __set__(self, inst, value):
+ if (value is not None) and (value != ''):
+ value = self.handler(value)
+ else:
+ value = self.default
+ if isinstance(value, Element):
+ value._locale = inst._locale
+ value._session = inst._session
+ inst._data[self.field] = value
+
+ def sethandler(self, handler):
+ # ensure handler is always callable, even for passthrough data
+ if handler is None:
+ self.handler = lambda x: x
+ elif isinstance(handler, ElementType) and self.raw:
+ self.handler = lambda x: handler(raw=x)
+ else:
+ self.handler = lambda x: handler(x)
+
+class Datapoint( Data ):
+ pass
+
+class Datalist( Data ):
+ """
+ Response definition class for list data
+ This maps to a key in a JSON dictionary storing a list of data
+ """
+ def __init__(self, field, handler=None, poller=None, sort=None, raw=True):
+ """
+ This defines how the dictionary value is to be processed by the poller
+ field -- defines the dictionary key that filters what data this uses
+ handler -- (optional) callable used to process the received value
+ before being stored in the Element object.
+ poller -- (optional) callable to be used if data is requested and
+ this value has not yet been defined. the callable should
+ return a dictionary of data from a JSON query. many
+ definitions may share a single poller, which will be
+ and the data used to populate all referenced definitions
+ based off their defined field
+ sort -- (optional) name of attribute in resultant data to be used
+ to sort the list after processing. this effectively
+ a handler be defined to process the data into something
+ that has attributes
+ raw -- (optional) if the specified handler is an Element class,
+ the data will be passed into it using the 'raw' keyword
+ attribute. setting this to false will force the data to
+ instead be passed in as the first argument
+ """
+ super(Datalist, self).__init__(field, None, handler, poller, raw)
+ self.sort = sort
+ def __set__(self, inst, value):
+ data = []
+ if value:
+ for val in value:
+ val = self.handler(val)
+ if isinstance(val, Element):
+ val._locale = inst._locale
+ val._session = inst._session
+ data.append(val)
+ if self.sort:
+ if self.sort is True:
+ data.sort()
+ else:
+ data.sort(key=lambda x: getattr(x, self.sort))
+ inst._data[self.field] = data
+
+class Datadict( Data ):
+ """
+ Response definition class for dictionary data
+ This maps to a key in a JSON dictionary storing a dictionary of data
+ """
+ def __init__(self, field, handler=None, poller=None, raw=True,
+ key=None, attr=None):
+ """
+ This defines how the dictionary value is to be processed by the poller
+ field -- defines the dictionary key that filters what data this uses
+ handler -- (optional) callable used to process the received value
+ before being stored in the Element object.
+ poller -- (optional) callable to be used if data is requested and
+ this value has not yet been defined. the callable should
+ return a dictionary of data from a JSON query. many
+ definitions may share a single poller, which will be
+ and the data used to populate all referenced definitions
+ based off their defined field
+ key -- (optional) name of key in resultant data to be used as
+ the key in the stored dictionary. if this is not the
+ field name from the source data is used instead
+ attr -- (optional) name of attribute in resultant data to be used
+ as the key in the stored dictionary. if this is not
+ the field name from the source data is used instead
+ raw -- (optional) if the specified handler is an Element class,
+ the data will be passed into it using the 'raw' keyword
+ attribute. setting this to false will force the data to
+ instead be passed in as the first argument
+ """
+ if key and attr:
+ raise TypeError("`key` and `attr` cannot both be defined")
+ super(Datadict, self).__init__(field, None, handler, poller, raw)
+ if key:
+ self.getkey = lambda x: x[key]
+ elif attr:
+ self.getkey = lambda x: getattr(x, attr)
+ else:
+ raise TypeError("Datadict requires `key` or `attr` be defined "+\
+ "for populating the dictionary")
+ def __set__(self, inst, value):
+ data = {}
+ if value:
+ for val in value:
+ val = self.handler(val)
+ if isinstance(val, Element):
+ val._locale = inst._locale
+ val._session = inst._session
+ data[self.getkey(val)] = val
+ inst._data[self.field] = data
+
+class ElementType( type ):
+ """
+ MetaClass used to pre-process Element-derived classes and set up the
+ Data definitions
+ """
+ def __new__(mcs, name, bases, attrs):
+ # any Data or Poller object defined in parent classes must be cloned
+ # and processed in this class to function properly
+ # scan through available bases for all such definitions and insert
+ # a copy into this class's attributes
+ # run in reverse order so higher priority values overwrite lower ones
+ data = {}
+ pollers = {'_populate':None}
+
+ for base in reversed(bases):
+ if isinstance(base, mcs):
+ for k, attr in base.__dict__.items():
+ if isinstance(attr, Data):
+ # extract copies of each defined Data element from
+ # parent classes
+ attr = copy(attr)
+ attr.poller = attr.poller.func
+ data[k] = attr
+ elif isinstance(attr, Poller):
+ # extract copies of each defined Poller function
+ # from parent classes
+ pollers[k] = attr.func
+ for k,attr in attrs.items():
+ if isinstance(attr, Data):
+ data[k] = attr
+ if '_populate' in attrs:
+ pollers['_populate'] = attrs['_populate']
+
+ # process all defined Data attribues, testing for use as an initial
+ # argument, and building a list of what Pollers are used to populate
+ # which Data points
+ pollermap = dict([(k,[]) for k in pollers])
+ initargs = []
+ for k,v in data.items():
+ v.name = k
+ if v.initarg:
+ initargs.append(v)
+ if v.poller:
+ pn = v.poller.__name__
+ if pn not in pollermap:
+ pollermap[pn] = []
+ if pn not in pollers:
+ pollers[pn] = v.poller
+ pollermap[pn].append(v)
+ else:
+ pollermap['_populate'].append(v)
+
+ # wrap each used poller function with a Poller class, and push into
+ # the new class attributes
+ for k,v in pollermap.items():
+ if len(v) == 0:
+ continue
+ lookup = dict([(attr.field, attr.name) for attr in v])
+ poller = Poller(pollers[k], lookup)
+ attrs[k] = poller
+ # backfill wrapped Poller into each mapped Data object, and ensure
+ # the data elements are defined for this new class
+ for attr in v:
+ attr.poller = poller
+ attrs[attr.name] = attr
+
+ # build sorted list of arguments used for intialization
+ attrs['_InitArgs'] = tuple([a.name for a in \
+ sorted(initargs, key=lambda x: x.initarg)])
+ return type.__new__(mcs, name, bases, attrs)
+
+ def __call__(cls, *args, **kwargs):
+ obj = cls.__new__(cls)
+ if ('locale' in kwargs) and (kwargs['locale'] is not None):
+ obj._locale = kwargs['locale']
+ else:
+ obj._locale = get_locale()
+
+ if 'session' in kwargs:
+ obj._session = kwargs['session']
+ else:
+ obj._session = get_session()
+
+ obj._data = {}
+ if 'raw' in kwargs:
+ # if 'raw' keyword is supplied, create populate object manually
+ if len(args) != 0:
+ raise TypeError('__init__() takes exactly 2 arguments (1 given)')
+ obj._populate.apply(kwargs['raw'], False)
+ else:
+ # if not, the number of input arguments must exactly match that
+ # defined by the Data definitions
+ if len(args) != len(cls._InitArgs):
+ raise TypeError('__init__() takes exactly {0} arguments ({1} given)'\
+ .format(len(cls._InitArgs)+1, len(args)+1))
+ for a,v in zip(cls._InitArgs, args):
+ setattr(obj, a, v)
+
+ obj.__init__()
+ return obj
+
+class Element( object ):
+ __metaclass__ = ElementType
+ _lang = 'en'
+
diff --git a/libs/tornado/__init__.py b/libs/tornado/__init__.py
index 4a264c35..41ba7a68 100755
--- a/libs/tornado/__init__.py
+++ b/libs/tornado/__init__.py
@@ -25,5 +25,5 @@ from __future__ import absolute_import, division, print_function, with_statement
# is zero for an official release, positive for a development branch,
# or negative for a release candidate or beta (after the base version
# number has been incremented)
-version = "3.1b1"
-version_info = (3, 1, 0, -98)
+version = "3.2.dev2"
+version_info = (3, 2, 0, -99)
diff --git a/libs/tornado/auth.py b/libs/tornado/auth.py
index 42400e19..0cbfa7c0 100755
--- a/libs/tornado/auth.py
+++ b/libs/tornado/auth.py
@@ -56,7 +56,7 @@ import hmac
import time
import uuid
-from tornado.concurrent import Future, chain_future, return_future
+from tornado.concurrent import TracebackFuture, chain_future, return_future
from tornado import gen
from tornado import httpclient
from tornado import escape
@@ -99,7 +99,7 @@ def _auth_return_future(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
- future = Future()
+ future = TracebackFuture()
callback, args, kwargs = replacer.replace(future, args, kwargs)
if callback is not None:
future.add_done_callback(
@@ -306,10 +306,10 @@ class OAuthMixin(object):
"""Redirects the user to obtain OAuth authorization for this service.
The ``callback_uri`` may be omitted if you have previously
- registered a callback URI with the third-party service. For some
- sevices (including Twitter and Friendfeed), you must use a
- previously-registered callback URI and cannot specify a callback
- via this method.
+ registered a callback URI with the third-party service. For
+ some sevices (including Friendfeed), you must use a
+ previously-registered callback URI and cannot specify a
+ callback via this method.
This method sets a cookie called ``_oauth_request_token`` which is
subsequently used (and cleared) in `get_authenticated_user` for
@@ -1158,7 +1158,7 @@ class FacebookMixin(object):
class FacebookGraphMixin(OAuth2Mixin):
"""Facebook authentication using the new Graph API and OAuth2."""
_OAUTH_ACCESS_TOKEN_URL = "https://graph.facebook.com/oauth/access_token?"
- _OAUTH_AUTHORIZE_URL = "https://graph.facebook.com/oauth/authorize?"
+ _OAUTH_AUTHORIZE_URL = "https://www.facebook.com/dialog/oauth?"
_OAUTH_NO_CALLBACKS = False
_FACEBOOK_BASE_URL = "https://graph.facebook.com"
diff --git a/libs/tornado/ca-certificates.crt b/libs/tornado/ca-certificates.crt
index 26971c8b..a1ede895 100755
--- a/libs/tornado/ca-certificates.crt
+++ b/libs/tornado/ca-certificates.crt
@@ -1,3576 +1,3562 @@
# This file contains certificates of known certificate authorities
# for use with SimpleAsyncHTTPClient.
#
-# It was copied from /etc/ssl/certs/ca-certificates.crt
-# on a stock install of Ubuntu 11.04 (ca-certificates package
-# version 20090814+nmu2ubuntu0.1). This data file is licensed
-# under the MPL/GPL.
+# It was extracted from the Mozilla source tree using libcurl's mk-ca-bundle
+# script on Aug 13, 2013.
+#
+# This data file is licenced under the MPL/GPL.
+
+##
+## ca-bundle.crt -- Bundle of CA Root Certificates
+##
+## Certificate data from Mozilla as of: Tue Aug 13 03:28:51 2013
+##
+## This is a bundle of X.509 certificates of public Certificate Authorities
+## (CA). These were automatically extracted from Mozilla's root certificates
+## file (certdata.txt). This file can be found in the mozilla source tree:
+## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
+##
+## It contains the certificates in PEM format and therefore
+## can be directly used with curl / libcurl / php_curl, or with
+## an Apache+mod_ssl webserver for SSL client authentication.
+## Just configure this file as the SSLCACertificateFile.
+##
+
+# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $
+
+GTE CyberTrust Global Root
+==========================
-----BEGIN CERTIFICATE-----
-MIIEuDCCA6CgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBtDELMAkGA1UEBhMCQlIx
-EzARBgNVBAoTCklDUC1CcmFzaWwxPTA7BgNVBAsTNEluc3RpdHV0byBOYWNpb25h
-bCBkZSBUZWNub2xvZ2lhIGRhIEluZm9ybWFjYW8gLSBJVEkxETAPBgNVBAcTCEJy
-YXNpbGlhMQswCQYDVQQIEwJERjExMC8GA1UEAxMoQXV0b3JpZGFkZSBDZXJ0aWZp
-Y2Fkb3JhIFJhaXogQnJhc2lsZWlyYTAeFw0wMTExMzAxMjU4MDBaFw0xMTExMzAy
-MzU5MDBaMIG0MQswCQYDVQQGEwJCUjETMBEGA1UEChMKSUNQLUJyYXNpbDE9MDsG
-A1UECxM0SW5zdGl0dXRvIE5hY2lvbmFsIGRlIFRlY25vbG9naWEgZGEgSW5mb3Jt
-YWNhbyAtIElUSTERMA8GA1UEBxMIQnJhc2lsaWExCzAJBgNVBAgTAkRGMTEwLwYD
-VQQDEyhBdXRvcmlkYWRlIENlcnRpZmljYWRvcmEgUmFpeiBCcmFzaWxlaXJhMIIB
-IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPMudwX/hvm+Uh2b/lQAcHVA
-isamaLkWdkwP9/S/tOKIgRrL6Oy+ZIGlOUdd6uYtk9Ma/3pUpgcfNAj0vYm5gsyj
-Qo9emsc+x6m4VWwk9iqMZSCK5EQkAq/Ut4n7KuLE1+gdftwdIgxfUsPt4CyNrY50
-QV57KM2UT8x5rrmzEjr7TICGpSUAl2gVqe6xaii+bmYR1QrmWaBSAG59LrkrjrYt
-bRhFboUDe1DK+6T8s5L6k8c8okpbHpa9veMztDVC9sPJ60MWXh6anVKo1UcLcbUR
-yEeNvZneVRKAAU6ouwdjDvwlsaKydFKwed0ToQ47bmUKgcm+wV3eTRk36UOnTwID
-AQABo4HSMIHPME4GA1UdIARHMEUwQwYFYEwBAQAwOjA4BggrBgEFBQcCARYsaHR0
-cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0RQQ2FjcmFpei5wZGYwPQYDVR0f
-BDYwNDAyoDCgLoYsaHR0cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0xDUmFj
-cmFpei5jcmwwHQYDVR0OBBYEFIr68VeEERM1kEL6V0lUaQ2kxPA3MA8GA1UdEwEB
-/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAZA5c1
-U/hgIh6OcgLAfiJgFWpvmDZWqlV30/bHFpj8iBobJSm5uDpt7TirYh1Uxe3fQaGl
-YjJe+9zd+izPRbBqXPVQA34EXcwk4qpWuf1hHriWfdrx8AcqSqr6CuQFwSr75Fos
-SzlwDADa70mT7wZjAmQhnZx2xJ6wfWlT9VQfS//JYeIc7Fue2JNLd00UOSMMaiK/
-t79enKNHEA2fupH3vEigf5Eh4bVAN5VohrTm6MY53x7XQZZr1ME7a55lFEnSeT0u
-mlOAjR2mAbvSM5X5oSZNrmetdzyTj2flCM8CC7MLab0kkdngRIlUBGHF1/S5nmPb
-K+9A46sd33oqK8n8
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz
+MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0
+IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u
+sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql
+HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID
+AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW
+M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF
+NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----
+
+Thawte Server CA
+================
-----BEGIN CERTIFICATE-----
-MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
-IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
-IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
-Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
-BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
-MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
-ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
-CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
-8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
-zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
-fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
-w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
-G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
-epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
-laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
-QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
-fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
-YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
-ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
-gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
-MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
-IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
-dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
-czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
-dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
-aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
-AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
-b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
-ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
-nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
-18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
-gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
-Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
-sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
-SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
-CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
-GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
-zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
-omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE
+AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j
+b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV
+BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u
+c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG
+A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
+ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl
+/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7
+1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J
+GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ
+GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc=
-----END CERTIFICATE-----
+
+Thawte Premium Server CA
+========================
-----BEGIN CERTIFICATE-----
-MIIGCDCCA/CgAwIBAgIBATANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
-IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
-IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
-Y2FjZXJ0Lm9yZzAeFw0wNTEwMTQwNzM2NTVaFw0zMzAzMjgwNzM2NTVaMFQxFDAS
-BgNVBAoTC0NBY2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5v
-cmcxHDAaBgNVBAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCrSTURSHzSJn5TlM9Dqd0o10Iqi/OHeBlYfA+e2ol9
-4fvrcpANdKGWZKufoCSZc9riVXbHF3v1BKxGuMO+f2SNEGwk82GcwPKQ+lHm9WkB
-Y8MPVuJKQs/iRIwlKKjFeQl9RrmK8+nzNCkIReQcn8uUBByBqBSzmGXEQ+xOgo0J
-0b2qW42S0OzekMV/CsLj6+YxWl50PpczWejDAz1gM7/30W9HxM3uYoNSbi4ImqTZ
-FRiRpoWSR7CuSOtttyHshRpocjWr//AQXcD0lKdq1TuSfkyQBX6TwSyLpI5idBVx
-bgtxA+qvFTia1NIFcm+M+SvrWnIl+TlG43IbPgTDZCciECqKT1inA62+tC4T7V2q
-SNfVfdQqe1z6RgRQ5MwOQluM7dvyz/yWk+DbETZUYjQ4jwxgmzuXVjit89Jbi6Bb
-6k6WuHzX1aCGcEDTkSm3ojyt9Yy7zxqSiuQ0e8DYbF/pCsLDpyCaWt8sXVJcukfV
-m+8kKHA4IC/VfynAskEDaJLM4JzMl0tF7zoQCqtwOpiVcK01seqFK6QcgCExqa5g
-eoAmSAC4AcCTY1UikTxW56/bOiXzjzFU6iaLgVn5odFTEcV7nQP2dBHgbbEsPyyG
-kZlxmqZ3izRg0RS0LKydr4wQ05/EavhvE/xzWfdmQnQeiuP43NJvmJzLR5iVQAX7
-6QIDAQABo4G/MIG8MA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUHAQEEUTBPMCMG
-CCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggrBgEFBQcwAoYc
-aHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBBMD8GCCsGAQQB
-gZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9yZy9pbmRleC5w
-aHA/aWQ9MTAwDQYJKoZIhvcNAQEEBQADggIBAH8IiKHaGlBJ2on7oQhy84r3HsQ6
-tHlbIDCxRd7CXdNlafHCXVRUPIVfuXtCkcKZ/RtRm6tGpaEQU55tiKxzbiwzpvD0
-nuB1wT6IRanhZkP+VlrRekF490DaSjrxC1uluxYG5sLnk7mFTZdPsR44Q4Dvmw2M
-77inYACHV30eRBzLI++bPJmdr7UpHEV5FpZNJ23xHGzDwlVks7wU4vOkHx4y/CcV
-Bc/dLq4+gmF78CEQGPZE6lM5+dzQmiDgxrvgu1pPxJnIB721vaLbLmINQjRBvP+L
-ivVRIqqIMADisNS8vmW61QNXeZvo3MhN+FDtkaVSKKKs+zZYPumUK5FQhxvWXtaM
-zPcPEAxSTtAWYeXlCmy/F8dyRlecmPVsYGN6b165Ti/Iubm7aoW8mA3t+T6XhDSU
-rgCvoeXnkm5OvfPi2RSLXNLrAWygF6UtEOucekq9ve7O/e0iQKtwOIj1CodqwqsF
-YMlIBdpTwd5Ed2qz8zw87YC8pjhKKSRf/lk7myV6VmMAZLldpGJ9VzZPrYPvH5JT
-oI53V93lYRE9IwCQTDz6o2CTBKOvNfYOao9PSmCnhQVsRqGP9Md246FZV/dxssRu
-FFxtbUFm3xuTsdQAw+7Lzzw9IYCpX2Nl/N3gX6T0K/CFcUHUZyX7GrGXrtaZghNB
-0m6lG5kngOcLqagA
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
+AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
+ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
+AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
+VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
+aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
+cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
+aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
+Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
+qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
+SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
+8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
+UCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
+
+Equifax Secure CA
+=================
-----BEGIN CERTIFICATE-----
-MIIESzCCAzOgAwIBAgIJAJigUTEEXRQpMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNV
-BAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xDjAMBgNVBAcTBUZ1bGRhMRAwDgYDVQQK
-EwdEZWJjb25mMRMwEQYDVQQDEwpEZWJjb25mIENBMR8wHQYJKoZIhvcNAQkBFhBq
-b2VyZ0BkZWJpYW4ub3JnMB4XDTA1MTEwNTE3NTUxNFoXDTE1MTEwMzE3NTUxNFow
-djELMAkGA1UEBhMCREUxDzANBgNVBAgTBkhlc3NlbjEOMAwGA1UEBxMFRnVsZGEx
-EDAOBgNVBAoTB0RlYmNvbmYxEzARBgNVBAMTCkRlYmNvbmYgQ0ExHzAdBgkqhkiG
-9w0BCQEWEGpvZXJnQGRlYmlhbi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
-ggEKAoIBAQCvbOo0SrIwI5IMlsshH8WF3dHB9r9JlSKhMPaybawa1EyvZspMQ3wa
-F5qxNf3Sj+NElEmjseEqvCZiIIzqwerHu0Qw62cDYCdCd2+Wb5m0bPYB5CGHiyU1
-eNP0je42O0YeXG2BvUujN8AviocVo39X2YwNQ0ryy4OaqYgm2pRlbtT2ESbF+SfV
-Y2iqQj/f8ymF+lHo/pz8tbAqxWcqaSiHFAVQJrdqtFhtoodoNiE3q76zJoUkZTXB
-k60Yc3MJSnatZCpnsSBr/D7zpntl0THrUjjtdRWCjQVhqfhM1yZJV+ApbLdheFh0
-ZWlSxdnp25p0q0XYw/7G92ELyFDfBUUNAgMBAAGjgdswgdgwHQYDVR0OBBYEFMuV
-dFNb4mCWUFbcP5LOtxFLrEVTMIGoBgNVHSMEgaAwgZ2AFMuVdFNb4mCWUFbcP5LO
-txFLrEVToXqkeDB2MQswCQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMQ4wDAYD
-VQQHEwVGdWxkYTEQMA4GA1UEChMHRGViY29uZjETMBEGA1UEAxMKRGViY29uZiBD
-QTEfMB0GCSqGSIb3DQEJARYQam9lcmdAZGViaWFuLm9yZ4IJAJigUTEEXRQpMAwG
-A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGZXxHg4mnkvilRIM1EQfGdY
-S5b/WcyF2MYSTeTvK4aIB6VHwpZoZCnDGj2m2D3CkHT0upAD9o0zM1tdsfncLzV+
-mDT/jNmBtYo4QXx5vEPwvEIcgrWjwk7SyaEUhZjtolTkHB7ACl0oD0r71St4iEPR
-qTUCEXk2E47bg1Fz58wNt/yo2+4iqiRjg1XCH4evkQuhpW+dTZnDyFNqwSYZapOE
-TBA+9zBb6xD1KM2DdY7r4GiyYItN0BKLfuWbh9LXGbl1C+f4P11g+m2MPiavIeCe
-1iazG5pcS3KoTLACsYlEX24TINtg4kcuS81XdllcnsV3Kdts0nIqPj6uhTTZD0k=
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
+ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
+B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
+fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
+8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
+CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
+spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
+zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
+BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
+70+sB3c4
-----END CERTIFICATE-----
+
+Digital Signature Trust Co. Global CA 1
+=======================================
-----BEGIN CERTIFICATE-----
-MIIDvjCCA3ygAwIBAgIFJQaThoEwCwYHKoZIzjgEAwUAMIGFMQswCQYDVQQGEwJG
-UjEPMA0GA1UECBMGRnJhbmNlMQ4wDAYDVQQHEwVQYXJpczEQMA4GA1UEChMHUE0v
-U0dETjEOMAwGA1UECxMFRENTU0kxDjAMBgNVBAMTBUlHQy9BMSMwIQYJKoZIhvcN
-AQkBFhRpZ2NhQHNnZG4ucG0uZ291di5mcjAeFw0wMjEyMTMxNDM5MTVaFw0yMDEw
-MTcxNDM5MTRaMIGFMQswCQYDVQQGEwJGUjEPMA0GA1UECBMGRnJhbmNlMQ4wDAYD
-VQQHEwVQYXJpczEQMA4GA1UEChMHUE0vU0dETjEOMAwGA1UECxMFRENTU0kxDjAM
-BgNVBAMTBUlHQy9BMSMwIQYJKoZIhvcNAQkBFhRpZ2NhQHNnZG4ucG0uZ291di5m
-cjCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCFkMImdk9zDzJfTO4XPdAAmLbAdWws
-ZiEMZh19RyTo3CyhFqO77OIXrwY6vc1pcc3MgWJ0dgQpAgrDMtmFFxpUu4gmjVsx
-8GpxQC+4VOgLY8Cvmcd/UDzYg07EIRto8BwCpPJ/JfUxwzV2V3N713aAX+cEoKZ/
-s+kgxC6nZCA7oQIVALME/JYjkdW2uKIGngsEPbXAjdhDAoGADh/uqWJx94UBm31c
-9d8ZTBfRGRnmSSRVFDgPWgA69JD4BR5da8tKz+1HjfMhDXljbMH86ixpD5Ka1Z0V
-pRYUPbyAoB37tsmXMJY7kjyD19d5VdaZboUjVvhH6UJy5lpNNNGSvFl4fqkxyvw+
-pq1QV0N5RcvK120hlXdfHUX+YKYDgYQAAoGAQGr7IuKJcYIvJRMjxwl43KxXY2xC
-aoCiM/bv117MfI94aNf1UusGhp7CbYAY9CXuL60P0oPMAajbaTE5Z34AuITeHq3Y
-CNMHwxalip8BHqSSGmGiQsXeK7T+r1rPXsccZ1c5ikGDZ4xn5gUaCyy2rCmb+fOJ
-6VAfCbAbAjmNKwejdzB1MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgFGMBUG
-A1UdIAQOMAwwCgYIKoF6AXkBAQEwHQYDVR0OBBYEFPkeNRcUf8idzpKblYbLNxs0
-MQhSMB8GA1UdIwQYMBaAFPkeNRcUf8idzpKblYbLNxs0MQhSMAsGByqGSM44BAMF
-AAMvADAsAhRVh+CJA5eVyEYU5AO9Tm7GxX0rmQIUBCqsU5u1WxoZ5lEXicDX5/Ob
-sRQ=
+MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
+ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMTAeFw05ODEy
+MTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
+IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQCgbIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJE
+NySZj9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlVSn5JTe2i
+o74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
+BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
+dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
+IoAPMTk5ODEyMTAxODEwMjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQY
+MBaAFGp5fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i+DAM
+BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
+ACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lNQseSJqBcNJo4cvj9axY+IO6CizEq
+kzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4
+RbyhkwS7hp86W0N6w4pl
-----END CERTIFICATE-----
+
+Digital Signature Trust Co. Global CA 3
+=======================================
-----BEGIN CERTIFICATE-----
-MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
-AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
-TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
-9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
-MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
-BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
-MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
-LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
-s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
-xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
-u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
-F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
-Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
-PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
-HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
-NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
-AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
-L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
-YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
-Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
-NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
-0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
+MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
+ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMjAeFw05ODEy
+MDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
+IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQC/k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGOD
+VvsoLeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3oTQPMx7JS
+xhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
+BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
+dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
+IoAPMTk5ODEyMDkxOTE3MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQY
+MBaAFB6CTShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5WzAM
+BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
+AEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHRxdf0CiUPPXiBng+xZ8SQTGPdXqfi
+up/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVLB3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1
+mPnHfxsb1gYgAlihw6ID
-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
-----BEGIN CERTIFICATE-----
-MIIDtTCCAp2gAwIBAgIRANAeQJAAAEZSAAAAAQAAAAQwDQYJKoZIhvcNAQEFBQAw
-gYkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJEQzETMBEGA1UEBxMKV2FzaGluZ3Rv
-bjEXMBUGA1UEChMOQUJBLkVDT00sIElOQy4xGTAXBgNVBAMTEEFCQS5FQ09NIFJv
-b3QgQ0ExJDAiBgkqhkiG9w0BCQEWFWFkbWluQGRpZ3NpZ3RydXN0LmNvbTAeFw05
-OTA3MTIxNzMzNTNaFw0wOTA3MDkxNzMzNTNaMIGJMQswCQYDVQQGEwJVUzELMAkG
-A1UECBMCREMxEzARBgNVBAcTCldhc2hpbmd0b24xFzAVBgNVBAoTDkFCQS5FQ09N
-LCBJTkMuMRkwFwYDVQQDExBBQkEuRUNPTSBSb290IENBMSQwIgYJKoZIhvcNAQkB
-FhVhZG1pbkBkaWdzaWd0cnVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
-ggEKAoIBAQCx0xHgeVVDBwhMywVCAOINg0Y95JO6tgbTDVm9PsHOQ2cBiiGo77zM
-0KLMsFWWU4RmBQDaREmA2FQKpSWGlO1jVv9wbKOhGdJ4vmgqRF4vz8wYXke8OrFG
-PR7wuSw0X4x8TAgpnUBV6zx9g9618PeKgw6hTLQ6pbNfWiKX7BmbwQVo/ea3qZGU
-LOR4SCQaJRk665WcOQqKz0Ky8BzVX/tr7WhWezkscjiw7pOp03t3POtxA6k4ShZs
-iSrK2jMTecJVjO2cu/LLWxD4LmE1xilMKtAqY9FlWbT4zfn0AIS2V0KFnTKo+SpU
-+/94Qby9cSj0u5C8/5Y0BONFnqFGKECBAgMBAAGjFjAUMBIGA1UdEwEB/wQIMAYB
-Af8CAQgwDQYJKoZIhvcNAQEFBQADggEBAARvJYbk5pYntNlCwNDJALF/VD6Hsm0k
-qS8Kfv2kRLD4VAe9G52dyntQJHsRW0mjpr8SdNWJt7cvmGQlFLdh6X9ggGvTZOir
-vRrWUfrAtF13Gn9kCF55xgVM8XrdTX3O5kh7VNJhkoHWG9YA8A6eKHegTYjHInYZ
-w8eeG6Z3ePhfm1bR8PIXrI6dWeYf/le22V7hXZ9F7GFoGUHhsiAm/lowdiT/QHI8
-eZ98IkirRs3bs4Ysj78FQdPB4xTjQRcm0HyncUwZ6EoPclgxfexgeqMiKL0ZJGA/
-O4dzwGvky663qyVDslUte6sGDnVdNOVdc22esnVApVnJTzFxiNmIf1Q=
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA
+TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah
+WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf
+Tqj/ZA1k
-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G2
+============================================================
-----BEGIN CERTIFICATE-----
-MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
-MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
-IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
-MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
-FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
-bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
-dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
-H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
-uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
-mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
-a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
-E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
-WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
-VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
-Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
-cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
-IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
-AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
-YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
-6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
-Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
-c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
-mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO
+FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71
+lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB
+MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT
+1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD
+Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9
-----END CERTIFICATE-----
+
+GlobalSign Root CA
+==================
-----BEGIN CERTIFICATE-----
-MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
-MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
-b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
-MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
-QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
-VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
-A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
-CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
-tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
-dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
-PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
-+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
-BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
-BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
-MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
-ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
-IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
-7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
-43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
-eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
-pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
-WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
+GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
+b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
+VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
+DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
+THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
+Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
+c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
+gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
+Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
+j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
+hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
+X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
+
+GlobalSign Root CA - R2
+=======================
-----BEGIN CERTIFICATE-----
-MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
-MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
-b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
-MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
-ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
-BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
-AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
-6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
-GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
-dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
-1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
-62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
-BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
-AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
-MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
-cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
-b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
-IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
-iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
-GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
-4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
-XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
-MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
-b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
-MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
-EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
-BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
-AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
-xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
-87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
-2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
-WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
-0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
-A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
-AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
-pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
-ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
-aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
-hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
-hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
-dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
-P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
-iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
-xqE=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
-MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
-bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
-MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
-ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
-Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
-hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
-1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
-OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
-2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
-O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
-AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
-AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
-BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
-Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
-LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
-oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
-MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
-sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
-MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
-bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
-MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
-ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
-Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
-ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
-206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
-KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
-JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
-BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
-Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
-PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
-Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
-Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
-o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
-+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
-YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
-FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
-AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
-xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
-LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
-obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
-CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
-IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
-DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
-AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
-Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
-AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
-Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
-RY8mkaKO/qk=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIID5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx
-HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh
-IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0
-aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyOTA2MDAwMFoXDTM3MTEyMDE1
-MDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg
-SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M
-IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnej8Mlo2k06AX3dLm/WpcZuS+U
-0pPlLYnKhHw/EEMbjIt8hFj4JHxIzyr9wBXZGH6EGhfT257XyuTZ16pYUYfw8ItI
-TuLCxFlpMGK2MKKMCxGZYTVtfu/FsRkGIBKOQuHfD5YQUqjPnF+VFNivO3ULMSAf
-RC+iYkGzuxgh28pxPIzstrkNn+9R7017EvILDOGsQI93f7DKeHEMXRZxcKLXwjqF
-zQ6axOAAsNUl6twr5JQtOJyJQVdkKGUZHLZEtMgxa44Be3ZZJX8VHIQIfHNlIAqh
-BC4aMqiaILGcLCFZ5/vP7nAtCMpjPiybkxlqpMKX/7eGV4iFbJ4VFitNLLMCAwEA
-AaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUoTYwFsuGkABFgFOxj8jY
-PXy+XxIwHwYDVR0jBBgwFoAUoTYwFsuGkABFgFOxj8jYPXy+XxIwDgYDVR0PAQH/
-BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQCKIBilvrMvtKaEAEAwKfq0FHNMeUWn
-9nDg6H5kHgqVfGphwu9OH77/yZkfB2FK4V1Mza3u0FIy2VkyvNp5ctZ7CegCgTXT
-Ct8RHcl5oIBN/lrXVtbtDyqvpxh1MwzqwWEFT2qaifKNuZ8u77BfWgDrvq2g+EQF
-Z7zLBO+eZMXpyD8Fv8YvBxzDNnGGyjhmSs3WuEvGbKeXO/oTLW4jYYehY0KswsuX
-n2Fozy1MBJ3XJU8KDk2QixhWqJNIV9xvrr2eZ1d3iVCzvhGbRWeDhhmH05i9CBoW
-H1iCC+GWaQVLjuyDUTEH1dSf/1l7qG6Fz9NLqUmwX7A5KGgOc90lmt4S
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx
-HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh
-IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0
-aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAwMFoXDTM3MDkyODIz
-NDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg
-SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M
-IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIw
-DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ
-7ouZzU9AhqS2TcnZsdw8TQ2FTBVsRotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilb
-m2BPJoPRYxJWSXakFsKlnUWsi4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOY
-xFSMFkpBd4aVdQxHAWZg/BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZ
-YYCLqJV+FNwSbKTQ2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbq
-JS5Gr42whTg0ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fx
-I2rSAG2X+Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETz
-kxmlJ85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh
-EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNoKk/S
-Btc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJKg71ZDIM
-gtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1ExMVCgyhwn2RAu
-rda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
-FE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaAFE9pbQN+nZ8HGEO8txBO
-1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAO/Ouyugu
-h4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0cnAxa8cZmIDJgt43d15Ui47y6mdP
-yXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRFASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q
-7C+qPBR7V8F+GBRn7iTGvboVsNIYvbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKT
-RuidDV29rs4prWPVVRaAMCf/drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/
-ClTluUI8JPu3B5wwn3la5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyB
-M5kYJRF3p+v9WAksmWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQ
-my8YJPamTQr5O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xO
-AU++CrYD062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT
-9Y41xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H
-hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOLZ8/5
-fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
-RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
-VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
-DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
-ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
-VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
-mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
-IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
-mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
-XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
-dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
-jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
-BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
-DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
-9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
-jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
-Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
-ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
-R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFajCCBFKgAwIBAgIEPLU9RjANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwli
-ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMq
-YmVUUlVTVGVkIFJvb3QgQ0EtQmFsdGltb3JlIEltcGxlbWVudGF0aW9uMB4XDTAy
-MDQxMTA3Mzg1MVoXDTIyMDQxMTA3Mzg1MVowZjESMBAGA1UEChMJYmVUUlVTVGVk
-MRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNVBAMTKmJlVFJVU1Rl
-ZCBSb290IENBLUJhbHRpbW9yZSBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBALx+xDmcjOPWHIb/ymKt4H8wRXqOGrO4x/nRNv8i
-805qX4QQ+2aBw5R5MdKR4XeOGCrDFN5R9U+jK7wYFuK13XneIviCfsuBH/0nLI/6
-l2Qijvj/YaOcGx6Sj8CoCd8JEey3fTGaGuqDIQY8n7pc/5TqarjDa1U0Tz0yH92B
-FODEPM2dMPgwqZfT7syj0B9fHBOB1BirlNFjw55/NZKeX0Tq7PQiXLfoPX2k+Ymp
-kbIq2eszh+6l/ePazIjmiSZuxyuC0F6dWdsU7JGDBcNeDsYq0ATdcT0gTlgn/FP7
-eHgZFLL8kFKJOGJgB7Sg7KxrUNb9uShr71ItOrL/8QFArDcCAwEAAaOCAh4wggIa
-MA8GA1UdEwEB/wQFMAMBAf8wggG1BgNVHSAEggGsMIIBqDCCAaQGDysGAQQBsT4A
-AAEJKIORMTCCAY8wggFIBggrBgEFBQcCAjCCAToaggE2UmVsaWFuY2Ugb24gb3Ig
-dXNlIG9mIHRoaXMgQ2VydGlmaWNhdGUgY3JlYXRlcyBhbiBhY2tub3dsZWRnbWVu
-dCBhbmQgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk
-IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgdGhlIENlcnRpZmljYXRpb24g
-UHJhY3RpY2UgU3RhdGVtZW50IGFuZCB0aGUgUmVseWluZyBQYXJ0eSBBZ3JlZW1l
-bnQsIHdoaWNoIGNhbiBiZSBmb3VuZCBhdCB0aGUgYmVUUlVTVGVkIHdlYiBzaXRl
-LCBodHRwOi8vd3d3LmJldHJ1c3RlZC5jb20vcHJvZHVjdHNfc2VydmljZXMvaW5k
-ZXguaHRtbDBBBggrBgEFBQcCARY1aHR0cDovL3d3dy5iZXRydXN0ZWQuY29tL3By
-b2R1Y3RzX3NlcnZpY2VzL2luZGV4Lmh0bWwwHQYDVR0OBBYEFEU9w6nR3D8kVpgc
-cxiIav+DR+22MB8GA1UdIwQYMBaAFEU9w6nR3D8kVpgccxiIav+DR+22MA4GA1Ud
-DwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEASZK8o+6svfoNyYt5hhwjdrCA
-WXf82n+0S9/DZEtqTg6t8n1ZdwWtColzsPq8y9yNAIiPpqCy6qxSJ7+hSHyXEHu6
-7RMdmgduyzFiEuhjA6p9beP4G3YheBufS0OM00mG9htc9i5gFdPp43t1P9ACg9AY
-gkHNZTfqjjJ+vWuZXTARyNtIVBw74acT02pIk/c9jH8F6M7ziCpjBLjqflh8AXtb
-4cV97yHgjQ5dUX2xZ/2jvTg2xvI4hocalmhgRvsoFEdV4aeADGvi6t9NfJBIoDa9
-CReJf8Py05yc493EG931t3GzUwWJBtDLSoDByFOQtTwxiBdQn8nEDovYqAJjDQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFLDCCBBSgAwIBAgIEOU99hzANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJX
-VzESMBAGA1UEChMJYmVUUlVTVGVkMRswGQYDVQQDExJiZVRSVVNUZWQgUm9vdCBD
-QXMxGjAYBgNVBAMTEWJlVFJVU1RlZCBSb290IENBMB4XDTAwMDYyMDE0MjEwNFoX
-DTEwMDYyMDEzMjEwNFowWjELMAkGA1UEBhMCV1cxEjAQBgNVBAoTCWJlVFJVU1Rl
-ZDEbMBkGA1UEAxMSYmVUUlVTVGVkIFJvb3QgQ0FzMRowGAYDVQQDExFiZVRSVVNU
-ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANS0c3oT
-CjhVAb6JVuGUntS+WutKNHUbYSnE4a0IYCF4SP+00PpeQY1hRIfo7clY+vyTmt9P
-6j41ffgzeubx181vSUs9Ty1uDoM6GHh3o8/n9E1z2Jo7Gh2+lVPPIJfCzz4kUmwM
-jmVZxXH/YgmPqsWPzGCgc0rXOD8Vcr+il7dw6K/ifhYGTPWqZCZyByWtNfwYsSbX
-2P8ZDoMbjNx4RWc0PfSvHI3kbWvtILNnmrRhyxdviTX/507AMhLn7uzf/5cwdO2N
-R47rtMNE5qdMf1ZD6Li8tr76g5fmu/vEtpO+GRg+jIG5c4gW9JZDnGdzF5DYCW5j
-rEq2I8QBoa2k5MUCAwEAAaOCAfgwggH0MA8GA1UdEwEB/wQFMAMBAf8wggFZBgNV
-HSAEggFQMIIBTDCCAUgGCisGAQQBsT4BAAAwggE4MIIBAQYIKwYBBQUHAgIwgfQa
-gfFSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1
-bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0
-ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGFuZCBjZXJ0aWZpY2F0aW9uIHBy
-YWN0aWNlIHN0YXRlbWVudCwgd2hpY2ggY2FuIGJlIGZvdW5kIGF0IGJlVFJVU1Rl
-ZCdzIHdlYiBzaXRlLCBodHRwczovL3d3dy5iZVRSVVNUZWQuY29tL3ZhdWx0L3Rl
-cm1zMDEGCCsGAQUFBwIBFiVodHRwczovL3d3dy5iZVRSVVNUZWQuY29tL3ZhdWx0
-L3Rlcm1zMDQGA1UdHwQtMCswKaAnoCWkIzAhMRIwEAYDVQQKEwliZVRSVVNUZWQx
-CzAJBgNVBAYTAldXMB0GA1UdDgQWBBQquZtpLjub2M3eKjEENGvKBxirZzAfBgNV
-HSMEGDAWgBQquZtpLjub2M3eKjEENGvKBxirZzAOBgNVHQ8BAf8EBAMCAf4wDQYJ
-KoZIhvcNAQEFBQADggEBAHlh26Nebhax6nZR+csVm8tpvuaBa58oH2U+3RGFktTo
-Qb9+M70j5/Egv6S0phkBxoyNNXxlpE8JpNbYIxUFE6dDea/bow6be3ga8wSGWsb2
-jCBHOElQBp1yZzrwmAOtlmdE/D8QDYZN5AA7KXvOOzuZhmElQITcE2K3+spZ1gMe
-1lMBzW1MaFVA4e5rxyoAAEiCswoBw2AqDPeCNe5IhpbkdNQ96gFxugR1QKepfzk5
-mlWXKWWuGVUlBXJH0+gY3Ljpr0NzARJ0o+FcXxVdJPP55PS2Z2cS52QiivalQaYc
-tmBjRYoQtLpGEK5BV2VsPyMQPyEQWbfkQN0mDCP2qq4=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIGUTCCBTmgAwIBAgIEPLVPQDANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwli
-ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMq
-YmVUUlVTVGVkIFJvb3QgQ0EgLSBFbnRydXN0IEltcGxlbWVudGF0aW9uMB4XDTAy
-MDQxMTA4MjQyN1oXDTIyMDQxMTA4NTQyN1owZjESMBAGA1UEChMJYmVUUlVTVGVk
-MRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNVBAMTKmJlVFJVU1Rl
-ZCBSb290IENBIC0gRW50cnVzdCBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBALr0RAOqEmq1Q+xVkrYwfTVXDNvzDSduTPdQqJtO
-K2/b9a0cS12zqcH+e0TrW6MFDR/FNCswACnxeECypP869AGIF37m1CbTukzqMvtD
-d5eHI8XbQ6P1KqNRXuE70mVpflUVm3rnafdE4Fe1FehmYA8NA/uCjqPoEXtsvsdj
-DheT389Lrm5zdeDzqrmkwAkbhepxKYhBMvnwKg5sCfJ0a2ZsUhMfGLzUPvfYbiCe
-yv78IZTuEyhL11xeDGbu6bsPwTSxfwh28z0mcMmLJR1iJAzqHHVOwBLkuhMdMCkt
-VjMFu5dZfsZJT4nXLySotohAtWSSU1Yk5KKghbNekLQSM80CAwEAAaOCAwUwggMB
-MIIBtwYDVR0gBIIBrjCCAaowggGmBg8rBgEEAbE+AAACCSiDkTEwggGRMIIBSQYI
-KwYBBQUHAgIwggE7GoIBN1JlbGlhbmNlIG9uIG9yIHVzZSBvZiB0aGlzIENlcnRp
-ZmljYXRlIGNyZWF0ZXMgYW4gYWNrbm93bGVkZ21lbnQgYW5kIGFjY2VwdGFuY2Ug
-b2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0
-aW9ucyBvZiB1c2UsIHRoZSBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
-dCBhbmQgdGhlIFJlbHlpbmcgUGFydHkgQWdyZWVtZW50LCB3aGljaCBjYW4gYmUg
-Zm91bmQgYXQgdGhlIGJlVFJVU1RlZCB3ZWIgc2l0ZSwgaHR0cHM6Ly93d3cuYmV0
-cnVzdGVkLmNvbS9wcm9kdWN0c19zZXJ2aWNlcy9pbmRleC5odG1sMEIGCCsGAQUF
-BwIBFjZodHRwczovL3d3dy5iZXRydXN0ZWQuY29tL3Byb2R1Y3RzX3NlcnZpY2Vz
-L2luZGV4Lmh0bWwwEQYJYIZIAYb4QgEBBAQDAgAHMIGJBgNVHR8EgYEwfzB9oHug
-eaR3MHUxEjAQBgNVBAoTCWJlVFJVU1RlZDEbMBkGA1UECxMSYmVUUlVTVGVkIFJv
-b3QgQ0FzMTMwMQYDVQQDEypiZVRSVVNUZWQgUm9vdCBDQSAtIEVudHJ1c3QgSW1w
-bGVtZW50YXRpb24xDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMjA0MTEw
-ODI0MjdagQ8yMDIyMDQxMTA4NTQyN1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaA
-FH1w5a44iwY/qhwaj/nPJDCqhIQWMB0GA1UdDgQWBBR9cOWuOIsGP6ocGo/5zyQw
-qoSEFjAMBgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIE
-kDANBgkqhkiG9w0BAQUFAAOCAQEAKrgXzh8QlOu4mre5X+za95IkrNySO8cgjfKZ
-5V04ocI07cUTWVwFtStPYZuR+0H8/NU8TZh2BvWBfevdkObRVlTa4y0MnxEylCIB
-evZsLHRnBMylj44ss0O1lKLQfelifwa+JwGDnjr9iu6YQ0pr17WXOzq/T220Y/oz
-ADQuLW2WyXvKmWO6vvT2MKAtmJbpVkQFqUSjYRDrgqFnXbxdJ3Wqiig2KjiS2d2k
-XgClzMx8KSreKJCrt+G2/30lC0DYqjSjLd4H61/OCt3Kfjp9JsFiaDrmLzfzgYYh
-xKlkqu9FNtEaZnz46TfW1mG+oq1I59/mdP7TbX3SJdysYlep9w==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFaDCCBFCgAwIBAgIQO1nHe81bV569N1KsdrSqGjANBgkqhkiG9w0BAQUFADBi
-MRIwEAYDVQQKEwliZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENB
-czEvMC0GA1UEAxMmYmVUUlVTVGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRp
-b24wHhcNMDIwNDExMTExODEzWhcNMjIwNDEyMTEwNzI1WjBiMRIwEAYDVQQKEwli
-ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEvMC0GA1UEAxMm
-YmVUUlVTVGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRpb24wggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkujQwCY5X0LkGLG9uJIAiv11DpvpPrILn
-HGhwhRujbrWqeNluB0s/6d/16uhUoWGKDi9pdRi3DOUUjXFumLhV/AyV0Jtu4S2I
-1DpAa5LxmZZk3tv/ePTulh1HiXzUvrmIdyM6CeYEnm2qXtLIvZpOGd+J6lsOfsPk
-tPDgaTuID0GQ+NRxQyTBjyZLO1bp/4xsN+lFrYWMU8NghpBKlsmzVLC7F/AcRdnU
-GxlkVgoZ98zh/4avflherHqQH8koOUV7orbHnB/ahdQhhlkwk75TMzf270HPM8er
-cmsl9fNTGwxMLvF1S++gh/f+ihXQbNXL+WhTuXAVE8L1LvtDNXUtAgMBAAGjggIY
-MIICFDAMBgNVHRMEBTADAQH/MIIBtQYDVR0gBIIBrDCCAagwggGkBg8rBgEEAbE+
-AAADCSiDkTEwggGPMEEGCCsGAQUFBwIBFjVodHRwOi8vd3d3LmJldHJ1c3RlZC5j
-b20vcHJvZHVjdHNfc2VydmljZXMvaW5kZXguaHRtbDCCAUgGCCsGAQUFBwICMIIB
-OhqCATZSZWxpYW5jZSBvbiBvciB1c2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjcmVh
-dGVzIGFuIGFja25vd2xlZGdtZW50IGFuZCBhY2NlcHRhbmNlIG9mIHRoZSB0aGVu
-IGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNl
-LCB0aGUgQ2VydGlmaWNhdGlvbiBQcmFjdGljZSBTdGF0ZW1lbnQgYW5kIHRoZSBS
-ZWx5aW5nIFBhcnR5IEFncmVlbWVudCwgd2hpY2ggY2FuIGJlIGZvdW5kIGF0IHRo
-ZSBiZVRSVVNUZWQgd2ViIHNpdGUsIGh0dHA6Ly93d3cuYmV0cnVzdGVkLmNvbS9w
-cm9kdWN0c19zZXJ2aWNlcy9pbmRleC5odG1sMAsGA1UdDwQEAwIBBjAfBgNVHSME
-GDAWgBSp7BR++dlDzFMrFK3P9/BZiUHNGTAdBgNVHQ4EFgQUqewUfvnZQ8xTKxSt
-z/fwWYlBzRkwDQYJKoZIhvcNAQEFBQADggEBANuXsHXqDMTBmMpWBcCorSZIry0g
-6IHHtt9DwSwddUvUQo3neqh03GZCWYez9Wlt2ames30cMcH1VOJZJEnl7r05pmuK
-mET7m9cqg5c0Lcd9NUwtNLg+DcTsiCevnpL9UGGCqGAHFFPMZRPB9kdEadIxyKbd
-LrML3kqNWz2rDcI1UqJWN8wyiyiFQpyRQHpwKzg21eFzGh/l+n5f3NacOzDq28Bb
-J1zTcwfBwvNMm2+fG8oeqqg4MwlYsq78B+g23FW6L09A/nq9BqaBwZMifIYRCgZ3
-SK41ty8ymmFei74pnykkiFY5LKjSq5YDWtRIn7lAhAuYaPsBQ9Yb4gmxlxw=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
-MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
-ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
-b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
-MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
-ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
-IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
-AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
-unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
-BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
-7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
-0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
-roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
-A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
-aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
-26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
-BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
-EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
-BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
-aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
-AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
-p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
-1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
-XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
-eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
-tGWaIZDgqtCYvDi1czyL+Nw=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
-MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
-ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
-YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
-MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
-NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
-A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
-A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
-Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
-QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
-eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
-B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
-z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
-AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
-ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
-TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
-MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
-VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
-VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
-bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
-AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
-bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
-ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
-VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
-ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
-AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
-PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
-cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
-MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
-IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
-ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
-VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
-kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
-EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
-H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
-HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
-DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
-QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
-Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
-AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
-yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
-FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
-ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
-kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
-l7+ijrRU
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
-MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
-QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
-MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
-QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
-jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
-ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
-ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
-Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
-AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
-HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
-uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
-TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
-xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
-CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
-O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
-6GAqm4VKQPNriiTsBhYscw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
-MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
-GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
-YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
-MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
-BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
-GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
-BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
-3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
-YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
-rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
-ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
-oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
-MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
-QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
-b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
-AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
-GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
-Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
-G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
-l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
-smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
-gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
-A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
-BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
-MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
-YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
-RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
-aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
-UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
-2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
-Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
-+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
-DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
-nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
-/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
-PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
-QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
-SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
-IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
-RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
-zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
-BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
-ZQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
-MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
-BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
-IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
-MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
-ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
-T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
-FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
-cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
-BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
-BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
-fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
-GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
-MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
-GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
-ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
-fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
-A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
-BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
-BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
-cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
-HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
-CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
-3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
-6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
-HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
-EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
-Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
-Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
-DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
-5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
-Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
-gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
-aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
-izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
-MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
-GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
-aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
-MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
-BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
-VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
-AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
-fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
-TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
-fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
-1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
-kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
-A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
-ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
-dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
-Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
-HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
-pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
-jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
-xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
-dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
-b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
-EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
-cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
-JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
-mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
-wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
-VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
-AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
-AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
-BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
-pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
-dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
-fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
-NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
-H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
-+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
-QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
-b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
-CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
-nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
-43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
-T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
-gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
-BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
-TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
-DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
-hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
-06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
-PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
-YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
-CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
-d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
-ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
-MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
-LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
-RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
-+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
-PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
-xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
-Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
-hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
-EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
-MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
-FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
-nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
-eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
-hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
-Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
-vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
-+OkuE6N36B9K
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
-UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
-EwhEU1RDQSBFMTAeFw05ODEyMTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJ
-BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
-ETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCg
-bIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJENySZ
-j9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlV
-Sn5JTe2io74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCG
-SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
-JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
-RFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMTAxODEw
-MjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFGp5
-fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i
-+DAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
-SIb3DQEBBQUAA4GBACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lN
-QseSJqBcNJo4cvj9axY+IO6CizEqkzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+
-gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4RbyhkwS7hp86W0N6w4pl
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIID2DCCAsACEQDQHkCLAAACfAAAAAIAAAABMA0GCSqGSIb3DQEBBQUAMIGpMQsw
-CQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENp
-dHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UE
-CxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBSb290Q0EgWDExITAfBgkqhkiG9w0B
-CQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODEyMDExODE4NTVaFw0wODExMjgx
-ODE4NTVaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMO
-U2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0
-IENvLjERMA8GA1UECxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBSb290Q0EgWDEx
-ITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBANLGJrbnpT3BxGjVUG9TxW9JEwm4ryxIjRRqoxdf
-WvnTLnUv2Chi0ZMv/E3Uq4flCMeZ55I/db3rJbQVwZsZPdJEjdd0IG03Ao9pk1uK
-xBmd9LIO/BZsubEFkoPRhSxglD5FVaDZqwgh5mDoO3TymVBRaNADLbGAvqPYUrBE
-zUNKcI5YhZXhTizWLUFv1oTnyJhEykfbLCSlaSbPa7gnYsP0yXqSI+0TZ4KuRS5F
-5X5yP4WdlGIQ5jyRoa13AOAV7POEgHJ6jm5gl8ckWRA0g1vhpaRptlc1HHhZxtMv
-OnNn7pTKBBMFYgZwI7P0fO5F2WQLW0mqpEPOJsREEmy43XkCAwEAATANBgkqhkiG
-9w0BAQUFAAOCAQEAojeyP2n714Z5VEkxlTMr89EJFEliYIalsBHiUMIdBlc+Legz
-ZL6bqq1fG03UmZWii5rJYnK1aerZWKs17RWiQ9a2vAd5ZWRzfdd5ynvVWlHG4VME
-lo04z6MXrDlxawHDi1M8Y+nuecDkvpIyZHqzH5eUYr3qsiAVlfuX8ngvYzZAOONG
-Dx3drJXK50uQe7FLqdTF65raqtWjlBRGjS0f8zrWkzr2Pnn86Oawde3uPclwx12q
-gUtGJRzHbBXjlU4PqjI3lAoXJJIThFjSY28r9+ZbYgsTF7ANUkz+/m9c4pFuHf2k
-Ytdo+o56T9II2pPc8JIRetDccpMMc5NihWjQ9A==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
-UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
-EwhEU1RDQSBFMjAeFw05ODEyMDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJ
-BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
-ETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC/
-k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGODVvso
-LeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3o
-TQPMx7JSxhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCG
-SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
-JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
-RFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMDkxOTE3
-MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFB6C
-TShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5
-WzAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
-SIb3DQEBBQUAA4GBAEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHR
-xdf0CiUPPXiBng+xZ8SQTGPdXqfiup/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVL
-B3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1mPnHfxsb1gYgAlihw6ID
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIID2DCCAsACEQDQHkCLAAB3bQAAAAEAAAAEMA0GCSqGSIb3DQEBBQUAMIGpMQsw
-CQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENp
-dHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UE
-CxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBSb290Q0EgWDIxITAfBgkqhkiG9w0B
-CQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODExMzAyMjQ2MTZaFw0wODExMjcy
-MjQ2MTZaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMO
-U2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0
-IENvLjERMA8GA1UECxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBSb290Q0EgWDIx
-ITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBANx18IzAdZaawGIfJvfE4Zrq4FZzW5nNAUSoCLbV
-p9oaBBg5kkp4o4HC9Xd6ULRw/5qrxsfKboNPQpj7Jgva3G3WqZlVUmfpKAOS3OWw
-BZoPFflrWXJW8vo5/Kpo7g8fEIMv/J36F5bdguPmRX3AS4BEH+0s4IT9kVySVGkl
-5WJp3OXuAFK9MwutdQKFp2RQLcUZGTDAJtvJ0/0uma1ZtQtN1EGuhUhDWdy3qOKi
-3sOP17ihYqZoUFLkzzGnlIXan0YyF1bl8utmPRL/Q9uY73fPy4GNNLHGUEom0eQ+
-QVCvbK4iNC7Va26Dunm4dmVI2gkpZGMiuftHdoWMhkTLCdsCAwEAATANBgkqhkiG
-9w0BAQUFAAOCAQEAtTYOXeFhKFoRZcA/gwN5Tb4opgsHAlKFzfiR0BBstWogWxyQ
-2TA8xkieil5k+aFxd+8EJx8H6+Qm93N0yUQYGmbT4EOvkTvRyyzYdFQ6HE3K1GjN
-I3wdEJ5F6fYAbqbNGf9PLCmPV03Ed5K+4EwJ+11EhmYhqLkyolbV6YyDfFk/xPEL
-553snr2cGA4+wjl5KLcDDQjLxufZATdQEOzMYRZA1K8xdHv8PzGn0EdzMzkbzE5q
-10mDEQb+64JYMzJM8FasHpwvVpp7wUocpf1VNs78lk30sPDst2yC7S8xmUJMqbIN
-uBVd8d+6ybVK1GSYsyapMMj9puyrliGtf8J4tg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
-MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
-ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
-MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
-VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
-FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
-MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
-ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
-gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
-fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
-ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
-ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
-MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
-c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
-dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
-aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
-hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
-QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
-h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
-nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
-rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
-9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
-MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
-DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
-PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
-Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
-rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
-OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
-xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
-7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
-aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
-HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
-SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
-ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
-AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
-R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
-JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
-Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEgzCCA+ygAwIBAgIEOJ725DANBgkqhkiG9w0BAQQFADCBtDEUMBIGA1UEChML
-RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9HQ0NBX0NQUyBp
-bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAw
-IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVu
-dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMDAyMDcxNjE2NDBaFw0yMDAy
-MDcxNjQ2NDBaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
-LmVudHJ1c3QubmV0L0dDQ0FfQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
-YWIuKTElMCMGA1UECxMcKGMpIDIwMDAgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
-A1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTdLS25MVL1qFof2LV7PdRV7Ny
-Spj10InJrWPNTTVRaoTUrcloeW+46xHbh65cJFET8VQlhK8pK5/jgOLZy93GRUk0
-iJBeAZfv6lOm3fzB3ksqJeTpNfpVBQbliXrqpBFXO/x8PTbNZzVtpKklWb1m9fkn
-5JVn1j+SgF7yNH0rhQIDAQABo4IBnjCCAZowEQYJYIZIAYb4QgEBBAQDAgAHMIHd
-BgNVHR8EgdUwgdIwgc+ggcyggcmkgcYwgcMxFDASBgNVBAoTC0VudHJ1c3QubmV0
-MUAwPgYDVQQLFDd3d3cuZW50cnVzdC5uZXQvR0NDQV9DUFMgaW5jb3JwLiBieSBy
-ZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5l
-dCBMaW1pdGVkMTMwMQYDVQQDEypFbnRydXN0Lm5ldCBDbGllbnQgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMDAy
-MDcxNjE2NDBagQ8yMDIwMDIwNzE2NDY0MFowCwYDVR0PBAQDAgEGMB8GA1UdIwQY
-MBaAFISLdP3FjcD/J20gN0V8/i3OutN9MB0GA1UdDgQWBBSEi3T9xY3A/ydtIDdF
-fP4tzrrTfTAMBgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4w
-AwIEkDANBgkqhkiG9w0BAQQFAAOBgQBObzWAO9GK9Q6nIMstZVXQkvTnhLUGJoMS
-hAusO7JE7r3PQNsgDrpuFOow4DtifH+La3xKp9U1PL6oXOpLu5OOgGarDyn9TS2/
-GpsKkMWr2tGzhtQvJFJcem3G8v7lTRowjJDyutdKPkN+1MhQGof4T4HHdguEOnKd
-zmVml64mXg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIElTCCA/6gAwIBAgIEOJsRPDANBgkqhkiG9w0BAQQFADCBujEUMBIGA1UEChML
-RW50cnVzdC5uZXQxPzA9BgNVBAsUNnd3dy5lbnRydXN0Lm5ldC9TU0xfQ1BTIGlu
-Y29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDIwMDAg
-RW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJl
-IFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMDAyMDQxNzIwMDBa
-Fw0yMDAyMDQxNzUwMDBaMIG6MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDE/MD0GA1UE
-CxQ2d3d3LmVudHJ1c3QubmV0L1NTTF9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
-dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5ldCBMaW1pdGVk
-MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
-b24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHwV9OcfHO
-8GCGD9JYf9Mzly0XonUwtZZkJi9ow0SrqHXmAGc0V55lxyKbc+bT3QgON1WqJUaB
-bL3+qPZ1V1eMkGxKwz6LS0MKyRFWmponIpnPVZ5h2QLifLZ8OAfc439PmrkDQYC2
-dWcTC5/oVzbIXQA23mYU2m52H083jIITiQIDAQABo4IBpDCCAaAwEQYJYIZIAYb4
-QgEBBAQDAgAHMIHjBgNVHR8EgdswgdgwgdWggdKggc+kgcwwgckxFDASBgNVBAoT
-C0VudHJ1c3QubmV0MT8wPQYDVQQLFDZ3d3cuZW50cnVzdC5uZXQvU1NMX0NQUyBp
-bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAw
-IEVudHJ1c3QubmV0IExpbWl0ZWQxOjA4BgNVBAMTMUVudHJ1c3QubmV0IFNlY3Vy
-ZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEw
-KwYDVR0QBCQwIoAPMjAwMDAyMDQxNzIwMDBagQ8yMDIwMDIwNDE3NTAwMFowCwYD
-VR0PBAQDAgEGMB8GA1UdIwQYMBaAFMtswGvjuz7L/CKc/vuLkpyw8m4iMB0GA1Ud
-DgQWBBTLbMBr47s+y/winP77i5KcsPJuIjAMBgNVHRMEBTADAQH/MB0GCSqGSIb2
-fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQQFAAOBgQBi24GRzsia
-d0Iv7L0no1MPUBvqTpLwqa+poLpIYcvvyQbvH9X07t9WLebKahlzqlO+krNQAraF
-JnJj2HVQYnUUt7NQGj/KEQALhUVpbbalrlHhStyCP2yMNLJ3a9kC9n8O6mUE8c1U
-yrrJzOCE98g+EZfTYAkYvAX/bIkz8OwVDw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
-RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
-bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
-IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
-ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy
-MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
-LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
-YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
-A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
-K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
-sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
-MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
-XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
-HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
-4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA
-vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G
-CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA
-WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
-oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ
-h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18
-f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN
-B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy
-vUxFnmG6v4SBkgPR0ml8xQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UEBhMC
-VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50cnVzdC5u
-ZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBsaW1pdHMgbGlh
-Yi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
-BAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
-Fw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBaMIHJMQswCQYDVQQGEwJVUzEU
-MBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9D
-bGllbnRfQ0FfSW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjEl
-MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMq
-RW50cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0G
-CSqGSIb3DQEBAQUAA4GLADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo
-6oT9n3V5z8GKUZSvx1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux
-5zDeg7K6PvHViTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zm
-AqTmT173iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSC
-ARkwggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50
-cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5m
-by9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMp
-IDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQg
-Q2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCyg
-KqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9DbGllbnQxLmNybDArBgNV
-HRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkxMDEyMTkyNDMwWjALBgNVHQ8E
-BAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW/O5bs8qZdIuV6kwwHQYDVR0OBBYE
-FMT7nCl7l81MlvzuW7PKmXSLlepMMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
-BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7
-pFuPeJoSSJn59DXeDDYHAmsQOokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzz
-wy5E97BnRqqS5TvaHBkUODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/a
-EkP/TOYGJqibGapEPHayXOw=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
-VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
-ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
-KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
-ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
-MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
-ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
-b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
-bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
-U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
-A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
-I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
-wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
-AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
-oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
-BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
-dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
-MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
-b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
-dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
-MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
-E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
-MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
-hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
-95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
-2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
-VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
-Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
-KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
-cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
-NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
-NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
-ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
-BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
-KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
-Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
-4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
-KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
-rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
-94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
-sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
-gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
-kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
-vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
-A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
-O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
-AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
-9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
-eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
-0vdXcDazv/wor3ElhVsT/h5/WrQ8
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
-UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
-dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
-MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
-dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
-BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
-cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
-AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
-MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
-aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
-ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
-IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
-MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
-A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
-7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
-1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
-MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
-ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
-MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
-LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
-KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
-RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
-WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
-Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
-AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
-eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
-zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
-WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
-/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
-UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj
-dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0
-NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD
-VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G
-vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/
-BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C
-AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX
-MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl
-IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw
-NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq
-y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF
-MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
-A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy
-0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1
-E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
-MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
-ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
-MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
-dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
-c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
-UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
-58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
-o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
-MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
-aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
-A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
-Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
-8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMx
-IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1
-dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
-MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w
-HhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTELMAkGA1UEBhMCRVMx
-IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1
-dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
-MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w
-ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5u
-Cp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5Vj1H5WuretXDE7aTt/6MNbg9kUDGvASdY
-rv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJHlShbz++AbOCQl4oBPB3z
-hxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf3H5idPay
-BQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcL
-iam8NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcb
-AgMBAAGjgZ8wgZwwKgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lv
-bmFsLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0
-MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
-FgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQADggEBAEdz/o0n
-VPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq
-u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36m
-hoEyIwOdyPdfwUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzfl
-ZKG+TQyTmAyX9odtsz/ny4Cm7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBp
-QWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YGVM+h4k0460tQtcsm9MracEpqoeJ5
-quGnM/b9Sh/22WA=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
-MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
-IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
-EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
-R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
-PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
-Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
-TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
-5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
-S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
-2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
-FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
-EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
-EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
-/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
-A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
-abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
-I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
-4iIprn2DQKi6bA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
-MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
-YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
-EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
-R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
-9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
-fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
-iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
-1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
-bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
-MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
-ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
-uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
-Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
-tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
-PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
-hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
-5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
-MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
-R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
-MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
-Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
-ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
-AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
-AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
-ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
-7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
-kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
-mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
-KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
-6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
-4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
-oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
-UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
-AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
-MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
-c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
-VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
-c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
-AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
-WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
-FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
-XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
-se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
-KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
-IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
-y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
-hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
-QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
-Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
-HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
-HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
-KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
-dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
-L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
-Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
-ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
-T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
-GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
-1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
-OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
-6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
-QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
-MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
-c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
-BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
-IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
-VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
-cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
-QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
-F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
-c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
-mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
-VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
-teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
-f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
-Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
-nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
-/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
-MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
-9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
-aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
-IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
-ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
-uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
-Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
-QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
-koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
-ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
-DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
-bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
-A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
-b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
-MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
-YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
-aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
-jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
-xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
-1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
-snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
-U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
-9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
-BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
-AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
-yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
-38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
-AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
-DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
-HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
-A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
-Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
-MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
-A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
-v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
-eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
-tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
-C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
-zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
-mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
-V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
-bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
-3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
-J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
-291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
-ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
-AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
+ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
+s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
+S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
+TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
+ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
+YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
+BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
+9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
+01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
+9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
-MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
-YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
-MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
-ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
-MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
-ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
-PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
-wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
-EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
-avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
-YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
-sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
-/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
-IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
-ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
-OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
-TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
-HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
-dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
-ReYNnyicsbkqWletNw+vHX/bvZ8=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
-VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
-bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
-b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
-UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
-cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
-b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
-iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
-r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
-04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
-GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
-3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
-lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIB+jCCAWMCAgGjMA0GCSqGSIb3DQEBBAUAMEUxCzAJBgNVBAYTAlVTMRgwFgYD
-VQQKEw9HVEUgQ29ycG9yYXRpb24xHDAaBgNVBAMTE0dURSBDeWJlclRydXN0IFJv
-b3QwHhcNOTYwMjIzMjMwMTAwWhcNMDYwMjIzMjM1OTAwWjBFMQswCQYDVQQGEwJV
-UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMRwwGgYDVQQDExNHVEUgQ3liZXJU
-cnVzdCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC45k+625h8cXyv
-RLfTD0bZZOWTwUKOx7pJjTUteueLveUFMVnGsS8KDPufpz+iCWaEVh43KRuH6X4M
-ypqfpX/1FZSj1aJGgthoTNE3FQZor734sLPwKfWVWgkWYXcKIiXUT0Wqx73llt/5
-1KiOQswkwB6RJ0q1bQaAYznEol44AwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBABKz
-dcZfHeFhVYAA1IFLezEPI2PnPfMD+fQ2qLvZ46WXTeorKeDWanOB5sCJo9Px4KWl
-IjeaY8JIILTbcuPI9tl8vrGvU9oUtCG41tWW4/5ODFlitppK+ULdjG+BqXH/9Apy
-bW1EDp3zdHSo1TRJ6V6e6bR64eVaH4QwnNOfpSXY
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARwxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEzMDEGA1UECxMq
-SVBTIENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYD
-VQQDEypJUFMgQ0EgQ2hhaW5lZCBDQXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
-HjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczAeFw0wMTEyMjkwMDUzNTha
-Fw0yNTEyMjcwMDUzNThaMIIBHDELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNl
-bG9uYTESMBAGA1UEBxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQg
-cHVibGlzaGluZyBTZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMu
-ZXMgQy5JLkYuICBCLTYwOTI5NDUyMTMwMQYDVQQLEypJUFMgQ0EgQ2hhaW5lZCBD
-QXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMzAxBgNVBAMTKklQUyBDQSBDaGFp
-bmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJARYP
-aXBzQG1haWwuaXBzLmVzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcVpJJ
-spQgvJhPUOtopKdJC7/SMejHT8KGC/po/UNaivNgkjWZOLtNA1IhW/A3mTXhQSCB
-hYEFcYGdtJUZqV92NC5jNzVXjrQfQj8VXOF6wV8TGDIxya2+o8eDZh65nAQTy2nB
-Bt4wBrszo7Uf8I9vzv+W6FS+ZoCua9tBhDaiPQIDAQABo4IEQzCCBD8wHQYDVR0O
-BBYEFKGtMbH5PuEXpsirNPxShwkeYlJBMIIBTgYDVR0jBIIBRTCCAUGAFKGtMbH5
-PuEXpsirNPxShwkeYlJBoYIBJKSCASAwggEcMQswCQYDVQQGEwJFUzESMBAGA1UE
-CBMJQmFyY2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJ
-bnRlcm5ldCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0Bt
-YWlsLmlwcy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxMzAxBgNVBAsTKklQUyBDQSBD
-aGFpbmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAxMqSVBT
-IENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI
-hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8E
-BQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMG
-CCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYB
-BAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMw
-EYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBC
-BglghkgBhvhCAQ0ENRYzQ2hhaW5lZCBDQSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkg
-aHR0cDovL3d3dy5pcHMuZXMvMCkGCWCGSAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlw
-cy5lcy9pcHMyMDAyLzA3BglghkgBhvhCAQQEKhYoaHR0cDovL3d3dy5pcHMuZXMv
-aXBzMjAwMi9pcHMyMDAyQ0FDLmNybDA8BglghkgBhvhCAQMELxYtaHR0cDovL3d3
-dy5pcHMuZXMvaXBzMjAwMi9yZXZvY2F0aW9uQ0FDLmh0bWw/MDkGCWCGSAGG+EIB
-BwQsFipodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3JlbmV3YWxDQUMuaHRtbD8w
-NwYJYIZIAYb4QgEIBCoWKGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5
-Q0FDLmh0bWwwbQYDVR0fBGYwZDAuoCygKoYoaHR0cDovL3d3dy5pcHMuZXMvaXBz
-MjAwMi9pcHMyMDAyQ0FDLmNybDAyoDCgLoYsaHR0cDovL3d3d2JhY2suaXBzLmVz
-L2lwczIwMDIvaXBzMjAwMkNBQy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF
-BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAERyMJ1W
-WKJBGyi3leGmGpVfp3hAK+/blkr8THFj2XOVvQLiogbHvpcqk4A0hgP63Ng9HgfN
-HnNDJGD1HWHc3JagvPsd4+cSACczAsDAK1M92GsDgaPb1pOVIO/Tln4mkImcJpvN
-b2ar7QMiRDjMWb2f2/YHogF/JsRj9SVCXmK9
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEuMCwGA1UECxMl
-SVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMl
-SVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3
-DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAwNTkzOFoXDTI1MTIyNzAw
-NTkzOFowggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYD
-VQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5n
-IFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4g
-IEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFTRTEgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTEgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzAN
-BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4FEnpwvdr9G5Q1uCN0VWcu+atsIS7ywS
-zHb5BlmvXSHU0lq4oNTzav3KaY1mSPd05u42veiWkXWmcSjK5yISMmmwPh5r9FBS
-YmL9Yzt9fuzuOOpi9GyocY3h6YvJP8a1zZRCb92CRTzo3wno7wpVqVZHYUxJZHMQ
-KD/Kvwn/xi8CAwEAAaOCBEowggRGMB0GA1UdDgQWBBTrsxl588GlHKzcuh9morKb
-adB4CDCCAUQGA1UdIwSCATswggE3gBTrsxl588GlHKzcuh9morKbadB4CKGCARqk
-ggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE
-BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBT
-ZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBC
-LTYwOTI5NDUyMS4wLAYDVQQLEyVJUFMgQ0EgQ0xBU0UxIENlcnRpZmljYXRpb24g
-QXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0EgQ0xBU0UxIENlcnRpZmljYXRpb24g
-QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYD
-VR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggr
-BgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB
-FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhC
-AQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGB
-D2lwc0BtYWlsLmlwcy5lczBBBglghkgBhvhCAQ0ENBYyQ0xBU0UxIENBIENlcnRp
-ZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgEC
-BBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIBBAQtFito
-dHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTEuY3JsMD8GCWCG
-SAGG+EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25D
-TEFTRTEuaHRtbD8wPAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lw
-czIwMDIvcmVuZXdhbENMQVNFMS5odG1sPzA6BglghkgBhvhCAQgELRYraHR0cDov
-L3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFTRTEuaHRtbDBzBgNVHR8EbDBq
-MDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTEu
-Y3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy
-Q0xBU0UxLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9v
-Y3NwLmlwcy5lcy8wDQYJKoZIhvcNAQEFBQADgYEAK9Dr/drIyllq2tPMMi7JVBuK
-Yn4VLenZMdMu9Ccj/1urxUq2ckCuU3T0vAW0xtnIyXf7t/k0f3gA+Nak5FI/LEpj
-V4F1Wo7ojPsCwJTGKbqz3Bzosq/SLmJbGqmODszFV0VRFOlOHIilkfSj945RyKm+
-hjM+5i9Ibq9UkE6tsSU=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEuMCwGA1UECxMl
-SVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMl
-SVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3
-DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMDE0NFoXDTI1MTIyNzAx
-MDE0NFowggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYD
-VQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5n
-IFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4g
-IEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFTRTMgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTMgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzAN
-BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxf+DrDGaBtT8FK+n/ra+osTBLsBjzLZ
-H49NzjaY2uQARIwo2BNEKqRrThckQpzTiKRBgtYj+4vJhuW5qYIF3PHeH+AMmVWY
-8jjsbJ0gA8DvqqPGZARRLXgNo9KoOtYkTOmWehisEyMiG3zoMRGzXwmqMHBxRiVr
-SXGAK5UBsh8CAwEAAaOCBEowggRGMB0GA1UdDgQWBBS4k/8uy9wsjqLnev42USGj
-mFsMNDCCAUQGA1UdIwSCATswggE3gBS4k/8uy9wsjqLnev42USGjmFsMNKGCARqk
-ggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE
-BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBT
-ZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBC
-LTYwOTI5NDUyMS4wLAYDVQQLEyVJUFMgQ0EgQ0xBU0UzIENlcnRpZmljYXRpb24g
-QXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0EgQ0xBU0UzIENlcnRpZmljYXRpb24g
-QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYD
-VR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggr
-BgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB
-FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhC
-AQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGB
-D2lwc0BtYWlsLmlwcy5lczBBBglghkgBhvhCAQ0ENBYyQ0xBU0UzIENBIENlcnRp
-ZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgEC
-BBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIBBAQtFito
-dHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTMuY3JsMD8GCWCG
-SAGG+EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25D
-TEFTRTMuaHRtbD8wPAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lw
-czIwMDIvcmVuZXdhbENMQVNFMy5odG1sPzA6BglghkgBhvhCAQgELRYraHR0cDov
-L3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFTRTMuaHRtbDBzBgNVHR8EbDBq
-MDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTMu
-Y3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy
-Q0xBU0UzLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9v
-Y3NwLmlwcy5lcy8wDQYJKoZIhvcNAQEFBQADgYEAF2VcmZVDAyevJuXr0LMXI/dD
-qsfwfewPxqmurpYPdikc4gYtfibFPPqhwYHOU7BC0ZdXGhd+pFFhxu7pXu8Fuuu9
-D6eSb9ijBmgpjnn1/7/5p6/ksc7C0YBCJwUENPjDfxZ4IwwHJPJGR607VNCv1TGy
-r33I6unUVtkOE7LFRVA=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMm
-SVBTIENBIENMQVNFQTEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMT
-JklQUyBDQSBDTEFTRUExIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI
-hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNTMyWhcNMjUxMjI3
-MDEwNTMyWjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ
-BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp
-bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G
-LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMw
-gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALsw19zQVL01Tp/FTILq0VA8R5j8
-m2mdd81u4D/u6zJfX5/S0HnllXNEITLgCtud186Nq1KLK3jgm1t99P1tCeWu4Wwd
-ByOgF9H5fahGRpEiqLJpxq339fWUoTCUvQDMRH/uxJ7JweaPCjbB/SQ9AaD1e+J8
-eGZDi09Z8pvZ+kmzAgMBAAGjggRTMIIETzAdBgNVHQ4EFgQUZyaW56G/2LUDnf47
-3P7yiuYV3TAwggFGBgNVHSMEggE9MIIBOYAUZyaW56G/2LUDnf473P7yiuYV3TCh
-ggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ
-BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp
-bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G
-LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOC
-AQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUF
-BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYB
-BAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglg
-hkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1Ud
-EgQTMBGBD2lwc0BtYWlsLmlwcy5lczBCBglghkgBhvhCAQ0ENRYzQ0xBU0VBMSBD
-QSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3dy5pcHMuZXMvMCkGCWCG
-SAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7BglghkgBhvhC
-AQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMS5j
-cmwwQAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2
-b2NhdGlvbkNMQVNFQTEuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cu
-aXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFQTEuaHRtbD8wOwYJYIZIAYb4QgEI
-BC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5Q0xBU0VBMS5odG1s
-MHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvaXBz
-MjAwMkNMQVNFQTEuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz
-MjAwMi9pcHMyMDAyQ0xBU0VBMS5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF
-BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAH66iqyA
-AIQVCtWYUQxkxZwCWINmyq0eB81+atqAB98DNEock8RLWCA1NnHtogo1EqWmZaeF
-aQoO42Hu6r4okzPV7Oi+xNtff6j5YzHIa5biKcJboOeXNp13XjFr/tOn2yrb25aL
-H2betgPAK7N41lUH5Y85UN4HI3LmvSAUS7SG
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMm
-SVBTIENBIENMQVNFQTMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMT
-JklQUyBDQSBDTEFTRUEzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI
-hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNzUwWhcNMjUxMjI3
-MDEwNzUwWjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ
-BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp
-bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G
-LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMw
-gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAO6AAPYaZC6tasiDsYun7o/ZttvN
-G7uGBiJ2MwwSbUhWYdLcgiViL5/SaTBlA0IjWLxH3GvWdV0XPOH/8lhneaDBgbHU
-VqLyjRGZ/fZ98cfEXgIqmuJKtROKAP2Md4bm15T1IHUuDky/dMQ/gT6DtKM4Ninn
-6Cr1jIhBqoCm42zvAgMBAAGjggRTMIIETzAdBgNVHQ4EFgQUHp9XUEe2YZM50yz8
-2l09BXW3mQIwggFGBgNVHSMEggE9MIIBOYAUHp9XUEe2YZM50yz82l09BXW3mQKh
-ggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ
-BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp
-bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G
-LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOC
-AQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUF
-BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYB
-BAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglg
-hkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1Ud
-EgQTMBGBD2lwc0BtYWlsLmlwcy5lczBCBglghkgBhvhCAQ0ENRYzQ0xBU0VBMyBD
-QSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3dy5pcHMuZXMvMCkGCWCG
-SAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7BglghkgBhvhC
-AQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMy5j
-cmwwQAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2
-b2NhdGlvbkNMQVNFQTMuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cu
-aXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFQTMuaHRtbD8wOwYJYIZIAYb4QgEI
-BC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5Q0xBU0VBMy5odG1s
-MHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvaXBz
-MjAwMkNMQVNFQTMuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz
-MjAwMi9pcHMyMDAyQ0xBU0VBMy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF
-BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAEo9IEca
-2on0eisxeewBwMwB9dbB/MjD81ACUZBYKp/nNQlbMAqBACVHr9QPDp5gJqiVp4MI
-3y2s6Q73nMify5NF8bpqxmdRSmlPa/59Cy9SKcJQrSRE7SOzSMtEQMEDlQwKeAYS
-AfWRMS1Jjbs/RU4s4OjNtckUFQzjB4ObJnXv
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICtzCCAiACAQAwDQYJKoZIhvcNAQEEBQAwgaMxCzAJBgNVBAYTAkVTMRIwEAYD
-VQQIEwlCQVJDRUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcGA1UEChMQSVBT
-IFNlZ3VyaWRhZCBDQTEYMBYGA1UECxMPQ2VydGlmaWNhY2lvbmVzMRcwFQYDVQQD
-Ew5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVz
-MB4XDTk4MDEwMTIzMjEwN1oXDTA5MTIyOTIzMjEwN1owgaMxCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCQVJDRUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcGA1UE
-ChMQSVBTIFNlZ3VyaWRhZCBDQTEYMBYGA1UECxMPQ2VydGlmaWNhY2lvbmVzMRcw
-FQYDVQQDEw5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwu
-aXBzLmVzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsT1J0nznqjtwlxLyY
-XZhkJAk8IbPMGbWOlI6H0fg3PqHILVikgDVboXVsHUUMH2Fjal5vmwpMwci4YSM1
-gf/+rHhwLWjhOgeYlQJU3c0jt4BT18g3RXIGJBK6E2Ehim51KODFDzT9NthFf+G4
-Nu+z4cYgjui0OLzhPvYR3oydAQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBACzzw3lY
-JN7GO9HgQmm47mSzPWIBubOE3yN93ZjPEKn+ANgilgUTB1RXxafey9m4iEL2mdsU
-dx+2/iU94aI+A6mB0i1sR/WWRowiq8jMDQ6XXotBtDvECgZAHd1G9AHduoIuPD14
-cJ58GNCr+Lh3B0Zx8coLY1xq+XKU1QFPoNtC
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIIODCCB6GgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCAR4xCzAJBgNVBAYTAkVT
-MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE
-ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE
-ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjE0MDIGA1UECxMr
-SVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTE0MDIG
-A1UEAxMrSVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
-eTEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMTAx
-OFoXDTI1MTIyNzAxMTAxOFowggEeMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFy
-Y2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5l
-dCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlw
-cy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxNDAyBgNVBAsTK0lQUyBDQSBUaW1lc3Rh
-bXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxNDAyBgNVBAMTK0lQUyBDQSBU
-aW1lc3RhbXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHjAcBgkqhkiG9w0B
-CQEWD2lwc0BtYWlsLmlwcy5lczCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
-vLjuVqWajOY2ycJioGaBjRrVetJznw6EZLqVtJCneK/K/lRhW86yIFcBrkSSQxA4
-Efdo/BdApWgnMjvEp+ZCccWZ73b/K5Uk9UmSGGjKALWkWi9uy9YbLA1UZ2t6KaFY
-q6JaANZbuxjC3/YeE1Z2m6Vo4pjOxgOKNNtMg0GmqaMCAwEAAaOCBIAwggR8MB0G
-A1UdDgQWBBSL0BBQCYHynQnVDmB4AyKiP8jKZjCCAVAGA1UdIwSCAUcwggFDgBSL
-0BBQCYHynQnVDmB4AyKiP8jKZqGCASakggEiMIIBHjELMAkGA1UEBhMCRVMxEjAQ
-BgNVBAgTCUJhcmNlbG9uYTESMBAGA1UEBxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJ
-UFMgSW50ZXJuZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJp
-cHNAbWFpbC5pcHMuZXMgQy5JLkYuICBCLTYwOTI5NDUyMTQwMgYDVQQLEytJUFMg
-Q0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTQwMgYDVQQD
-EytJUFMgQ0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4w
-HAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAM
-BgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYB
-BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIB
-FgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYD
-VR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlw
-cy5lczBHBglghkgBhvhCAQ0EOhY4VGltZXN0YW1waW5nIENBIENlcnRpZmljYXRl
-IGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgECBBwWGmh0
-dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMEAGCWCGSAGG+EIBBAQzFjFodHRwOi8v
-d3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMEUGCWCG
-SAGG+EIBAwQ4FjZodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25U
-aW1lc3RhbXBpbmcuaHRtbD8wQgYJYIZIAYb4QgEHBDUWM2h0dHA6Ly93d3cuaXBz
-LmVzL2lwczIwMDIvcmVuZXdhbFRpbWVzdGFtcGluZy5odG1sPzBABglghkgBhvhC
-AQgEMxYxaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lUaW1lc3RhbXBp
-bmcuaHRtbDB/BgNVHR8EeDB2MDegNaAzhjFodHRwOi8vd3d3Lmlwcy5lcy9pcHMy
-MDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMDugOaA3hjVodHRwOi8vd3d3YmFj
-ay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyVGltZXN0YW1waW5nLmNybDAvBggrBgEF
-BQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9vY3NwLmlwcy5lcy8wDQYJKoZI
-hvcNAQEFBQADgYEAZbrBzAAalZHK6Ww6vzoeFAh8+4Pua2JR0zORtWB5fgTYXXk3
-6MNbsMRnLWhasl8OCvrNPzpFoeo2zyYepxEoxZSPhExTCMWTs/zif/WN87GphV+I
-3pGW7hdbrqXqcGV4LCFkAZXOzkw+UPS2Wctjjba9GNSHSl/c7+lW8AoM6HU=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
-ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
-b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
-EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
-OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
-A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
-Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
-dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
-SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
-gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
-iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
-Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
-BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
-SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
-b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
-bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
-Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
-aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
-IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
-c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
-biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
-ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
-UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
-YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
-dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
-bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
-sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
-n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
-NitjrFgBazMpUIaD8QFI
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
-ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
-b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
-EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
-DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
-DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
-c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
-TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
-BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
-OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
-2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
-RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
-AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
-ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
-YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
-b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
-ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
-IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
-b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
-ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
-YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
-a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
-SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
-aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
-YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
-Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
-ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
-pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
-Fp1hBWeAyNDYpQcCNJgEjTME1A==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
-MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
-TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
-dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
-KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
-N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
-dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
-MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
-b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
-zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
-3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
-WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
-Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
-NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
-ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
-QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
-YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
-aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
-IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
-ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
-ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
-amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
-IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
-Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
-ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
-YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
-dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
-b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
-CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
-xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
-0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
-QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
-f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
-8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUx
-ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
-b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQD
-EzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVneXpvaSAoQ2xhc3MgUUEpIFRhbnVz
-aXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0bG9jay5odTAeFw0w
-MzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTERMA8G
-A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
-Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5l
-dExvY2sgTWlub3NpdGV0dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZh
-bnlraWFkbzEeMBwGCSqGSIb3DQEJARYPaW5mb0BuZXRsb2NrLmh1MIIBIjANBgkq
-hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRVCacbvWy5FPSKAtt2/Goq
-eKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e8ia6AFQe
-r7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO5
-3Lhbm+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWd
-vLrqOU+L73Sa58XQ0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0l
-mT+1fMptsK6ZmfoIYOcZwvK9UdPM0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4IC
-wDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8EBAMCAQYwggJ1Bglg
-hkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2YW55IGEgTmV0
-TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh
-biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQg
-ZWxla3Ryb25pa3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywg
-dmFsYW1pbnQgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6
-b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwgYXogQWx0YWxhbm9zIFN6ZXJ6b2Rl
-c2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kgZWxqYXJhcyBtZWd0
-ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczovL3d3
-dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0Bu
-ZXRsb2NrLm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBh
-bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRo
-ZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMgYXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3
-Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0IGluZm9AbmV0bG9jay5u
-ZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3DQEBBQUA
-A4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQ
-MznNwNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+
-NFAwLvt/MpqNPfMgW/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCR
-VCHnpgu0mfVRQdzNo0ci2ccBgcTcR08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY
-83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR5qq5aKrN9p2QdRLqOBrKROi3
-macqaJVmlaut74nLYKkGEsaUR+ko
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
-MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
-MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
-dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
-UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
-ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
-c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
-OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
-mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
-BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
-qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
-gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
-BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
-bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
-dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
-6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
-h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
-/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
-wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
-pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
-GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
-b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
-BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
-YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
-GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
-Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
-WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
-rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
-+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
-ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
-Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
-PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
-/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
-oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
-yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
-EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
-A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
-MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
-ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
-BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
-g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
-fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
-WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
-B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
-hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
-TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
-mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
-ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
-4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
-8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
-GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
-b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
-BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
-YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
-V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
-4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
-H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
-8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
-vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
-mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
-btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
-T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
-WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
-c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
-4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
-VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
-CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
-aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
-aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
-dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
-czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
-A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
-TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
-Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
-7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
-d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
-+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
-4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
-t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
-DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
-k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
-zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
-Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
-mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
-4SVhM7JZG+Ju1zdXtg2pEto=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
-TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
-aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
-aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
-MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
-IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
-dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
-li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
-rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
-WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
-F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
-xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
-Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
-dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
-ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
-IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
-c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
-ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
-Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
-KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
-KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
-y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
-dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
-VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
-MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
-fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
-7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
-cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
-mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
-xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
-SnQ2+Q==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
-IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
-BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
-aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
-9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
-NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
-azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
-YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
-Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
-cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
-cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
-2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
-JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
-Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
-n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
-PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICXDCCAcWgAwIBAgIQCgEBAQAAAnwAAAALAAAAAjANBgkqhkiG9w0BAQUFADA6
-MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
-dHkgMTAyNCBWMzAeFw0wMTAyMjIyMTAxNDlaFw0yNjAyMjIyMDAxNDlaMDoxGTAX
-BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAx
-MDI0IFYzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDV3f5mCc8kPD6ugU5O
-isRpgFtZO9+5TUzKtS3DJy08rwBCbbwoppbPf9dYrIMKo1W1exeQFYRMiu4mmdxY
-78c4pqqv0I5CyGLXq6yp+0p9v+r+Ek3d/yYtbzZUaMjShFbuklNhCbM/OZuoyZu9
-zp9+1BlqFikYvtc6adwlWzMaUQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBTEwBykB5T9zU0B1FTapQxf3q4FWjAd
-BgNVHQ4EFgQUxMAcpAeU/c1NAdRU2qUMX96uBVowDQYJKoZIhvcNAQEFBQADgYEA
-Py1q4yZDlX2Jl2X7deRyHUZXxGFraZ8SmyzVWujAovBDleMf6XbN3Ou8k6BlCsdN
-T1+nr6JGFLkM88y9am63nd4lQtBU/55oc2PcJOsiv6hy8l4A4Q1OOkNumU4/iXgD
-mMrzVcydro7BqkWY+o8aoI2II/EVQQ2lRj6RP4vr93E=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
-MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
-dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
-BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
-MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
-eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
-/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
-wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
-AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
-PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
-AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
-MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
-HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
-Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
-f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
-rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
-6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
-7CAFYd4=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
-MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
-GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
-MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
-Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
-iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
-/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
-jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
-HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
-sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
-gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
-MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
-KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
-AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
-URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
-H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
-I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
-iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
-f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
-MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
-FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
-MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
-cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
-Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
-0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
-wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
-7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
-8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
-BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
-/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
-JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
-NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
-6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
-3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
-D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
-CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
-3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
-MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
-dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
-WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
-VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
-DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
-9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
-DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
-Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
-QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
-xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
-A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
-AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
-kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
-Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
-Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
-JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
-RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
-MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx
-MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV
-BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG
-29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk
-oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk
-3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL
-qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN
-nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw
-DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG
-MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX
-ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H
-DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO
-TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv
-kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w
-zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
-MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
-MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
-BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
-Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
-5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
-3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
-vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
-8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
-DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
-MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
-zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
-3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
-FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
-Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
-ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
-TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
-dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
-MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
-ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
-ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
-9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
-hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
-tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
-BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
-SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
-OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
-cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
-7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
-/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
-eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
-u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
-7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
-iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
-MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
-U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
-NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
-ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
-ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
-DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
-8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
-+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
-X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
-K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
-1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
-A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
-zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
-YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
-bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
-DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
-L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
-eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
-xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
-VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
-WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
-MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
-Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
-MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
-U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
-cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
-A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
-pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
-OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
-Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
-Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
-HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
-Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
-+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
-Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
-Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
-26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
-AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
-FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
-ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
-LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
-BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
-Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
-dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
-cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
-YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
-dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
-bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
-YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
-TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
-9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
-jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
-FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
-ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
-ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
-EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
-L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
-yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
-O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
-um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
-NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFFjCCBH+gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsDELMAkGA1UEBhMCSUwx
-DzANBgNVBAgTBklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0
-Q29tIEx0ZC4xGjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBG
-cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS
-YWRtaW5Ac3RhcnRjb20ub3JnMB4XDTA1MDMxNzE3Mzc0OFoXDTM1MDMxMDE3Mzc0
-OFowgbAxCzAJBgNVBAYTAklMMQ8wDQYDVQQIEwZJc3JhZWwxDjAMBgNVBAcTBUVp
-bGF0MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMRowGAYDVQQLExFDQSBBdXRob3Jp
-dHkgRGVwLjEpMCcGA1UEAxMgRnJlZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
-dHkxITAfBgkqhkiG9w0BCQEWEmFkbWluQHN0YXJ0Y29tLm9yZzCBnzANBgkqhkiG
-9w0BAQEFAAOBjQAwgYkCgYEA7YRgACOeyEpRKSfeOqE5tWmrCbIvNP1h3D3TsM+x
-18LEwrHkllbEvqoUDufMOlDIOmKdw6OsWXuO7lUaHEe+o5c5s7XvIywI6Nivcy+5
-yYPo7QAPyHWlLzRMGOh2iCNJitu27Wjaw7ViKUylS7eYtAkUEKD4/mJ2IhULpNYI
-LzUCAwEAAaOCAjwwggI4MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgHmMB0G
-A1UdDgQWBBQcicOWzL3+MtUNjIExtpidjShkjTCB3QYDVR0jBIHVMIHSgBQcicOW
-zL3+MtUNjIExtpidjShkjaGBtqSBszCBsDELMAkGA1UEBhMCSUwxDzANBgNVBAgT
-BklzcmFlbDEOMAwGA1UEBxMFRWlsYXQxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4x
-GjAYBgNVBAsTEUNBIEF1dGhvcml0eSBEZXAuMSkwJwYDVQQDEyBGcmVlIFNTTCBD
-ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSYWRtaW5Ac3Rh
-cnRjb20ub3JnggEAMB0GA1UdEQQWMBSBEmFkbWluQHN0YXJ0Y29tLm9yZzAdBgNV
-HRIEFjAUgRJhZG1pbkBzdGFydGNvbS5vcmcwEQYJYIZIAYb4QgEBBAQDAgAHMC8G
-CWCGSAGG+EIBDQQiFiBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAy
-BglghkgBhvhCAQQEJRYjaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL2NhLWNybC5j
-cmwwKAYJYIZIAYb4QgECBBsWGWh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy8wOQYJ
-YIZIAYb4QgEIBCwWKmh0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9pbmRleC5waHA/
-YXBwPTExMTANBgkqhkiG9w0BAQQFAAOBgQBscSXhnjSRIe/bbL0BCFaPiNhBOlP1
-ct8nV0t2hPdopP7rPwl+KLhX6h/BquL/lp9JmeaylXOWxkjHXo0Hclb4g4+fd68p
-00UOpO6wNnQt8M2YI3s3S9r+UZjEHjQ8iP2ZO1CnwYszx8JSFhKVU2Ui77qLzmLb
-cCOxgN8aIDjnfg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
-MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
-YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
-Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
-AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
-Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
-BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
-m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
-FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
-TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
-EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
-kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
-HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
-vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
-19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
-L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
-bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
-JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
-FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
-BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
-K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
-ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
-Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
-sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
-3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
-ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
-mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
-b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
-rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
-hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
-zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
-MBr1mmz0DlP5OlvRHA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
-BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
-biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
-MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
-d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
-CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
-76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
-bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
-6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
-emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
-MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
-MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
-MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
-FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
-aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
-gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
-qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
-lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
-8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
-L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
-45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
-UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
-O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
-bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
-GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
-77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
-hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
-92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
-Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
-ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
-Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
-BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu
-IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw
-WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD
-ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD
-ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y
-IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn
-IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+
-6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob
-jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw
-izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl
-+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY
-zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP
-pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF
-KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW
-ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB
-AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
-BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0
-ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
-IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA
-A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0
-uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+
-FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7
-jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/
-u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D
-YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1
-puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa
-icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG
-DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x
-kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z
-Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
-BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
-IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
-RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
-U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
-MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
-Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
-YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
-nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
-6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
-eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
-c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
-MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
-HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
-jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
-5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
-rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
-F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
-wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
-cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
-AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
-WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
-xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
-2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
-IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
-aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
-em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
-dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
-OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
-hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
-tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
-MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
-YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
-PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
-Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
-AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
-IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
-gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
-yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
-F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
-jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
-ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
-VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
-YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
-EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
-Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
-DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
-MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
-UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
-TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
-qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
-ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
-JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
-hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
-EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
-nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
-udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
-ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
-LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
-pYYsfPQS
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDXDCCAsWgAwIBAgICA+owDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF
-MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU
-QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI
-MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAyIENBMSkwJwYJKoZIhvcN
-AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla
-Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy
-ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y
-IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1
-c3RDZW50ZXIgQ2xhc3MgMiBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA
-dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANo46O0y
-AClxgwENv4wB3NrGrTmkqYov1YtcaF9QxmL1Zr3KkSLsqh1R1z2zUbKDTl3LSbDw
-TFXlay3HhQswHJJOgtTKAu33b77c4OMUuAVT8pr0VotanoWT0bSCVq5Nu6hLVxa8
-/vhYnvgpjbB7zXjJT6yLZwzxnPv8V5tXXE8NAgMBAAGjazBpMA8GA1UdEwEB/wQF
-MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3
-LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G
-CSqGSIb3DQEBBAUAA4GBAIRS+yjf/x91AbwBvgRWl2p0QiQxg/lGsQaKic+WLDO/
-jLVfenKhhQbOhvgFjuj5Jcrag4wGrOs2bYWRNAQ29ELw+HkuCkhcq8xRT3h2oNms
-Gb0q0WkEKJHKNhAngFdb0lz1wlurZIFjdFH0l7/NEij3TWZ/p/AcASZ4smZHcFFk
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDXDCCAsWgAwIBAgICA+swDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF
-MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU
-QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI
-MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAzIENBMSkwJwYJKoZIhvcN
-AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla
-Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy
-ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y
-IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1
-c3RDZW50ZXIgQ2xhc3MgMyBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA
-dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALa0wTUF
-Lg2N7KBAahwOJ6ZQkmtQGwfeLud2zODa/ISoXoxjaitN2U4CdhHBC/KNecoAtvGw
-Dtf7pBc9r6tpepYnv68zoZoqWarEtTcI8hKlMbZD9TKWcSgoq40oht+77uMMfTDW
-w1Krj10nnGvAo+cFa1dJRLNu6mTP0o56UHd3AgMBAAGjazBpMA8GA1UdEwEB/wQF
-MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3
-LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G
-CSqGSIb3DQEBBAUAA4GBABY9xs3Bu4VxhUafPiCPUSiZ7C1FIWMjWwS7TJC4iJIE
-Tb19AaM/9uzO8d7+feXhPrvGq14L3T2WxMup1Pkm5gZOngylerpuw3yCGdHHsbHD
-2w2Om0B8NwvxXej9H5CIpQ5ON2QhqE6NtJ/x3kit1VYYUimLRzQSCdS7kjXvD9s0
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE
-SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg
-Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV
-BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl
-cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA
-vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu
-Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a
-0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1
-4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN
-eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD
-R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG
-A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu
-dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME
-Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3
-WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw
-HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ
-KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO
-Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX
-wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
-2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89
-9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0
-jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38
-aQNiuJkFBT1reBK9sG9l
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIFGTCCBAGgAwIBAgIEPki9xDANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJE
-SzEMMAoGA1UEChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTAeFw0wMzAyMTEw
-ODM5MzBaFw0zNzAyMTEwOTA5MzBaMDExCzAJBgNVBAYTAkRLMQwwCgYDVQQKEwNU
-REMxFDASBgNVBAMTC1REQyBPQ0VTIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
-MIIBCgKCAQEArGL2YSCyz8DGhdfjeebM7fI5kqSXLmSjhFuHnEz9pPPEXyG9VhDr
-2y5h7JNp46PMvZnDBfwGuMo2HP6QjklMxFaaL1a8z3sM8W9Hpg1DTeLpHTk0zY0s
-2RKY+ePhwUp8hjjEqcRhiNJerxomTdXkoCJHhNlktxmW/OwZ5LKXJk5KTMuPJItU
-GBxIYXvViGjaXbXqzRowwYCDdlCqT9HU3Tjw7xb04QxQBr/q+3pJoSgrHPb8FTKj
-dGqPqcNiKXEx5TukYBdedObaE+3pHx8b0bJoc8YQNHVGEBDjkAB2QMuLt0MJIf+r
-TpPGWOmlgtt3xDqZsXKVSQTwtyv6e1mO3QIDAQABo4ICNzCCAjMwDwYDVR0TAQH/
-BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgewGA1UdIASB5DCB4TCB3gYIKoFQgSkB
-AQEwgdEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2VydGlmaWthdC5kay9yZXBv
-c2l0b3J5MIGdBggrBgEFBQcCAjCBkDAKFgNUREMwAwIBARqBgUNlcnRpZmlrYXRl
-ciBmcmEgZGVubmUgQ0EgdWRzdGVkZXMgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEu
-MS4xLiBDZXJ0aWZpY2F0ZXMgZnJvbSB0aGlzIENBIGFyZSBpc3N1ZWQgdW5kZXIg
-T0lEIDEuMi4yMDguMTY5LjEuMS4xLjARBglghkgBhvhCAQEEBAMCAAcwgYEGA1Ud
-HwR6MHgwSKBGoESkQjBAMQswCQYDVQQGEwJESzEMMAoGA1UEChMDVERDMRQwEgYD
-VQQDEwtUREMgT0NFUyBDQTENMAsGA1UEAxMEQ1JMMTAsoCqgKIYmaHR0cDovL2Ny
-bC5vY2VzLmNlcnRpZmlrYXQuZGsvb2Nlcy5jcmwwKwYDVR0QBCQwIoAPMjAwMzAy
-MTEwODM5MzBagQ8yMDM3MDIxMTA5MDkzMFowHwYDVR0jBBgwFoAUYLWF7FZkfhIZ
-J2cdUBVLc647+RIwHQYDVR0OBBYEFGC1hexWZH4SGSdnHVAVS3OuO/kSMB0GCSqG
-SIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEACrom
-JkbTc6gJ82sLMJn9iuFXehHTuJTXCRBuo7E4A9G28kNBKWKnctj7fAXmMXAnVBhO
-inxO5dHKjHiIzxvTkIvmI/gLDjNDfZziChmPyQE+dF10yYscA+UYyAFMP8uXBV2Y
-caaYb7Z8vTd/vuGTJW1v8AqtFxjhA7wHKcitJuj4YfD9IQl+mo6paH1IYnK9AOoB
-mbgGglGBTvH1tJFUuSN6AJqfXY3gPGS5GhKSKseCRHI53OI8xthV9RVOyAUO28bQ
-YqbsFbS1AoLbrIyigfCbmTH1ICCoiGEKB5+U/NDXG8wuF/MEJ3Zn61SD/aSQfgY9
-BKNDLdr8C2LqL19iUw==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
-VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
-ZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFBlcnNvbmFsIEJhc2lj
-IENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNpY0B0aGF3dGUuY29tMB4X
-DTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgcsxCzAJBgNVBAYTAlpBMRUw
-EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE
-ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy
-dmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBD
-QTEoMCYGCSqGSIb3DQEJARYZcGVyc29uYWwtYmFzaWNAdGhhd3RlLmNvbTCBnzAN
-BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLyTU23AUE+CFeZIlDWmWr5vQvoPR+53
-dXLdjUmbllegeNTKP1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJl+A1OFdK
-wPQIcOk8RHtQfmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7
-G1sY0b8jkyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQF
-AAOBgQAt4plrsD16iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7
-c8a914phXAPjLSeoF+CEhULcXpvGt7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95B21P
-9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN92NWod8isQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
-VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
-ZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVt
-YWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUu
-Y29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNVBAYT
-AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEa
-MBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp
-b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBG
-cmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhh
-d3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfY
-DFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5E
-rHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVq
-uzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zAN
-BgkqhkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP
-MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa
-/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr5PjRznei
-gQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD
-VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT
-ZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMaVGhhd3RlIFBlcnNvbmFsIFByZW1p
-dW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXByZW1pdW1AdGhhd3RlLmNv
-bTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5NTlaMIHPMQswCQYDVQQGEwJa
-QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xGjAY
-BgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9u
-IFNlcnZpY2VzIERpdmlzaW9uMSMwIQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJl
-bWl1bSBDQTEqMCgGCSqGSIb3DQEJARYbcGVyc29uYWwtcHJlbWl1bUB0aGF3dGUu
-Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJZtn4B0TPuYwu8KHvE0Vs
-Bd/eJxZRNkERbGw77f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ8/JE2dWI
-Et12TfIa/G8jHnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYD
-ZicRFTuqW/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
-SIb3DQEBBAUAA4GBAGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIH
-b4Vnjt4rueIXsDqg8A6iAJrf8xQVbrvIhVqYgPn/vnQdPfP+MCXRNzRn+qVxeTBh
-KXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+WjS9Q2wfD6h+rM+D1KzGJ
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
-VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
-biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
-dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
-MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
-MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
-A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
-b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
-cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
-bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
-VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
-ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
-uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
-9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
-hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
-pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
-qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
-Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
-MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
-BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
-NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
-LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
-A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
-IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
-W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
-3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
-6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
-Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
-NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
-MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
-r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
-DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
-YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
-xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
-/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
-LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
-jVaMaA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
-VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
-biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
-MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
-MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
-DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
-dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
-cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
-DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
-gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
-yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
-L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
-EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
-7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
-QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
-qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx
-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN
-BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd
-BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN
-MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g
-Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG
-A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l
-c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT
-6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa
-Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL
-8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
-Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC
-9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ
-pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ
-CayJSdM=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc
-UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
-c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg
-MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
-dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz
-MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy
-dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD
-VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg
-xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu
-xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7
-XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k
-heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J
-YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C
-urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1
-JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51
-b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV
-9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7
-kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh
-fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
-B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA
-aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS
-RGQDJereW26fyfJOrN3H
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc
-UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
-c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS
-S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
-SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3
-WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv
-bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU
-UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw
-bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe
-LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef
-J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh
-R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ
-Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX
-JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p
-zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S
-Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
-KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq
-ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
-Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz
-gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH
-uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS
-y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
-kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
-Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
-dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
-IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
-EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
-VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
-dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
-BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
-E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
-D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
-4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
-lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
-bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
-o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
-MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
-LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
-BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
-AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
-Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
-j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
-KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
-2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
-mfnGV/TJVTl4uix5yaaIK/QI
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB
-rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
-Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
-dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt
-Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa
-Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV
-BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
-dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE
-AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B
-YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9
-hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l
-L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm
-SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM
-1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws
-6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
-DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw
-Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50
-aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
-AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u
-7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0
-xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ
-rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim
-eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk
-USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
-lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
-Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
-dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
-SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
-A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
-MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
-d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
-cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
-0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
-M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
-MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
-oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
-DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
-oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
-VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
-dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
-bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
-BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
-//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
-CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
-CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
-3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
-KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEZDCCA0ygAwIBAgIQRL4Mi1AAJLQR0zYwS8AzdzANBgkqhkiG9w0BAQUFADCB
-ozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
-Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
-dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3Qt
-TmV0d29yayBBcHBsaWNhdGlvbnMwHhcNOTkwNzA5MTg0ODM5WhcNMTkwNzA5MTg1
-NzQ5WjCBozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0
-IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYD
-VQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VS
-Rmlyc3QtTmV0d29yayBBcHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB
-DwAwggEKAoIBAQCz+5Gh5DZVhawGNFugmliy+LUPBXeDrjKxdpJo7CNKyXY/45y2
-N3kDuatpjQclthln5LAbGHNhSuh+zdMvZOOmfAz6F4CjDUeJT1FxL+78P/m4FoCH
-iZMlIJpDgmkkdihZNaEdwH+DBmQWICzTSaSFtMBhf1EI+GgVkYDLpdXuOzr0hARe
-YFmnjDRy7rh4xdE7EkpvfmUnuaRVxblvQ6TFHSyZwFKkeEwVs0CYCGtDxgGwenv1
-axwiP8vv/6jQOkt2FZ7S0cYu49tXGzKiuG/ohqY/cKvlcJKrRB5AUPuco2LkbG6g
-yN7igEL66S/ozjIEj3yNtxyjNTwV3Z7DrpelAgMBAAGjgZEwgY4wCwYDVR0PBAQD
-AgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPqGydvguul49Uuo1hXf8NPh
-ahQ8ME8GA1UdHwRIMEYwRKBCoECGPmh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9V
-VE4tVVNFUkZpcnN0LU5ldHdvcmtBcHBsaWNhdGlvbnMuY3JsMA0GCSqGSIb3DQEB
-BQUAA4IBAQCk8yXM0dSRgyLQzDKrm5ZONJFUICU0YV8qAhXhi6r/fWRRzwr/vH3Y
-IWp4yy9Rb/hCHTO967V7lMPDqaAt39EpHx3+jz+7qEUqf9FuVSTiuwL7MT++6Lzs
-QCv4AdRWOOTKRIK1YSAhZ2X28AvnNPilwpyjXEAfhZOVBt5P1CeptqX8Fs1zMT+4
-ZSfP1FMa8Kxun08FDAOBp4QpxFq9ZFdyrTvPNximmMatBrTcCKME1SmklpoSZ0qM
-YEWd8SOasACcaLWYUNPvji6SZbFIPiG+FTAqDbUMo2s/rn9X9R+WfN9v3YIwLGUb
-QErNaLly7HF27FSOH4UMAWr6pjisH8SE
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
-IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
-BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
-aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
-9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
-NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
-azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
-YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
-Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
-cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
-LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
-TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
-TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
-LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
-I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
-nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
-IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
-BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
-aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
-9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
-NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
-azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
-YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
-Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
-cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
-dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
-WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
-v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
-UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
-IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
-W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ
-BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh
-c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05
-NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD
-VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp
-bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB
-jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N
-H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR
-4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN
-BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo
-EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5
-FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx
-lA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
-BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
-c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
-MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
-emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
-DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
-FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg
-UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
-YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
-MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
-AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK
-VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm
-Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID
-AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J
-h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul
-uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68
-DzFc6PLZ
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
-CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
-cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
-LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
-aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
-dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
-VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
-aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
-bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
-IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
-LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
-nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
-8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
-ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
-PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
-6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
-n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
-qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
-wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
-ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
-pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
-E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkG
-A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
-cyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
-MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
-BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmlt
-YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
-ADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyh
-YGt+eSz6Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7
-FYCTXOvnzAhsPz6zSvz/S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0G
-CSqGSIb3DQEBAgUAA4GBAIobK/o5wXTXXtgZZKJYSi034DNHD6zt96rbHuSLBlxg
-J8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUYYAS/QoD90KioHgE796Nc
-r6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2lw0Xd8rY
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
-CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
-YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
-MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
-aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
-Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
-MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
-IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
-KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
-eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
-HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
-DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
-AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
-nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
-rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
-jBJ7xUS0rg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
-BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
-aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
-IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
-Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
-eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
-BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
-Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
-Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
-Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
-IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
-J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
-JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
-wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
-koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
-qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
-Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
-xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
-7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
-sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
-sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
-cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
-A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
-cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
-MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
-BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
-YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
-ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
-BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
-I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
-CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
-lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
-AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
-BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
-c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
-MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
-emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
-DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
-FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
-UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
-YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
-MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
-AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
-pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
-13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
-AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
-U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
-F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
-oJ2daZH9
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
-CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
-cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
-LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
-aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
-dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
-VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
-aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
-bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
-IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
-LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
-N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
-KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
-kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
-CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
-Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
-imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
-2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
-DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
-/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
-F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
-TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
-yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
-ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
-U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
-ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
-aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
-MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
-ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
-biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
-U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
-aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
-nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
-t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
-SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
-BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
-rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
-NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
-BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
-BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
-aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
-MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
-p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
-5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
-WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
-4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
-hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
-BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
-c3MgNCBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
-MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
-emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
-DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
-FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMg
-UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
-YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
-MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
-AQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4xBewRNzjMHPVKmIquNDM
-HO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDHqGKB3FtK
-qsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwID
-AQABMA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwj
-cSGIL4LcY/oCRaxFWdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0y
-cyfYaT5DdPauxYma51N86Xv2S/PBZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRP
-T8qAkbYp
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
-CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
-cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
-LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
-aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
-dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
-VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
-aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
-bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
-IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
-LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
-GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
-+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
-U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
-NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
-ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
-ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
-CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
-g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
-fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
-2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
-bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG
-A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
-VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0
-MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV
-BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy
-dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ
-ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII
-0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI
-uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI
-hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3
-YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc
-1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDzTCCAzagAwIBAgIQU2GyYK7bcY6nlLMTM/QHCTANBgkqhkiG9w0BAQUFADCB
-wTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTwwOgYDVQQL
-EzNDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
-IC0gRzIxOjA4BgNVBAsTMShjKSAxOTk4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1
-dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
-cmswHhcNMDAwOTI2MDAwMDAwWhcNMTAwOTI1MjM1OTU5WjCBpTEXMBUGA1UEChMO
-VmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsx
-OzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5j
-b20vcnBhIChjKTAwMSwwKgYDVQQDEyNWZXJpU2lnbiBUaW1lIFN0YW1waW5nIEF1
-dGhvcml0eSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0hmdZ8IAIVli
-zrQJIkRpivglWtvtDbc2fk7gu5Q+kCWHwmFHKdm9VLhjzCx9abQzNvQ3B5rB3UBU
-/OB4naCTuQk9I1F/RMIUdNsKvsvJMDRAmD7Q1yUQgZS9B0+c1lQn3y6ov8uQjI11
-S7zi6ESHzeZBCiVu6PQkAsVSD27smHUCAwEAAaOB3zCB3DAPBgNVHRMECDAGAQH/
-AgEAMEUGA1UdIAQ+MDwwOgYMYIZIAYb4RQEHFwEDMCowKAYIKwYBBQUHAgEWHGh0
-dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9ycGEwMQYDVR0fBCowKDAmoCSgIoYgaHR0
-cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwCwYDVR0PBAQDAgEGMEIGCCsG
-AQUFBwEBBDYwNDAyBggrBgEFBQcwAaYmFiRodHRwOi8vb2NzcC52ZXJpc2lnbi5j
-b20vb2NzcC9zdGF0dXMwDQYJKoZIhvcNAQEFBQADgYEAgnBold+2DcIBcBlK0lRW
-HqzyRUyHuPU163hLBanInTsZIS5wNEqi9YngFXVF5yg3ADQnKeg3S/LvRJdrF1Ea
-w1adPBqK9kpGRjeM+sv1ZFo4aC4cw+9wzrhGBha/937ntag+RaypJXUie28/sJyU
-58dzq6wf7iWbwBbtt8pb8BQ=
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
-MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
-cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
-bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
-CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
-dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
-cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
-2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
-lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
-ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
-299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
-vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
-dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
-AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
-AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
-zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
-LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
-7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
-++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+
+ValiCert Class 1 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
+MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
+GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
+DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
+lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
+icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
+Orf1LXLI
+-----END CERTIFICATE-----
+
+ValiCert Class 2 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
+CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
+ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
+SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
+UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
+W9ViH0Pd
+-----END CERTIFICATE-----
+
+RSA Root Certificate 1
+======================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td
+3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H
+BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs
+3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF
+V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r
+on+jjBXu
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1
+EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc
+cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw
+EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj
+055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f
+j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0
+xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa
+t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+
+Verisign Class 4 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS
+tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM
+8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW
+Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX
+Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt
+mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd
+RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG
+UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+
+Entrust.net Secure Server CA
+============================
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
+ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
+A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
+eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
+dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
+aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
+gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
+ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
+CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
+dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
+NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
+HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
+BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
+Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
+n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+
+Entrust.net Premium 2048 Secure Server CA
+=========================================
+-----BEGIN CERTIFICATE-----
+MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
+NzUwNTFaFw0xOTEyMjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo3QwcjARBglghkgBhvhC
+AQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGAvtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdER
+gL7YibkIozH5oSQJFrlwMB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0B
+AQUFAAOCAQEAWUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
+oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQh7A6tcOdBTcS
+o8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18f3v/rxzP5tsHrV7bhZ3QKw0z
+2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfNB/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjX
+OP/swNlQ8C5LWK5Gb9Auw2DaclVyvUxFnmG6v4SBkgPR0ml8xQ==
+-----END CERTIFICATE-----
+
+Baltimore CyberTrust Root
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
+ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
+ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
+SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
+dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
+uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
+UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
+G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
+XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
+l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
+VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
+cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
+hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
+Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
+RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+Equifax Secure Global eBusiness CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp
+bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx
+HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds
+b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV
+PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN
+qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn
+hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
+BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs
+MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN
+I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY
+NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 1
+=============================
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB
+LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE
+ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz
+IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ
+1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a
+IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk
+MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW
+Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF
+AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5
+lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+
+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 2
+=============================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEXMBUGA1UE
+ChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0y
+MB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
+DkVxdWlmYXggU2VjdXJlMSYwJAYDVQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn
+2Z0GvxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/BPO3QSQ5
+BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0CAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUx
+JjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTkwNjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9e
+uSBIplBqy/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAAyGgq3oThr1
+jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia
+78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUm
+V+GRMOrN
+-----END CERTIFICATE-----
+
+AddTrust Low-Value Services Root
+================================
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU
+cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw
+CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO
+ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6
+54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr
+oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1
+Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui
+GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w
+HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT
+RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw
+HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt
+ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph
+iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr
+mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj
+ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+
+AddTrust External Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD
+VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw
+NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU
+cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg
+Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821
++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw
+Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo
+aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy
+2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7
+7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk
+VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl
+j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355
+e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u
+G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+
+AddTrust Public Services Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU
+cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ
+BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l
+dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu
+nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i
+d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG
+Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw
+HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G
+A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G
+A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4
+JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL
++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9
+Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H
+EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+
+AddTrust Qualified Certificates Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU
+cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx
+CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ
+IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx
+64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3
+KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o
+L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR
+wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU
+MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE
+BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y
+azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG
+GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze
+RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB
+iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=
+-----END CERTIFICATE-----
+
+Entrust Root Certification Authority
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
+BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
+b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
+A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
+MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
+MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
+Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
+dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
+A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
+Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
+j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
+rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
+MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
+hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
+Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
+v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
+W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
+tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+RSA Security 2048 v3
+====================
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK
+ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy
+MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb
+BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7
+Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb
+WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH
+KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP
++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/
+MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E
+FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY
+v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj
+0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj
+VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395
+nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA
+pKnXwiJPZ9d37CAFYd4=
+-----END CERTIFICATE-----
+
+GeoTrust Global CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw
+MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo
+BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet
+8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc
+T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU
+vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk
+DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q
+zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4
+d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2
+mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p
+XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm
+Mw==
+-----END CERTIFICATE-----
+
+GeoTrust Global CA 2
+====================
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw
+MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/
+NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k
+LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA
+Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b
+HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH
+K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7
+srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh
+ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL
+OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC
+x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF
+H4z1Ir+rzoPz4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA
+=====================
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1
+MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu
+Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t
+JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e
+RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs
+7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d
+8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V
+qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga
+Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB
+Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu
+KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08
+ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0
+XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB
+hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2
+qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL
+oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK
+xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF
+KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2
+DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK
+xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU
+p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI
+P/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA 2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0
+MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg
+SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0
+DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17
+j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q
+JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a
+QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2
+WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP
+20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn
+ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC
+SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG
+8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2
++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E
+BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ
+4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+
+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq
+A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg
+Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP
+pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d
+FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp
+gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm
+X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 1
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG
+v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z
+DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh
+sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP
+8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z
+o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf
+GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF
+VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft
+3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g
+Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en
+fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8
+f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO
+qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN
+RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0
+gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn
+6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid
+FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6
+Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj
+B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op
+aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY
+T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p
++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg
+JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy
+zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO
+ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh
+1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf
+GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff
+Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP
+cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk=
+-----END CERTIFICATE-----
+
+Visa eCommerce Root
+===================
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG
+EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug
+QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2
+WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm
+VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL
+F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b
+RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0
+TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI
+/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs
+GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
+MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc
+CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW
+YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz
+zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu
+YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----
+
+Certum Root CA
+==============
-----BEGIN CERTIFICATE-----
-MIIDgDCCAmigAwIBAgICAx4wDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCVVMx
-DTALBgNVBAoTBFZJU0ExLzAtBgNVBAsTJlZpc2EgSW50ZXJuYXRpb25hbCBTZXJ2
-aWNlIEFzc29jaWF0aW9uMRIwEAYDVQQDEwlHUCBSb290IDIwHhcNMDAwODE2MjI1
-MTAwWhcNMjAwODE1MjM1OTAwWjBhMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklT
-QTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRp
-b24xEjAQBgNVBAMTCUdQIFJvb3QgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
-AQoCggEBAKkBcLWqxEDwq2omYXkZAPy/mzdZDK9vZBv42pWUJGkzEXDK41Z0ohdX
-ZFwgBuHW73G3O/erwWnQSaSxBNf0V2KJXLB1LRckaeNCYOTudNargFbYiCjh+20i
-/SN8RnNPflRzHqgsVVh1t0zzWkWlAhr62p3DRcMiXvOL8WAp0sdftAw6UYPvMPjU
-58fy+pmjIlC++QU3o63tmsPm7IgbthknGziLgE3sucfFicv8GjLtI/C1AVj59o/g
-halMCXI5Etuz9c9OYmTaxhkVOmMd6RdVoUwiPDQyRvhlV7or7zaMavrZ2UT0qt2E
-1w0cslSsMoW0ZA3eQbuxNMYBhjJk1Z8CAwEAAaNCMEAwHQYDVR0OBBYEFJ59SzS/
-ca3CBfYDdYDOqU8axCRMMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
-MA0GCSqGSIb3DQEBBQUAA4IBAQAhpXYUVfmtJ3CPPPTVbMjMCqujmAuKBiPFyWHb
-mQdpNSYx/scuhMKZYdQN6X0uEyt8joW2hcdLzzW2LEc9zikv2G+fiRxkk78IvXbQ
-kIqUs38oW26sTTMs7WXcFsziza6kPWKSBpUmv9+55CCmc2rBvveURNZNbyoLaxhN
-dBA2aGpawWqn3TYpjLgwi08hPwAuVDAHOrqK5MOeyti12HvOdUVmB/RtLdh6yumJ
-ivIj2C/LbgA2T/vwLwHMD8AiZfSr4k5hLQOCfZEWtTDVFN5ex5D8ofyrEK9ca3Cn
-B+8phuiyJccg/ybdd+95RBTEvd07xQObdyPsoOy7Wjm1zK0G
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
+ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
+Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
+by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
+wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
+kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
+89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
+Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
+NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
+GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
+GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
+0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
+qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----
+
+Comodo AAA Services root
+========================
-----BEGIN CERTIFICATE-----
-MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
-VVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBD
-ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9v
-dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDAxMDExMTY0MTI4WhcNMjEwMTE0
-MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSww
-KgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0G
-A1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
-MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n13
-5zHCLielTWi5MbqNQ1mXx3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHE
-SxP9cMIlrCL1dQu3U+SlK93OvRw6esP3E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4O
-JgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5OEL8pahbSCOz6+MlsoCu
-ltQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4jsNtlAHCE
-AQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMB
-AAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcB
-CzAyMDAGCCsGAQUFBwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRw
-b2xpY3kwDQYJKoZIhvcNAQEFBQADggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo
-7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrvm+0fazbuSCUlFLZWohDo7qd/
-0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0ROhPs7fpvcmR7
-nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
-x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ
-33ZwmVxwQ023tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
+MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
+c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
+BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
+C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
+i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
+Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
+Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
+Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
+cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
+LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
+7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
+8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
+12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
+
+Comodo Secure Services root
+===========================
-----BEGIN CERTIFICATE-----
-MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
-IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
-cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
-dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
-MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
-bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
-DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
-ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
-WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
-Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
-HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
-z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
-SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
-AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
-KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
-AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
-BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
-VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
-ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
-Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
-ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
-/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
-A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
-k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
-iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
-2G0xffX8oRAHh84vWdw+WNs=
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw
+MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu
+Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi
+BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP
+9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc
+rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC
+oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V
+p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E
+FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj
+YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm
+aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm
+4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL
+DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw
+pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H
+RR3B7Hzs/Sk=
-----END CERTIFICATE-----
+
+Comodo Trusted Services root
+============================
-----BEGIN CERTIFICATE-----
-MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
-gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
-MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
-UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
-NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
-dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
-dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
-dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
-38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
-KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
-DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
-qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
-JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
-PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
-BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
-jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
-eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
-ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
-vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
-qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
-IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
-i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
-O+7ETPTsJ3xCwnR8gooJybQDJbw=
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw
+MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h
+bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw
+IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7
+3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y
+/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6
+juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS
+ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud
+DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp
+ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl
+cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw
+uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA
+BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l
+R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O
+9y5Xt5hwXsjEeLBi
-----END CERTIFICATE-----
+
+QuoVadis Root CA
+================
-----BEGIN CERTIFICATE-----
-MIIETzCCAzegAwIBAgIEO63vKTANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDEwOTIzMTQxODE3WhcNMTEw
-OTIzMTMxODE3WjB1MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
-LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MR8wHQYDVQQDExZDQyBTaWdu
-ZXQgLSBDQSBLbGFzYSAxMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4SRW9Q58g5DY1Hw7h
-gCRKBEdPdGn0MFHsfw7rlu/oQm7IChI/uWd9q5wwo77YojtTDjRnpgZsjqBeynX8T90vFILqsY2K
-5CF1OESalwvVr3sZiQX79lisuFKat92u6hBFikFIVxfHHB67Af+g7u0dEHdDW7lwy81MwFYxBTRy
-9wIDAQABo4IBbTCCAWkwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwggEEBgNVHSAE
-gfwwgfkwgfYGDSsGAQQBvj8CAQoBAQAwgeQwgZoGCCsGAQUFBwICMIGNGoGKQ2VydHlmaWthdCB3
-eXN0YXdpb255IHpnb2RuaWUgeiBkb2t1bWVudGVtOiAiUG9saXR5a2EgQ2VydHlmaWthY2ppIGRs
-YSBSb290Q0EiLiBDZXJ0eWZpa2F0IHd5c3Rhd2lvbnkgcHJ6ZXogUm9vdENBIHcgaGllcmFyY2hp
-aSBDQyBTaWduZXQuMEUGCCsGAQUFBwIBFjlodHRwOi8vd3d3LnNpZ25ldC5wbC9yZXBvenl0b3Jp
-dW0vZG9rdW1lbnR5L3BjX3Jvb3RjYS50eHQwHwYDVR0jBBgwFoAUwJvFIw0C4aZOSGsfAOnjmhQb
-sa8wHQYDVR0OBBYEFMODHtVZd1T7TftXR/nEI1zR54njMA0GCSqGSIb3DQEBBQUAA4IBAQBRIHQB
-FIGh8Jpxt87AgSLwIEEk4+oGy769u3NtoaR0R3WNMdmt7fXTi0tyTQ9V4AIszxVjhnUPaKnF1KYy
-f8Tl+YTzk9ZfFkZ3kCdSaILZAOIrmqWNLPmjUQ5/JiMGho0e1YmWUcMci84+pIisTsytFzVP32/W
-+sz2H4FQAvOIMmxB7EJX9AdbnXn9EXZ+4nCqi0ft5z96ZqOJJiCB3vSaoYg+wdkcvb6souMJzuc2
-uptXtR1Xf3ihlHaGW+hmnpcwFA6AoNrom6Vgzk6U1ienx0Cw28BhRSKqzKkyXkuK8gRflZUx84uf
-tXncwKJrMiE3lvgOOBITRzcahirLer4c
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
+ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
+MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
+cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
+EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
+J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
+F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
+YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
+AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
+PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
+ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
+MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
+YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
+ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
+Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu
+BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
+FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
+tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
+fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
+LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
+gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
+5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
+5nrQNiOKSnQ2+Q==
-----END CERTIFICATE-----
+
+QuoVadis Root CA 2
+==================
-----BEGIN CERTIFICATE-----
-MIIE9zCCA9+gAwIBAgIEPL/xoTANBgkqhkiG9w0BAQUFADB2MQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MSAwHgYDVQQDExdDQyBTaWduZXQgLSBQQ0EgS2xhc2EgMjAeFw0wMjA0MTkxMDI5NTNa
-Fw0xNzA0MTgxMjUzMDdaMHUxCzAJBgNVBAYTAlBMMR8wHQYDVQQKExZUUCBJbnRlcm5ldCBTcC4g
-eiBvLm8uMSQwIgYDVQQLExtDZW50cnVtIENlcnR5ZmlrYWNqaSBTaWduZXQxHzAdBgNVBAMTFkND
-IFNpZ25ldCAtIENBIEtsYXNhIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqgLJu
-QqY4yavbSgHg8CyfKTx4BokNSDOVz4eD9vptUr11Kqd06ED1hlH7Sg0goBFAfntNU/QTKwSBaNui
-me7C4sSEdgsKrPoAhGb4Mq8y7Ty7RqZz7mkzNMqzL2L2U4yQ2QjvpH8MH0IBqOWEcpSkpwnrCDIm
-RoTfd+YlZWKi2JceQixUUYIQ45Ox8+x8hHbvvZdgqtcvo8PW27qoHkp/7hMuJ44kDAGrmxffBXl/
-OBRZp0uO1CSLcMcVJzyr2phKhy406MYdWrtNPEluGs0GFDzd0nrIctiWAO4cmct4S72S9Q6e//0G
-O9f3/Ca5Kb2I1xYLj/xE+HgjHX9aD2MhAgMBAAGjggGMMIIBiDAPBgNVHRMBAf8EBTADAQH/MA4G
-A1UdDwEB/wQEAwIBBjCB4wYDVR0gBIHbMIHYMIHVBg0rBgEEAb4/AhQKAQEAMIHDMHUGCCsGAQUF
-BwICMGkaZ0NlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
-eWthIENlcnR5ZmlrYWNqaSBQQ0EyIC0gQ2VydHlmaWthdHkgVXJ6ZWRvdyBLbGFzeSAyIi4wSgYI
-KwYBBQUHAgEWPmh0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9kb2t1bWVudHkva2xh
-c2EyL3BjX3BjYTIudHh0MD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly93d3cuc2lnbmV0LnBsL3Jl
-cG96eXRvcml1bS9jcmwvcGNhMi5jcmwwHwYDVR0jBBgwFoAUwGxGyl2CfpYHRonE82AVXO08kMIw
-HQYDVR0OBBYEFLtFBlILy4HNKVSzvHxBTM0HDowlMA0GCSqGSIb3DQEBBQUAA4IBAQBWTsCbqXrX
-hBBev5v5cIuc6gJM8ww7oR0uMQRZoFSqvQUPWBYM2/TLI/f8UM9hSShUVj3zEsSj/vFHagUVmzuV
-Xo5u0WK8iaqATSyEVBhADHrPG6wYcLKJlagge/ILA0m+SieyP2sjYD9MUB9KZIEyBKv0429UuDTw
-6P7pslxMWJBSNyQxaLIs0SRKsqZZWkc7ZYAj2apSkBMX2Is1oHA+PwkF6jQMwCao/+CndXPUzfCF
-6caa9WwW31W26MlXCvSmJgfiTPwGvm4PkPmOnmWZ3CczzhHl4q7ztHFzshJH3sZWDnrWwBFjzz5e
-Pr3WHV1wA7EY6oT4zBx+2gT9XBTB
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
+ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
+XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
+lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
+lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
+lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
+66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
+wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
+D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
+BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
+J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
+DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
+a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
+Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
+UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
+VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
+IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
+WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
+f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
+4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
+VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----
+
+QuoVadis Root CA 3
+==================
-----BEGIN CERTIFICATE-----
-MIIEUzCCAzugAwIBAgIEPq+qjzANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJQTDE3MDUGA1UE
-ChMuQ1ppQyBDZW50cmFzdCBTQSB3IGltaWVuaXUgTWluaXN0cmEgR29zcG9kYXJraTEZMBcGA1UE
-AxMQQ1ppQyBDZW50cmFzdCBTQTAeFw0wMzA0MzAxMDUwNTVaFw0wODA0MjgxMDUwNTVaMGgxCzAJ
-BgNVBAYTAlBMMR8wHQYDVQQKExZUUCBJbnRlcm5ldCBTcC4geiBvLm8uMR8wHQYDVQQDExZDQyBT
-aWduZXQgLSBDQSBLbGFzYSAzMRcwFQYDVQQFEw5OdW1lciB3cGlzdTogNDCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBALVdeOM62cPH2NERFxbS5FIp/HSv3fgesdVsTUFxZbGtE+/E0RMl
-KZQJHH9emx7vRYubsi4EOLCjYsCOTFvgGRIpZzx7R7T5c0Di5XFkRU4gjBl7aHJoKb5SLzGlWdoX
-GsekVtl6keEACrizV2EafqjI8cnBWY7OxQ1ooLQp5AeFjXg+5PT0lO6TUZAubqjFbhVbxSWjqvdj
-93RGfyYE76MnNn4c2xWySD07n7uno06TC0IJe6+3WSX1h+76VsIFouWBXOoM7cxxiLjoqdBVu24+
-P8e81SukE7qEvOwDPmk9ZJFtt1nBNg8a1kaixcljrA/43XwOPz6qnJ+cIj/xywECAwEAAaOCAQow
-ggEGMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMDMGA1UdIAEB/wQpMCcwJQYEVR0g
-ADAdMBsGCCsGAQUFBwIBFg93d3cuY2VudHJhc3QucGwwgY4GA1UdIwSBhjCBg4AU2a7r85Cp1iJN
-W0Ca1LR6VG3996ShZaRjMGExCzAJBgNVBAYTAlBMMTcwNQYDVQQKEy5DWmlDIENlbnRyYXN0IFNB
-IHcgaW1pZW5pdSBNaW5pc3RyYSBHb3Nwb2RhcmtpMRkwFwYDVQQDExBDWmlDIENlbnRyYXN0IFNB
-ggQ9/0sQMB0GA1UdDgQWBBR7Y8wZkHq0zrY7nn1tFSdQ0PlJuTANBgkqhkiG9w0BAQUFAAOCAQEA
-ldt/svO5c1MU08FKgrOXCGEbEPbQxhpM0xcd6Iv3dCo6qugEgjEs9Qm5CwUNKMnFsvR27cJWUvZb
-MVcvwlwCwclOdwF6u/QRS8bC2HYErhYo9bp9yuxxzuow2A94c5fPqfVrjXy+vDouchAm6+A5Wjzv
-J8wxVFDCs+9iGACmyUWr/JGXCYiQIbQkwlkRKHHlan9ymKf1NvIej/3EpeT8fKr6ywxGuhAfqofW
-pg3WJY/RCB4lTzD8vZGNwfMFGkWhJkypad3i9w3lGmDVpsHaWtCgGfd0H7tUtWPkP+t7EjIRCD9J
-HYnTR+wbbewc5vOI+UobR15ynGfFIaSIiMTVtQ==
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
+OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
+DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
+KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
+DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
+BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
+p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
+nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
+MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
+Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
+uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
+BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
+YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
+BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
+VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
+ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
+AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
+qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
+hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
+POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
+Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
+8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
+bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
+g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
+vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
+qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
+
+Security Communication Root CA
+==============================
-----BEGIN CERTIFICATE-----
-MIIEejCCA2KgAwIBAgIEP4vk6TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJQ
-TDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2Vu
-dHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MR8wHQYDVQQDExZDQyBTaWduZXQgLSBD
-QSBLbGFzYSAyMB4XDTAzMTAxNDExNTgyMloXDTE3MDQxODEyNTMwN1owdzELMAkG
-A1UEBhMCUEwxHzAdBgNVBAoTFlRQIEludGVybmV0IFNwLiB6IG8uby4xJDAiBgNV
-BAsTG0NlbnRydW0gQ2VydHlmaWthY2ppIFNpZ25ldDEhMB8GA1UEAxMYQ0MgU2ln
-bmV0IC0gT0NTUCBLbGFzYSAyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo
-VCsaBStblXQYVNthe3dvaCrfvKpPXngh4almm988iIlEv9CVTaAdCfaJNihvA+Vs
-Qw8++ix1VqteMQE474/MV/YaXigP0Zr0QB+g+/7PWVlv+5U9Gzp9+Xx4DJay8AoI
-iB7Iy5Qf9iZiHm5BiPRIuUXT4ZRbZRYPh0/76vgRsQIDAQABo4IBkjCCAY4wDgYD
-VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMJMEEGA1UdHwQ6MDgwNqA0
-oDKGMGh0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9jcmwva2xhc2Ey
-LmNybDCB2AYDVR0gBIHQMIHNMIHKBg4rBgEEAb4/AoFICgwBADCBtzBsBggrBgEF
-BQcCAjBgGl5DZXJ0eWZpa2F0IHd5ZGFueSB6Z29kbmllIHogZG9rdW1lbnRlbSAi
-UG9saXR5a2EgQ2VydHlmaWthY2ppIC0gQ2VydHlmaWthdHkgcmVzcG9uZGVyb3cg
-T0NTUCIuMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnNpZ25ldC5wbC9yZXBvenl0
-b3JpdW0vZG9rdW1lbnR5L3BjX29jc3BfMV8wLnBkZjAfBgNVHSMEGDAWgBS7RQZS
-C8uBzSlUs7x8QUzNBw6MJTAdBgNVHQ4EFgQUKEVrOY7cEHvsVgvoyZdytlbtgwEw
-CQYDVR0TBAIwADANBgkqhkiG9w0BAQUFAAOCAQEAQrRg5MV6dxr0HU2IsLInxhvt
-iUVmSFkIUsBCjzLoewOXA16d2oDyHhI/eE+VgAsp+2ANjZu4xRteHIHoYMsN218M
-eD2MLRsYS0U9xxAFK9gDj/KscPbrrdoqLvtPSMhUb4adJS9HLhvUe6BicvBf3A71
-iCNe431axGNDWKnpuj2KUpj4CFHYsWCXky847YtTXDjri9NIwJJauazsrSjK+oXp
-ngRS506mdQ7vWrtApkh8zhhWp7duCkjcCo1O8JxqYr2qEW1fXmgOISe010v2mmuv
-hHxPyVwoAU4KkOw0nbXZn53yak0is5+XmAjh0wWue44AssHrjC9nUh3mkLt6eQ==
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
+8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
+DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
+5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
+DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
+JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
+0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
+mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
+s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
+6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
+FL39vmwLAw==
-----END CERTIFICATE-----
+
+Sonera Class 2 Root CA
+======================
-----BEGIN CERTIFICATE-----
-MIIEezCCA2OgAwIBAgIEP4vnLzANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJQ
-TDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEfMB0GA1UEAxMWQ0Mg
-U2lnbmV0IC0gQ0EgS2xhc2EgMzEXMBUGA1UEBRMOTnVtZXIgd3Bpc3U6IDQwHhcN
-MDMxMDE0MTIwODAwWhcNMDgwNDI4MTA1MDU1WjB3MQswCQYDVQQGEwJQTDEfMB0G
-A1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBD
-ZXJ0eWZpa2FjamkgU2lnbmV0MSEwHwYDVQQDExhDQyBTaWduZXQgLSBPQ1NQIEts
-YXNhIDMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM/9GwvARNuCVN+PqZmO
-4FqH8vTqhenUyqRkmAVT4YhLu0a9AXeLAYVDu+NTkYzsAUMAfu55rIKHNLlm6WbF
-KvLiKKz4p4pbUr+ToPcwl/TDotidloUdBAxDg0SL+PmQqACZDe3seJho2IYf2vDL
-/G4TLMbKmNB0mlWFuN0f4fJNAgMBAAGjggGgMIIBnDAOBgNVHQ8BAf8EBAMCB4Aw
-EwYDVR0lBAwwCgYIKwYBBQUHAwkwTwYDVR0fBEgwRjBEoEKgQIY+aHR0cDovL3d3
-dy5zaWduZXQucGwva3dhbGlmaWtvd2FuZS9yZXBvenl0b3JpdW0vY3JsL2tsYXNh
-My5jcmwwgdgGA1UdIASB0DCBzTCBygYOKwYBBAG+PwKCLAoCAQAwgbcwbAYIKwYB
-BQUHAgIwYBpeQ2VydHlmaWthdCB3eWRhbnkgemdvZG5pZSB6IGRva3VtZW50ZW0g
-IlBvbGl0eWthIENlcnR5ZmlrYWNqaSAtIENlcnR5ZmlrYXR5IHJlc3BvbmRlcm93
-IE9DU1AiLjBHBggrBgEFBQcCARY7aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5
-dG9yaXVtL2Rva3VtZW50eS9wY19vY3NwXzFfMC5wZGYwHwYDVR0jBBgwFoAUe2PM
-GZB6tM62O559bRUnUND5SbkwHQYDVR0OBBYEFG4jnCMvBALRQXtmDn9TyXQ/EKP+
-MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADggEBACXrKG5Def5lpRwmZom3UEDq
-bl7y4U3qomG4B+ok2FVZGgPZti+ZgvrenPj7PtbYCUBPsCSTNrznKinoT3gD9lQQ
-xkEHwdc6VD1GlFp+qI64u0+wS9Epatrdf7aBnizrOIB4LJd4E2TWQ6trspetjMIU
-upyWls1BmYUxB91R7QkTiAUSNZ87s3auhZuG4f0V0JLVCcg2rn7AN1rfMkgxCbHk
-GxiQbYWFljl6aatxR3odnnzVUe1I8uoY2JXpmmUcOG4dNGuQYziyKG3mtXCQWvug
-5qi9Mf3KUh1oSTKx6HfLjjNl1+wMB5Mdb8LF0XyZLdJM9yIZh7SBRsYm9QiXevY=
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
+NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
+IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3
+/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
+dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
+f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
+tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
+nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
+XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
+0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
+cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
+Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
+EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH
+llpwrN9M
-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA
+=============================
-----BEGIN CERTIFICATE-----
-MIIFGjCCBAKgAwIBAgIEPL7eEDANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDIwNDE4MTQ1NDA4WhcNMjYw
-OTIxMTU0MjE5WjB2MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
-LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MSAwHgYDVQQDExdDQyBTaWdu
-ZXQgLSBQQ0EgS2xhc2EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7BrBlbN5ma
-M5eg0BOTqoZ+9NBDvU8Lm5rTdrMswFTCathzpVVLK/JD4K3+4oCZ9SRAspEXE4gvwb08ASY6w5s+
-HpRkeJw8YzMFR5kDZD5adgnCAy4vDfIXYZgppXPaTQ8wnfUZ7BZ7Zfa7QBemUIcJIzJBB0UqgtxW
-Ceol9IekpBRVmuuSA6QG0Jkm+pGDJ05yj2eQG8jTcBENM7sVA8rGRMyFA4skSZ+D0OG6FS2xC1i9
-JyN0ag1yII/LPx8HK5J4W9MaPRNjAEeaa2qI9EpchwrOxnyVbQfSedCG1VRJfAsE/9tT9CMUPZ3x
-W20QjQcSZJqVcmGW9gVsXKQOVLsCAwEAAaOCAbMwggGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
-AQH/BAQDAgEGMIIBBAYDVR0gBIH8MIH5MIH2Bg0rBgEEAb4/AgEKAQEBMIHkMIGaBggrBgEFBQcC
-AjCBjRqBikNlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
-eWthIENlcnR5ZmlrYWNqaSBkbGEgUm9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6
-IFJvb3RDQSB3IGhpZXJhcmNoaWkgQ0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5z
-aWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY19yb290Y2EudHh0MEQGA1UdHwQ9MDsw
-OaA3oDWGM2h0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9yb290Y2Evcm9vdGNhLmNy
-bDAfBgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAdBgNVHQ4EFgQUwGxGyl2CfpYHRonE
-82AVXO08kMIwDQYJKoZIhvcNAQEFBQADggEBABp1TAUsa+BeVWg4cjowc8yTJ5XN3GvN96GObMkx
-UGY7U9kVrLI71xBgoNVyzXTiMNDBvjh7vdPWjpl5SDiRpnnKiOFXA43HvNWzUaOkTu1mxjJsZsan
-ot1Xt6j0ZDC+03FjLHdYMyM9kSWp6afb4980EPYZCcSzgM5TOGfJmNii5Tq468VFKrX+52Aou1G2
-2Ohu+EEOlOrG7ylKv1hHUJJCjwN0ZVEIn1nDbrU9FeGCz8J9ihVUvnENEBbBkU37PWqWuHitKQDV
-tcwTwJJdR8cmKq3NmkwAm9fPacidQLpaw0WkuGrS+fEDhu1Nhy9xELP6NA9GRTCNxm/dXlcwnmY=
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE
+ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w
+HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh
+bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt
+vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P
+jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca
+C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth
+vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6
+22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV
+HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v
+dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN
+BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR
+EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw
+MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y
+nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
-----END CERTIFICATE-----
+
+TDC Internet Root CA
+====================
-----BEGIN CERTIFICATE-----
-MIIFGjCCBAKgAwIBAgIEPV0tNDANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDIwODE2MTY0OTU2WhcNMjYw
-OTIxMTU0MjE5WjB2MQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
-LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MSAwHgYDVQQDExdDQyBTaWdu
-ZXQgLSBQQ0EgS2xhc2EgMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALN3LanJtdue
-Ne6geWUTFENa+lEuzqELcoqhYB+a/tJcPEkc6TX/bYPzalRRjqs+quMP6KZTU0DixOrV+K7iWaqA
-iQ913HX5IBLmKDCrTVW/ZvSDpiBKbxlHfSNuJxAuVT6HdbzK7yAW38ssX+yS2tZYHZ5FhZcfqzPE
-OpO94mAKcBUhk6T/ki0evXX/ZvvktwmF3hKattzwtM4JMLurAEl8SInyEYULw5JdlfcBez2Tg6Db
-w34hA1A+ckTwhxzecrB8TUe2BnQKOs9vr2cCACpFFcOmPkM0Drtjctr1QHm1tYSqRFRf9VcV5tfC
-3P8QqoK4ONjtLPHc9x5NE1uK/FMCAwEAAaOCAbMwggGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
-AQH/BAQDAgEGMIIBBAYDVR0gBIH8MIH5MIH2Bg0rBgEEAb4/AgEKAQECMIHkMIGaBggrBgEFBQcC
-AjCBjRqBikNlcnR5ZmlrYXQgd3lzdGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0
-eWthIENlcnR5ZmlrYWNqaSBkbGEgUm9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6
-IFJvb3RDQSB3IGhpZXJhcmNoaWkgQ0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5z
-aWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY19yb290Y2EudHh0MEQGA1UdHwQ9MDsw
-OaA3oDWGM2h0dHA6Ly93d3cuc2lnbmV0LnBsL3JlcG96eXRvcml1bS9yb290Y2Evcm9vdGNhLmNy
-bDAfBgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAdBgNVHQ4EFgQUXvthcPHlH5BgGhlM
-ErJNXWlhlgAwDQYJKoZIhvcNAQEFBQADggEBACIce95Mvn710KCAISA0CuHD4aznTU6pLoCDShW4
-7OR+GTpJUm1coTcUqlBHV9mra4VFrBcBuOkHZoBLq/jmE0QJWnpSEULDcH9J3mF0nqO9SM+mWyJG
-dsJF/XU/7smummgjMNQXwzQTtWORF+6v5KUbWX85anO2wR+M6YTBWC55zWpWi4RG3vkHFs5Ze2oF
-JTlpuxw9ZgxTnWlwI9QR2MvEhYIUMKMOWxw1nt0kKj+5TCNQQGh/VJJ1dsiroGh/io1DOcePEhKz
-1Ag52y6Wf0nJJB9yk0sFakqZH18F7eQecQImgZyyeRtsG95leNugB3BXWCW+KxwiBrtQTXv4dTE=
+MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE
+ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx
+NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu
+ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j
+xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL
+znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc
+5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6
+otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI
+AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM
+VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM
+MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC
+AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe
+UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G
+CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m
+gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
+2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb
+O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU
+Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l
-----END CERTIFICATE-----
+
+UTN DATACorp SGC Root CA
+========================
-----BEGIN CERTIFICATE-----
-MIIEzzCCA7egAwIBAgIEO6ocGTANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MRswGQYDVQQDExJDQyBTaWduZXQgLSBSb290Q0EwHhcNMDEwOTIwMTY0MjE5WhcNMjYw
-OTIxMTU0MjE5WjBxMQswCQYDVQQGEwJQTDEfMB0GA1UEChMWVFAgSW50ZXJuZXQgU3AuIHogby5v
-LjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2FjamkgU2lnbmV0MRswGQYDVQQDExJDQyBTaWdu
-ZXQgLSBSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrr2vydnNpELfGW3Ks
-ARiDhJvwDtUe4AbWev+OfMc3+vA29nX8ZmIwno3gmItjo5DbUCCRiCMq5c9epcGu+kg4a3BJChVX
-REl8gVh0ST15rr3RKrSc4VgsvQzl0ZUraeQLl8JoRT5PLsUj3qwF78jUCQVckiiLVcnGfZtFCm+D
-CJXliQBDMB9XFAUEiO/DtEBs0B7wJGx7lgJeJpQUcGiaOPjcJDYOk7rNAYmmD2gWeSlepufO8luU
-YG/YDxTC4mqhRqfa4MnVO5dqy+ICj2UvUpHbZDB0KfGRibgBYeQP1kuqgIzJN4UqknVAJb0aMBSP
-l+9k2fAUdchx1njlbdcbAgMBAAGjggFtMIIBaTAPBgNVHRMBAf8EBTADAQH/MIIBBAYDVR0gBIH8
-MIH5MIH2Bg0rBgEEAb4/AgEKAQEAMIHkMIGaBggrBgEFBQcCAjCBjRqBikNlcnR5ZmlrYXQgd3lz
-dGF3aW9ueSB6Z29kbmllIHogZG9rdW1lbnRlbTogIlBvbGl0eWthIENlcnR5ZmlrYWNqaSBkbGEg
-Um9vdENBIi4gQ2VydHlmaWthdCB3eXN0YXdpb255IHByemV6IFJvb3RDQSB3IGhpZXJhcmNoaWkg
-Q0MgU2lnbmV0LjBFBggrBgEFBQcCARY5aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5dG9yaXVt
-L2Rva3VtZW50eS9wY19yb290Y2EudHh0MB0GA1UdDgQWBBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAf
-BgNVHSMEGDAWgBTAm8UjDQLhpk5Iax8A6eOaFBuxrzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcN
-AQEFBQADggEBAGnY5QmYqnnO9OqFOWZxxb25UHRnaRF6IV9aaGit5BZufZj2Tq3v8L3SgE34GOoI
-cdRMMG5JEpEU4mN/Ef3oY6Eo+7HfqaPHI4KFmbDSPiK5s+wmf+bQSm0Yq5/h4ZOdcAESlLQeLSt1
-CQk2JoKQJ6pyAf6xJBgWEIlm4RXE4J3324PUiOp83kW6MDvaa1xY976WyInr4rwoLgxVl11LZeKW
-ha0RJJxJgw/NyWpKG7LWCm1fglF8JH51vZNndGYq1iKtfnrIOvLZq6bzaCiZm1EurD8HE6P7pmAB
-KK6o3C2OXlNfNIgwkDN/cDqk5TYsTkrpfriJPdxXBH8hQOkW89g=
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ
+BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa
+MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w
+HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
+dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys
+raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo
+wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA
+9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv
+33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud
+DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9
+BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD
+LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3
+DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0
+I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx
+EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP
+DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI
-----END CERTIFICATE-----
+
+UTN USERFirst Hardware Root CA
+==============================
-----BEGIN CERTIFICATE-----
-MIID/TCCA2agAwIBAgIEP4/gkTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJQTDEfMB0GA1UE
-ChMWVFAgSW50ZXJuZXQgU3AuIHogby5vLjEkMCIGA1UECxMbQ2VudHJ1bSBDZXJ0eWZpa2Fjamkg
-U2lnbmV0MR8wHQYDVQQDExZDQyBTaWduZXQgLSBDQSBLbGFzYSAxMB4XDTAzMTAxNzEyMjkwMloX
-DTExMDkyMzExMTgxN1owdjELMAkGA1UEBhMCUEwxHzAdBgNVBAoTFlRQIEludGVybmV0IFNwLiB6
-IG8uby4xJDAiBgNVBAsTG0NlbnRydW0gQ2VydHlmaWthY2ppIFNpZ25ldDEgMB4GA1UEAxMXQ0Mg
-U2lnbmV0IC0gVFNBIEtsYXNhIDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOJYrISEtSsd
-uHajROh5/n7NGrkpYTT9NEaPe9+ucuQ37KxIbfJwXJjgUc1dw4wCkcQ12FJarD1X6mSQ4cfN/60v
-LfKI5ZD4nhJTMKlAj1pX9ScQ/MuyvKStCbn5WTkjPhjRAM0tdwXSnzuTEunfw0Oup559y3Iqxg1c
-ExflB6cfAgMBAAGjggGXMIIBkzBBBgNVHR8EOjA4MDagNKAyhjBodHRwOi8vd3d3LnNpZ25ldC5w
-bC9yZXBvenl0b3JpdW0vY3JsL2tsYXNhMS5jcmwwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQM
-MAoGCCsGAQUFBwMIMIHaBgNVHSAEgdIwgc8wgcwGDSsGAQQBvj8CZAoRAgEwgbowbwYIKwYBBQUH
-AgIwYxphQ2VydHlmaWthdCB3eXN0YXdpb255IHpnb2RuaWUgeiBkb2t1bWVudGVtICJQb2xpdHlr
-YSBDZXJ0eWZpa2FjamkgQ0MgU2lnbmV0IC0gWm5ha293YW5pZSBjemFzZW0iLjBHBggrBgEFBQcC
-ARY7aHR0cDovL3d3dy5zaWduZXQucGwvcmVwb3p5dG9yaXVtL2Rva3VtZW50eS9wY190c2ExXzJf
-MS5wZGYwHwYDVR0jBBgwFoAUw4Me1Vl3VPtN+1dH+cQjXNHnieMwHQYDVR0OBBYEFJdDwEqtcavO
-Yd9u9tej53vWXwNBMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADgYEAnpiQkqLCJQYXUrqMHUEz
-+z3rOqS0XzSFnVVLhkVssvXc8S3FkJIiQTUrkScjI4CToCzujj3EyfNxH6yiLlMbskF8I31JxIeB
-vueqV+s+o76CZm3ycu9hb0I4lswuxoT+q5ZzPR8Irrb51rZXlolR+7KtwMg4sFDJZ8RNgOf7tbA=
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd
+BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx
+OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0
+eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
+ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI
+wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd
+tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8
+i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf
+Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw
+gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF
+lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF
+UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF
+BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW
+XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2
+lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn
+iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67
+nfhmqA==
-----END CERTIFICATE-----
+
+Camerfirma Chambers of Commerce Root
+====================================
-----BEGIN CERTIFICATE-----
-MIIEFTCCA36gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBvjELMAkGA1UEBhMCVVMx
-EDAOBgNVBAgTB0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UE
-ChMfU29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9z
-dG1hc3RlcjEgMB4GA1UEAxMXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJTAjBgkq
-hkiG9w0BCQEWFmhvc3RtYXN0ZXJAc3BpLWluYy5vcmcwHhcNMDMwMTE1MTYyOTE3
-WhcNMDcwMTE0MTYyOTE3WjCBvjELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0luZGlh
-bmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMfU29mdHdhcmUgaW4g
-dGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1hc3RlcjEgMB4GA1UE
-AxMXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJTAjBgkqhkiG9w0BCQEWFmhvc3Rt
-YXN0ZXJAc3BpLWluYy5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPB6
-rdoiLR3RodtM22LMcfwfqb5OrJNl7fwmvskgF7yP6sdD2bOfDIXhg9852jhY8/kL
-VOFe1ELAL2OyN4RAxk0rliZQVgeTgqvgkOVIBbNwgnjN6mqtuWzFiPL+NXQExq40
-I3whM+4lEiwSHaV+MYxWanMdhc+kImT50LKfkxcdAgMBAAGjggEfMIIBGzAdBgNV
-HQ4EFgQUB63oQR1/vda/G4F6P4xLiN4E0vowgesGA1UdIwSB4zCB4IAUB63oQR1/
-vda/G4F6P4xLiN4E0vqhgcSkgcEwgb4xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdJ
-bmRpYW5hMRUwEwYDVQQHEwxJbmRpYW5hcG9saXMxKDAmBgNVBAoTH1NvZnR3YXJl
-IGluIHRoZSBQdWJsaWMgSW50ZXJlc3QxEzARBgNVBAsTCmhvc3RtYXN0ZXIxIDAe
-BgNVBAMTF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MSUwIwYJKoZIhvcNAQkBFhZo
-b3N0bWFzdGVyQHNwaS1pbmMub3JnggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
-AQEEBQADgYEAm/Abn8c2y1nO3fgpAIslxvi9iNBZDhQtJ0VQZY6wgSfANyDOR4DW
-iexO/AlorB49KnkFS7TjCAoLOZhcg5FaNiKnlstMI5krQmau1Qnb/vGSNsE/UGms
-1ts+QYPUs0KmGEAFUri2XzLy+aQo9Kw74VBvqnxvaaMeY5yMcKNOieY=
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
+NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
+cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
+MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
+AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
+xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
+NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
+DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
+d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
+EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
+cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
+AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
+bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
+VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
+fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
+L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
+UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
+ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
+erfutGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
+
+Camerfirma Global Chambersign Root
+==================================
-----BEGIN CERTIFICATE-----
-MIIIDjCCBfagAwIBAgIJAOiOtsn4KhQoMA0GCSqGSIb3DQEBBQUAMIG8MQswCQYD
-VQQGEwJVUzEQMA4GA1UECBMHSW5kaWFuYTEVMBMGA1UEBxMMSW5kaWFuYXBvbGlz
-MSgwJgYDVQQKEx9Tb2Z0d2FyZSBpbiB0aGUgUHVibGljIEludGVyZXN0MRMwEQYD
-VQQLEwpob3N0bWFzdGVyMR4wHAYDVQQDExVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx
-JTAjBgkqhkiG9w0BCQEWFmhvc3RtYXN0ZXJAc3BpLWluYy5vcmcwHhcNMDgwNTEz
-MDgwNzU2WhcNMTgwNTExMDgwNzU2WjCBvDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
-B0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMfU29mdHdh
-cmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1hc3RlcjEe
-MBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcNAQkBFhZo
-b3N0bWFzdGVyQHNwaS1pbmMub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
-CgKCAgEA3DbmR0LCxFF1KYdAw9iOIQbSGE7r7yC9kDyFEBOMKVuUY/b0LfEGQpG5
-GcRCaQi/izZF6igFM0lIoCdDkzWKQdh4s/Dvs24t3dHLfer0dSbTPpA67tfnLAS1
-fOH1fMVO73e9XKKTM5LOfYFIz2u1IiwIg/3T1c87Lf21SZBb9q1NE8re06adU1Fx
-Y0b4ShZcmO4tbZoWoXaQ4mBDmdaJ1mwuepiyCwMs43pPx93jzONKao15Uvr0wa8u
-jyoIyxspgpJyQ7zOiKmqp4pRQ1WFmjcDeJPI8L20QcgHQprLNZd6ioFl3h1UCAHx
-ZFy3FxpRvB7DWYd2GBaY7r/2Z4GLBjXFS21ZGcfSxki+bhQog0oQnBv1b7ypjvVp
-/rLBVcznFMn5WxRTUQfqzj3kTygfPGEJ1zPSbqdu1McTCW9rXRTunYkbpWry9vjQ
-co7qch8vNGopCsUK7BxAhRL3pqXTT63AhYxMfHMgzFMY8bJYTAH1v+pk1Vw5xc5s
-zFNaVrpBDyXfa1C2x4qgvQLCxTtVpbJkIoRRKFauMe5e+wsWTUYFkYBE7axt8Feo
-+uthSKDLG7Mfjs3FIXcDhB78rKNDCGOM7fkn77SwXWfWT+3Qiz5dW8mRvZYChD3F
-TbxCP3T9PF2sXEg2XocxLxhsxGjuoYvJWdAY4wCAs1QnLpnwFVMCAwEAAaOCAg8w
-ggILMB0GA1UdDgQWBBQ0cdE41xU2g0dr1zdkQjuOjVKdqzCB8QYDVR0jBIHpMIHm
-gBQ0cdE41xU2g0dr1zdkQjuOjVKdq6GBwqSBvzCBvDELMAkGA1UEBhMCVVMxEDAO
-BgNVBAgTB0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMf
-U29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1h
-c3RlcjEeMBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcN
-AQkBFhZob3N0bWFzdGVyQHNwaS1pbmMub3JnggkA6I62yfgqFCgwDwYDVR0TAQH/
-BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAAcwCQYDVR0SBAIwADAuBglghkgBhvhC
-AQ0EIRYfU29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDAwBglghkgBhvhC
-AQQEIxYhaHR0cHM6Ly9jYS5zcGktaW5jLm9yZy9jYS1jcmwucGVtMDIGCWCGSAGG
-+EIBAwQlFiNodHRwczovL2NhLnNwaS1pbmMub3JnL2NlcnQtY3JsLnBlbTAhBgNV
-HREEGjAYgRZob3N0bWFzdGVyQHNwaS1pbmMub3JnMA4GA1UdDwEB/wQEAwIBBjAN
-BgkqhkiG9w0BAQUFAAOCAgEAtM294LnqsgMrfjLp3nI/yUuCXp3ir1UJogxU6M8Y
-PCggHam7AwIvUjki+RfPrWeQswN/2BXja367m1YBrzXU2rnHZxeb1NUON7MgQS4M
-AcRb+WU+wmHo0vBqlXDDxm/VNaSsWXLhid+hoJ0kvSl56WEq2dMeyUakCHhBknIP
-qxR17QnwovBc78MKYiC3wihmrkwvLo9FYyaW8O4x5otVm6o6+YI5HYg84gd1GuEP
-sTC8cTLSOv76oYnzQyzWcsR5pxVIBcDYLXIC48s9Fmq6ybgREOJJhcyWR2AFJS7v
-dVkz9UcZFu/abF8HyKZQth3LZjQl/GaD68W2MEH4RkRiqMEMVObqTFoo5q7Gt/5/
-O5aoLu7HaD7dAD0prypjq1/uSSotxdz70cbT0ZdWUoa2lOvUYFG3/B6bzAKb1B+P
-+UqPti4oOxfMxaYF49LTtcYDyeFIQpvLP+QX4P4NAZUJurgNceQJcHdC2E3hQqlg
-g9cXiUPS1N2nGLar1CQlh7XU4vwuImm9rWgs/3K1mKoGnOcqarihk3bOsPN/nOHg
-T7jYhkalMwIsJWE3KpLIrIF0aGOHM3a9BX9e1dUCbb2v/ypaqknsmHlHU5H2DjRa
-yaXG67Ljxay2oHA1u8hRadDytaIybrw/oDc5fHE2pgXfDBLkFqfF1stjo5VwP+YE
-o2A=
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
+NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
+YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
+MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
+ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
+1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
+by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
+6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
+8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
+BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
+aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
+Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
+aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
+ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
+PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
+gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
+PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
+IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
+t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----
+
+NetLock Notary (Class A) Root
+=============================
-----BEGIN CERTIFICATE-----
-MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
-MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
-IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
-IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
-RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
-U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
-IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
-ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
-QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
-rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
-NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
-QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
-txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
-BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
-AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
-tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
-IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
-6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
-xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI
+EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j
+ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX
+DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH
+EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD
+VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz
+cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM
+D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ
+z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC
+/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7
+tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6
+4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG
+A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC
+Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv
+bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn
+LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0
+ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz
+IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh
+IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu
+b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh
+bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg
+Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp
+bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5
+ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP
+ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB
+CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr
+KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM
+8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+
+NetLock Business (Class B) Root
+===============================
+-----BEGIN CERTIFICATE-----
+MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg
+VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD
+VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv
+bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg
+VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S
+o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr
+1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
+HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ
+RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh
+dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0
+ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv
+c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg
+YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
+c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz
+Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA
+bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl
+IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2
+YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj
+cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM
+43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR
+stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI
+-----END CERTIFICATE-----
+
+NetLock Express (Class C) Root
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j
+ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z
+W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63
+euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw
+DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN
+RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn
+YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB
+IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i
+aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0
+ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
+ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo
+dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y
+emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k
+IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ
+UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg
+YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2
+xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW
+gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A==
+-----END CERTIFICATE-----
+
+XRamp Global CA Root
+====================
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
+BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
+dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
+HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
+U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
+IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
+foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
+zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
+AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
+xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
+oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
+AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
+/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
+nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
+8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+
+Go Daddy Class 2 CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
+VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
+A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
+ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
+qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
+YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
+vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
+BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
+atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
+MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
+PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
+I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
+Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
+vZ8=
+-----END CERTIFICATE-----
+
+Starfield Class 2 CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
+U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
+MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
+A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
+SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
+bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
+JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
+epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
+F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
+MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
+hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
+bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
+afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
+PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
+KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
+QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj
+YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH
+AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw
+Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg
+U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5
+LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh
+cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT
+dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC
+AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh
+3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm
+vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk
+fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3
+fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ
+EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl
+1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/
+lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro
+g14=
+-----END CERTIFICATE-----
+
+Taiwan GRCA
+===========
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG
+EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X
+DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv
+dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN
+w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5
+BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O
+1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO
+htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov
+J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7
+Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t
+B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB
+O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8
+lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV
+HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2
+09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj
+Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2
+Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU
+D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz
+DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk
+Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk
+7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ
+CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy
++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS
+-----END CERTIFICATE-----
+
+Firmaprofesional Root CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT
+GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp
+Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA
+ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL
+MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT
+OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2
+ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V
+j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH
+lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf
+3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8
+NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww
+KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG
+AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD
+ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq
+u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf
+wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm
+7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG
+VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA=
+-----END CERTIFICATE-----
+
+Wells Fargo Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDAxMDExMTY0MTI4WhcNMjEwMTE0MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dl
+bGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEv
+MC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n135zHCLielTWi5MbqNQ1mX
+x3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHESxP9cMIlrCL1dQu3U+SlK93OvRw6esP3
+E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4OJgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5
+OEL8pahbSCOz6+MlsoCultQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4j
+sNtlAHCEAQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMBAAGj
+YTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcBCzAyMDAGCCsGAQUF
+BwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRwb2xpY3kwDQYJKoZIhvcNAQEFBQAD
+ggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrv
+m+0fazbuSCUlFLZWohDo7qd/0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0R
+OhPs7fpvcmR7nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
+x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ33ZwmVxwQ023
+tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
+-----END CERTIFICATE-----
+
+Swisscom Root CA 1
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4
+MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM
+MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF
+NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe
+AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC
+b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn
+7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN
+cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp
+WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5
+haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY
+MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9
+MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn
+jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ
+MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H
+VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl
+vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl
+OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3
+1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq
+nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy
+x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW
+NY6E0F/6MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+
+DigiCert Assured ID Root CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
+MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
+9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
+UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
+/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
+oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
+GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
+66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
+hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
+EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
+SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
+8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+
+DigiCert Global Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
+MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
+TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
+BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
+4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
+7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
+o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
+8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
+BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
+EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
+tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
+UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+
+DigiCert High Assurance EV Root CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
+KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
+MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
+Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
+Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
+OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
+MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
+NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
+h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
+Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
+JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
+V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
+myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
+mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Certplus Class 2 Primary CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE
+BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN
+OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy
+dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR
+5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ
+Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO
+YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e
+e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME
+CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ
+YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t
+L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD
+P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R
+TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+
+7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW
+//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+
+DST Root CA X3
+==============
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
+DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
+cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
+rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
+UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
+xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
+utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
+MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug
+dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
+GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
+RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
+fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+
+DST ACES CA X6
+==============
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT
+MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha
+MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE
+CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI
+DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa
+pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow
+GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy
+MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu
+Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy
+dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU
+CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2
+5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t
+Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs
+vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
+oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 1
+==============================================
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
+MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
+acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
+MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
+TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
+aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
+yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
+Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
+8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
+W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
+BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
+sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
+q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
+nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2
+==============================================
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
+MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
+dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
+A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
+acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
+LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
+x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
+QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
+5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
+AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
+Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
+9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
+UrbnBEI=
+-----END CERTIFICATE-----
+
+SwissSign Gold CA - G2
+======================
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
+EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
+MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
+c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
+t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
+jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
+vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
+ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
+AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
+jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
+peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
+7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
+GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
+OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
+5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
+44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
+Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
+Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
+mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
+vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
+KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
+NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
+viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+
+SwissSign Silver CA - G2
+========================
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
+BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
+DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
+aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
+N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
+6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
+MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
+qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
+FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
+ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
+celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
+CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
+tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
+4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
+kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
+3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
+/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
+DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
+e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
+WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
+DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
+DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ
+cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN
+b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9
+nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge
+RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt
+tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI
+hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K
+Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN
+NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa
+Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG
+1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+
+thawte Primary Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3
+MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg
+SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv
+KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT
+FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs
+oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ
+1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc
+q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K
+aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p
+afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF
+AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE
+uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89
+jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH
+z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G5
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
+biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
+dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
+j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
+Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
+fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
+SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
+KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
+Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
+ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+
+SecureTrust CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
+dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
+BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
+OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
+DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
+GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
+01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
+ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
+aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
+SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
+mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
+nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+
+Secure Global CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
+bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
+MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
+YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
+bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
+8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
+HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
+0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
+oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
+MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
+CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
+3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+
+COMODO Certification Authority
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
+MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
+T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
+xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
+4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
+1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
+rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
+b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
+AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
+OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
+IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
+-----END CERTIFICATE-----
+
+Network Solutions Certificate Authority
+=======================================
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
+EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
+IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
+MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
+jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
+aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
+crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
+/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
+AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
+bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
+A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
+4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
+GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
+ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+
+WellsSecure Public Root Certificate Authority
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM
+F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw
+NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl
+bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD
+VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1
+iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13
+i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8
+bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB
+K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB
+AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu
+cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm
+lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB
+i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww
+GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI
+K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0
+bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj
+qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es
+E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ
+tylv2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+
+COMODO ECC Certification Authority
+==================================
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
+GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
+4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
+wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
+FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
+U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+IGC/A
+=====
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE
+Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy
+MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI
+EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT
+STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2
+TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW
+So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy
+HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd
+frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ
+tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB
+egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC
+iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK
+q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q
+MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI
+lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF
+0mBWWg==
+-----END CERTIFICATE-----
+
+Security Communication EV RootCA1
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
+BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
+Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
+/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
+WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
+ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
+bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
+9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
+iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
+Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
+mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
+T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+
+OISTE WISeKey Global Root GA CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
+BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
+A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
+bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
+VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
+IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
+IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
+Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
+Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
+d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
+/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
+LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
+MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
+okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE
+BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL
+EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0
+MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz
+dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT
+GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG
+d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N
+oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc
+QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ
+PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb
+MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG
+IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD
+VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3
+LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A
+dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA
+4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg
+AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA
+egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6
+Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO
+PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv
+c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h
+cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw
+IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT
+WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV
+MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER
+MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp
+Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal
+HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT
+nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE
+aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK
+yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB
+S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+
+Certigna
+========
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
+EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
+MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
+Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
+XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
+GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
+ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
+DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
+Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
+tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
+BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
+SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
+hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
+PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
+1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+AC Ra\xC3\xADz Certic\xC3\xA1mara S.A.
+======================================
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT
+AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg
+LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w
+HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+
+U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh
+IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN
+yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU
+2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3
+4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP
+2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm
+8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf
+HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa
+Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK
+5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b
+czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g
+ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF
+BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug
+cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf
+AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX
+EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v
+/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3
+MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4
+3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk
+eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f
+/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h
+RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU
+Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 2 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw
+MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw
+IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2
+xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ
+Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u
+SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G
+dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ
+KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj
+TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP
+JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk
+vQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 3 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw
+MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W
+yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo
+6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ
+uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk
+2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE
+O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8
+yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9
+IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal
+092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc
+5A==
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA I
+=============================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN
+MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg
+VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw
+JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC
+qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv
+xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw
+ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O
+gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j
+BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG
+1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy
+vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3
+ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a
+7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+
+Deutsche Telekom Root CA 2
+==========================
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT
+RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG
+A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5
+MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G
+A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS
+b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5
+bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI
+KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY
+AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK
+Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV
+jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV
+HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr
+E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy
+zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8
+rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G
+dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----
+ComSign Secured CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE
+AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w
+NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD
+QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs
+49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH
+7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB
+kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1
+9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw
+AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t
+U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA
+j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC
+AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a
+BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp
+FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP
+51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+
+Cybertrust Global Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
+ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
+MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
+ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
+0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
+AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
+89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
+8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
+MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
+A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
+lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
+5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
+hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
+X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+
+ePKI Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
+MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
+MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
+IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
+lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
+qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
+12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
+WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
+lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
+vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
+Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
+MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
+1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
+KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
+xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
+NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
+GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
+xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
+gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
+sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
+BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3
+=============================================================================================================================
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH
+DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q
+aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry
+b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV
+BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg
+S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4
+MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl
+IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF
+n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl
+IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft
+dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl
+cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO
+Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1
+xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR
+6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd
+BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4
+N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT
+y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh
+LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M
+dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+
+Buypass Class 2 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2
+MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M
+cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83
+0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4
+0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R
+uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV
+1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt
+7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2
+fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w
+wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+
+Buypass Class 3 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1
+MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx
+ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0
+n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia
+AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c
+1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7
+pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA
+EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5
+htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj
+el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+
+EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1
+==========================================================================
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg
+QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe
+Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt
+IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by
+X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b
+gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr
+eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ
+TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy
+Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn
+uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI
+qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm
+ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0
+Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW
+Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t
+FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm
+zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k
+XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT
+bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU
+RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK
+1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt
+2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ
+Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9
+AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+
+certSIGN ROOT CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
+VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
+Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
+CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
+JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
+rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
+ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
+0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
+AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
+AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
+SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
+x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
+vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
+TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+CNNIC ROOT
+==========
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE
+ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw
+OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD
+o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz
+VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT
+VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or
+czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK
+y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC
+wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S
+lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5
+Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM
+O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8
+BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2
+G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m
+mxE=
+-----END CERTIFICATE-----
+
+ApplicationCA - Japanese Government
+===================================
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT
+SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw
+MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl
+cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4
+fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN
+wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE
+jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu
+nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU
+WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD
+vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs
+o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g
+/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD
+io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW
+dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G3
+=============================================
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz
+NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo
+YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT
+LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j
+K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE
+c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C
+IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu
+dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr
+2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9
+cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE
+Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s
+t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G2
+===========================
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC
+VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu
+IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg
+Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV
+MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG
+b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt
+IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS
+LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5
+8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN
+G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K
+rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G3
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w
+ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD
+VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG
+A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At
+P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC
++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY
+7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW
+vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ
+KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK
+A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC
+8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm
+er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
+OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
+b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
+KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
+EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
+ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
+npaqBA+K
+-----END CERTIFICATE-----
+
+VeriSign Universal Root Certification Authority
+===============================================
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj
+1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP
+MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
+9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
+AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR
+tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
+CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O
+a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
+Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
+Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
+P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
+wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4
+mJO37M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G4
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3
+b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz
+ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo
+b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8
+Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz
+rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw
+HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u
+Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD
+A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx
+AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+NetLock Arany (Class Gold) FÅ‘tanúsÃtvány
+============================================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
+A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
+dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
+cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
+MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
+ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
+c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
+0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
+/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
+H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
+fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
+neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
+qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
+YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
+NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
+dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA - G2
+==================================
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC
+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ
+5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn
+vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj
+CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil
+e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR
+OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI
+CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65
+48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi
+trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737
+qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB
+AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC
+ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA
+A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz
++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj
+f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN
+kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk
+CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF
+URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb
+CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h
+oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV
+IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm
+66+KAQ==
+-----END CERTIFICATE-----
+
+CA Disig
+========
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK
+QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw
+MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz
+bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm
+GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD
+Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo
+hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt
+ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w
+gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P
+AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz
+aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff
+ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa
+BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t
+WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3
+mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K
+ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA
+4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+
+Juur-SK
+=======
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA
+c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw
+DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG
+SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy
+aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf
+TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC
++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw
+UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa
+Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF
+MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD
+HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh
+AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA
+cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr
+AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw
+cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G
+A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo
+ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL
+abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678
+IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh
+Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2
+yyqcjg==
+-----END CERTIFICATE-----
+
+Hongkong Post Root CA 1
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
+DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
+NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
+IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
+ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
+auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
+qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
+V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
+HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
+h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
+l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
+IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
+T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
+c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
+-----END CERTIFICATE-----
+
+SecureSign RootCA11
+===================
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
+SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
+b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
+KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
+cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
+TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
+wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
+g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
+O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
+bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
+t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
+OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
+bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
+Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
+y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
+lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+
+ACEDICOM Root
+=============
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD
+T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4
+MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG
+A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk
+WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD
+YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew
+MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb
+m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk
+HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT
+xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2
+3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9
+2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq
+TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz
+4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU
+9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg
+aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP
+eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk
+zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1
+ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI
+KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq
+nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE
+I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp
+MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o
+tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky
+CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX
+bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/
+D/xwzoiQ
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
+MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
+c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
+BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
+U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
+fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
+0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
+pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
+1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
+AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
+QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
+FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
+lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
+I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
+yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
+LXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi
+===================================================
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz
+ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3
+MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0
+cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u
+aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY
+8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y
+jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI
+JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk
+9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG
+SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d
+F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq
+D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4
+Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R3
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
+iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
+0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
+rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
+OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
+xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
+lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
+EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
+bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
+YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
+kpeDMdmztcpHWD9f
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA III
+===============================
+-----BEGIN CERTIFICATE-----
+MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAe
+Fw0wOTA5MDkwODE1MjdaFw0yOTEyMzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNU
+QyBUcnVzdENlbnRlciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0Ex
+KDAmBgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF5+cvAqBNLaT6hdqbJYUt
+QCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYvDIRlzg9uwliT6CwLOunBjvvya8o84pxO
+juT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8vzArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+Eut
+CHnNaYlAJ/Uqwa1D7KRTyGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1
+M4BDj5yjdipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBhMB8G
+A1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI4jANBgkqhkiG9w0BAQUFAAOCAQEA
+g8ev6n9NCjw5sWi+e22JLumzCecYV42FmhfzdkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+
+KGwWaODIl0YgoGhnYIg5IFHYaAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhK
+BgePxLcHsU0GDeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV
+CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPHLQNjO9Po5KIq
+woIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg==
+-----END CERTIFICATE-----
+
+Autoridad de Certificacion Firmaprofesional CIF A62634068
+=========================================================
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
+EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
+DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
+bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
+ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
+51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
+R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
+T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
+Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
+osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
+crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
+saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
+KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
+6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+Izenpe.com
+==========
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
+EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
+MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
+QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
+03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
+ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
+PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
+OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
+F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
+0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
+leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
+AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
+SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
+NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
+BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
+Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
+kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
+hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
+g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
+aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
+nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
+ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
+Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
+WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+Chambers of Commerce Root - 2008
+================================
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
+Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
+ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
+EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
+cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
+XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
+h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
+ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
+NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
+D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
+lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
+0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
+EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
+G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
+BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
+bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
+bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
+CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
+AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
+wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
+3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
+RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
+M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
+YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
+9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
+zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
+nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
+-----END CERTIFICATE-----
+
+Global Chambersign Root - 2008
+==============================
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
+NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
+Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
+QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
+VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
+XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
+ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
+/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
+TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
+H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
+Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
+HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
+AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
+BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
+BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
+aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
+aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
+1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
+dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
+/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
+ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
+dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
+9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
+foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
+qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
+P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
+c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+
+Go Daddy Root Certificate Authority - G2
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
+MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
+A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
+9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
+fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
+NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
+BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
+vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
+5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
+N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+Starfield Root Certificate Authority - G2
+=========================================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
+eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
+DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
+VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
+W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
+bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
+N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
+ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
+JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
+TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
+4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
+F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
+c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+Starfield Services Root Certificate Authority - G2
+==================================================
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
+IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
+dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
+h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
+hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
+LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
+rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
+SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
+E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
+xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
+YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
+-----END CERTIFICATE-----
+
+AffirmTrust Commercial
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
+MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
+DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
+C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
+BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
+MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
+HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
+hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
+qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
+0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
+sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+AffirmTrust Networking
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
+MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
+Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
+dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
+/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
+h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
+HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
+UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
+12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
+WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
+/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+AffirmTrust Premium
+===================
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
+OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
+dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
+BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
+5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
+GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
+p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
+S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
+6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
+/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
+MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
+6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
+L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
+BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
+IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
+g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
+zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+AffirmTrust Premium ECC
+=======================
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
+BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
+MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
+cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
+N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
+BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
+BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
+57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
+eQ==
+-----END CERTIFICATE-----
+
+Certum Trusted Network CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
+ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
+MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
+ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
+l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
+J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
+fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
+cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
+Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
+DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
+jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
+mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
+Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+Certinomis - Autorité Racine
+=============================
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg
+LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG
+A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw
+JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa
+wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly
+Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw
+2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N
+jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q
+c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC
+lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb
+xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g
+530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna
+4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x
+WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva
+R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40
+nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B
+CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv
+JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE
+qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b
+WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE
+wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/
+vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+
+Root CA Generalitat Valenciana
+==============================
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE
+ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290
+IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3
+WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE
+CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2
+F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B
+ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ
+D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte
+JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB
+AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n
+dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB
+ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl
+AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA
+YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy
+AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt
+AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA
+YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu
+AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA
+OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0
+dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV
+BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S
+b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh
+TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz
+Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63
+NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH
+iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt
++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+
+A-Trust-nQual-03
+================
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE
+Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy
+a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R
+dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw
+RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0
+ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1
+c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA
+zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n
+yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE
+SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4
+iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V
+cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV
+eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40
+ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr
+sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd
+JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6
+ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+
+TWCA Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
+VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
+EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
+IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
+QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
+oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
+4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
+y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
+9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
+mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
+QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
+T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
+Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+Security Communication RootCA2
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
+SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
+aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
+3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
+spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
+EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
+QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
+u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
+3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
+tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
+mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+EC-ACC
+======
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
+BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
+ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
+VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
+CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
+BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
+MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
+SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
+Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
+cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
+w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
+ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
+HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
+E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
+0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
+VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
+Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
+dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
+lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
+Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
+l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
+E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
+5EI=
+-----END CERTIFICATE-----
+
+Hellenic Academic and Research Institutions RootCA 2011
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
+O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
+aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
+AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
+IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
+1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
+71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
+8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
+3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
+MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
+MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
+b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
+XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
+/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
+7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+
+Actalis Authentication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
+BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
+AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
+MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
+IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
+wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
+by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
+zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
+YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
+oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
+EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
+hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
+EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
+jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
+iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
+WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
+JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
+K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
+Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
+4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
+2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
+lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
+OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
+vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+Trustis FPS Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
+EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
+IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
+BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ
+RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
+H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
+cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
+o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
+AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
+BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c
+GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
+yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P
+8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
+l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
+iB6XzCGcKQENZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ
+Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0
+dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu
+c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv
+bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0
+aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t
+L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG
+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5
+fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm
+N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN
+Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T
+tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX
+e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA
+2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs
+HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib
+D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority G2
+===================================
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE
+ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O
+o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG
+4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi
+Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul
+Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs
+O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H
+vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L
+nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS
+FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa
+z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ
+KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk
+J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+
+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG
+/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc
+nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld
+blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc
+l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm
+7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm
+obp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+
+Buypass Class 2 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
+DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
+g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
+9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
+/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
+CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
+awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
+zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
+Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
+Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
+M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
+osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
+aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
+DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
+LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
+oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
+wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
+CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
+rJgWVqA=
+-----END CERTIFICATE-----
+
+Buypass Class 3 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
+DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
+sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
+5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
+7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
+ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
+2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
+/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
+RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
+Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
+j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
+uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
+Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
+ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
+KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
+6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
+UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
+eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
+Cp/HuZc=
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 3
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
+MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
+9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
+NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
+iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
+0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
+AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
+fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
+ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
+P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
+-----END CERTIFICATE-----
+
+EE Certification Centre Root CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy
+dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw
+MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB
+UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy
+ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM
+TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2
+rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw
+93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN
+P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ
+MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
+BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj
+xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM
+lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU
+3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM
+dcGWxZ0=
+-----END CERTIFICATE-----
diff --git a/libs/tornado/escape.py b/libs/tornado/escape.py
index 91c3e740..4e2d20d7 100755
--- a/libs/tornado/escape.py
+++ b/libs/tornado/escape.py
@@ -49,8 +49,9 @@ try:
except NameError:
unichr = chr
-_XHTML_ESCAPE_RE = re.compile('[&<>"]')
-_XHTML_ESCAPE_DICT = {'&': '&', '<': '<', '>': '>', '"': '"'}
+_XHTML_ESCAPE_RE = re.compile('[&<>"\']')
+_XHTML_ESCAPE_DICT = {'&': '&', '<': '<', '>': '>', '"': '"',
+ '\'': '''}
def xhtml_escape(value):
diff --git a/libs/tornado/httpclient.py b/libs/tornado/httpclient.py
index a34eb66b..67675894 100755
--- a/libs/tornado/httpclient.py
+++ b/libs/tornado/httpclient.py
@@ -33,7 +33,7 @@ import functools
import time
import weakref
-from tornado.concurrent import Future
+from tornado.concurrent import TracebackFuture
from tornado.escape import utf8
from tornado import httputil, stack_context
from tornado.ioloop import IOLoop
@@ -144,9 +144,16 @@ class AsyncHTTPClient(Configurable):
def close(self):
"""Destroys this HTTP client, freeing any file descriptors used.
- Not needed in normal use, but may be helpful in unittests that
- create and destroy http clients. No other methods may be called
- on the `AsyncHTTPClient` after ``close()``.
+
+ This method is **not needed in normal use** due to the way
+ that `AsyncHTTPClient` objects are transparently reused.
+ ``close()`` is generally only necessary when either the
+ `.IOLoop` is also being closed, or the ``force_instance=True``
+ argument was used when creating the `AsyncHTTPClient`.
+
+ No other methods may be called on the `AsyncHTTPClient` after
+ ``close()``.
+
"""
if self._async_clients().get(self.io_loop) is self:
del self._async_clients()[self.io_loop]
@@ -174,7 +181,7 @@ class AsyncHTTPClient(Configurable):
# where normal dicts get converted to HTTPHeaders objects.
request.headers = httputil.HTTPHeaders(request.headers)
request = _RequestProxy(request, self.defaults)
- future = Future()
+ future = TracebackFuture()
if callback is not None:
callback = stack_context.wrap(callback)
diff --git a/libs/tornado/ioloop.py b/libs/tornado/ioloop.py
index 5f37032f..91ee2c5b 100755
--- a/libs/tornado/ioloop.py
+++ b/libs/tornado/ioloop.py
@@ -59,6 +59,9 @@ except ImportError:
from tornado.platform.auto import set_close_exec, Waker
+_POLL_TIMEOUT = 3600.0
+
+
class TimeoutError(Exception):
pass
@@ -356,7 +359,7 @@ class IOLoop(Configurable):
if isinstance(result, Future):
future_cell[0] = result
else:
- future_cell[0] = Future()
+ future_cell[0] = TracebackFuture()
future_cell[0].set_result(result)
self.add_future(future_cell[0], lambda future: self.stop())
self.add_callback(run)
@@ -596,7 +599,7 @@ class PollIOLoop(IOLoop):
pass
while True:
- poll_timeout = 3600.0
+ poll_timeout = _POLL_TIMEOUT
# Prevent IO event starvation by delaying new callbacks
# to the next iteration of the event loop.
@@ -605,6 +608,9 @@ class PollIOLoop(IOLoop):
self._callbacks = []
for callback in callbacks:
self._run_callback(callback)
+ # Closures may be holding on to a lot of memory, so allow
+ # them to be freed before we go into our poll wait.
+ callbacks = callback = None
if self._timeouts:
now = self.time()
@@ -616,6 +622,7 @@ class PollIOLoop(IOLoop):
elif self._timeouts[0].deadline <= now:
timeout = heapq.heappop(self._timeouts)
self._run_callback(timeout.callback)
+ del timeout
else:
seconds = self._timeouts[0].deadline - now
poll_timeout = min(seconds, poll_timeout)
@@ -669,17 +676,16 @@ class PollIOLoop(IOLoop):
while self._events:
fd, events = self._events.popitem()
try:
- self._handlers[fd](fd, events)
+ if self._handlers.has_key(fd):
+ self._handlers[fd](fd, events)
except (OSError, IOError) as e:
if e.args[0] == errno.EPIPE:
# Happens when the client closes the connection
pass
else:
- app_log.error("Exception in I/O handler for fd %s",
- fd, exc_info=True)
+ self.handle_callback_exception(self._handlers.get(fd))
except Exception:
- app_log.error("Exception in I/O handler for fd %s",
- fd, exc_info=True)
+ self.handle_callback_exception(self._handlers.get(fd))
# reset the stopped flag so another start/stop pair can be issued
self._stopped = False
if self._blocking_signal_threshold is not None:
@@ -717,14 +723,14 @@ class PollIOLoop(IOLoop):
list_empty = not self._callbacks
self._callbacks.append(functools.partial(
stack_context.wrap(callback), *args, **kwargs))
- if list_empty and thread.get_ident() != self._thread_ident:
- # If we're in the IOLoop's thread, we know it's not currently
- # polling. If we're not, and we added the first callback to an
- # empty list, we may need to wake it up (it may wake up on its
- # own, but an occasional extra wake is harmless). Waking
- # up a polling IOLoop is relatively expensive, so we try to
- # avoid it when we can.
- self._waker.wake()
+ if list_empty and thread.get_ident() != self._thread_ident:
+ # If we're in the IOLoop's thread, we know it's not currently
+ # polling. If we're not, and we added the first callback to an
+ # empty list, we may need to wake it up (it may wake up on its
+ # own, but an occasional extra wake is harmless). Waking
+ # up a polling IOLoop is relatively expensive, so we try to
+ # avoid it when we can.
+ self._waker.wake()
def add_callback_from_signal(self, callback, *args, **kwargs):
with stack_context.NullContext():
@@ -813,7 +819,7 @@ class PeriodicCallback(object):
try:
self.callback()
except Exception:
- app_log.error("Error in periodic callback", exc_info=True)
+ self.io_loop.handle_callback_exception(self.callback)
self._schedule_next()
def _schedule_next(self):
diff --git a/libs/tornado/iostream.py b/libs/tornado/iostream.py
index 079012c9..6bdc6397 100755
--- a/libs/tornado/iostream.py
+++ b/libs/tornado/iostream.py
@@ -46,6 +46,14 @@ try:
except ImportError:
_set_nonblocking = None
+# These errnos indicate that a non-blocking operation must be retried
+# at a later time. On most platforms they're the same value, but on
+# some they differ.
+_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
+
+# These errnos indicate that a connection has been abruptly terminated.
+# They should be caught and handled less noisily than other errors.
+_ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE)
class StreamClosedError(IOError):
"""Exception raised by `IOStream` methods when the stream is closed.
@@ -257,15 +265,19 @@ class BaseIOStream(object):
self._maybe_run_close_callback()
def _maybe_run_close_callback(self):
- if (self.closed() and self._close_callback and
- self._pending_callbacks == 0):
- # if there are pending callbacks, don't run the close callback
- # until they're done (see _maybe_add_error_handler)
- cb = self._close_callback
- self._close_callback = None
- self._run_callback(cb)
+ # If there are pending callbacks, don't run the close callback
+ # until they're done (see _maybe_add_error_handler)
+ if self.closed() and self._pending_callbacks == 0:
+ if self._close_callback is not None:
+ cb = self._close_callback
+ self._close_callback = None
+ self._run_callback(cb)
# Delete any unfinished callbacks to break up reference cycles.
self._read_callback = self._write_callback = None
+ # Clear the buffers so they can be cleared immediately even
+ # if the IOStream object is kept alive by a reference cycle.
+ # TODO: Clear the read buffer too; it currently breaks some tests.
+ self._write_buffer = None
def reading(self):
"""Returns true if we are currently reading from the stream."""
@@ -447,7 +459,7 @@ class BaseIOStream(object):
chunk = self.read_from_fd()
except (socket.error, IOError, OSError) as e:
# ssl.SSLError is a subclass of socket.error
- if e.args[0] == errno.ECONNRESET:
+ if e.args[0] in _ERRNO_CONNRESET:
# Treat ECONNRESET as a connection close rather than
# an error to minimize log spam (the exception will
# be available on self.error for apps that care).
@@ -550,12 +562,12 @@ class BaseIOStream(object):
self._write_buffer_frozen = False
_merge_prefix(self._write_buffer, num_bytes)
self._write_buffer.popleft()
- except socket.error as e:
- if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ except (socket.error, IOError, OSError) as e:
+ if e.args[0] in _ERRNO_WOULDBLOCK:
self._write_buffer_frozen = True
break
else:
- if e.args[0] not in (errno.EPIPE, errno.ECONNRESET):
+ if e.args[0] not in _ERRNO_CONNRESET:
# Broken pipe errors are usually caused by connection
# reset, and its better to not log EPIPE errors to
# minimize log spam
@@ -682,7 +694,7 @@ class IOStream(BaseIOStream):
try:
chunk = self.socket.recv(self.read_chunk_size)
except socket.error as e:
- if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ if e.args[0] in _ERRNO_WOULDBLOCK:
return None
else:
raise
@@ -725,7 +737,8 @@ class IOStream(BaseIOStream):
# returned immediately when attempting to connect to
# localhost, so handle them the same way as an error
# reported later in _handle_connect.
- if e.args[0] not in (errno.EINPROGRESS, errno.EWOULDBLOCK):
+ if (e.args[0] != errno.EINPROGRESS and
+ e.args[0] not in _ERRNO_WOULDBLOCK):
gen_log.warning("Connect error on fd %d: %s",
self.socket.fileno(), e)
self.close(exc_info=True)
@@ -789,6 +802,17 @@ class SSLIOStream(IOStream):
self._ssl_connect_callback = None
self._server_hostname = None
+ # If the socket is already connected, attempt to start the handshake.
+ try:
+ self.socket.getpeername()
+ except socket.error:
+ pass
+ else:
+ # Indirectly start the handshake, which will run on the next
+ # IOLoop iteration and then the real IO state will be set in
+ # _handle_events.
+ self._add_io_state(self.io_loop.WRITE)
+
def reading(self):
return self._handshake_reading or super(SSLIOStream, self).reading()
@@ -821,7 +845,7 @@ class SSLIOStream(IOStream):
return self.close(exc_info=True)
raise
except socket.error as err:
- if err.args[0] in (errno.ECONNABORTED, errno.ECONNRESET):
+ if err.args[0] in _ERRNO_CONNRESET:
return self.close(exc_info=True)
except AttributeError:
# On Linux, if the connection was reset before the call to
@@ -917,7 +941,7 @@ class SSLIOStream(IOStream):
else:
raise
except socket.error as e:
- if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ if e.args[0] in _ERRNO_WOULDBLOCK:
return None
else:
raise
@@ -953,7 +977,7 @@ class PipeIOStream(BaseIOStream):
try:
chunk = os.read(self.fd, self.read_chunk_size)
except (IOError, OSError) as e:
- if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
+ if e.args[0] in _ERRNO_WOULDBLOCK:
return None
elif e.args[0] == errno.EBADF:
# If the writing half of a pipe is closed, select will
diff --git a/libs/tornado/netutil.py b/libs/tornado/netutil.py
index 37037180..9dc8506e 100755
--- a/libs/tornado/netutil.py
+++ b/libs/tornado/netutil.py
@@ -159,6 +159,10 @@ def is_valid_ip(ip):
Supports IPv4 and IPv6.
"""
+ if not ip or '\x00' in ip:
+ # getaddrinfo resolves empty strings to localhost, and truncates
+ # on zero bytes.
+ return False
try:
res = socket.getaddrinfo(ip, 0, socket.AF_UNSPEC,
socket.SOCK_STREAM,
diff --git a/libs/tornado/process.py b/libs/tornado/process.py
index 9bc193c0..ffd2d29d 100755
--- a/libs/tornado/process.py
+++ b/libs/tornado/process.py
@@ -190,23 +190,34 @@ class Subprocess(object):
def __init__(self, *args, **kwargs):
self.io_loop = kwargs.pop('io_loop', None) or ioloop.IOLoop.current()
+ # All FDs we create should be closed on error; those in to_close
+ # should be closed in the parent process on success.
+ pipe_fds = []
to_close = []
if kwargs.get('stdin') is Subprocess.STREAM:
in_r, in_w = _pipe_cloexec()
kwargs['stdin'] = in_r
+ pipe_fds.extend((in_r, in_w))
to_close.append(in_r)
self.stdin = PipeIOStream(in_w, io_loop=self.io_loop)
if kwargs.get('stdout') is Subprocess.STREAM:
out_r, out_w = _pipe_cloexec()
kwargs['stdout'] = out_w
+ pipe_fds.extend((out_r, out_w))
to_close.append(out_w)
self.stdout = PipeIOStream(out_r, io_loop=self.io_loop)
if kwargs.get('stderr') is Subprocess.STREAM:
err_r, err_w = _pipe_cloexec()
kwargs['stderr'] = err_w
+ pipe_fds.extend((err_r, err_w))
to_close.append(err_w)
self.stderr = PipeIOStream(err_r, io_loop=self.io_loop)
- self.proc = subprocess.Popen(*args, **kwargs)
+ try:
+ self.proc = subprocess.Popen(*args, **kwargs)
+ except:
+ for fd in pipe_fds:
+ os.close(fd)
+ raise
for fd in to_close:
os.close(fd)
for attr in ['stdin', 'stdout', 'stderr', 'pid']:
diff --git a/libs/tornado/template.py b/libs/tornado/template.py
index 341e07c6..77fd579e 100755
--- a/libs/tornado/template.py
+++ b/libs/tornado/template.py
@@ -169,6 +169,10 @@ with ``{# ... #}``.
{% module Template("foo.html", arg=42) %}
+ ``UIModules`` are a feature of the `tornado.web.RequestHandler`
+ class (and specifically its ``render`` method) and will not work
+ when the template system is used on its own in other contexts.
+
``{% raw *expr* %}``
Outputs the result of the given expression without autoescaping.
diff --git a/libs/tornado/web.py b/libs/tornado/web.py
index eade230e..5f8d6091 100755
--- a/libs/tornado/web.py
+++ b/libs/tornado/web.py
@@ -437,15 +437,25 @@ class RequestHandler(object):
morsel[k] = v
def clear_cookie(self, name, path="/", domain=None):
- """Deletes the cookie with the given name."""
+ """Deletes the cookie with the given name.
+
+ Due to limitations of the cookie protocol, you must pass the same
+ path and domain to clear a cookie as were used when that cookie
+ was set (but there is no way to find out on the server side
+ which values were used for a given cookie).
+ """
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
self.set_cookie(name, value="", path=path, expires=expires,
domain=domain)
- def clear_all_cookies(self):
- """Deletes all the cookies the user sent with this request."""
+ def clear_all_cookies(self, path="/", domain=None):
+ """Deletes all the cookies the user sent with this request.
+
+ See `clear_cookie` for more information on the path and domain
+ parameters.
+ """
for name in self.request.cookies:
- self.clear_cookie(name)
+ self.clear_cookie(name, path=path, domain=domain)
def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
"""Signs and timestamps a cookie so it cannot be forged.
@@ -751,10 +761,10 @@ class RequestHandler(object):
if hasattr(self.request, "connection"):
# Now that the request is finished, clear the callback we
- # set on the IOStream (which would otherwise prevent the
+ # set on the HTTPConnection (which would otherwise prevent the
# garbage collection of the RequestHandler when there
# are keepalive connections)
- self.request.connection.stream.set_close_callback(None)
+ self.request.connection.set_close_callback(None)
if not self.application._wsgi:
self.flush(include_footers=True)
@@ -1142,7 +1152,7 @@ class RequestHandler(object):
elif isinstance(result, Future):
if result.done():
if result.result() is not None:
- raise ValueError('Expected None, got %r' % result)
+ raise ValueError('Expected None, got %r' % result.result())
callback()
else:
# Delayed import of IOLoop because it's not available
@@ -1827,6 +1837,10 @@ class StaticFileHandler(RequestHandler):
return
if start is not None and start < 0:
start += size
+ if end is not None and end > size:
+ # Clients sometimes blindly use a large range to limit their
+ # download size; cap the endpoint at the actual file size.
+ end = size
# Note: only return HTTP 206 if less than the entire range has been
# requested. Not only is this semantically correct, but Chrome
# refuses to play audio if it gets an HTTP 206 in response to
@@ -2305,9 +2319,12 @@ class UIModule(object):
self.handler = handler
self.request = handler.request
self.ui = handler.ui
- self.current_user = handler.current_user
self.locale = handler.locale
+ @property
+ def current_user(self):
+ return self.handler.current_user
+
def render(self, *args, **kwargs):
"""Overridden in subclasses to return this module's output."""
raise NotImplementedError()
diff --git a/libs/tornado/websocket.py b/libs/tornado/websocket.py
index 1eef4019..676d21bf 100755
--- a/libs/tornado/websocket.py
+++ b/libs/tornado/websocket.py
@@ -31,7 +31,7 @@ import time
import tornado.escape
import tornado.web
-from tornado.concurrent import Future
+from tornado.concurrent import TracebackFuture
from tornado.escape import utf8, native_str
from tornado import httpclient
from tornado.ioloop import IOLoop
@@ -51,6 +51,10 @@ class WebSocketError(Exception):
pass
+class WebSocketClosedError(WebSocketError):
+ pass
+
+
class WebSocketHandler(tornado.web.RequestHandler):
"""Subclass this class to create a basic WebSocket handler.
@@ -160,6 +164,8 @@ class WebSocketHandler(tornado.web.RequestHandler):
message will be sent as utf8; in binary mode any byte string
is allowed.
"""
+ if self.ws_connection is None:
+ raise WebSocketClosedError()
if isinstance(message, dict):
message = tornado.escape.json_encode(message)
self.ws_connection.write_message(message, binary=binary)
@@ -195,6 +201,8 @@ class WebSocketHandler(tornado.web.RequestHandler):
def ping(self, data):
"""Send ping frame to the remote end."""
+ if self.ws_connection is None:
+ raise WebSocketClosedError()
self.ws_connection.write_ping(data)
def on_pong(self, data):
@@ -210,8 +218,9 @@ class WebSocketHandler(tornado.web.RequestHandler):
Once the close handshake is successful the socket will be closed.
"""
- self.ws_connection.close()
- self.ws_connection = None
+ if self.ws_connection:
+ self.ws_connection.close()
+ self.ws_connection = None
def allow_draft76(self):
"""Override to enable support for the older "draft76" protocol.
@@ -764,7 +773,7 @@ class WebSocketProtocol13(WebSocketProtocol):
class WebSocketClientConnection(simple_httpclient._HTTPConnection):
"""WebSocket client connection."""
def __init__(self, io_loop, request):
- self.connect_future = Future()
+ self.connect_future = TracebackFuture()
self.read_future = None
self.read_queue = collections.deque()
self.key = base64.b64encode(os.urandom(16))
@@ -825,7 +834,7 @@ class WebSocketClientConnection(simple_httpclient._HTTPConnection):
ready.
"""
assert self.read_future is None
- future = Future()
+ future = TracebackFuture()
if self.read_queue:
future.set_result(self.read_queue.popleft())
else:
diff --git a/libs/tornado/wsgi.py b/libs/tornado/wsgi.py
index 6b5bfb8f..5e25a564 100755
--- a/libs/tornado/wsgi.py
+++ b/libs/tornado/wsgi.py
@@ -242,10 +242,12 @@ class WSGIContainer(object):
return response.append
app_response = self.wsgi_application(
WSGIContainer.environ(request), start_response)
- response.extend(app_response)
- body = b"".join(response)
- if hasattr(app_response, "close"):
- app_response.close()
+ try:
+ response.extend(app_response)
+ body = b"".join(response)
+ finally:
+ if hasattr(app_response, "close"):
+ app_response.close()
if not data:
raise Exception("WSGI app did not call start_response")
diff --git a/libs/unrar2/__init__.py b/libs/unrar2/__init__.py
new file mode 100644
index 00000000..b5c9a4d3
--- /dev/null
+++ b/libs/unrar2/__init__.py
@@ -0,0 +1,177 @@
+# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""
+pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll.
+
+It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple,
+stable and foolproof.
+Notice that it has INCOMPATIBLE interface.
+
+It enables reading and unpacking of archives created with the
+RAR/WinRAR archivers. There is a low-level interface which is very
+similar to the C interface provided by UnRAR. There is also a
+higher level interface which makes some common operations easier.
+"""
+
+__version__ = '0.99.2'
+
+try:
+ WindowsError
+ in_windows = True
+except NameError:
+ in_windows = False
+
+if in_windows:
+ from windows import RarFileImplementation
+else:
+ from unix import RarFileImplementation
+
+
+import fnmatch, time, weakref
+
+class RarInfo(object):
+ """Represents a file header in an archive. Don't instantiate directly.
+ Use only to obtain information about file.
+ YOU CANNOT EXTRACT FILE CONTENTS USING THIS OBJECT.
+ USE METHODS OF RarFile CLASS INSTEAD.
+
+ Properties:
+ index - index of file within the archive
+ filename - name of the file in the archive including path (if any)
+ datetime - file date/time as a struct_time suitable for time.strftime
+ isdir - True if the file is a directory
+ size - size in bytes of the uncompressed file
+ comment - comment associated with the file
+
+ Note - this is not currently intended to be a Python file-like object.
+ """
+
+ def __init__(self, rarfile, data):
+ self.rarfile = weakref.proxy(rarfile)
+ self.index = data['index']
+ self.filename = data['filename']
+ self.isdir = data['isdir']
+ self.size = data['size']
+ self.datetime = data['datetime']
+ self.comment = data['comment']
+
+
+
+ def __str__(self):
+ try :
+ arcName = self.rarfile.archiveName
+ except ReferenceError:
+ arcName = "[ARCHIVE_NO_LONGER_LOADED]"
+ return '' % (self.filename, arcName)
+
+class RarFile(RarFileImplementation):
+
+ def __init__(self, archiveName, password=None):
+ """Instantiate the archive.
+
+ archiveName is the name of the RAR file.
+ password is used to decrypt the files in the archive.
+
+ Properties:
+ comment - comment associated with the archive
+
+ >>> print RarFile('test.rar').comment
+ This is a test.
+ """
+ self.archiveName = archiveName
+ RarFileImplementation.init(self, password)
+
+ def __del__(self):
+ self.destruct()
+
+ def infoiter(self):
+ """Iterate over all the files in the archive, generating RarInfos.
+
+ >>> import os
+ >>> for fileInArchive in RarFile('test.rar').infoiter():
+ ... print os.path.split(fileInArchive.filename)[-1],
+ ... print fileInArchive.isdir,
+ ... print fileInArchive.size,
+ ... print fileInArchive.comment,
+ ... print tuple(fileInArchive.datetime)[0:5],
+ ... print time.strftime('%a, %d %b %Y %H:%M', fileInArchive.datetime)
+ test True 0 None (2003, 6, 30, 1, 59) Mon, 30 Jun 2003 01:59
+ test.txt False 20 None (2003, 6, 30, 2, 1) Mon, 30 Jun 2003 02:01
+ this.py False 1030 None (2002, 2, 8, 16, 47) Fri, 08 Feb 2002 16:47
+ """
+ for params in RarFileImplementation.infoiter(self):
+ yield RarInfo(self, params)
+
+ def infolist(self):
+ """Return a list of RarInfos, descripting the contents of the archive."""
+ return list(self.infoiter())
+
+ def read_files(self, condition='*'):
+ """Read specific files from archive into memory.
+ If "condition" is a list of numbers, then return files which have those positions in infolist.
+ If "condition" is a string, then it is treated as a wildcard for names of files to extract.
+ If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
+ and returns boolean True (extract) or False (skip).
+ If "condition" is omitted, all files are returned.
+
+ Returns list of tuples (RarInfo info, str contents)
+ """
+ checker = condition2checker(condition)
+ return RarFileImplementation.read_files(self, checker)
+
+
+ def extract(self, condition='*', path='.', withSubpath=True, overwrite=True):
+ """Extract specific files from archive to disk.
+
+ If "condition" is a list of numbers, then extract files which have those positions in infolist.
+ If "condition" is a string, then it is treated as a wildcard for names of files to extract.
+ If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object
+ and returns either boolean True (extract) or boolean False (skip).
+ DEPRECATED: If "condition" callback returns string (only supported for Windows) -
+ that string will be used as a new name to save the file under.
+ If "condition" is omitted, all files are extracted.
+
+ "path" is a directory to extract to
+ "withSubpath" flag denotes whether files are extracted with their full path in the archive.
+ "overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true.
+
+ Returns list of RarInfos for extracted files."""
+ checker = condition2checker(condition)
+ return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite)
+
+def condition2checker(condition):
+ """Converts different condition types to callback"""
+ if type(condition) in [str, unicode]:
+ def smatcher(info):
+ return fnmatch.fnmatch(info.filename, condition)
+ return smatcher
+ elif type(condition) in [list, tuple] and type(condition[0]) in [int, long]:
+ def imatcher(info):
+ return info.index in condition
+ return imatcher
+ elif callable(condition):
+ return condition
+ else:
+ raise TypeError
+
+
diff --git a/libs/unrar2/rar_exceptions.py b/libs/unrar2/rar_exceptions.py
new file mode 100644
index 00000000..d90d1c8d
--- /dev/null
+++ b/libs/unrar2/rar_exceptions.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Low level interface - see UnRARDLL\UNRARDLL.TXT
+
+
+class ArchiveHeaderBroken(Exception): pass
+class InvalidRARArchive(Exception): pass
+class FileOpenError(Exception): pass
+class IncorrectRARPassword(Exception): pass
+class InvalidRARArchiveUsage(Exception): pass
diff --git a/libs/unrar2/unix.py b/libs/unrar2/unix.py
new file mode 100644
index 00000000..21f384cf
--- /dev/null
+++ b/libs/unrar2/unix.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Unix version uses unrar command line executable
+
+import subprocess
+import gc
+
+import os, os.path
+import time, re
+
+from rar_exceptions import *
+
+class UnpackerNotInstalled(Exception): pass
+
+rar_executable_cached = None
+
+def call_unrar(params):
+ "Calls rar/unrar command line executable, returns stdout pipe"
+ global rar_executable_cached
+ if rar_executable_cached is None:
+ for command in ('unrar', 'rar', os.path.join(os.path.dirname(__file__), 'unrar')):
+ try:
+ subprocess.Popen([command], stdout = subprocess.PIPE)
+ rar_executable_cached = command
+ break
+ except OSError:
+ pass
+ if rar_executable_cached is None:
+ raise UnpackerNotInstalled("No suitable RAR unpacker installed")
+
+ assert type(params) == list, "params must be list"
+ args = [rar_executable_cached] + params
+ try:
+ gc.disable() # See http://bugs.python.org/issue1336
+ return subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ finally:
+ gc.enable()
+
+class RarFileImplementation(object):
+
+ def init(self, password = None):
+ self.password = password
+
+
+
+ stdoutdata, stderrdata = self.call('v', []).communicate()
+
+ for line in stderrdata.splitlines():
+ if line.strip().startswith("Cannot open"):
+ raise FileOpenError
+ if line.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
+ accum = []
+ source = iter(stdoutdata.splitlines())
+ line = ''
+ while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')):
+ if line.strip().endswith('is not RAR archive'):
+ raise InvalidRARArchive
+ line = source.next()
+ while not line.startswith('Pathname/Comment'):
+ accum.append(line.rstrip('\n'))
+ line = source.next()
+ if len(accum):
+ accum[0] = accum[0][9:]
+ self.comment = '\n'.join(accum[:-1])
+ else:
+ self.comment = None
+
+ def escaped_password(self):
+ return '-' if self.password == None else self.password
+
+
+ def call(self, cmd, options = [], files = []):
+ options2 = options + ['p' + self.escaped_password()]
+ soptions = ['-' + x for x in options2]
+ return call_unrar([cmd] + soptions + ['--', self.archiveName] + files)
+
+ def infoiter(self):
+
+ stdoutdata, stderrdata = self.call('v', ['c-']).communicate()
+
+ for line in stderrdata.splitlines():
+ if line.strip().startswith("Cannot open"):
+ raise FileOpenError
+
+ accum = []
+ source = iter(stdoutdata.splitlines())
+ line = ''
+ while not line.startswith('--------------'):
+ if line.strip().endswith('is not RAR archive'):
+ raise InvalidRARArchive
+ if line.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
+ line = source.next()
+ line = source.next()
+ i = 0
+ re_spaces = re.compile(r"\s+")
+ while not line.startswith('--------------'):
+ accum.append(line)
+ if len(accum) == 2:
+ data = {}
+ data['index'] = i
+ data['filename'] = accum[0].strip()
+ info = re_spaces.split(accum[1].strip())
+ data['size'] = int(info[0])
+ attr = info[5]
+ data['isdir'] = 'd' in attr.lower()
+ data['datetime'] = time.strptime(info[3] + " " + info[4], '%d-%m-%y %H:%M')
+ data['comment'] = None
+ yield data
+ accum = []
+ i += 1
+ line = source.next()
+
+ def read_files(self, checker):
+ res = []
+ for info in self.infoiter():
+ checkres = checker(info)
+ if checkres == True and not info.isdir:
+ pipe = self.call('p', ['inul'], [info.filename]).stdout
+ res.append((info, pipe.read()))
+ return res
+
+
+ def extract(self, checker, path, withSubpath, overwrite):
+ res = []
+ command = 'x'
+ if not withSubpath:
+ command = 'e'
+ options = []
+ if overwrite:
+ options.append('o+')
+ else:
+ options.append('o-')
+ if not path.endswith(os.sep):
+ path += os.sep
+ names = []
+ for info in self.infoiter():
+ checkres = checker(info)
+ if type(checkres) in [str, unicode]:
+ raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows")
+ if checkres == True and not info.isdir:
+ names.append(info.filename)
+ res.append(info)
+ names.append(path)
+ proc = self.call(command, options, names)
+ stdoutdata, stderrdata = proc.communicate()
+ if stderrdata.find("CRC failed") >= 0:
+ raise IncorrectRARPassword
+ return res
+
+ def destruct(self):
+ pass
+
+
diff --git a/libs/unrar2/unrar b/libs/unrar2/unrar
new file mode 100755
index 00000000..3de16761
Binary files /dev/null and b/libs/unrar2/unrar differ
diff --git a/libs/unrar2/unrar.dll b/libs/unrar2/unrar.dll
new file mode 100644
index 00000000..9757bf3d
Binary files /dev/null and b/libs/unrar2/unrar.dll differ
diff --git a/libs/unrar2/unrar64.dll b/libs/unrar2/unrar64.dll
new file mode 100644
index 00000000..e17a19e5
Binary files /dev/null and b/libs/unrar2/unrar64.dll differ
diff --git a/libs/unrar2/windows.py b/libs/unrar2/windows.py
new file mode 100644
index 00000000..e249f8f3
--- /dev/null
+++ b/libs/unrar2/windows.py
@@ -0,0 +1,314 @@
+# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# Low level interface - see UnRARDLL\UNRARDLL.TXT
+
+from __future__ import generators
+from couchpotato.environment import Env
+from shutil import copyfile
+import ctypes.wintypes
+import os.path
+import time
+
+from rar_exceptions import *
+
+ERAR_END_ARCHIVE = 10
+ERAR_NO_MEMORY = 11
+ERAR_BAD_DATA = 12
+ERAR_BAD_ARCHIVE = 13
+ERAR_UNKNOWN_FORMAT = 14
+ERAR_EOPEN = 15
+ERAR_ECREATE = 16
+ERAR_ECLOSE = 17
+ERAR_EREAD = 18
+ERAR_EWRITE = 19
+ERAR_SMALL_BUF = 20
+ERAR_UNKNOWN = 21
+
+RAR_OM_LIST = 0
+RAR_OM_EXTRACT = 1
+
+RAR_SKIP = 0
+RAR_TEST = 1
+RAR_EXTRACT = 2
+
+RAR_VOL_ASK = 0
+RAR_VOL_NOTIFY = 1
+
+RAR_DLL_VERSION = 3
+
+# enum UNRARCALLBACK_MESSAGES
+UCM_CHANGEVOLUME = 0
+UCM_PROCESSDATA = 1
+UCM_NEEDPASSWORD = 2
+
+architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8
+dll_name = "unrar.dll"
+if architecture_bits == 64:
+ dll_name = "unrar64.dll"
+
+# Copy dll first
+dll_file = os.path.join(os.path.dirname(__file__), dll_name)
+dll_copy = os.path.join(Env.get('cache_dir'), 'copied.dll')
+
+if os.path.isfile(dll_copy):
+ os.remove(dll_copy)
+
+copyfile(dll_file, dll_copy)
+
+unrar = ctypes.WinDLL(dll_copy)
+
+
+class RAROpenArchiveDataEx(ctypes.Structure):
+ def __init__(self, ArcName=None, ArcNameW=u'', OpenMode=RAR_OM_LIST):
+ self.CmtBuf = ctypes.c_buffer(64*1024)
+ ctypes.Structure.__init__(self, ArcName=ArcName, ArcNameW=ArcNameW, OpenMode=OpenMode, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
+
+ _fields_ = [
+ ('ArcName', ctypes.c_char_p),
+ ('ArcNameW', ctypes.c_wchar_p),
+ ('OpenMode', ctypes.c_uint),
+ ('OpenResult', ctypes.c_uint),
+ ('_CmtBuf', ctypes.c_voidp),
+ ('CmtBufSize', ctypes.c_uint),
+ ('CmtSize', ctypes.c_uint),
+ ('CmtState', ctypes.c_uint),
+ ('Flags', ctypes.c_uint),
+ ('Reserved', ctypes.c_uint*32),
+ ]
+
+class RARHeaderDataEx(ctypes.Structure):
+ def __init__(self):
+ self.CmtBuf = ctypes.c_buffer(64*1024)
+ ctypes.Structure.__init__(self, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf))
+
+ _fields_ = [
+ ('ArcName', ctypes.c_char*1024),
+ ('ArcNameW', ctypes.c_wchar*1024),
+ ('FileName', ctypes.c_char*1024),
+ ('FileNameW', ctypes.c_wchar*1024),
+ ('Flags', ctypes.c_uint),
+ ('PackSize', ctypes.c_uint),
+ ('PackSizeHigh', ctypes.c_uint),
+ ('UnpSize', ctypes.c_uint),
+ ('UnpSizeHigh', ctypes.c_uint),
+ ('HostOS', ctypes.c_uint),
+ ('FileCRC', ctypes.c_uint),
+ ('FileTime', ctypes.c_uint),
+ ('UnpVer', ctypes.c_uint),
+ ('Method', ctypes.c_uint),
+ ('FileAttr', ctypes.c_uint),
+ ('_CmtBuf', ctypes.c_voidp),
+ ('CmtBufSize', ctypes.c_uint),
+ ('CmtSize', ctypes.c_uint),
+ ('CmtState', ctypes.c_uint),
+ ('Reserved', ctypes.c_uint*1024),
+ ]
+
+def DosDateTimeToTimeTuple(dosDateTime):
+ """Convert an MS-DOS format date time to a Python time tuple.
+ """
+ dosDate = dosDateTime >> 16
+ dosTime = dosDateTime & 0xffff
+ day = dosDate & 0x1f
+ month = (dosDate >> 5) & 0xf
+ year = 1980 + (dosDate >> 9)
+ second = 2*(dosTime & 0x1f)
+ minute = (dosTime >> 5) & 0x3f
+ hour = dosTime >> 11
+ return time.localtime(time.mktime((year, month, day, hour, minute, second, 0, 1, -1)))
+
+def _wrap(restype, function, argtypes):
+ result = function
+ result.argtypes = argtypes
+ result.restype = restype
+ return result
+
+RARGetDllVersion = _wrap(ctypes.c_int, unrar.RARGetDllVersion, [])
+
+RAROpenArchiveEx = _wrap(ctypes.wintypes.HANDLE, unrar.RAROpenArchiveEx, [ctypes.POINTER(RAROpenArchiveDataEx)])
+
+RARReadHeaderEx = _wrap(ctypes.c_int, unrar.RARReadHeaderEx, [ctypes.wintypes.HANDLE, ctypes.POINTER(RARHeaderDataEx)])
+
+_RARSetPassword = _wrap(ctypes.c_int, unrar.RARSetPassword, [ctypes.wintypes.HANDLE, ctypes.c_char_p])
+def RARSetPassword(*args, **kwargs):
+ _RARSetPassword(*args, **kwargs)
+
+RARProcessFile = _wrap(ctypes.c_int, unrar.RARProcessFile, [ctypes.wintypes.HANDLE, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p])
+
+RARCloseArchive = _wrap(ctypes.c_int, unrar.RARCloseArchive, [ctypes.wintypes.HANDLE])
+
+UNRARCALLBACK = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint, ctypes.c_long, ctypes.c_long, ctypes.c_long)
+RARSetCallback = _wrap(ctypes.c_int, unrar.RARSetCallback, [ctypes.wintypes.HANDLE, UNRARCALLBACK, ctypes.c_long])
+
+
+
+RARExceptions = {
+ ERAR_NO_MEMORY : MemoryError,
+ ERAR_BAD_DATA : ArchiveHeaderBroken,
+ ERAR_BAD_ARCHIVE : InvalidRARArchive,
+ ERAR_EOPEN : FileOpenError,
+ }
+
+class PassiveReader:
+ """Used for reading files to memory"""
+ def __init__(self, usercallback = None):
+ self.buf = []
+ self.ucb = usercallback
+
+ def _callback(self, msg, UserData, P1, P2):
+ if msg == UCM_PROCESSDATA:
+ data = (ctypes.c_char*P2).from_address(P1).raw
+ if self.ucb!=None:
+ self.ucb(data)
+ else:
+ self.buf.append(data)
+ return 1
+
+ def get_result(self):
+ return ''.join(self.buf)
+
+class RarInfoIterator(object):
+ def __init__(self, arc):
+ self.arc = arc
+ self.index = 0
+ self.headerData = RARHeaderDataEx()
+ self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
+ if self.res==ERAR_BAD_DATA:
+ raise IncorrectRARPassword
+ self.arc.lockStatus = "locked"
+ self.arc.needskip = False
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self.index>0:
+ if self.arc.needskip:
+ RARProcessFile(self.arc._handle, RAR_SKIP, None, None)
+ self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData))
+
+ if self.res:
+ raise StopIteration
+ self.arc.needskip = True
+
+ data = {}
+ data['index'] = self.index
+ data['filename'] = self.headerData.FileName
+ data['datetime'] = DosDateTimeToTimeTuple(self.headerData.FileTime)
+ data['isdir'] = ((self.headerData.Flags & 0xE0) == 0xE0)
+ data['size'] = self.headerData.UnpSize + (self.headerData.UnpSizeHigh << 32)
+ if self.headerData.CmtState == 1:
+ data['comment'] = self.headerData.CmtBuf.value
+ else:
+ data['comment'] = None
+ self.index += 1
+ return data
+
+
+ def __del__(self):
+ self.arc.lockStatus = "finished"
+
+def generate_password_provider(password):
+ def password_provider_callback(msg, UserData, P1, P2):
+ if msg == UCM_NEEDPASSWORD and password!=None:
+ (ctypes.c_char*P2).from_address(P1).value = password
+ return 1
+ return password_provider_callback
+
+class RarFileImplementation(object):
+
+ def init(self, password=None):
+ self.password = password
+ archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT)
+ self._handle = RAROpenArchiveEx(ctypes.byref(archiveData))
+ self.c_callback = UNRARCALLBACK(generate_password_provider(self.password))
+ RARSetCallback(self._handle, self.c_callback, 1)
+
+ if archiveData.OpenResult != 0:
+ raise RARExceptions[archiveData.OpenResult]
+
+ if archiveData.CmtState == 1:
+ self.comment = archiveData.CmtBuf.value
+ else:
+ self.comment = None
+
+ if password:
+ RARSetPassword(self._handle, password)
+
+ self.lockStatus = "ready"
+
+
+
+ def destruct(self):
+ if self._handle and RARCloseArchive:
+ RARCloseArchive(self._handle)
+
+ def make_sure_ready(self):
+ if self.lockStatus == "locked":
+ raise InvalidRARArchiveUsage("cannot execute infoiter() without finishing previous one")
+ if self.lockStatus == "finished":
+ self.destruct()
+ self.init(self.password)
+
+ def infoiter(self):
+ self.make_sure_ready()
+ return RarInfoIterator(self)
+
+ def read_files(self, checker):
+ res = []
+ for info in self.infoiter():
+ if checker(info) and not info.isdir:
+ reader = PassiveReader()
+ c_callback = UNRARCALLBACK(reader._callback)
+ RARSetCallback(self._handle, c_callback, 1)
+ tmpres = RARProcessFile(self._handle, RAR_TEST, None, None)
+ if tmpres==ERAR_BAD_DATA:
+ raise IncorrectRARPassword
+ self.needskip = False
+ res.append((info, reader.get_result()))
+ return res
+
+
+ def extract(self, checker, path, withSubpath, overwrite):
+ res = []
+ for info in self.infoiter():
+ checkres = checker(info)
+ if checkres!=False and not info.isdir:
+ if checkres==True:
+ fn = info.filename
+ if not withSubpath:
+ fn = os.path.split(fn)[-1]
+ target = os.path.join(path, fn)
+ else:
+ raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows"
+ target = checkres
+ if overwrite or (not os.path.exists(target)):
+ tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target)
+ if tmpres==ERAR_BAD_DATA:
+ raise IncorrectRARPassword
+
+ self.needskip = False
+ res.append(info)
+ return res
+
+