From 263c9b671395dd46e523f3cbb0bf7eccb4fdfca3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=81Z=C3=81R=20Imre=20AI=20Agent?=
", "\n", $comment); // Replace HTML4
with single newlines
+ $comment = str_replace("
", "\n", $comment); // Replace HTML4
with single newlines
+ $comment = str_replace("
", "\n", $comment); // Replace HTML
with single newlines
+ $comment = str_replace("
+ Akeeba Backup $lbl_coreorpro $lbl_version +
+ + +ENDHTML; + } + + protected function createExtrainfo() + { + $abversion = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : AKEEBA_VERSION; + $host = Platform::getInstance()->get_host(); + $backupdate = gmdate('Y-m-d H:i:s'); + $phpversion = PHP_VERSION; + $rootPath = Platform::getInstance()->get_site_root(); + $ret = <<' . str_replace('#__', $this->db->getPrefix(), $this) . '';
+ }
+
+ /**
+ * Add a table name to the DELETE clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->delete('#__a')->where('id = 1');
+ *
+ * @param string $table The name of the table to delete from.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function delete($table = null)
+ {
+ $this->type = 'delete';
+ $this->delete = new QueryElement('DELETE', null);
+
+ if (!empty($table))
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to escape a string for usage in an SQL statement.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the escape method directly.
+ *
+ * Note that 'e' is an alias for this method as it is in DriverBase.
+ *
+ * @param string $text The string to be escaped.
+ * @param boolean $extra Optional parameter to provide extra escaping.
+ *
+ * @return string The escaped string.
+ */
+ public function escape($text, $extra = false)
+ {
+ if (!($this->db instanceof DriverBase))
+ {
+ throw new QueryException('Invalid database object');
+ }
+
+ return $this->db->escape($text, $extra);
+ }
+
+ /**
+ * Add a single column, or array of columns to the EXEC clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ * The exec method can, however, be called multiple times in the same query.
+ *
+ * Usage:
+ * $query->exec('a.*')->exec('b.id');
+ * $query->exec(array('a.*', 'b.id'));
+ *
+ * @param mixed $columns A string or an array of field names.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function exec($columns)
+ {
+ $this->type = 'exec';
+
+ if (is_null($this->exec))
+ {
+ $this->exec = new QueryElement('EXEC', $columns);
+ }
+ else
+ {
+ $this->exec->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a table to the FROM clause of the query.
+ *
+ * Note that while an array of tables can be provided, it is recommended you use explicit joins.
+ *
+ * Usage:
+ * $query->select('*')->from('#__a');
+ *
+ * @param mixed $tables A string or array of table names.
+ * This can be a Base object (or a child of it) when used
+ * as a subquery in FROM clause along with a value for $subQueryAlias.
+ * @param string $subQueryAlias Alias used when $tables is a Base.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function from($tables, $subQueryAlias = null)
+ {
+ if (is_null($this->from))
+ {
+ if ($tables instanceof $this)
+ {
+ if (is_null($subQueryAlias))
+ {
+ throw new QueryException('Null subquery defined');
+ }
+
+ $tables = '( ' . (string)$tables . ' ) AS ' . $this->quoteName($subQueryAlias);
+ }
+
+ $this->from = new QueryElement('FROM', $tables);
+ }
+ else
+ {
+ $this->from->append($tables);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Used to get a string to extract year from date column.
+ *
+ * Usage:
+ * $query->select($query->year($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing year to be extracted.
+ *
+ * @return string Returns string to extract year from a date.
+ */
+ public function year($date)
+ {
+ return 'YEAR(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract month from date column.
+ *
+ * Usage:
+ * $query->select($query->month($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing month to be extracted.
+ *
+ * @return string Returns string to extract month from a date.
+ */
+ public function month($date)
+ {
+ return 'MONTH(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract day from date column.
+ *
+ * Usage:
+ * $query->select($query->day($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing day to be extracted.
+ *
+ * @return string Returns string to extract day from a date.
+ */
+ public function day($date)
+ {
+ return 'DAY(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract hour from date column.
+ *
+ * Usage:
+ * $query->select($query->hour($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing hour to be extracted.
+ *
+ * @return string Returns string to extract hour from a date.
+ */
+ public function hour($date)
+ {
+ return 'HOUR(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract minute from date column.
+ *
+ * Usage:
+ * $query->select($query->minute($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing minute to be extracted.
+ *
+ * @return string Returns string to extract minute from a date.
+ */
+ public function minute($date)
+ {
+ return 'MINUTE(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract seconds from date column.
+ *
+ * Usage:
+ * $query->select($query->second($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing second to be extracted.
+ *
+ * @return string Returns string to extract second from a date.
+ */
+ public function second($date)
+ {
+ return 'SECOND(' . $date . ')';
+ }
+
+ /**
+ * Add a grouping column to the GROUP clause of the query.
+ *
+ * Usage:
+ * $query->group('id');
+ *
+ * @param mixed $columns A string or array of ordering columns.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function group($columns)
+ {
+ if (is_null($this->group))
+ {
+ $this->group = new QueryElement('GROUP BY', $columns);
+ }
+ else
+ {
+ $this->group->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * A conditions to the HAVING clause of the query.
+ *
+ * Usage:
+ * $query->group('id')->having('COUNT(id) > 5');
+ *
+ * @param mixed $conditions A string or array of columns.
+ * @param string $glue The glue by which to join the conditions. Defaults to AND.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function having($conditions, $glue = 'AND')
+ {
+ if (is_null($this->having))
+ {
+ $glue = strtoupper($glue);
+ $this->having = new QueryElement('HAVING', $conditions, " $glue ");
+ }
+ else
+ {
+ $this->having->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an INNER JOIN clause to the query.
+ *
+ * Usage:
+ * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function innerJoin($condition)
+ {
+ $this->join('INNER', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add a table name to the INSERT clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->insert('#__a')->set('id = 1');
+ * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4');
+ * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4'));
+ *
+ * @param mixed $table The name of the table to insert data into.
+ * @param boolean $incrementField The name of the field to auto increment.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function insert($table, $incrementField = false)
+ {
+ $this->type = 'insert';
+ $this->insert = new QueryElement('INSERT INTO', $table);
+ $this->autoIncrementField = $incrementField;
+
+ return $this;
+ }
+
+ /**
+ * Add a JOIN clause to the query.
+ *
+ * Usage:
+ * $query->join('INNER', 'b ON b.id = a.id);
+ *
+ * @param string $type The type of join. This string is prepended to the JOIN keyword.
+ * @param string $conditions A string or array of conditions.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function join($type, $conditions)
+ {
+ if (is_null($this->join))
+ {
+ $this->join = array();
+ }
+ $this->join[] = new QueryElement(strtoupper($type) . ' JOIN', $conditions);
+
+ return $this;
+ }
+
+ /**
+ * Add a LEFT JOIN clause to the query.
+ *
+ * Usage:
+ * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function leftJoin($condition)
+ {
+ $this->join('LEFT', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Get the length of a string in bytes.
+ *
+ * Note, use 'charLength' to find the number of characters in a string.
+ *
+ * Usage:
+ * query->where($query->length('a').' > 3');
+ *
+ * @param string $value The string to measure.
+ *
+ * @return int
+ */
+ public function length($value)
+ {
+ return 'LENGTH(' . $value . ')';
+ }
+
+ /**
+ * Get the null or zero representation of a timestamp for the database driver.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the nullDate method directly.
+ *
+ * Usage:
+ * $query->where('modified_date <> '.$query->nullDate());
+ *
+ * @param boolean $quoted Optionally wraps the null date in database quotes (true by default).
+ *
+ * @return string Null or zero representation of a timestamp.
+ */
+ public function nullDate($quoted = true)
+ {
+ if (!($this->db instanceof DriverBase))
+ {
+ throw new QueryException('Invalid database object');
+ }
+
+ $result = $this->db->getNullDate($quoted);
+
+ if ($quoted)
+ {
+ return $this->db->quote($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add a ordering column to the ORDER clause of the query.
+ *
+ * Usage:
+ * $query->order('foo')->order('bar');
+ * $query->order(array('foo','bar'));
+ *
+ * @param mixed $columns A string or array of ordering columns.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function order($columns)
+ {
+ if (is_null($this->order))
+ {
+ $this->order = new QueryElement('ORDER BY', $columns);
+ }
+ else
+ {
+ $this->order->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an OUTER JOIN clause to the query.
+ *
+ * Usage:
+ * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function outerJoin($condition)
+ {
+ $this->join('OUTER', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Method to quote and optionally escape a string to database requirements for insertion into the database.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the quote method directly.
+ *
+ * Note that 'q' is an alias for this method as it is in DriverBase.
+ *
+ * Usage:
+ * $query->quote('fulltext');
+ * $query->q('fulltext');
+ * $query->q(array('option', 'fulltext'));
+ *
+ * @param mixed $text A string or an array of strings to quote.
+ * @param boolean $escape True to escape the string, false to leave it unchanged.
+ *
+ * @return string The quoted input string.
+ *
+ * @throws QueryException if the internal db property is not a valid object.
+ */
+ public function quote($text, $escape = true)
+ {
+ if (!($this->db instanceof DriverBase))
+ {
+ throw new QueryException('Invalid database object');
+ }
+
+ return $this->db->quote($text, $escape);
+ }
+
+ /**
+ * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
+ * risks and reserved word conflicts.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the quoteName method directly.
+ *
+ * Note that 'qn' is an alias for this method as it is in DriverBase.
+ *
+ * Usage:
+ * $query->quoteName('#__a');
+ * $query->qn('#__a');
+ *
+ * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
+ * Each type supports dot-notation name.
+ * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has
+ * to be same length of $name; if is null there will not be any AS part for string or array
+ * element.
+ *
+ * @return mixed The quote wrapped name, same type of $name.
+ *
+ * @throws QueryException if the internal db property is not a valid object.
+ */
+ public function quoteName($name, $as = null)
+ {
+ if (!($this->db instanceof DriverBase))
+ {
+ throw new QueryException('Invalid database object');
+ }
+
+ return $this->db->quoteName($name, $as);
+ }
+
+ /**
+ * Add a RIGHT JOIN clause to the query.
+ *
+ * Usage:
+ * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function rightJoin($condition)
+ {
+ $this->join('RIGHT', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add a single column, or array of columns to the SELECT clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ * The select method can, however, be called multiple times in the same query.
+ *
+ * Usage:
+ * $query->select('a.*')->select('b.id');
+ * $query->select(array('a.*', 'b.id'));
+ *
+ * @param mixed $columns A string or an array of field names.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function select($columns)
+ {
+ $this->type = 'select';
+
+ if (is_null($this->select))
+ {
+ $this->select = new QueryElement('SELECT', $columns);
+ }
+ else
+ {
+ $this->select->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a single condition string, or an array of strings to the SET clause of the query.
+ *
+ * Usage:
+ * $query->set('a = 1')->set('b = 2');
+ * $query->set(array('a = 1', 'b = 2');
+ *
+ * @param mixed $conditions A string or array of string conditions.
+ * @param string $glue The glue by which to join the condition strings. Defaults to ,.
+ * Note that the glue is set on first use and cannot be changed.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function set($conditions, $glue = ',')
+ {
+ if (is_null($this->set))
+ {
+ $glue = strtoupper($glue);
+ $this->set = new QueryElement('SET', $conditions, "\n\t$glue ");
+ }
+ else
+ {
+ $this->set->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows a direct query to be provided to the database
+ * driver's setQuery() method, but still allow queries
+ * to have bounded variables.
+ *
+ * Usage:
+ * $query->setQuery('select * from #__users');
+ *
+ * @param mixed $query An SQL Query
+ *
+ * @return QueryElement Returns this object to allow chaining.
+ */
+ public function setQuery($query)
+ {
+ $this->sql = $query;
+
+ return $this;
+ }
+
+ /**
+ * Add a table name to the UPDATE clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->update('#__foo')->set(...);
+ *
+ * @param string $table A table to update.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function update($table)
+ {
+ $this->type = 'update';
+ $this->update = new QueryElement('UPDATE', $table);
+
+ return $this;
+ }
+
+ /**
+ * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement.
+ *
+ * Usage:
+ * $query->values('1,2,3')->values('4,5,6');
+ * $query->values(array('1,2,3', '4,5,6'));
+ *
+ * @param string $values A single tuple, or array of tuples.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function values($values)
+ {
+ if (is_null($this->values))
+ {
+ $this->values = new QueryElement('()', $values, '),(');
+ }
+ else
+ {
+ $this->values->append($values);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a single condition, or an array of conditions to the WHERE clause of the query.
+ *
+ * Usage:
+ * $query->where('a = 1')->where('b = 2');
+ * $query->where(array('a = 1', 'b = 2'));
+ *
+ * @param mixed $conditions A string or array of where conditions.
+ * @param string $glue The glue by which to join the conditions. Defaults to AND.
+ * Note that the glue is set on first use and cannot be changed.
+ *
+ * @return Base Returns this object to allow chaining.
+ */
+ public function where($conditions, $glue = 'AND')
+ {
+ if (is_null($this->where))
+ {
+ $glue = strtoupper($glue);
+ $this->where = new QueryElement('WHERE', $conditions, " $glue ");
+ }
+ else
+ {
+ $this->where->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to provide deep copy support to nested objects and
+ * arrays when cloning.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ foreach ($this as $k => $v)
+ {
+ if ($k === 'db')
+ {
+ continue;
+ }
+
+ if (is_object($v) || is_array($v))
+ {
+ $this->$k = unserialize(serialize($v));
+ }
+ }
+ }
+
+ /**
+ * Add a query to UNION with the current query.
+ * Multiple unions each require separate statements and create an array of unions.
+ *
+ * Usage:
+ * $query->union('SELECT name FROM #__foo')
+ * $query->union('SELECT name FROM #__foo','distinct')
+ * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar'))
+ *
+ * @param mixed $query The Base object or string to union.
+ * @param boolean $distinct True to only return distinct rows from the union.
+ * @param string $glue The glue by which to join the conditions.
+ *
+ * @return mixed The Base object on success or boolean false on failure.
+ */
+ public function union($query, $distinct = false, $glue = '')
+ {
+ // Clear any ORDER BY clause in UNION query
+ // See http://dev.mysql.com/doc/refman/5.0/en/union.html
+ if (!is_null($this->order))
+ {
+ $this->clear('order');
+ }
+
+ // Set up the DISTINCT flag, the name with parentheses, and the glue.
+ if ($distinct)
+ {
+ $name = 'UNION DISTINCT ()';
+ $glue = ')' . PHP_EOL . 'UNION DISTINCT (';
+ }
+ else
+ {
+ $glue = ')' . PHP_EOL . 'UNION (';
+ $name = 'UNION ()';
+ }
+
+ // Get the QueryElement if it does not exist
+ if (is_null($this->union))
+ {
+ $this->union = new QueryElement($name, $query, "$glue");
+ }
+ // Otherwise append the second UNION.
+ else
+ {
+ $glue = '';
+ $this->union->append($query);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a query to UNION DISTINCT with the current query. Simply a proxy to Union with the Distinct clause.
+ *
+ * Usage:
+ * $query->unionDistinct('SELECT name FROM #__foo')
+ *
+ * @param mixed $query The Base object or string to union.
+ * @param string $glue The glue by which to join the conditions.
+ *
+ * @return mixed The Base object on success or boolean false on failure.
+ */
+ public function unionDistinct($query, $glue = '')
+ {
+ $distinct = true;
+
+ // Apply the distinct flag to the union.
+ return $this->union($query, $distinct, $glue);
+ }
+
+ /**
+ * Find and replace sprintf-like tokens in a format string.
+ * Each token takes one of the following forms:
+ * %% - A literal percent character.
+ * %[t] - Where [t] is a type specifier.
+ * %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier.
+ *
+ * Types:
+ * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped.
+ * e - Escape: Replacement text is passed to $this->escape().
+ * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument.
+ * n - Name Quote: Replacement text is passed to $this->quoteName().
+ * q - Quote: Replacement text is passed to $this->quote().
+ * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument.
+ * r - Raw: Replacement text is used as-is. (Be careful)
+ *
+ * Date Types:
+ * - Replacement text automatically quoted (use uppercase for Name Quote).
+ * - Replacement text should be a string in date format or name of a date column.
+ * y/Y - Year
+ * m/M - Month
+ * d/D - Day
+ * h/H - Hour
+ * i/I - Minute
+ * s/S - Second
+ *
+ * Invariable Types:
+ * - Takes no argument.
+ * - Argument index not incremented.
+ * t - Replacement text is the result of $this->currentTimestamp().
+ * z - Replacement text is the result of $this->nullDate(false).
+ * Z - Replacement text is the result of $this->nullDate(true).
+ *
+ * Usage:
+ * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1);
+ * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1
+ *
+ * Notes:
+ * The argument specifier is optional but recommended for clarity.
+ * The argument index used for unspecified tokens is incremented only when used.
+ *
+ * @param string $format The formatting string.
+ *
+ * @return string Returns a string produced according to the formatting string.
+ */
+ public function format($format)
+ {
+ $query = $this;
+ $args = array_slice(func_get_args(), 1);
+ array_unshift($args, null);
+
+ $i = 1;
+ $func = function ($match) use ($query, $args, &$i)
+ {
+ if (isset($match[6]) && $match[6] == '%')
+ {
+ return '%';
+ }
+
+ // No argument required, do not increment the argument index.
+ switch ($match[5])
+ {
+ case 't':
+ return $query->currentTimestamp();
+ break;
+
+ case 'z':
+ return $query->nullDate(false);
+ break;
+
+ case 'Z':
+ return $query->nullDate(true);
+ break;
+ }
+
+ // Increment the argument index only if argument specifier not provided.
+ $index = is_numeric($match[4]) ? (int)$match[4] : $i++;
+
+ if (!$index || !isset($args[$index]))
+ {
+ // TODO - What to do? sprintf() throws a Warning in these cases.
+ $replacement = '';
+ }
+ else
+ {
+ $replacement = $args[$index];
+ }
+
+ switch ($match[5])
+ {
+ case 'a':
+ return 0 + $replacement;
+ break;
+
+ case 'e':
+ return $query->escape($replacement);
+ break;
+
+ case 'E':
+ return $query->escape($replacement, true);
+ break;
+
+ case 'n':
+ return $query->quoteName($replacement);
+ break;
+
+ case 'q':
+ return $query->quote($replacement);
+ break;
+
+ case 'Q':
+ return $query->quote($replacement, false);
+ break;
+
+ case 'r':
+ return $replacement;
+ break;
+
+ // Dates
+ case 'y':
+ return $query->year($query->quote($replacement));
+ break;
+
+ case 'Y':
+ return $query->year($query->quoteName($replacement));
+ break;
+
+ case 'm':
+ return $query->month($query->quote($replacement));
+ break;
+
+ case 'M':
+ return $query->month($query->quoteName($replacement));
+ break;
+
+ case 'd':
+ return $query->day($query->quote($replacement));
+ break;
+
+ case 'D':
+ return $query->day($query->quoteName($replacement));
+ break;
+
+ case 'h':
+ return $query->hour($query->quote($replacement));
+ break;
+
+ case 'H':
+ return $query->hour($query->quoteName($replacement));
+ break;
+
+ case 'i':
+ return $query->minute($query->quote($replacement));
+ break;
+
+ case 'I':
+ return $query->minute($query->quoteName($replacement));
+ break;
+
+ case 's':
+ return $query->second($query->quote($replacement));
+ break;
+
+ case 'S':
+ return $query->second($query->quoteName($replacement));
+ break;
+ }
+
+ return '';
+ };
+
+ /**
+ * Regexp to find an replace all tokens.
+ * Matched fields:
+ * 0: Full token
+ * 1: Everything following '%'
+ * 2: Everything following '%' unless '%'
+ * 3: Argument specifier and '$'
+ * 4: Argument specifier
+ * 5: Type specifier
+ * 6: '%' if full token is '%%'
+ */
+
+ return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format);
+ }
+
+ /**
+ * Add to the current date and time.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ * Note: Not all drivers support all units.
+ *
+ * @param string $date The SQL-formatted date to add to. May be a date or datetime string.
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @see http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ return trim("DATE_ADD('" . $date . "', INTERVAL " . $interval . ' ' . $datePart . ')');
+ }
+
+ /**
+ * Add a query to UNION ALL with the current query.
+ * Multiple unions each require separate statements and create an array of unions.
+ *
+ * Usage:
+ * $query->union('SELECT name FROM #__foo')
+ * $query->union('SELECT name FROM #__foo','distinct')
+ * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar'))
+ *
+ * @param mixed $query The Base object or string to union.
+ * @param boolean $distinct True to only return distinct rows from the union.
+ * @param string $glue The glue by which to join the conditions.
+ *
+ * @return mixed The Base object on success or boolean false on failure.
+ */
+ public function unionAll($query, $distinct = false, $glue = '')
+ {
+ $glue = ')' . PHP_EOL . 'UNION ALL (';
+ $name = 'UNION ALL ()';
+
+ // Get the QueryElement if it does not exist
+ if (is_null($this->unionAll))
+ {
+ $this->unionAll = new QueryElement($name, $query, "$glue");
+ }
+
+ // Otherwise append the second UNION.
+ else
+ {
+ $glue = '';
+ $this->unionAll->append($query);
+ }
+
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Element.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Element.php
new file mode 100644
index 00000000..0667ebba
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Element.php
@@ -0,0 +1,116 @@
+elements = array();
+ $this->name = $name;
+ $this->glue = $glue;
+
+ $this->append($elements);
+ }
+
+ /**
+ * Magic function to convert the query element to a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (substr($this->name, -2) == '()')
+ {
+ return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')';
+ }
+ else
+ {
+ return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements);
+ }
+ }
+
+ /**
+ * Appends element parts to the internal list.
+ *
+ * @param mixed $elements String or array.
+ *
+ * @return void
+ */
+ public function append($elements)
+ {
+ if (is_array($elements))
+ {
+ $this->elements = array_merge($this->elements, $elements);
+ }
+ else
+ {
+ $this->elements = array_merge($this->elements, array($elements));
+ }
+ }
+
+ /**
+ * Gets the elements of this element.
+ *
+ * @return string
+ */
+ public function getElements()
+ {
+ return $this->elements;
+ }
+
+ /**
+ * Method to provide deep copy support to nested objects and arrays
+ * when cloning.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ foreach ($this as $k => $v)
+ {
+ if (is_object($v) || is_array($v))
+ {
+ $this->{$k} = unserialize(serialize($v));
+ }
+ }
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Limitable.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Limitable.php
new file mode 100644
index 00000000..0453c36e
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Limitable.php
@@ -0,0 +1,60 @@
+setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0);
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Mysql.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Mysql.php
new file mode 100644
index 00000000..7f6cfa3b
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Mysql.php
@@ -0,0 +1,22 @@
+ 0 || $offset > 0)
+ {
+ $query .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int)$limit;
+ $this->offset = (int)$offset;
+
+ return $this;
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ $concat_string = 'CONCAT_WS(' . $this->quote($separator);
+
+ foreach ($values as $value)
+ {
+ $concat_string .= ', ' . $value;
+ }
+
+ return $concat_string . ')';
+ }
+ else
+ {
+ return 'CONCAT(' . implode(',', $values) . ')';
+ }
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Pdomysql.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Pdomysql.php
new file mode 100644
index 00000000..ea96a0b4
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Pdomysql.php
@@ -0,0 +1,22 @@
+type)
+ {
+ case 'select':
+ $query .= (string)$this->select;
+ $query .= (string)$this->from;
+ if ($this->join)
+ {
+ // Special case for joins
+ foreach ($this->join as $join)
+ {
+ $query .= (string)$join;
+ }
+ }
+
+ if ($this->where)
+ {
+ $query .= (string)$this->where;
+ }
+
+ if ($this->group)
+ {
+ $query .= (string)$this->group;
+ }
+
+ if ($this->having)
+ {
+ $query .= (string)$this->having;
+ }
+
+ if ($this->order)
+ {
+ $query .= (string)$this->order;
+ }
+
+ if ($this->limit)
+ {
+ $query .= (string)$this->limit;
+ }
+
+ if ($this->offset)
+ {
+ $query .= (string)$this->offset;
+ }
+
+ if ($this->forUpdate)
+ {
+ $query .= (string)$this->forUpdate;
+ }
+ else
+ {
+ if ($this->forShare)
+ {
+ $query .= (string)$this->forShare;
+ }
+ }
+
+ if ($this->noWait)
+ {
+ $query .= (string)$this->noWait;
+ }
+
+ break;
+
+ case 'update':
+ $query .= (string)$this->update;
+ $query .= (string)$this->set;
+
+ if ($this->join)
+ {
+ $onWord = ' ON ';
+
+ // Workaround for special case of JOIN with UPDATE
+ foreach ($this->join as $join)
+ {
+ $joinElem = $join->getElements();
+
+ $joinArray = explode($onWord, $joinElem[0]);
+
+ $this->from($joinArray[0]);
+ $this->where($joinArray[1]);
+ }
+
+ $query .= (string)$this->from;
+ }
+
+ if ($this->where)
+ {
+ $query .= (string)$this->where;
+ }
+
+ break;
+
+ case 'insert':
+ $query .= (string)$this->insert;
+
+ if ($this->values)
+ {
+ if ($this->columns)
+ {
+ $query .= (string)$this->columns;
+ }
+
+ $elements = $this->values->getElements();
+ if (!($elements[0] instanceof $this))
+ {
+ $query .= ' VALUES ';
+ }
+
+ $query .= (string)$this->values;
+
+ if ($this->returning)
+ {
+ $query .= (string)$this->returning;
+ }
+ }
+
+ break;
+
+ default:
+ $query = parent::__toString();
+ break;
+
+ }
+
+ return $query;
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return void
+ *
+ * @since 11.3
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case 'limit':
+ $this->limit = null;
+ break;
+
+ case 'offset':
+ $this->offset = null;
+ break;
+
+ case 'forUpdate':
+ $this->forUpdate = null;
+ break;
+
+ case 'forShare':
+ $this->forShare = null;
+ break;
+
+ case 'noWait':
+ $this->noWait = null;
+ break;
+
+ case 'returning':
+ $this->returning = null;
+ break;
+
+ case 'select':
+ case 'update':
+ case 'delete':
+ case 'insert':
+ case 'from':
+ case 'join':
+ case 'set':
+ case 'where':
+ case 'group':
+ case 'having':
+ case 'order':
+ case 'columns':
+ case 'values':
+ parent::clear($clause);
+ break;
+
+ default:
+ $this->type = null;
+ $this->limit = null;
+ $this->offset = null;
+ $this->forUpdate = null;
+ $this->forShare = null;
+ $this->noWait = null;
+ $this->returning = null;
+ parent::clear($clause);
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Casts a value to a char.
+ *
+ * Ensure that the value is properly quoted before passing to the method.
+ *
+ * Usage:
+ * $query->select($query->castAsChar('a'));
+ *
+ * @param string $value The value to cast as a char.
+ *
+ * @return string Returns the cast value.
+ *
+ * @since 11.1
+ */
+ public function castAsChar($value)
+ {
+ return $value . '::text';
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * Usage:
+ * $query->select($query->concatenate(array('a', 'b')));
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 11.3
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return implode(' || ' . $this->quote($separator) . ' || ', $values);
+ }
+ else
+ {
+ return implode(' || ', $values);
+ }
+ }
+
+ /**
+ * Gets the current date and time.
+ *
+ * @return string Return string used in query to obtain
+ *
+ * @since 11.3
+ */
+ public function currentTimestamp()
+ {
+ return 'NOW()';
+ }
+
+ /**
+ * Sets the FOR UPDATE lock on select's output row
+ *
+ * @param string $table_name The table to lock
+ * @param boolean $glue The glue by which to join the conditions. Defaults to ',' .
+ *
+ * @return BaseQuery FOR UPDATE query element
+ *
+ * @since 11.3
+ */
+ public function forUpdate($table_name, $glue = ',')
+ {
+ $this->type = 'forUpdate';
+
+ if (is_null($this->forUpdate))
+ {
+ $glue = strtoupper($glue);
+ $this->forUpdate = new QueryElement('FOR UPDATE', 'OF ' . $table_name, "$glue ");
+ }
+ else
+ {
+ $this->forUpdate->append($table_name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the FOR SHARE lock on select's output row
+ *
+ * @param string $table_name The table to lock
+ * @param boolean $glue The glue by which to join the conditions. Defaults to ',' .
+ *
+ * @return BaseQuery FOR SHARE query element
+ *
+ * @since 11.3
+ */
+ public function forShare($table_name, $glue = ',')
+ {
+ $this->type = 'forShare';
+
+ if (is_null($this->forShare))
+ {
+ $glue = strtoupper($glue);
+ $this->forShare = new QueryElement('FOR SHARE', 'OF ' . $table_name, "$glue ");
+ }
+ else
+ {
+ $this->forShare->append($table_name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Used to get a string to extract year from date column.
+ *
+ * Usage:
+ * $query->select($query->year($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing year to be extracted.
+ *
+ * @return string Returns string to extract year from a date.
+ *
+ * @since 12.1
+ */
+ public function year($date)
+ {
+ return 'EXTRACT (YEAR FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract month from date column.
+ *
+ * Usage:
+ * $query->select($query->month($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing month to be extracted.
+ *
+ * @return string Returns string to extract month from a date.
+ *
+ * @since 12.1
+ */
+ public function month($date)
+ {
+ return 'EXTRACT (MONTH FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract day from date column.
+ *
+ * Usage:
+ * $query->select($query->day($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing day to be extracted.
+ *
+ * @return string Returns string to extract day from a date.
+ *
+ * @since 12.1
+ */
+ public function day($date)
+ {
+ return 'EXTRACT (DAY FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract hour from date column.
+ *
+ * Usage:
+ * $query->select($query->hour($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing hour to be extracted.
+ *
+ * @return string Returns string to extract hour from a date.
+ *
+ * @since 12.1
+ */
+ public function hour($date)
+ {
+ return 'EXTRACT (HOUR FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract minute from date column.
+ *
+ * Usage:
+ * $query->select($query->minute($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing minute to be extracted.
+ *
+ * @return string Returns string to extract minute from a date.
+ *
+ * @since 12.1
+ */
+ public function minute($date)
+ {
+ return 'EXTRACT (MINUTE FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract seconds from date column.
+ *
+ * Usage:
+ * $query->select($query->second($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing second to be extracted.
+ *
+ * @return string Returns string to extract second from a date.
+ *
+ * @since 12.1
+ */
+ public function second($date)
+ {
+ return 'EXTRACT (SECOND FROM ' . $date . ')';
+ }
+
+ /**
+ * Sets the NOWAIT lock on select's output row
+ *
+ * @return BaseQuery NO WAIT query element
+ *
+ * @since 11.3
+ */
+ public function noWait()
+ {
+ $this->type = 'noWait';
+
+ if (is_null($this->noWait))
+ {
+ $this->noWait = new QueryElement('NOWAIT', null);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the LIMIT clause to the query
+ *
+ * @param int $limit An int of how many row will be returned
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function limit($limit = 0)
+ {
+ if (is_null($this->limit))
+ {
+ $this->limit = new QueryElement('LIMIT', (int)$limit);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the OFFSET clause to the query
+ *
+ * @param int $offset An int for skipping row
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function offset($offset = 0)
+ {
+ if (is_null($this->offset))
+ {
+ $this->offset = new QueryElement('OFFSET', (int)$offset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add the RETURNING element to INSERT INTO statement.
+ *
+ * @param mixed $pkCol The name of the primary key column.
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function returning($pkCol)
+ {
+ if (is_null($this->returning))
+ {
+ $this->returning = new QueryElement('RETURNING', $pkCol);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return BaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int)$limit;
+ $this->offset = (int)$offset;
+
+ return $this;
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ if ($limit > 0)
+ {
+ $query .= ' LIMIT ' . $limit;
+ }
+
+ if ($offset > 0)
+ {
+ $query .= ' OFFSET ' . $offset;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Add to the current date and time in Postgresql.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ *
+ * @param string $date The date to add to, in SQL format
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @since 13.1
+ * @note Not all drivers support all units. Check appropriate references
+ * @link http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ if (substr($interval, 0, 1) != '-')
+ {
+ return "timestamp '" . $date . "' + interval '" . $interval . " " . $datePart . "'";
+ }
+ else
+ {
+ return "timestamp '" . $date . "' - interval '" . ltrim($interval, '-') . " " . $datePart . "'";
+ }
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Preparable.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Preparable.php
new file mode 100644
index 00000000..2a531e56
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Preparable.php
@@ -0,0 +1,56 @@
+bounded = array();
+
+ return $this;
+ }
+
+ // Case 2: Key Provided, null value (unset key from $bounded array)
+ if (is_null($value))
+ {
+ if (isset($this->bounded[$key]))
+ {
+ unset($this->bounded[$key]);
+ }
+
+ return $this;
+ }
+
+ $obj = new \stdClass;
+
+ $obj->value = &$value;
+ $obj->dataType = $dataType;
+ $obj->length = $length;
+ $obj->driverOptions = $driverOptions;
+
+ // Case 3: Simply add the Key/Value into the bounded array
+ $this->bounded[$key] = $obj;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
+ * returned.
+ *
+ * @param mixed $key The bounded variable key to retrieve.
+ *
+ * @return mixed
+ *
+ * @since 1.0
+ */
+ public function &getBounded($key = null)
+ {
+ if (empty($key))
+ {
+ return $this->bounded;
+ }
+ else
+ {
+ if (isset($this->bounded[$key]))
+ {
+ return $this->bounded[$key];
+ }
+ }
+ }
+
+ /**
+ * Gets the number of characters in a string.
+ *
+ * Note, use 'length' to find the number of bytes in a string.
+ *
+ * Usage:
+ * $query->select($query->charLength('a'));
+ *
+ * @param string $field A value.
+ * @param string $operator Comparison operator between charLength integer value and $condition
+ * @param string $condition Integer value to compare charLength with.
+ *
+ * @return string The required char length call.
+ *
+ * @since 1.1.0
+ */
+ public function charLength($field, $operator = null, $condition = null)
+ {
+ return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return Sqlite Returns this object to allow chaining.
+ *
+ * @since 1.0
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case null:
+ $this->bounded = array();
+ break;
+ }
+
+ return parent::clear($clause);
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * Usage:
+ * $query->select($query->concatenate(array('a', 'b')));
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 1.1.0
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return implode(' || ' . $this->quote($separator) . ' || ', $values);
+ }
+ else
+ {
+ return implode(' || ', $values);
+ }
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset. This method is used
+ * automatically by the __toString() method if it detects that the
+ * query implements the LimitableInterface.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 1.0
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ if ($limit > 0 || $offset > 0)
+ {
+ $query .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return Sqlite Returns this object to allow chaining.
+ *
+ * @since 1.0
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Sqlsrv.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Sqlsrv.php
new file mode 100644
index 00000000..389dd326
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Query/Sqlsrv.php
@@ -0,0 +1,196 @@
+type)
+ {
+ case 'insert':
+ $query .= (string)$this->insert;
+
+ // Set method
+ if ($this->set)
+ {
+ $query .= (string)$this->set;
+ }
+ // Columns-Values method
+ elseif ($this->values)
+ {
+ if ($this->columns)
+ {
+ $query .= (string)$this->columns;
+ }
+
+ $elements = $this->insert->getElements();
+ $tableName = array_shift($elements);
+
+ $query .= 'VALUES ';
+ $query .= (string)$this->values;
+
+ if ($this->autoIncrementField)
+ {
+ $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;';
+ }
+
+ if ($this->where)
+ {
+ $query .= (string)$this->where;
+ }
+
+ }
+
+ break;
+
+ default:
+ $query = parent::__toString();
+ break;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Casts a value to a char.
+ *
+ * Ensure that the value is properly quoted before passing to the method.
+ *
+ * @param string $value The value to cast as a char.
+ *
+ * @return string Returns the cast value.
+ */
+ public function castAsChar($value)
+ {
+ return 'CAST(' . $value . ' as NVARCHAR(10))';
+ }
+
+ /**
+ * Gets the number of characters in a string.
+ *
+ * Note, use 'length' to find the number of bytes in a string.
+ *
+ * Usage:
+ * $query->select($query->charLength('a'));
+ *
+ * @param string $field A value.
+ * @param string $operator Comparison operator between charLength integer value and $condition
+ * @param string $condition Integer value to compare charLength with.
+ *
+ * @return string The required char length call.
+ */
+ public function charLength($field, $operator = null, $condition = null)
+ {
+ if (empty($operator) && empty($condition))
+ {
+ $operator = 'IS NOT';
+ $condition = 'NULL';
+ }
+
+ return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')';
+ }
+ else
+ {
+ return '(' . implode('+', $values) . ')';
+ }
+ }
+
+ /**
+ * Gets the current date and time.
+ *
+ * @return string
+ */
+ public function currentTimestamp()
+ {
+ return 'GETDATE()';
+ }
+
+ /**
+ * Get the length of a string in bytes.
+ *
+ * @param string $value The string to measure.
+ *
+ * @return integer
+ */
+ public function length($value)
+ {
+ return 'LEN(' . $value . ')';
+ }
+
+ /**
+ * Add to the current date and time.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ *
+ * @param string $date The date (SQL formatted) to add to; type may be a string of a time or a datetime.
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @note Not all drivers support all units.
+ * @link http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ return "DATEADD('" . $datePart . "', '" . $interval . "', '" . $date . "'" . ')';
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Queryexception.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Queryexception.php
new file mode 100644
index 00000000..e57d1ea2
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Queryexception.php
@@ -0,0 +1,16 @@
+driverType = 'sqlite';
+
+ parent::__construct($options);
+
+ if (!is_object($this->connection))
+ {
+ $this->open();
+ }
+ }
+
+ /**
+ * Destructor.
+ *
+ * @since 1.0
+ */
+ public function __destruct()
+ {
+ $this->freeResult();
+ unset($this->connection);
+ }
+
+ /**
+ * Disconnects the database.
+ *
+ * @return void
+ *
+ * @since 1.0
+ */
+ public function disconnect()
+ {
+ $this->freeResult();
+ unset($this->connection);
+ }
+
+ /**
+ * Drops a table from the database.
+ *
+ * @param string $tableName The name of the database table to drop.
+ * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
+ *
+ * @return Sqlite Returns this object to support chaining.
+ *
+ * @since 1.0
+ */
+ public function dropTable($tableName, $ifExists = true)
+ {
+ $this->open();
+
+ $query = $this->getQuery(true);
+
+ $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));
+
+ $this->execute();
+
+ return $this;
+ }
+
+ /**
+ * Method to escape a string for usage in an SQLite statement.
+ *
+ * Note: Using query objects with bound variables is preferable to the below.
+ *
+ * @param string $text The string to be escaped.
+ * @param boolean $extra Unused optional parameter to provide extra escaping.
+ *
+ * @return string The escaped string.
+ *
+ * @since 1.0
+ */
+ public function escape($text, $extra = false)
+ {
+ if (is_int($text) || is_float($text))
+ {
+ return $text;
+ }
+
+ return SQLite3::escapeString($text);
+ }
+
+ /**
+ * Method to get the database collation in use by sampling a text field of a table in the database.
+ *
+ * @return mixed The collation in use by the database or boolean false if not supported.
+ *
+ * @since 1.0
+ */
+ public function getCollation()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Shows the table CREATE statement that creates the given tables.
+ *
+ * Note: Doesn't appear to have support in SQLite
+ *
+ * @param mixed $tables A table name or a list of table names.
+ *
+ * @return array A list of the create SQL for the tables.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function getTableCreate($tables)
+ {
+ $this->open();
+
+ // Sanitize input to an array and iterate over the list.
+ settype($tables, 'array');
+
+ return $tables;
+ }
+
+ /**
+ * Retrieves field information about a given table.
+ *
+ * @param string $table The name of the database table.
+ * @param boolean $typeOnly True to only return field types.
+ *
+ * @return array An array of fields for the database table.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function getTableColumns($table, $typeOnly = true)
+ {
+ $this->open();
+
+ $columns = array();
+ $query = $this->getQuery(true);
+
+ $fieldCasing = $this->getOption(\PDO::ATTR_CASE);
+
+ $this->setOption(\PDO::ATTR_CASE, \PDO::CASE_UPPER);
+
+ $table = strtoupper($table);
+
+ $query->setQuery('pragma table_info(' . $table . ')');
+
+ $this->setQuery($query);
+ $fields = $this->loadObjectList();
+
+ if ($typeOnly)
+ {
+ foreach ($fields as $field)
+ {
+ $columns[$field->NAME] = $field->TYPE;
+ }
+ }
+ else
+ {
+ foreach ($fields as $field)
+ {
+ // Do some dirty translation to MySQL output.
+ $columns[$field->NAME] = (object)array(
+ 'Field' => $field->NAME,
+ 'Type' => $field->TYPE,
+ 'Null' => ($field->NOTNULL == '1' ? 'NO' : 'YES'),
+ 'Default' => $field->DFLT_VALUE,
+ 'Key' => ($field->PK == '1' ? 'PRI' : '')
+ );
+ }
+ }
+
+ $this->setOption(\PDO::ATTR_CASE, $fieldCasing);
+
+ return $columns;
+ }
+
+ /**
+ * Get the details list of keys for a table.
+ *
+ * @param string $table The name of the table.
+ *
+ * @return array An array of the column specification for the table.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function getTableKeys($table)
+ {
+ $this->open();
+
+ $keys = array();
+ $query = $this->getQuery(true);
+
+ $fieldCasing = $this->getOption(\PDO::ATTR_CASE);
+
+ $this->setOption(\PDO::ATTR_CASE, \PDO::CASE_UPPER);
+
+ $table = strtoupper($table);
+ $query->setQuery('pragma table_info( ' . $table . ')');
+
+ // $query->bind(':tableName', $table);
+
+ $this->setQuery($query);
+ $rows = $this->loadObjectList();
+
+ foreach ($rows as $column)
+ {
+ if ($column->PK == 1)
+ {
+ $keys[$column->NAME] = $column;
+ }
+ }
+
+ $this->setOption(\PDO::ATTR_CASE, $fieldCasing);
+
+ return $keys;
+ }
+
+ /**
+ * Method to get an array of all tables in the database (schema).
+ *
+ * @return array An array of all the tables in the database.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function getTableList()
+ {
+ $this->open();
+
+ /* @type Query\Sqlite $query */
+ $query = $this->getQuery(true);
+
+ $type = 'table';
+
+ $query->select('name');
+ $query->from('sqlite_master');
+ $query->where('type = :type');
+ $query->bind(':type', $type);
+ $query->order('name');
+
+ $this->setQuery($query);
+
+ $tables = $this->loadColumn();
+
+ return $tables;
+ }
+
+ /**
+ * There's no point on return "a list of tables" inside a SQLite database: we are simple going to
+ * copy the whole database file in the new location
+ *
+ * @param bool $abstract
+ * @return array
+ */
+ public function getTables($abstract = true)
+ {
+ return array();
+ }
+
+ /**
+ * Get the version of the database connector.
+ *
+ * @return string The database connector version.
+ *
+ * @since 1.0
+ */
+ public function getVersion()
+ {
+ $this->open();
+
+ $this->setQuery("SELECT sqlite_version()");
+
+ return $this->loadResult();
+ }
+
+ /**
+ * Select a database for use.
+ *
+ * @param string $database The name of the database to select for use.
+ *
+ * @return boolean True if the database was successfully selected.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function select($database)
+ {
+ $this->open();
+
+ $this->_database = $database;
+
+ return true;
+ }
+
+ /**
+ * Set the connection to use UTF-8 character encoding.
+ *
+ * Returns false automatically for the Oracle driver since
+ * you can only set the character set when the connection
+ * is created.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.0
+ */
+ public function setUTF()
+ {
+ $this->open();
+
+ return false;
+ }
+
+ /**
+ * Locks a table in the database.
+ *
+ * @param string $table The name of the table to unlock.
+ *
+ * @return Sqlite Returns this object to support chaining.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function lockTable($table)
+ {
+ return $this;
+ }
+
+ /**
+ * Renames a table in the database.
+ *
+ * @param string $oldTable The name of the table to be renamed
+ * @param string $newTable The new name for the table.
+ * @param string $backup Not used by Sqlite.
+ * @param string $prefix Not used by Sqlite.
+ *
+ * @return Sqlite Returns this object to support chaining.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
+ {
+ $this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME TO ' . $newTable)->execute();
+
+ return $this;
+ }
+
+ /**
+ * Unlocks tables in the database.
+ *
+ * @return Sqlite Returns this object to support chaining.
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function unlockTables()
+ {
+ return $this;
+ }
+
+ /**
+ * Test to see if the PDO ODBC connector is available.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 1.0
+ */
+ public static function isSupported()
+ {
+ return class_exists('\\PDO') && in_array('sqlite', \PDO::getAvailableDrivers());
+ }
+
+ /**
+ * Determines if the connection to the server is active.
+ *
+ * @return boolean True if connected to the database engine.
+ */
+ public function connected()
+ {
+ return !empty($this->connection);
+ }
+
+ /**
+ * Method to commit a transaction.
+ *
+ * @param boolean $toSavepoint If true, commit to the last savepoint.
+ *
+ * @return void
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function transactionCommit($toSavepoint = false)
+ {
+ $this->open();
+
+ if (!$toSavepoint || $this->transactionDepth <= 1)
+ {
+ $this->open();
+
+ if (!$toSavepoint || $this->transactionDepth == 1)
+ {
+ $this->connection->commit();
+ }
+
+ $this->transactionDepth--;
+ }
+ else
+ {
+ $this->transactionDepth--;
+ }
+ }
+
+ /**
+ * Method to roll back a transaction.
+ *
+ * @param boolean $toSavepoint If true, rollback to the last savepoint.
+ *
+ * @return void
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function transactionRollback($toSavepoint = false)
+ {
+ $this->connected();
+
+ if (!$toSavepoint || $this->transactionDepth <= 1)
+ {
+ $this->open();
+
+ if (!$toSavepoint || $this->transactionDepth == 1)
+ {
+ $this->connection->rollBack();
+ }
+
+ $this->transactionDepth--;
+ }
+ else
+ {
+ $savepoint = 'SP_' . ($this->transactionDepth - 1);
+ $this->setQuery('ROLLBACK TO ' . $this->quoteName($savepoint));
+
+ if ($this->execute())
+ {
+ $this->transactionDepth--;
+ }
+ }
+ }
+
+ /**
+ * Method to initialize a transaction.
+ *
+ * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
+ *
+ * @return void
+ *
+ * @since 1.0
+ * @throws \RuntimeException
+ */
+ public function transactionStart($asSavepoint = false)
+ {
+ $this->connected();
+
+ if (!$asSavepoint || !$this->transactionDepth)
+ {
+ $this->open();
+
+ if (!$asSavepoint || !$this->transactionDepth)
+ {
+ $this->connection->beginTransaction();
+ }
+
+ $this->transactionDepth++;
+ }
+ else
+ {
+ $savepoint = 'SP_' . $this->transactionDepth;
+ $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));
+
+ if ($this->execute())
+ {
+ $this->transactionDepth++;
+ }
+ }
+ }
+
+ /**
+ * Get the current query object or a new Query object.
+ * We have to override the parent method since it will always return a PDO query, while we have a
+ * specialized class for SQLite
+ *
+ * @param boolean $new False to return the current query object, True to return a new Query object.
+ *
+ * @return QueryBase The current query object or a new object extending the Query class.
+ *
+ * @throws \RuntimeException
+ */
+ public function getQuery($new = false)
+ {
+ if ($new)
+ {
+ $class = '\\Akeeba\\Engine\\Driver\\Query\\Sqlite';
+
+ return new $class($this);
+ }
+ else
+ {
+ return $this->sql;
+ }
+ }
+
+ public function open()
+ {
+ if ($this->connected())
+ {
+ return;
+ }
+ else
+ {
+ $this->close();
+ }
+
+ if (isset($this->options['version']) && $this->options['version'] == 2)
+ {
+ $format = 'sqlite2:#DBNAME#';
+ }
+ else
+ {
+ $format = 'sqlite:#DBNAME#';
+ }
+
+ $replace = array('#DBNAME#');
+ $with = array($this->options['database']);
+
+ // Create the connection string:
+ $connectionString = str_replace($replace, $with, $format);
+
+ try
+ {
+ $this->connection = new \PDO(
+ $connectionString,
+ $this->options['user'],
+ $this->options['password']
+ );
+ }
+ catch (\PDOException $e)
+ {
+ throw new \RuntimeException('Could not connect to PDO' . ': ' . $e->getMessage(), 2, $e);
+ }
+ }
+
+ public function close()
+ {
+ $return = false;
+
+ if (is_object($this->cursor))
+ {
+ $this->cursor->closeCursor();
+ }
+
+ $this->connection = null;
+
+ return $return;
+ }
+
+ protected function fetchArray($cursor = NULL)
+ {
+ if (!empty($cursor) && $cursor instanceof \PDOStatement)
+ {
+ return $cursor->fetch(\PDO::FETCH_NUM);
+ }
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ return $this->prepared->fetch(\PDO::FETCH_NUM);
+ }
+ }
+
+ public function fetchAssoc($cursor = NULL)
+ {
+ if (!empty($cursor) && $cursor instanceof \PDOStatement)
+ {
+ return $cursor->fetch(\PDO::FETCH_ASSOC);
+ }
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ return $this->prepared->fetch(\PDO::FETCH_ASSOC);
+ }
+ }
+
+ protected function fetchObject($cursor = NULL, $class = 'stdClass')
+ {
+ if (!empty($cursor) && $cursor instanceof \PDOStatement)
+ {
+ return $cursor->fetchObject($class);
+ }
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ return $this->prepared->fetchObject($class);
+ }
+ }
+
+ public function freeResult($cursor = NULL)
+ {
+ $this->executed = false;
+
+ if ($cursor instanceof \PDOStatement)
+ {
+ $cursor->closeCursor();
+ $cursor = null;
+ }
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ $this->prepared->closeCursor();
+ $this->prepared = null;
+ }
+ }
+
+ public function getAffectedRows()
+ {
+ $this->open();
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ return $this->prepared->rowCount();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ public function getNumRows($cursor = NULL)
+ {
+ $this->open();
+
+ if ($cursor instanceof \PDOStatement)
+ {
+ return $cursor->rowCount();
+ }
+ elseif ($this->prepared instanceof \PDOStatement)
+ {
+ return $this->prepared->rowCount();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ public function insertid()
+ {
+ $this->open();
+
+ // Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
+ return @$this->connection->lastInsertId();
+ }
+
+ public function query()
+ {
+ static $isReconnecting = false;
+
+ $this->open();
+
+ if (!is_object($this->connection))
+ {
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+
+ // Take a local copy so that we don't modify the original query and cause issues later
+ $sql = $this->replacePrefix((string) $this->sql);
+
+ if ($this->limit > 0 || $this->offset > 0)
+ {
+ // @TODO
+ $sql .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
+ }
+
+ // Increment the query counter.
+ $this->count++;
+
+ // If debugging is enabled then let's log the query.
+ if ($this->debug)
+ {
+ // Add the query to the object queue.
+ $this->log[] = $sql;
+ }
+
+ // Reset the error values.
+ $this->errorNum = 0;
+ $this->errorMsg = '';
+
+ // Execute the query.
+ $this->executed = false;
+
+ if ($this->prepared instanceof \PDOStatement)
+ {
+ // Bind the variables:
+ if ($this->sql instanceof Preparable)
+ {
+ $bounded =& $this->sql->getBounded();
+
+ foreach ($bounded as $key => $obj)
+ {
+ $this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
+ }
+ }
+
+ $this->executed = $this->prepared->execute();
+ }
+
+ // If an error occurred handle it.
+ if (!$this->executed)
+ {
+ // Get the error number and message before we execute any more queries.
+ $errorNum = (int) $this->connection->errorCode();
+ $errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo());
+
+ // Check if the server was disconnected.
+ if (!$this->connected() && !$isReconnecting)
+ {
+ $isReconnecting = true;
+
+ try
+ {
+ // Attempt to reconnect.
+ $this->connection = null;
+ $this->open();
+ }
+ catch (\RuntimeException $e)
+ // If connect fails, ignore that exception and throw the normal exception.
+ {
+ // Get the error number and message.
+ $this->errorNum = (int) $this->connection->errorCode();
+ $this->errorMsg = (string) 'SQL: ' . implode(", ", $this->connection->errorInfo());
+
+ // Throw the normal query exception.
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+
+ // Since we were able to reconnect, run the query again.
+ $result = $this->query();
+ $isReconnecting = false;
+
+ return $result;
+ }
+ else
+ // The server was not disconnected.
+ {
+ // Get the error number and message from before we tried to reconnect.
+ $this->errorNum = $errorNum;
+ $this->errorMsg = $errorMsg;
+
+ // Throw the normal query exception.
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+ }
+
+ return $this->prepared;
+ }
+
+ /**
+ * Retrieve a PDO database connection attribute
+ * http://www.php.net/manual/en/pdo.getattribute.php
+ *
+ * Usage: $db->getOption(PDO::ATTR_CASE);
+ *
+ * @param mixed $key One of the PDO::ATTR_* Constants
+ *
+ * @return mixed
+ *
+ * @since 1.0
+ */
+ public function getOption($key)
+ {
+ $this->open();
+
+ return $this->connection->getAttribute($key);
+ }
+
+ /**
+ * Sets an attribute on the PDO database handle.
+ * http://www.php.net/manual/en/pdo.setattribute.php
+ *
+ * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
+ *
+ * @param integer $key One of the PDO::ATTR_* Constants
+ * @param mixed $value One of the associated PDO Constants
+ * related to the particular attribute
+ * key.
+ *
+ * @return boolean
+ *
+ * @since 1.0
+ */
+ public function setOption($key, $value)
+ {
+ $this->open();
+
+ return $this->connection->setAttribute($key, $value);
+ }
+
+ /**
+ * Sets the SQL statement string for later execution.
+ *
+ * @param mixed $query The SQL statement to set either as a JDatabaseQuery object or a string.
+ * @param integer $offset The affected row offset to set.
+ * @param integer $limit The maximum affected rows to set.
+ * @param array $driverOptions The optional PDO driver options
+ *
+ * @return Base This object to support method chaining.
+ *
+ * @since 1.0
+ */
+ public function setQuery($query, $offset = null, $limit = null, $driverOptions = array())
+ {
+ $this->open();
+
+ $this->freeResult();
+
+ if (is_string($query))
+ {
+ // Allows taking advantage of bound variables in a direct query:
+ $query = $this->getQuery(true)->setQuery($query);
+ }
+
+ if ($query instanceof Limitable && !is_null($offset) && !is_null($limit))
+ {
+ $query->setLimit($limit, $offset);
+ }
+
+ $sql = $this->replacePrefix((string) $query);
+
+ $this->prepared = $this->connection->prepare($sql, $driverOptions);
+
+ // Store reference to the DatabaseQuery instance:
+ parent::setQuery($query, $offset, $limit);
+
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Sqlsrv.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Sqlsrv.php
new file mode 100644
index 00000000..29ac162c
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Driver/Sqlsrv.php
@@ -0,0 +1,992 @@
+open();
+
+ // Add tables
+ $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';');
+ $all_tables = $this->loadColumn();
+
+ if (!empty($all_tables))
+ {
+ foreach ($all_tables as $table_name)
+ {
+ if ($abstract)
+ {
+ $table_name = $this->getAbstract($table_name);
+ }
+
+ $tables[$table_name] = 'table';
+ }
+ }
+
+ // Add VIEWs
+ $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Views WHERE type_desc = \'VIEW\';');
+ $all_tables = $this->loadColumn();
+
+ if (!empty($all_tables))
+ {
+ foreach ($all_tables as $table_name)
+ {
+ if ($abstract)
+ {
+ $table_name = $this->getAbstract($table_name);
+ }
+
+ $tables[$table_name] = 'view';
+ }
+ }
+
+ ksort($tables);
+
+ return $tables;
+ }
+
+ /**
+ * Test to see if the SQLSRV connector is available.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 11.1
+ */
+ public static function isSupported()
+ {
+ return (function_exists('sqlsrv_connect'));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param array $options List of options used to configure the connection
+ *
+ * @since 11.1
+ */
+ public function __construct($options)
+ {
+ $this->driverType = 'mssql';
+
+ // Get some basic values from the options.
+ $host = array_key_exists('host', $options) ? $options['host'] : 'localhost';
+ $port = array_key_exists('port', $options) ? $options['port'] : '';
+ $user = array_key_exists('user', $options) ? $options['user'] : '';
+ $password = array_key_exists('password', $options) ? $options['password'] : '';
+ $database = array_key_exists('database', $options) ? $options['database'] : '';
+ $prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
+ $select = array_key_exists('select', $options) ? $options['select'] : true;
+
+ // Build the connection configuration array.
+ $this->connectionConfig = array(
+ 'Database' => $database,
+ 'uid' => $user,
+ 'pwd' => $password,
+ 'CharacterSet' => 'UTF-8',
+ 'ReturnDatesAsStrings' => true
+ );
+
+ parent::__construct($options);
+
+ $this->host = $host;
+ $this->user = $user;
+ $this->password = $password;
+ $this->_database = $database;
+ $this->selectDatabase = $select;
+
+ if (!is_resource($this->connection))
+ {
+ $this->open();
+ }
+ }
+
+ public function open()
+ {
+ if (is_resource($this->connection))
+ {
+ return;
+ }
+
+ $config = $this->connectionConfig;
+
+ // Make sure the SQLSRV extension for PHP is installed and enabled.
+ if (!function_exists('sqlsrv_connect'))
+ {
+ $this->errorNum = 1;
+ $this->errorMsg = 'You do not have the sqlsrv extension installed on this server';
+
+ return;
+ }
+
+ // Attempt to connect to the server.
+ if (!($this->connection = @ sqlsrv_connect($this->host, $config)))
+ {
+
+ $this->errorNum = 2;
+ $this->errorMsg = 'Can not connect to Microsoft SQL Server';
+
+ return;
+ }
+
+ // Make sure that DB warnings are not returned as errors.
+ sqlsrv_configure('WarningsReturnAsErrors', 0);
+
+ // If auto-select is enabled select the given database.
+ if ($this->selectDatabase && !empty($this->_database))
+ {
+ $this->select($this->_database);
+ }
+ }
+
+ public function close()
+ {
+ $return = false;
+
+ if (is_resource($this->cursor))
+ {
+ sqlsrv_free_result($this->cursor);
+ }
+
+ if (is_resource($this->connection))
+ {
+ $return = sqlsrv_close($this->connection);
+ }
+
+ $this->connection = null;
+
+ return $return;
+ }
+
+ /**
+ * Get table constraints
+ *
+ * @param string $tableName The name of the database table.
+ *
+ * @return array Any constraints available for the table.
+ */
+ protected function getTableConstraints($tableName)
+ {
+ $query = $this->getQuery(true);
+
+ $this->setQuery(
+ 'SELECT CONSTRAINT_NAME FROM' . ' INFORMATION_SCHEMA.TABLE_CONSTRAINTS' . ' WHERE TABLE_NAME = ' . $query->quote($tableName)
+ );
+
+ return $this->loadColumn();
+ }
+
+ /**
+ * Rename constraints.
+ *
+ * @param array $constraints Array(strings) of table constraints
+ * @param string $prefix A string
+ * @param string $backup A string
+ *
+ * @return void
+ */
+ protected function renameConstraints($constraints = array(), $prefix = null, $backup = null)
+ {
+ foreach ($constraints as $constraint)
+ {
+ $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint));
+ $this->query();
+ }
+ }
+
+ /**
+ * Method to escape a string for usage in an SQL statement.
+ *
+ * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need
+ * to handle the escaping ourselves.
+ *
+ * @param string $text The string to be escaped.
+ * @param boolean $extra Optional parameter to provide extra escaping.
+ *
+ * @return string The escaped string.
+ */
+ public function escape($text, $extra = false)
+ {
+ $result = addslashes($text);
+ $result = str_replace("\'", "''", $result);
+ $result = str_replace('\"', '"', $result);
+ $result = str_replace('\/', '/', $result);
+
+ if ($extra)
+ {
+ // We need the below str_replace since the search in sql server doesn't recognize _ character.
+ $result = str_replace('_', '[_]', $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determines if the connection to the server is active.
+ *
+ * @return boolean True if connected to the database engine.
+ */
+ public function connected()
+ {
+ // TODO: Run a blank query here
+ return true;
+ }
+
+ /**
+ * Drops a table from the database.
+ *
+ * @param string $tableName The name of the database table to drop.
+ * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
+ *
+ * @return Sqlsrv Returns this object to support chaining.
+ */
+ public function dropTable($tableName, $ifExists = true)
+ {
+ $query = $this->getQuery(true);
+
+ if ($ifExists)
+ {
+ $this->setQuery(
+ 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName
+ );
+ }
+ else
+ {
+ $this->setQuery('DROP TABLE ' . $tableName);
+ }
+
+ $this->execute();
+
+ return $this;
+ }
+
+ /**
+ * Get the number of affected rows for the previous executed SQL statement.
+ *
+ * @return integer The number of affected rows.
+ */
+ public function getAffectedRows()
+ {
+ return sqlsrv_rows_affected($this->cursor);
+ }
+
+ /**
+ * Method to get the database collation in use by sampling a text field of a table in the database.
+ *
+ * @return mixed The collation in use by the database or boolean false if not supported.
+ */
+ public function getCollation()
+ {
+ // TODO: Not fake this
+ return 'MSSQL UTF-8 (UCS2)';
+ }
+
+ /**
+ * Get the number of returned rows for the previous executed SQL statement.
+ *
+ * @param resource $cursor An optional database cursor resource to extract the row count from.
+ *
+ * @return integer The number of returned rows.
+ */
+ public function getNumRows($cursor = null)
+ {
+ return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
+ }
+
+ /**
+ * Get the current or query, or new JDatabaseQuery object.
+ *
+ * @param boolean $new False to return the last query set, True to return a new JDatabaseQuery object.
+ *
+ * @return mixed The current value of the internal SQL variable or a new QuerySqlsrv object.
+ */
+ public function getQuery($new = false)
+ {
+ if ($new)
+ {
+ return new QuerySqlsrv($this);
+ }
+ else
+ {
+ return $this->sql;
+ }
+ }
+
+ /**
+ * Retrieves field information about the given tables.
+ *
+ * @param mixed $table A table name
+ * @param boolean $typeOnly True to only return field types.
+ *
+ * @return array An array of fields.
+ */
+ public function getTableColumns($table, $typeOnly = true)
+ {
+ $result = array();
+
+ $table_temp = $this->replacePrefix((string)$table);
+
+ // Set the query to get the table fields statement.
+ $this->setQuery(
+ 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' .
+ ' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp)
+ );
+ $fields = $this->loadObjectList();
+
+ // If we only want the type as the value add just that to the list.
+ if ($typeOnly)
+ {
+ foreach ($fields as $field)
+ {
+ $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
+ }
+ }
+ // If we want the whole field data object add that to the list.
+ else
+ {
+ foreach ($fields as $field)
+ {
+ $result[$field->Field] = $field;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Shows the table CREATE statement that creates the given tables.
+ *
+ * This is unsupported by MSSQL.
+ *
+ * @param mixed $tables A table name or a list of table names.
+ *
+ * @return array A list of the create SQL for the tables.
+ */
+ public function getTableCreate($tables)
+ {
+ // @todo Implement my own approximate function
+ return '';
+ }
+
+ /**
+ * Get the details list of keys for a table.
+ *
+ * @param string $table The name of the table.
+ *
+ * @return array An array of the column specification for the table.
+ */
+ public function getTableKeys($table)
+ {
+ // TODO To implement.
+ return array();
+ }
+
+ /**
+ * Method to get an array of all tables in the database.
+ *
+ * @return array An array of all the tables in the database.
+ */
+ public function getTableList()
+ {
+ // Set the query to get the tables statement.
+ $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';');
+ $tables = $this->loadColumn();
+
+ return $tables;
+ }
+
+ /**
+ * Get the version of the database connector.
+ *
+ * @return string The database connector version.
+ */
+ public function getVersion()
+ {
+ $version = sqlsrv_server_info($this->connection);
+
+ return $version['SQLServerVersion'];
+ }
+
+ /**
+ * Determines if the database engine supports UTF-8 character encoding.
+ *
+ * @return boolean True if supported.
+ */
+ public function hasUTF()
+ {
+ return true;
+ }
+
+ /**
+ * Inserts a row into a table based on an object's properties.
+ *
+ * @param string $table The name of the database table to insert into.
+ * @param object &$object A reference to an object whose public properties match the table fields.
+ * @param string $key The name of the primary key. If provided the object property is updated.
+ *
+ * @return boolean True on success.
+ */
+ public function insertObject($table, &$object, $key = null)
+ {
+ $fields = array();
+ $values = array();
+ $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)';
+ foreach (get_object_vars($object) as $k => $v)
+ {
+ if (is_array($v) or is_object($v) or $v === null)
+ {
+ continue;
+ }
+ if (!$this->checkFieldExists($table, $k))
+ {
+ continue;
+ }
+ if ($k[0] == '_')
+ {
+ // Internal field
+ continue;
+ }
+ if ($k == $key && $key == 0)
+ {
+ continue;
+ }
+ $fields[] = $this->quoteName($k);
+ $values[] = $this->quote($v);
+ }
+ // Set the query and execute the insert.
+ $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values)));
+ if (!$this->execute())
+ {
+ return false;
+ }
+ $id = $this->insertid();
+ if ($key && $id)
+ {
+ $object->$key = $id;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to get the auto-incremented value from the last INSERT statement.
+ *
+ * @return integer The value of the auto-increment field from the last inserted row.
+ */
+ public function insertid()
+ {
+ $this->setQuery('SELECT @@IDENTITY');
+
+ return (int)$this->loadResult();
+ }
+
+ /**
+ * Method to get the first field of the first row of the result set from the database query.
+ *
+ * @return mixed The return value or null if the query failed.
+ */
+ public function loadResult()
+ {
+ $ret = null;
+
+ // Execute the query and get the result set cursor.
+ if (!($cursor = $this->execute()))
+ {
+ return null;
+ }
+
+ // Get the first row from the result set as an array.
+ if ($row = sqlsrv_fetch_array($cursor, SQLSRV_FETCH_NUMERIC))
+ {
+ $ret = $row[0];
+ }
+ // Free up system resources and return.
+ $this->freeResult($cursor);
+
+ // For SQLServer - we need to strip slashes
+ $ret = stripslashes($ret);
+
+ return $ret;
+ }
+
+ /**
+ * Execute the SQL statement.
+ *
+ * @return mixed A database cursor resource on success, boolean false on failure.
+ *
+ * @since 11.1
+ * @throws \Exception
+ */
+ public function query()
+ {
+ static $isReconnecting = false;
+
+ $this->open();
+
+ if (!is_resource($this->connection))
+ {
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+
+ // Take a local copy so that we don't modify the original query and cause issues later
+ $query = $this->replacePrefix((string)$this->sql);
+ if ($this->limit > 0 || $this->offset > 0)
+ {
+ $query = $this->limit($query, $this->limit, $this->offset);
+ }
+
+ // Increment the query counter.
+ $this->count++;
+
+ // If debugging is enabled then let's log the query.
+ if ($this->debug)
+ {
+ // Add the query to the object queue.
+ $this->log[] = $query;
+ }
+
+ // Reset the error values.
+ $this->errorNum = 0;
+ $this->errorMsg = '';
+
+ // SQLSrv_num_rows requires a static or keyset cursor.
+ if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0)
+ {
+ $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
+ }
+ else
+ {
+ $array = array();
+ }
+
+ // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
+ $this->cursor = @sqlsrv_query($this->connection, $query, array(), $array);
+
+ // If an error occurred handle it.
+ if (!$this->cursor)
+ {
+ // Check if the server was disconnected.
+ if (!$this->connected() && !$isReconnecting)
+ {
+ $isReconnecting = true;
+
+ try
+ {
+ // Attempt to reconnect.
+ $this->connection = null;
+ $this->open();
+ }
+ // If connect fails, ignore that exception and throw the normal exception.
+ catch (\RuntimeException $e)
+ {
+ // Get the error number and message.
+ $errors = sqlsrv_errors();
+ $this->errorNum = $errors[0]['SQLSTATE'];
+ $this->errorMsg = $errors[0]['message'] . 'SQL=' . $query;
+
+ // Throw the normal query exception.
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+
+ // Since we were able to reconnect, run the query again.
+ $result = $this->query();
+ $isReconnecting = false;
+
+ return $result;
+ }
+ // The server was not disconnected.
+ else
+ {
+ // Get the error number and message.
+ $errors = sqlsrv_errors();
+ $this->errorNum = $errors[0]['SQLSTATE'];
+ $this->errorMsg = $errors[0]['message'] . 'SQL=' . $query;
+
+ // Throw the normal query exception.
+ throw new \RuntimeException($this->errorMsg, $this->errorNum);
+ }
+ }
+
+ return $this->cursor;
+ }
+
+ /**
+ * This function replaces a string identifier $prefix with the string held is the
+ * tablePrefix class variable.
+ *
+ * @param string $sql The SQL statement to prepare.
+ * @param string $prefix The common table prefix.
+ *
+ * @return string The processed SQL statement.
+ *
+ * @since 11.1
+ */
+ public function replacePrefix($query, $prefix = '#__')
+ {
+ $escaped = false;
+ $startPos = 0;
+ $quoteChar = '';
+ $literal = '';
+
+ $query = trim($query);
+ $n = strlen($query);
+
+ while ($startPos < $n)
+ {
+ $ip = strpos($query, $prefix, $startPos);
+ if ($ip === false)
+ {
+ break;
+ }
+
+ $j = strpos($query, "N'", $startPos);
+ $k = strpos($query, '"', $startPos);
+ if (($k !== false) && (($k < $j) || ($j === false)))
+ {
+ $quoteChar = '"';
+ $j = $k;
+ }
+ else
+ {
+ $quoteChar = "'";
+ }
+
+ if ($j === false)
+ {
+ $j = $n;
+ }
+
+ $literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
+ $startPos = $j;
+
+ $j = $startPos + 1;
+
+ if ($j >= $n)
+ {
+ break;
+ }
+
+ // Quote comes first, find end of quote
+ while (true)
+ {
+ $k = strpos($query, $quoteChar, $j);
+ $escaped = false;
+ if ($k === false)
+ {
+ break;
+ }
+ $l = $k - 1;
+ while ($l >= 0 && $query{$l} == '\\')
+ {
+ $l--;
+ $escaped = !$escaped;
+ }
+ if ($escaped)
+ {
+ $j = $k + 1;
+ continue;
+ }
+ break;
+ }
+ if ($k === false)
+ {
+ // Error in the query - no end quote; ignore it
+ break;
+ }
+ $literal .= substr($query, $startPos, $k - $startPos + 1);
+ $startPos = $k + 1;
+ }
+ if ($startPos < $n)
+ {
+ $literal .= substr($query, $startPos, $n - $startPos);
+ }
+
+ return $literal;
+ }
+
+ /**
+ * Select a database for use.
+ *
+ * @param string $database The name of the database to select for use.
+ *
+ * @return boolean True if the database was successfully selected.
+ *
+ * @since 11.1
+ * @throws \Exception
+ */
+ public function select($database)
+ {
+ if (!$database)
+ {
+ return false;
+ }
+
+ if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC)))
+ {
+ throw new \RuntimeException('Could not connect to database');
+ }
+
+ return true;
+ }
+
+ /**
+ * Set the connection to use UTF-8 character encoding.
+ *
+ * @return boolean True on success.
+ */
+ public function setUTF()
+ {
+ // TODO: Remove this?
+ }
+
+ /**
+ * Method to commit a transaction.
+ *
+ * @return void
+ */
+ public function transactionCommit()
+ {
+ $this->setQuery('COMMIT TRANSACTION');
+ $this->query();
+ }
+
+ /**
+ * Method to roll back a transaction.
+ *
+ * @return void
+ */
+ public function transactionRollback()
+ {
+ $this->setQuery('ROLLBACK TRANSACTION');
+ $this->query();
+ }
+
+ /**
+ * Method to initialize a transaction.
+ *
+ * @return void
+ */
+ public function transactionStart()
+ {
+ $this->setQuery('BEGIN TRANSACTION');
+ $this->query();
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an array.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ */
+ protected function fetchArray($cursor = null)
+ {
+ return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC);
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an associative array.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ */
+ public function fetchAssoc($cursor = null)
+ {
+ return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC);
+ }
+
+ /**
+ * Method to fetch a row from the result set cursor as an object.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ * @param string $class The class name to use for the returned row object.
+ *
+ * @return mixed Either the next row from the result set or false if there are no more rows.
+ */
+ protected function fetchObject($cursor = null, $class = 'stdClass')
+ {
+ return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
+ }
+
+ /**
+ * Method to free up the memory used for the result set.
+ *
+ * @param mixed $cursor The optional result set cursor from which to fetch the row.
+ *
+ * @return void
+ */
+ public function freeResult($cursor = null)
+ {
+ sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
+ }
+
+ /**
+ * Method to check and see if a field exists in a table.
+ *
+ * @param string $table The table in which to verify the field.
+ * @param string $field The field to verify.
+ *
+ * @return boolean True if the field exists in the table.
+ */
+ protected function checkFieldExists($table, $field)
+ {
+ $table = $this->replacePrefix((string)$table);
+ $query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS" . " WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" .
+ " ORDER BY ORDINAL_POSITION";
+ $this->setQuery($query);
+
+ if ($this->loadResult())
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set.
+ *
+ * @param string $query The SQL statement to process.
+ * @param integer $limit The maximum affected rows to set.
+ * @param integer $offset The affected row offset to set.
+ *
+ * @return string The processed SQL statement.
+ */
+ protected function limit($query, $limit, $offset)
+ {
+ $orderBy = stristr($query, 'ORDER BY');
+ if (is_null($orderBy) || empty($orderBy))
+ {
+ $orderBy = 'ORDER BY (select 0)';
+ }
+ $query = str_ireplace($orderBy, '', $query);
+
+ $rowNumberText = ',ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM ';
+
+ $query = preg_replace('/\\s+FROM/', '\\1 ' . $rowNumberText . ' ', $query, 1);
+ $query = 'SELECT TOP ' . $this->limit . ' * FROM (' . $query . ') _myResults WHERE RowNumber > ' . $this->offset;
+
+ return $query;
+ }
+
+ /**
+ * Renames a table in the database.
+ *
+ * @param string $oldTable The name of the table to be renamed
+ * @param string $newTable The new name for the table.
+ * @param string $backup Table prefix
+ * @param string $prefix For the table - used to rename constraints in non-mysql databases
+ *
+ * @return Sqlsrv Returns this object to support chaining.
+ */
+ public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
+ {
+ $constraints = array();
+
+ if (!is_null($prefix) && !is_null($backup))
+ {
+ $constraints = $this->getTableConstraints($oldTable);
+ }
+ if (!empty($constraints))
+ {
+ $this->renameConstraints($constraints, $prefix, $backup);
+ }
+
+ $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'");
+
+ return $this->execute();
+ }
+
+ /**
+ * Locks a table in the database.
+ *
+ * @param string $tableName The name of the table to lock.
+ *
+ * @return Sqlsrv Returns this object to support chaining.
+ */
+ public function lockTable($tableName)
+ {
+ return $this;
+ }
+
+ /**
+ * Unlocks tables in the database.
+ *
+ * @return Sqlsrv Returns this object to support chaining.
+ */
+ public function unlockTables()
+ {
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Base.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Base.php
new file mode 100644
index 00000000..aa572ba4
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Base.php
@@ -0,0 +1,877 @@
+log(LogLevel::DEBUG, __CLASS__ . " :: Getting temporary file");
+ $this->tempFile = Factory::getTempFiles()->registerTempFile(dechex(crc32(microtime())) . '.sql');
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Temporary file is {$this->tempFile}");
+ // Get the base name of the dump file
+ $partNumber = intval($partNumber);
+ $baseName = $this->dumpFile;
+ if ($partNumber > 0)
+ {
+ // The file names are in the format dbname.sql, dbname.s01, dbname.s02, etc
+ if (strtolower(substr($baseName, -4)) == '.sql')
+ {
+ $baseName = substr($baseName, 0, -4) . '.s' . sprintf('%02u', $partNumber);
+ }
+ else
+ {
+ $baseName = $baseName . '.s' . sprintf('%02u', $partNumber);
+ }
+ }
+
+ if (empty($this->installerSettings))
+ {
+ // Fetch the installer settings
+ $this->installerSettings = (object)array(
+ 'installerroot' => 'installation',
+ 'sqlroot' => 'installation/sql',
+ 'databasesini' => 1,
+ 'readme' => 1,
+ 'extrainfo' => 1
+ );
+ $config = Factory::getConfiguration();
+ $installerKey = $config->get('akeeba.advanced.embedded_installer');
+ $installerDescriptors = Factory::getEngineParamsProvider()->getInstallerList();
+ if (array_key_exists($installerKey, $installerDescriptors))
+ {
+ // The selected installer exists, use it
+ $this->installerSettings = (object)$installerDescriptors[$installerKey];
+ }
+ elseif (array_key_exists('angie', $installerDescriptors))
+ {
+ // The selected installer doesn't exist, but ANGIE exists; use that instead
+ $this->installerSettings = (object)$installerDescriptors['angie'];
+ }
+ }
+
+ switch (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal'))
+ {
+ case 'output':
+ // The SQL file will be stored uncompressed in the output directory
+ $statistics = Factory::getStatistics();
+ $statRecord = $statistics->getRecord();
+ $this->saveAsName = $statRecord['absolute_path'];
+ break;
+
+ case 'normal':
+ // The SQL file will be stored in the SQL root of the archive, as
+ // specified by the particular embedded installer's settings
+ $this->saveAsName = $this->installerSettings->sqlroot . '/' . $baseName;
+ break;
+
+ case 'short':
+ // The SQL file will be stored on archive's root
+ $this->saveAsName = $baseName;
+ break;
+ }
+
+ if ($partNumber > 0)
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "AkeebaDomainDBBackup :: Creating new SQL dump part #$partNumber");
+ }
+ Factory::getLog()->log(LogLevel::DEBUG, "AkeebaDomainDBBackup :: SQL temp file is " . $this->tempFile);
+ Factory::getLog()->log(LogLevel::DEBUG, "AkeebaDomainDBBackup :: SQL file location in archive is " . $this->saveAsName);
+ }
+
+ /**
+ * Deletes any leftover files from previous backup attempts
+ *
+ */
+ protected function removeOldFiles()
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "AkeebaDomainDBBackup :: Deleting leftover files, if any");
+ if (file_exists($this->tempFile))
+ {
+ @unlink($this->tempFile);
+ }
+ }
+
+ /**
+ * Populates the table arrays with the information for the db entities to backup
+ *
+ * @return null
+ */
+ protected abstract function getTablesToBackup();
+
+ /**
+ * Runs a step of the database dump
+ *
+ * @return null
+ */
+ protected abstract function stepDatabaseDump();
+
+ /**
+ * Implements the _prepare abstract method
+ *
+ */
+ protected function _prepare()
+ {
+ $this->setStep('Initialization');
+ $this->setSubstep('');
+
+ // Process parameters, passed to us using the setup() public method
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Processing parameters");
+ if (is_array($this->_parametersArray))
+ {
+ $this->driver = array_key_exists('driver', $this->_parametersArray) ? $this->_parametersArray['driver'] : $this->driver;
+ $this->host = array_key_exists('host', $this->_parametersArray) ? $this->_parametersArray['host'] : $this->host;
+ $this->port = array_key_exists('port', $this->_parametersArray) ? $this->_parametersArray['port'] : $this->port;
+ $this->username = array_key_exists('username', $this->_parametersArray) ? $this->_parametersArray['username'] : $this->username;
+ $this->username = array_key_exists('user', $this->_parametersArray) ? $this->_parametersArray['user'] : $this->username;
+ $this->password = array_key_exists('password', $this->_parametersArray) ? $this->_parametersArray['password'] : $this->password;
+ $this->database = array_key_exists('database', $this->_parametersArray) ? $this->_parametersArray['database'] : $this->database;
+ $this->prefix = array_key_exists('prefix', $this->_parametersArray) ? $this->_parametersArray['prefix'] : $this->prefix;
+ $this->dumpFile = array_key_exists('dumpFile', $this->_parametersArray) ? $this->_parametersArray['dumpFile'] : $this->dumpFile;
+ $this->processEmptyPrefix = array_key_exists('process_empty_prefix', $this->_parametersArray) ? $this->_parametersArray['process_empty_prefix'] : $this->processEmptyPrefix;
+ }
+
+ // Make sure we have self-assigned the first part
+ $this->partNumber = 0;
+
+ // Get DB backup only mode
+ $configuration = Factory::getConfiguration();
+
+ // Find tables to be included and put them in the $_tables variable
+ $this->getTablesToBackup();
+ if ($this->getError())
+ {
+ return;
+ }
+
+ // Find where to store the database backup files
+ $this->getBackupFilePaths($this->partNumber);
+
+ // Remove any leftovers
+ $this->removeOldFiles();
+
+ // Initialize the extended INSERTs feature
+ $this->extendedInserts = ($configuration->get('engine.dump.common.extended_inserts', 0) != 0);
+ $this->packetSize = $configuration->get('engine.dump.common.packet_size', 0);
+ if ($this->packetSize == 0)
+ {
+ $this->extendedInserts = false;
+ }
+
+ // Initialize the split dump feature
+ $this->partSize = $configuration->get('engine.dump.common.splitsize', 1048576);
+ if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') == 'output')
+ {
+ $this->partSize = 0;
+ }
+ if (($this->partSize != 0) && ($this->packetSize != 0) && ($this->packetSize > $this->partSize))
+ {
+ $this->packetSize = $this->partSize / 2;
+ }
+
+ // Initialize the algorithm
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Initializing algorithm for first run");
+ $this->nextTable = array_shift($this->tables);
+
+ // If there is no table to back up we are done with the database backup
+ if (empty($this->nextTable))
+ {
+ $this->setState('postrun');
+ return;
+ }
+
+ $this->nextRange = 0;
+ $this->query = '';
+
+ // FIX 2.2: First table of extra databases was not being written to disk.
+ // This deserved a place in the Bug Fix Hall Of Fame. In subsequent calls to _init, the $fp in
+ // _writeline() was not nullified. Therefore, the first dump chunk (that is, the first table's
+ // definition and first chunk of its data) were not written to disk. This call causes $fp to be
+ // nullified, causing it to be recreated, pointing to the correct file.
+ $null = null;
+ $this->writeline($null);
+
+ // Finally, mark ourselves "prepared".
+ $this->setState('prepared');
+ }
+
+ /**
+ * Implements the _run() abstract method
+ */
+ protected function _run()
+ {
+ // Check if we are already done
+ if ($this->getState() == 'postrun')
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Already finished");
+ $this->setStep("");
+ $this->setSubstep("");
+
+ return;
+ }
+
+ // Mark ourselves as still running (we will test if we actually do towards the end ;) )
+ $this->setState('running');
+
+ /**
+ * Resume packing / post-processing part files if necessary.
+ *
+ * @see \Akeeba\Engine\Archiver\BaseArchiver::putDataFromFileIntoArchive
+ *
+ * Sometimes the SQL part file may be bigger than the big file threshold (engine.archiver.common.
+ * big_file_threshold). In this case when we try to add it to the backup archive the archiver engine figures
+ * out it has to be added uncompressed, one chunk (engine.archiver.common.chunk_size) bytes at a time. This
+ * happens in a loop. We read a chunk, push it to the archive, rinse and repeat.
+ *
+ * There are two cases when we might break the loop:
+ *
+ * 1. Not enough free space in the backup archive part and engine.postproc.common.after_part (immediate post-
+ * processing) is enabled. We break the step to let the backup part be post-processed.
+ *
+ * 2. We ran out of time copying data.
+ *
+ * The following if-blocks deal with these two cases.
+ */
+ if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') != 'output')
+ {
+ $archiver = Factory::getArchiverEngine();
+ $configuration = Factory::getConfiguration();
+
+ // Case 1. Immediate post-processing was triggered in the previous step
+ if ($configuration->get('engine.postproc.common.after_part', 0) && !empty($archiver->finishedPart))
+ {
+ if (Pack::postProcessDonePartFile($this, $archiver, $configuration))
+ {
+ return;
+ }
+ }
+
+ // We had already started archiving the db file, but it needs more time
+ if ($configuration->get('volatile.engine.archiver.processingfile', false))
+ {
+ /**
+ * We MUST NOT try to continue adding the file to the backup archive manually. Instead, we have to go
+ * through getNextDumpPart. This method will continue adding the part to the backup archive and when
+ * this is done it will remove the file and create a new one.
+ *
+ * If that method returns false it means that we either hit an error or the archiver didn't have enough
+ * time to add the part to the backup archive. In either case we have to return and let the Engine step.
+ */
+ if ($this->getNextDumpPart() === false)
+ {
+ return;
+ }
+ }
+ }
+
+ $this->stepDatabaseDump();
+
+ $null = null;
+ $this->writeline($null);
+ }
+
+ /**
+ * Implements the _finalize() abstract method
+ *
+ */
+ protected function _finalize()
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "Adding any extra SQL statements imposed by the filters");
+ $filters = Factory::getFilters();
+ $this->writeline($filters->getExtraSQL($this->databaseRoot));
+
+ // Close the file pointer (otherwise the SQL file is left behind)
+ $this->closeFile();
+
+ // If we are not just doing a main db only backup, add the SQL file to the archive
+ $finished = true;
+ $configuration = Factory::getConfiguration();
+ if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') != 'output')
+ {
+ $archiver = Factory::getArchiverEngine();
+ $configuration = Factory::getConfiguration();
+
+ if ($configuration->get('volatile.engine.archiver.processingfile', false))
+ {
+ // We had already started archiving the db file, but it needs more time
+ Factory::getLog()->log(LogLevel::DEBUG, "Continuing adding the SQL dump to the archive");
+ $archiver->addFile(null, null, null);
+
+ $this->propagateFromObject($archiver);
+
+ if ($this->getError())
+ {
+ return;
+ }
+
+ $finished = !$configuration->get('volatile.engine.archiver.processingfile', false);
+ }
+ else
+ {
+ // We have to add the dump file to the archive
+ Factory::getLog()->log(LogLevel::DEBUG, "Adding the final SQL dump to the archive");
+ $archiver->addFileRenamed($this->tempFile, $this->saveAsName);
+
+ $this->propagateFromObject($archiver);
+
+ if ($this->getError())
+ {
+ return;
+ }
+
+ $finished = !$configuration->get('volatile.engine.archiver.processingfile', false);
+ }
+ }
+ else
+ {
+ // We just have to move the dump file to its final destination
+ Factory::getLog()->log(LogLevel::DEBUG, "Moving the SQL dump to its final location");
+ $result = Platform::getInstance()->move($this->tempFile, $this->saveAsName);
+
+ if (!$result)
+ {
+ $this->setError('Could not move the SQL dump to its final location');
+ }
+ }
+
+ // Make sure that if the archiver needs more time to process the file we can supply it
+ if ($finished)
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "Removing temporary file of final SQL dump");
+ Factory::getTempFiles()->unregisterAndDeleteTempFile($this->tempFile, true);
+
+ if ($this->getError())
+ {
+ return;
+ }
+
+ $this->setState('finished');
+ }
+ }
+
+ /**
+ * Creates a new dump part
+ */
+ protected function getNextDumpPart()
+ {
+ // On database dump only mode we mustn't create part files!
+ if (Factory::getEngineParamsProvider()->getScriptingParameter('db.saveasname', 'normal') == 'output')
+ {
+ return false;
+ }
+
+ // Is the archiver still processing?
+ $configuration = Factory::getConfiguration();
+ $archiver = Factory::getArchiverEngine();
+ $stillProcessing = $configuration->get('volatile.engine.archiver.processingfile', false);
+
+ if ($stillProcessing)
+ {
+ /**
+ * The archiver is still adding the previous dump part. This means that we are called from the top few lines
+ * of the _run method. We must continue adding the previous dump part.
+ */
+ Factory::getLog()->log(LogLevel::DEBUG, "Continuing adding the SQL dump part to the archive");
+ $result = $archiver->addFile('', '', '');
+ }
+ else
+ {
+ /**
+ * There is no other dump part being processed. Therefore the current SQL dump part is still open. We must
+ * close it and ask the archiver to add it to the backup archive.
+ */
+ $this->closeFile();
+ Factory::getLog()->log(LogLevel::DEBUG, "Adding the SQL dump part to the archive");
+ $result = $archiver->addFileRenamed($this->tempFile, $this->saveAsName);
+ }
+
+ // Propagate errors and warnings from the archiver
+ $this->propagateFromObject($archiver);
+
+ // Did the archiver return an error?
+ if ($this->getError())
+ {
+ return false;
+ }
+
+ // Return false if the file didn't finish getting added to the archive
+ if ($configuration->get('volatile.engine.archiver.processingfile', false))
+ {
+ Factory::getLog()->debug("The SQL dump file has not been processed thoroughly by the archiver. Resuming in the next step.");
+
+ return false;
+ }
+
+ /**
+ * If you are here the SQL dump part file is completely added to the backup archive. All we have to do now is
+ * remove it and create a new dump part file.
+ */
+
+ // Remove the old file
+ Factory::getLog()->log(LogLevel::DEBUG, "Removing dump part's temporary file");
+ Factory::getTempFiles()->unregisterAndDeleteTempFile($this->tempFile, true);
+
+ // Create the new dump part
+ $this->partNumber++;
+ $this->getBackupFilePaths($this->partNumber);
+ $null = null;
+ $this->writeline($null);
+
+ return true;
+ }
+
+ /**
+ * Creates a new dump part, but only if required to do so
+ *
+ * @return bool
+ */
+ protected function createNewPartIfRequired()
+ {
+ if ($this->partSize == 0)
+ {
+ return true;
+ }
+
+ $filesize = 0;
+
+ if (@file_exists($this->tempFile))
+ {
+ $filesize = @filesize($this->tempFile);
+ }
+
+ $projectedSize = $filesize + strlen($this->query);
+
+ if ($this->extendedInserts)
+ {
+ $projectedSize = $filesize + $this->packetSize;
+ }
+
+ if ($projectedSize > $this->partSize)
+ {
+ return $this->getNextDumpPart();
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a table's abstract name (replacing the prefix with the magic #__ string)
+ *
+ * @param string $tableName The canonical name, e.g. 'jos_content'
+ *
+ * @return string The abstract name, e.g. '#__content'
+ */
+ protected function getAbstract($tableName)
+ {
+ // Don't return abstract names for non-CMS tables
+ if (is_null($this->prefix))
+ {
+ return $tableName;
+ }
+
+ switch ($this->prefix)
+ {
+ case '':
+ if ($this->processEmptyPrefix)
+ {
+ // This is more of a hack; it assumes all tables are core CMS tables if the prefix is empty.
+ return '#__' . $tableName;
+ }
+ else
+ {
+ // If $this->processEmptyPrefix (the process_empty_prefix config flag) is false, we don't
+ // assume anything.
+ return $tableName;
+ }
+ break;
+
+ default:
+ // Normal behaviour for 99% of sites
+ // Fix 2.4 : Abstracting the prefix only if it's found in the beginning of the table name
+ $tableAbstract = $tableName;
+ if (!empty($this->prefix))
+ {
+ if (substr($tableName, 0, strlen($this->prefix)) == $this->prefix)
+ {
+ $tableAbstract = '#__' . substr($tableName, strlen($this->prefix));
+ }
+ else
+ {
+ // FIX 2.4: If there is no prefix, it's a non-core table.
+ $tableAbstract = $tableName;
+ }
+ }
+
+ return $tableAbstract;
+ break;
+ }
+ }
+
+ /**
+ * Writes the SQL dump into the output files. If it fails, it sets the error
+ *
+ * @param string $data Data to write to the dump file. Pass NULL to force flushing to file.
+ *
+ * @return boolean TRUE on successful write, FALSE otherwise
+ */
+ protected function writeDump(&$data)
+ {
+ if (!empty($data))
+ {
+ $this->data_cache .= $data;
+
+ if (strlen($data) > $this->largest_query)
+ {
+ $this->largest_query = strlen($data);
+ Factory::getConfiguration()->set('volatile.database.largest_query', $this->largest_query);
+ }
+
+ }
+ if ((strlen($this->data_cache) >= $this->cache_size) || (is_null($data) && (!empty($this->data_cache))))
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "Writing " . strlen($this->data_cache) . " bytes to the dump file");
+ $result = $this->writeline($this->data_cache);
+ if (!$result)
+ {
+ $errorMessage = 'Couldn\'t write to the SQL dump file ' . $this->tempFile . '; check the temporary directory permissions and make sure you have enough disk space available.';
+ $this->setError($errorMessage);
+
+ return false;
+ }
+ $this->data_cache = '';
+ }
+
+ return true;
+ }
+
+ /**
+ * Saves the string in $fileData to the file $backupfile. Returns TRUE. If saving
+ * failed, return value is FALSE.
+ *
+ * @param string $fileData Data to write. Set to null to close the file handle.
+ *
+ * @return boolean TRUE is saving to the file succeeded
+ */
+ protected function writeline(&$fileData)
+ {
+ if (!is_resource($this->fp))
+ {
+ $this->fp = @fopen($this->tempFile, 'a');
+ if ($this->fp === false)
+ {
+ $this->setError('Could not open ' . $this->tempFile . ' for append, in DB dump.');
+
+ return;
+ }
+ }
+
+ if (is_null($fileData))
+ {
+ if (is_resource($this->fp))
+ {
+ @fclose($this->fp);
+ }
+ $this->fp = null;
+
+ return true;
+ }
+ else
+ {
+ if ($this->fp)
+ {
+ $ret = fwrite($this->fp, $fileData);
+ @clearstatcache();
+
+ // Make sure that all data was written to disk
+ return ($ret == strlen($fileData));
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ function _onSerialize()
+ {
+ $this->closeFile();
+ }
+
+ function __destruct()
+ {
+ $this->closeFile();
+ }
+
+ public function closeFile()
+ {
+ if (is_resource($this->fp))
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, "Closing SQL dump file.");
+
+ @fclose($this->fp);
+ $this->fp = null;
+ }
+ }
+
+ /**
+ * Return an instance of DriverBase
+ *
+ * @return DriverBase
+ */
+ protected function &getDB()
+ {
+ $host = $this->host . ($this->port != '' ? ':' . $this->port : '');
+ $user = $this->username;
+ $password = $this->password;
+ $driver = $this->driver;
+ $database = $this->database;
+ $prefix = is_null($this->prefix) ? '' : $this->prefix;
+ $options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database, 'prefix' => $prefix);
+
+ $db = Factory::getDatabase($options);
+
+ if ($error = $db->getError())
+ {
+ $this->setError(__CLASS__ . ' :: Database Error: ' . $error);
+ $false = false;
+
+ return $false;
+ }
+
+ if ($db->getErrorNum() > 0)
+ {
+ $error = $db->getErrorMsg();
+ $this->setError(__CLASS__ . ' :: Database Error: ' . $error);
+ $false = false;
+
+ return $false;
+ }
+
+ return $db;
+ }
+
+ /**
+ * Return the current database name by querying the database connection object (e.g. SELECT DATABASE() in MySQL)
+ *
+ * @return string
+ */
+ abstract protected function getDatabaseNameFromConnection();
+
+ /**
+ * Returns the database name. If the name was not declared when the object was created we will go through the
+ * getDatabaseNameFromConnection method to populate it.
+ *
+ * @return string
+ */
+ protected function getDatabaseName()
+ {
+ if (empty($this->database))
+ {
+ $this->database = $this->getDatabaseNameFromConnection();
+ }
+
+ return $this->database;
+ }
+
+ public function callStage($stage)
+ {
+ switch ($stage)
+ {
+ case '_prepare':
+ return $this->_prepare();
+ break;
+
+ case '_run':
+ return $this->_run();
+ break;
+
+ case '_finalize':
+ return $this->_finalize();
+ break;
+ }
+ }
+
+ /**
+ * Post process a quoted value before it's written to the database dump.
+ * So far it's only required for SQL Server which has a problem escaping
+ * newline characters...
+ *
+ * @param string $value The quoted value to post-process
+ *
+ * @return string
+ */
+ protected function postProcessQuotedValue($value)
+ {
+ return $value;
+ }
+
+ /**
+ * Returns a preamble for the data dump portion of the SQL backup. This is
+ * used to output commands before the first INSERT INTO statement for a
+ * table when outputting a plain SQL file.
+ *
+ * Practical use: the SET IDENTITY_INSERT sometable ON required for SQL Server
+ *
+ * @param string $tableAbstract Abstract name of the table, e.g. #__foobar
+ * @param string $tableName Real name of the table, e.g. abc_foobar
+ * @param integer $maxRange Row count on this table
+ *
+ * @return string The SQL commands you want to be written in the dump file
+ */
+ protected function getDataDumpPreamble($tableAbstract, $tableName, $maxRange)
+ {
+ return '';
+ }
+
+ /**
+ * Returns an epilogue for the data dump portion of the SQL backup. This is
+ * used to output commands after the last INSERT INTO statement for a
+ * table when outputting a plain SQL file.
+ *
+ * Practical use: the SET IDENTITY_INSERT sometable OFF required for SQL Server
+ *
+ * @param string $tableAbstract Abstract name of the table, e.g. #__foobar
+ * @param string $tableName Real name of the table, e.g. abc_foobar
+ * @param integer $maxRange Row count on this table
+ *
+ * @return string The SQL commands you want to be written in the dump file
+ */
+ protected function getDataDumpEpilogue($tableAbstract, $tableName, $maxRange)
+ {
+ return '';
+ }
+
+ /**
+ * Return a list of field names for the INSERT INTO statements. This is only
+ * required for Microsoft SQL Server because without it the SET IDENTITY_INSERT
+ * has no effect.
+ *
+ * @param array $fieldNames A list of field names in array format
+ * @param integer $numOfFields The number of fields we should be dumping
+ *
+ * @return string
+ */
+ protected function getFieldListSQL($fieldNames, $numOfFields)
+ {
+ return '';
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native.php
new file mode 100644
index 00000000..671cea18
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native.php
@@ -0,0 +1,115 @@
+log(LogLevel::DEBUG, __CLASS__ . " :: New instance");
+ }
+
+ protected function _prepare()
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Processing parameters");
+
+ $options = null;
+
+ // Get the DB connection parameters
+ if (is_array($this->_parametersArray))
+ {
+ $driver = array_key_exists('driver', $this->_parametersArray) ? $this->_parametersArray['driver'] : 'mysql';
+ $host = array_key_exists('host', $this->_parametersArray) ? $this->_parametersArray['host'] : '';
+ $port = array_key_exists('port', $this->_parametersArray) ? $this->_parametersArray['port'] : '';
+ $username = array_key_exists('username', $this->_parametersArray) ? $this->_parametersArray['username'] : '';
+ $username = array_key_exists('user', $this->_parametersArray) ? $this->_parametersArray['user'] : $username;
+ $password = array_key_exists('password', $this->_parametersArray) ? $this->_parametersArray['password'] : '';
+ $database = array_key_exists('database', $this->_parametersArray) ? $this->_parametersArray['database'] : '';
+ $prefix = array_key_exists('prefix', $this->_parametersArray) ? $this->_parametersArray['prefix'] : '';
+
+ if (($driver == 'mysql') && !function_exists('mysql_connect'))
+ {
+ $driver = 'mysqli';
+ }
+
+ $options = array(
+ 'driver' => $driver,
+ 'host' => $host . ($port != '' ? ':' . $port : ''),
+ 'user' => $username,
+ 'password' => $password,
+ 'database' => $database,
+ 'prefix' => is_null($prefix) ? '' : $prefix
+ );
+ }
+
+ $db = Factory::getDatabase($options);
+
+ $driverType = $db->getDriverType();
+ $className = '\\Akeeba\\Engine\\Dump\\Native\\' . ucfirst($driverType);
+
+ // Check if we have a native dump driver
+ if (!class_exists($className, true))
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Native database dump engine $className not found; trying Reverse Engineering instead");
+ // Native driver nor found, I will try falling back to reverse engineering
+ $className = '\\Akeeba\\Engine\\Dump\\Reverse\\' . ucfirst($driverType);
+ }
+
+ if (!class_exists($className, true))
+ {
+ $this->setState('error', 'Akeeba Engine does not have a native dump engine for ' . $driverType . ' databases');
+ }
+ else
+ {
+ Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: Instanciating new native database dump engine $className");
+ $this->_engine = new $className;
+ $this->_engine->setup($this->_parametersArray);
+ $this->_engine->callStage('_prepare');
+ $this->setState($this->_engine->getState(), $this->_engine->getError());
+ $this->propagateFromObject($this->_engine);
+ }
+ }
+
+ protected function _finalize()
+ {
+ $this->_engine->callStage('_finalize');
+ $this->setState($this->_engine->getState(), $this->_engine->getError());
+ $this->propagateFromObject($this->_engine);
+ }
+
+ protected function _run()
+ {
+ $this->_engine->callStage('_run');
+ $this->propagateFromObject($this->_engine);
+ $this->setState($this->_engine->getState(), $this->_engine->getError());
+ $this->setStep($this->_engine->getStep());
+ $this->setSubstep($this->_engine->getSubstep());
+ $this->partNumber = $this->_engine->partNumber;
+ }
+
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native/Mysql.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native/Mysql.php
new file mode 100644
index 00000000..7569e0b7
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Dump/Native/Mysql.php
@@ -0,0 +1,2069 @@
+ MySQL database server host name or IP address
+ * port In this directory, Akeeba Backup and Akeeba Solo store the optional filters (Optional Filters in the Configuration + page). Unlike regular filters which are always loaded, optional filters are only loaded when the user chooses to + enable them. Each filter consists of two files: filtername.ini and filtername.php. The former + contains the filter-specific configuration options and the later contains the actual filter code.
+ ++ If you want to create new optional filters, put them in here. Do note that the INI file must always contain a + boolean key named core.filters.filtername.enabled which controls the loading of this particular filter. +
+ ++ Optional filters are always named Akeeba\Engine\Filter\Stack\StackFiltername so that the autoloader can + find them. For the same reason their filename must be StackFiltername.php Please watch out for the + letter case in the names, it's important. +
+ + diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackDateconditional.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackDateconditional.php new file mode 100644 index 00000000..881e9982 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackDateconditional.php @@ -0,0 +1,74 @@ +object = 'file'; + $this->subtype = 'all'; + $this->method = 'api'; + + if (Factory::getKettenrad()->getTag() == 'restorepoint') + { + $this->enabled = false; + } + } + + protected function is_excluded_by_api($test, $root) + { + static $from_datetime; + + $config = Factory::getConfiguration(); + + if (is_null($from_datetime)) + { + $user_setting = $config->get('core.filters.dateconditional.start'); + $from_datetime = strtotime($user_setting); + } + + // Get the filesystem path for $root + $fsroot = $config->get('volatile.filesystem.current_root', ''); + $ds = ($fsroot == '') || ($fsroot == '/') ? '' : DIRECTORY_SEPARATOR; + $filename = $fsroot . $ds . $test; + + // Get the timestamp of the file + $timestamp = @filemtime($filename); + + // If we could not get this information, include the file in the archive + if ($timestamp === false) + { + return false; + } + + // Compare it with the user-defined minimum timestamp and exclude if it's older than that + if ($timestamp <= $from_datetime) + { + return true; + } + + // No match? Just include the file! + return false; + } + +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackErrorlogs.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackErrorlogs.php new file mode 100644 index 00000000..22a1a692 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackErrorlogs.php @@ -0,0 +1,54 @@ +object = 'file'; + $this->subtype = 'all'; + $this->method = 'api'; + + if (empty($this->filter_name)) + { + $this->filter_name = strtolower(basename(__FILE__, '.php')); + } + + parent::__construct(); + } + + protected function is_excluded_by_api($test, $root) + { + // Is it an error log? Exclude the file. + if (in_array(basename($test), array( + 'php_error', + 'php_errorlog', + 'error_log', + 'error.log' + ))) { + return true; + } + + // No match? Just include the file! + return false; + } + +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackHoststats.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackHoststats.php new file mode 100644 index 00000000..7f2cd326 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/StackHoststats.php @@ -0,0 +1,50 @@ +object = 'dir'; + $this->subtype = 'all'; + $this->method = 'api'; + + if (empty($this->filter_name)) + { + $this->filter_name = strtolower(basename(__FILE__, '.php')); + } + + parent::__construct(); + } + + protected function is_excluded_by_api($test, $root) + { + if($test == 'stats') + { + return true; + } + + // No match? Just include the file! + return false; + } + +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/dateconditional.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/dateconditional.ini new file mode 100644 index 00000000..d01dcf59 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/dateconditional.ini @@ -0,0 +1,20 @@ +; "Date conditional" optional filter +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: dateconditional.ini 409 2011-01-24 09:30:22Z nikosdion $ + +; ====================================================================== +; Advanced configuration +; ====================================================================== + +[core.filters.dateconditional.enabled] +default = 0 +type = bool +title = COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_ENABLED_TITLE +description = COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_ENABLED_DESCRIPTION +bold = 1 + +[core.filters.dateconditional.start] +default = 1981-02-20 12:15 GMT+2 +type = string +title = COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_START_TITLE +description = COM_AKEEBA_CONFIG_OPTIONALFILTERS_DATECONDITIONAL_START_DESCRIPTION diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/errorlogs.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/errorlogs.ini new file mode 100644 index 00000000..8b15d9b8 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/errorlogs.ini @@ -0,0 +1,14 @@ +; "Error Logs" optional filter +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: dateconditional.ini 409 2011-01-24 09:30:22Z nikosdion $ + +; ====================================================================== +; Advanced configuration +; ====================================================================== + +[core.filters.errorlogs.enabled] +default=1 +type=bool +title=COM_AKEEBA_CONFIG_OPTIONALFILTERS_ERRORLOGS_ENABLED_TITLE +description=COM_AKEEBA_CONFIG_OPTIONALFILTERS_ERRORLOGS_ENABLED_DESCRIPTION +bold=1 diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/hoststats.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/hoststats.ini new file mode 100644 index 00000000..77e3e1a3 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Stack/hoststats.ini @@ -0,0 +1,14 @@ +; "Error Logs" optional filter +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: dateconditional.ini 409 2011-01-24 09:30:22Z nikosdion $ + +; ====================================================================== +; Advanced configuration +; ====================================================================== + +[core.filters.hoststats.enabled] +default=1 +type=bool +title=COM_AKEEBA_CONFIG_OPTIONALFILTERS_HOSTSTATS_ENABLED_TITLE +description=COM_AKEEBA_CONFIG_OPTIONALFILTERS_HOSTSTATS_ENABLED_DESCRIPTION +bold=1 diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tabledata.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tabledata.php new file mode 100644 index 00000000..000ecc14 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tabledata.php @@ -0,0 +1,44 @@ +object = 'dbobject'; + $this->subtype = 'content'; + $this->method = 'direct'; + + if (empty($this->filter_name)) + { + $this->filter_name = strtolower(basename(__FILE__, '.php')); + } + + if (Factory::getKettenrad()->getTag() == 'restorepoint') + { + $this->enabled = false; + } + + parent::__construct(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tables.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tables.php new file mode 100644 index 00000000..6d22507b --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Filter/Tables.php @@ -0,0 +1,46 @@ +object = 'dbobject'; + $this->subtype = 'all'; + $this->method = 'direct'; + + if (Factory::getKettenrad()->getTag() == 'restorepoint') + { + $this->enabled = false; + } + + if (empty($this->filter_name)) + { + $this->filter_name = strtolower(basename(__FILE__, '.php')); + } + + if (Factory::getKettenrad()->getTag() == 'restorepoint') + { + $this->enabled = false; + } + + parent::__construct(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform.php new file mode 100644 index 00000000..cee2b1f6 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform.php @@ -0,0 +1,352 @@ +platformName; + } + + return array_merge( + static::$knownPlatformsDirectories, + $defaultPath + ); + } + + /** + * Public class constructor + * + * @param string $platform Optional; platform name. Leave blank to auto-detect. + * + * @throws \Exception When the platform cannot be loaded + */ + public function __construct($platform = null) + { + if (empty($platform) || is_null($platform)) + { + $platform = static::detectPlatform(); + } + + if (empty($platform)) + { + throw new \Exception('Can not find a suitable Akeeba Engine platform for your site'); + } + + static::$platformConnectorInstance = static::loadPlatform($platform); + + if (!is_object(static::$platformConnectorInstance)) + { + throw new \Exception("Can not load Akeeba Engine platform $platform"); + } + } + + /** + * Auto-detect the suitable platform for this site + * + * @return string + * + * @throws \Exception When no platform is detected + */ + protected static function detectPlatform() + { + $platforms = static::listPlatforms(); + + if (empty($platforms)) + { + throw new \Exception('No Akeeba Engine platform class found'); + } + + $bestPlatform = (object)array( + 'name' => null, + 'priority' => 0, + ); + + foreach ($platforms as $platform => $path) + { + $o = static::loadPlatform($platform, $path); + + if (is_null($o)) + { + continue; + } + + if ($o->isThisPlatform()) + { + if ($o->priority > $bestPlatform->priority) + { + $bestPlatform->priority = $o->priority; + $bestPlatform->name = $platform; + } + } + } + + return $bestPlatform->name; + } + + /** + * Load a given platform and return the platform object + * + * @param string $platform Platform name + * @param string $path The path to laod the platform from (optional) + * + * @return \Akeeba\Engine\Platform\Base + */ + protected static function &loadPlatform($platform, $path = null) + { + if (empty($path)) + { + if (isset(static::$knownPlatformsDirectories[$platform])) + { + $path = static::$knownPlatformsDirectories[$platform]; + } + } + + if (empty($path)) + { + $path = dirname(__FILE__) . '/' . $platform; + } + + $classFile = $path . '/Platform.php'; + $className = '\\Akeeba\\Engine\\Platform\\' . ucfirst($platform); + + $null = null; + + if (!file_exists($classFile)) + { + return $null; + } + + require_once($classFile); + + if (!class_exists($className, false)) + { + return $null; + } + + $o = new $className; + + return $o; + } + + /** + * Lists available platforms + * + * @staticvar array $platforms Static cache of the available platforms + * + * @return array The list of available platforms + */ + static public function listPlatforms() + { + if (empty(static::$knownPlatformsDirectories)) + { + $di = new \DirectoryIterator(__DIR__ . '/Platform'); + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if (!$file->isDir()) + { + continue; + } + + if ($file->isDot()) + { + continue; + } + + $shortName = $file->getFilename(); + + static::$knownPlatformsDirectories[$shortName] = $file->getRealPath(); + } + } + + return static::$knownPlatformsDirectories; + } + + /** + * Add a platform to the list of known platforms + * + * @param string $slug Short name of the platform + * @param string $platformDirectory The path where you can find it + * + * @return void + */ + public static function addPlatform($slug, $platformDirectory) + { + if (empty(static::$knownPlatformsDirectories)) + { + static::listPlatforms(); + + static::$knownPlatformsDirectories[$slug] = $platformDirectory; + } + } + + /** + * Magic method to proxy all calls to the loaded platform object + * + * @param string $name The name of the method to call + * @param array $arguments The arguments to pass + * + * @return mixed The result of the method being called + * + * @throws \Exception When the platform isn't loaded or an non-existent method is called + */ + public function __call($name, array $arguments) + { + if (is_null(static::$platformConnectorInstance)) + { + throw new \Exception('Akeeba Engine platform is not loaded'); + } + + if (method_exists(static::$platformConnectorInstance, $name)) + { + // Call_user_func_array is ~3 times slower than direct method calls. + // See the on-line PHP documentation page of call_user_func_array for more information. + switch (count($arguments)) + { + case 0 : + $result = static::$platformConnectorInstance->$name(); + break; + case 1 : + $result = static::$platformConnectorInstance->$name($arguments[0]); + break; + case 2: + $result = static::$platformConnectorInstance->$name($arguments[0], $arguments[1]); + break; + case 3: + $result = static::$platformConnectorInstance->$name($arguments[0], $arguments[1], $arguments[2]); + break; + case 4: + $result = static::$platformConnectorInstance->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]); + break; + case 5: + $result = static::$platformConnectorInstance->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]); + break; + default: + // Resort to using call_user_func_array for many segments + $result = call_user_func_array(array(static::$platformConnectorInstance, $name), $arguments); + } + return $result; + } + else + { + throw new \Exception('Method ' . $name . ' not found in Akeeba Platform'); + } + } + + /** + * Magic getter for the properties of the loaded platform + * + * @param string $name The name of the property to get + * + * @return mixed The value of the property + */ + public function __get($name) + { + if (!isset(static::$platformConnectorInstance->$name) || !property_exists(static::$platformConnectorInstance, $name)) + { + static::$platformConnectorInstance->$name = null; + user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE); + } + + return static::$platformConnectorInstance->$name; + } + + /** + * Magic setter for the properties of the loaded platform + * + * @param string $name The name of the property to set + * @param mixed $value The value of the property to set + */ + public function __set($name, $value) + { + if (isset(static::$platformConnectorInstance->$name) || property_exists(static::$platformConnectorInstance, $name)) + { + static::$platformConnectorInstance->$name = $value; + } + else + { + static::$platformConnectorInstance->$name = null; + user_error(__CLASS__ . ' does not support property ' . $name, E_NOTICE); + } + } + + /** + * Force a platform connector object instance. This is used only in Unit Tests. + * + * @param \Akeeba\Engine\Platform\Base $platform The platform connector object to force + * + * @throws \Exception when used outside of Unit Tests + */ + public static function forcePlatformInstance(Base $platform) + { + if (!interface_exists('PHPUnit_Exception', false)) + { + $method = __METHOD__; + throw new \Exception("You can only use $method in Unit Tests", 500); + } + + static::$platformConnectorInstance = $platform; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Base.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Base.php new file mode 100644 index 00000000..ee664cd5 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Base.php @@ -0,0 +1,1005 @@ +platformName); + } + + public function isThisPlatform() + { + return true; + } + + public function register_autoloader() + { + } + + /** + * Saves the current configuration to the database table + * + * @param int $profile_id The profile where to save the configuration to, defaults to current profile + * + * @return bool True if everything was saved properly + */ + public function save_configuration($profile_id = null) + { + // Load the database class + $db = Factory::getDatabase($this->get_platform_database_options()); + + if (!$db->connected()) + { + return false; + } + + // Get the active profile number, if no profile was specified + if (is_null($profile_id)) + { + $profile_id = $this->get_active_profile(); + } + + // Get an INI format registry dump + $registry = Factory::getConfiguration(); + $dump_profile = $registry->exportAsINI(); + + // Encrypt the registry dump if required + $secureSettings = Factory::getSecureSettings(); + $dump_profile = $secureSettings->encryptSettings($dump_profile); + + // Does the record already exist? + $exists = true; + $sql = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->qn($this->tableNameProfiles)) + ->where($db->qn('id') . ' = ' . $db->q($profile_id)); + + try + { + $count = $db->setQuery($sql)->loadResult(); + $exists = ($count > 0); + } + catch (\Exception $e) + { + $exists = true; + } + + if ($exists) + { + $sql = $db->getQuery(true) + ->update($db->qn($this->tableNameProfiles)) + ->set($db->qn('configuration') . ' = ' . $db->q($dump_profile)) + ->where($db->qn('id') . ' = ' . $db->q($profile_id)); + } + else + { + $sql = $db->getQuery(true) + ->insert($db->qn($this->tableNameProfiles)) + ->columns(array($db->qn('id'), $db->qn('description'), $db->qn('configuration'), + $db->qn('filters'), $db->qn('quickicon'))) + ->values( + $db->q(1) . ', ' . + $db->q("Default backup profile") . ', ' . + $db->q($dump_profile) . ', ' . + $db->q('') . ', ' . + $db->q(1) + ); + } + + $db->setQuery($sql); + + try + { + $result = $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + return ($result == true); + } + + /** + * Loads the current configuration off the database table + * + * @param int $profile_id The profile where to read the configuration from, defaults to current profile + * + * @return bool True if everything was read properly + * + * @throws DecryptionException When the settings cannot be decrypted + */ + public function load_configuration($profile_id = null) + { + // Load the database class + $db = Factory::getDatabase($this->get_platform_database_options()); + + // Get the active profile number, if no profile was specified + if (is_null($profile_id)) + { + $profile_id = $this->get_active_profile(); + } + + // Initialize the registry + $registry = Factory::getConfiguration(); + $registry->reset(); + + // Is the database connected? + if (!$db->connected()) + { + return false; + } + + // Load the INI format local configuration dump off the database + $sql = $db->getQuery(true) + ->select($db->qn('configuration')) + ->from($db->qn($this->tableNameProfiles)) + ->where($db->qn('id') . ' = ' . $db->q($profile_id)); + + $db->setQuery($sql); + $databaseData = $db->loadResult(); + + /** + * If the profile is not the default and we can't load anything let's switch back to the default profile. + * + * You will end up here when you have opened the application in two different browsers and Browser A is used to + * delete the active profile you were using with Browser B. If we were not to load the default profile Browser B + * would try to save the default configuration data to the deleted profile. However, since the profile does not + * exist in the database any more the load_configuration at the end of the following if-block would trigger the + * same code path, recursively, infinitely until you reached the maximum nesting level in PHP, run out of memory + * or hit the execution time limit. + */ + if ((empty($databaseData) || is_null($databaseData)) && ($profile_id != 1)) + { + return $this->load_configuration(1); + } + + if (empty($databaseData) || is_null($databaseData)) + { + // No configuration was saved yet - store the defaults + $saved = $this->save_configuration($profile_id); + + // If this is the case we probably don't have the necesary table. Throw an exception. + if (!$saved) + { + throw new \RuntimeException("Could not save data to backup profile #$profile_id", 500); + } + + return $this->load_configuration($profile_id); + } + + // Configuration found. Convert to array format. + if (function_exists('get_magic_quotes_runtime')) + { + if (@get_magic_quotes_runtime()) + { + $databaseData = stripslashes($databaseData); + } + } + + // Decrypt the data if required + $secureSettings = Factory::getSecureSettings(); + $noData = empty($databaseData); + $signature = ($noData || (strlen($databaseData) < 12)) ? '' : substr($databaseData, 0, 12); + $parsedData = array(); + + /** + * Special case: profile data is encrypted but encryption is set to false. This means that the user has just + * asked for the encryption to be disabled. We have to NOT load the settings so that the application has the + * chance to decode the data and write the decoded data back to the database. + */ + + if (!$secureSettings->supportsEncryption() && in_array($signature, array('###AES128###', '###CTR128###'))) + { + $dataArray = array('volatile' => array('fake_decrypt_flag' => 1)); + } + else + { + $databaseData = $secureSettings->decryptSettings($databaseData); + $dataArray = ParseIni::parse_ini_file($databaseData, true, true); + + // Did the decryption fail and we were asked to throw an exception? + if ($this->decryptionException && !$noData) + { + // No decrypted data + if (empty($databaseData)) + { + throw new DecryptionException; + } + + // Corrupt data + if (!strstr($databaseData, '[akeeba]')) + { + throw new DecryptionException; + } + } + } + + unset($databaseData); + + foreach ($dataArray as $section => $row) + { + if ($section == 'volatile') + { + continue; + } + + if (is_array($row) && !empty($row)) + { + foreach ($row as $key => $value) + { + $parsedData["$section.$key"] = $value; + } + } + } + + unset($dataArray); + + // Import the configuration array + $protected_keys = $registry->getProtectedKeys(); + $registry->resetProtectedKeys(); + $registry->mergeArray($parsedData, false, false); + + // Old profiles have advanced.proc_engine instead of advanced.postproc_engine. Migrate them. + $procEngine = $registry->get('akeeba.advanced.proc_engine', null); + + if (!empty($procEngine)) + { + $registry->set('akeeba.advanced.postproc_engine', $procEngine); + $registry->set('akeeba.advanced.proc_engine', null); + } + + // Apply config overrides + if (is_array($this->configOverrides) && !empty($this->configOverrides)) + { + $registry->mergeArray($this->configOverrides, false, false); + } + + $registry->setProtectedKeys($protected_keys); + $registry->activeProfile = $profile_id; + + return true; + } + + public function get_stock_directories() + { + return array(); + } + + public function get_site_root() + { + return ''; + } + + public function get_installer_images_path() + { + return ''; + } + + public function get_active_profile() + { + return 1; + } + + public function get_profile_name($id = null) + { + return ''; + } + + public function get_backup_origin() + { + return 'backend'; + } + + public function get_timestamp_database($date = 'now') + { + return ''; + } + + public function get_local_timestamp($format) + { + $dateNow = new \DateTime('now', new \DateTimeZone('UTC')); + + return $dateNow->format($format); + } + + public function get_host() + { + return ''; + } + + public function get_site_name() + { + return ''; + } + + public function get_default_database_driver($use_platform = true) + { + return '\\Akeeba\\Engine\\Driver\\Mysqli'; + } + + /** + * Creates or updates the statistics record of the current backup attempt + * + * @param int $id Backup record ID, use null for new record + * @param array $data The data to store + * @param \Akeeba\Engine\Base\BaseObject $caller The calling object + * + * @return int|null|bool The new record id, or null if this doesn't apply, or false if it failed + */ + public function set_or_update_statistics($id = null, $data = array(), &$caller) + { + // No valid data? + if (!is_array($data)) + { + return null; + } + + // No data at all? + if (empty($data)) + { + return null; + } + + $db = Factory::getDatabase($this->get_platform_database_options()); + + $tableFields = $db->getTableColumns($this->tableNameStats); + $tableFields = array_keys($tableFields); + + if (is_null($id)) + { + // Create a new record + $sql_fields = array(); + $sql_values = ''; + + foreach ($data as $key => $value) + { + if (!in_array($key, $tableFields)) + { + continue; + } + + $sql_fields[] = $db->qn($key); + $sql_values .= (!empty($sql_values) ? ',' : '') . $db->quote($value); + } + + $sql = $db->getQuery(true) + ->insert($db->quoteName($this->tableNameStats)) + ->columns($sql_fields) + ->values($sql_values); + + $db->setQuery($sql); + + try + { + $db->query(); + } + catch (\Exception $exc) + { + if (is_object($caller) && ($caller instanceof BaseObject)) + { + $caller->setError($exc->getMessage()); + } + + return false; + } + + return $db->insertid(); + } + else + { + $sql_set = array(); + foreach ($data as $key => $value) + { + if ($key == 'id') + { + continue; + } + + $sql_set[] = $db->qn($key) . '=' . $db->q($value); + } + $sql = $db->getQuery(true) + ->update($db->qn($this->tableNameStats)) + ->set($sql_set) + ->where($db->qn('id') . '=' . $db->q($id)); + $db->setQuery($sql); + + try + { + $db->query(); + } + catch (\Exception $exc) + { + if (is_object($caller) && ($caller instanceof BaseObject)) + { + $caller->setError($exc->getMessage()); + } + + return false; + } + + return null; + } + } + + + /** + * Loads and returns a backup statistics record as a hash array + * + * @param int $id Backup record ID + * + * @return array + */ + public function get_statistics($id) + { + $db = Factory::getDatabase($this->get_platform_database_options()); + $query = $db->getQuery(true) + ->select('*') + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('id') . ' = ' . $db->q($id)); + $db->setQuery($query); + + return $db->loadAssoc(true); + } + + + /** + * Completely removes a backup statistics record + * + * @param int $id Backup record ID + * + * @return bool True on success + */ + public function delete_statistics($id) + { + $db = Factory::getDatabase($this->get_platform_database_options()); + $query = $db->getQuery(true) + ->delete($db->qn($this->tableNameStats)) + ->where($db->qn('id') . ' = ' . $db->q($id)); + $db->setQuery($query); + + $result = true; + try + { + $db->query(); + } + catch (\Exception $exc) + { + $result = false; + } + + return $result; + } + + + /** + * Returns a list of backup statistics records, respecting the pagination + * + * The $config array allows the following options to be set: + * limitstart int Offset in the recordset to start from + * limit int How many records to return at once + * filters array An array of filters to apply to the results. Alternatively you can just pass a profile + * ID to filter by that profile. order array Record ordering information (by and ordering) + * + * @return array + */ + function &get_statistics_list($config = array()) + { + $defaultConfiguration = array( + 'limitstart' => 0, + 'limit' => 0, + 'filters' => array(), + 'order' => null + ); + $config = (object)array_merge($defaultConfiguration, $config); + + $db = Factory::getDatabase($this->get_platform_database_options()); + + $query = $db->getQuery(true); + + if (!empty($config->filters)) + { + if (is_array($config->filters)) + { + if (!empty($config->filters)) + { + // Parse the filters array + foreach ($config->filters as $f) + { + $clause = $db->qn($f['field']); + if (array_key_exists('operand', $f)) + { + $clause .= ' ' . strtoupper($f['operand']) . ' '; + if ($f['operand'] == 'BETWEEN') + { + $clause .= $db->q($f['value']) . ' AND ' . $db->q($f['value2']); + } + elseif ($f['operand'] == 'LIKE') + { + $clause .= '\'%' . $db->escape($f['value']) . '%\''; + } + else + { + $clause .= $db->q($f['value']); + } + } + else + { + $clause .= ' = ' . $db->q($f['value']); + } + + $query->where($clause); + } + } + } + else + { + // Legacy mode: profile ID given + $query->where($db->qn('profile_id') . ' = ' . $db->q($config->filters)); + } + } + + if (empty($config->order) || !is_array($config->order)) + { + $config->order = array( + 'by' => 'id', + 'order' => 'DESC' + ); + } + + $query->select('*') + ->from($db->qn($this->tableNameStats)) + ->order($db->qn($config->order['by']) . " " . strtoupper($config->order['order'])); + + $db->setQuery($query, $config->limitstart, $config->limit); + + $list = $db->loadAssocList(); + + return $list; + } + + /** + * Return the total number of statistics records + * + * @param array $filters An array of filters to apply to the results. Alternatively you can just pass a profile + * ID to filter by that profile. + * + * @return int + */ + function get_statistics_count($filters = null) + { + $db = Factory::getDatabase($this->get_platform_database_options()); + + $query = $db->getQuery(true); + + if (!empty($filters)) + { + if (is_array($filters)) + { + if (!empty($filters)) + { + // Parse the filters array + foreach ($filters as $f) + { + $clause = $db->quoteName($f['field']); + if (array_key_exists('operand', $f)) + { + $clause .= ' ' . strtoupper($f['operand']) . ' '; + } + else + { + $clause .= ' = '; + } + if ($f['operand'] == 'BETWEEN') + { + $clause .= $db->q($f['value']) . ' AND ' . $db->q($f['value2']); + } + elseif ($f['operand'] == 'LIKE') + { + $clause .= '\'%' . $db->escape($f['value']) . '%\''; + } + else + { + $clause .= $db->q($f['value']); + } + $query->where($clause); + } + } + } + else + { + // Legacy mode: profile ID given + $query->where($db->qn('profile_id') . ' = ' . $db->q($filters)); + } + } + + $query->select('COUNT(*)') + ->from($db->quoteName($this->tableNameStats)); + $db->setQuery($query); + + return $db->loadResult(); + } + + /** + * Returns an array with the specifics of running backups + * + * @param string $tag + * + * @throws \Akeeba\Engine\Driver\QueryException + * + * @return array Array list of associative arrays + */ + public function get_running_backups($tag = null) + { + $db = Factory::getDatabase($this->get_platform_database_options()); + $query = $db->getQuery(true) + ->select('*') + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('status') . ' = ' . $db->q('run')) + ->where(' NOT ' . $db->qn('archivename') . ' = ' . $db->q('')); + if (!empty($tag)) + { + $query->where($db->qn('origin') . ' LIKE ' . $db->q($tag . '%')); + } + $db->setQuery($query); + + return $db->loadAssocList(); + } + + /** + * Multiple backup attempts can share the same backup file name. Only + * the last backup attempt's file is considered valid. Previous attempts + * have to be deemed "obsolete". This method returns a list of backup + * statistics ID's with "valid"-looking names. IT DOES NOT CHECK FOR THE + * EXISTENCE OF THE BACKUP FILE! + * + * @param bool $useprofile If true, it will only return backup records of the current profile + * @param array $tagFilters Which tags to include; leave blank for all. If the first item is "NOT", then all + * tags EXCEPT those listed will be included. * + * @param string $ordering + * + * @throws \Akeeba\Engine\Driver\QueryException + * + * @return array A list of ID's for records w/ "valid"-looking backup files + */ + public function &get_valid_backup_records($useprofile = false, $tagFilters = array(), $ordering = 'DESC') + { + $db = Factory::getDatabase($this->get_platform_database_options()); + + $query2 = $db->getQuery(true) + ->select('MAX(' . $db->qn('id') . ') AS ' . $db->qn('id')) + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('status') . ' = ' . $db->q('complete')) + ->group($db->qn('absolute_path')); + + $query = $db->getQuery(true) + ->select($db->qn('id')) + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('filesexist') . ' = ' . $db->q(1)) + ->where($db->qn('id') . ' IN (' . $query2 . ')') + ->where('NOT ' . $db->qn('absolute_path') . ' = ' . $db->q('')) + ->order($db->qn('id') . ' ' . $ordering); + + if ($useprofile) + { + $profile_id = $this->get_active_profile(); + $query->where($db->qn('profile_id') . " = " . $db->q($profile_id)); + } + + if (!empty($tagFilters)) + { + $operator = ''; + $first = array_shift($tagFilters); + if ($first == 'NOT') + { + $operator = 'NOT'; + } + else + { + array_unshift($tagFilters, $first); + } + + $quotedTags = array(); + foreach ($tagFilters as $tag) + { + $quotedTags[] = $db->q($tag); + } + $filter = implode(', ', $quotedTags); + unset($quotedTags); + $query->where($operator . ' ' . $db->quoteName('tag') . ' IN (' . $filter . ')'); + } + + $db->setQuery($query); + $array = $db->loadColumn(); + + return $array; + } + + /** + * Invalidates older records sharing the same $archivename + * + * @param string $archivename + */ + public function remove_duplicate_backup_records($archivename) + { + Factory::getLog()->log(LogLevel::DEBUG, "Removing any old records with $archivename filename"); + $db = Factory::getDatabase($this->get_platform_database_options()); + + $query = $db->getQuery(true) + ->select($db->qn('id')) + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('archivename') . ' = ' . $db->q($archivename)) + ->order($db->qn('id') . ' DESC'); + + $db->setQuery($query); + $array = $db->loadColumn(); + + Factory::getLog()->log(LogLevel::DEBUG, count($array) . " records found"); + + // No records?! Quit. + if (empty($array)) + { + return; + } + // Only one record. Quit. + if (count($array) == 1) + { + return; + } + + // Shift the first (latest) element off the array + $currentID = array_shift($array); + + // Invalidate older records + $this->invalidate_backup_records($array); + } + + /** + * Marks the specified backup records as having no files + * + * @param array $ids Array of backup record IDs to ivalidate + */ + public function invalidate_backup_records($ids) + { + if (empty($ids)) + { + return false; + } + $db = Factory::getDatabase($this->get_platform_database_options()); + $temp = array(); + foreach ($ids as $id) + { + $temp[] = $db->q($id); + } + $list = implode(',', $temp); + $sql = $db->getQuery(true) + ->update($db->qn($this->tableNameStats)) + ->set($db->qn('filesexist') . ' = ' . $db->q('0')) + ->where($db->qn('id') . ' IN (' . $list . ')');; + $db->setQuery($sql); + + try + { + $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + return true; + } + + /** + * Gets a list of records with remotely stored files in the selected remote storage + * provider and profile. + * + * @param $profile int (optional) The profile to use. Skip or use null for active profile. + * @param $engine string (optional) The remote engine to looks for. Skip or use null for the active profile's + * engine. + * + * @return array + */ + public function get_valid_remote_records($profile = null, $engine = null) + { + $config = Factory::getConfiguration(); + $result = array(); + + if (is_null($profile)) + { + $profile = $this->get_active_profile(); + } + if (is_null($engine)) + { + $engine = $config->get('akeeba.advanced.postproc_engine', ''); + } + + if (empty($engine)) + { + return $result; + } + + $db = Factory::getDatabase($this->get_platform_database_options()); + $sql = $db->getQuery(true) + ->select('*') + ->from($db->qn($this->tableNameStats)) + ->where($db->qn('profile_id') . ' = ' . $db->q($profile)) + ->where($db->qn('remote_filename') . ' LIKE ' . $db->q($engine . '://%')) + ->order($db->qn('id') . ' ASC'); + + $db->setQuery($sql); + + return $db->loadAssocList(); + } + + /** + * Returns the filter data for the entire filter group collection + * + * @return array + */ + public function &load_filters() + { + // Load the filter data from the database + $profile_id = $this->get_active_profile(); + $db = Factory::getDatabase($this->get_platform_database_options()); + + // Load the INI format local configuration dump off the database + $sql = $db->getQuery(true) + ->select($db->qn('filters')) + ->from($db->qn($this->tableNameProfiles)) + ->where($db->qn('id') . ' = ' . $db->q($profile_id)); + $db->setQuery($sql); + $all_filter_data = $db->loadResult(); + + if (is_null($all_filter_data) || empty($all_filter_data)) + { + $all_filter_data = array(); + } + else + { + if (function_exists('get_magic_quotes_runtime')) + { + if (@get_magic_quotes_runtime()) + { + $all_filter_data = stripslashes($all_filter_data); + } + } + $all_filter_data = @unserialize($all_filter_data); + if (empty($all_filter_data)) + { + $all_filter_data = array(); + } // Catch unserialization errors + } + + return $all_filter_data; + } + + /** + * Saves the nested filter data array $filter_data to the database + * + * @param array $filter_data The filter data to save + * + * @return bool True on success + */ + public function save_filters(&$filter_data) + { + $profile_id = $this->get_active_profile(); + $db = Factory::getDatabase($this->get_platform_database_options()); + + $sql = $db->getQuery(true) + ->update($db->qn($this->tableNameProfiles)) + ->set($db->qn('filters') . '=' . $db->q(serialize($filter_data))) + ->where($db->qn('id') . ' = ' . $db->q($profile_id)); + $db->setQuery($sql); + + try + { + $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + return true; + } + + public function get_platform_database_options() + { + return array(); + } + + public function translate($key) + { + return ''; + } + + public function load_version_defines() + { + } + + public function getPlatformVersion() + { + return array( + 'name' => 'Platform', + 'version' => 'unknown' + ); + } + + public function log_platform_special_directories() + { + } + + public function get_platform_configuration_option($key, $default) + { + return ''; + } + + public function get_administrator_emails() + { + return array(); + } + + public function send_email($to, $subject, $body, $attachFile = null) + { + return false; + } + + public function unlink($file) + { + return @unlink($file); + } + + public function move($from, $to) + { + $result = @rename($from, $to); + if (!$result) + { + $result = @copy($from, $to); + if ($result) + { + $result = $this->unlink($from); + } + } + + return $result; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Exception/DecryptionException.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Exception/DecryptionException.php new file mode 100644 index 00000000..3bc7ebe8 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/Exception/DecryptionException.php @@ -0,0 +1,37 @@ +translate('COM_AKEEBA_CONFIG_ERR_DECRYPTION'); + } + + parent::__construct($message, $code, $previous); + } + +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/PlatformInterface.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/PlatformInterface.php new file mode 100644 index 00000000..d23a8285 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Platform/PlatformInterface.php @@ -0,0 +1,392 @@ +get('engine.postproc.email.address', '')); + $subject = $config->get('engine.postproc.email.subject', '0'); + + // Sanity checks + if (empty($address)) + { + $this->setError('You have not set up a recipient\'s email address for the backup files'); + + return false; + } + + // Send the file + $basename = empty($upload_as) ? basename($absolute_filename) : $upload_as; + Factory::getLog()->log(LogLevel::INFO, "Preparing to email $basename to $address"); + if (empty($subject)) + { + if (class_exists('JText')) + { + $subject = \JText::_('COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT'); + } + elseif (class_exists('\Awf\Text\Text')) + { + $subject = \Awf\Text\Text::_('COM_AKEEBA_COMMON_EMAIL_DEAFULT_SUBJECT'); + } + else + { + $subject = "You have a new backup part"; + } + } + $body = "Emailing $basename"; + + Factory::getLog()->log(LogLevel::DEBUG, "Subject: $subject"); + Factory::getLog()->log(LogLevel::DEBUG, "Body: $body"); + + $result = Platform::getInstance()->send_email($address, $subject, $body, $absolute_filename); + + // Return the result + if ($result !== true) + { + // An error occurred + $this->setError($result); + + // Notify that we failed + return false; + } + else + { + // Return success + Factory::getLog()->log(LogLevel::INFO, "Email sent successfully"); + + return true; + } + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/None.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/None.php new file mode 100644 index 00000000..b6459a91 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/None.php @@ -0,0 +1,32 @@ +break_after = false; + $this->break_before = false; + $this->allow_deletes = false; + } + + public function processPart($absolute_filename, $upload_as = null) + { + // Really nothing to do!! + return true; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/email.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/email.ini new file mode 100644 index 00000000..05dd4699 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/email.ini @@ -0,0 +1,36 @@ +; Akeeba Send by Email post processing engine +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: email.ini 738 2011-06-15 13:11:38Z nikosdion $ + +; Engine information +[_information] +title=COM_AKEEBA_CONFIG_ENGINE_POSTPROC_EMAIL_TITLE +description=COM_AKEEBA_CONFIG_ENGINE_POSTPROC_EMAIL_DESCRIPTION + +; Post-process after generating each part? +[engine.postproc.common.after_part] +default=0 +type=bool +title=COM_AKEEBA_CONFIG_POSTPROCPARTS_TITLE +description=COM_AKEEBA_CONFIG_POSTPROCPARTS_DESCRIPTION + +; Delete from server after processing? +[engine.postproc.common.delete_after] +default=1 +type=bool +title=COM_AKEEBA_CONFIG_DELETEAFTER_TITLE +description=COM_AKEEBA_CONFIG_DELETEAFTER_DESCRIPTION + +; Email address +[engine.postproc.email.address] +default="" +type=string +title=COM_AKEEBA_CONFIG_PROCEMAIL_ADDRESS_TITLE +description=COM_AKEEBA_CONFIG_PROCEMAIL_ADDRESS_DESCRIPTION + +; Subject +[engine.postproc.email.subject] +default="" +type=string +title=COM_AKEEBA_CONFIG_PROCEMAIL_SUBJECT_TITLE +description=COM_AKEEBA_CONFIG_PROCEMAIL_SUBJECT_DESCRIPTION diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/none.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/none.ini new file mode 100644 index 00000000..8a6dcef8 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Postproc/none.ini @@ -0,0 +1,8 @@ +; Akeeba no post=processing engine +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: none.ini 409 2011-01-24 09:30:22Z nikosdion $ + +; Engine information +[_information] +title = COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_TITLE +description = COM_AKEEBA_CONFIG_ENGINE_POSTPROC_NONE_DESCRIPTION diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/AbstractLogger.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/AbstractLogger.php new file mode 100644 index 00000000..00f90345 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/AbstractLogger.php @@ -0,0 +1,120 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/InvalidArgumentException.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/InvalidArgumentException.php new file mode 100644 index 00000000..67f852d1 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/LoggerInterface.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/LoggerInterface.php new file mode 100644 index 00000000..476bb962 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/LoggerInterface.php @@ -0,0 +1,114 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/NullLogger.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/NullLogger.php new file mode 100644 index 00000000..553a3c59 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Psr/Log/NullLogger.php @@ -0,0 +1,27 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/Base.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/Base.php new file mode 100644 index 00000000..8a1fb309 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/Base.php @@ -0,0 +1,39 @@ +get('volatile.breakflag', false); + + // Reset break flag before continuing + $breakflag = false; + + // Initialize variables + $arr = array(); + $false = false; + + if (!@is_dir($folder) && !@is_dir($folder . '/')) + { + return $false; + } + + $counter = 0; + $registry = Factory::getConfiguration(); + $maxCounter = $registry->get('engine.scan.smart.large_dir_threshold', 100); + + $allowBreakflag = ($registry->get('volatile.operation_counter', 0) != 0) && !$breakflag_before_process; + + if (!@is_dir($folder)) + { + $this->setWarning('Unreadable directory ' . $folder); + + return $false; + } + + if (!@is_readable($folder)) + { + $this->setWarning('Unreadable directory ' . $folder); + + return $false; + } + + $di = new \DirectoryIterator($folder); + $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR; + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if ($breakflag) + { + break; + } + + /** + * If the directory entry is a link pointing somewhere outside the allowed directories per open_basedir we + * will get a RuntimeException (tested on PHP 5.3 onwards). Catching it lets us report the link as + * unreadable without suffering a PHP Fatal Error. + */ + try { + $file->isLink(); + } + catch (\RuntimeException $e) + { + $this->setWarning(sprintf("Link %s is inaccessible. Check the open_basedir restrictions in your server's PHP configuration", $file->getPathname())); + + continue; + } + + if ($file->isDot()) + { + continue; + } + + if ($file->isDir()) + { + continue; + } + + $dir = $folder . $ds . $file->getFilename(); + $data = $dir; + + if (_AKEEBA_IS_WINDOWS) + { + $data = Factory::getFilesystemTools()->TranslateWinPath($dir); + } + + if ($data) + { + $arr[] = $data; + } + + $counter++; + + if ($counter >= $maxCounter) + { + $breakflag = $allowBreakflag; + } + } + + // Save break flag status + $registry->set('volatile.breakflag', $breakflag); + + return $arr; + } + + public function &getFolders($folder, &$position) + { + // Was the breakflag set BEFORE starting? -- This workaround is required due to PHP5 defaulting to assigning variables by reference + $registry = Factory::getConfiguration(); + $breakflag_before_process = $registry->get('volatile.breakflag', false); + + // Reset break flag before continuing + $breakflag = false; + + // Initialize variables + $arr = array(); + $false = false; + + if (!is_dir($folder) && !is_dir($folder . '/')) + { + return $false; + } + + $counter = 0; + $registry = Factory::getConfiguration(); + $maxCounter = $registry->get('engine.scan.smart.large_dir_threshold', 100); + + $allowBreakflag = ($registry->get('volatile.operation_counter', 0) != 0) && !$breakflag_before_process; + + if (!@is_readable($folder)) + { + $this->setWarning('Unreadable directory ' . $folder); + + return $false; + } + + $di = new \DirectoryIterator($folder); + $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR; + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if ($breakflag) + { + break; + } + + /** + * If the directory entry is a link pointing somewhere outside the allowed directories per open_basedir we + * will get a RuntimeException (tested on PHP 5.3 onwards). Catching it lets us report the link as + * unreadable without suffering a PHP Fatal Error. + */ + try { + $file->isLink(); + } + catch (\RuntimeException $e) + { + $this->setWarning(sprintf("Link %s is inaccessible. Check the open_basedir restrictions in your server's PHP configuration", $file->getPathname())); + + continue; + } + + if ($file->isDot()) + { + continue; + } + + if (!$file->isDir()) + { + continue; + } + + $dir = $folder . $ds . $file->getFilename(); + $data = $dir; + + if (_AKEEBA_IS_WINDOWS) + { + $data = Factory::getFilesystemTools()->TranslateWinPath($dir); + } + + if ($data) + { + $arr[] = $data; + } + + $counter++; + + if ($counter >= $maxCounter) + { + $breakflag = $allowBreakflag; + } + } + + // Save break flag status + $registry->set('volatile.breakflag', $breakflag); + + return $arr; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/smart.ini b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/smart.ini new file mode 100644 index 00000000..d1da3660 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Scan/smart.ini @@ -0,0 +1,30 @@ +; Akeeba 'Smart' Scan Engine +; Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd +; Version $Id: smart.ini 604 2011-05-17 08:29:51Z nikosdion $ + +; Engine information +[_information] +title = COM_AKEEBA_CONFIG_ENGINE_SCAN_SMART_TITLE +description = COM_AKEEBA_CONFIG_ENGINE_SCAN_SMART_DESCRIPTION + +[engine.scan.smart.large_dir_threshold] +default = 100 +type = integer +min = 0 +max = 500 +shortcuts = "20|50|100|200|300|400|500" +scale = 1 +uom = +title = COM_AKEEBA_CONFIG_LARGEDIRTHRESHOLD_TITLE +description = COM_AKEEBA_CONFIG_LARGEDIRTHRESHOLD_DESCRIPTION + +[engine.scan.common.largefile] +default = 10485760 +type = integer +min = 1048576 +max = 1048576000 +shortcuts = "1048576|2097152|5242880|10485760|15728640|20971520|26214400|31457280|41943040|52428800|78643200|104857600" +scale = 1048576 +uom = MB +title = COM_AKEEBA_CONFIG_LARGEFILE_TITLE +description = COM_AKEEBA_CONFIG_LARGEFILE_DESCRIPTION diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AbstractAdapter.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AbstractAdapter.php new file mode 100644 index 00000000..b80f96f5 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AbstractAdapter.php @@ -0,0 +1,91 @@ + $size) + { + if (function_exists('mb_substr')) + { + return mb_substr($key, 0, $size, 'ASCII'); + } + + return substr($key, 0, $size); + } + + return $key . str_repeat("\0", ($size - $keyLength)); + } + + /** + * Returns null bytes to append to the string so that it's zero padded to the specified block size + * + * @param string $string The binary string which will be zero padded + * @param int $blockSize The block size + * + * @return string The zero bytes to append to the string to zero pad it to $blockSize + */ + protected function getZeroPadding($string, $blockSize) + { + $stringSize = strlen($string); + + if (function_exists('mb_strlen')) + { + $stringSize = mb_strlen($string, 'ASCII'); + } + + if ($stringSize == $blockSize) + { + return ''; + } + + if ($stringSize < $blockSize) + { + return str_repeat("\0", $blockSize - $stringSize); + } + + $paddingBytes = $stringSize % $blockSize; + + return str_repeat("\0", $blockSize - $paddingBytes); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AdapterInterface.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AdapterInterface.php new file mode 100644 index 00000000..65b05dd1 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/AdapterInterface.php @@ -0,0 +1,84 @@ +cipherType = MCRYPT_RIJNDAEL_128; + break; + + case '192': + $this->cipherType = MCRYPT_RIJNDAEL_192; + break; + + case '256': + $this->cipherType = MCRYPT_RIJNDAEL_256; + break; + } + + switch (strtolower($mode)) + { + case 'ecb': + $this->cipherMode = MCRYPT_MODE_ECB; + break; + + default: + case 'cbc': + $this->cipherMode = MCRYPT_MODE_CBC; + break; + } + + } + + public function encrypt($plainText, $key, $iv = null) + { + $iv_size = $this->getBlockSize(); + $key = $this->resizeKey($key, $iv_size); + $iv = $this->resizeKey($iv, $iv_size); + + if (empty($iv)) + { + $randVal = new RandomValue(); + $iv = $randVal->generate($iv_size); + } + + $cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv); + $cipherText = $iv . $cipherText; + + return $cipherText; + } + + public function decrypt($cipherText, $key) + { + $iv_size = $this->getBlockSize(); + $key = $this->resizeKey($key, $iv_size); + $iv = substr($cipherText, 0, $iv_size); + $cipherText = substr($cipherText, $iv_size); + $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv); + + return $plainText; + } + + public function isSupported() + { + if (!function_exists('mcrypt_get_key_size')) + { + return false; + } + + if (!function_exists('mcrypt_get_iv_size')) + { + return false; + } + + if (!function_exists('mcrypt_create_iv')) + { + return false; + } + + if (!function_exists('mcrypt_encrypt')) + { + return false; + } + + if (!function_exists('mcrypt_decrypt')) + { + return false; + } + + if (!function_exists('mcrypt_list_algorithms')) + { + return false; + } + + if (!function_exists('hash')) + { + return false; + } + + if (!function_exists('hash_algos')) + { + return false; + } + + $algorightms = mcrypt_list_algorithms(); + + if (!in_array('rijndael-128', $algorightms)) + { + return false; + } + + if (!in_array('rijndael-192', $algorightms)) + { + return false; + } + + if (!in_array('rijndael-256', $algorightms)) + { + return false; + } + + $algorightms = hash_algos(); + + if (!in_array('sha256', $algorightms)) + { + return false; + } + + return true; + } + + public function getBlockSize() + { + return mcrypt_get_iv_size($this->cipherType, $this->cipherMode); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/OpenSSL.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/OpenSSL.php new file mode 100644 index 00000000..68507300 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/AesAdapter/OpenSSL.php @@ -0,0 +1,179 @@ +openSSLOptions = 1; + + // PHP 5.4 - Do it THE RIGHT WAY(tm) + if (version_compare(PHP_VERSION, '5.4.0', 'ge')) + { + $this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING; + } + } + + public function setEncryptionMode($mode = 'cbc', $strength = 128) + { + static $availableAlgorithms = null; + static $defaultAlgo = 'aes-128-cbc'; + + if (!is_array($availableAlgorithms)) + { + $availableAlgorithms = openssl_get_cipher_methods(); + + foreach (array('aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc', + 'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb') as $algo) + { + if (in_array($algo, $availableAlgorithms)) + { + $defaultAlgo = $algo; + break; + } + } + } + + $strength = (int) $strength; + $mode = strtolower($mode); + + if (!in_array($strength, array(128, 192, 256))) + { + $strength = 256; + } + + if (!in_array($mode, array('cbc', 'ebc'))) + { + $mode = 'cbc'; + } + + $algo = 'aes-' . $strength . '-' . $mode; + + if (!in_array($algo, $availableAlgorithms)) + { + $algo = $defaultAlgo; + } + + $this->method = $algo; + } + + public function encrypt($plainText, $key, $iv = null) + { + $iv_size = $this->getBlockSize(); + $key = $this->resizeKey($key, $iv_size); + $iv = $this->resizeKey($iv, $iv_size); + + if (empty($iv)) + { + $randVal = new RandomValue(); + $iv = $randVal->generate($iv_size); + } + + $plainText .= $this->getZeroPadding($plainText, $iv_size); + $cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv); + $cipherText = $iv . $cipherText; + + return $cipherText; + } + + public function decrypt($cipherText, $key) + { + $iv_size = $this->getBlockSize(); + $key = $this->resizeKey($key, $iv_size); + $iv = substr($cipherText, 0, $iv_size); + $cipherText = substr($cipherText, $iv_size); + $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv); + + return $plainText; + } + + public function isSupported() + { + if (!function_exists('openssl_get_cipher_methods')) + { + return false; + } + + if (!function_exists('openssl_random_pseudo_bytes')) + { + return false; + } + + if (!function_exists('openssl_cipher_iv_length')) + { + return false; + } + + if (!function_exists('openssl_encrypt')) + { + return false; + } + + if (!function_exists('openssl_decrypt')) + { + return false; + } + + if (!function_exists('hash')) + { + return false; + } + + if (!function_exists('hash_algos')) + { + return false; + } + + $algorightms = openssl_get_cipher_methods(); + + if (!in_array('aes-128-cbc', $algorightms)) + { + return false; + } + + $algorightms = hash_algos(); + + if (!in_array('sha256', $algorightms)) + { + return false; + } + + return true; + } + + /** + * @return int + */ + public function getBlockSize() + { + return openssl_cipher_iv_length($this->method); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Buffer.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Buffer.php new file mode 100644 index 00000000..19d12a0e --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Buffer.php @@ -0,0 +1,193 @@ +name = $url["host"]; + $this->_buffers[$this->name] = null; + $this->position = 0; + + return true; + } + + /** + * Read stream + * + * @param integer $count How many bytes of data from the current position should be returned. + * + * @return mixed The data from the stream up to the specified number of bytes (all data if + * the total number of bytes in the stream is less than $count. Null if + * the stream is empty. + * + * @see streamWrapper::stream_read + */ + public function stream_read($count) + { + $ret = substr($this->_buffers[$this->name], $this->position, $count); + $this->position += strlen($ret); + + return $ret; + } + + /** + * Write stream + * + * @param string $data The data to write to the stream. + * + * @return integer + * + * @see streamWrapper::stream_write + */ + public function stream_write($data) + { + $left = substr($this->_buffers[$this->name], 0, $this->position); + $right = substr($this->_buffers[$this->name], $this->position + strlen($data)); + $this->_buffers[$this->name] = $left . $data . $right; + $this->position += strlen($data); + + return strlen($data); + } + + /** + * Function to get the current position of the stream + * + * @return integer + * + * @see streamWrapper::stream_tell + */ + public function stream_tell() + { + return $this->position; + } + + /** + * Function to test for end of file pointer + * + * @return boolean True if the pointer is at the end of the stream + * + * @see streamWrapper::stream_eof + */ + public function stream_eof() + { + return $this->position >= strlen($this->_buffers[$this->name]); + } + + /** + * The read write position updates in response to $offset and $whence + * + * @param integer $offset The offset in bytes + * @param integer $whence Position the offset is added to + * Options are SEEK_SET, SEEK_CUR, and SEEK_END + * + * @return boolean True if updated + * + * @see streamWrapper::stream_seek + */ + public function stream_seek($offset, $whence) + { + switch ($whence) + { + case SEEK_SET: + if ($offset < strlen($this->_buffers[$this->name]) && $offset >= 0) + { + $this->position = $offset; + + return true; + } + else + { + return false; + } + break; + + case SEEK_CUR: + if ($offset >= 0) + { + $this->position += $offset; + + return true; + } + else + { + return false; + } + break; + + case SEEK_END: + if (strlen($this->_buffers[$this->name]) + $offset >= 0) + { + $this->position = strlen($this->_buffers[$this->name]) + $offset; + + return true; + } + else + { + return false; + } + break; + + default: + return false; + } + } +} + +// Register the stream +stream_wrapper_register("buffer", "\\Akeeba\\Engine\\Util\\Buffer"); diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/CRC32.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/CRC32.php new file mode 100644 index 00000000..a9f32621 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/CRC32.php @@ -0,0 +1,117 @@ +crc32_file_php512($filename); + Factory::getLog()->log(LogLevel::DEBUG, "File $filename - CRC32 = " . dechex($res) . " [HASH_FILE]"); + } + else if (function_exists("file_get_contents") && (@filesize($filename) <= $AkeebaPackerZIP_CHUNK_SIZE)) + { + $res = $this->crc32_file_getcontents($filename); + Factory::getLog()->log(LogLevel::DEBUG, "File $filename - CRC32 = " . dechex($res) . " [FILE_GET_CONTENTS]"); + } + else + { + $res = 0; + Factory::getLog()->log(LogLevel::DEBUG, "File $filename - CRC32 = " . dechex($res) . " [FAKE - CANNOT CALCULATE]"); + } + + if ($res === false) + { + $res = 0; + + $this->setWarning("File $filename - NOT READABLE: CRC32 IS WRONG!"); + } + + return $res; + } + + /** + * Very efficient CRC32 calculation for PHP 5.1.2 and greater, requiring + * the 'hash' PECL extension + * + * @param string $filename Absolute filepath + * + * @return integer The CRC32 + */ + protected function crc32_file_php512($filename) + { + // Detection of buggy PHP hosts + static $mustInvert = null; + + if (is_null($mustInvert)) + { + $test_crc = @hash('crc32b', 'test', false); + $mustInvert = (strtolower($test_crc) == '0c7e7fd8'); // Normally, it's D87F7E0C :) + + if ($mustInvert) + { + Factory::getLog()->log(LogLevel::WARNING, 'Your server has a buggy PHP version which produces inverted CRC32 values. Attempting a workaround. ZIP files may appear as corrupt.'); + } + } + + $res = @hash_file('crc32b', $filename, false); + + if ($mustInvert) + { + // Workaround for buggy PHP versions (I think before 5.1.8) which produce inverted CRC32 sums + $res2 = substr($res, 6, 2) . substr($res, 4, 2) . substr($res, 2, 2) . substr($res, 0, 2); + $res = $res2; + } + $res = hexdec($res); + + return $res; + } + + /** + * A compatible CRC32 calculation using file_get_contents, utilizing immense amounts of RAM + * + * @param string $filename + * + * @return integer + */ + protected function crc32_file_getcontents($filename) + { + return crc32(@file_get_contents($filename)); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Complexify.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Complexify.php new file mode 100644 index 00000000..983ab99f --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Complexify.php @@ -0,0 +1,390 @@ +bannedPasswords = self::$BANLIST; + + foreach ($options as $opt => $val) + { + if ($opt === 'banmode') + { + trigger_error('The lowercase banmode option is deprecated. Use banMode instead.', E_USER_DEPRECATED); + $opt = 'banMode'; + } + + $this->{$opt} = $val; + } + } + + /** + * Determine the complexity added from a character set if it is used in a string + * + * @param string $str String to check + * @param int [2] $charset Array of unicode code points representing the lower and upper bound of the + * character range + * + * @return int 0 if there are no characters from the character set, size of the character set if there are any + * characters used in the string + */ + private function additionalComplexityForCharset($str, $charset) + { + $len = mb_strlen($str, $this->encoding); + for ($i = 0; $i < $len; $i++) + { + $c = + unpack('Nord', mb_convert_encoding(mb_substr($str, $i, 1, $this->encoding), 'UCS-4BE', $this->encoding)); + if ($charset[0] <= $c['ord'] && $c['ord'] <= $charset[1]) + { + return $charset[1] - $charset[0] + 1; + } + } + + return 0; + } + + /** + * Check if a string is in the banned password list + * + * @param string $str String to check + * + * @return bool TRUE if $str is a banned password, or if it is a substring of a banned password and + * $this->banMode is 'strict' + */ + private function inBanlist($str) + { + if ($str == '') + { + return false; + } + + $str = mb_convert_case($str, MB_CASE_LOWER, $this->encoding); + + if ($this->banMode === 'strict') + { + for ($i = 0; $i < count($this->bannedPasswords); $i++) + { + if (mb_strpos($this->bannedPasswords[ $i ], $str, 0, $this->encoding) !== false) + { + return true; + } + } + + return false; + } + + return in_array($str, $this->bannedPasswords); + } + + /** + * Check the complexity of a password + * + * @param string $password The password to check + * + * @return object StdClass object with properties "valid", "complexity", and "error" + * - valid: TRUE if the password is complex enough, FALSE if it is not + * - complexity: The complexity of the password as a percent + * - errors: Array containing descriptions of what made the password fail. Possible values are: banned, toosimple, + * tooshort + */ + public function evaluateSecurity($password) + { + $complexity = 0; + $error = array(); + + // Reset complexity to 0 when banned password is found + if (!$this->inBanlist($password)) + { + // Add character complexity + foreach (self::$CHARSETS as $charset) + { + $complexity += $this->additionalComplexityForCharset($password, $charset); + } + } + else + { + array_push($error, 'banned'); + $complexity = 1; + } + + // Use natural log to produce linear scale + $complexity = log(pow($complexity, mb_strlen($password, $this->encoding))) * (1 / $this->strengthScaleFactor); + + if ($complexity <= self::$MIN_COMPLEXITY) + { + array_push($error, 'toosimple'); + } + + if (mb_strlen($password, $this->encoding) < $this->minimumChars) + { + array_push($error, 'tooshort'); + } + + // Scale to percentage, so it can be used for a progress bar + $complexity = ($complexity / self::$MAX_COMPLEXITY) * 100; + $complexity = ($complexity > 100) ? 100 : $complexity; + + return (object)array('valid' => count($error) === 0, 'complexity' => $complexity, 'errors' => $error); + } + + /** + * Checks if a password is strong enough for use on a live site. Used to check the front-end Secret Word. + * + * @param string $password The password to check + * @param bool $throwExceptions Throw an exception if the password is not strong enough? + * + * @return bool + */ + public static function isStrongEnough($password, $throwExceptions = true) + { + $complexify = new self(); + + $res = (object) array( + 'valid' => strlen($password) >= 32, + 'complexity' => 50, + 'errors' => (strlen($password) >= 32) ? array() : array('tooshort') + ); + + if (function_exists('mb_strlen') && function_exists('mb_convert_encoding') && + function_exists('mb_substr') && function_exists('mb_convert_case')) + { + $res = $complexify->evaluateSecurity($password); + } + + + if ($res->valid) + { + return true; + } + + if (!$throwExceptions) + { + return false; + } + + $error = count($res->errors) ? array_shift($res->errors) : 'toosimple'; + + $errorMessage = Platform::getInstance()->translate('COM_AKEEBA_CPANEL_ERR_FESECRETWORD_' . $error); + + throw new \RuntimeException($errorMessage, 403); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ConfigurationCheck.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ConfigurationCheck.php new file mode 100644 index 00000000..3ee6f8be --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ConfigurationCheck.php @@ -0,0 +1,606 @@ + '001', 'severity' => 'critical', 'callback' => array(null, 'q001'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q001'), + array('code' => '003', 'severity' => 'critical', 'callback' => array(null, 'q003'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q003'), + array('code' => '004', 'severity' => 'critical', 'callback' => array(null, 'q004'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q004'), + + array('code' => '101', 'severity' => 'high', 'callback' => array(null, 'q101'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q101'), + array('code' => '103', 'severity' => 'high', 'callback' => array(null, 'q103'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q103'), + array('code' => '104', 'severity' => 'high', 'callback' => array(null, 'q104'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q104'), + array('code' => '106', 'severity' => 'high', 'callback' => array(null, 'q106'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q106'), + + array('code' => '201', 'severity' => 'medium', 'callback' => array(null, 'q201'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q201'), + array('code' => '202', 'severity' => 'medium', 'callback' => array(null, 'q202'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q202'), + array('code' => '204', 'severity' => 'medium', 'callback' => array(null, 'q204'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q204'), + + array('code' => '203', 'severity' => 'low', 'callback' => array(null, 'q203'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q203'), + array('code' => '401', 'severity' => 'low', 'callback' => array(null, 'q401'), 'description' => 'COM_AKEEBA_CPANEL_WARNING_Q401'), + ); + + /** + * The public constructor replaces the missing object reference in the configuration check callbacks + */ + function __construct() + { + $temp = array(); + + foreach ($this->configurationChecks as $check) + { + $check['callback'] = array($this, $check['callback'][1]); + $temp[] = $check; + } + + $this->configurationChecks = $temp; + } + + /** + * Returns the output & temporary folder writable status + * + * @return array A hash array with the writable status + */ + public function getFolderStatus() + { + static $status = null; + + if (is_null($status)) + { + $stock_dirs = Platform::getInstance()->get_stock_directories(); + + // Get output writable status + $registry = Factory::getConfiguration(); + $outdir = $registry->get('akeeba.basic.output_directory'); + + foreach ($stock_dirs as $macro => $replacement) + { + $outdir = str_replace($macro, $replacement, $outdir); + } + + $status['output'] = @is_writable($outdir); + } + + return $status; + } + + /** + * Returns the overall status. It's true when both the temporary and output directories are writable and there are + * no critical configuration check failures. + * + * @return boolean + */ + public function getShortStatus() + { + // Base the status on directory writeable status + $status = $this->getFolderStatus(); + $ret = $status['output']; + + // Scan for high severity configuration check errors + $detailedStatus = $this->getDetailedStatus(); + + if (!empty($detailedStatus)) + { + foreach ($detailedStatus as $configCheck) + { + if ($configCheck['severity'] == 'critical') + { + $ret = false; + } + } + } + + // Return status + return $ret; + } + + /** + * Add a configuration check definition + * + * @param string $code The configuration check code (three digit number) + * @param string $severity The severity (low, medium, high, critical) + * @param string $description The description key for this configuration check + * @param null $callback The callback used to determine the status of the configuration check + * + * @return void + */ + public function addConfigurationCheckDefinition($code, $severity = 'low', $description = null, $callback = null) + { + if (!is_callable($callback)) + { + $callback = array($this, 'q' . $code); + } + + if (empty($description)) + { + $description = 'COM_AKEEBA_CPANEL_WARNING_Q' . $code; + } + + $newConfigurationCheck = array( + 'code' => $code, + 'severity' => $severity, + 'description' => $description, + 'callback' => $callback, + ); + + $this->configurationChecks[$code] = $newConfigurationCheck; + } + + /** + * Remove a configuration check definition + * + * @param string $code The code of the configuration check to remove + * + * @return void + */ + public function removeConfigurationCheckDefinition($code) + { + if (isset($this->configurationChecks[$code])) + { + unset($this->configurationChecks[$code]); + } + } + + /** + * Clear the configuration check definitions + * + * @return void + */ + public function clearConfigurationCheckDefinitions() + { + $this->configurationChecks = array(); + } + + /** + * Runs the configuration check scripts. These are potential problems related to server + * configuration, out of Akeeba's control. They are intended to give the user a + * chance to fix them before they cause the backup to fail. + * + * Numbering scheme: + * Q0xx No-go errors + * Q1xx Critical system configuration errors + * Q2xx Medium and low system configuration warnings + * Q3xx Critical software configuration errors + * Q4xx Medium and low component configuration warnings + * + * @param boolean $low_priority Should I include low priority quirks? + * @param string $help_url_template The sprintf template from creating a help URL from a config check code + * + * @return array + */ + public function getDetailedStatus($low_priority = false, $help_url_template = 'https://www.akeebabackup.com/documentation/warnings/q%s.html') + { + static $detailedStatus = null; + + if (is_null($detailedStatus)) + { + $detailedStatus = array(); + + foreach ($this->configurationChecks as $quirkDef) + { + if (!$low_priority && ($quirkDef['severity'] == 'low')) + { + continue; + } + + $this->checkConfiguration($detailedStatus, $quirkDef, $help_url_template); + } + } + + return $detailedStatus; + } + + /** + * Make a configuration check and adds it to the list if it raises a warning / error + * + * @param array $detailedStatus The configuration checks status array + * @param array $quirkDef The configuration check definition + * @param string $help_url_template The sprintf template from creating a help URL from a quirk code + * + * @return void + */ + protected function checkConfiguration(&$detailedStatus, $quirkDef, $help_url_template) + { + if (call_user_func($quirkDef['callback'])) + { + $description = Platform::getInstance()->translate($quirkDef['description']); + + $detailedStatus[(string)$quirkDef['code']] = array( + 'code' => $quirkDef['code'], + 'severity' => $quirkDef['severity'], + 'description' => $description, + 'help_url' => sprintf($help_url_template, $quirkDef['code']), + ); + } + } + + /** + * Q001 - HIGH - Output directory unwriteable + * + * @return bool + */ + private function q001() + { + $status = $this->getFolderStatus(); + + return !$status['output']; + } + + /** + * Q003 - HIGH - Backup output or temporary set to site's root + * + * @return bool + */ + private function q003() + { + $stock_dirs = Platform::getInstance()->get_stock_directories(); + + $registry = Factory::getConfiguration(); + $outdir = $registry->get('akeeba.basic.output_directory'); + + foreach ($stock_dirs as $macro => $replacement) + { + $outdir = str_replace($macro, $replacement, $outdir); + } + + $outdir_real = @realpath($outdir); + + if (!empty($outdir_real)) + { + $outdir = $outdir_real; + } + + $siteroot = Platform::getInstance()->get_site_root(); + $siteroot_real = @realpath($siteroot); + + if (!empty($siteroot_real)) + { + $siteroot = $siteroot_real; + } + + return ($siteroot == $outdir); + } + + /** + * Q004 - HIGH - Free memory too low + * + * @return bool + */ + private function q004() + { + // If we can't figure this out, don't report a problem. It doesn't + // really matter, as the backup WILL crash eventually. + if (!function_exists('ini_get')) + { + return false; + } + + $memLimit = ini_get("memory_limit"); + $memLimit = $this->_return_bytes($memLimit); + + if ($memLimit <= 0) + { + return false; + } + + // No limit? + $availableRAM = $memLimit - memory_get_usage(); + + // We need at least 12Mb of free memory + return ($availableRAM <= (12 * 1024 * 1024)); + } + + /** + * Q101 - HIGH - open_basedir on output directory + * + * @return bool + */ + private function q101() + { + $stock_dirs = Platform::getInstance()->get_stock_directories(); + + // Get output writable status + $registry = Factory::getConfiguration(); + $outdir = $registry->get('akeeba.basic.output_directory'); + + foreach ($stock_dirs as $macro => $replacement) + { + $outdir = str_replace($macro, $replacement, $outdir); + } + + return $this->checkOpenBasedirs($outdir); + } + + /** + * Q103 - HIGH - Less than 10" of max_execution_time with PHP Safe Mode enabled + * + * @return bool + */ + private function q103() + { + $exectime = ini_get('max_execution_time'); + $safemode = ini_get('safe_mode'); + + if (!$safemode) + { + return false; + } + + if (!is_numeric($exectime)) + { + return false; + } + + if ($exectime <= 0) + { + return false; + } + + return $exectime < 10; + } + + /** + * Q104 - HIGH - Temp directory is the same as the site's root + * + * @return bool + */ + private function q104() + { + + $siteroot = Platform::getInstance()->get_site_root(); + $siteroot_real = @realpath($siteroot); + + if (!empty($siteroot_real)) + { + $siteroot = $siteroot_real; + } + + $stockDirs = Platform::getInstance()->get_stock_directories(); + $temp_directory = $stockDirs['[SITETMP]']; + $temp_directory = @realpath($temp_directory); + + if (empty($temp_directory)) + { + $temp_directory = $siteroot; + } + + return ($siteroot == $temp_directory); + } + + /** + * Q106 - HIGH - Table name prefix contains uppercase characters + * + * @return bool + */ + private function q106() + { + $filters = Factory::getFilters(); + $databases = $filters->getInclusions('db'); + + foreach ($databases as $db) + { + if (!isset($db['prefix'])) + { + continue; + } + + if (preg_match('/[A-Z]/', $db['prefix'])) + { + return true; + } + } + + return false; + } + + /** + * Q201 - MEDIUM - Outdated PHP version. + * + * We currently check for PHP lower than 5.5. + * + * @return bool + */ + private function q201() + { + return version_compare(PHP_VERSION, '5.5.0', 'lt'); + } + + /** + * Q202 - MED - CRC problems with hash extension not present + * + * @return bool + */ + private function q202() + { + $registry = Factory::getConfiguration(); + $archiver = $registry->get('akeeba.advanced.archiver_engine'); + + if ($archiver != 'zip') + { + return false; + } + + return !function_exists('hash_file'); + } + + /** + * Q203 - MED - Default output directory in use + * + * @return bool + */ + private function q203() + { + $stock_dirs = Platform::getInstance()->get_stock_directories(); + + $registry = Factory::getConfiguration(); + $outdir = $registry->get('akeeba.basic.output_directory'); + + foreach ($stock_dirs as $macro => $replacement) + { + $outdir = str_replace($macro, $replacement, $outdir); + } + + $default = $stock_dirs['[DEFAULT_OUTPUT]']; + + $outdir = Factory::getFilesystemTools()->TranslateWinPath($outdir); + $default = Factory::getFilesystemTools()->TranslateWinPath($default); + + return $outdir == $default; + } + + /** + * Q204 - MED - Disabled functions may affect operation + * + * @return bool + */ + private function q204() + { + $disabled = ini_get('disabled_functions'); + + return (!empty($disabled)); + } + + /** + * Q401 - LOW - ZIP format selected + * + * @return bool + */ + private function q401() + { + $registry = Factory::getConfiguration(); + $archiver = $registry->get('akeeba.advanced.archiver_engine'); + + return $archiver == 'zip'; + } + + /** + * Checks if a path is restricted by open_basedirs + * + * @param string $check The path to check + * + * @return bool True if the path is restricted (which is bad) + */ + public function checkOpenBasedirs($check) + { + static $paths; + + if (empty($paths)) + { + $open_basedir = ini_get('open_basedir'); + + if (empty($open_basedir)) + { + return false; + } + + $delimiter = strpos($open_basedir, ';') !== false ? ';' : ':'; + $paths_temp = explode($delimiter, $open_basedir); + + // Some open_basedirs are using environemtn variables + $paths = array(); + + foreach ($paths_temp as $path) + { + if (array_key_exists($path, $_ENV)) + { + $paths[] = $_ENV[$path]; + } + else + { + $paths[] = $path; + } + } + } + + if (empty($paths)) + { + return false; // no restrictions + } + else + { + $newcheck = @realpath($check); // Resolve symlinks, like PHP does + + if (!($newcheck === false)) + { + $check = $newcheck; + } + + $included = false; + + foreach ($paths as $path) + { + $newpath = @realpath($path); + + if (!($newpath === false)) + { + $path = $newpath; + } + + if (strlen($check) >= strlen($path)) + { + // Only check if the path to check is longer than the inclusion path. + // Otherwise, I guarantee it's not included!! + // If the path to check begins with an inclusion path, it's permitted. Easy, huh? + if (substr($check, 0, strlen($path)) == $path) + { + $included = true; + } + } + } + + return !$included; + } + } + + private function _return_bytes($setting) + { + $val = trim($setting); + $last = strtolower(substr($val, -1)); + $val = substr($val, 0, -1); + + if (is_numeric($last)) + { + return $setting; + } + + switch ($last) + { + case 't': + $val *= 1024; + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return (int) $val; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Encrypt.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Encrypt.php new file mode 100644 index 00000000..20e7311e --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/Encrypt.php @@ -0,0 +1,996 @@ +AddRoundKey($state, $w, 0, $Nb); + + for ($round = 1; $round < $Nr; $round++) + { + // apply Nr rounds + $state = $this->SubBytes($state, $Nb); + $state = $this->ShiftRows($state, $Nb); + $state = $this->MixColumns($state, $Nb); + $state = $this->AddRoundKey($state, $w, $round, $Nb); + } + + $state = $this->SubBytes($state, $Nb); + $state = $this->ShiftRows($state, $Nb); + $state = $this->AddRoundKey($state, $w, $Nr, $Nb); + + $output = array(4 * $Nb); // convert state to 1-d array before returning [�3.4] + + for ($i = 0; $i < 4 * $Nb; $i++) + { + $output[$i] = $state[$i % 4][(int)floor($i / 4)]; + } + + return $output; + } + + protected function AddRoundKey($state, $w, $rnd, $Nb) + { + // xor Round Key into state S [�5.1.4] + for ($r = 0; $r < 4; $r++) + { + for ($c = 0; $c < $Nb; $c++) + { + $state[$r][$c] ^= $w[$rnd * 4 + $c][$r]; + } + } + + return $state; + } + + protected function SubBytes($s, $Nb) + { + // apply SBox to state S [�5.1.1] + for ($r = 0; $r < 4; $r++) + { + for ($c = 0; $c < $Nb; $c++) + { + $s[$r][$c] = $this->Sbox[$s[$r][$c]]; + } + } + + return $s; + } + + protected function ShiftRows($s, $Nb) + { + // shift row r of state S left by r bytes [�5.1.2] + $t = array(4); + + for ($r = 1; $r < 4; $r++) + { + // shift into temp copy + for ($c = 0; $c < 4; $c++) + { + $t[$c] = $s[$r][($c + $r) % $Nb]; + } + + // and copy back + for ($c = 0; $c < 4; $c++) + { + $s[$r][$c] = $t[$c]; + } + + } + // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES): + + return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf + } + + protected function MixColumns($s, $Nb) + { + // combine bytes of each col of state S [�5.1.3] + for ($c = 0; $c < 4; $c++) + { + $a = array(4); // 'a' is a copy of the current column from 's' + $b = array(4); // 'b' is a�{02} in GF(2^8) + + for ($i = 0; $i < 4; $i++) + { + $a[$i] = $s[$i][$c]; + $b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1; + } + + // a[n] ^ b[n] is a�{03} in GF(2^8) + $s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3 + $s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3 + $s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3 + $s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3 + } + + return $s; + } + + /** + * Key expansion for Rijndael Cipher(): performs key expansion on cipher key + * to generate a key schedule + * + * @param array $key cipher key byte-array (16 bytes) + * + * @return array key schedule as 2D byte-array (Nr+1 x Nb bytes) + */ + public function KeyExpansion($key) + { + // generate Key Schedule from Cipher Key [�5.2] + $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) + $Nk = count($key) / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys + $Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys + + $w = array(); + $temp = array(); + + for ($i = 0; $i < $Nk; $i++) + { + $r = array($key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]); + $w[$i] = $r; + } + + for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++) + { + $w[(int)$i] = array(); + + for ($t = 0; $t < 4; $t++) + { + $temp[$t] = $w[(int)$i - 1][$t]; + } + + if ($i % $Nk == 0) + { + $temp = $this->SubWord($this->RotWord($temp)); + + for ($t = 0; $t < 4; $t++) + { + $temp[$t] ^= $this->Rcon[(int)($i / $Nk)][$t]; + } + } + elseif ($Nk > 6 && $i % $Nk == 4) + { + $temp = $this->SubWord($temp); + } + + for ($t = 0; $t < 4; $t++) + { + $w[(int)$i][$t] = $w[(int)$i - $Nk][$t] ^ $temp[$t]; + } + } + + return $w; + } + + protected function SubWord($w) + { + // apply SBox to 4-byte word w + for ($i = 0; $i < 4; $i++) + { + $w[$i] = $this->Sbox[$w[$i]]; + } + + return $w; + } + + protected function RotWord($w) + { + // rotate 4-byte word w left by one byte + $tmp = $w[0]; + + for ($i = 0; $i < 3; $i++) + { + $w[$i] = $w[$i + 1]; + } + + $w[3] = $tmp; + + return $w; + } + + /* + * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints + * + * @param a number to be shifted (32-bit integer) + * @param b number of bits to shift a to the right (0..31) + * @return a right-shifted and zero-filled by b bits + */ + protected function urs($a, $b) + { + $a &= 0xffffffff; + $b &= 0x1f; // (bounds check) + + if ($a & 0x80000000 && $b > 0) + { + // if left-most bit set + $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit + $a = $a >> ($b - 1); // remaining right-shifts + } + else + { + // otherwise + $a = ($a >> $b); // use normal right-shift + } + + return $a; + } + + /** + * Encrypt a text using AES encryption in Counter mode of operation + * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + * + * Unicode multi-byte character safe + * + * @param string $plaintext source text to be encrypted + * @param string $password the password to use to generate a key + * @param int $nBits number of bits to be used in the key (128, 192, or 256) + * + * @return string encrypted text + */ + public function AESEncryptCtr($plaintext, $password, $nBits) + { + $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES + + // standard allows 128/192/256 bit keys + if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) + { + return ''; + } + + // note PHP (5) gives us plaintext and password in UTF8 encoding! + + // use AES itself to encrypt password to get cipher key (using plain password as source for + // key expansion) - gives us well encrypted key + $nBytes = $nBits / 8; // no bytes in key + $pwBytes = array(); + + for ($i = 0; $i < $nBytes; $i++) + { + $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; + } + + $key = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes)); + $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long + + // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in + // 1st 8 bytes, block counter in 2nd 8 bytes + $counterBlock = array(); + $nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970 + $nonceSec = floor($nonce / 1000); + $nonceMs = $nonce % 1000; + + // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes + for ($i = 0; $i < 4; $i++) + { + $counterBlock[$i] = $this->urs($nonceSec, $i * 8) & 0xff; + } + + for ($i = 0; $i < 4; $i++) + { + $counterBlock[$i + 4] = $nonceMs & 0xff; + } + + // and convert it to a string to go on the front of the ciphertext + $ctrTxt = ''; + + for ($i = 0; $i < 8; $i++) + { + $ctrTxt .= chr($counterBlock[$i]); + } + + // generate key schedule - an expansion of the key into distinct Key Rounds for each round + $keySchedule = $this->KeyExpansion($key); + + $blockCount = ceil(strlen($plaintext) / $blockSize); + $ciphertxt = array(); // ciphertext as array of strings + + for ($b = 0; $b < $blockCount; $b++) + { + // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) + // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) + for ($c = 0; $c < 4; $c++) + { + $counterBlock[15 - $c] = $this->urs($b, $c * 8) & 0xff; + } + + for ($c = 0; $c < 4; $c++) + { + $counterBlock[15 - $c - 4] = $this->urs($b / 0x100000000, $c * 8); + } + + $cipherCntr = $this->Cipher($counterBlock, $keySchedule); // -- encrypt counter block -- + + // block size is reduced on final block + $blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1; + $cipherByte = array(); + + for ($i = 0; $i < $blockLength; $i++) + { // -- xor plaintext with ciphered counter byte-by-byte -- + $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1)); + $cipherByte[$i] = chr($cipherByte[$i]); + } + + $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext + } + + // implode is more efficient than repeated string concatenation + $ciphertext = $ctrTxt . implode('', $ciphertxt); + $ciphertext = base64_encode($ciphertext); + + return $ciphertext; + } + + /** + * Decrypt a text encrypted by AES in counter mode of operation + * + * @param string $ciphertext source text to be decrypted + * @param string $password the password to use to generate a key + * @param int $nBits number of bits to be used in the key (128, 192, or 256) + * + * @return string decrypted text + */ + public function AESDecryptCtr($ciphertext, $password, $nBits) + { + $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES + + // standard allows 128/192/256 bit keys + if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) + { + return ''; + } + + $ciphertext = base64_decode($ciphertext); + + // use AES to encrypt password (mirroring encrypt routine) + $nBytes = $nBits / 8; // no bytes in key + $pwBytes = array(); + + for ($i = 0; $i < $nBytes; $i++) + { + $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; + } + + $key = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes)); + $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long + + // recover nonce from 1st element of ciphertext + $counterBlock = array(); + $ctrTxt = substr($ciphertext, 0, 8); + + for ($i = 0; $i < 8; $i++) + { + $counterBlock[$i] = ord(substr($ctrTxt, $i, 1)); + } + + // generate key schedule + $keySchedule = $this->KeyExpansion($key); + + // separate ciphertext into blocks (skipping past initial 8 bytes) + $nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize); + $ct = array(); + + for ($b = 0; $b < $nBlocks; $b++) + { + $ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16); + } + + $ciphertext = $ct; // ciphertext is now array of block-length strings + + // plaintext will get generated block-by-block into array of block-length strings + $plaintxt = array(); + + for ($b = 0; $b < $nBlocks; $b++) + { + // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) + for ($c = 0; $c < 4; $c++) + { + $counterBlock[15 - $c] = $this->urs($b, $c * 8) & 0xff; + } + + for ($c = 0; $c < 4; $c++) + { + $counterBlock[15 - $c - 4] = $this->urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff; + } + + $cipherCntr = $this->Cipher($counterBlock, $keySchedule); // encrypt counter block + + $plaintxtByte = array(); + + for ($i = 0; $i < strlen($ciphertext[$b]); $i++) + { + // -- xor plaintext with ciphered counter byte-by-byte -- + $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1)); + $plaintxtByte[$i] = chr($plaintxtByte[$i]); + } + + $plaintxt[$b] = implode('', $plaintxtByte); + } + + // join array of blocks into single plaintext string + $plaintext = implode('', $plaintxt); + + return $plaintext; + } + + /** + * AES encryption in CBC mode. This is the standard mode (the CTR methods + * actually use Rijndael-128 in CTR mode, which - technically - isn't AES). + * The data length is tucked as a 32-bit unsigned integer (little endian) + * after the ciphertext. It supports AES-128 only. + * + * @since 3.0.1 + * @author Nicholas K. Dionysopoulos + * + * @param string $plaintext The data to encrypt + * @param string $password Encryption password + * + * @return string The ciphertext + */ + public function AESEncryptCBC($plaintext, $password) + { + $adapter = $this->getAdapter(); + + if (!$adapter->isSupported()) + { + return false; + } + + // Get encryption parameters + $rand = new RandomValue(); + $params = $this->getKeyDerivationParameters(); + $useStaticSalt = $params['useStaticSalt']; + $keySizeBytes = $params['keySize']; + + if ($useStaticSalt) + { + $key = $this->getStaticSaltExpandedKey($password); + } + else + { + // Create a salt and derive a key from the password using PBKDF2 + $algorithm = $params['algorithm']; + $iterations = $params['iterations']; + $salt = $rand->generate(64); + $key = $this->pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes); + } + + + // Also create a new, random IV + $iv = $rand->generate($keySizeBytes); + + // The ciphertext is the encrypted string... + $ciphertext = $adapter->encrypt($plaintext, $key, $iv); + + // ...minus the IV which was placed in front + $ciphertext = substr($ciphertext, $keySizeBytes); + + if (!$useStaticSalt) + { + // ...plus the PBKDF2 setup values at the end (68 bytes)... + $ciphertext .= 'JPST' . $salt; + } + + // ...plus the IV at the end (20 bytes)... + $ciphertext .= 'JPIV' . $iv; + + // ...plus the plaintext length (4 bytes). + $ciphertext .= pack('V', strlen($plaintext)); + + return $ciphertext; + } + + /** + * Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static + * parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block + * to minimize the risk of attacks against the password. + * + * @return array + */ + public function getKeyDerivationParameters() + { + return array( + 'keySize' => 16, + 'algorithm' => $this->pbkdf2Algorithm, + 'iterations' => $this->pbkdf2Iterations, + 'useStaticSalt' => $this->pbkdf2UseStaticSalt, + 'staticSalt' => $this->pbkdf2StaticSalt, + ); + } + + /** + * AES decryption in CBC mode. This is the standard mode (the CTR methods + * actually use Rijndael-128 in CTR mode, which - technically - isn't AES). + * + * It supports AES-128 only. It assumes that the last 4 bytes + * contain a little-endian unsigned long integer representing the unpadded + * data length. + * + * @since 3.0.1 + * @author Nicholas K. Dionysopoulos + * + * @param string $ciphertext The data to encrypt + * @param string $password Encryption password + * + * @return string The plaintext + */ + public function AESDecryptCBC($ciphertext, $password) + { + $adapter = $this->getAdapter(); + + if (!$adapter->isSupported()) + { + return false; + } + + // Read the data size + $data_size = unpack('V', substr($ciphertext, -4)); + + // Do I have a PBKDF2 salt? + $salt = substr($ciphertext, -92, 68); + $rightStringLimit = -4; + + $params = $this->getKeyDerivationParameters(); + $keySizeBytes = $params['keySize']; + $algorithm = $params['algorithm']; + $iterations = $params['iterations']; + $useStaticSalt = $params['useStaticSalt']; + + if (substr($salt, 0, 4) == 'JPST') + { + // We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes + // (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the + // uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the + // format specification). + $salt = substr($salt, 4); + $rightStringLimit -= 68; + + $key = $this->pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes); + } + elseif ($useStaticSalt) + { + // We have a static salt. Use it for PBKDF2. + $key = $this->getStaticSaltExpandedKey($password); + } + else + { + // Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD. + $key = $this->expandKey($password); + } + + // Try to get the IV from the data + $iv = substr($ciphertext, -24, 20); + + if (substr($iv, 0, 4) == 'JPIV') + { + // We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes + // (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length) + $iv = substr($iv, 4); + $rightStringLimit -= 20; + } + else + { + // No stored IV. Do it the dumb way. + $iv = $this->createTheWrongIV($password); + } + + // Decrypt + $plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key); + + // Trim padding, if necessary + if (strlen($plaintext) > $data_size) + { + $plaintext = substr($plaintext, 0, $data_size); + } + + return $plaintext; + } + + /** + * That's the old way of creating an IV that's definitely not cryptographically sound. + * + * DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA + * + * @param string $password The raw password from which we create an IV in a super bozo way + * + * @return string A 16-byte IV string + * + * @since 4.6.0 + * @author Nicholas K. Dionysopoulos + */ + function createTheWrongIV($password) + { + static $ivs = array(); + + $key = md5($password); + + if (!isset($ivs[$key])) + { + // Create an Initialization Vector (IV) based on the password, using the same technique as for the key + $nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes + $pwBytes = array(); + + for ($i = 0; $i < $nBytes; $i++) + { + $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; + } + + $iv = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes)); + $newIV = ''; + + foreach ($iv as $int) + { + $newIV .= chr($int); + } + + $ivs[$key] = $newIV; + } + + return $ivs[$key]; + } + + /** + * Expand the password to an appropriate 128-bit encryption key. THIS CODE IS OBSOLETE. DO NOT USE. + * + * @param string $password + * + * @return string + * + * @since 5.2.0 + * @author Nicholas K. Dionysopoulos + */ + public function expandKey($password) + { + // Try to fetch cached key or create it if it doesn't exist + $nBits = 128; + $lookupKey = md5($password . '-' . $nBits); + + if (array_key_exists($lookupKey, $this->passwords)) + { + $key = $this->passwords[$lookupKey]; + + return $key; + } + + // use AES itself to encrypt password to get cipher key (using plain password as source for + // key expansion) - gives us well encrypted key. + $nBytes = $nBits / 8; // Number of bytes in key + $pwBytes = array(); + + for ($i = 0; $i < $nBytes; $i++) + { + $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; + } + + $key = $this->Cipher($pwBytes, $this->KeyExpansion($pwBytes)); + $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long + $newKey = ''; + + foreach ($key as $int) + { + $newKey .= chr($int); + } + + $key = $newKey; + + $this->passwords[$lookupKey] = $key; + + return $key; + } + + /** + * Returns the correct AES-128 CBC encryption adapter + * + * @return AdapterInterface + * + * @since 5.2.0 + * @author Nicholas K. Dionysopoulos + */ + public function getAdapter() + { + static $adapter = null; + + if (is_object($adapter) && ($adapter instanceof AdapterInterface)) + { + return $adapter; + } + + $adapter = new OpenSSL(); + + if (!$adapter->isSupported()) + { + $adapter = new Mcrypt(); + } + + return $adapter; + } + + /** + * Returns the length of a string in BYTES, not characters + * + * @param string $string The string to get the length for + * + * @return int The size in BYTES + */ + public function stringLength($string) + { + return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string); + } + + /** + * Attempt to use mbstring for getting parts of strings + * + * @param string $string + * @param int $start + * @param int|null $length + * + * @return string + */ + public function subString($string, $start, $length = null) + { + return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : + substr($string, $start, $length); + } + + /** + * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt + * + * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt + * + * This implementation of PBKDF2 was originally created by https://defuse.ca + * With improvements by http://www.variations-of-shadow.com + * Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster) + * + * @param string $password The password. + * @param string $salt A salt that is unique to the password. + * @param string $algorithm The hash algorithm to use. Default is sha1. + * @param int $count Iteration count. Higher is better, but slower. Default: 1000. + * @param int $key_length The length of the derived key in bytes. + * + * @return string A string of $key_length bytes + */ + public function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16) + { + if (function_exists("hash_pbkdf2")) + { + return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true); + } + + $hash_length = $this->stringLength(hash($algorithm, "", true)); + $block_count = ceil($key_length / $hash_length); + + $output = ""; + + for ($i = 1; $i <= $block_count; $i++) + { + // $i encoded as 4 bytes, big endian. + $last = $salt . pack("N", $i); + + // First iteration + $xorResult = hash_hmac($algorithm, $last, $password, true); + $last = $xorResult; + + // Perform the other $count - 1 iterations + for ($j = 1; $j < $count; $j++) + { + $last = hash_hmac($algorithm, $last, $password, true); + $xorResult ^= $last; + } + + $output .= $xorResult; + } + + return $this->subString($output, 0, $key_length); + } + + /** + * @return string + */ + public function getPbkdf2Algorithm() + { + return $this->pbkdf2Algorithm; + } + + /** + * @param string $pbkdf2Algorithm + * @return Encrypt + */ + public function setPbkdf2Algorithm($pbkdf2Algorithm) + { + $this->pbkdf2Algorithm = $pbkdf2Algorithm; + + return $this; + } + + /** + * @return int + */ + public function getPbkdf2Iterations() + { + return $this->pbkdf2Iterations; + } + + /** + * @param int $pbkdf2Iterations + * @return Encrypt + */ + public function setPbkdf2Iterations($pbkdf2Iterations) + { + $this->pbkdf2Iterations = $pbkdf2Iterations; + + return $this; + } + + /** + * @return int + */ + public function getPbkdf2UseStaticSalt() + { + return $this->pbkdf2UseStaticSalt; + } + + /** + * @param int $pbkdf2UseStaticSalt + * @return Encrypt + */ + public function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt) + { + $this->pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt; + + return $this; + } + + /** + * @return string + */ + public function getPbkdf2StaticSalt() + { + return $this->pbkdf2StaticSalt; + } + + /** + * @param string $pbkdf2StaticSalt + * @return Encrypt + */ + public function setPbkdf2StaticSalt($pbkdf2StaticSalt) + { + $this->pbkdf2StaticSalt = $pbkdf2StaticSalt; + + return $this; + } + + /** + * Get the expanded key from the user supplied password using a static salt. The results are cached for performance + * reasons. + * + * @param string $password The user-supplied password, UTF-8 encoded. + * + * @return string The expanded key + */ + public function getStaticSaltExpandedKey($password) + { + $params = $this->getKeyDerivationParameters(); + $keySizeBytes = $params['keySize']; + $algorithm = $params['algorithm']; + $iterations = $params['iterations']; + $staticSalt = $params['staticSalt']; + + $lookupKey = "PBKDF2-$algorithm-$iterations-" . md5($password . $staticSalt); + + if (!array_key_exists($lookupKey, $this->passwords)) + { + $this->passwords[$lookupKey] = $this->pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes); + } + + return $this->passwords[$lookupKey]; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/EngineParameters.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/EngineParameters.php new file mode 100644 index 00000000..9149b3ed --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/EngineParameters.php @@ -0,0 +1,937 @@ +scripting)) + { + $ini_file_name = Factory::getAkeebaRoot() . '/Core/scripting.ini'; + + if (@file_exists($ini_file_name)) + { + $raw_data = ParseIni::parse_ini_file($ini_file_name, false); + $domain_keys = explode('|', $raw_data['volatile.akeebaengine.domains']); + $domains = array(); + + foreach ($domain_keys as $key) + { + $record = array( + 'domain' => $raw_data['volatile.domain.' . $key . '.domain'], + 'class' => $raw_data['volatile.domain.' . $key . '.class'], + 'text' => $raw_data['volatile.domain.' . $key . '.text'] + ); + $domains[$key] = $record; + } + + $script_keys = explode('|', $raw_data['volatile.akeebaengine.scripts']); + $scripts = array(); + + foreach ($script_keys as $key) + { + $record = array( + 'chain' => explode('|', $raw_data['volatile.scripting.' . $key . '.chain']), + 'text' => $raw_data['volatile.scripting.' . $key . '.text'] + ); + $scripts[$key] = $record; + } + + $this->scripting = array( + 'domains' => $domains, + 'scripts' => $scripts, + 'data' => $raw_data + ); + } + else + { + $this->scripting = array(); + } + } + + return $this->scripting; + } + + /** + * Imports the volatile scripting parameters to the registry + * + * @return void + */ + public function importScriptingToRegistry() + { + $scripting = $this->loadScripting(); + $configuration = Factory::getConfiguration(); + $configuration->mergeArray($scripting['data'], false); + } + + /** + * Returns a volatile scripting parameter for the active backup type + * + * @param string $key The relative key, e.g. core.createarchive + * @param mixed $default Default value + * + * @return mixed The scripting parameter's value + */ + public function getScriptingParameter($key, $default = null) + { + $configuration = Factory::getConfiguration(); + + if (is_null($this->activeType)) + { + $this->activeType = $configuration->get('akeeba.basic.backup_type', 'full'); + } + + return $configuration->get('volatile.scripting.' . $this->activeType . '.' . $key, $default); + } + + /** + * Returns an array with domain keys and domain class names for the current + * backup type. The idea is that shifting this array walks through the backup + * process. When the array is empty, the backup is done. + * + * Each element of the array is an array with two keys: domain and class. + * + * @return array + */ + public function getDomainChain() + { + $configuration = Factory::getConfiguration(); + $script = $configuration->get('akeeba.basic.backup_type', 'full'); + + $scripting = $this->loadScripting(); + $domains = $scripting['domains']; + $keys = $scripting['scripts'][$script]['chain']; + + $result = array(); + foreach ($keys as $domain_key) + { + $result[] = array( + 'domain' => $domains[$domain_key]['domain'], + 'class' => $domains[$domain_key]['class'] + ); + } + + return $result; + } + + /** + * Append a path to the end of the paths list for a specific section + * + * @param string $path Absolute filesystem path to add + * @param string $section The section to add it to (gui, engine, installer, filters) + * + * @return void + */ + public function addPath($path, $section = 'gui') + { + $path = Factory::getFilesystemTools()->TranslateWinPath($path); + + // If the array is empty, populate with the defaults + if (!array_key_exists($section, $this->enginePartPaths)) + { + $this->getEnginePartPaths($section); + } + + // If the path doesn't already exist, add it + if (!in_array($path, $this->enginePartPaths[$section])) + { + $this->enginePartPaths[$section][] = $path; + } + } + + /** + * Add a path to the beginning of the paths list for a specific section + * + * @param string $path Absolute filesystem path to add + * @param string $section The section to add it to (gui, engine, installer, filters) + * + * @return void + */ + public function prependPath($path, $section = 'gui') + { + $path = Factory::getFilesystemTools()->TranslateWinPath($path); + + // If the array is empty, populate with the defaults + if (!array_key_exists($section, $this->enginePartPaths)) + { + $this->getEnginePartPaths($section); + } + + // If the path doesn't already exist, add it + if (!in_array($path, $this->enginePartPaths[$section])) + { + array_unshift($this->enginePartPaths[$section], $path); + } + } + + /** + * Get the paths for a specific section + * + * @param string $section The section to get the path list for (engine, installer, gui, filter) + * + * @return array + */ + public function getEnginePartPaths($section = 'gui') + { + // Create the key if it's not already present + if (!array_key_exists($section, $this->enginePartPaths)) + { + $this->enginePartPaths[$section] = array(); + } + + // Add the defaults if the list is empty + if (empty($this->enginePartPaths[$section])) + { + switch ($section) + { + case 'engine': + $this->enginePartPaths[$section] = array( + Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot()), + ); + break; + + case 'installer': + $this->enginePartPaths[$section] = array( + Factory::getFilesystemTools()->TranslateWinPath(Platform::getInstance()->get_installer_images_path()) + ); + break; + + case 'gui': + // Add core GUI definitions + $this->enginePartPaths[$section] = array( + Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Core') + ); + + // Add platform GUI definition files + $platform_paths = Platform::getInstance()->getPlatformDirectories(); + + foreach ($platform_paths as $p) + { + $this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Config'); + + $pro = defined('AKEEBA_PRO') && AKEEBA_PRO; + $pro = defined('AKEEBABACKUP_PRO') ? (AKEEBABACKUP_PRO ? true : false) : $pro; + + if ($pro) + { + $this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Config/Pro'); + } + } + break; + + case 'filter': + $this->enginePartPaths[$section] = array( + Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Platform/Filter/Stack'), + Factory::getFilesystemTools()->TranslateWinPath(Factory::getAkeebaRoot() . '/Filter/Stack'), + ); + + $platform_paths = Platform::getInstance()->getPlatformDirectories(); + + foreach ($platform_paths as $p) + { + $this->enginePartPaths[$section][] = Factory::getFilesystemTools()->TranslateWinPath($p . '/Filter/Stack'); + } + + break; + } + } + + return $this->enginePartPaths[$section]; + } + + /** + * Returns a hash list of Akeeba engines and their data. Each entry has the engine + * name as key and contains two arrays, under the 'information' and 'parameters' keys. + * + * @param string $engine_type The engine type to return information for + * + * @return array + */ + public function getEnginesList($engine_type) + { + $engine_type = ucfirst($engine_type); + + // Try to serve cached data first + if (isset($this->engine_list[$engine_type])) + { + return $this->engine_list[$engine_type]; + } + + // Find absolute path to normal and plugins directories + $temp = $this->getEnginePartPaths('engine'); + $path_list = array(); + + foreach ($temp as $path) + { + $path_list[] = $path . '/' . $engine_type; + } + + // Initialize the array where we store our data + $this->engine_list[$engine_type] = array(); + + // Loop for the paths where engines can be found + foreach ($path_list as $path) + { + if (!@is_dir($path)) + { + continue; + } + + if (!@is_readable($path)) + { + continue; + } + + $di = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if (!$file->isFile()) + { + continue; + } + + // PHP 5.3.5 and earlier do not support getExtension + // if ($file->getExtension() !== 'ini') + if (substr($file->getBasename(), -4) != '.ini') + { + continue; + } + + $bare_name = ucfirst($file->getBasename('.ini')); + + // Some hosts copy .ini and .php files, renaming them (ie foobar.1.php) + // We need to exclude them, otherwise we'll get a fatal error for declaring the same class twice + if (preg_match('/[^A-Za-z0-9]/', $bare_name)) + { + continue; + } + + $information = array(); + $parameters = array(); + + $this->parseEngineINI($file->getRealPath(), $information, $parameters); + + $this->engine_list[$engine_type][lcfirst($bare_name)] = array + ( + 'information' => $information, + 'parameters' => $parameters + ); + } + } + + return $this->engine_list[$engine_type]; + } + + /** + * Parses the GUI INI files and returns an array of groups and their data + * + * @return array + */ + public function getGUIGroups() + { + // Try to serve cached data first + if (!empty($this->gui_list) && is_array($this->gui_list)) + { + if (count($this->gui_list) > 0) + { + return $this->gui_list; + } + } + + // Find absolute path to normal and plugins directories + $path_list = $this->getEnginePartPaths('gui'); + + // Initialize the array where we store our data + $this->gui_list = array(); + + // Loop for the paths where engines can be found + foreach ($path_list as $path) + { + if (!@is_dir($path)) + { + continue; + } + + if (!@is_readable($path)) + { + continue; + } + + $allINIs = array(); + $di = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if (!$file->isFile()) + { + continue; + } + + // PHP 5.3.5 and earlier do not support getExtension + // if ($file->getExtension() !== 'ini') + if (substr($file->getBasename(), -4) != '.ini') + { + continue; + } + + $allINIs[] = $file->getRealPath(); + } + + if (empty($allINIs)) + { + continue; + } + + // Sort GUI files alphabetically + asort($allINIs); + + // Include each GUI def file + foreach ($allINIs as $filename) + { + $information = array(); + $parameters = array(); + + $this->parseInterfaceINI($filename, $information, $parameters); + + // This effectively skips non-GUI INIs (e.g. the scripting INI) + if (!empty($information['description'])) + { + if (!isset($information['merge'])) + { + $information['merge'] = 0; + } + + $group_name = substr(basename($filename), 0, -4); + + $def = array( + 'information' => $information, + 'parameters' => $parameters + ); + + if (!$information['merge'] || !isset($this->gui_list[$group_name])) + { + $this->gui_list[$group_name] = $def; + } + else + { + $this->gui_list[$group_name]['information'] = array_merge($this->gui_list[$group_name]['information'], $def['information']); + $this->gui_list[$group_name]['parameters'] = array_merge($this->gui_list[$group_name]['parameters'], $def['parameters']); + } + } + } + } + + ksort($this->gui_list); + + // Push stack filter settings to the 03.filters section + $path_list = $this->getEnginePartPaths('filter'); + + // Loop for the paths where optional filters can be found + foreach ($path_list as $path) + { + if (!@is_dir($path)) + { + continue; + } + + if (!@is_readable($path)) + { + continue; + } + + // Store INI names in temp array because we'll sort based on filename (GUI order IS IMPORTANT!!) + $allINIs = array(); + + $di = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if (!$file->isFile()) + { + continue; + } + + // PHP 5.3.5 and earlier do not support getExtension + // if ($file->getExtension() !== 'ini') + if (substr($file->getBasename(), -4) != '.ini') + { + continue; + } + + $allINIs[] = $file->getRealPath(); + } + + if (empty($allINIs)) + { + continue; + } + + // Sort filter files alphabetically + asort($allINIs); + + // Include each filter def file + foreach ($allINIs as $filename) + { + $information = array(); + $parameters = array(); + + $this->parseInterfaceINI($filename, $information, $parameters); + + if (!array_key_exists('03.filters', $this->gui_list)) + { + $this->gui_list['03.filters'] = array('parameters' => array()); + } + + if (!array_key_exists('parameters', $this->gui_list['03.filters'])) + { + $this->gui_list['03.filters']['parameters'] = array(); + } + + if (!is_array($parameters)) + { + $parameters = array(); + } + + $this->gui_list['03.filters']['parameters'] = array_merge($this->gui_list['03.filters']['parameters'], $parameters); + } + } + + return $this->gui_list; + } + + /** + * Parses the installer INI files and returns an array of installers and their data + * + * @param boolean $forDisplay If true only returns the information relevant for displaying the GUI + * + * @return array + */ + public function getInstallerList($forDisplay = false) + { + // Try to serve cached data first + if (!empty($this->installer_list) && is_array($this->installer_list)) + { + if (count($this->installer_list) > 0) + { + return $this->installer_list; + } + } + + // Find absolute path to normal and plugins directories + $path_list = array( + Platform::getInstance()->get_installer_images_path() + ); + + // Initialize the array where we store our data + $this->installer_list = array(); + + // Loop for the paths where engines can be found + foreach ($path_list as $path) + { + if (!@is_dir($path)) + { + continue; + } + + if (!@is_readable($path)) + { + continue; + } + + $di = new \DirectoryIterator($path); + + /** @var \DirectoryIterator $file */ + foreach ($di as $file) + { + if (!$file->isFile()) + { + continue; + } + + // PHP 5.3.5 and earlier do not support getExtension + // if ($file->getExtension() !== 'ini') + if (substr($file->getBasename(), -4) != '.ini') + { + continue; + } + + $data = ParseIni::parse_ini_file($file->getRealPath(), true); + + if ($forDisplay) + { + $innerData = reset($data); + + if (array_key_exists('listinoptions', $innerData)) + { + if ($innerData['listinoptions'] == 0) + { + continue; + } + } + } + + foreach ($data as $key => $values) + { + $this->installer_list[$key] = array(); + + foreach ($values as $key2 => $value) + { + $this->installer_list[$key][$key2] = $value; + } + } + } + } + + return $this->installer_list; + } + + /** + * Returns the JSON representation of the GUI definition and the associated values + * + * @return string + */ + public function getJsonGuiDefinition() + { + // Initialize the array which will be converted to JSON representation + $json_array = array( + 'engines' => array(), + 'installers' => array(), + 'gui' => array() + ); + + // Get a reference to the configuration + $configuration = Factory::getConfiguration(); + + // Get data for all engines + $engine_types = array( + 'archiver', + 'dump', + 'scan', + 'writer', + 'postproc', + ); + + foreach ($engine_types as $type) + { + $engines = $this->getEnginesList($type); + + $tempArray = array(); + $engineTitles = array(); + + foreach ($engines as $engine_name => $engine_data) + { + // Translate information + foreach ($engine_data['information'] as $key => $value) + { + switch ($key) + { + case 'title': + case 'description': + $value = Platform::getInstance()->translate($value); + break; + } + + $tempArray[$engine_name]['information'][$key] = $value; + + if ($key == 'title') + { + $engineTitles[$engine_name] = $value; + } + } + + // Process parameters + $parameters = array(); + + foreach ($engine_data['parameters'] as $param_key => $param) + { + $param['default'] = $configuration->get($param_key, $param['default'], false); + + foreach ($param as $option_key => $option_value) + { + // Translate title, description, enumkeys + switch ($option_key) + { + case 'title': + case 'description': + case 'labelempty': + case 'labelnotempty': + $param[$option_key] = Platform::getInstance()->translate($option_value); + break; + + case 'enumkeys': + $enumkeys = explode('|', $option_value); + $new_keys = array(); + foreach ($enumkeys as $old_key) + { + $new_keys[] = Platform::getInstance()->translate($old_key); + } + $param[$option_key] = implode('|', $new_keys); + break; + + default: + } + } + + $parameters[$param_key] = $param; + } + + // Add processed parameters + $tempArray[$engine_name]['parameters'] = $parameters; + } + + asort($engineTitles); + + foreach ($engineTitles as $engineName => $title) + { + $json_array['engines'][$type][$engineName] = $tempArray[$engineName]; + } + } + + // Get data for GUI elements + $json_array['gui'] = array(); + $groupdefs = $this->getGUIGroups(); + + foreach ($groupdefs as $group_ini => $definition) + { + $group_name = ''; + + if (isset($definition['information']) && isset($definition['information']['description'])) + { + $group_name = Platform::getInstance()->translate($definition['information']['description']); + } + + // Skip no-name groups + if (empty($group_name)) + { + continue; + } + + $parameters = array(); + + foreach ($definition['parameters'] as $param_key => $param) + { + $param['default'] = $configuration->get($param_key, $param['default'], false); + + foreach ($param as $option_key => $option_value) + { + // Translate title, description, enumkeys + switch ($option_key) + { + case 'title': + case 'description': + $param[$option_key] = Platform::getInstance()->translate($option_value); + break; + + case 'enumkeys': + $enumkeys = explode('|', $option_value); + $new_keys = array(); + foreach ($enumkeys as $old_key) + { + $new_keys[] = Platform::getInstance()->translate($old_key); + } + $param[$option_key] = implode('|', $new_keys); + break; + + default: + } + } + $parameters[$param_key] = $param; + } + $json_array['gui'][$group_name] = $parameters; + } + + // Get data for the installers + $json_array['installers'] = $this->getInstallerList(true); + + uasort($json_array['installers'], function($a, $b){ + if ($a['name'] == $b['name']) + { + return 0; + } + + return ($a['name'] < $b['name']) ? -1 : 1; + }); + + $json = json_encode($json_array); + + return $json; + } + + /** + * Parses an engine INI file returning two arrays, one with the general information + * of that engine and one with its configuration variables' definitions + * + * @param string $inifile Absolute path to engine INI file + * @param array $information [out] The engine information hash array + * @param array $parameters [out] The parameters hash array + * + * @return bool True if the file was loaded + */ + public function parseEngineINI($inifile, &$information, &$parameters) + { + if (!file_exists($inifile)) + { + return false; + } + + $information = array( + 'title' => '', + 'description' => '' + ); + + $parameters = array(); + + $inidata = ParseIni::parse_ini_file($inifile, true); + + foreach ($inidata as $section => $data) + { + if (is_array($data)) + { + if ($section == '_information') + { + // Parse information + foreach ($data as $key => $value) + { + $information[$key] = $value; + } + } + elseif (substr($section, 0, 1) != '_') + { + // Parse parameters + $newparam = array( + 'title' => '', + 'description' => '', + 'type' => 'string', + 'default' => '' + ); + + foreach ($data as $key => $value) + { + $newparam[$key] = $value; + } + $parameters[$section] = $newparam; + } + } + } + + return true; + } + + /** + * Parses a graphical interface INI file returning two arrays, one with the general + * information of that configuration section and one with its configuration variables' + * definitions. + * + * @param string $inifile Absolute path to engine INI file + * @param array $information [out] The GUI information hash array + * @param array $parameters [out] The parameters hash array + * + * @return bool True if the file was loaded + */ + public function parseInterfaceINI($inifile, &$information, &$parameters) + { + if (!file_exists($inifile)) + { + return false; + } + + $information = array( + 'description' => '' + ); + + $parameters = array(); + $inidata = ParseIni::parse_ini_file($inifile, true); + + foreach ($inidata as $section => $data) + { + if (is_array($data)) + { + if ($section == '_group') + { + // Parse information + foreach ($data as $key => $value) + { + $information[$key] = $value; + } + + continue; + } + + if (substr($section, 0, 1) != '_') + { + // Parse parameters + $newparam = array( + 'title' => '', + 'description' => '', + 'type' => 'string', + 'default' => '', + 'protected' => 0, + ); + + foreach ($data as $key => $value) + { + $newparam[$key] = $value; + } + + $parameters[$section] = $newparam; + } + } + } + + return true; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FactoryStorage.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FactoryStorage.php new file mode 100644 index 00000000..846918f7 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FactoryStorage.php @@ -0,0 +1,289 @@ +setStorageEngine($storageEngine); + } + + /** + * Sets the storage engine which will be used + * + * @param string $engine The storage engine (currently only db or file can be specified) + */ + public function setStorageEngine($engine = null) + { + if (empty($engine)) + { + $config = Factory::getConfiguration(); + $usedb = $config->get('akeeba.core.usedbstorage', 0); + $engine = $usedb ? 'db' : 'file'; + } + + $this->storageEngine = $engine; + } + + /** + * Returns the name of the storage engine + * + * @return string + */ + public function getStorageEngine() + { + return $this->storageEngine; + } + + /** + * Returns the fully qualified path to the storage file + * + * @param string $tag + * + * @return string + */ + public function get_storage_filename($tag = null) + { + static $basepath = null; + + if ($this->storageEngine == 'db') + { + return empty($tag) ? 'storage' : $tag; + } + else + { + if (is_null($basepath)) + { + $registry = Factory::getConfiguration(); + $basepath = $registry->get('akeeba.basic.output_directory') . DIRECTORY_SEPARATOR; + } + + if (empty($tag)) + { + $tag = 'storage'; + } + + return $basepath . 'akeeba_' . $tag; + } + } + + /** + * Resets the storage. This method removes all stored values. + * @param null $tag + * + * @return bool True on success + */ + public function reset($tag = null) + { + switch ($this->storageEngine) + { + case 'file': + $filename = $this->get_storage_filename($tag); + + if (!is_file($filename) && !is_link($filename)) + { + return false; + } + + return @unlink($this->get_storage_filename($tag)); + + break; + + case 'db': + $dbtag = $this->get_storage_filename($tag); + $db = Factory::getDatabase(); + $sql = $db->getQuery(true) + ->delete($db->qn('#__ak_storage')) + ->where($db->qn('tag') . ' = ' . $db->q($dbtag)); + $db->setQuery($sql); + + try + { + $result = $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + return ($result !== false); + + break; + } + + return false; + } + + public function set($value, $tag = null) + { + $storage_filename = $this->get_storage_filename($tag); + + switch ($this->storageEngine) + { + case 'file': + // Remove old file (if exists) + if (file_exists($storage_filename)) + { + @unlink($storage_filename); + } + + // Open the new file + $fp = @fopen($storage_filename, 'wb'); + + if ($fp === false) + { + return false; + } + + // Add a header + fwrite($fp, $this->encode($value)); + fclose($fp); + + return true; + + break; + + case 'db': + $db = Factory::getDatabase(); + + // Delete any old records + $sql = $db->getQuery(true) + ->delete($db->qn('#__ak_storage')) + ->where($db->qn('tag') . ' = ' . $db->q($storage_filename)); + $db->setQuery($sql); + + try + { + $result = $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + // Add the new record + $sql = $db->getQuery(true) + ->insert($db->qn('#__ak_storage')) + ->columns(array( + $db->qn('tag'), + $db->qn('data'), + ))->values($db->q($storage_filename) . ',' . $db->q($this->encode($value))); + + $db->setQuery($sql); + + try + { + $result = $db->query(); + } + catch (\Exception $exc) + { + return false; + } + + return ($result !== false); + + break; + } + } + + public function &get($tag = null) + { + $storage_filename = $this->get_storage_filename($tag); + + $ret = false; + + switch ($this->storageEngine) + { + case 'file': + $data = @file_get_contents($storage_filename); + + if ($data === false) + { + return $ret; + } + + break; + + case 'db': + $db = Factory::getDatabase(); + $sql = $db->getQuery(true) + ->select($db->qn('data')) + ->from($db->qn('#__ak_storage')) + ->where($db->qn('tag') . ' = ' . $db->q($storage_filename)); + $db->setQuery($sql); + + try + { + $data = $db->loadResult(); + + if (empty($data)) + { + return $ret; + } + } + catch (\Exception $e) + { + return $ret; + } + + break; + } + + $ret = $this->decode($data); + unset($data); + + return $ret; + } + + public function encode(&$data) + { + // Should I base64-encode? + if (function_exists('base64_encode') && function_exists('base64_decode')) + { + return base64_encode($data); + } + elseif (function_exists('convert_uuencode') && function_exists('convert_uudecode')) + { + return convert_uuencode($data); + } + else + { + return $data; + } + } + + public function decode(&$data) + { + if (function_exists('base64_encode') && function_exists('base64_decode')) + { + return base64_decode($data); + } + elseif (function_exists('convert_uuencode') && function_exists('convert_uudecode')) + { + return convert_uudecode($data); + } + else + { + return $data; + } + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileLister.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileLister.php new file mode 100644 index 00000000..92827b21 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileLister.php @@ -0,0 +1,154 @@ +get('engine.archiver.common.dereference_symlinks'); + + while ((($file = @readdir($handle)) !== false)) + { + if (($file != '.') && ($file != '..')) + { + // # Fix 2.4.b1: Do not add DS if we are on the site's root and it's an empty string + // # Fix 2.4.b2: Do not add DS is the last character _is_ DS + $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR; + $dir = "$folder/$file"; + $isDir = @is_dir($dir); + $isLink = @is_link($dir); + + //if (!$isDir || ($isDir && $isLink && !$dereferencesymlinks) ) { + if ( !$isDir) + { + if ($fullpath) + { + $data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($dir) : $dir; + } + else + { + $data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($file) : $file; + } + if ($data) + { + $arr[] = $data; + } + } + } + } + @closedir($handle); + + return $arr; + } + + public function &getFolders($folder, $fullpath = false) + { + // Initialize variables + $arr = array(); + $false = false; + + if ( !is_dir($folder) && !is_dir($folder . '/')) + { + return $false; + } + + $handle = @opendir($folder); + if ($handle === false) + + { + $handle = @opendir($folder . '/'); + } + + // If directory is not accessible, just return FALSE + if ($handle === false) + { + return $false; + } + + $registry = Factory::getConfiguration(); + $dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks'); + + while ((($file = @readdir($handle)) !== false)) + { + if (($file != '.') && ($file != '..')) + { + $dir = "$folder/$file"; + $isDir = @is_dir($dir); + $isLink = @is_link($dir); + + if ($isDir) + { + //if(!$dereferencesymlinks && $isLink) continue; + if ($fullpath) + { + $data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($dir) : $dir; + } + else + { + $data = _AKEEBA_IS_WINDOWS ? Factory::getFilesystemTools()->TranslateWinPath($file) : $file; + } + + if ($data) + { + $arr[] = $data; + } + } + } + } + @closedir($handle); + + return $arr; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileSystem.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileSystem.php new file mode 100644 index 00000000..8c8ee125 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/FileSystem.php @@ -0,0 +1,230 @@ +isWindows = (DIRECTORY_SEPARATOR == '\\'); + } + + /** + * Makes a Windows path more UNIX-like, by turning backslashes to forward slashes. + * It takes into account UNC paths, e.g. \\myserver\some\folder becomes + * \\myserver/some/folder. + * + * This function will also fix paths with multiple slashes, e.g. convert /var//www////html to /var/www/html + * + * @param string $p_path The path to transform + * + * @return string + */ + public function TranslateWinPath($p_path) + { + $is_unc = false; + + if ($this->isWindows) + { + // Is this a UNC path? + $is_unc = (substr($p_path, 0, 2) == '\\\\') || (substr($p_path, 0, 2) == '//'); + + // Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) + { + $p_path = strtr($p_path, '\\', '/'); + } + } + + // Remove multiple slashes + $p_path = str_replace('///', '/', $p_path); + $p_path = str_replace('//', '/', $p_path); + + // Fix UNC paths + if ($is_unc) + { + $p_path = '//' . ltrim($p_path, '/'); + } + + return $p_path; + } + + /** + * Removes trailing slash or backslash from a pathname + * + * @param string $path The path to treat + * + * @return string The path without the trailing slash/backslash + */ + public function TrimTrailingSlash($path) + { + $newpath = $path; + + if (substr($path, strlen($path) - 1, 1) == '\\') + { + $newpath = substr($path, 0, strlen($path) - 1); + } + + if (substr($path, strlen($path) - 1, 1) == '/') + { + $newpath = substr($path, 0, strlen($path) - 1); + } + + return $newpath; + } + + /** + * Returns an array with the archive name variables and their values. This is used to replace variables in archive + * and directory names, etc. + * + * If there is a non-empty configuration value called volatile.core.archivenamevars with a serialised array it will + * be unserialised and used. Otherwise the name variables will be calculated on-the-fly. + * + * IMPORTANT: These variables do NOT include paths such as [SITEROOT] + * + * @return array + */ + public function get_archive_name_variables() + { + $variables = array(); + + $registry = Factory::getConfiguration(); + $serialized = $registry->get('volatile.core.archivenamevars', null); + + if (!empty($serialized)) + { + $variables = @unserialize($serialized); + } + + if (empty($variables) || !is_array($variables)) + { + $host = Platform::getInstance()->get_host(); + $version = defined('AKEEBA_VERSION') ? AKEEBA_VERSION : 'svn'; + $version = defined('AKEEBABACKUP_VERSION') ? AKEEBABACKUP_VERSION : $version; + $platformVars = Platform::getInstance()->getPlatformVersion(); + + $siteName = Platform::getInstance()->get_site_name(); + $siteName = htmlentities(utf8_decode($siteName)); + $siteName = preg_replace( + array('/ß/', '/&(..)lig;/', '/&([aouAOU])uml;/', '/&(.)[^;]*;/'), + array('ss', "$1", "$1" . 'e', "$1"), + $siteName); + $siteName = trim(strtolower($siteName)); + $siteName = preg_replace(array('/\s+/', '/[^A-Za-z0-9\-]/'), array('-', ''), $siteName); + + if (strlen($siteName) > 50) + { + $siteName = substr($siteName, 0, 50); + } + + /** + * Time components. Expressed in whatever timezone the Platform decides to use. + */ + // Raw timezone, e.g. "EEST" + $rawTz = Platform::getInstance()->get_local_timestamp("T"); + // Filename-safe timezone, e.g. "eest". Note the lowercase letters. + $fsSafeTZ = strtolower(str_replace(array(' ', '/', ':'), array('_', '_', '_'), $rawTz)); + + $variables = array( + '[DATE]' => Platform::getInstance()->get_local_timestamp("Ymd"), + '[YEAR]' => Platform::getInstance()->get_local_timestamp("Y"), + '[MONTH]' => Platform::getInstance()->get_local_timestamp("m"), + '[DAY]' => Platform::getInstance()->get_local_timestamp("d"), + '[TIME]' => Platform::getInstance()->get_local_timestamp("His"), + '[TIME_TZ]' => Platform::getInstance()->get_local_timestamp("His") . $fsSafeTZ, + '[WEEK]' => Platform::getInstance()->get_local_timestamp("W"), + '[WEEKDAY]' => Platform::getInstance()->get_local_timestamp("l"), + '[TZ]' => $fsSafeTZ, + '[TZ_RAW]' => $rawTz, + '[GMT_OFFSET]' => Platform::getInstance()->get_local_timestamp("O"), + '[HOST]' => empty($host) ? 'unknown_host' : $host, + '[RANDOM]' => md5(microtime()), + '[VERSION]' => $version, + '[PLATFORM_NAME]' => $platformVars['name'], + '[PLATFORM_VERSION]' => $platformVars['version'], + '[SITENAME]' => $siteName, + ); + } + + return $variables; + } + + /** + * Expands the archive name variables in $source. For example "[DATE]-foobar" would be expanded to something + * like "141101-foobar". IMPORTANT: These variables do NOT include paths. + * + * @param string $source The input string, possibly containing variables in the form of [VARIABLE] + * + * @return string The expanded string + */ + public function replace_archive_name_variables($source) + { + $tagReplacements = $this->get_archive_name_variables(); + + return str_replace(array_keys($tagReplacements), array_values($tagReplacements), $source); + } + + /** + * Expand the platform-specific stock directories variables in the input string. For example "[SITEROOT]/foobar" + * would be expanded to something like "/var/www/html/mysite/foobar" + * + * @param string $folder The input string to expand + * @param bool $translate_win_dirs Should I translate Windows path separators to UNIX path separators? (default: false) + * @param bool $trim_trailing_slash Should I remove the trailing slash (default: false) + * + * @return string The expanded string + */ + function translateStockDirs($folder, $translate_win_dirs = false, $trim_trailing_slash = false) + { + static $stock_dirs; + + if (empty($stock_dirs)) + { + $stock_dirs = Platform::getInstance()->get_stock_directories(); + } + + $temp = $folder; + + foreach ($stock_dirs as $find => $replace) + { + $temp = str_replace($find, $replace, $temp); + } + + if ($translate_win_dirs) + { + $temp = $this->TranslateWinPath($temp); + } + + if ($trim_trailing_slash) + { + $temp = $this->TrimTrailingSlash($temp); + } + + return $temp; + } +} + diff --git a/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ListingParser.php b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ListingParser.php new file mode 100644 index 00000000..f9ca65c7 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/BackupEngine/Util/ListingParser.php @@ -0,0 +1,388 @@ +parseUnixListing($list, $quick); + + if (empty($res)) + { + $res = $this->parseMSDOSListing($list, $quick); + } + + return $res; + } + + /** + * Parse a UNIX-style directory listing. This is the format produced by ls -la on *NIX systems. + * + * You get a hash array with entries. Each entry has the following keys: + * name: the file / folder name. + * type: file, dir or link. + * target: link target (when type == link). + * user: owner user, numeric or text. IIS FTP fakes this with the literal string "owner". + * group: owner group, numeric or text. IIS FTP fakes this with the literal string "group". + * size: size in bytes; note that some Linux servers report non-zero sizes for directories. + * date: file creation date, most likely blatantly wrong; see below + * perms: permissions in decimal format. Cast with dec2oct to get the 4 digit permissions string, e.g. 1755 + * + * @param string $list The raw listing + * @param bool $quick True to only include name, type, size and link target for each file. + * + * @return array + */ + protected function parseUnixListing($list, $quick = false) + { + $ret = array(); + + $list = str_replace(array("\r\n", "\r", "\n\n"), array("\n", "\n", "\n"), $list); + $list = explode("\n", $list); + $list = array_map('rtrim', $list); + + foreach ($list as $v) + { + $vInfo = preg_split("/[\s]+/", $v, 9); + + if (count($vInfo) != 9) + { + continue; + } + + $entry = array( + 'name' => '', + 'type' => 'file', + 'target' => '', + 'user' => '0', + 'group' => '0', + 'size' => '0', + 'date' => '0', + 'perms' => '0', + ); + + if ($quick) + { + $entry = array( + 'name' => '', + 'type' => 'file', + 'size' => '0', + 'target' => '', + ); + } + + // ===== Parse permissions ===== + $permString = $vInfo[0]; + $permStringLen = strlen($permString); + $typeBit = '-'; + $userPerms = 'r--'; + $groupPerms = 'r--'; + $otherPerms = 'r--'; + + if ($permStringLen) + { + $typeBit = substr($permString, 0, 1); + } + + switch ($typeBit) + { + case "d": + $entry['type'] = 'dir'; + break; + + case "l": + $entry['type'] = 'link'; + break; + } + + // ===== Parse size ===== + $entry['size'] = $vInfo[4]; + + if (!$quick) + { + if ($permStringLen >= 4) + { + $userPerms = substr($permString, 1, 3); + } + + if ($permStringLen >= 7) + { + $groupPerms = substr($permString, 4, 3); + } + + if ($permStringLen >= 10) + { + $otherPerms = substr($permString, 7, 3); + } + + $bitPart = 0; + $permsPart = ''; + + list($thisPerms, $thisBit) = $this->textPermsDecode($userPerms); + $bitPart += 4 * $thisBit; // SetUID + $permsPart .= $thisPerms; + + list($thisPerms, $thisBit) = $this->textPermsDecode($groupPerms); + $bitPart += 2 * $thisBit; // SetGID + $permsPart .= $thisPerms; + + list($thisPerms, $thisBit) = $this->textPermsDecode($otherPerms); + $bitPart += $thisBit; // Sticky (restricted deletion) + $permsPart .= $thisPerms; + + $entry['perms'] = octdec($bitPart . $permsPart); + + // ===== Parse ownership ===== + $entry['user'] = $vInfo[2]; + $entry['group'] = $vInfo[3]; + + // ===== Parse date ===== + $dateString = $vInfo[6] . ' ' . $vInfo[5] . ' ' . $vInfo[7]; + $x = date_create($dateString); + $entry['date'] = ($x === false) ? 0 : $x->getTimestamp(); + } + + // ===== Parse name ===== + $name = $vInfo[8]; + + // Ubuntu (possibly others?) tacks a start when either suid/sgid bits is set + if (substr($name, -1) == '*') + { + $name = substr($name, 0, -1); + } + + // Link target parsing + if (strpos($name, '->') !== false) + { + list($name, $target) = explode('->', $name); + + $entry['target'] = trim($target); + } + + $entry['name'] = trim($name); + + // ===== Return the entry ===== + $ret[] = $entry; + } + + return $ret; + } + + /** + * Parse am MS-DOS-style directory listing. This is the format produced by dir on MS-DOS and Windows systems. + * + * You get a hash array with entries. Each entry has the following keys: + * name: the file / folder name. + * type: file, dir or link. + * target: link target (when type == link). + * user: owner user, numeric or text. IIS FTP fakes this with the literal string "owner". + * group: owner group, numeric or text. IIS FTP fakes this with the literal string "group". + * size: size in bytes; note that some Linux servers report non-zero sizes for directories. + * date: file creation date, most likely blatantly wrong; see below + * perms: permissions in decimal format. Cast with dec2oct to get the 4 digit permissions string, e.g. 1755 + * + * @param string $list The raw listing + * @param bool $quick True to only include name, type, size and link target for each file. + * + * @return array + */ + protected function parseMSDOSListing($list, $quick = false) + { + $ret = array(); + + $list = str_replace(array("\r\n", "\r", "\n\n"), array("\n", "\n", "\n"), $list); + $list = explode("\n", $list); + $list = array_map('rtrim', $list); + + foreach ($list as $v) + { + $vInfo = preg_split("/[\s]+/", $v, 5); + + if (count($vInfo) < 4) + { + continue; + } + + $entry = array( + 'name' => '', + 'type' => 'file', + 'target' => '', + 'user' => '0', + 'group' => '0', + 'size' => '0', + 'date' => '0', + 'perms' => '0', + ); + + if ($quick) + { + $entry = array( + 'name' => '', + 'type' => 'file', + 'size' => '0', + 'target' => '', + ); + } + + // The first two fields are date and time + $dateString = $vInfo[0] . ' ' . $vInfo[1]; + + // If position 2 is AM/PM append it and remove it from the list + if (in_array(strtoupper($vInfo[2]), array('AM', 'PM'))) + { + $dateString .= ' ' . $vInfo[2]; + + // This trick is required to remove the element and fix the indices for the rest of the parsing to work. + unset ($vInfo[2]); + $vInfo = array_merge($vInfo); + } + + if (!$quick) + { + $x = date_create($dateString); + $entry['date'] = ($x === false) ? 0 : $x->getTimestamp(); + } + + // The third field is either a special type indicator or the file size + switch (strtoupper($vInfo[2])) + { + // Regular directory + case '+ + {$strings['button']} + + + {$strings['infolbl']} + +
+ +HTML; + } + + echo '###' . $result . '###'; + + // Cut the execution short + $this->container->platform->closeApplication(); + } + + /** + * Applies the Download ID when the user is prompted about it in the Control Panel + */ + public function applydlid() + { + // CSRF prevention + $this->csrfProtection(); + + $msg = JText::_('COM_AKEEBA_CPANEL_ERR_INVALIDDOWNLOADID'); + $msgType = 'error'; + $dlid = $this->input->getString('dlid', ''); + + // If the Download ID seems legit let's apply it + if (preg_match('/^([0-9]{1,}:)?[0-9a-f]{32}$/i', $dlid)) + { + $msg = null; + $msgType = null; + + $this->container->params->set('update_dlid', $dlid); + $this->container->params->save(); + } + + // Redirect back to the control panel + $url = ''; + $returnurl = $this->input->get('returnurl', '', 'base64'); + + if (!empty($returnurl)) + { + $url = base64_decode($returnurl); + } + + if (empty($url)) + { + $url = JUri::base() . 'index.php?option=com_akeeba'; + } + + $this->setRedirect($url, $msg, $msgType); + } + + /** + * Reset the Secret Word for front-end and remote backup + * + * @return void + */ + public function resetSecretWord() + { + // CSRF prevention + $this->csrfProtection(); + + $newSecret = $this->container->platform->getSessionVar('newSecretWord', null, 'akeeba.cpanel'); + + if (empty($newSecret)) + { + $random = new \Akeeba\Engine\Util\RandomValue(); + $newSecret = $random->generateString(32); + $this->container->platform->setSessionVar('newSecretWord', $newSecret, 'akeeba.cpanel'); + } + + $this->container->params->set('frontend_secret_word', $newSecret); + $this->container->params->save(); + + $msg = JText::sprintf('COM_AKEEBA_CPANEL_MSG_FESECRETWORD_RESET', $newSecret); + + $url = 'index.php?option=com_akeeba'; + $this->setRedirect($url, $msg); + } + + public function reloadUpdateInformation() + { + $msg = null; + + /** @var Updates $model */ + $model = $this->container->factory->model('Updates')->tmpInstance(); + $model->getUpdates(true); + + $msg = JText::_('COM_AKEEBA_COMMON_UPDATE_INFORMATION_RELOADED'); + $url = 'index.php?option=com_akeeba'; + + $this->setRedirect($url, $msg); + } + + /** + * Resets the "updatedb" flag and forces the database updates + */ + public function forceUpdateDb() + { + // Reset the flag so the updates could take place + $this->container->params->set('updatedb', null); + $this->container->params->save(); + + /** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */ + $model = $this->getModel(); + + try + { + $model->checkAndFixDatabase(); + } + catch (\RuntimeException $e) + { + // This should never happen, since we reset the flag before execute the update, but you never know + } + + $this->setRedirect('index.php?option=com_akeeba'); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/DatabaseFilters.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/DatabaseFilters.php new file mode 100644 index 00000000..c907c6e2 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/DatabaseFilters.php @@ -0,0 +1,77 @@ +setPredefinedTaskList(['main', 'ajax']); + } + + /** + * Handles the "main" task, which displays a folder and file list + * + */ + public function main() + { + $task = $this->input->get('task', 'normal', 'cmd'); + + /** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */ + $model = $this->getModel(); + $model->setState('browse_task', $task); + + $this->display(false, false); + } + + /** + * AJAX proxy + */ + public function ajax() + { + // Parse the JSON data and reset the action query param to the resulting array + $action_json = $this->input->get('action', '', 'none', 2); + $action = json_decode($action_json, $this->decodeJsonAsArray); + + /** @var \Akeeba\Backup\Admin\Model\DatabaseFilters $model */ + $model = $this->getModel(); + + $model->setState('action', $action); + + $ret = $model->doAjax(); + + @ob_end_clean(); + echo '###' . json_encode($ret) . '###'; + flush(); + + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/FTPBrowser.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/FTPBrowser.php new file mode 100644 index 00000000..3e967e4b --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/FTPBrowser.php @@ -0,0 +1,49 @@ +getModel(); + + // Grab the data and push them to the model + $model->host = $this->input->get('host', '', 'string'); + $model->port = $this->input->get('port', 21, 'int'); + $model->passive = $this->input->get('passive', 1, 'int'); + $model->ssl = $this->input->get('ssl', 0, 'int'); + $model->username = $this->input->get('username', '', 'none', 2); + $model->password = $this->input->get('password', '', 'none', 2); + $model->directory = $this->input->get('directory', '', 'none', 2); + + if (empty($model->port)) + { + $model->port = $model->ssl ? 990 : 21; + } + + $ret = $model->doBrowse(); + + @ob_end_clean(); + echo '###' . json_encode($ret) . '###'; + flush(); + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/FileFilters.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/FileFilters.php new file mode 100644 index 00000000..fb045633 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/FileFilters.php @@ -0,0 +1,41 @@ + 'FileFilters', + 'viewName' => 'FileFilters' + ], $config); + + parent::__construct($container, $config); + } + +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Log.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Log.php new file mode 100644 index 00000000..672310c7 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Log.php @@ -0,0 +1,127 @@ +onCustomACLBeforeExecute($task); + + $profile_id = $this->input->getInt('profileid', null); + + if (!empty($profile_id) && is_numeric($profile_id) && ($profile_id > 0)) + { + $this->container->platform->setSessionVar('profile', $profile_id, 'akeeba'); + } + } + + /** + * Display the log page + * + * @return void + */ + public function onBeforeDefault() + { + $tag = $this->input->get('tag', null, 'cmd'); + $latest = $this->input->get('latest', false, 'int'); + + if (empty($tag)) + { + $tag = null; + } + + /** @var LogModel $model */ + $model = $this->getModel(); + + if ($latest) + { + $logFiles = $model->getLogFiles(); + $tag = array_shift($logFiles); + } + + $model->setState('tag', $tag); + + Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile()); + } + + /** + * Renders the contents of the log, used inside the IFRAME of the log page + * + * @return void + */ + public function iframe() + { + $tag = $this->input->get('tag', null, 'cmd'); + + if (empty($tag)) + { + $tag = null; + } + + /** @var LogModel $model */ + $model = $this->getModel(); + $model->setState('tag', $tag); + + Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile()); + + $this->display(); + } + + /** + * Download the log file as a text file + * + * @return void + */ + public function download() + { + Platform::getInstance()->load_configuration(Platform::getInstance()->get_active_profile()); + + $tag = $this->input->get('tag', null, 'cmd'); + + if (empty($tag)) + { + $tag = null; + } + + $asAttachment = $this->input->getBool('attachment', true); + + @ob_end_clean(); // In case some braindead plugin spits its own HTML + header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 + header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past + header("Content-Description: File Transfer"); + header('Content-Type: text/plain'); + + if ($asAttachment) + { + header('Content-Disposition: attachment; filename="Akeeba Backup Debug Log.txt"'); + } + + /** @var LogModel $model */ + $model = $this->getModel(); + $model->setState('tag', $tag); + $model->echoRawLog(); + + flush(); + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Manage.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Manage.php new file mode 100644 index 00000000..f03473c7 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Manage.php @@ -0,0 +1,385 @@ +getIDsFromRequest(); + $id = count($ids) ? array_pop($ids) : -1; + + $part = $this->input->get('part', -1, 'int'); + + if ($id <= 0) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error'); + + return; + } + + $stat = Platform::getInstance()->get_statistics($id); + $allFilenames = Factory::getStatistics()->get_all_filenames($stat); + + $filename = null; + + // Check single part files + if ((count($allFilenames) == 1) && ($part == -1)) + { + $filename = array_shift($allFilenames); + } + elseif ((count($allFilenames) > 0) && (count($allFilenames) > $part) && ($part >= 0)) + { + $filename = $allFilenames[ $part ]; + } + + if (is_null($filename) || empty($filename) || !@file_exists($filename)) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDDOWNLOAD'), 'error'); + + return; + } + + // For a certain unmentionable browser -- Thank you, Nooku, for the tip + if (function_exists('ini_get') && function_exists('ini_set')) + { + if (ini_get('zlib.output_compression')) + { + ini_set('zlib.output_compression', 'Off'); + } + } + + // Remove php's time limit + if (function_exists('ini_get') && function_exists('set_time_limit')) + { + if (!ini_get('safe_mode')) + { + @set_time_limit(0); + } + } + + $basename = @basename($filename); + $filesize = @filesize($filename); + $extension = strtolower(str_replace(".", "", strrchr($filename, "."))); + + while (@ob_end_clean()) + { + ; + } + @clearstatcache(); + // Send MIME headers + header('MIME-Version: 1.0'); + header('Content-Disposition: attachment; filename="' . $basename . '"'); + header('Content-Transfer-Encoding: binary'); + header('Accept-Ranges: bytes'); + + switch ($extension) + { + case 'zip': + // ZIP MIME type + header('Content-Type: application/zip'); + break; + + default: + // Generic binary data MIME type + header('Content-Type: application/octet-stream'); + break; + } + + // Notify of filesize, if this info is available + if ($filesize > 0) + { + header('Content-Length: ' . @filesize($filename)); + } + + // Disable caching + header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); + header("Expires: 0"); + header('Pragma: no-cache'); + + flush(); + + if (!$filesize) + { + // If the filesize is not reported, hope that readfile works + @readfile($filename); + + $this->container->platform->closeApplication(0); + } + + // If the filesize is reported, use 1M chunks for echoing the data to the browser + $blocksize = 1048576; //1M chunks + $handle = @fopen($filename, "r"); + + // Now we need to loop through the file and echo out chunks of file data + if ($handle !== false) + { + while (!@feof($handle)) + { + echo @fread($handle, $blocksize); + @ob_flush(); + flush(); + } + } + + if ($handle !== false) + { + @fclose($handle); + } + + $this->container->platform->closeApplication(0); + } + + /** + * Deletes one or more backup statistics records and their associated backup files + */ + public function remove() + { + // CSRF prevention + $this->csrfProtection(); + + $ids = $this->getIDsFromRequest(); + + if (empty($ids)) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error'); + + return; + } + + foreach ($ids as $id) + { + try + { + $msg = JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'); + $result = false; + + if ($id > 0) + { + /** @var Statistics $model */ + $model = $this->getModel(); + $model->setState('id', $id); + $result = $model->delete(); + } + + } + catch (\RuntimeException $e) + { + $result = false; + $msg = $e->getMessage(); + } + + if (!$result) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error'); + + return; + } + } + + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_MSG_DELETED')); + } + + /** + * Deletes backup files associated to one or several backup statistics records + */ + public function deletefiles() + { + // CSRF prevention + $this->csrfProtection(); + + $ids = $this->getIDsFromRequest(); + + if (empty($ids)) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error'); + + return; + } + + foreach ($ids as $id) + { + try + { + $msg = JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'); + $result = false; + + if ($id > 0) + { + /** @var Statistics $model */ + $model = $this->getModel(); + $model->setState('id', $id); + $result = $model->deleteFile(); + } + } + catch (\RuntimeException $e) + { + $result = false; + $msg = $e->getMessage(); + } + + if (!$result) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', $msg, 'error'); + + return; + } + } + + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_MSG_DELETEDFILE')); + } + + public function showcomment() + { + $ids = $this->getIDsFromRequest(); + + if (empty($ids)) + { + $ids = [0]; + } + + $id = array_pop($ids); + + if ($id <= 0) + { + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_BUADMIN_ERROR_INVALIDID'), 'error'); + } + + /** @var Statistics $model */ + $model = $this->getModel(); + $model->setState('id', $id); + + $this->layout = 'comment'; + $this->display(false); + } + + /** + * Save the comments back to a backup record + */ + public function save() + { + // CSRF prevention + $this->csrfProtection(); + + $id = $this->input->get('id', 0, 'int'); + $description = $this->input->get('description', '', 'string'); + $comment = $this->input->get('comment', null, 'string', 4); + + $statistic = Platform::getInstance()->get_statistics($id); + $statistic['description'] = $description; + $statistic['comment'] = $comment; + + $dummy = null; + $result = Platform::getInstance()->set_or_update_statistics($id, $statistic, $dummy); + + $message = JText::_('COM_AKEEBA_BUADMIN_LOG_SAVEDOK'); + $type = 'message'; + + if ($result === false) + { + $message = JText::_('COM_AKEEBA_BUADMIN_LOG_SAVEERROR'); + $type = 'error'; + } + + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage', $message, $type); + } + + public function restore() + { + // CSRF prevention + $this->csrfProtection(); + + $ids = $this->getIDsFromRequest(); + + if (empty($ids)) + { + $ids = [0]; + } + + $id = array_pop($ids); + + $url = JUri::base() . 'index.php?option=com_akeeba&view=Restore&id=' . $id; + $this->setRedirect($url); + } + + public function cancel() + { + // CSRF prevention + $this->csrfProtection(); + + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage'); + } + + public function hidemodal() + { + /** @var Statistics $model */ + $model = $this->getModel(); + $model->hideRestorationInstructionsModal(); + + $this->setRedirect(JUri::base() . 'index.php?option=com_akeeba&view=Manage'); + } + + /** + * Gets the list of IDs from the request data + * + * @return array + */ + protected function getIDsFromRequest() + { + // Get the ID or list of IDs from the request or the configuration + $cid = $this->input->get('cid', array(), 'array'); + $id = $this->input->getInt('id', 0); + + $ids = array(); + + if (is_array($cid) && !empty($cid)) + { + $ids = $cid; + } + elseif (!empty($id)) + { + $ids = array($id); + } + + return $ids; + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/CustomACL.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/CustomACL.php new file mode 100644 index 00000000..bf581e92 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/CustomACL.php @@ -0,0 +1,74 @@ +akeebaBackupACLCheck($this->view, $this->task); + } + + /** + * Checks if the currently logged in user has the required ACL privileges to access the current view. If not, a + * RuntimeException is thrown. + * + * @return void + */ + protected function akeebaBackupACLCheck($view, $task) + { + // Akeeba Backup-specific ACL checks. All views not listed here are limited by the akeeba.configure privilege. + $viewACLMap = [ + 'ControlPanel' => 'core.manage', + 'Backup' => 'akeeba.backup', + 'Manage' => 'core.manage', + 'Manage.download' => 'akeeba.download', + 'Manage.remove' => 'akeeba.download', + 'Manage.deletefiles' => 'akeeba.download', + 'Manage.showcomment' => 'akeeba.backup', + 'Manage.save' => 'akeeba.download', + 'Manage.restore' => 'akeeba.configure', + 'Manage.cancel' => 'akeeba.backup', + 'Upload' => 'akeeba.backup', + 'RemoteFiles' => 'akeeba.download', + 'Transfer' => 'akeeba.download', + ]; + + // Default + $privilege = 'akeeba.configure'; + + // Just the view was found + if (array_key_exists($view, $viewACLMap)) + { + $privilege = $viewACLMap[$view]; + } + + // The view AND task was found + if (array_key_exists($view . '.' . $task, $viewACLMap)) + { + $privilege = $viewACLMap[$view . '.' . $task]; + } + + // If an empty privilege is defined do not perform any ACL checks + if (empty($privilege)) + { + return; + } + + if (!$this->container->platform->authorise($privilege, 'com_akeeba')) + { + throw new RuntimeException(JText::_('JERROR_ALERTNOAUTHOR'), 403); + } + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/PredefinedTaskList.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/PredefinedTaskList.php new file mode 100644 index 00000000..462f8982 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Mixin/PredefinedTaskList.php @@ -0,0 +1,71 @@ +predefinedTaskList)) + { + $task = reset($this->predefinedTaskList); + } + + return parent::execute($task); + } + + /** + * Sets the predefined task list and registers the first task in the list as the Controller's default task + * + * @param array $taskList The task list to register + */ + public function setPredefinedTaskList(array $taskList) + { + // First, unregister all known tasks which are not in the taskList + $allTasks = $this->getTasks(); + + foreach ($allTasks as $task) + { + if (in_array($task, $taskList)) + { + continue; + } + + $this->unregisterTask($task); + } + + // Set the predefined task list + $this->predefinedTaskList = $taskList; + + // Set the default task + $this->registerDefaultTask(reset($this->predefinedTaskList)); + + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Profile.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Profile.php new file mode 100644 index 00000000..a565ae37 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Profile.php @@ -0,0 +1,16 @@ +csrfProtection(); + + if (!$this->container->platform->authorise('akeeba.configure', 'com_akeeba')) + { + throw new RuntimeException(JText::_('JERROR_ALERTNOAUTHOR'), 403); + } + + /** @var \Akeeba\Backup\Admin\Model\Profiles $model */ + $model = $this->getModel(); + + // Get some data from the request + $file = $this->input->files->get('importfile', array(), 'array'); + + if (!isset($file['name'])) + { + $this->setRedirect('index.php?option=com_akeeba&view=Profiles', JText::_('MSG_UPLOAD_INVALID_REQUEST'), 'error'); + + return; + } + + // Load the file data + $data = @file_get_contents($file['tmp_name']); + @unlink($file['tmp_name']); + + // JSON decode + $data = json_decode($data, true); + + // Import + $message = JText::_('COM_AKEEBA_PROFILES_MSG_IMPORT_COMPLETE'); + $messageType = null; + + try + { + $model->reset()->import($data); + } + catch (RuntimeException $e) + { + $message = $e->getMessage(); + $messageType = 'error'; + } + + // Redirect back to the main page + $this->setRedirect('index.php?option=com_akeeba&view=Profiles', $message, $messageType); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Restore.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Restore.php new file mode 100644 index 00000000..ea976c29 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Restore.php @@ -0,0 +1,127 @@ +setPredefinedTaskList(['main', 'start', 'ajax']); + } + + /** + * Main task, displays the main page + */ + public function main() + { + /** @var RestoreModel $model */ + $model = $this->getModel(); + + $message = $model->validateRequest(); + + if ($message !== true) + { + $this->setRedirect('index.php?option=com_akeeba&view=Manage', $message, 'error'); + $this->redirect(); + + return; + } + + $model->setState('restorationstep', 0); + + $this->display(false, false); + } + + /** + * Start the restoration + */ + public function start() + { + $this->csrfProtection(); + + /** @var RestoreModel $model */ + $model = $this->getModel(); + + $model->setState('restorationstep', 1); + $message = $model->validateRequest(); + + if ($message !== true) + { + $this->setRedirect('index.php?option=com_akeeba&view=Manage', $message, 'error'); + $this->redirect(); + + return; + } + + $model->setState('jps_key', $this->input->get('jps_key', '', 'cmd')); + $model->setState('procengine', $this->input->get('procengine', 'direct', 'cmd')); + $model->setState('ftp_host', $this->input->get('ftp_host', '', 'none', 2)); + $model->setState('ftp_port', $this->input->get('ftp_port', 21, 'int')); + $model->setState('ftp_user', $this->input->get('ftp_user', '', 'none', 2)); + $model->setState('ftp_pass', $this->input->get('ftp_pass', '', 'none', 2)); + $model->setState('ftp_root', $this->input->get('ftp_root', '', 'none', 2)); + $model->setState('tmp_path', $this->input->get('tmp_path', '', 'none', 2)); + $model->setState('ftp_ssl', $this->input->get('usessl', 'false', 'cmd') == 'true'); + $model->setState('ftp_pasv', $this->input->get('passive', 'true', 'cmd') == 'true'); + + $status = $model->createRestorationINI(); + + if ($status === false) + { + $this->setRedirect('index.php?option=com_akeeba&view=Manage', JText::_('COM_AKEEBA_RESTORE_ERROR_CANT_WRITE'), 'error'); + $this->redirect(); + + return; + } + + $this->display(false, false); + } + + /** + * Perform a step through AJAX + */ + public function ajax() + { + /** @var RestoreModel $model */ + $model = $this->getModel(); + + $ajax = $this->input->get('ajax', '', 'cmd'); + $model->setState('ajax', $ajax); + + $ret = $model->doAjax(); + + @ob_end_clean(); + echo '###' . json_encode($ret) . '###'; + flush(); + + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/SFTPBrowser.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/SFTPBrowser.php new file mode 100644 index 00000000..d6ba93fe --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/SFTPBrowser.php @@ -0,0 +1,49 @@ +getModel(); + + // Grab the data and push them to the model + $model->host = $this->input->get('host', '', 'string'); + $model->port = $this->input->get('port', 21, 'int'); + $model->username = $this->input->get('username', '', 'none', 2); + $model->password = $this->input->get('password', '', 'none', 2); + $model->privkey = $this->input->get('privkey', '', 'none', 2); + $model->pubkey = $this->input->get('pubkey', '', 'none', 2); + $model->directory = $this->input->get('directory', '', 'none', 2); + + if (empty($model->port)) + { + $model->port = 22; + } + + $ret = $model->doBrowse(); + + @ob_end_clean(); + echo '###' . json_encode($ret) . '###'; + flush(); + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Controller/Schedule.php b/deployed/akeeba/administrator/components/com_akeeba/Controller/Schedule.php new file mode 100644 index 00000000..181de1dc --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Controller/Schedule.php @@ -0,0 +1,22 @@ +setPredefinedTaskList(['wizard', 'checkUrl', 'applyConnection', 'initialiseUpload', 'upload', 'reset']); + } + + /** + * Reset the wizard + * + * @return void + */ + public function reset() + { + $this->container->platform->setSessionVar('transfer', null, 'akeeba'); + $this->container->platform->setSessionVar('transfer.url', null, 'akeeba'); + $this->container->platform->setSessionVar('transfer.url_status', null, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpsupport', null, 'akeeba'); + + /** @var \Akeeba\Backup\Admin\Model\Transfer $model */ + $model = $this->getModel(); + $model->resetUpload(); + + $this->setRedirect('index.php?option=com_akeeba&view=Transfer'); + } + + /** + * Cleans and checks the validity of the new site's URL + * + * @return void + */ + public function checkUrl() + { + $url = $this->input->get('url', '', 'raw'); + + /** @var \Akeeba\Backup\Admin\Model\Transfer $model */ + $model = $this->getModel(); + $model->savestate(true); + $result = $model->checkAndCleanUrl($url); + + $this->container->platform->setSessionVar('transfer.url', $result['url'], 'akeeba'); + $this->container->platform->setSessionVar('transfer.url_status', $result['status'], 'akeeba'); + + @ob_end_clean(); + echo '###' . json_encode($result) . '###'; + $this->container->platform->closeApplication(); + } + + /** + * Applies the FTP/SFTP connection information and makes some preliminary validation + * + * @return void + */ + public function applyConnection() + { + $result = (object) [ + 'status' => true, + 'message' => '', + 'ignorable' => false, + ]; + + // Get the parameters from the request + $transferOption = $this->input->getCmd('method', 'ftp'); + $force = $this->input->getInt('force', 0); + $ftpHost = $this->input->get('host', '', 'raw', 2); + $ftpPort = $this->input->getInt('port', null); + $ftpUsername = $this->input->get('username', '', 'raw', 2); + $ftpPassword = $this->input->get('password', '', 'raw', 2); + $ftpPubKey = $this->input->get('public', '', 'raw', 2); + $ftpPrivateKey = $this->input->get('private', '', 'raw', 2); + $ftpPassive = $this->input->getInt('passive', 1); + $ftpPassiveFix = $this->input->getInt('passive_fix', 1); + $ftpDirectory = $this->input->get('directory', '', 'raw', 2); + $chunkMode = $this->input->getCmd('chunkMode', 'chunked'); + $chunkSize = $this->input->getInt('chunkSize', '5242880'); + + // Fix the port if it's missing + if (empty($ftpPort)) + { + switch ($transferOption) + { + case 'ftp': + case 'ftpcurl': + $ftpPort = 21; + break; + + case 'ftps': + case 'ftpscurl': + $ftpPort = 990; + break; + + case 'sftp': + case 'sftpcurl': + $ftpPort = 22; + break; + } + } + + // Store everything in the session + $this->container->platform->setSessionVar('transfer.transferOption', $transferOption, 'akeeba'); + $this->container->platform->setSessionVar('transfer.force', $force, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpHost', $ftpHost, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPort', $ftpPort, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpUsername', $ftpUsername, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPassword', $ftpPassword, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPubKey', $ftpPubKey, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPrivateKey', $ftpPrivateKey, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpDirectory', $ftpDirectory, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPassive', $ftpPassive ? 1 : 0, 'akeeba'); + $this->container->platform->setSessionVar('transfer.ftpPassiveFix', $ftpPassiveFix ? 1 : 0, 'akeeba'); + $this->container->platform->setSessionVar('transfer.chunkMode', $chunkMode, 'akeeba'); + $this->container->platform->setSessionVar('transfer.uploadLimit', $chunkSize, 'akeeba'); + + /** @var \Akeeba\Backup\Admin\Model\Transfer $model */ + $model = $this->getModel(); + + try + { + $config = $model->getFtpConfig(); + $model->testConnection($config); + } + catch (TransferIgnorableError $e) + { + $result = (object) [ + 'status' => false, + 'ignorable' => true, + 'message' => $e->getMessage(), + ]; + } + catch (Exception $e) + { + $result = (object) [ + 'status' => false, + 'message' => $e->getMessage(), + 'ignorable' => false, + ]; + } + + @ob_end_clean(); + echo '###' . json_encode($result) . '###'; + $this->container->platform->closeApplication(); + } + + /** + * Initialise the upload: sends Kickstart and our add-on script to the remote server + * + * @return void + */ + public function initialiseUpload() + { + $result = (object)[ + 'status' => true, + 'message' => '', + ]; + + /** @var \Akeeba\Backup\Admin\Model\Transfer $model */ + $model = $this->getModel(); + + try + { + $config = $model->getFtpConfig(); + $model->initialiseUpload($config); + } + catch (Exception $e) + { + $result = (object)[ + 'status' => false, + 'message' => $e->getMessage(), + ]; + } + + @ob_end_clean(); + echo '###' . json_encode($result) . '###'; + $this->container->platform->closeApplication(); + } + + /** + * Perform an upload step. Pass start=1 to reset the upload and start over. + * + * @return void + */ + public function upload() + { + /** @var \Akeeba\Backup\Admin\Model\Transfer $model */ + $model = $this->getModel(); + + if ($this->input->getBool('start', false)) + { + $model->resetUpload(); + } + + try + { + $config = $model->getFtpConfig(); + $uploadResult = $model->uploadChunk($config); + } + catch (Exception $e) + { + $uploadResult = (object)[ + 'status' => false, + 'message' => $e->getMessage(), + 'totalSize' => 0, + 'doneSize' => 0, + 'done' => false + ]; + } + + $result = (object)$uploadResult; + + @ob_end_clean(); + echo '###' . json_encode($result) . '###'; + $this->container->platform->closeApplication(); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/Dispatcher.php b/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/Dispatcher.php new file mode 100644 index 00000000..9f24711a --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/Dispatcher.php @@ -0,0 +1,278 @@ +viewNameAliases = [ + 'buadmin' => 'Manage', + 'buadmins' => 'Manage', + 'config' => 'Configuration', + 'configs' => 'Configuration', + 'confwiz' => 'ConfigurationWizard', + 'confwizs' => 'ConfigurationWizard', + 'confwizes' => 'ConfigurationWizard', + 'cpanel' => 'ControlPanel', + 'cpanels' => 'ControlPanel', + 'dbef' => 'DatabaseFilters', + 'dbefs' => 'DatabaseFilters', + 'eff' => 'IncludeFolders', + 'effs' => 'IncludeFolders', + 'fsfilter' => 'FileFilters', + 'fsfilters' => 'FileFilters', + 'ftpbrowser' => 'FTPBrowser', + 'ftpbrowsers' => 'FTPBrowser', + 'sftpbrowser' => 'SFTPBrowser', + 'sftpbrowsers' => 'SFTPBrowser', + 'multidb' => 'MultipleDatabases', + 'multidbs' => 'MultipleDatabases', + 'regexdbfilter' => 'RegExDatabaseFilters', + 'regexdbfilters' => 'RegExDatabaseFilters', + 'regexfsfilter' => 'RegExFileFilters', + 'regexfsfilters' => 'RegExFileFilters', + 'remotefile' => 'RemoteFiles', + 'remotefiles' => 'RemoteFiles', + 's3import' => 'S3Import', + 's3imports' => 'S3Import', + ]; + + } + + /** + * Executes before dispatching the request to the appropriate controller + */ + public function onBeforeDispatch() + { + $this->onBeforeDispatchViewAliases(); + + // Load the FOF language + $lang = $this->container->platform->getLanguage(); + $lang->load('lib_fof30', JPATH_ADMINISTRATOR, 'en-GB', true, true); + $lang->load('lib_fof30', JPATH_ADMINISTRATOR, null, true, false); + + // Necessary for routing the Alice view + $this->container->inflector->addWord('Alice', 'Alices'); + + // Does the user have adequate permissions to access our component? + if (!$this->container->platform->authorise('core.manage', 'com_akeeba')) + { + throw new \RuntimeException(\JText::_('JERROR_ALERTNOAUTHOR'), 404); + } + + // FEF Renderer options. Used to load the common CSS file. + $this->container->renderer->setOptions([ + 'custom_css' => 'media://com_akeeba/css/akeebaui.min.css' + ]); + + // Load Akeeba Engine + $this->loadAkeebaEngine(); + + // Load the Akeeba Engine configuration + try + { + $this->loadAkeebaEngineConfiguration(); + } + catch (\Exception $e) + { + // Maybe the tables are not installed? + /** @var ControlPanel $cPanelModel */ + $cPanelModel = $this->container->factory->model('ControlPanel')->tmpInstance(); + + try + { + $cPanelModel->checkAndFixDatabase(); + } + catch (\RuntimeException $e) + { + // The update is stuck. We will display a warning in the Control Panel + } + + $msg = \JText::_('COM_AKEEBA_CONTROLPANEL_MSG_REBUILTTABLES'); + $this->container->platform->redirect('index.php', 307, $msg, 'warning'); + } + + // Prevents the "SQLSTATE[HY000]: General error: 2014" due to resource sharing with Akeeba Engine + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // !!!!! WARNING: ALWAYS GO THROUGH JFactory; DO NOT GO THROUGH $this->container->db !!!!! + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + $jDbo = JFactory::getDbo(); + + if ($jDbo->name == 'pdomysql') + { + @JFactory::getDbo()->disconnect(); + } + + // Load the utils helper library + Platform::getInstance()->load_version_defines(); + Platform::getInstance()->apply_quirk_definitions(); + + // Make sure the front-end backup Secret Word is stored encrypted + $params = $this->container->params; + SecretWord::enforceEncryption($params, 'frontend_secret_word'); + + // Make sure we have a version loaded + @include_once($this->container->backEndPath . '/version.php'); + + if (!defined('AKEEBA_VERSION')) + { + define('AKEEBA_VERSION', 'dev'); + define('AKEEBA_DATE', date('Y-m-d')); + } + + // Create a media file versioning tag + $this->container->mediaVersion = md5(AKEEBA_VERSION . AKEEBA_DATE); + + // Perform certain functionality only in HTML tasks + $format = $this->input->getCmd('format', 'html'); + + if ($format == 'html') + { + // Load common Javascript files. NOTE: CSS and anything style-related is loaded by the FEF Renderer class. + $this->loadCommonJavascript(); + + // Perform common maintenance tasks + $this->autoMaintenance(); + } + + // Set the linkbar style to Classic (Bootstrap tabs). The sidebar takes too much space and requires adding + // manual HTML to render it... + $this->container->renderer->setOption('linkbar_style', 'classic'); + } + + /** + * Loads the Javascript files which are common across many views of the component. + * + * @return void + */ + private function loadCommonJavascript() + { + \JHtml::_('jquery.framework'); + + $mediaVersion = $this->container->mediaVersion; + + // Do not mode: everything depends on UserInterfaceCommon + $this->container->template->addJS('media://com_akeeba/js/UserInterfaceCommon.min.js', false, false, $mediaVersion); + // Do not move: System depends on Modal + $this->container->template->addJS('media://com_akeeba/js/Modal.min.js', false, false, $mediaVersion); + // Do not move: System depends on Ajax + $this->container->template->addJS('media://com_akeeba/js/Ajax.min.js', false, false, $mediaVersion); + // Do not move: System depends on Ajax + $this->container->template->addJS('media://com_akeeba/js/System.min.js', false, false, $mediaVersion); + // Do not move: Tooltip depends on System + $this->container->template->addJS('media://com_akeeba/js/Tooltip.min.js', false, false, $mediaVersion); + // Always add last (it's the least important) + $this->container->template->addJS('media://com_akeeba/js/piecon.min.js', false, false, $mediaVersion); + } + + /** + * Perform common maintenance tasks + * + * @return void + */ + private function autoMaintenance() + { + /** @var \Akeeba\Backup\Admin\Model\ControlPanel $model */ + $model = $this->container->factory->model('ControlPanel')->tmpInstance(); + + // Update the db structure if necessary (once per session at most) + $lastVersion = $this->container->platform->getSessionVar('magicParamsUpdateVersion', null, 'com_akeeba'); + + if ($lastVersion != AKEEBA_VERSION) + { + try + { + $model->checkAndFixDatabase(); + $this->container->platform->setSessionVar('magicParamsUpdateVersion', AKEEBA_VERSION, 'com_akeeba'); + } + catch (\RuntimeException $e) + { + // The update is stuck. We will display a warning in the Control Panel + } + } + + // Update magic parameters if necessary + $model->updateMagicParameters(); + } + + public function onAfterDispatch() + { + // See the after_render.php file for an explanation. TL;DR: CloudFlare Rocket Loader is a broken pile of crap. + if ($this->input->get('format', 'html') != 'html') + { + return; + } + + if (!function_exists('akeebaBackupOnAfterRenderToFixBrokenCloudFlareRocketLoader')) + { + require_once __DIR__ . '/after_render.php'; + } + + JFactory::getApplication()->registerEvent('onAfterRender', 'akeebaBackupOnAfterRenderToFixBrokenCloudFlareRocketLoader'); + } + + public function loadAkeebaEngine() + { + // Necessary defines for Akeeba Engine + if (!defined('AKEEBAENGINE')) + { + define('AKEEBAENGINE', 1); + define('AKEEBAROOT', $this->container->backEndPath . '/BackupEngine'); + define('ALICEROOT', $this->container->backEndPath . '/AliceEngine'); + } + + // Make sure we have a profile set throughout the component's lifetime + $profile_id = $this->container->platform->getSessionVar('profile', null, 'akeeba'); + + if (is_null($profile_id)) + { + $this->container->platform->setSessionVar('profile', 1, 'akeeba'); + } + + // Load Akeeba Engine + $basePath = $this->container->backEndPath; + require_once $basePath . '/BackupEngine/Factory.php'; + + // Load ALICE (Pro version only) + if (@file_exists($basePath . '/AliceEngine/factory.php')) + { + require_once $basePath . '/AliceEngine/factory.php'; + } + } + + public function loadAkeebaEngineConfiguration() + { + Platform::addPlatform('joomla3x', $this->container->backEndPath . '/BackupPlatform/Joomla3x'); + $akeebaEngineConfig = Factory::getConfiguration(); + Platform::getInstance()->load_configuration(); + unset($akeebaEngineConfig); + } +} diff --git a/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/after_render.php b/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/after_render.php new file mode 100644 index 00000000..2d2241e5 --- /dev/null +++ b/deployed/akeeba/administrator/components/com_akeeba/Dispatcher/after_render.php @@ -0,0 +1,74 @@ +getBody(); + + // Find the+ +
+ warningsCell; ?> + ++ +
+| + + + + + | +
| + escape($subfolder); ?> + | +
| + + + + + | +
+ format(JText::_('DATE_FORMAT_LC1')), + $akeebaCommonDateObsolescence->format(JText::_('DATE_FORMAT_LC1')), + '5.6' + ); ?> +
++ getContainer()->db->getPrefix(), + 'index.php?option=com_akeeba&view=ControlPanel&task=forceUpdateDb' + )?> +
++ +
+ +| + | + |
+ logSize / (1024 * 1024), 2))?> +
+ + + +'.JText::_('COM_AKEEBA_LOG_ERROR_LOGFILENOTEXISTS').'
'; + return; +} +else +{ + // Allright, let's load and render it + $fp = fopen( $logName, "rt" ); + if ($fp === FALSE) + { + // Oops! The log isn't readable?! + echo ''.JText::_('COM_AKEEBA_LOG_ERROR_UNREADABLE').'
'; + return; + } + + while( !feof($fp) ) + { + $line = fgets( $fp ); + if(!$line) return; + $exploded = explode( "|", $line, 3 ); + unset( $line ); + switch( trim($exploded[0]) ) + { + case "ERROR": + $fmtString = "["; + break; + case "WARNING": + $fmtString = "["; + break; + case "INFO": + $fmtString = "["; + break; + case "DEBUG": + $fmtString = "["; + break; + default: + $fmtString = "["; + break; + } + $fmtString .= $exploded[1] . "] " . htmlspecialchars($exploded[2]) . "+ +
+| + | + | + |
|---|
+ +
+| + + | ++ + | +
| + + | ++ + | +
| + + | ++ + | +
+ ; +
+ ++ +
++ + + + +
+
+
+
+ escape($this->checkinfo->info->php_path); ?>
+ escape($this->checkinfo->cli->path); ?>
+
+
+
+ + croninfo->info->php_path); ?> +
+ + + ++
++ + + + +
++ +
++ + + + +
++ +
++ +
+
+
+
+ escape($this->checkinfo->info->php_path); ?>
+ escape($this->checkinfo->altcli->path); ?>
+
+
+
+ + checkinfo->info->php_path); ?> +
+ + + + ++
++ + + + +
++ +
++ + + + +
++ +
++ +
++ +
+ +| + | + + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ escape($this->checkinfo->info->root_url); ?>/escape($this->checkinfo->frontend->path); ?> + + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ + | +
| + | + + | +
+
+
+ wget --max-redirect=10000 "escape($this->checkinfo->info->root_url); ?>/escape($this->checkinfo->frontend->path); ?>" -O - 1>/dev/null 2>/dev/null
+
+
+
+
+ curl -L --max-redirs 1000 -v "escape($this->checkinfo->info->root_url); ?>/escape($this->checkinfo->frontend->path); ?>" 1>/dev/null 2>/dev/null
+
+
+ +
++<?php + $curl_handle=curl_init(); + curl_setopt($curl_handle, CURLOPT_URL, 'escape($this->checkinfo->info->root_url); ?>/escape($this->checkinfo->frontend->path); ?> ?>'); + curl_setopt($curl_handle,CURLOPT_FOLLOWLOCATION, TRUE); + curl_setopt($curl_handle,CURLOPT_MAXREDIRS, 10000); + curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER, 1); + $buffer = curl_exec($curl_handle); + curl_close($curl_handle); + if (empty($buffer)) + echo "Sorry, the check didn't work."; + else + echo $buffer; +?> ++ + + +
+
+
+ escape($this->checkinfo->info->root_url); ?>/escape($this->checkinfo->frontend->path); ?>
+
+
+ +
+ ++ +
++ + + + +
+
+
+
+ escape($this->croninfo->info->php_path); ?>
+ escape($this->croninfo->cli->path); ?>
+
+
+ + croninfo->info->php_path); ?> +
+ + + ++
++ + + + +
++ +
+ + + + ++ +
++ +
+
+
+
+ escape($this->croninfo->info->php_path); ?>
+ escape($this->croninfo->altcli->path); ?>
+
+
+ + croninfo->info->php_path); ?> +
+ + + + + ++
++ + + + +
++ +
++ + + + +
++ +
++ +
++ +
+ ++ +
+ +| + | + + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ escape($this->croninfo->info->root_url); ?>/escape($this->croninfo->frontend->path); ?> + + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ + | +
| + + | ++ + | +
| + | + + | +
+
+
+ wget --max-redirect=10000 "escape($this->croninfo->info->root_url); ?>/escape($this->croninfo->frontend->path); ?>" -O - 1>/dev/null 2>/dev/null
+
+
+
+
+ curl -L --max-redirs 1000 -v "escape($this->croninfo->info->root_url); ?>/escape($this->croninfo->frontend->path); ?>" 1>/dev/null 2>/dev/null
+
+
+ +
++<?php + $curl_handle=curl_init(); + curl_setopt($curl_handle, CURLOPT_URL, 'escape($this->croninfo->info->root_url); ?>/escape($this->croninfo->frontend->path); ?>'); + curl_setopt($curl_handle,CURLOPT_FOLLOWLOCATION, TRUE); + curl_setopt($curl_handle,CURLOPT_MAXREDIRS, 10000); + curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER, 1); + $buffer = curl_exec($curl_handle); + curl_close($curl_handle); + if (empty($buffer)) + echo "Sorry, the backup didn't work."; + else + echo $buffer; +?> ++ + + +
+
+
+ croninfo->info->root_url ?>/croninfo->frontend->path ?>
+
+
|
+
+
+
+
+ + + latestBackup)): ?> + + + lastBackupDate); ?> + + + |
+ + latestBackup)): ?> + + + + + | +
|
+
+ spaceRequired['string']); ?>
+
+ + + + + |
+ + | +
+ Your host is using a broken, abandoned PHP extension which doesn't allow modern software to run. Ask your + host to disable eAccelerator before trying to run this component. +
++ Your host is using eAccelerator, a code cache which is broken (example) + and abandoned in 2012. In fact, there is an open + issue on its project site about it being dead. Simple facts: +
++ Please let your host know that they are using outdated software on their site and demand that they deactivate + it at once. Once eAccelerator is deactivated this message will go away and Akeeba Backup will work just fine. +
++ Please note that PHP 5.5 and later come with Zend Opcache built in. It is a much better solution which is + actively supported by the people who make PHP. Ask your server to use it. +
++ Please contact the administrator of the site and let them know of this error and what you were doing when this happened. +
+ + +
+ File getFile()) ?>
+ Line getLine() ?>
+
+ Would you like us to help you faster? +
++ Save this page as PDF or HTML. When filing a support ticket please attach that PDF or HTML file. +
++ Why do we need all that information? This information is an x-ray of your site at the time the error + occurred. It lets us reproduce the issue or, if it's not a bug in our software, help you pinpoint the external reason which + led to it. +
++ What about privacy? + Attachments are private in our ticket system: only you and us can see them, even if you file a public ticket, and + they are automatically deleted after a month. +
+ + ++ + + The content below this point is for developers and power users. + +
+
+ Exception type:
+
getTraceAsString() ?>+ +
| Operating System (reported by PHP) | ++ |
| PHP version (as reported by your server) | ++ |
| PHP Built On | ++ |
| PHP SAPI | ++ |
| Server identity | ++ |
| Browser identity | ++ |
| Joomla! version | ++ |
| Database driver name | +getName() ?> | +
| Database driver type | +getServerType() ?> | +
| Database server version | +getVersion() ?> | +
| Database collation | +getCollation()?> | +
| Database connection collation | +getConnectionCollation()?> | +
| PHP Memory limit | ++ |
| Peak Memory usage | ++ |
| PHP Timeout (seconds) | ++ |
getSession()->getData()->toArray());
+ }
+ else
+ {
+ print_r($app->getSession()->all());
+ }
+?>
+
+getDirectory();
+ $extensions = $model->getExtensions();
+ $phpSettings = $model->getPhpSettings();
+ $hasPHPInfo = $model->phpinfoEnabled();
+}
+catch (\Exception $e)
+{
+ /**
+ * If you are here, Joomla! had an unhandled exception inside its own code, typically decoding JSON. The only thing
+ * you can do is die. If you try returning the unhandled exception will bubble up Joomla's error handler and you're
+ * stuck with a misleading error. Sorry :(
+ */
+ die;
+}
+?>
+
+| + | + |
| + | + + + + | +
| + | + | + | + | + |
| + + + | ++ + Writeable + + Unwriteable + + | +
+ FEF is the name of our custom CSS framework. It's responsible for rendering the interface of our Joomla! + extensions. It is automatically installed when you install our extensions on your site. +
++ FEF can be missing from your site either because Joomla failed to install it or because you, another Super User, + or another extension mistakenly uninstalled it. +
++ If it's missing we cannot display the interface to this component. That's why you see this message. +
++ You do not have to worry about adding bloat to your site. FEF is very small. It will also be automatically + uninstalled when you uninstall all components which depend on it. +
+
+ FEF is installed in the media/fef folder under your site's root. It appears in Joomla's Extensions,
+ Manage page as file_fef. Please do not remove it from your site.
+
+ FOF is a Joomla component framework. It's the low level code which sits between our Joomla! extensions and + Joomla! itself. It is automatically installed when you install our extensions on your site. +
++ FOF can be missing from your site either because Joomla failed to install it or because you, another Super User, + or another extension mistakenly uninstalled it. +
++ If it's missing, our components cannot talk to Joomla — or vice versa. Because of that they can not run. + That's why you see this message. +
++ You do not have to worry about adding bloat to your site. FOF is very small. It will also be automatically + uninstalled when you uninstall all components which depend on it. +
+
+ FOF is installed in the /fof30 folder on your
+ server. It appears in Joomla's Extensions, Manage page as FOF30. Please do not remove it from your
+ site.
+
+ Joomla includes an old, obsolete version of FOF - version 2.x. It is installed
+ in the fof folder on your server. It appears in
+ Joomla's Extensions, Manage page as FOF. Please do not remove it from your site; Joomla needs it
+ to function properly.
+
+ We discontinued FOF 2.x in 2015 — that's years ago. Ever since, we replaced it + with FOF 3.x. The two versions are incompatible with each other but both are required; FOF 2.x for Joomla! + itself and FOF 3.x for our extensions. That's why you see both. You must not remove either of them or something + will break! +
+ ++ HHVM was Facebook's attempt at modernizing the PHP 5.x language and making it faster. Unfortunately it's also incompatible with PHP proper. + PHP 7 has solved all these issues. It's fast, modern and fully compatible with our software. + Please switch to PHP 7. If you are unsure how to do that, contact your host or the person responsible for maintaining your server. + They are the only people who can help you configure your server. +
++ Kindly note that HHVM is not -and has never been- a supported execution environment for our software. + As a result, if you see this message on your site you are unfortunately ineligible for support and / or filing bug reports. + Please switch to PHP 7. If your problem persists after that we can help you / accept your bug report. Thank you for your understanding. +
++ Akeeba Backup requires PHP or any later version to work. +
++ We strongly urge you to update to PHP or later. If you are + unsure how to do this, please ask your host. +
++ Version numbers don't make sense? +
+ ++ Your version of PHP, , has reached the end + of its life on format(JText::_('DATE_FORMAT_LC1')) ?>. You are + strongly urged to upgrade to a current version, as using older versions may expose you to security + vulnerabilities and bugs that have been fixed in more recent versions of PHP. +
+images/cat.png or shell pattern such as images/*.png on each line. Only files matching this list will be written to disk. Leave empty to extract everything (default).',
+ );
+
+ /**
+ * The array holding the translation keys
+ *
+ * @var array
+ */
+ private $strings;
+
+ /**
+ * The currently detected language (ISO code)
+ *
+ * @var string
+ */
+ private $language;
+
+ /*
+ * Initializes the translation engine
+ * @return AKText
+ */
+ public function __construct()
+ {
+ // Start with the default translation
+ $this->strings = $this->default_translation;
+ // Try loading the translation file in English, if it exists
+ $this->loadTranslation('en-GB');
+ // Try loading the translation file in the browser's preferred language, if it exists
+ $this->getBrowserLanguage();
+ if (!is_null($this->language))
+ {
+ $this->loadTranslation();
+ }
+ }
+
+ private function loadTranslation($lang = null)
+ {
+ if (defined('KSLANGDIR'))
+ {
+ $dirname = KSLANGDIR;
+ }
+ else
+ {
+ $dirname = KSROOTDIR;
+ }
+ $basename = basename(__FILE__, '.php') . '.ini';
+ if (empty($lang))
+ {
+ $lang = $this->language;
+ }
+
+ $translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
+ if (!@file_exists($translationFilename) && ($basename != 'kickstart.ini'))
+ {
+ $basename = 'kickstart.ini';
+ $translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename;
+ }
+ if (!@file_exists($translationFilename))
+ {
+ return;
+ }
+ $temp = self::parse_ini_file($translationFilename, false);
+
+ if (!is_array($this->strings))
+ {
+ $this->strings = array();
+ }
+ if (empty($temp))
+ {
+ $this->strings = array_merge($this->default_translation, $this->strings);
+ }
+ else
+ {
+ $this->strings = array_merge($this->strings, $temp);
+ }
+ }
+
+ /**
+ * A PHP based INI file parser.
+ *
+ * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
+ * the parse_ini_file page on http://gr.php.net/parse_ini_file
+ *
+ * @param string $file Filename to process
+ * @param bool $process_sections True to also process INI sections
+ *
+ * @return array An associative array of sections, keys and values
+ * @access private
+ */
+ public static function parse_ini_file($file, $process_sections = false, $raw_data = false)
+ {
+ $process_sections = ($process_sections !== true) ? false : true;
+
+ if (!$raw_data)
+ {
+ $ini = @file($file);
+ }
+ else
+ {
+ $ini = $file;
+ }
+ if (count($ini) == 0)
+ {
+ return array();
+ }
+
+ $sections = array();
+ $values = array();
+ $result = array();
+ $globals = array();
+ $i = 0;
+ if (!empty($ini))
+ {
+ foreach ($ini as $line)
+ {
+ $line = trim($line);
+ $line = str_replace("\t", " ", $line);
+
+ // Comments
+ if (!preg_match('/^[a-zA-Z0-9[]/', $line))
+ {
+ continue;
+ }
+
+ // Sections
+ if ($line{0} == '[')
+ {
+ $tmp = explode(']', $line);
+ $sections[] = trim(substr($tmp[0], 1));
+ $i++;
+ continue;
+ }
+
+ // Key-value pair
+ list($key, $value) = explode('=', $line, 2);
+ $key = trim($key);
+ $value = trim($value);
+ if (strstr($value, ";"))
+ {
+ $tmp = explode(';', $value);
+ if (count($tmp) == 2)
+ {
+ if ((($value{0} != '"') && ($value{0} != "'")) ||
+ preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
+ preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value)
+ )
+ {
+ $value = $tmp[0];
+ }
+ }
+ else
+ {
+ if ($value{0} == '"')
+ {
+ $value = preg_replace('/^"(.*)".*/', '$1', $value);
+ }
+ elseif ($value{0} == "'")
+ {
+ $value = preg_replace("/^'(.*)'.*/", '$1', $value);
+ }
+ else
+ {
+ $value = $tmp[0];
+ }
+ }
+ }
+ $value = trim($value);
+ $value = trim($value, "'\"");
+
+ if ($i == 0)
+ {
+ if (substr($line, -1, 2) == '[]')
+ {
+ $globals[$key][] = $value;
+ }
+ else
+ {
+ $globals[$key] = $value;
+ }
+ }
+ else
+ {
+ if (substr($line, -1, 2) == '[]')
+ {
+ $values[$i - 1][$key][] = $value;
+ }
+ else
+ {
+ $values[$i - 1][$key] = $value;
+ }
+ }
+ }
+ }
+
+ for ($j = 0; $j < $i; $j++)
+ {
+ if ($process_sections === true)
+ {
+ $result[$sections[$j]] = $values[$j];
+ }
+ else
+ {
+ $result[] = $values[$j];
+ }
+ }
+
+ return $result + $globals;
+ }
+
+ public function getBrowserLanguage()
+ {
+ // Detection code from Full Operating system language detection, by Harald Hope
+ // Retrieved from http://techpatterns.com/downloads/php_language_detection.php
+ $user_languages = array();
+ //check to see if language is set
+ if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]))
+ {
+ $languages = strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
+ // $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3';
+ // need to remove spaces from strings to avoid error
+ $languages = str_replace(' ', '', $languages);
+ $languages = explode(",", $languages);
+
+ foreach ($languages as $language_list)
+ {
+ // pull out the language, place languages into array of full and primary
+ // string structure:
+ $temp_array = array();
+ // slice out the part before ; on first step, the part before - on second, place into array
+ $temp_array[0] = substr($language_list, 0, strcspn($language_list, ';'));//full language
+ $temp_array[1] = substr($language_list, 0, 2);// cut out primary language
+ if ((strlen($temp_array[0]) == 5) && ((substr($temp_array[0], 2, 1) == '-') || (substr($temp_array[0], 2, 1) == '_')))
+ {
+ $langLocation = strtoupper(substr($temp_array[0], 3, 2));
+ $temp_array[0] = $temp_array[1] . '-' . $langLocation;
+ }
+ //place this array into main $user_languages language array
+ $user_languages[] = $temp_array;
+ }
+ }
+ else// if no languages found
+ {
+ $user_languages[0] = array('', ''); //return blank array.
+ }
+
+ $this->language = null;
+ $basename = basename(__FILE__, '.php') . '.ini';
+
+ // Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist.
+ if (class_exists('AKUtilsLister'))
+ {
+ $fs = new AKUtilsLister();
+ $iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
+ if (empty($iniFiles) && ($basename != 'kickstart.ini'))
+ {
+ $basename = 'kickstart.ini';
+ $iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename);
+ }
+ }
+ else
+ {
+ $iniFiles = null;
+ }
+
+ if (is_array($iniFiles))
+ {
+ foreach ($user_languages as $languageStruct)
+ {
+ if (is_null($this->language))
+ {
+ // Get files matching the main lang part
+ $iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1] . '-??.' . $basename);
+ if (count($iniFiles) > 0)
+ {
+ $filename = $iniFiles[0];
+ $filename = substr($filename, strlen(KSROOTDIR) + 1);
+ $this->language = substr($filename, 0, 5);
+ }
+ else
+ {
+ $this->language = null;
+ }
+ }
+ }
+ }
+
+ if (is_null($this->language))
+ {
+ // Try to find a full language match
+ foreach ($user_languages as $languageStruct)
+ {
+ if (@file_exists($languageStruct[0] . '.' . $basename) && is_null($this->language))
+ {
+ $this->language = $languageStruct[0];
+ }
+ else
+ {
+
+ }
+ }
+ }
+ else
+ {
+ // Do we have an exact match?
+ foreach ($user_languages as $languageStruct)
+ {
+ if (substr($this->language, 0, strlen($languageStruct[1])) == $languageStruct[1])
+ {
+ if (file_exists($languageStruct[0] . '.' . $basename))
+ {
+ $this->language = $languageStruct[0];
+ }
+ }
+ }
+ }
+
+ // Now, scan for full language based on the partial match
+
+ }
+
+ public static function sprintf($key)
+ {
+ $text = self::getInstance();
+ $args = func_get_args();
+ if (count($args) > 0)
+ {
+ $args[0] = $text->_($args[0]);
+
+ return @call_user_func_array('sprintf', $args);
+ }
+
+ return '';
+ }
+
+ /**
+ * Singleton pattern for Language
+ *
+ * @return AKText The global AKText instance
+ */
+ public static function &getInstance()
+ {
+ static $instance;
+
+ if (!is_object($instance))
+ {
+ $instance = new AKText();
+ }
+
+ return $instance;
+ }
+
+ public static function _($string)
+ {
+ $text = self::getInstance();
+
+ $key = strtoupper($string);
+ $key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key;
+
+ if (isset ($text->strings[$key]))
+ {
+ $string = $text->strings[$key];
+ }
+ else
+ {
+ if (defined($string))
+ {
+ $string = constant($string);
+ }
+ }
+
+ return $string;
+ }
+
+ public function dumpLanguage()
+ {
+ $out = '';
+ foreach ($this->strings as $key => $value)
+ {
+ $out .= "$key=$value\n";
+ }
+
+ return $out;
+ }
+
+ public function asJavascript()
+ {
+ $out = '';
+ foreach ($this->strings as $key => $value)
+ {
+ $key = addcslashes($key, '\\\'"');
+ $value = addcslashes($value, '\\\'"');
+ if (!empty($out))
+ {
+ $out .= ",\n";
+ }
+ $out .= "'$key':\t'$value'";
+ }
+
+ return $out;
+ }
+
+ public function resetTranslation()
+ {
+ $this->strings = $this->default_translation;
+ }
+
+ public function addDefaultLanguageStrings($stringList = array())
+ {
+ if (!is_array($stringList))
+ {
+ return;
+ }
+ if (empty($stringList))
+ {
+ return;
+ }
+
+ $this->strings = array_merge($stringList, $this->strings);
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * The Akeeba Kickstart Factory class
+ *
+ * This class is reponssible for instantiating all Akeeba Kickstart classes
+ */
+class AKFactory
+{
+ /** @var array A list of instantiated objects */
+ private $objectlist = array();
+
+ /** @var array Simple hash data storage */
+ private $varlist = array();
+
+ /** @var self Static instance */
+ private static $instance = null;
+
+ /**
+ * AKFactory constructor.
+ *
+ * This is a private constructor makes sure we can't instantiate the class unless we go through the static
+ * getInstance singleton method. This is different than making the class abstract (preventing any kind of object
+ * instantiation).
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Gets a serialized snapshot of the Factory for safekeeping (hibernate)
+ *
+ * @return string The serialized snapshot of the Factory
+ */
+ public static function serialize()
+ {
+ $engine = self::getUnarchiver();
+ $engine->shutdown();
+ $serialized = serialize(self::getInstance());
+
+ if (function_exists('base64_encode') && function_exists('base64_decode'))
+ {
+ $serialized = base64_encode($serialized);
+ }
+
+ return $serialized;
+ }
+
+ /**
+ * Gets the unarchiver engine
+ *
+ * @return AKAbstractUnarchiver
+ */
+ public static function &getUnarchiver($configOverride = null)
+ {
+ static $class_name;
+
+ if (!empty($configOverride) && isset($configOverride['reset']) && $configOverride['reset'])
+ {
+ $class_name = null;
+ }
+
+ if (empty($class_name))
+ {
+ $filetype = self::get('kickstart.setup.filetype', null);
+
+ if (empty($filetype))
+ {
+ $filename = self::get('kickstart.setup.sourcefile', null);
+ $basename = basename($filename);
+ $baseextension = strtoupper(substr($basename, -3));
+
+ switch ($baseextension)
+ {
+ case 'JPA':
+ $filetype = 'JPA';
+ break;
+
+ case 'JPS':
+ $filetype = 'JPS';
+ break;
+
+ case 'ZIP':
+ $filetype = 'ZIP';
+ break;
+
+ default:
+ die('Invalid archive type or extension in file ' . $filename);
+ break;
+ }
+ }
+
+ $class_name = 'AKUnarchiver' . ucfirst($filetype);
+ }
+
+ $destdir = self::get('kickstart.setup.destdir', null);
+
+ if (empty($destdir))
+ {
+ $destdir = KSROOTDIR;
+ }
+
+ /** @var AKAbstractUnarchiver $object */
+ $object = self::getClassInstance($class_name);
+
+ if ($object->getState() == 'init')
+ {
+ $sourcePath = self::get('kickstart.setup.sourcepath', '');
+ $sourceFile = self::get('kickstart.setup.sourcefile', '');
+
+ if (!empty($sourcePath))
+ {
+ $sourceFile = rtrim($sourcePath, '/\\') . '/' . $sourceFile;
+ }
+
+ // Initialize the object –– Any change here MUST be reflected to echoHeadJavascript (default values)
+ $config = array(
+ 'filename' => $sourceFile,
+ 'restore_permissions' => self::get('kickstart.setup.restoreperms', 0),
+ 'post_proc' => self::get('kickstart.procengine', 'direct'),
+ 'add_path' => self::get('kickstart.setup.targetpath', $destdir),
+ 'remove_path' => self::get('kickstart.setup.removepath', ''),
+ 'rename_files' => self::get('kickstart.setup.renamefiles', array(
+ '.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak',
+ '.user.ini' => '.user.ini.bak',
+ )),
+ 'skip_files' => self::get('kickstart.setup.skipfiles', array(
+ basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak',
+ 'cacert.pem',
+ )),
+ 'ignoredirectories' => self::get('kickstart.setup.ignoredirectories', array(
+ 'tmp', 'log', 'logs',
+ )),
+ );
+
+ if (!defined('KICKSTART'))
+ {
+ // In restore.php mode we have to exclude the restoration.php files
+ $moreSkippedFiles = array(
+ // Akeeba Backup for Joomla!
+ 'administrator/components/com_akeeba/restoration.php',
+ // Joomla! Update
+ 'administrator/components/com_joomlaupdate/restoration.php',
+ // Akeeba Backup for WordPress
+ 'wp-content/plugins/akeebabackupwp/app/restoration.php',
+ 'wp-content/plugins/akeebabackupcorewp/app/restoration.php',
+ 'wp-content/plugins/akeebabackup/app/restoration.php',
+ 'wp-content/plugins/akeebabackupwpcore/app/restoration.php',
+ // Akeeba Solo
+ 'app/restoration.php',
+ );
+
+ $config['skip_files'] = array_merge($config['skip_files'], $moreSkippedFiles);
+ }
+
+ if (!empty($configOverride))
+ {
+ $config = array_merge($config, $configOverride);
+ }
+
+ $object->setup($config);
+ }
+
+ return $object;
+ }
+
+ // ========================================================================
+ // Public factory interface
+ // ========================================================================
+
+ public static function get($key, $default = null)
+ {
+ $self = self::getInstance();
+
+ if (array_key_exists($key, $self->varlist))
+ {
+ return $self->varlist[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Gets a single, internally used instance of the Factory
+ *
+ * @param string $serialized_data [optional] Serialized data to spawn the instance from
+ *
+ * @return AKFactory A reference to the unique Factory object instance
+ */
+ protected static function &getInstance($serialized_data = null)
+ {
+ if (!is_object(self::$instance) || !is_null($serialized_data))
+ {
+ if (!is_null($serialized_data))
+ {
+ self::$instance = unserialize($serialized_data);
+
+ return self::$instance;
+ }
+
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Internal function which instantiates a class named $class_name.
+ * The autoloader
+ *
+ * @param string $class_name
+ *
+ * @return object
+ */
+ protected static function &getClassInstance($class_name)
+ {
+ $self = self::getInstance();
+
+ if (!isset($self->objectlist[$class_name]))
+ {
+ $self->objectlist[$class_name] = new $class_name;
+ }
+
+ return $self->objectlist[$class_name];
+ }
+
+ // ========================================================================
+ // Public hash data storage interface
+ // ========================================================================
+
+ /**
+ * Regenerates the full Factory state from a serialized snapshot (resume)
+ *
+ * @param string $serialized_data The serialized snapshot to resume from
+ */
+ public static function unserialize($serialized_data)
+ {
+ if (function_exists('base64_encode') && function_exists('base64_decode'))
+ {
+ $serialized_data = base64_decode($serialized_data);
+ }
+
+ self::getInstance($serialized_data);
+ }
+
+ /**
+ * Reset the internal factory state, freeing all previously created objects
+ */
+ public static function nuke()
+ {
+ self::$instance = null;
+ }
+
+ // ========================================================================
+ // Akeeba Kickstart classes
+ // ========================================================================
+
+ public static function set($key, $value)
+ {
+ $self = self::getInstance();
+ $self->varlist[$key] = $value;
+ }
+
+ /**
+ * Gets the post processing engine
+ *
+ * @param string $proc_engine
+ *
+ * @return AKAbstractPostproc
+ */
+ public static function &getPostProc($proc_engine = null)
+ {
+ static $class_name;
+
+ if (empty($class_name))
+ {
+ if (empty($proc_engine))
+ {
+ $proc_engine = self::get('kickstart.procengine', 'direct');
+ }
+
+ $class_name = 'AKPostproc' . ucfirst($proc_engine);
+ }
+
+ return self::getClassInstance($class_name);
+ }
+
+ /**
+ * Get the a reference to the Akeeba Engine's timer
+ *
+ * @return AKCoreTimer
+ */
+ public static function &getTimer()
+ {
+ return self::getClassInstance('AKCoreTimer');
+ }
+
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * Interface for AES encryption adapters
+ */
+interface AKEncryptionAESAdapterInterface
+{
+ /**
+ * Decrypts a string. Returns the raw binary ciphertext, zero-padded.
+ *
+ * @param string $plainText The plaintext to encrypt
+ * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
+ *
+ * @return string The raw encrypted binary string.
+ */
+ public function decrypt($plainText, $key);
+
+ /**
+ * Returns the encryption block size in bytes
+ *
+ * @return int
+ */
+ public function getBlockSize();
+
+ /**
+ * Is this adapter supported?
+ *
+ * @return bool
+ */
+ public function isSupported();
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * Abstract AES encryption class
+ */
+abstract class AKEncryptionAESAdapterAbstract
+{
+ /**
+ * Trims or zero-pads a key / IV
+ *
+ * @param string $key The key or IV to treat
+ * @param int $size The block size of the currently used algorithm
+ *
+ * @return null|string Null if $key is null, treated string of $size byte length otherwise
+ */
+ public function resizeKey($key, $size)
+ {
+ if (empty($key))
+ {
+ return null;
+ }
+
+ $keyLength = strlen($key);
+
+ if (function_exists('mb_strlen'))
+ {
+ $keyLength = mb_strlen($key, 'ASCII');
+ }
+
+ if ($keyLength == $size)
+ {
+ return $key;
+ }
+
+ if ($keyLength > $size)
+ {
+ if (function_exists('mb_substr'))
+ {
+ return mb_substr($key, 0, $size, 'ASCII');
+ }
+
+ return substr($key, 0, $size);
+ }
+
+ return $key . str_repeat("\0", ($size - $keyLength));
+ }
+
+ /**
+ * Returns null bytes to append to the string so that it's zero padded to the specified block size
+ *
+ * @param string $string The binary string which will be zero padded
+ * @param int $blockSize The block size
+ *
+ * @return string The zero bytes to append to the string to zero pad it to $blockSize
+ */
+ protected function getZeroPadding($string, $blockSize)
+ {
+ $stringSize = strlen($string);
+
+ if (function_exists('mb_strlen'))
+ {
+ $stringSize = mb_strlen($string, 'ASCII');
+ }
+
+ if ($stringSize == $blockSize)
+ {
+ return '';
+ }
+
+ if ($stringSize < $blockSize)
+ {
+ return str_repeat("\0", $blockSize - $stringSize);
+ }
+
+ $paddingBytes = $stringSize % $blockSize;
+
+ return str_repeat("\0", $blockSize - $paddingBytes);
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+class Mcrypt extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
+{
+ protected $cipherType = MCRYPT_RIJNDAEL_128;
+
+ protected $cipherMode = MCRYPT_MODE_CBC;
+
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);
+
+ return $plainText;
+ }
+
+ public function isSupported()
+ {
+ if (!function_exists('mcrypt_get_key_size'))
+ {
+ return false;
+ }
+
+ if (!function_exists('mcrypt_get_iv_size'))
+ {
+ return false;
+ }
+
+ if (!function_exists('mcrypt_create_iv'))
+ {
+ return false;
+ }
+
+ if (!function_exists('mcrypt_encrypt'))
+ {
+ return false;
+ }
+
+ if (!function_exists('mcrypt_decrypt'))
+ {
+ return false;
+ }
+
+ if (!function_exists('mcrypt_list_algorithms'))
+ {
+ return false;
+ }
+
+ if (!function_exists('hash'))
+ {
+ return false;
+ }
+
+ if (!function_exists('hash_algos'))
+ {
+ return false;
+ }
+
+ $algorightms = mcrypt_list_algorithms();
+
+ if (!in_array('rijndael-128', $algorightms))
+ {
+ return false;
+ }
+
+ if (!in_array('rijndael-192', $algorightms))
+ {
+ return false;
+ }
+
+ if (!in_array('rijndael-256', $algorightms))
+ {
+ return false;
+ }
+
+ $algorightms = hash_algos();
+
+ if (!in_array('sha256', $algorightms))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getBlockSize()
+ {
+ return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+class OpenSSL extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface
+{
+ /**
+ * The OpenSSL options for encryption / decryption
+ *
+ * @var int
+ */
+ protected $openSSLOptions = 0;
+
+ /**
+ * The encryption method to use
+ *
+ * @var string
+ */
+ protected $method = 'aes-128-cbc';
+
+ public function __construct()
+ {
+ $this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
+ }
+
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
+
+ return $plainText;
+ }
+
+ public function isSupported()
+ {
+ if (!function_exists('openssl_get_cipher_methods'))
+ {
+ return false;
+ }
+
+ if (!function_exists('openssl_random_pseudo_bytes'))
+ {
+ return false;
+ }
+
+ if (!function_exists('openssl_cipher_iv_length'))
+ {
+ return false;
+ }
+
+ if (!function_exists('openssl_encrypt'))
+ {
+ return false;
+ }
+
+ if (!function_exists('openssl_decrypt'))
+ {
+ return false;
+ }
+
+ if (!function_exists('hash'))
+ {
+ return false;
+ }
+
+ if (!function_exists('hash_algos'))
+ {
+ return false;
+ }
+
+ $algorightms = openssl_get_cipher_methods();
+
+ if (!in_array('aes-128-cbc', $algorightms))
+ {
+ return false;
+ }
+
+ $algorightms = hash_algos();
+
+ if (!in_array('sha256', $algorightms))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return int
+ */
+ public function getBlockSize()
+ {
+ return openssl_cipher_iv_length($this->method);
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * AES implementation in PHP (c) Chris Veness 2005-2016.
+ * Right to use and adapt is granted for under a simple creative commons attribution
+ * licence. No warranty of any form is offered.
+ *
+ * Heavily modified for Akeeba Backup by Nicholas K. Dionysopoulos
+ * Also added AES-128 CBC mode (with mcrypt and OpenSSL) on top of AES CTR
+ * Removed CTR encrypt / decrypt (no longer used)
+ */
+class AKEncryptionAES
+{
+ // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1]
+ protected static $Sbox =
+ array(0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16);
+
+ // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]
+ protected static $Rcon = array(
+ array(0x00, 0x00, 0x00, 0x00),
+ array(0x01, 0x00, 0x00, 0x00),
+ array(0x02, 0x00, 0x00, 0x00),
+ array(0x04, 0x00, 0x00, 0x00),
+ array(0x08, 0x00, 0x00, 0x00),
+ array(0x10, 0x00, 0x00, 0x00),
+ array(0x20, 0x00, 0x00, 0x00),
+ array(0x40, 0x00, 0x00, 0x00),
+ array(0x80, 0x00, 0x00, 0x00),
+ array(0x1b, 0x00, 0x00, 0x00),
+ array(0x36, 0x00, 0x00, 0x00));
+
+ protected static $passwords = array();
+
+ /**
+ * The algorithm to use for PBKDF2. Must be a supported hash_hmac algorithm. Default: sha1
+ *
+ * @var string
+ */
+ private static $pbkdf2Algorithm = 'sha1';
+
+ /**
+ * Number of iterations to use for PBKDF2
+ *
+ * @var int
+ */
+ private static $pbkdf2Iterations = 1000;
+
+ /**
+ * Should we use a static salt for PBKDF2?
+ *
+ * @var int
+ */
+ private static $pbkdf2UseStaticSalt = 0;
+
+ /**
+ * The static salt to use for PBKDF2
+ *
+ * @var string
+ */
+ private static $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+ /**
+ * AES Cipher function: encrypt 'input' with Rijndael algorithm
+ *
+ * @param array $input Message as byte-array (16 bytes)
+ * @param array $w key schedule as 2D byte-array (Nr+1 x Nb bytes) -
+ * generated from the cipher key by KeyExpansion()
+ *
+ * @return string Ciphertext as byte-array (16 bytes)
+ */
+ protected static function Cipher($input, $w)
+ {
+ // main Cipher function [�5.1]
+ $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
+ $Nr = count($w) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
+
+ $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]
+
+ for ($i = 0; $i < 4 * $Nb; $i++)
+ {
+ $state[$i % 4][floor($i / 4)] = $input[$i];
+ }
+
+ $state = self::AddRoundKey($state, $w, 0, $Nb);
+
+ for ($round = 1; $round < $Nr; $round++)
+ { // apply Nr rounds
+ $state = self::SubBytes($state, $Nb);
+ $state = self::ShiftRows($state, $Nb);
+ $state = self::MixColumns($state);
+ $state = self::AddRoundKey($state, $w, $round, $Nb);
+ }
+
+ $state = self::SubBytes($state, $Nb);
+ $state = self::ShiftRows($state, $Nb);
+ $state = self::AddRoundKey($state, $w, $Nr, $Nb);
+
+ $output = array(4 * $Nb); // convert state to 1-d array before returning [�3.4]
+
+ for ($i = 0; $i < 4 * $Nb; $i++)
+ {
+ $output[$i] = $state[$i % 4][floor($i / 4)];
+ }
+
+ return $output;
+ }
+
+ protected static function AddRoundKey($state, $w, $rnd, $Nb)
+ {
+ // xor Round Key into state S [�5.1.4]
+ for ($r = 0; $r < 4; $r++)
+ {
+ for ($c = 0; $c < $Nb; $c++)
+ {
+ $state[$r][$c] ^= $w[$rnd * 4 + $c][$r];
+ }
+ }
+
+ return $state;
+ }
+
+ protected static function SubBytes($s, $Nb)
+ {
+ // apply SBox to state S [�5.1.1]
+ for ($r = 0; $r < 4; $r++)
+ {
+ for ($c = 0; $c < $Nb; $c++)
+ {
+ $s[$r][$c] = self::$Sbox[$s[$r][$c]];
+ }
+ }
+
+ return $s;
+ }
+
+ protected static function ShiftRows($s, $Nb)
+ {
+ // shift row r of state S left by r bytes [�5.1.2]
+ $t = array(4);
+
+ for ($r = 1; $r < 4; $r++)
+ {
+ for ($c = 0; $c < 4; $c++)
+ {
+ $t[$c] = $s[$r][($c + $r) % $Nb];
+ } // shift into temp copy
+
+ for ($c = 0; $c < 4; $c++)
+ {
+ $s[$r][$c] = $t[$c];
+ } // and copy back
+ } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
+
+ return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
+ }
+
+ protected static function MixColumns($s)
+ {
+ // combine bytes of each col of state S [�5.1.3]
+ for ($c = 0; $c < 4; $c++)
+ {
+ $a = array(4); // 'a' is a copy of the current column from 's'
+ $b = array(4); // 'b' is a�{02} in GF(2^8)
+
+ for ($i = 0; $i < 4; $i++)
+ {
+ $a[$i] = $s[$i][$c];
+ $b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1;
+ }
+
+ // a[n] ^ b[n] is a�{03} in GF(2^8)
+ $s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3
+ $s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3
+ $s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3
+ $s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3
+ }
+
+ return $s;
+ }
+
+ /**
+ * Key expansion for Rijndael Cipher(): performs key expansion on cipher key
+ * to generate a key schedule
+ *
+ * @param array $key Cipher key byte-array (16 bytes)
+ *
+ * @return array Key schedule as 2D byte-array (Nr+1 x Nb bytes)
+ */
+ protected static function KeyExpansion($key)
+ {
+ // generate Key Schedule from Cipher Key [�5.2]
+
+ // block size (in words): no of columns in state (fixed at 4 for AES)
+ $Nb = 4;
+ // key length (in words): 4/6/8 for 128/192/256-bit keys
+ $Nk = (int) (count($key) / 4);
+ // no of rounds: 10/12/14 for 128/192/256-bit keys
+ $Nr = $Nk + 6;
+
+ $w = array();
+ $temp = array();
+
+ for ($i = 0; $i < $Nk; $i++)
+ {
+ $r = array($key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]);
+ $w[$i] = $r;
+ }
+
+ for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++)
+ {
+ $w[$i] = array();
+ for ($t = 0; $t < 4; $t++)
+ {
+ $temp[$t] = $w[$i - 1][$t];
+ }
+ if ($i % $Nk == 0)
+ {
+ $temp = self::SubWord(self::RotWord($temp));
+ for ($t = 0; $t < 4; $t++)
+ {
+ $rConIndex = (int) ($i / $Nk);
+ $temp[$t] ^= self::$Rcon[$rConIndex][$t];
+ }
+ }
+ else if ($Nk > 6 && $i % $Nk == 4)
+ {
+ $temp = self::SubWord($temp);
+ }
+ for ($t = 0; $t < 4; $t++)
+ {
+ $w[$i][$t] = $w[$i - $Nk][$t] ^ $temp[$t];
+ }
+ }
+
+ return $w;
+ }
+
+ protected static function SubWord($w)
+ {
+ // apply SBox to 4-byte word w
+ for ($i = 0; $i < 4; $i++)
+ {
+ $w[$i] = self::$Sbox[$w[$i]];
+ }
+
+ return $w;
+ }
+
+ /*
+ * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints
+ *
+ * @param a number to be shifted (32-bit integer)
+ * @param b number of bits to shift a to the right (0..31)
+ * @return a right-shifted and zero-filled by b bits
+ */
+
+ protected static function RotWord($w)
+ {
+ // rotate 4-byte word w left by one byte
+ $tmp = $w[0];
+ for ($i = 0; $i < 3; $i++)
+ {
+ $w[$i] = $w[$i + 1];
+ }
+ $w[3] = $tmp;
+
+ return $w;
+ }
+
+ protected static function urs($a, $b)
+ {
+ $a &= 0xffffffff;
+ $b &= 0x1f; // (bounds check)
+ if ($a & 0x80000000 && $b > 0)
+ { // if left-most bit set
+ $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit
+ $a = $a >> ($b - 1); // remaining right-shifts
+ }
+ else
+ { // otherwise
+ $a = ($a >> $b); // use normal right-shift
+ }
+
+ return $a;
+ }
+
+ /**
+ * AES decryption in CBC mode. This is the standard mode (the CTR methods
+ * actually use Rijndael-128 in CTR mode, which - technically - isn't AES).
+ *
+ * It supports AES-128 only. It assumes that the last 4 bytes
+ * contain a little-endian unsigned long integer representing the unpadded
+ * data length.
+ *
+ * @since 3.0.1
+ * @author Nicholas K. Dionysopoulos
+ *
+ * @param string $ciphertext The data to encrypt
+ * @param string $password Encryption password
+ *
+ * @return string The plaintext
+ */
+ public static function AESDecryptCBC($ciphertext, $password)
+ {
+ $adapter = self::getAdapter();
+
+ if (!$adapter->isSupported())
+ {
+ return false;
+ }
+
+ // Read the data size
+ $data_size = unpack('V', substr($ciphertext, -4));
+
+ // Do I have a PBKDF2 salt?
+ $salt = substr($ciphertext, -92, 68);
+ $rightStringLimit = -4;
+
+ $params = self::getKeyDerivationParameters();
+ $keySizeBytes = $params['keySize'];
+ $algorithm = $params['algorithm'];
+ $iterations = $params['iterations'];
+ $useStaticSalt = $params['useStaticSalt'];
+
+ if (substr($salt, 0, 4) == 'JPST')
+ {
+ // We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes
+ // (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the
+ // uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the
+ // format specification).
+ $salt = substr($salt, 4);
+ $rightStringLimit -= 68;
+
+ $key = self::pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes);
+ }
+ elseif ($useStaticSalt)
+ {
+ // We have a static salt. Use it for PBKDF2.
+ $key = self::getStaticSaltExpandedKey($password);
+ }
+ else
+ {
+ // Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD.
+ $key = self::expandKey($password);
+ }
+
+ // Try to get the IV from the data
+ $iv = substr($ciphertext, -24, 20);
+
+ if (substr($iv, 0, 4) == 'JPIV')
+ {
+ // We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes
+ // (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length)
+ $iv = substr($iv, 4);
+ $rightStringLimit -= 20;
+ }
+ else
+ {
+ // No stored IV. Do it the dumb way.
+ $iv = self::createTheWrongIV($password);
+ }
+
+ // Decrypt
+ $plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key);
+
+ // Trim padding, if necessary
+ if (strlen($plaintext) > $data_size)
+ {
+ $plaintext = substr($plaintext, 0, $data_size);
+ }
+
+ return $plaintext;
+ }
+
+ /**
+ * That's the old way of creating an IV that's definitely not cryptographically sound.
+ *
+ * DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA
+ *
+ * @param string $password The raw password from which we create an IV in a super bozo way
+ *
+ * @return string A 16-byte IV string
+ */
+ public static function createTheWrongIV($password)
+ {
+ static $ivs = array();
+
+ $key = md5($password);
+
+ if (!isset($ivs[$key]))
+ {
+ $nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes
+ $pwBytes = array();
+ for ($i = 0; $i < $nBytes; $i++)
+ {
+ $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
+ }
+ $iv = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
+ $newIV = '';
+ foreach ($iv as $int)
+ {
+ $newIV .= chr($int);
+ }
+
+ $ivs[$key] = $newIV;
+ }
+
+ return $ivs[$key];
+ }
+
+ /**
+ * Expand the password to an appropriate 128-bit encryption key
+ *
+ * @param string $password
+ *
+ * @return string
+ *
+ * @since 5.2.0
+ * @author Nicholas K. Dionysopoulos
+ */
+ public static function expandKey($password)
+ {
+ // Try to fetch cached key or create it if it doesn't exist
+ $nBits = 128;
+ $lookupKey = md5($password . '-' . $nBits);
+
+ if (array_key_exists($lookupKey, self::$passwords))
+ {
+ $key = self::$passwords[$lookupKey];
+
+ return $key;
+ }
+
+ // use AES itself to encrypt password to get cipher key (using plain password as source for
+ // key expansion) - gives us well encrypted key.
+ $nBytes = $nBits / 8; // Number of bytes in key
+ $pwBytes = array();
+
+ for ($i = 0; $i < $nBytes; $i++)
+ {
+ $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff;
+ }
+
+ $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes));
+ $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long
+ $newKey = '';
+
+ foreach ($key as $int)
+ {
+ $newKey .= chr($int);
+ }
+
+ $key = $newKey;
+
+ self::$passwords[$lookupKey] = $key;
+
+ return $key;
+ }
+
+ /**
+ * Returns the correct AES-128 CBC encryption adapter
+ *
+ * @return AKEncryptionAESAdapterInterface
+ *
+ * @since 5.2.0
+ * @author Nicholas K. Dionysopoulos
+ */
+ public static function getAdapter()
+ {
+ static $adapter = null;
+
+ if (is_object($adapter) && ($adapter instanceof AKEncryptionAESAdapterInterface))
+ {
+ return $adapter;
+ }
+
+ $adapter = new OpenSSL();
+
+ if (!$adapter->isSupported())
+ {
+ $adapter = new Mcrypt();
+ }
+
+ return $adapter;
+ }
+
+ /**
+ * @return string
+ */
+ public static function getPbkdf2Algorithm()
+ {
+ return self::$pbkdf2Algorithm;
+ }
+
+ /**
+ * @param string $pbkdf2Algorithm
+ * @return void
+ */
+ public static function setPbkdf2Algorithm($pbkdf2Algorithm)
+ {
+ self::$pbkdf2Algorithm = $pbkdf2Algorithm;
+ }
+
+ /**
+ * @return int
+ */
+ public static function getPbkdf2Iterations()
+ {
+ return self::$pbkdf2Iterations;
+ }
+
+ /**
+ * @param int $pbkdf2Iterations
+ * @return void
+ */
+ public static function setPbkdf2Iterations($pbkdf2Iterations)
+ {
+ self::$pbkdf2Iterations = $pbkdf2Iterations;
+ }
+
+ /**
+ * @return int
+ */
+ public static function getPbkdf2UseStaticSalt()
+ {
+ return self::$pbkdf2UseStaticSalt;
+ }
+
+ /**
+ * @param int $pbkdf2UseStaticSalt
+ * @return void
+ */
+ public static function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt)
+ {
+ self::$pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt;
+ }
+
+ /**
+ * @return string
+ */
+ public static function getPbkdf2StaticSalt()
+ {
+ return self::$pbkdf2StaticSalt;
+ }
+
+ /**
+ * @param string $pbkdf2StaticSalt
+ * @return void
+ */
+ public static function setPbkdf2StaticSalt($pbkdf2StaticSalt)
+ {
+ self::$pbkdf2StaticSalt = $pbkdf2StaticSalt;
+ }
+
+ /**
+ * Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static
+ * parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block
+ * to minimize the risk of attacks against the password.
+ *
+ * @return array
+ */
+ public static function getKeyDerivationParameters()
+ {
+ return array(
+ 'keySize' => 16,
+ 'algorithm' => self::$pbkdf2Algorithm,
+ 'iterations' => self::$pbkdf2Iterations,
+ 'useStaticSalt' => self::$pbkdf2UseStaticSalt,
+ 'staticSalt' => self::$pbkdf2StaticSalt,
+ );
+ }
+
+ /**
+ * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
+ *
+ * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
+ *
+ * This implementation of PBKDF2 was originally created by https://defuse.ca
+ * With improvements by http://www.variations-of-shadow.com
+ * Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster)
+ *
+ * @param string $password The password.
+ * @param string $salt A salt that is unique to the password.
+ * @param string $algorithm The hash algorithm to use. Default is sha1.
+ * @param int $count Iteration count. Higher is better, but slower. Default: 1000.
+ * @param int $key_length The length of the derived key in bytes.
+ *
+ * @return string A string of $key_length bytes
+ */
+ public static function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16)
+ {
+ if (function_exists("hash_pbkdf2"))
+ {
+ return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true);
+ }
+
+ $hash_length = akstringlen(hash($algorithm, "", true));
+ $block_count = ceil($key_length / $hash_length);
+
+ $output = "";
+
+ for ($i = 1; $i <= $block_count; $i++)
+ {
+ // $i encoded as 4 bytes, big endian.
+ $last = $salt . pack("N", $i);
+
+ // First iteration
+ $xorResult = hash_hmac($algorithm, $last, $password, true);
+ $last = $xorResult;
+
+ // Perform the other $count - 1 iterations
+ for ($j = 1; $j < $count; $j++)
+ {
+ $last = hash_hmac($algorithm, $last, $password, true);
+ $xorResult ^= $last;
+ }
+
+ $output .= $xorResult;
+ }
+
+ return aksubstr($output, 0, $key_length);
+ }
+
+ /**
+ * Get the expanded key from the user supplied password using a static salt. The results are cached for performance
+ * reasons.
+ *
+ * @param string $password The user-supplied password, UTF-8 encoded.
+ *
+ * @return string The expanded key
+ */
+ private static function getStaticSaltExpandedKey($password)
+ {
+ $params = self::getKeyDerivationParameters();
+ $keySizeBytes = $params['keySize'];
+ $algorithm = $params['algorithm'];
+ $iterations = $params['iterations'];
+ $staticSalt = $params['staticSalt'];
+
+ $lookupKey = "PBKDF2-$algorithm-$iterations-" . md5($password . $staticSalt);
+
+ if (!array_key_exists($lookupKey, self::$passwords))
+ {
+ self::$passwords[$lookupKey] = self::pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes);
+ }
+
+ return self::$passwords[$lookupKey];
+ }
+
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+/**
+ * A timing safe equals comparison
+ *
+ * @param string $safe The internal (safe) value to be checked
+ * @param string $user The user submitted (unsafe) value
+ *
+ * @return boolean True if the two strings are identical.
+ *
+ * @see http://blog.ircmaxell.com/2014/11/its-all-about-time.html
+ */
+function timingSafeEquals($safe, $user)
+{
+ $safeLen = strlen($safe);
+ $userLen = strlen($user);
+
+ if ($userLen != $safeLen)
+ {
+ return false;
+ }
+
+ $result = 0;
+
+ for ($i = 0; $i < $userLen; $i++)
+ {
+ $result |= (ord($safe[$i]) ^ ord($user[$i]));
+ }
+
+ // They are only identical strings if $result is exactly 0...
+ return $result === 0;
+}
+
+/**
+ * The Master Setup will read the configuration parameters from restoration.php or
+ * the JSON-encoded "configuration" input variable and return the status.
+ *
+ * @return bool True if the master configuration was applied to the Factory object
+ */
+function masterSetup()
+{
+ // ------------------------------------------------------------
+ // 1. Import basic setup parameters
+ // ------------------------------------------------------------
+
+ $ini_data = null;
+
+ // In restore.php mode, require restoration.php or fail
+ if (!defined('KICKSTART'))
+ {
+ // This is the standalone mode, used by Akeeba Backup Professional. It looks for a restoration.php
+ // file to perform its magic. If the file is not there, we will abort.
+ $setupFile = 'restoration.php';
+
+ if (!file_exists($setupFile))
+ {
+ AKFactory::set('kickstart.enabled', false);
+
+ return false;
+ }
+
+ // Load restoration.php. It creates a global variable named $restoration_setup
+ require_once $setupFile;
+
+ $ini_data = $restoration_setup;
+
+ if (empty($ini_data))
+ {
+ // No parameters fetched. Darn, how am I supposed to work like that?!
+ AKFactory::set('kickstart.enabled', false);
+
+ return false;
+ }
+
+ AKFactory::set('kickstart.enabled', true);
+ }
+ else
+ {
+ // Maybe we have $restoration_setup defined in the head of kickstart.php
+ global $restoration_setup;
+
+ if (!empty($restoration_setup) && !is_array($restoration_setup))
+ {
+ $ini_data = AKText::parse_ini_file($restoration_setup, false, true);
+ }
+ elseif (is_array($restoration_setup))
+ {
+ $ini_data = $restoration_setup;
+ }
+ }
+
+ // Import any data from $restoration_setup
+ if (!empty($ini_data))
+ {
+ foreach ($ini_data as $key => $value)
+ {
+ AKFactory::set($key, $value);
+ }
+ AKFactory::set('kickstart.enabled', true);
+ }
+
+ // Reinitialize $ini_data
+ $ini_data = null;
+
+ // ------------------------------------------------------------
+ // 2. Explode JSON parameters into $_REQUEST scope
+ // ------------------------------------------------------------
+
+ // Detect a JSON string in the request variable and store it.
+ $json = getQueryParam('json', null);
+
+ // Detect a password in the request variable and store it.
+ $userPassword = getQueryParam('password', '');
+
+ // Remove everything from the request, post and get arrays
+ if (!empty($_REQUEST))
+ {
+ foreach ($_REQUEST as $key => $value)
+ {
+ unset($_REQUEST[$key]);
+ }
+ }
+
+ if (!empty($_POST))
+ {
+ foreach ($_POST as $key => $value)
+ {
+ unset($_POST[$key]);
+ }
+ }
+
+ if (!empty($_GET))
+ {
+ foreach ($_GET as $key => $value)
+ {
+ unset($_GET[$key]);
+ }
+ }
+
+ // Authentication - Akeeba Restore 5.4.0 or later
+ $password = AKFactory::get('kickstart.security.password', null);
+ $isAuthenticated = false;
+
+ /**
+ * Akeeba Restore 5.3.1 and earlier use a custom implementation of AES-128 in CTR mode to encrypt the JSON data
+ * between client and server. This is not used as a means to maintain secrecy (it's symmetrical encryption and the
+ * key is, by necessity, transmitted with the HTML page to the client). It's meant as a form of authentication, so
+ * that the server part can ensure that it only receives commands by an authorized client.
+ *
+ * The downside is that encryption in CTR mode (like CBC) is an all-or-nothing affair. This opens the possibility
+ * for a padding oracle attack (https://en.wikipedia.org/wiki/Padding_oracle_attack). While Akeeba Restore was
+ * hardened in 2014 to prevent the bulk of suck attacks it is still possible to attack the encryption using a very
+ * large number of requests (several dozens of thousands).
+ *
+ * Since Akeeba Restore 5.4.0 we have removed this authentication method and replaced it with the transmission of a
+ * very large length password. On the server side we use a timing safe password comparison. By its very nature, it
+ * will only leak the (well known, constant and large) length of the password but no more information about the
+ * password itself. See http://blog.ircmaxell.com/2014/11/its-all-about-time.html As a result this form of
+ * authentication is many orders of magnitude harder to crack than regular encryption.
+ *
+ * Now you may wonder "how is sending a password in the clear hardier than encryption?". If you ask that question
+ * you were not paying attention. The password needs to be known by BOTH the server AND the client (browser). Since
+ * this password is generated programmatically by the server, it MUST be sent to the client by the server. If an
+ * attacker is able to intercept this transmission (man in the middle attack) using encryption is irrelevant: the
+ * attacker already knows your password. This situation also applies when the user sends their own password to the
+ * server, e.g. when logging into their site. The ONLY way to avoid security issues regarding information being
+ * stolen in transit is using HTTPS with a commercially signed SSL certificate. Unlike 2008, when Kickstart was
+ * originally written, obtaining such a certificate nowadays is trivial and costs absolutely nothing thanks to Let's
+ * Encrypt (https://letsencrypt.org/).
+ *
+ * TL;DR: Use HTTPS with a commercially signed SSL certificate, e.g. a free certificate from Let's Encrypt. Client-
+ * side cryptography does NOT protect you against an attacker (see
+ * https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/).
+ * Moreover, sending a plaintext password is safer than relying on client-side encryption for authentication as it
+ * reoves the possibility of an attacker inferring the contents of the authentication key (password) in a relatively
+ * easy and automated manner.
+ */
+ if (!empty($password))
+ {
+ // Timing-safe password comparison. See http://blog.ircmaxell.com/2014/11/its-all-about-time.html
+ if (!timingSafeEquals($password, $userPassword))
+ {
+ die('###{"status":false,"message":"Invalid login"}###');
+ }
+ }
+
+ // No JSON data? Die.
+ if (empty($json))
+ {
+ die('###{"status":false,"message":"Invalid JSON data"}###');
+ }
+
+ // Handle the JSON string
+ $raw = json_decode($json, true);
+
+ // Invalid JSON data?
+ if (empty($raw))
+ {
+ die('###{"status":false,"message":"Invalid JSON data"}###');
+ }
+
+ // Pass all JSON data to the request array
+ if (!empty($raw))
+ {
+ foreach ($raw as $key => $value)
+ {
+ $_REQUEST[$key] = $value;
+ }
+ }
+
+ // ------------------------------------------------------------
+ // 3. Try the "factory" variable
+ // ------------------------------------------------------------
+ // A "factory" variable will override all other settings.
+ $serialized = getQueryParam('factory', null);
+
+ if (!is_null($serialized))
+ {
+ // Get the serialized factory
+ AKFactory::unserialize($serialized);
+ AKFactory::set('kickstart.enabled', true);
+
+ return true;
+ }
+
+ // ------------------------------------------------------------
+ // 4. Try the configuration variable for Kickstart
+ // ------------------------------------------------------------
+ if (defined('KICKSTART'))
+ {
+ $configuration = getQueryParam('configuration');
+
+ if (!is_null($configuration))
+ {
+ // Let's decode the configuration from JSON to array
+ $ini_data = json_decode($configuration, true);
+ }
+ else
+ {
+ // Neither exists. Enable Kickstart's interface anyway.
+ $ini_data = array('kickstart.enabled' => true);
+ }
+
+ // Import any INI data we might have from other sources
+ if (!empty($ini_data))
+ {
+ foreach ($ini_data as $key => $value)
+ {
+ AKFactory::set($key, $value);
+ }
+
+ AKFactory::set('kickstart.enabled', true);
+
+ return true;
+ }
+ }
+}
+
+/**
+ * Akeeba Restore
+ * A JSON-powered JPA, JPS and ZIP archive extraction library
+ *
+ * @copyright Copyright (c)2008-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ * @license GNU GPL v2 or - at your option - any later version
+ * @package akeebabackup
+ * @subpackage kickstart
+ */
+
+// Mini-controller for restore.php
+if (!defined('KICKSTART'))
+{
+ // The observer class, used to report number of files and bytes processed
+ class RestorationObserver extends AKAbstractPartObserver
+ {
+ public $compressedTotal = 0;
+ public $uncompressedTotal = 0;
+ public $filesProcessed = 0;
+
+ public function update($object, $message)
+ {
+ if (!is_object($message))
+ {
+ return;
+ }
+
+ if (!array_key_exists('type', get_object_vars($message)))
+ {
+ return;
+ }
+
+ if ($message->type == 'startfile')
+ {
+ $this->filesProcessed++;
+ $this->compressedTotal += $message->content->compressed;
+ $this->uncompressedTotal += $message->content->uncompressed;
+ }
+ }
+
+ public function __toString()
+ {
+ return __CLASS__;
+ }
+
+ }
+
+ // Import configuration
+ masterSetup();
+
+ $retArray = array(
+ 'status' => true,
+ 'message' => null
+ );
+
+ $enabled = AKFactory::get('kickstart.enabled', false);
+
+ if ($enabled)
+ {
+ $task = getQueryParam('task');
+
+ switch ($task)
+ {
+ case 'ping':
+ // ping task - realy does nothing!
+ $timer = AKFactory::getTimer();
+ $timer->enforce_min_exec_time();
+ break;
+
+ /**
+ * There are two separate steps here since we were using an inefficient restoration intialization method in
+ * the past. Now both startRestore and stepRestore are identical. The difference in behavior depends
+ * exclusively on the calling Javascript. If no serialized factory was passed in the request then we start a
+ * new restoration. If a serialized factory was passed in the request then the restoration is resumed. For
+ * this reason we should NEVER call AKFactory::nuke() in startRestore anymore: that would simply reset the
+ * extraction engine configuration which was done in masterSetup() leading to an error about the file being
+ * invalid (since no file is found).
+ */
+ case 'startRestore':
+ case 'stepRestore':
+ $engine = AKFactory::getUnarchiver(); // Get the engine
+ $observer = new RestorationObserver(); // Create a new observer
+ $engine->attach($observer); // Attach the observer
+ $engine->tick();
+ $ret = $engine->getStatusArray();
+
+ if ($ret['Error'] != '')
+ {
+ $retArray['status'] = false;
+ $retArray['done'] = true;
+ $retArray['message'] = $ret['Error'];
+ }
+ elseif (!$ret['HasRun'])
+ {
+ $retArray['files'] = $observer->filesProcessed;
+ $retArray['bytesIn'] = $observer->compressedTotal;
+ $retArray['bytesOut'] = $observer->uncompressedTotal;
+ $retArray['status'] = true;
+ $retArray['done'] = true;
+ }
+ else
+ {
+ $retArray['files'] = $observer->filesProcessed;
+ $retArray['bytesIn'] = $observer->compressedTotal;
+ $retArray['bytesOut'] = $observer->uncompressedTotal;
+ $retArray['status'] = true;
+ $retArray['done'] = false;
+ $retArray['factory'] = AKFactory::serialize();
+ }
+ break;
+
+ case 'finalizeRestore':
+ $root = AKFactory::get('kickstart.setup.destdir');
+ // Remove the installation directory
+ recursive_remove_directory($root . '/installation');
+
+ $postproc = AKFactory::getPostProc();
+
+ /**
+ * Should I rename the htaccess.bak and web.config.bak files back to their live filenames...?
+ */
+ $renameFiles = AKFactory::get('kickstart.setup.postrenamefiles', true);
+
+ if ($renameFiles)
+ {
+ // Rename htaccess.bak to .htaccess
+ if (file_exists($root . '/htaccess.bak'))
+ {
+ if (file_exists($root . '/.htaccess'))
+ {
+ $postproc->unlink($root . '/.htaccess');
+ }
+ $postproc->rename($root . '/htaccess.bak', $root . '/.htaccess');
+ }
+
+ // Rename htaccess.bak to .htaccess
+ if (file_exists($root . '/web.config.bak'))
+ {
+ if (file_exists($root . '/web.config'))
+ {
+ $postproc->unlink($root . '/web.config');
+ }
+ $postproc->rename($root . '/web.config.bak', $root . '/web.config');
+ }
+ }
+
+ // Remove restoration.php
+ $basepath = KSROOTDIR;
+ $basepath = rtrim(str_replace('\\', '/', $basepath), '/');
+ if (!empty($basepath))
+ {
+ $basepath .= '/';
+ }
+ $postproc->unlink($basepath . 'restoration.php');
+
+ // Import a custom finalisation file
+ $filename = dirname(__FILE__) . '/restore_finalisation.php';
+ if (file_exists($filename))
+ {
+ // opcode cache busting before including the filename
+ if (function_exists('opcache_invalidate'))
+ {
+ opcache_invalidate($filename);
+ }
+ if (function_exists('apc_compile_file'))
+ {
+ apc_compile_file($filename);
+ }
+ if (function_exists('wincache_refresh_if_changed'))
+ {
+ wincache_refresh_if_changed(array($filename));
+ }
+ if (function_exists('xcache_asm'))
+ {
+ xcache_asm($filename);
+ }
+ include_once $filename;
+ }
+
+ // Run a custom finalisation script
+ if (function_exists('finalizeRestore'))
+ {
+ finalizeRestore($root, $basepath);
+ }
+ break;
+
+ default:
+ // Invalid task!
+ $enabled = false;
+ break;
+ }
+ }
+
+ // Maybe we weren't authorized or the task was invalid?
+ if (!$enabled)
+ {
+ // Maybe the user failed to enter any information
+ $retArray['status'] = false;
+ $retArray['message'] = AKText::_('ERR_INVALID_LOGIN');
+ }
+
+ // JSON encode the message
+ $json = json_encode($retArray);
+
+ // Return the message
+ echo "###$json###";
+
+}
+
+// ------------ lixlpixel recursive PHP functions -------------
+// recursive_remove_directory( directory to delete, empty )
+// expects path to directory and optional TRUE / FALSE to empty
+// of course PHP has to have the rights to delete the directory
+// you specify and all files and folders inside the directory
+// ------------------------------------------------------------
+function recursive_remove_directory($directory)
+{
+ // if the path has a slash at the end we remove it here
+ if (substr($directory, -1) == '/')
+ {
+ $directory = substr($directory, 0, -1);
+ }
+ // if the path is not valid or is not a directory ...
+ if (!file_exists($directory) || !is_dir($directory))
+ {
+ // ... we return false and exit the function
+ return false;
+ // ... if the path is not readable
+ }
+ elseif (!is_readable($directory))
+ {
+ // ... we return false and exit the function
+ return false;
+ // ... else if the path is readable
+ }
+ else
+ {
+ // we open the directory
+ $handle = opendir($directory);
+ $postproc = AKFactory::getPostProc();
+ // and scan through the items inside
+ while (false !== ($item = readdir($handle)))
+ {
+ // if the filepointer is not the current directory
+ // or the parent directory
+ if ($item != '.' && $item != '..')
+ {
+ // we build the new path to delete
+ $path = $directory . '/' . $item;
+ // if the new path is a directory
+ if (is_dir($path))
+ {
+ // we call this function with the new path
+ recursive_remove_directory($path);
+ // if the new path is a file
+ }
+ else
+ {
+ // we remove the file
+ $postproc->unlink($path);
+ }
+ }
+ }
+ // close the directory
+ closedir($handle);
+ // try to delete the now empty directory
+ if (!$postproc->rmdir($directory))
+ {
+ // return false if not possible
+ return false;
+ }
+
+ // return success
+ return true;
+ }
+}
diff --git a/deployed/akeeba/administrator/components/com_akeeba/script.com_akeeba.php b/deployed/akeeba/administrator/components/com_akeeba/script.com_akeeba.php
new file mode 100644
index 00000000..b5f34cbb
--- /dev/null
+++ b/deployed/akeeba/administrator/components/com_akeeba/script.com_akeeba.php
@@ -0,0 +1,1026 @@
+ array(
+ // Pro component features
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Directftp.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/directftp.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Directftpcurl.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/directftpcurl.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Directsftp.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/directsftp.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Directsftpcurl.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/directsftpcurl.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Jps.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/jps.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/Zipnative.php',
+ 'administrator/components/com_akeeba/BackupEngine/Archiver/zipnative.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/amazons3.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Amazons3.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/azure.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Azure.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/backblaze.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Backblaze.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/cloudfiles.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Cloudfiles.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/cloudme.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Cloudme.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/dreamobjects.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Dreamobjects.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox2.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox2.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/ftp.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Ftp.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/ftpcurl.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Ftpcurl.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/googledrive.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Googledrive.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/googlestorage.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Googlestorage.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/googlestoragejson.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Googlestoragejson.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/idrivesync.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Idrivesync.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/onedrive.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Onedrive.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/onedrivebusiness.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Onedrivebusiness.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/s3.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/S3.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/sftp.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Sftp.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/sftpcurl.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Sftpcurl.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/sugarsync.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Sugarsync.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/webdav.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Webdav.php',
+ 'administrator/components/com_akeeba/BackupEngine/Scan/large.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Scan/Large.php',
+
+ 'administrator/components/com_akeeba/Controller/Alice.php',
+ 'administrator/components/com_akeeba/Controller/Discover.php',
+ 'administrator/components/com_akeeba/Controller/IncludeFolders.php',
+ 'administrator/components/com_akeeba/Controller/MultipleDatabases.php',
+ 'administrator/components/com_akeeba/Controller/RegExDatabaseFilters.php',
+ 'administrator/components/com_akeeba/Controller/RegExFileFilters.php',
+ 'administrator/components/com_akeeba/Controller/RemoteFiles.php',
+ 'administrator/components/com_akeeba/Controller/S3Import.php',
+ 'administrator/components/com_akeeba/Controller/Upload.php',
+
+ 'administrator/components/com_akeeba/Model/Alice.php',
+ 'administrator/components/com_akeeba/Model/Discover.php',
+ 'administrator/components/com_akeeba/Model/IncludeFolders.php',
+ 'administrator/components/com_akeeba/Model/MultipleDatabases.php',
+ 'administrator/components/com_akeeba/Model/RegExDatabaseFilters.php',
+ 'administrator/components/com_akeeba/Model/RegExFileFilters.php',
+ 'administrator/components/com_akeeba/Model/RemoteFiles.php',
+ 'administrator/components/com_akeeba/Model/S3Import.php',
+ 'administrator/components/com_akeeba/Model/Upload.php',
+
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Components.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Extensiondirs.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Extensionfiles.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Languages.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Modules.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Plugins.php',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Filter/Templates.php',
+
+ // Additional ANGIE installers which are not used in Core
+ 'administrator/components/com_akeeba/Master/Installers/abi.ini',
+ 'administrator/components/com_akeeba/Master/Installers/abi.jpa',
+ 'administrator/components/com_akeeba/Master/Installers/angie-generic.ini',
+ 'administrator/components/com_akeeba/Master/Installers/angie-generic.jpa',
+ ),
+ 'folders' => array(
+ // Pro component features
+ 'administrator/components/com_akeeba/Alice',
+ 'administrator/components/com_akeeba/BackupPlatform/Joomla3x/Config/Pro',
+ 'administrator/components/com_akeeba/View/Alice',
+ 'administrator/components/com_akeeba/View/Discover',
+ 'administrator/components/com_akeeba/View/IncludeFolders',
+ 'administrator/components/com_akeeba/View/MultipleDatabases',
+ 'administrator/components/com_akeeba/View/RegExDatabaseFilters',
+ 'administrator/components/com_akeeba/View/RegExFileFilter',
+ 'administrator/components/com_akeeba/View/RemoteFiles',
+ 'administrator/components/com_akeeba/View/S3Import',
+ 'administrator/components/com_akeeba/View/Upload',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Connector',
+ )
+ );
+
+ /**
+ * Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and
+ * some files inevitably become obsolete and need to be removed.
+ *
+ * @var array
+ */
+ protected $removeFilesAllVersions = array(
+ 'files' => array(
+ // Outdated CLI scripts
+ 'cli/akeeba-update.php',
+
+ // Outdated media files
+ 'media/com_akeeba/icons/akeeba-48.png',
+ 'media/com_akeeba/icons/akeeba-warning-48.png',
+ 'media/com_akeeba/icons/arrow_small.png',
+ 'media/com_akeeba/icons/error_small.png',
+ 'media/com_akeeba/icons/ok_small.png',
+ 'media/com_akeeba/icons/reload.png',
+ 'media/com_akeeba/icons/scheduling-32.png',
+ 'media/com_akeeba/icons/update.png',
+
+ 'media/com_akeeba/js/akeebajq.js',
+ 'media/com_akeeba/js/akeebajqui.js',
+ 'media/com_akeeba/js/akeebaui.js',
+ 'media/com_akeeba/js/akeebauipro.js',
+ 'media/com_akeeba/js/alice.js',
+ 'media/com_akeeba/js/backup.js',
+ 'media/com_akeeba/js/configuration.js',
+ 'media/com_akeeba/js/confwiz.js',
+ 'media/com_akeeba/js/dbef.js',
+ 'media/com_akeeba/js/eff.js',
+ 'media/com_akeeba/js/encryption.js',
+ 'media/com_akeeba/js/fsfilter.js',
+ 'media/com_akeeba/js/gui-helpers.js',
+ 'media/com_akeeba/js/jquery.js',
+ 'media/com_akeeba/js/jquery-ui.js',
+ 'media/com_akeeba/js/multidb.js',
+ 'media/com_akeeba/js/regexdbfilter.js',
+ 'media/com_akeeba/js/regexfsfilter.js',
+ 'media/com_akeeba/js/restore.js',
+ 'media/com_akeeba/js/stepper.js',
+ 'media/com_akeeba/js/system.js',
+ 'media/com_akeeba/js/transfer.js',
+
+ // Old CLI backup scripts, obsolete since 3.5.0, removed in 4.0.0
+ 'administrator/components/com_akeeba/backup.php',
+ 'administrator/components/com_akeeba/altbackup.php',
+
+ // Files used in version 4.2, but before 5.0
+ // -- Back-end
+ 'administrator/components/com_akeeba/dispatcher.php',
+ 'administrator/components/com_akeeba/toolbar.php',
+ 'administrator/components/com_akeeba/views/backup/view.html.php',
+ 'administrator/components/com_akeeba/views/backup/tmpl/default.php',
+ 'administrator/components/com_akeeba/views/restore/view.html.php',
+ 'administrator/components/com_akeeba/views/restore/tmpl/default.php',
+ 'administrator/components/com_akeeba/views/restore/tmpl/restore.php',
+ 'administrator/components/com_akeeba/views/transfer/view.html.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default_dialogs.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default_manualtransfer.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default_prerequisites.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default_remoteconnection.php',
+ 'administrator/components/com_akeeba/views/transfer/tmpl/default_upload.php',
+
+ // -- Front-end
+ 'components/com_akeeba/dispatcher.php',
+
+ // Integrity check (obsolete)
+ 'administrator/components/com_akeeba/fileslist.php',
+
+ // Blade files (replaced by regular PHP view files)
+ "administrator/components/com_akeeba/View/Alice/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Backup/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Backup/tmpl/script.blade.php",
+ "administrator/components/com_akeeba/View/Browser/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/ErrorModal.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/FolderBrowser.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/FTPBrowser.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/FTPConnectionTest.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/ProfileName.blade.php",
+ "administrator/components/com_akeeba/View/CommonTemplates/tmpl/SFTPBrowser.blade.php",
+ "administrator/components/com_akeeba/View/Configuration/tmpl/confwiz_modal.blade.php",
+ "administrator/components/com_akeeba/View/Configuration/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/ConfigurationWizard/tmpl/wizard.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/footer.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/icons_advanced.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/icons_basic.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/icons_includeexclude.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/icons_troubleshooting.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/oneclick.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/profile.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/sidebar_backup.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/sidebar_status.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/warning_phpversion.blade.php",
+ "administrator/components/com_akeeba/View/ControlPanel/tmpl/warnings.blade.php",
+ "administrator/components/com_akeeba/View/DatabaseFilters/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/DatabaseFilters/tmpl/tabular.blade.php",
+ "administrator/components/com_akeeba/View/Discover/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Discover/tmpl/discover.blade.php",
+ "administrator/components/com_akeeba/View/FileFilters/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/FileFilters/tmpl/tabular.blade.php",
+ "administrator/components/com_akeeba/View/IncludeFolders/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Log/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Manage/tmpl/comment.blade.php",
+ "administrator/components/com_akeeba/View/Manage/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Manage/tmpl/howtorestore_modal.blade.php",
+ "administrator/components/com_akeeba/View/Manage/tmpl/manage_column.blade.php",
+ "administrator/components/com_akeeba/View/MultipleDatabases/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Profiles/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Profiles/tmpl/form.blade.php",
+ "administrator/components/com_akeeba/View/RegExDatabaseFilters/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/RegExFileFilter/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/RemoteFiles/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/RemoteFiles/tmpl/dlprogress.blade.php",
+ "administrator/components/com_akeeba/View/Restore/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Restore/tmpl/restore.blade.php",
+ "administrator/components/com_akeeba/View/S3Import/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/S3Import/tmpl/downloading.blade.php",
+ "administrator/components/com_akeeba/View/Schedule/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Schedule/tmpl/default_checkbackups.blade.php",
+ "administrator/components/com_akeeba/View/Schedule/tmpl/default_runbackups.blade.php",
+ "administrator/components/com_akeeba/View/Transfer/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Transfer/tmpl/default_manualtransfer.blade.php",
+ "administrator/components/com_akeeba/View/Transfer/tmpl/default_prerequisites.blade.php",
+ "administrator/components/com_akeeba/View/Transfer/tmpl/default_remoteconnection.blade.php",
+ "administrator/components/com_akeeba/View/Transfer/tmpl/default_upload.blade.php",
+ "administrator/components/com_akeeba/View/Upload/tmpl/default.blade.php",
+ "administrator/components/com_akeeba/View/Upload/tmpl/done.blade.php",
+ "administrator/components/com_akeeba/View/Upload/tmpl/error.blade.php",
+ "administrator/components/com_akeeba/View/Upload/tmpl/uploading.blade.php",
+
+ // Dropbox v1 integration
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/dropbox.ini',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Dropbox.php',
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Dropbox.php',
+
+ // Obsolete Azure files
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Azure/Credentials/Sharedsignature.php',
+
+ // Obsolete AES-128 CTR implementation in Javascript
+ 'media/com_akeeba/js/Encryption.min.js',
+ 'media/com_akeeba/js/Encryption.min.map',
+
+ // PHP 7.2 compatibility
+ 'administrator/components/com_akeeba/BackupEngine/Base/Object.php',
+
+ // Obsolete media files
+ 'media/com_akeeba/icons/akeeba-ui-32.png',
+ 'media/com_akeeba/changelog.png',
+ ),
+ 'folders' => array(
+ // Directories used up to version 4.1 (inclusive)
+ // -- Back-end
+ 'administrator/components/com_akeeba/akeeba',
+ 'administrator/components/com_akeeba/plugins',
+ // -- Front-end
+ 'components/com_akeeba/views',
+
+ // Directories used in version 4.2, but before 5.0
+ // -- Back-end
+ 'administrator/components/com_akeeba/alice',
+ 'administrator/components/com_akeeba/assets',
+ 'administrator/components/com_akeeba/controllers',
+ 'administrator/components/com_akeeba/engine',
+ 'administrator/components/com_akeeba/helpers',
+ 'administrator/components/com_akeeba/models',
+ 'administrator/components/com_akeeba/platform',
+ 'administrator/components/com_akeeba/tables',
+ 'administrator/components/com_akeeba/views/alices',
+ 'administrator/components/com_akeeba/views/browser',
+ 'administrator/components/com_akeeba/views/buadmin',
+ 'administrator/components/com_akeeba/views/config',
+ 'administrator/components/com_akeeba/views/confwiz',
+ 'administrator/components/com_akeeba/views/cpanel',
+ 'administrator/components/com_akeeba/views/dbef',
+ 'administrator/components/com_akeeba/views/discover',
+ 'administrator/components/com_akeeba/views/eff',
+ 'administrator/components/com_akeeba/views/fsfilter',
+ 'administrator/components/com_akeeba/views/log',
+ 'administrator/components/com_akeeba/views/multidb',
+ 'administrator/components/com_akeeba/views/profiles',
+ 'administrator/components/com_akeeba/views/regexdbfilter',
+ 'administrator/components/com_akeeba/views/regexfsfilter',
+ 'administrator/components/com_akeeba/views/remotefiles',
+ 'administrator/components/com_akeeba/views/s3import',
+ 'administrator/components/com_akeeba/views/schedule',
+ 'administrator/components/com_akeeba/views/updates',
+ 'administrator/components/com_akeeba/views/upload',
+ // -- Front-end
+ 'components/com_akeeba/controllers',
+ 'components/com_akeeba/models',
+
+ // Outdated media directories
+ 'media/com_akeeba/theme',
+
+ // Obsolete Plugins
+ 'plugins/system/aklazy',
+ 'plugins/system/srp',
+
+ // Obsolete Modules
+ 'administrator/modules/mod_akadmin',
+
+ // Dropbox v1 integration
+ 'administrator/components/com_akeeba/BackupEngine/Postproc/Connector/Dropbox',
+ )
+ );
+
+ /**
+ * Runs on installation
+ *
+ * @param JInstallerAdapterComponent $parent The parent object
+ *
+ * @return void
+ */
+ public function install($parent)
+ {
+ if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
+ {
+ define('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH', 1);
+ }
+ }
+
+ /**
+ * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
+ * tell Joomla! if it should abort the installation.
+ *
+ * @param string $type Installation type (install, update, discover_install)
+ * @param JInstallerAdapterComponent $parent Parent object
+ *
+ * @return boolean True to let the installation proceed, false to halt the installation
+ */
+ public function preflight($type, $parent)
+ {
+ $this->isPaid = is_dir($parent->getParent()->getPath('source') . '/backend/AliceEngine');
+
+ $result = parent::preflight($type, $parent);
+
+ if (!$result)
+ {
+ return $result;
+ }
+
+ // Move the server key file from /akeeba or /engine to /BackupEngine
+ $componentPath = JPATH_ADMINISTRATOR . '/components/com_akeeba';
+ $fromFile = $componentPath . '/akeeba/serverkey.php';
+ $toFile = $componentPath . '/BackupEngine/serverkey.php';
+
+ if (!file_exists($fromFile))
+ {
+ $fromFile = $componentPath . '/engine/serverkey.php';
+ }
+
+ if (@file_exists($fromFile) && !@file_exists($toFile))
+ {
+ $toPath = $componentPath . '/BackupEngine';
+
+ if (class_exists('JLoader') && method_exists('JLoader', 'import'))
+ {
+ JLoader::import('joomla.filesystem.folder');
+ JLoader::import('joomla.filesystem.file');
+ }
+
+ if (@is_dir($componentPath) && !@is_dir($toPath))
+ {
+ JFolder::create($toPath);
+ }
+
+ if (@is_dir($toPath))
+ {
+ JFile::copy($fromFile, $toFile);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
+ * or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
+ * database updates and similar housekeeping functions.
+ *
+ * @param string $type install, update or discover_update
+ * @param JInstallerAdapterComponent $parent Parent object
+ */
+ function postflight($type, $parent)
+ {
+ // Let's install common tables
+ $container = null;
+ $model = null;
+
+ if (class_exists('FOF30\\Container\\Container'))
+ {
+ try
+ {
+ $container = \FOF30\Container\Container::getInstance('com_akeeba');
+ }
+ catch (\Exception $e)
+ {
+ $container = null;
+ }
+ }
+
+ if (is_object($container) && class_exists('FOF30\\Container\\Container') && ($container instanceof \FOF30\Container\Container))
+ {
+ /** @var \Akeeba\Backup\Admin\Model\UsageStatistics $model */
+ try
+ {
+ $model = $container->factory->model('UsageStatistics')->tmpInstance();
+ }
+ catch (\Exception $e)
+ {
+ $model = null;
+ }
+ }
+
+ if (is_object($model) && class_exists('Akeeba\\Backup\\Admin\\Model\\UsageStatistics')
+ && ($model instanceof Akeeba\Backup\Admin\Model\UsageStatistics)
+ && method_exists($model, 'checkAndFixCommonTables'))
+ {
+ try
+ {
+ $model->checkAndFixCommonTables();
+ }
+ catch (Exception $e)
+ {
+ // Do nothing if that failed.
+ }
+ }
+
+ // Parent method
+ parent::postflight($type, $parent);
+
+ // Add ourselves to the list of extensions depending on Akeeba FEF
+ $this->addDependency('file_fef', $this->componentName);
+
+ // Uninstall post-installation messages we are no longer using
+ $this->uninstallObsoletePostinstallMessages();
+
+ // Remove the update sites for this component on installation. The update sites are now handled at the package
+ // level.
+ $this->removeObsoleteUpdateSites($parent);
+
+ // Remove the FOF 2.x update sites (annoying leftovers)
+ $this->removeFOFUpdateSites();
+
+ // If this is a new installation tell it to NOT mark the backup profiles as configured.
+ if (defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
+ {
+ $this->markProfilesAsNotConfiguredYet();
+ }
+
+ // This is an update of an existing installation
+ if (!defined('AKEEBA_THIS_IS_INSTALLATION_FROM_SCRATCH'))
+ {
+ // Migrate profiles if necessary
+ $this->migrateProfiles();
+ }
+ }
+
+ /**
+ * Override this method to display a custom component installation message if you so wish
+ *
+ * @param \JInstallerAdapterComponent $parent Parent class calling us
+ */
+ protected function renderPostInstallation($parent)
+ {
+ try
+ {
+ $this->warnAboutJSNPowerAdmin();
+ }
+ catch (Exception $e)
+ {
+ // Don't sweat if the site's db croaks while I'm checking for 3PD software that causes trouble
+ }
+
+ // Load the version file
+ if (!defined('AKEEBA_PRO'))
+ {
+ @include_once JPATH_ADMINISTRATOR . '/components/com_akeeba/version.php';
+ }
+
+ if (!defined('AKEEBA_PRO'))
+ {
+ define('AKEEBA_PRO', '0');
+ }
+
+ $videoTutorialURL = 'https://www.akeebabackup.com/videos/1212-akeeba-backup-core.html';
+
+ if (AKEEBA_PRO)
+ {
+ $videoTutorialURL = 'https://www.akeebabackup.com/videos/1213-akeeba-backup-for-joomla-pro.html';
+ }
+
+ ?>
+ We are sorry that you decided to uninstall Akeeba Backup. Please let us know why by using the Contact Us form on our site. We + appreciate your feedback; it helps us develop better software!
+ getQuery(true) + ->delete($db->qn('#__postinstall_messages')) + ->where($db->qn('title_key') . ' = ' . $db->q($obsoleteKey)); + try + { + $db->setQuery($query)->execute(); + } + catch (Exception $e) + { + // Do nothing + } + } + } + + /** + * The PowerAdmin extension makes menu items disappear. People assume it's our fault. JSN PowerAdmin authors don't + * own up to their software's issue. I have no choice but to warn our users about the faulty third party software. + */ + private function warnAboutJSNPowerAdmin() + { + $db = JFactory::getDbo(); + + $query = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->qn('#__extensions')) + ->where($db->qn('type') . ' = ' . $db->q('component')) + ->where($db->qn('element') . ' = ' . $db->q('com_poweradmin')) + ->where($db->qn('enabled') . ' = ' . $db->q('1')); + $hasPowerAdmin = $db->setQuery($query)->loadResult(); + + if (!$hasPowerAdmin) + { + return; + } + + $query = $db->getQuery(true) + ->select('manifest_cache') + ->from($db->qn('#__extensions')) + ->where($db->qn('type') . ' = ' . $db->q('component')) + ->where($db->qn('element') . ' = ' . $db->q('com_poweradmin')) + ->where($db->qn('enabled') . ' = ' . $db->q('1')); + $paramsJson = $db->setQuery($query)->loadResult(); + + $className = class_exists('JRegistry') ? 'JRegistry' : '\Joomla\Registry\Registry'; + + /** @var \Joomla\Registry\Registry $jsnPAManifest */ + $jsnPAManifest = new $className(); + $jsnPAManifest->loadString($paramsJson, 'JSON'); + $version = $jsnPAManifest->get('version', '0.0.0'); + + if (version_compare($version, '2.1.2', 'ge')) + { + return; + } + + echo <<< HTML ++ We have detected that you are using JSN PowerAdmin on your site. This software ignores Joomla! standards and + hides the Component menu items to {$this->componentName} in the administrator backend of your site. Unfortunately we + can't provide support for third party software. Please contact the developers of JSN PowerAdmin for support + regarding this issue. +
++ Tip: You can disable JSN PowerAdmin to see the menu items to Akeeba Backup. +
+$this->sql" : ''); + } + else + { + return JText::_('JLIB_DATABASE_FUNCTION_NOERROR'); + } + } + + /** + * Test to see if the connector is available. + * + * @return boolean True on success, false otherwise. + * + * @since 11.1 + * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use FOFDatabaseDriver::isSupported() instead. + */ + public static function test() + { + if (class_exists('JLog')) + { + JLog::add('FOFDatabase::test() is deprecated. Use FOFDatabaseDriver::isSupported() instead.', JLog::WARNING, 'deprecated'); + } + + return static::isSupported(); + } +} diff --git a/deployed/akeeba/libraries/fof/database/driver.php b/deployed/akeeba/libraries/fof/database/driver.php new file mode 100644 index 00000000..27b17efc --- /dev/null +++ b/deployed/akeeba/libraries/fof/database/driver.php @@ -0,0 +1,2255 @@ +getFilename(); + + // Only load for php files. + if (!$file->isFile() || $file->getExtension() != 'php') + { + continue; + } + + // Block the ext/mysql driver for PHP 7 + if ($fileName === 'mysql.php' && PHP_MAJOR_VERSION >= 7) + { + continue; + } + + // Derive the class name from the type. + $class = str_ireplace('.php', '', 'FOFDatabaseDriver' . ucfirst(trim($fileName))); + + // If the class doesn't exist we have nothing left to do but look at the next type. We did our best. + if (!class_exists($class)) + { + continue; + } + + // Sweet! Our class exists, so now we just need to know if it passes its test method. + if ($class::isSupported()) + { + // Connector names should not have file extensions. + $connectors[] = str_ireplace('.php', '', $fileName); + } + } + + return $connectors; + } + + /** + * Method to return a FOFDatabaseDriver instance based on the given options. There are three global options and then + * the rest are specific to the database driver. The 'driver' option defines which FOFDatabaseDriver class is + * used for the connection -- the default is 'mysqli'. The 'database' option determines which database is to + * be used for the connection. The 'select' option determines whether the connector should automatically select + * the chosen database. + * + * Instances are unique to the given options and new objects are only created when a unique options array is + * passed into the method. This ensures that we don't end up with unnecessary database connection resources. + * + * @param array $options Parameters to be passed to the database driver. + * + * @return FOFDatabaseDriver A database object. + * + * @since 11.1 + * @throws RuntimeException + */ + public static function getInstance($options = array()) + { + // Sanitize the database connector options. + $options['driver'] = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli'; + $options['database'] = (isset($options['database'])) ? $options['database'] : null; + $options['select'] = (isset($options['select'])) ? $options['select'] : true; + + // If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver. + if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7) + { + // Check if we have support for the other MySQL drivers + $mysqliSupported = FOFDatabaseDriverMysqli::isSupported(); + $pdoMysqlSupported = FOFDatabaseDriverPdomysql::isSupported(); + + // If neither is supported, then the user cannot use MySQL; throw an exception + if (!$mysqliSupported && !$pdoMysqlSupported) + { + throw new RuntimeException( + 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.' + . ' Also, this system does not support MySQLi or PDO MySQL. Cannot instantiate database driver.' + ); + } + + // Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver + if ($mysqliSupported) + { + if (class_exists('JLog')) + { + JLog::add( + 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `mysqli` instead.', + JLog::WARNING, + 'deprecated' + ); + } + + $options['driver'] = 'mysqli'; + } + else + { + if (class_exists('JLog')) + { + JLog::add( + 'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver. Trying `pdomysql` instead.', + JLog::WARNING, + 'deprecated' + ); + } + + $options['driver'] = 'pdomysql'; + } + } + + // Get the options signature for the database connector. + $signature = md5(serialize($options)); + + // If we already have a database connector instance for these options then just use that. + if (empty(self::$instances[$signature])) + { + // Derive the class name from the driver. + $class = 'FOFDatabaseDriver' . ucfirst(strtolower($options['driver'])); + + // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best. + if (!class_exists($class)) + { + throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver'])); + } + + // Create our new FOFDatabaseDriver connector based on the options given. + try + { + $instance = new $class($options); + } + catch (RuntimeException $e) + { + throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e); + } + + // Set the new connector to the global instances based on signature. + self::$instances[$signature] = $instance; + } + + return self::$instances[$signature]; + } + + /** + * Splits a string of multiple queries into an array of individual queries. + * + * @param string $sql Input SQL string with which to split into individual queries. + * + * @return array The queries from the input string separated into an array. + * + * @since 11.1 + */ + public static function splitSql($sql) + { + $start = 0; + $open = false; + $char = ''; + $end = strlen($sql); + $queries = array(); + + for ($i = 0; $i < $end; $i++) + { + $current = substr($sql, $i, 1); + + if (($current == '"' || $current == '\'')) + { + $n = 2; + + while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i) + { + $n++; + } + + if ($n % 2 == 0) + { + if ($open) + { + if ($current == $char) + { + $open = false; + $char = ''; + } + } + else + { + $open = true; + $char = $current; + } + } + } + + if (($current == ';' && !$open) || $i == $end - 1) + { + $queries[] = substr($sql, $start, ($i - $start + 1)); + $start = $i + 1; + } + } + + return $queries; + } + + /** + * Magic method to provide method alias support for quote() and quoteName(). + * + * @param string $method The called method. + * @param array $args The array of arguments passed to the method. + * + * @return mixed The aliased method's return value or null. + * + * @since 11.1 + */ + public function __call($method, $args) + { + if (empty($args)) + { + return; + } + + switch ($method) + { + case 'q': + return $this->quote($args[0], isset($args[1]) ? $args[1] : true); + break; + case 'qn': + return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null); + break; + } + } + + /** + * Constructor. + * + * @param array $options List of options used to configure the connection + * + * @since 11.1 + */ + public function __construct($options) + { + // Initialise object variables. + $this->_database = (isset($options['database'])) ? $options['database'] : ''; + $this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_'; + $this->connection = array_key_exists('connection', $options) ? $options['connection'] : null; + + $this->count = 0; + $this->errorNum = 0; + $this->log = array(); + + // Set class options. + $this->options = $options; + } + + /** + * Alter database's character set, obtaining query string from protected member. + * + * @param string $dbName The database name that will be altered + * + * @return string The query that alter the database query string + * + * @since 12.2 + * @throws RuntimeException + */ + public function alterDbCharacterSet($dbName) + { + if (is_null($dbName)) + { + throw new RuntimeException('Database name must not be null.'); + } + + $this->setQuery($this->getAlterDbCharacterSet($dbName)); + + return $this->execute(); + } + + /** + * Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is + * wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the + * conversion. This prevents data corruption. + * + * @param string $tableName The name of the table to alter + * @param boolean $rethrow True to rethrow database exceptions. Default: false (exceptions are suppressed) + * + * @return boolean True if successful + * + * @since CMS 3.5.0 + * @throws RuntimeException If the table name is empty + * @throws Exception Relayed from the database layer if a database error occurs and $rethrow == true + */ + public function alterTableCharacterSet($tableName, $rethrow = false) + { + if (is_null($tableName)) + { + throw new RuntimeException('Table name must not be null.'); + } + + $queries = $this->getAlterTableCharacterSet($tableName); + + if (empty($queries)) + { + return false; + } + + $hasTransaction = true; + + try + { + $this->transactionStart(); + } + catch (Exception $e) + { + $hasTransaction = false; + $this->lockTable($tableName); + } + + foreach ($queries as $query) + { + try + { + $this->setQuery($query)->execute(); + } + catch (Exception $e) + { + if ($hasTransaction) + { + $this->transactionRollback(); + } + else + { + $this->unlockTables(); + } + + if ($rethrow) + { + throw $e; + } + + return false; + } + } + + if ($hasTransaction) + { + try + { + $this->transactionCommit(); + } + catch (Exception $e) + { + $this->transactionRollback(); + + if ($rethrow) + { + throw $e; + } + + return false; + } + } + else + { + $this->unlockTables(); + } + + return true; + } + + /** + * Connects to the database if needed. + * + * @return void Returns void if the database connected successfully. + * + * @since 12.1 + * @throws RuntimeException + */ + abstract public function connect(); + + /** + * Determines if the connection to the server is active. + * + * @return boolean True if connected to the database engine. + * + * @since 11.1 + */ + abstract public function connected(); + + /** + * Create a new database using information from $options object, obtaining query string + * from protected member. + * + * @param stdClass $options Object used to pass user and database name to database driver. + * This object must have "db_name" and "db_user" set. + * @param boolean $utf True if the database supports the UTF-8 character set. + * + * @return string The query that creates database + * + * @since 12.2 + * @throws RuntimeException + */ + public function createDatabase($options, $utf = true) + { + if (is_null($options)) + { + throw new RuntimeException('$options object must not be null.'); + } + elseif (empty($options->db_name)) + { + throw new RuntimeException('$options object must have db_name set.'); + } + elseif (empty($options->db_user)) + { + throw new RuntimeException('$options object must have db_user set.'); + } + + $this->setQuery($this->getCreateDatabaseQuery($options, $utf)); + + return $this->execute(); + } + + /** + * Disconnects the database. + * + * @return void + * + * @since 12.1 + */ + abstract public function disconnect(); + + /** + * Adds a function callable just before disconnecting the database. Parameter of the callable is $this FOFDatabaseDriver + * + * @param callable $callable Function to call in disconnect() method just before disconnecting from database + * + * @return void + * + * @since CMS 3.1.2 + */ + public function addDisconnectHandler($callable) + { + $this->disconnectHandlers[] = $callable; + } + + /** + * Drops a table from the database. + * + * @param string $table The name of the database table to drop. + * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. + * + * @return FOFDatabaseDriver Returns this object to support chaining. + * + * @since 11.4 + * @throws RuntimeException + */ + public abstract function dropTable($table, $ifExists = true); + + /** + * Escapes a string for usage in an SQL statement. + * + * @param string $text The string to be escaped. + * @param boolean $extra Optional parameter to provide extra escaping. + * + * @return string The escaped string. + * + * @since 11.1 + */ + abstract public function escape($text, $extra = false); + + /** + * Method to fetch a row from the result set cursor as an array. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 11.1 + */ + abstract protected function fetchArray($cursor = null); + + /** + * Method to fetch a row from the result set cursor as an associative array. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 11.1 + */ + abstract protected function fetchAssoc($cursor = null); + + /** + * Method to fetch a row from the result set cursor as an object. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * @param string $class The class name to use for the returned row object. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 11.1 + */ + abstract protected function fetchObject($cursor = null, $class = 'stdClass'); + + /** + * Method to free up the memory used for the result set. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return void + * + * @since 11.1 + */ + abstract protected function freeResult($cursor = null); + + /** + * Get the number of affected rows for the previous executed SQL statement. + * + * @return integer The number of affected rows. + * + * @since 11.1 + */ + abstract public function getAffectedRows(); + + /** + * Return the query string to alter the database character set. + * + * @param string $dbName The database name + * + * @return string The query that alter the database query string + * + * @since 12.2 + */ + public function getAlterDbCharacterSet($dbName) + { + $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; + + return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`'; + } + + /** + * Get the query strings to alter the character set and collation of a table. + * + * @param string $tableName The name of the table + * + * @return string[] The queries required to alter the table's character set and collation + * + * @since CMS 3.5.0 + */ + public function getAlterTableCharacterSet($tableName) + { + $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; + $collation = $charset . '_general_ci'; + + $quotedTableName = $this->quoteName($tableName); + + $queries = array(); + $queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation"; + + /** + * We also need to convert each text column, modifying their character set and collation. This allows us to + * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column. + */ + $sql = "SHOW FULL COLUMNS FROM $quotedTableName"; + $this->setQuery($sql); + $columns = $this->loadAssocList(); + $columnMods = array(); + + if (is_array($columns)) + { + foreach ($columns as $column) + { + // Make sure we are redefining only columns which do support a collation + $col = (object) $column; + + if (empty($col->Collation)) + { + continue; + } + + // Default new collation: utf8_general_ci or utf8mb4_general_ci + $newCollation = $charset . '_general_ci'; + $collationParts = explode('_', $col->Collation); + + /** + * If the collation is in the form charset_collationType_ci or charset_collationType we have to change + * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT + * utf8mb4_general_ci). + */ + if (count($collationParts) >= 2) + { + $ci = array_pop($collationParts); + $collationType = array_pop($collationParts); + $newCollation = $charset . '_' . $collationType . '_' . $ci; + + /** + * When the last part of the old collation is not _ci we have a charset_collationType format, + * something like utf8_bin. Therefore the new collation only has *two* parts. + */ + if ($ci != 'ci') + { + $newCollation = $charset . '_' . $ci; + } + } + + // If the old and new collation is the same we don't have to change the collation type + if (strtolower($newCollation) == strtolower($col->Collation)) + { + continue; + } + + $null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL'; + $default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'"; + $columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default"; + } + } + + if (count($columnMods)) + { + $queries[] = "ALTER TABLE $quotedTableName " . + implode(',', $columnMods) . + " CHARACTER SET $charset COLLATE $collation"; + } + + return $queries; + } + + /** + * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used + * when the server doesn't support UTF-8 Multibyte. + * + * @param string $query The query to convert + * + * @return string The converted query + */ + public function convertUtf8mb4QueryToUtf8($query) + { + if ($this->hasUTF8mb4Support()) + { + return $query; + } + + // If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert + $beginningOfQuery = substr($query, 0, 12); + $beginningOfQuery = strtoupper($beginningOfQuery); + + if (!in_array($beginningOfQuery, array('ALTER TABLE ', 'CREATE TABLE'))) + { + return $query; + } + + // Replace utf8mb4 with utf8 + return str_replace('utf8mb4', 'utf8', $query); + } + + /** + * Return the query string to create new Database. + * Each database driver, other than MySQL, need to override this member to return correct string. + * + * @param stdClass $options Object used to pass user and database name to database driver. + * This object must have "db_name" and "db_user" set. + * @param boolean $utf True if the database supports the UTF-8 character set. + * + * @return string The query that creates database + * + * @since 12.2 + */ + protected function getCreateDatabaseQuery($options, $utf) + { + if ($utf) + { + $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; + + return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '`'; + } + + return 'CREATE DATABASE ' . $this->quoteName($options->db_name); + } + + /** + * Method to get the database collation in use by sampling a text field of a table in the database. + * + * @return mixed The collation in use by the database or boolean false if not supported. + * + * @since 11.1 + */ + abstract public function getCollation(); + + /** + * Method to get the database connection collation, as reported by the driver. If the connector doesn't support + * reporting this value please return an empty string. + * + * @return string + */ + public function getConnectionCollation() + { + return ''; + } + + /** + * Method that provides access to the underlying database connection. Useful for when you need to call a + * proprietary method such as postgresql's lo_* methods. + * + * @return resource The underlying database connection resource. + * + * @since 11.1 + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the total number of SQL statements executed by the database driver. + * + * @return integer + * + * @since 11.1 + */ + public function getCount() + { + return $this->count; + } + + /** + * Gets the name of the database used by this conneciton. + * + * @return string + * + * @since 11.4 + */ + protected function getDatabase() + { + return $this->_database; + } + + /** + * Returns a PHP date() function compliant date format for the database driver. + * + * @return string The format string. + * + * @since 11.1 + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Get the database driver SQL statement log. + * + * @return array SQL statements executed by the database driver. + * + * @since 11.1 + */ + public function getLog() + { + return $this->log; + } + + /** + * Get the database driver SQL statement log. + * + * @return array SQL statements executed by the database driver. + * + * @since CMS 3.1.2 + */ + public function getTimings() + { + return $this->timings; + } + + /** + * Get the database driver SQL statement log. + * + * @return array SQL statements executed by the database driver. + * + * @since CMS 3.1.2 + */ + public function getCallStacks() + { + return $this->callStacks; + } + + /** + * Get the minimum supported database version. + * + * @return string The minimum version number for the database driver. + * + * @since 12.1 + */ + public function getMinimum() + { + return static::$dbMinimum; + } + + /** + * Get the null or zero representation of a timestamp for the database driver. + * + * @return string Null or zero representation of a timestamp. + * + * @since 11.1 + */ + public function getNullDate() + { + return $this->nullDate; + } + + /** + * Get the number of returned rows for the previous executed SQL statement. + * + * @param resource $cursor An optional database cursor resource to extract the row count from. + * + * @return integer The number of returned rows. + * + * @since 11.1 + */ + abstract public function getNumRows($cursor = null); + + /** + * Get the common table prefix for the database driver. + * + * @return string The common database table prefix. + * + * @since 11.1 + */ + public function getPrefix() + { + return $this->tablePrefix; + } + + /** + * Gets an exporter class object. + * + * @return FOFDatabaseExporter An exporter object. + * + * @since 12.1 + * @throws RuntimeException + */ + public function getExporter() + { + // Derive the class name from the driver. + $class = 'FOFDatabaseExporter' . ucfirst($this->name); + + // Make sure we have an exporter class for this driver. + if (!class_exists($class)) + { + // If it doesn't exist we are at an impasse so throw an exception. + throw new RuntimeException('Database Exporter not found.'); + } + + $o = new $class; + $o->setDbo($this); + + return $o; + } + + /** + * Gets an importer class object. + * + * @return FOFDatabaseImporter An importer object. + * + * @since 12.1 + * @throws RuntimeException + */ + public function getImporter() + { + // Derive the class name from the driver. + $class = 'FOFDatabaseImporter' . ucfirst($this->name); + + // Make sure we have an importer class for this driver. + if (!class_exists($class)) + { + // If it doesn't exist we are at an impasse so throw an exception. + throw new RuntimeException('Database Importer not found'); + } + + $o = new $class; + $o->setDbo($this); + + return $o; + } + + /** + * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the + * class name. + * + * @return string + * + * @since CMS 3.5.0 + */ + public function getName() + { + if (empty($this->name)) + { + $className = get_class($this); + $className = str_replace('FOFDatabaseDriver', '', $className); + $this->name = strtolower($className); + } + + return $this->name; + } + + /** + * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it + * will attempt guessing the server family type from the driver name. If this is not possible the driver name will + * be returned instead. + * + * @return string + * + * @since CMS 3.5.0 + */ + public function getServerType() + { + if (empty($this->serverType)) + { + $name = $this->getName(); + + if (stristr($name, 'mysql') !== false) + { + $this->serverType = 'mysql'; + } + elseif (stristr($name, 'postgre') !== false) + { + $this->serverType = 'postgresql'; + } + elseif (stristr($name, 'oracle') !== false) + { + $this->serverType = 'oracle'; + } + elseif (stristr($name, 'sqlite') !== false) + { + $this->serverType = 'sqlite'; + } + elseif (stristr($name, 'sqlsrv') !== false) + { + $this->serverType = 'mssql'; + } + elseif (stristr($name, 'mssql') !== false) + { + $this->serverType = 'mssql'; + } + else + { + $this->serverType = $name; + } + } + + return $this->serverType; + } + + /** + * Get the current query object or a new FOFDatabaseQuery object. + * + * @param boolean $new False to return the current query object, True to return a new FOFDatabaseQuery object. + * + * @return FOFDatabaseQuery The current query object or a new object extending the FOFDatabaseQuery class. + * + * @since 11.1 + * @throws RuntimeException + */ + public function getQuery($new = false) + { + if ($new) + { + // Derive the class name from the driver. + $class = 'FOFDatabaseQuery' . ucfirst($this->name); + + // Make sure we have a query class for this driver. + if (!class_exists($class)) + { + // If it doesn't exist we are at an impasse so throw an exception. + throw new RuntimeException('Database Query Class not found.'); + } + + return new $class($this); + } + else + { + return $this->sql; + } + } + + /** + * Get a new iterator on the current query. + * + * @param string $column An option column to use as the iterator key. + * @param string $class The class of object that is returned. + * + * @return FOFDatabaseIterator A new database iterator. + * + * @since 12.1 + * @throws RuntimeException + */ + public function getIterator($column = null, $class = 'stdClass') + { + // Derive the class name from the driver. + $iteratorClass = 'FOFDatabaseIterator' . ucfirst($this->name); + + // Make sure we have an iterator class for this driver. + if (!class_exists($iteratorClass)) + { + // If it doesn't exist we are at an impasse so throw an exception. + throw new RuntimeException(sprintf('class *%s* is not defined', $iteratorClass)); + } + + // Return a new iterator + return new $iteratorClass($this->execute(), $column, $class); + } + + /** + * Retrieves field information about the given tables. + * + * @param string $table The name of the database table. + * @param boolean $typeOnly True (default) to only return field types. + * + * @return array An array of fields by table. + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function getTableColumns($table, $typeOnly = true); + + /** + * Shows the table CREATE statement that creates the given tables. + * + * @param mixed $tables A table name or a list of table names. + * + * @return array A list of the create SQL for the tables. + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function getTableCreate($tables); + + /** + * Retrieves field information about the given tables. + * + * @param mixed $tables A table name or a list of table names. + * + * @return array An array of keys for the table(s). + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function getTableKeys($tables); + + /** + * Method to get an array of all tables in the database. + * + * @return array An array of all the tables in the database. + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function getTableList(); + + /** + * Determine whether or not the database engine supports UTF-8 character encoding. + * + * @return boolean True if the database engine supports UTF-8 character encoding. + * + * @since 11.1 + * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use hasUTFSupport() instead + */ + public function getUTFSupport() + { + if (class_exists('JLog')) + { + JLog::add('FOFDatabaseDriver::getUTFSupport() is deprecated. Use FOFDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated'); + } + + return $this->hasUTFSupport(); + } + + /** + * Determine whether or not the database engine supports UTF-8 character encoding. + * + * @return boolean True if the database engine supports UTF-8 character encoding. + * + * @since 12.1 + */ + public function hasUTFSupport() + { + return $this->utf; + } + + /** + * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to + * MySQL databases. + * + * @return boolean True if the database engine supports UTF-8 Multibyte. + * + * @since CMS 3.5.0 + */ + public function hasUTF8mb4Support() + { + return $this->utf8mb4; + } + + /** + * Get the version of the database connector + * + * @return string The database connector version. + * + * @since 11.1 + */ + abstract public function getVersion(); + + /** + * Method to get the auto-incremented value from the last INSERT statement. + * + * @return mixed The value of the auto-increment field from the last inserted row. + * + * @since 11.1 + */ + abstract public function insertid(); + + /** + * Inserts a row into a table based on an object's properties. + * + * @param string $table The name of the database table to insert into. + * @param object &$object A reference to an object whose public properties match the table fields. + * @param string $key The name of the primary key. If provided the object property is updated. + * + * @return boolean True on success. + * + * @since 11.1 + * @throws RuntimeException + */ + public function insertObject($table, &$object, $key = null) + { + $fields = array(); + $values = array(); + + // Iterate over the object variables to build the query fields and values. + foreach (get_object_vars($object) as $k => $v) + { + // Only process non-null scalars. + if (is_array($v) or is_object($v) or $v === null) + { + continue; + } + + // Ignore any internal fields. + if ($k[0] == '_') + { + continue; + } + + // Prepare and sanitize the fields and values for the database query. + $fields[] = $this->quoteName($k); + $values[] = $this->quote($v); + } + + // Create the base insert statement. + $query = $this->getQuery(true) + ->insert($this->quoteName($table)) + ->columns($fields) + ->values(implode(',', $values)); + + // Set the query and execute the insert. + $this->setQuery($query); + + if (!$this->execute()) + { + return false; + } + + // Update the primary key if it exists. + $id = $this->insertid(); + + if ($key && $id && is_string($key)) + { + $object->$key = $id; + } + + return true; + } + + /** + * Method to check whether the installed database version is supported by the database driver + * + * @return boolean True if the database version is supported + * + * @since 12.1 + */ + public function isMinimumVersion() + { + return version_compare($this->getVersion(), static::$dbMinimum) >= 0; + } + + /** + * Method to get the first row of the result set from the database query as an associative array + * of ['field_name' => 'row_value']. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadAssoc() + { + $this->connect(); + + $ret = null; + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get the first row from the result set as an associative array. + if ($array = $this->fetchAssoc($cursor)) + { + $ret = $array; + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $ret; + } + + /** + * Method to get an array of the result set rows from the database query where each row is an associative array + * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to + * a sequential numeric array. + * + * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted + * behavior and should be avoided. + * + * @param string $key The name of a field on which to key the result array. + * @param string $column An optional column name. Instead of the whole row, only this column value will be in + * the result array. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadAssocList($key = null, $column = null) + { + $this->connect(); + + $array = array(); + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get all of the rows from the result set. + while ($row = $this->fetchAssoc($cursor)) + { + $value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row; + + if ($key) + { + $array[$row[$key]] = $value; + } + else + { + $array[] = $value; + } + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $array; + } + + /** + * Method to get an array of values from the $offset field in each row of the result set from + * the database query. + * + * @param integer $offset The row offset to use to build the result array. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadColumn($offset = 0) + { + $this->connect(); + + $array = array(); + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get all of the rows from the result set as arrays. + while ($row = $this->fetchArray($cursor)) + { + $array[] = $row[$offset]; + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $array; + } + + /** + * Method to get the next row in the result set from the database query as an object. + * + * @param string $class The class name to use for the returned row object. + * + * @return mixed The result of the query as an array, false if there are no more rows. + * + * @since 11.1 + * @throws RuntimeException + * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use getIterator() instead + */ + public function loadNextObject($class = 'stdClass') + { + if (class_exists('JLog')) + { + JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); + } + + $this->connect(); + + static $cursor = null; + + // Execute the query and get the result set cursor. + if ( is_null($cursor) ) + { + if (!($cursor = $this->execute())) + { + return $this->errorNum ? null : false; + } + } + + // Get the next row from the result set as an object of type $class. + if ($row = $this->fetchObject($cursor, $class)) + { + return $row; + } + + // Free up system resources and return. + $this->freeResult($cursor); + $cursor = null; + + return false; + } + + /** + * Method to get the next row in the result set from the database query as an array. + * + * @return mixed The result of the query as an array, false if there are no more rows. + * + * @since 11.1 + * @throws RuntimeException + * @deprecated 4.0 (CMS) Use FOFDatabaseDriver::getIterator() instead + */ + public function loadNextRow() + { + if (class_exists('JLog')) + { + JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated'); + } + + $this->connect(); + + static $cursor = null; + + // Execute the query and get the result set cursor. + if ( is_null($cursor) ) + { + if (!($cursor = $this->execute())) + { + return $this->errorNum ? null : false; + } + } + + // Get the next row from the result set as an object of type $class. + if ($row = $this->fetchArray($cursor)) + { + return $row; + } + + // Free up system resources and return. + $this->freeResult($cursor); + $cursor = null; + + return false; + } + + /** + * Method to get the first row of the result set from the database query as an object. + * + * @param string $class The class name to use for the returned row object. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadObject($class = 'stdClass') + { + $this->connect(); + + $ret = null; + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get the first row from the result set as an object of type $class. + if ($object = $this->fetchObject($cursor, $class)) + { + $ret = $object; + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $ret; + } + + /** + * Method to get an array of the result set rows from the database query where each row is an object. The array + * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. + * + * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted + * behavior and should be avoided. + * + * @param string $key The name of a field on which to key the result array. + * @param string $class The class name to use for the returned row objects. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadObjectList($key = '', $class = 'stdClass') + { + $this->connect(); + + $array = array(); + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get all of the rows from the result set as objects of type $class. + while ($row = $this->fetchObject($cursor, $class)) + { + if ($key) + { + $array[$row->$key] = $row; + } + else + { + $array[] = $row; + } + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $array; + } + + /** + * Method to get the first field of the first row of the result set from the database query. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadResult() + { + $this->connect(); + + $ret = null; + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get the first row from the result set as an array. + if ($row = $this->fetchArray($cursor)) + { + $ret = $row[0]; + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $ret; + } + + /** + * Method to get the first row of the result set from the database query as an array. Columns are indexed + * numerically so the first column in the result set would be accessible via $row[0], etc. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadRow() + { + $this->connect(); + + $ret = null; + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get the first row from the result set as an array. + if ($row = $this->fetchArray($cursor)) + { + $ret = $row; + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $ret; + } + + /** + * Method to get an array of the result set rows from the database query where each row is an array. The array + * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. + * + * NOTE: Choosing to key the result array by a non-unique field can result in unwanted + * behavior and should be avoided. + * + * @param string $key The name of a field on which to key the result array. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadRowList($key = null) + { + $this->connect(); + + $array = array(); + + // Execute the query and get the result set cursor. + if (!($cursor = $this->execute())) + { + return null; + } + + // Get all of the rows from the result set as arrays. + while ($row = $this->fetchArray($cursor)) + { + if ($key !== null) + { + $array[$row[$key]] = $row; + } + else + { + $array[] = $row; + } + } + + // Free up system resources and return. + $this->freeResult($cursor); + + return $array; + } + + /** + * Locks a table in the database. + * + * @param string $tableName The name of the table to unlock. + * + * @return FOFDatabaseDriver Returns this object to support chaining. + * + * @since 11.4 + * @throws RuntimeException + */ + public abstract function lockTable($tableName); + + /** + * Quotes and optionally escapes a string to database requirements for use in database queries. + * + * @param mixed $text A string or an array of strings to quote. + * @param boolean $escape True (default) to escape the string, false to leave it unchanged. + * + * @return string The quoted input string. + * + * @note Accepting an array of strings was added in 12.3. + * @since 11.1 + */ + public function quote($text, $escape = true) + { + if (is_array($text)) + { + foreach ($text as $k => $v) + { + $text[$k] = $this->quote($v, $escape); + } + + return $text; + } + else + { + return '\'' . ($escape ? $this->escape($text) : $text) . '\''; + } + } + + /** + * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection + * risks and reserved word conflicts. + * + * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. + * Each type supports dot-notation name. + * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be + * same length of $name; if is null there will not be any AS part for string or array element. + * + * @return mixed The quote wrapped name, same type of $name. + * + * @since 11.1 + */ + public function quoteName($name, $as = null) + { + if (is_string($name)) + { + $quotedName = $this->quoteNameStr(explode('.', $name)); + + $quotedAs = ''; + + if (!is_null($as)) + { + settype($as, 'array'); + $quotedAs .= ' AS ' . $this->quoteNameStr($as); + } + + return $quotedName . $quotedAs; + } + else + { + $fin = array(); + + if (is_null($as)) + { + foreach ($name as $str) + { + $fin[] = $this->quoteName($str); + } + } + elseif (is_array($name) && (count($name) == count($as))) + { + $count = count($name); + + for ($i = 0; $i < $count; $i++) + { + $fin[] = $this->quoteName($name[$i], $as[$i]); + } + } + + return $fin; + } + } + + /** + * Quote strings coming from quoteName call. + * + * @param array $strArr Array of strings coming from quoteName dot-explosion. + * + * @return string Dot-imploded string of quoted parts. + * + * @since 11.3 + */ + protected function quoteNameStr($strArr) + { + $parts = array(); + $q = $this->nameQuote; + + foreach ($strArr as $part) + { + if (is_null($part)) + { + continue; + } + + if (strlen($q) == 1) + { + $parts[] = $q . $part . $q; + } + else + { + $parts[] = $q{0} . $part . $q{1}; + } + } + + return implode('.', $parts); + } + + /** + * This function replaces a string identifier $prefix with the string held is the + * tablePrefix class variable. + * + * @param string $sql The SQL statement to prepare. + * @param string $prefix The common table prefix. + * + * @return string The processed SQL statement. + * + * @since 11.1 + */ + public function replacePrefix($sql, $prefix = '#__') + { + $startPos = 0; + $literal = ''; + + $sql = trim($sql); + $n = strlen($sql); + + while ($startPos < $n) + { + $ip = strpos($sql, $prefix, $startPos); + + if ($ip === false) + { + break; + } + + $j = strpos($sql, "'", $startPos); + $k = strpos($sql, '"', $startPos); + + if (($k !== false) && (($k < $j) || ($j === false))) + { + $quoteChar = '"'; + $j = $k; + } + else + { + $quoteChar = "'"; + } + + if ($j === false) + { + $j = $n; + } + + $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos)); + $startPos = $j; + + $j = $startPos + 1; + + if ($j >= $n) + { + break; + } + + // Quote comes first, find end of quote + while (true) + { + $k = strpos($sql, $quoteChar, $j); + $escaped = false; + + if ($k === false) + { + break; + } + + $l = $k - 1; + + while ($l >= 0 && $sql{$l} == '\\') + { + $l--; + $escaped = !$escaped; + } + + if ($escaped) + { + $j = $k + 1; + continue; + } + + break; + } + + if ($k === false) + { + // Error in the query - no end quote; ignore it + break; + } + + $literal .= substr($sql, $startPos, $k - $startPos + 1); + $startPos = $k + 1; + } + + if ($startPos < $n) + { + $literal .= substr($sql, $startPos, $n - $startPos); + } + + return $literal; + } + + /** + * Renames a table in the database. + * + * @param string $oldTable The name of the table to be renamed + * @param string $newTable The new name for the table. + * @param string $backup Table prefix + * @param string $prefix For the table - used to rename constraints in non-mysql databases + * + * @return FOFDatabaseDriver Returns this object to support chaining. + * + * @since 11.4 + * @throws RuntimeException + */ + public abstract function renameTable($oldTable, $newTable, $backup = null, $prefix = null); + + /** + * Select a database for use. + * + * @param string $database The name of the database to select for use. + * + * @return boolean True if the database was successfully selected. + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function select($database); + + /** + * Sets the database debugging state for the driver. + * + * @param boolean $level True to enable debugging. + * + * @return boolean The old debugging level. + * + * @since 11.1 + */ + public function setDebug($level) + { + $previous = $this->debug; + $this->debug = (bool) $level; + + return $previous; + } + + /** + * Sets the SQL statement string for later execution. + * + * @param mixed $query The SQL statement to set either as a FOFDatabaseQuery object or a string. + * @param integer $offset The affected row offset to set. + * @param integer $limit The maximum affected rows to set. + * + * @return FOFDatabaseDriver This object to support method chaining. + * + * @since 11.1 + */ + public function setQuery($query, $offset = 0, $limit = 0) + { + $this->sql = $query; + + if ($query instanceof FOFDatabaseQueryLimitable) + { + if (!$limit && $query->limit) + { + $limit = $query->limit; + } + + if (!$offset && $query->offset) + { + $offset = $query->offset; + } + + $query->setLimit($limit, $offset); + } + else + { + $this->limit = (int) max(0, $limit); + $this->offset = (int) max(0, $offset); + } + + return $this; + } + + /** + * Set the connection to use UTF-8 character encoding. + * + * @return boolean True on success. + * + * @since 11.1 + */ + abstract public function setUtf(); + + /** + * Method to commit a transaction. + * + * @param boolean $toSavepoint If true, commit to the last savepoint. + * + * @return void + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function transactionCommit($toSavepoint = false); + + /** + * Method to roll back a transaction. + * + * @param boolean $toSavepoint If true, rollback to the last savepoint. + * + * @return void + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function transactionRollback($toSavepoint = false); + + /** + * Method to initialize a transaction. + * + * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. + * + * @return void + * + * @since 11.1 + * @throws RuntimeException + */ + abstract public function transactionStart($asSavepoint = false); + + /** + * Method to truncate a table. + * + * @param string $table The table to truncate + * + * @return void + * + * @since 11.3 + * @throws RuntimeException + */ + public function truncateTable($table) + { + $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table)); + $this->execute(); + } + + /** + * Updates a row in a table based on an object's properties. + * + * @param string $table The name of the database table to update. + * @param object &$object A reference to an object whose public properties match the table fields. + * @param array $key The name of the primary key. + * @param boolean $nulls True to update null fields or false to ignore them. + * + * @return boolean True on success. + * + * @since 11.1 + * @throws RuntimeException + */ + public function updateObject($table, &$object, $key, $nulls = false) + { + $fields = array(); + $where = array(); + + if (is_string($key)) + { + $key = array($key); + } + + if (is_object($key)) + { + $key = (array) $key; + } + + // Create the base update statement. + $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; + + // Iterate over the object variables to build the query fields/value pairs. + foreach (get_object_vars($object) as $k => $v) + { + // Only process scalars that are not internal fields. + if (is_array($v) or is_object($v) or $k[0] == '_') + { + continue; + } + + // Set the primary key to the WHERE clause instead of a field to update. + if (in_array($k, $key)) + { + $where[] = $this->quoteName($k) . '=' . $this->quote($v); + continue; + } + + // Prepare and sanitize the fields and values for the database query. + if ($v === null) + { + // If the value is null and we want to update nulls then set it. + if ($nulls) + { + $val = 'NULL'; + } + // If the value is null and we do not want to update nulls then ignore this field. + else + { + continue; + } + } + // The field is not null so we prep it for update. + else + { + $val = $this->quote($v); + } + + // Add the field to be updated. + $fields[] = $this->quoteName($k) . '=' . $val; + } + + // We don't have any fields to update. + if (empty($fields)) + { + return true; + } + + // Set the query and execute the update. + $this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where))); + + return $this->execute(); + } + + /** + * Execute the SQL statement. + * + * @return mixed A database cursor resource on success, boolean false on failure. + * + * @since 12.1 + * @throws RuntimeException + */ + abstract public function execute(); + + /** + * Unlocks tables in the database. + * + * @return FOFDatabaseDriver Returns this object to support chaining. + * + * @since 11.4 + * @throws RuntimeException + */ + public abstract function unlockTables(); +} diff --git a/deployed/akeeba/libraries/fof/database/driver/joomla.php b/deployed/akeeba/libraries/fof/database/driver/joomla.php new file mode 100644 index 00000000..63b67627 --- /dev/null +++ b/deployed/akeeba/libraries/fof/database/driver/joomla.php @@ -0,0 +1,778 @@ +dbo = JFactory::getDbo(); + + $reflection = new ReflectionClass($this->dbo); + + try + { + $refProp = $reflection->getProperty('nameQuote'); + $refProp->setAccessible(true); + $this->nameQuote = $refProp->getValue($this->dbo); + } + catch (Exception $e) + { + $this->nameQuote = '`'; + } + } + + public function close() + { + if (method_exists($this->dbo, 'close')) + { + $this->dbo->close(); + } + elseif (method_exists($this->dbo, 'disconnect')) + { + $this->dbo->disconnect(); + } + } + + public function disconnect() + { + $this->close(); + } + + public function open() + { + if (method_exists($this->dbo, 'open')) + { + $this->dbo->open(); + } + elseif (method_exists($this->dbo, 'connect')) + { + $this->dbo->connect(); + } + } + + public function connect() + { + $this->open(); + } + + public function connected() + { + if (method_exists($this->dbo, 'connected')) + { + return $this->dbo->connected(); + } + + return true; + } + + public function escape($text, $extra = false) + { + return $this->dbo->escape($text, $extra); + } + + public function execute() + { + if (method_exists($this->dbo, 'execute')) + { + return $this->dbo->execute(); + } + + return $this->dbo->query(); + } + + public function getAffectedRows() + { + if (method_exists($this->dbo, 'getAffectedRows')) + { + return $this->dbo->getAffectedRows(); + } + + return 0; + } + + public function getCollation() + { + if (method_exists($this->dbo, 'getCollation')) + { + return $this->dbo->getCollation(); + } + + return 'utf8_general_ci'; + } + + public function getConnection() + { + if (method_exists($this->dbo, 'getConnection')) + { + return $this->dbo->getConnection(); + } + + return null; + } + + public function getCount() + { + if (method_exists($this->dbo, 'getCount')) + { + return $this->dbo->getCount(); + } + + return 0; + } + + public function getDateFormat() + { + if (method_exists($this->dbo, 'getDateFormat')) + { + return $this->dbo->getDateFormat(); + } + + return 'Y-m-d H:i:s';; + } + + public function getMinimum() + { + if (method_exists($this->dbo, 'getMinimum')) + { + return $this->dbo->getMinimum(); + } + + return '5.0.40'; + } + + public function getNullDate() + { + if (method_exists($this->dbo, 'getNullDate')) + { + return $this->dbo->getNullDate(); + } + + return '0000-00-00 00:00:00'; + } + + public function getNumRows($cursor = null) + { + if (method_exists($this->dbo, 'getNumRows')) + { + return $this->dbo->getNumRows($cursor); + } + + return 0; + } + + public function getQuery($new = false) + { + if (method_exists($this->dbo, 'getQuery')) + { + return $this->dbo->getQuery($new); + } + + return null; + } + + public function getTableColumns($table, $typeOnly = true) + { + if (method_exists($this->dbo, 'getTableColumns')) + { + return $this->dbo->getTableColumns($table, $typeOnly); + } + + $result = $this->dbo->getTableFields(array($table), $typeOnly); + + return $result[$table]; + } + + public function getTableKeys($tables) + { + if (method_exists($this->dbo, 'getTableKeys')) + { + return $this->dbo->getTableKeys($tables); + } + + return array(); + } + + public function getTableList() + { + if (method_exists($this->dbo, 'getTableList')) + { + return $this->dbo->getTableList(); + } + + return array(); + } + + public function getVersion() + { + if (method_exists($this->dbo, 'getVersion')) + { + return $this->dbo->getVersion(); + } + + return '5.0.40'; + } + + public function insertid() + { + if (method_exists($this->dbo, 'insertid')) + { + return $this->dbo->insertid(); + } + + return null; + } + + public function insertObject($table, &$object, $key = null) + { + if (method_exists($this->dbo, 'insertObject')) + { + return $this->dbo->insertObject($table, $object, $key); + } + + return null; + } + + public function loadAssoc() + { + if (method_exists($this->dbo, 'loadAssoc')) + { + return $this->dbo->loadAssoc(); + } + + return null; + } + + public function loadAssocList($key = null, $column = null) + { + if (method_exists($this->dbo, 'loadAssocList')) + { + return $this->dbo->loadAssocList($key, $column); + } + + return null; + } + + public function loadObject($class = 'stdClass') + { + if (method_exists($this->dbo, 'loadObject')) + { + return $this->dbo->loadObject($class); + } + + return null; + } + + public function loadObjectList($key = '', $class = 'stdClass') + { + if (method_exists($this->dbo, 'loadObjectList')) + { + return $this->dbo->loadObjectList($key, $class); + } + + return null; + } + + public function loadResult() + { + if (method_exists($this->dbo, 'loadResult')) + { + return $this->dbo->loadResult(); + } + + return null; + } + + public function loadRow() + { + if (method_exists($this->dbo, 'loadRow')) + { + return $this->dbo->loadRow(); + } + + return null; + } + + public function loadRowList($key = null) + { + if (method_exists($this->dbo, 'loadRowList')) + { + return $this->dbo->loadRowList($key); + } + + return null; + } + + public function lockTable($tableName) + { + if (method_exists($this->dbo, 'lockTable')) + { + return $this->dbo->lockTable($this); + } + + return $this; + } + + public function quote($text, $escape = true) + { + if (method_exists($this->dbo, 'quote')) + { + return $this->dbo->quote($text, $escape); + } + + return $text; + } + + public function select($database) + { + if (method_exists($this->dbo, 'select')) + { + return $this->dbo->select($database); + } + + return false; + } + + public function setQuery($query, $offset = 0, $limit = 0) + { + if (method_exists($this->dbo, 'setQuery')) + { + return $this->dbo->setQuery($query, $offset, $limit); + } + + return false; + } + + public function transactionCommit($toSavepoint = false) + { + if (method_exists($this->dbo, 'transactionCommit')) + { + $this->dbo->transactionCommit($toSavepoint); + } + } + + public function transactionRollback($toSavepoint = false) + { + if (method_exists($this->dbo, 'transactionRollback')) + { + $this->dbo->transactionRollback($toSavepoint); + } + } + + public function transactionStart($asSavepoint = false) + { + if (method_exists($this->dbo, 'transactionStart')) + { + $this->dbo->transactionStart($asSavepoint); + } + } + + public function unlockTables() + { + if (method_exists($this->dbo, 'unlockTables')) + { + return $this->dbo->unlockTables(); + } + + return $this; + } + + public function updateObject($table, &$object, $key, $nulls = false) + { + if (method_exists($this->dbo, 'updateObject')) + { + return $this->dbo->updateObject($table, $object, $key, $nulls); + } + + return false; + } + + public function getLog() + { + if (method_exists($this->dbo, 'getLog')) + { + return $this->dbo->getLog(); + } + + return array(); + } + + public function dropTable($table, $ifExists = true) + { + if (method_exists($this->dbo, 'dropTable')) + { + return $this->dbo->dropTable($table, $ifExists); + } + + return $this; + } + + public function getTableCreate($tables) + { + if (method_exists($this->dbo, 'getTableCreate')) + { + return $this->dbo->getTableCreate($tables); + } + + return array(); + } + + public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) + { + if (method_exists($this->dbo, 'renameTable')) + { + return $this->dbo->renameTable($oldTable, $newTable, $backup, $prefix); + } + + return $this; + } + + public function setUtf() + { + if (method_exists($this->dbo, 'setUtf')) + { + return $this->dbo->setUtf(); + } + + return false; + } + + + protected function freeResult($cursor = null) + { + return false; + } + + /** + * Method to get an array of values from the $offset field in each row of the result set from + * the database query. + * + * @param integer $offset The row offset to use to build the result array. + * + * @return mixed The return value or null if the query failed. + * + * @since 11.1 + * @throws RuntimeException + */ + public function loadColumn($offset = 0) + { + if (method_exists($this->dbo, 'loadColumn')) + { + return $this->dbo->loadColumn($offset); + } + + return $this->dbo->loadResultArray($offset); + } + + /** + * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection + * risks and reserved word conflicts. + * + * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. + * Each type supports dot-notation name. + * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be + * same length of $name; if is null there will not be any AS part for string or array element. + * + * @return mixed The quote wrapped name, same type of $name. + * + * @since 11.1 + */ + public function quoteName($name, $as = null) + { + if (is_string($name)) + { + $quotedName = $this->quoteNameStr(explode('.', $name)); + + $quotedAs = ''; + + if (!is_null($as)) + { + settype($as, 'array'); + $quotedAs .= ' AS ' . $this->quoteNameStr($as); + } + + return $quotedName . $quotedAs; + } + else + { + $fin = array(); + + if (is_null($as)) + { + foreach ($name as $str) + { + $fin[] = $this->quoteName($str); + } + } + elseif (is_array($name) && (count($name) == count($as))) + { + $count = count($name); + + for ($i = 0; $i < $count; $i++) + { + $fin[] = $this->quoteName($name[$i], $as[$i]); + } + } + + return $fin; + } + } + + /** + * Quote strings coming from quoteName call. + * + * @param array $strArr Array of strings coming from quoteName dot-explosion. + * + * @return string Dot-imploded string of quoted parts. + * + * @since 11.3 + */ + protected function quoteNameStr($strArr) + { + $parts = array(); + $q = $this->nameQuote; + + foreach ($strArr as $part) + { + if (is_null($part)) + { + continue; + } + + if (strlen($q) == 1) + { + $parts[] = $q . $part . $q; + } + else + { + $parts[] = $q{0} . $part . $q{1}; + } + } + + return implode('.', $parts); + } + + /** + * Gets the error message from the database connection. + * + * @param boolean $escaped True to escape the message string for use in JavaScript. + * + * @return string The error message for the most recent query. + * + * @since 11.1 + */ + public function getErrorMsg($escaped = false) + { + if (method_exists($this->dbo, 'getErrorMsg')) + { + $errorMessage = $this->dbo->getErrorMsg(); + } + else + { + $errorMessage = $this->errorMsg; + } + + if ($escaped) + { + return addslashes($errorMessage); + } + + return $errorMessage; + } + + /** + * Gets the error number from the database connection. + * + * @return integer The error number for the most recent query. + * + * @since 11.1 + * @deprecated 13.3 (Platform) & 4.0 (CMS) + */ + public function getErrorNum() + { + if (method_exists($this->dbo, 'getErrorNum')) + { + $errorNum = $this->dbo->getErrorNum(); + } + else + { + $errorNum = $this->getErrorNum; + } + + return $errorNum; + } + + /** + * Return the most recent error message for the database connector. + * + * @param boolean $showSQL True to display the SQL statement sent to the database as well as the error. + * + * @return string The error message for the most recent query. + */ + public function stderr($showSQL = false) + { + if (method_exists($this->dbo, 'stderr')) + { + return $this->dbo->stderr($showSQL); + } + + return parent::stderr($showSQL); + } + + /** + * Magic method to proxy all calls to the loaded database driver object + */ + public function __call($name, array $arguments) + { + if (is_null($this->dbo)) + { + throw new Exception('FOF database driver is not loaded'); + } + + if (method_exists($this->dbo, $name) || in_array($name, array('q', 'nq', 'qn', 'query'))) + { + switch ($name) + { + case 'execute': + $name = 'query'; + break; + + case 'q': + $name = 'quote'; + break; + + case 'qn': + case 'nq': + switch (count($arguments)) + { + case 0 : + $result = $this->quoteName(); + break; + case 1 : + $result = $this->quoteName($arguments[0]); + break; + case 2: + default: + $result = $this->quoteName($arguments[0], $arguments[1]); + break; + } + return $result; + + break; + } + + switch (count($arguments)) + { + case 0 : + $result = $this->dbo->$name(); + break; + case 1 : + $result = $this->dbo->$name($arguments[0]); + break; + case 2: + $result = $this->dbo->$name($arguments[0], $arguments[1]); + break; + case 3: + $result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2]); + break; + case 4: + $result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]); + break; + case 5: + $result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]); + break; + default: + // Resort to using call_user_func_array for many segments + $result = call_user_func_array(array($this->dbo, $name), $arguments); + } + + if (class_exists('JDatabase') && is_object($result) && ($result instanceof JDatabase)) + { + return $this; + } + + return $result; + } + else + { + throw new \Exception('Method ' . $name . ' not found in FOFDatabase'); + } + } + + public function __get($name) + { + if (isset($this->dbo->$name) || property_exists($this->dbo, $name)) + { + return $this->dbo->$name; + } + else + { + $this->dbo->$name = null; + user_error('Database driver does not support property ' . $name); + } + } + + public function __set($name, $value) + { + if (isset($this->dbo->name) || property_exists($this->dbo, $name)) + { + $this->dbo->$name = $value; + } + else + { + $this->dbo->$name = null; + user_error('Database driver not support property ' . $name); + } + } +} diff --git a/deployed/akeeba/libraries/fof/database/driver/mysql.php b/deployed/akeeba/libraries/fof/database/driver/mysql.php new file mode 100644 index 00000000..5e35879f --- /dev/null +++ b/deployed/akeeba/libraries/fof/database/driver/mysql.php @@ -0,0 +1,577 @@ += 7) + { + throw new RuntimeException( + 'This driver is unsupported in PHP 7, please use the MySQLi or PDO MySQL driver instead.' + ); + } + + // Get some basic values from the options. + $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost'; + $options['user'] = (isset($options['user'])) ? $options['user'] : 'root'; + $options['password'] = (isset($options['password'])) ? $options['password'] : ''; + $options['database'] = (isset($options['database'])) ? $options['database'] : ''; + $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true; + + // Finalize initialisation. + parent::__construct($options); + } + + /** + * Destructor. + * + * @since 12.1 + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Connects to the database if needed. + * + * @return void Returns void if the database connected successfully. + * + * @since 12.1 + * @throws RuntimeException + */ + public function connect() + { + if ($this->connection) + { + return; + } + + // Make sure the MySQL extension for PHP is installed and enabled. + if (!self::isSupported()) + { + throw new RuntimeException('Could not connect to MySQL.'); + } + + // Attempt to connect to the server. + if (!($this->connection = @ mysql_connect($this->options['host'], $this->options['user'], $this->options['password'], true))) + { + throw new RuntimeException('Could not connect to MySQL.'); + } + + // Set sql_mode to non_strict mode + mysql_query("SET @@SESSION.sql_mode = '';", $this->connection); + + // If auto-select is enabled select the given database. + if ($this->options['select'] && !empty($this->options['database'])) + { + $this->select($this->options['database']); + } + + // Pre-populate the UTF-8 Multibyte compatibility flag based on server version + $this->utf8mb4 = $this->serverClaimsUtf8mb4Support(); + + // Set the character set (needed for MySQL 4.1.2+). + $this->utf = $this->setUtf(); + + // Turn MySQL profiling ON in debug mode: + if ($this->debug && $this->hasProfiling()) + { + mysql_query("SET profiling = 1;", $this->connection); + } + } + + /** + * Disconnects the database. + * + * @return void + * + * @since 12.1 + */ + public function disconnect() + { + // Close the connection. + if (is_resource($this->connection)) + { + foreach ($this->disconnectHandlers as $h) + { + call_user_func_array($h, array( &$this)); + } + + mysql_close($this->connection); + } + + $this->connection = null; + } + + /** + * Method to escape a string for usage in an SQL statement. + * + * @param string $text The string to be escaped. + * @param boolean $extra Optional parameter to provide extra escaping. + * + * @return string The escaped string. + * + * @since 12.1 + */ + public function escape($text, $extra = false) + { + $this->connect(); + + $result = mysql_real_escape_string($text, $this->getConnection()); + + if ($extra) + { + $result = addcslashes($result, '%_'); + } + + return $result; + } + + /** + * Test to see if the MySQL connector is available. + * + * @return boolean True on success, false otherwise. + * + * @since 12.1 + */ + public static function isSupported() + { + return (function_exists('mysql_connect')); + } + + /** + * Determines if the connection to the server is active. + * + * @return boolean True if connected to the database engine. + * + * @since 12.1 + */ + public function connected() + { + if (is_resource($this->connection)) + { + return @mysql_ping($this->connection); + } + + return false; + } + + /** + * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement. + * + * @return integer The number of affected rows. + * + * @since 12.1 + */ + public function getAffectedRows() + { + $this->connect(); + + return mysql_affected_rows($this->connection); + } + + /** + * Get the number of returned rows for the previous executed SQL statement. + * This command is only valid for statements like SELECT or SHOW that return an actual result set. + * To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows(). + * + * @param resource $cursor An optional database cursor resource to extract the row count from. + * + * @return integer The number of returned rows. + * + * @since 12.1 + */ + public function getNumRows($cursor = null) + { + $this->connect(); + + return mysql_num_rows($cursor ? $cursor : $this->cursor); + } + + /** + * Get the version of the database connector. + * + * @return string The database connector version. + * + * @since 12.1 + */ + public function getVersion() + { + $this->connect(); + + return mysql_get_server_info($this->connection); + } + + /** + * Method to get the auto-incremented value from the last INSERT statement. + * + * @return integer The value of the auto-increment field from the last inserted row. + * + * @since 12.1 + */ + public function insertid() + { + $this->connect(); + + return mysql_insert_id($this->connection); + } + + /** + * Execute the SQL statement. + * + * @return mixed A database cursor resource on success, boolean false on failure. + * + * @since 12.1 + * @throws RuntimeException + */ + public function execute() + { + $this->connect(); + + if (!is_resource($this->connection)) + { + if (class_exists('JLog')) + { + JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database'); + } + throw new RuntimeException($this->errorMsg, $this->errorNum); + } + + // Take a local copy so that we don't modify the original query and cause issues later + $query = $this->replacePrefix((string) $this->sql); + + if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0)) + { + $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit; + } + + // Increment the query counter. + $this->count++; + + // Reset the error values. + $this->errorNum = 0; + $this->errorMsg = ''; + + // If debugging is enabled then let's log the query. + if ($this->debug) + { + // Add the query to the object queue. + $this->log[] = $query; + + if (class_exists('JLog')) + { + JLog::add($query, JLog::DEBUG, 'databasequery'); + } + + $this->timings[] = microtime(true); + } + + // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost. + $this->cursor = @mysql_query($query, $this->connection); + + if ($this->debug) + { + $this->timings[] = microtime(true); + + if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) + { + $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + else + { + $this->callStacks[] = debug_backtrace(); + } + } + + // If an error occurred handle it. + if (!$this->cursor) + { + // Get the error number and message before we execute any more queries. + $this->errorNum = $this->getErrorNumber(); + $this->errorMsg = $this->getErrorMessage($query); + + // Check if the server was disconnected. + if (!$this->connected()) + { + try + { + // Attempt to reconnect. + $this->connection = null; + $this->connect(); + } + // If connect fails, ignore that exception and throw the normal exception. + catch (RuntimeException $e) + { + // Get the error number and message. + $this->errorNum = $this->getErrorNumber(); + $this->errorMsg = $this->getErrorMessage($query); + + // Throw the normal query exception. + if (class_exists('JLog')) + { + JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); + } + + throw new RuntimeException($this->errorMsg, $this->errorNum, $e); + } + + // Since we were able to reconnect, run the query again. + return $this->execute(); + } + // The server was not disconnected. + else + { + // Throw the normal query exception. + if (class_exists('JLog')) + { + JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error'); + } + + throw new RuntimeException($this->errorMsg, $this->errorNum); + } + } + + return $this->cursor; + } + + /** + * Select a database for use. + * + * @param string $database The name of the database to select for use. + * + * @return boolean True if the database was successfully selected. + * + * @since 12.1 + * @throws RuntimeException + */ + public function select($database) + { + $this->connect(); + + if (!$database) + { + return false; + } + + if (!mysql_select_db($database, $this->connection)) + { + throw new RuntimeException('Could not connect to database'); + } + + return true; + } + + /** + * Set the connection to use UTF-8 character encoding. + * + * @return boolean True on success. + * + * @since 12.1 + */ + public function setUtf() + { + // If UTF is not supported return false immediately + if (!$this->utf) + { + return false; + } + + // Make sure we're connected to the server + $this->connect(); + + // Which charset should I use, plain utf8 or multibyte utf8mb4? + $charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8'; + + $result = @mysql_set_charset($charset, $this->connection); + + /** + * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise. + * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd + * masks the server version and reports only its own we can not be sure if the server actually does support + * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we + * catch the error and determine that utf8mb4 is not supported! + */ + if (!$result && $this->utf8mb4) + { + $this->utf8mb4 = false; + $result = @mysql_set_charset('utf8', $this->connection); + } + + return $result; + } + + /** + * Method to fetch a row from the result set cursor as an array. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 12.1 + */ + protected function fetchArray($cursor = null) + { + return mysql_fetch_row($cursor ? $cursor : $this->cursor); + } + + /** + * Method to fetch a row from the result set cursor as an associative array. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 12.1 + */ + protected function fetchAssoc($cursor = null) + { + return mysql_fetch_assoc($cursor ? $cursor : $this->cursor); + } + + /** + * Method to fetch a row from the result set cursor as an object. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * @param string $class The class name to use for the returned row object. + * + * @return mixed Either the next row from the result set or false if there are no more rows. + * + * @since 12.1 + */ + protected function fetchObject($cursor = null, $class = 'stdClass') + { + return mysql_fetch_object($cursor ? $cursor : $this->cursor, $class); + } + + /** + * Method to free up the memory used for the result set. + * + * @param mixed $cursor The optional result set cursor from which to fetch the row. + * + * @return void + * + * @since 12.1 + */ + protected function freeResult($cursor = null) + { + mysql_free_result($cursor ? $cursor : $this->cursor); + } + + /** + * Internal function to check if profiling is available + * + * @return boolean + * + * @since 3.1.3 + */ + private function hasProfiling() + { + try + { + $res = mysql_query("SHOW VARIABLES LIKE 'have_profiling'", $this->connection); + $row = mysql_fetch_assoc($res); + + return isset($row); + } + catch (Exception $e) + { + return false; + } + } + + /** + * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation? + * + * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9. + * + * @return boolean + * + * @since CMS 3.5.0 + */ + private function serverClaimsUtf8mb4Support() + { + $client_version = mysql_get_client_info(); + + if (strpos($client_version, 'mysqlnd') !== false) + { + $client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version); + + return version_compare($client_version, '5.0.9', '>='); + } + else + { + return version_compare($client_version, '5.5.3', '>='); + } + } + + /** + * Return the actual SQL Error number + * + * @return integer The SQL Error number + * + * @since 3.4.6 + */ + protected function getErrorNumber() + { + return (int) mysql_errno($this->connection); + } + + /** + * Return the actual SQL Error message + * + * @param string $query The SQL Query that fails + * + * @return string The SQL Error message + * + * @since 3.4.6 + */ + protected function getErrorMessage($query) + { + $errorMessage = (string) mysql_error($this->connection); + + // Replace the Databaseprefix with `#__` if we are not in Debug + if (!$this->debug) + { + $errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage); + $query = str_replace($this->tablePrefix, '#__', $query); + } + + return $errorMessage . ' SQL=' . $query; + } +} diff --git a/deployed/akeeba/libraries/fof/database/driver/mysqli.php b/deployed/akeeba/libraries/fof/database/driver/mysqli.php new file mode 100644 index 00000000..bc271f1c --- /dev/null +++ b/deployed/akeeba/libraries/fof/database/driver/mysqli.php @@ -0,0 +1,1019 @@ +disconnect(); + } + + /** + * Connects to the database if needed. + * + * @return void Returns void if the database connected successfully. + * + * @since 12.1 + * @throws RuntimeException + */ + public function connect() + { + if ($this->connection) + { + return; + } + + /* + * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we + * have to extract them from the host string. + */ + $port = isset($this->options['port']) ? $this->options['port'] : 3306; + $regex = '/^(?P
' . str_replace('#__', $this->db->getPrefix(), $this) . '';
+ }
+
+ /**
+ * Add a table name to the DELETE clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->delete('#__a')->where('id = 1');
+ *
+ * @param string $table The name of the table to delete from.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function delete($table = null)
+ {
+ $this->type = 'delete';
+ $this->delete = new FOFDatabaseQueryElement('DELETE', null);
+
+ if (!empty($table))
+ {
+ $this->from($table);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to escape a string for usage in an SQL statement.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the escape method directly.
+ *
+ * Note that 'e' is an alias for this method as it is in FOFDatabaseDriver.
+ *
+ * @param string $text The string to be escaped.
+ * @param boolean $extra Optional parameter to provide extra escaping.
+ *
+ * @return string The escaped string.
+ *
+ * @since 11.1
+ * @throws RuntimeException if the internal db property is not a valid object.
+ */
+ public function escape($text, $extra = false)
+ {
+ if (!($this->db instanceof FOFDatabaseDriver))
+ {
+ throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
+ }
+
+ return $this->db->escape($text, $extra);
+ }
+
+ /**
+ * Add a single column, or array of columns to the EXEC clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ * The exec method can, however, be called multiple times in the same query.
+ *
+ * Usage:
+ * $query->exec('a.*')->exec('b.id');
+ * $query->exec(array('a.*', 'b.id'));
+ *
+ * @param mixed $columns A string or an array of field names.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function exec($columns)
+ {
+ $this->type = 'exec';
+
+ if (is_null($this->exec))
+ {
+ $this->exec = new FOFDatabaseQueryElement('EXEC', $columns);
+ }
+ else
+ {
+ $this->exec->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a table to the FROM clause of the query.
+ *
+ * Note that while an array of tables can be provided, it is recommended you use explicit joins.
+ *
+ * Usage:
+ * $query->select('*')->from('#__a');
+ *
+ * @param mixed $tables A string or array of table names.
+ * This can be a FOFDatabaseQuery object (or a child of it) when used
+ * as a subquery in FROM clause along with a value for $subQueryAlias.
+ * @param string $subQueryAlias Alias used when $tables is a FOFDatabaseQuery.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @throws RuntimeException
+ *
+ * @since 11.1
+ */
+ public function from($tables, $subQueryAlias = null)
+ {
+ if (is_null($this->from))
+ {
+ if ($tables instanceof $this)
+ {
+ if (is_null($subQueryAlias))
+ {
+ throw new RuntimeException('JLIB_DATABASE_ERROR_NULL_SUBQUERY_ALIAS');
+ }
+
+ $tables = '( ' . (string) $tables . ' ) AS ' . $this->quoteName($subQueryAlias);
+ }
+
+ $this->from = new FOFDatabaseQueryElement('FROM', $tables);
+ }
+ else
+ {
+ $this->from->append($tables);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Used to get a string to extract year from date column.
+ *
+ * Usage:
+ * $query->select($query->year($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing year to be extracted.
+ *
+ * @return string Returns string to extract year from a date.
+ *
+ * @since 12.1
+ */
+ public function year($date)
+ {
+ return 'YEAR(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract month from date column.
+ *
+ * Usage:
+ * $query->select($query->month($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing month to be extracted.
+ *
+ * @return string Returns string to extract month from a date.
+ *
+ * @since 12.1
+ */
+ public function month($date)
+ {
+ return 'MONTH(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract day from date column.
+ *
+ * Usage:
+ * $query->select($query->day($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing day to be extracted.
+ *
+ * @return string Returns string to extract day from a date.
+ *
+ * @since 12.1
+ */
+ public function day($date)
+ {
+ return 'DAY(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract hour from date column.
+ *
+ * Usage:
+ * $query->select($query->hour($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing hour to be extracted.
+ *
+ * @return string Returns string to extract hour from a date.
+ *
+ * @since 12.1
+ */
+ public function hour($date)
+ {
+ return 'HOUR(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract minute from date column.
+ *
+ * Usage:
+ * $query->select($query->minute($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing minute to be extracted.
+ *
+ * @return string Returns string to extract minute from a date.
+ *
+ * @since 12.1
+ */
+ public function minute($date)
+ {
+ return 'MINUTE(' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract seconds from date column.
+ *
+ * Usage:
+ * $query->select($query->second($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing second to be extracted.
+ *
+ * @return string Returns string to extract second from a date.
+ *
+ * @since 12.1
+ */
+ public function second($date)
+ {
+ return 'SECOND(' . $date . ')';
+ }
+
+ /**
+ * Add a grouping column to the GROUP clause of the query.
+ *
+ * Usage:
+ * $query->group('id');
+ *
+ * @param mixed $columns A string or array of ordering columns.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function group($columns)
+ {
+ if (is_null($this->group))
+ {
+ $this->group = new FOFDatabaseQueryElement('GROUP BY', $columns);
+ }
+ else
+ {
+ $this->group->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * A conditions to the HAVING clause of the query.
+ *
+ * Usage:
+ * $query->group('id')->having('COUNT(id) > 5');
+ *
+ * @param mixed $conditions A string or array of columns.
+ * @param string $glue The glue by which to join the conditions. Defaults to AND.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function having($conditions, $glue = 'AND')
+ {
+ if (is_null($this->having))
+ {
+ $glue = strtoupper($glue);
+ $this->having = new FOFDatabaseQueryElement('HAVING', $conditions, " $glue ");
+ }
+ else
+ {
+ $this->having->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an INNER JOIN clause to the query.
+ *
+ * Usage:
+ * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function innerJoin($condition)
+ {
+ $this->join('INNER', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add a table name to the INSERT clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->insert('#__a')->set('id = 1');
+ * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4');
+ * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4'));
+ *
+ * @param mixed $table The name of the table to insert data into.
+ * @param boolean $incrementField The name of the field to auto increment.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function insert($table, $incrementField=false)
+ {
+ $this->type = 'insert';
+ $this->insert = new FOFDatabaseQueryElement('INSERT INTO', $table);
+ $this->autoIncrementField = $incrementField;
+
+ return $this;
+ }
+
+ /**
+ * Add a JOIN clause to the query.
+ *
+ * Usage:
+ * $query->join('INNER', 'b ON b.id = a.id);
+ *
+ * @param string $type The type of join. This string is prepended to the JOIN keyword.
+ * @param string $conditions A string or array of conditions.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function join($type, $conditions)
+ {
+ if (is_null($this->join))
+ {
+ $this->join = array();
+ }
+
+ $this->join[] = new FOFDatabaseQueryElement(strtoupper($type) . ' JOIN', $conditions);
+
+ return $this;
+ }
+
+ /**
+ * Add a LEFT JOIN clause to the query.
+ *
+ * Usage:
+ * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function leftJoin($condition)
+ {
+ $this->join('LEFT', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Get the length of a string in bytes.
+ *
+ * Note, use 'charLength' to find the number of characters in a string.
+ *
+ * Usage:
+ * query->where($query->length('a').' > 3');
+ *
+ * @param string $value The string to measure.
+ *
+ * @return int
+ *
+ * @since 11.1
+ */
+ public function length($value)
+ {
+ return 'LENGTH(' . $value . ')';
+ }
+
+ /**
+ * Get the null or zero representation of a timestamp for the database driver.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the nullDate method directly.
+ *
+ * Usage:
+ * $query->where('modified_date <> '.$query->nullDate());
+ *
+ * @param boolean $quoted Optionally wraps the null date in database quotes (true by default).
+ *
+ * @return string Null or zero representation of a timestamp.
+ *
+ * @since 11.1
+ */
+ public function nullDate($quoted = true)
+ {
+ if (!($this->db instanceof FOFDatabaseDriver))
+ {
+ throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
+ }
+
+ $result = $this->db->getNullDate($quoted);
+
+ if ($quoted)
+ {
+ return $this->db->quote($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add a ordering column to the ORDER clause of the query.
+ *
+ * Usage:
+ * $query->order('foo')->order('bar');
+ * $query->order(array('foo','bar'));
+ *
+ * @param mixed $columns A string or array of ordering columns.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function order($columns)
+ {
+ if (is_null($this->order))
+ {
+ $this->order = new FOFDatabaseQueryElement('ORDER BY', $columns);
+ }
+ else
+ {
+ $this->order->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an OUTER JOIN clause to the query.
+ *
+ * Usage:
+ * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function outerJoin($condition)
+ {
+ $this->join('OUTER', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Method to quote and optionally escape a string to database requirements for insertion into the database.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the quote method directly.
+ *
+ * Note that 'q' is an alias for this method as it is in FOFDatabaseDriver.
+ *
+ * Usage:
+ * $query->quote('fulltext');
+ * $query->q('fulltext');
+ * $query->q(array('option', 'fulltext'));
+ *
+ * @param mixed $text A string or an array of strings to quote.
+ * @param boolean $escape True to escape the string, false to leave it unchanged.
+ *
+ * @return string The quoted input string.
+ *
+ * @since 11.1
+ * @throws RuntimeException if the internal db property is not a valid object.
+ */
+ public function quote($text, $escape = true)
+ {
+ if (!($this->db instanceof FOFDatabaseDriver))
+ {
+ throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
+ }
+
+ return $this->db->quote($text, $escape);
+ }
+
+ /**
+ * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
+ * risks and reserved word conflicts.
+ *
+ * This method is provided for use where the query object is passed to a function for modification.
+ * If you have direct access to the database object, it is recommended you use the quoteName method directly.
+ *
+ * Note that 'qn' is an alias for this method as it is in FOFDatabaseDriver.
+ *
+ * Usage:
+ * $query->quoteName('#__a');
+ * $query->qn('#__a');
+ *
+ * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
+ * Each type supports dot-notation name.
+ * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be
+ * same length of $name; if is null there will not be any AS part for string or array element.
+ *
+ * @return mixed The quote wrapped name, same type of $name.
+ *
+ * @since 11.1
+ * @throws RuntimeException if the internal db property is not a valid object.
+ */
+ public function quoteName($name, $as = null)
+ {
+ if (!($this->db instanceof FOFDatabaseDriver))
+ {
+ throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
+ }
+
+ return $this->db->quoteName($name, $as);
+ }
+
+ /**
+ * Add a RIGHT JOIN clause to the query.
+ *
+ * Usage:
+ * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id');
+ *
+ * @param string $condition The join condition.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function rightJoin($condition)
+ {
+ $this->join('RIGHT', $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add a single column, or array of columns to the SELECT clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ * The select method can, however, be called multiple times in the same query.
+ *
+ * Usage:
+ * $query->select('a.*')->select('b.id');
+ * $query->select(array('a.*', 'b.id'));
+ *
+ * @param mixed $columns A string or an array of field names.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function select($columns)
+ {
+ $this->type = 'select';
+
+ if (is_null($this->select))
+ {
+ $this->select = new FOFDatabaseQueryElement('SELECT', $columns);
+ }
+ else
+ {
+ $this->select->append($columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a single condition string, or an array of strings to the SET clause of the query.
+ *
+ * Usage:
+ * $query->set('a = 1')->set('b = 2');
+ * $query->set(array('a = 1', 'b = 2');
+ *
+ * @param mixed $conditions A string or array of string conditions.
+ * @param string $glue The glue by which to join the condition strings. Defaults to ,.
+ * Note that the glue is set on first use and cannot be changed.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function set($conditions, $glue = ',')
+ {
+ if (is_null($this->set))
+ {
+ $glue = strtoupper($glue);
+ $this->set = new FOFDatabaseQueryElement('SET', $conditions, "\n\t$glue ");
+ }
+ else
+ {
+ $this->set->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Allows a direct query to be provided to the database
+ * driver's setQuery() method, but still allow queries
+ * to have bounded variables.
+ *
+ * Usage:
+ * $query->setQuery('select * from #__users');
+ *
+ * @param mixed $sql An SQL Query
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setQuery($sql)
+ {
+ $this->sql = $sql;
+
+ return $this;
+ }
+
+ /**
+ * Add a table name to the UPDATE clause of the query.
+ *
+ * Note that you must not mix insert, update, delete and select method calls when building a query.
+ *
+ * Usage:
+ * $query->update('#__foo')->set(...);
+ *
+ * @param string $table A table to update.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function update($table)
+ {
+ $this->type = 'update';
+ $this->update = new FOFDatabaseQueryElement('UPDATE', $table);
+
+ return $this;
+ }
+
+ /**
+ * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement.
+ *
+ * Usage:
+ * $query->values('1,2,3')->values('4,5,6');
+ * $query->values(array('1,2,3', '4,5,6'));
+ *
+ * @param string $values A single tuple, or array of tuples.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function values($values)
+ {
+ if (is_null($this->values))
+ {
+ $this->values = new FOFDatabaseQueryElement('()', $values, '),(');
+ }
+ else
+ {
+ $this->values->append($values);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a single condition, or an array of conditions to the WHERE clause of the query.
+ *
+ * Usage:
+ * $query->where('a = 1')->where('b = 2');
+ * $query->where(array('a = 1', 'b = 2'));
+ *
+ * @param mixed $conditions A string or array of where conditions.
+ * @param string $glue The glue by which to join the conditions. Defaults to AND.
+ * Note that the glue is set on first use and cannot be changed.
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 11.1
+ */
+ public function where($conditions, $glue = 'AND')
+ {
+ if (is_null($this->where))
+ {
+ $glue = strtoupper($glue);
+ $this->where = new FOFDatabaseQueryElement('WHERE', $conditions, " $glue ");
+ }
+ else
+ {
+ $this->where->append($conditions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to provide deep copy support to nested objects and
+ * arrays when cloning.
+ *
+ * @return void
+ *
+ * @since 11.3
+ */
+ public function __clone()
+ {
+ foreach ($this as $k => $v)
+ {
+ if ($k === 'db')
+ {
+ continue;
+ }
+
+ if (is_object($v) || is_array($v))
+ {
+ $this->{$k} = unserialize(serialize($v));
+ }
+ }
+ }
+
+ /**
+ * Add a query to UNION with the current query.
+ * Multiple unions each require separate statements and create an array of unions.
+ *
+ * Usage (the $query base query MUST be a select query):
+ * $query->union('SELECT name FROM #__foo')
+ * $query->union('SELECT name FROM #__foo', true)
+ * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar'))
+ * $query->union($query2)->union($query3)
+ * $query->union(array($query2, $query3))
+ *
+ * @param mixed $query The FOFDatabaseQuery object or string to union.
+ * @param boolean $distinct True to only return distinct rows from the union.
+ * @param string $glue The glue by which to join the conditions.
+ *
+ * @return mixed The FOFDatabaseQuery object on success or boolean false on failure.
+ *
+ * @see http://dev.mysql.com/doc/refman/5.0/en/union.html
+ *
+ * @since 12.1
+ */
+ public function union($query, $distinct = false, $glue = '')
+ {
+ // Set up the DISTINCT flag, the name with parentheses, and the glue.
+ if ($distinct)
+ {
+ $name = 'UNION DISTINCT ()';
+ $glue = ')' . PHP_EOL . 'UNION DISTINCT (';
+ }
+ else
+ {
+ $glue = ')' . PHP_EOL . 'UNION (';
+ $name = 'UNION ()';
+ }
+
+ // Get the FOFDatabaseQueryElement if it does not exist
+ if (is_null($this->union))
+ {
+ $this->union = new FOFDatabaseQueryElement($name, $query, "$glue");
+ }
+ // Otherwise append the second UNION.
+ else
+ {
+ $this->union->append($query);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a query to UNION DISTINCT with the current query. Simply a proxy to union with the DISTINCT keyword.
+ *
+ * Usage:
+ * $query->unionDistinct('SELECT name FROM #__foo')
+ *
+ * @param mixed $query The FOFDatabaseQuery object or string to union.
+ * @param string $glue The glue by which to join the conditions.
+ *
+ * @return mixed The FOFDatabaseQuery object on success or boolean false on failure.
+ *
+ * @see union
+ *
+ * @since 12.1
+ */
+ public function unionDistinct($query, $glue = '')
+ {
+ $distinct = true;
+
+ // Apply the distinct flag to the union.
+ return $this->union($query, $distinct, $glue);
+ }
+
+ /**
+ * Find and replace sprintf-like tokens in a format string.
+ * Each token takes one of the following forms:
+ * %% - A literal percent character.
+ * %[t] - Where [t] is a type specifier.
+ * %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier.
+ *
+ * Types:
+ * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped.
+ * e - Escape: Replacement text is passed to $this->escape().
+ * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument.
+ * n - Name Quote: Replacement text is passed to $this->quoteName().
+ * q - Quote: Replacement text is passed to $this->quote().
+ * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument.
+ * r - Raw: Replacement text is used as-is. (Be careful)
+ *
+ * Date Types:
+ * - Replacement text automatically quoted (use uppercase for Name Quote).
+ * - Replacement text should be a string in date format or name of a date column.
+ * y/Y - Year
+ * m/M - Month
+ * d/D - Day
+ * h/H - Hour
+ * i/I - Minute
+ * s/S - Second
+ *
+ * Invariable Types:
+ * - Takes no argument.
+ * - Argument index not incremented.
+ * t - Replacement text is the result of $this->currentTimestamp().
+ * z - Replacement text is the result of $this->nullDate(false).
+ * Z - Replacement text is the result of $this->nullDate(true).
+ *
+ * Usage:
+ * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1);
+ * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1
+ *
+ * Notes:
+ * The argument specifier is optional but recommended for clarity.
+ * The argument index used for unspecified tokens is incremented only when used.
+ *
+ * @param string $format The formatting string.
+ *
+ * @return string Returns a string produced according to the formatting string.
+ *
+ * @since 12.3
+ */
+ public function format($format)
+ {
+ $query = $this;
+ $args = array_slice(func_get_args(), 1);
+ array_unshift($args, null);
+
+ $i = 1;
+ $func = function ($match) use ($query, $args, &$i)
+ {
+ if (isset($match[6]) && $match[6] == '%')
+ {
+ return '%';
+ }
+
+ // No argument required, do not increment the argument index.
+ switch ($match[5])
+ {
+ case 't':
+ return $query->currentTimestamp();
+ break;
+
+ case 'z':
+ return $query->nullDate(false);
+ break;
+
+ case 'Z':
+ return $query->nullDate(true);
+ break;
+ }
+
+ // Increment the argument index only if argument specifier not provided.
+ $index = is_numeric($match[4]) ? (int) $match[4] : $i++;
+
+ if (!$index || !isset($args[$index]))
+ {
+ // TODO - What to do? sprintf() throws a Warning in these cases.
+ $replacement = '';
+ }
+ else
+ {
+ $replacement = $args[$index];
+ }
+
+ switch ($match[5])
+ {
+ case 'a':
+ return 0 + $replacement;
+ break;
+
+ case 'e':
+ return $query->escape($replacement);
+ break;
+
+ case 'E':
+ return $query->escape($replacement, true);
+ break;
+
+ case 'n':
+ return $query->quoteName($replacement);
+ break;
+
+ case 'q':
+ return $query->quote($replacement);
+ break;
+
+ case 'Q':
+ return $query->quote($replacement, false);
+ break;
+
+ case 'r':
+ return $replacement;
+ break;
+
+ // Dates
+ case 'y':
+ return $query->year($query->quote($replacement));
+ break;
+
+ case 'Y':
+ return $query->year($query->quoteName($replacement));
+ break;
+
+ case 'm':
+ return $query->month($query->quote($replacement));
+ break;
+
+ case 'M':
+ return $query->month($query->quoteName($replacement));
+ break;
+
+ case 'd':
+ return $query->day($query->quote($replacement));
+ break;
+
+ case 'D':
+ return $query->day($query->quoteName($replacement));
+ break;
+
+ case 'h':
+ return $query->hour($query->quote($replacement));
+ break;
+
+ case 'H':
+ return $query->hour($query->quoteName($replacement));
+ break;
+
+ case 'i':
+ return $query->minute($query->quote($replacement));
+ break;
+
+ case 'I':
+ return $query->minute($query->quoteName($replacement));
+ break;
+
+ case 's':
+ return $query->second($query->quote($replacement));
+ break;
+
+ case 'S':
+ return $query->second($query->quoteName($replacement));
+ break;
+ }
+
+ return '';
+ };
+
+ /**
+ * Regexp to find an replace all tokens.
+ * Matched fields:
+ * 0: Full token
+ * 1: Everything following '%'
+ * 2: Everything following '%' unless '%'
+ * 3: Argument specifier and '$'
+ * 4: Argument specifier
+ * 5: Type specifier
+ * 6: '%' if full token is '%%'
+ */
+ return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format);
+ }
+
+ /**
+ * Add to the current date and time.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ * Note: Not all drivers support all units.
+ *
+ * @param datetime $date The date to add to. May be date or datetime
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @link http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add
+ * @since 13.1
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ return trim("DATE_ADD('" . $date . "', INTERVAL " . $interval . ' ' . $datePart . ')');
+ }
+
+ /**
+ * Add a query to UNION ALL with the current query.
+ * Multiple unions each require separate statements and create an array of unions.
+ *
+ * Usage:
+ * $query->union('SELECT name FROM #__foo')
+ * $query->union(array('SELECT name FROM #__foo','SELECT name FROM #__bar'))
+ *
+ * @param mixed $query The FOFDatabaseQuery object or string to union.
+ * @param boolean $distinct Not used - ignored.
+ * @param string $glue Not used - ignored.
+ *
+ * @return mixed The FOFDatabaseQuery object on success or boolean false on failure.
+ *
+ * @see union
+ *
+ * @since 13.1
+ */
+ public function unionAll($query, $distinct = false, $glue = '')
+ {
+ $glue = ')' . PHP_EOL . 'UNION ALL (';
+ $name = 'UNION ALL ()';
+
+ // Get the FOFDatabaseQueryElement if it does not exist
+ if (is_null($this->unionAll))
+ {
+ $this->unionAll = new FOFDatabaseQueryElement($name, $query, "$glue");
+ }
+
+ // Otherwise append the second UNION.
+ else
+ {
+ $this->unionAll->append($query);
+ }
+
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/element.php b/deployed/akeeba/libraries/fof/database/query/element.php
new file mode 100644
index 00000000..f3a5bae8
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/element.php
@@ -0,0 +1,132 @@
+elements = array();
+ $this->name = $name;
+ $this->glue = $glue;
+
+ $this->append($elements);
+ }
+
+ /**
+ * Magic function to convert the query element to a string.
+ *
+ * @return string
+ *
+ * @since 11.1
+ */
+ public function __toString()
+ {
+ if (substr($this->name, -2) == '()')
+ {
+ return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')';
+ }
+ else
+ {
+ return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements);
+ }
+ }
+
+ /**
+ * Appends element parts to the internal list.
+ *
+ * @param mixed $elements String or array.
+ *
+ * @return void
+ *
+ * @since 11.1
+ */
+ public function append($elements)
+ {
+ if (is_array($elements))
+ {
+ $this->elements = array_merge($this->elements, $elements);
+ }
+ else
+ {
+ $this->elements = array_merge($this->elements, array($elements));
+ }
+ }
+
+ /**
+ * Gets the elements of this element.
+ *
+ * @return array
+ *
+ * @since 11.1
+ */
+ public function getElements()
+ {
+ return $this->elements;
+ }
+
+ /**
+ * Method to provide deep copy support to nested objects and arrays
+ * when cloning.
+ *
+ * @return void
+ *
+ * @since 11.3
+ */
+ public function __clone()
+ {
+ foreach ($this as $k => $v)
+ {
+ if (is_object($v) || is_array($v))
+ {
+ $this->{$k} = unserialize(serialize($v));
+ }
+ }
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/limitable.php b/deployed/akeeba/libraries/fof/database/query/limitable.php
new file mode 100644
index 00000000..5d450e5b
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/limitable.php
@@ -0,0 +1,72 @@
+setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0);
+ }
+}
+
+/**
+ * Joomla Database Query Limitable Interface.
+ * Adds bind/unbind methods as well as a getBounded() method
+ * to retrieve the stored bounded variables on demand prior to
+ * query execution.
+ *
+ * @since 12.1
+ */
+interface FOFDatabaseQueryLimitable extends JDatabaseQueryLimitable
+{
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/mysql.php b/deployed/akeeba/libraries/fof/database/query/mysql.php
new file mode 100644
index 00000000..73db5b31
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/mysql.php
@@ -0,0 +1,23 @@
+ 0 || $offset > 0)
+ {
+ $query .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 11.1
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ $concat_string = 'CONCAT_WS(' . $this->quote($separator);
+
+ foreach ($values as $value)
+ {
+ $concat_string .= ', ' . $value;
+ }
+
+ return $concat_string . ')';
+ }
+ else
+ {
+ return 'CONCAT(' . implode(',', $values) . ')';
+ }
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+
+ /**
+ * Return correct regexp operator for mysqli.
+ *
+ * Ensure that the regexp operator is mysqli compatible.
+ *
+ * Usage:
+ * $query->where('field ' . $query->regexp($search));
+ *
+ * @param string $value The regex pattern.
+ *
+ * @return string Returns the regex operator.
+ *
+ * @since 11.3
+ */
+ public function regexp($value)
+ {
+ return ' REGEXP ' . $value;
+ }
+
+ /**
+ * Return correct rand() function for Mysql.
+ *
+ * Ensure that the rand() function is Mysql compatible.
+ *
+ * Usage:
+ * $query->Rand();
+ *
+ * @return string The correct rand function.
+ *
+ * @since 3.5
+ */
+ public function Rand()
+ {
+ return ' RAND() ';
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/oracle.php b/deployed/akeeba/libraries/fof/database/query/oracle.php
new file mode 100644
index 00000000..62206908
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/oracle.php
@@ -0,0 +1,205 @@
+bounded = array();
+
+ return $this;
+ }
+
+ // Case 2: Key Provided, null value (unset key from $bounded array)
+ if (is_null($value))
+ {
+ if (isset($this->bounded[$key]))
+ {
+ unset($this->bounded[$key]);
+ }
+
+ return $this;
+ }
+
+ $obj = new stdClass;
+
+ $obj->value = &$value;
+ $obj->dataType = $dataType;
+ $obj->length = $length;
+ $obj->driverOptions = $driverOptions;
+
+ // Case 3: Simply add the Key/Value into the bounded array
+ $this->bounded[$key] = $obj;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
+ * returned.
+ *
+ * @param mixed $key The bounded variable key to retrieve.
+ *
+ * @return mixed
+ *
+ * @since 12.1
+ */
+ public function &getBounded($key = null)
+ {
+ if (empty($key))
+ {
+ return $this->bounded;
+ }
+ else
+ {
+ if (isset($this->bounded[$key]))
+ {
+ return $this->bounded[$key];
+ }
+ }
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return FOFDatabaseQueryOracle Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case null:
+ $this->bounded = array();
+ break;
+ }
+
+ parent::clear($clause);
+
+ return $this;
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset. This method is used
+ * automatically by the __toString() method if it detects that the
+ * query implements the FOFDatabaseQueryLimitable interface.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ // Check if we need to mangle the query.
+ if ($limit || $offset)
+ {
+ $query = "SELECT joomla2.*
+ FROM (
+ SELECT joomla1.*, ROWNUM AS joomla_db_rownum
+ FROM (
+ " . $query . "
+ ) joomla1
+ ) joomla2";
+
+ // Check if the limit value is greater than zero.
+ if ($limit > 0)
+ {
+ $query .= ' WHERE joomla2.joomla_db_rownum BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $limit);
+ }
+ else
+ {
+ // Check if there is an offset and then use this.
+ if ($offset)
+ {
+ $query .= ' WHERE joomla2.joomla_db_rownum > ' . ($offset + 1);
+ }
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQueryOracle Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/pdo.php b/deployed/akeeba/libraries/fof/database/query/pdo.php
new file mode 100644
index 00000000..13d36198
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/pdo.php
@@ -0,0 +1,22 @@
+type)
+ {
+ case 'select':
+ $query .= (string) $this->select;
+ $query .= (string) $this->from;
+
+ if ($this->join)
+ {
+ // Special case for joins
+ foreach ($this->join as $join)
+ {
+ $query .= (string) $join;
+ }
+ }
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+
+ if ($this->group)
+ {
+ $query .= (string) $this->group;
+ }
+
+ if ($this->having)
+ {
+ $query .= (string) $this->having;
+ }
+
+ if ($this->order)
+ {
+ $query .= (string) $this->order;
+ }
+
+ if ($this->forUpdate)
+ {
+ $query .= (string) $this->forUpdate;
+ }
+ else
+ {
+ if ($this->forShare)
+ {
+ $query .= (string) $this->forShare;
+ }
+ }
+
+ if ($this->noWait)
+ {
+ $query .= (string) $this->noWait;
+ }
+
+ break;
+
+ case 'update':
+ $query .= (string) $this->update;
+ $query .= (string) $this->set;
+
+ if ($this->join)
+ {
+ $onWord = ' ON ';
+
+ // Workaround for special case of JOIN with UPDATE
+ foreach ($this->join as $join)
+ {
+ $joinElem = $join->getElements();
+
+ $joinArray = explode($onWord, $joinElem[0]);
+
+ $this->from($joinArray[0]);
+ $this->where($joinArray[1]);
+ }
+
+ $query .= (string) $this->from;
+ }
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+
+ break;
+
+ case 'insert':
+ $query .= (string) $this->insert;
+
+ if ($this->values)
+ {
+ if ($this->columns)
+ {
+ $query .= (string) $this->columns;
+ }
+
+ $elements = $this->values->getElements();
+
+ if (!($elements[0] instanceof $this))
+ {
+ $query .= ' VALUES ';
+ }
+
+ $query .= (string) $this->values;
+
+ if ($this->returning)
+ {
+ $query .= (string) $this->returning;
+ }
+ }
+
+ break;
+
+ default:
+ $query = parent::__toString();
+ break;
+ }
+
+ if ($this instanceof FOFDatabaseQueryLimitable)
+ {
+ $query = $this->processLimit($query, $this->limit, $this->offset);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case 'limit':
+ $this->limit = null;
+ break;
+
+ case 'offset':
+ $this->offset = null;
+ break;
+
+ case 'forUpdate':
+ $this->forUpdate = null;
+ break;
+
+ case 'forShare':
+ $this->forShare = null;
+ break;
+
+ case 'noWait':
+ $this->noWait = null;
+ break;
+
+ case 'returning':
+ $this->returning = null;
+ break;
+
+ case 'select':
+ case 'update':
+ case 'delete':
+ case 'insert':
+ case 'from':
+ case 'join':
+ case 'set':
+ case 'where':
+ case 'group':
+ case 'having':
+ case 'order':
+ case 'columns':
+ case 'values':
+ parent::clear($clause);
+ break;
+
+ default:
+ $this->type = null;
+ $this->limit = null;
+ $this->offset = null;
+ $this->forUpdate = null;
+ $this->forShare = null;
+ $this->noWait = null;
+ $this->returning = null;
+ parent::clear($clause);
+ break;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Casts a value to a char.
+ *
+ * Ensure that the value is properly quoted before passing to the method.
+ *
+ * Usage:
+ * $query->select($query->castAsChar('a'));
+ *
+ * @param string $value The value to cast as a char.
+ *
+ * @return string Returns the cast value.
+ *
+ * @since 11.3
+ */
+ public function castAsChar($value)
+ {
+ return $value . '::text';
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * Usage:
+ * $query->select($query->concatenate(array('a', 'b')));
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 11.3
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return implode(' || ' . $this->quote($separator) . ' || ', $values);
+ }
+ else
+ {
+ return implode(' || ', $values);
+ }
+ }
+
+ /**
+ * Gets the current date and time.
+ *
+ * @return string Return string used in query to obtain
+ *
+ * @since 11.3
+ */
+ public function currentTimestamp()
+ {
+ return 'NOW()';
+ }
+
+ /**
+ * Sets the FOR UPDATE lock on select's output row
+ *
+ * @param string $table_name The table to lock
+ * @param string $glue The glue by which to join the conditions. Defaults to ',' .
+ *
+ * @return FOFDatabaseQueryPostgresql FOR UPDATE query element
+ *
+ * @since 11.3
+ */
+ public function forUpdate($table_name, $glue = ',')
+ {
+ $this->type = 'forUpdate';
+
+ if (is_null($this->forUpdate))
+ {
+ $glue = strtoupper($glue);
+ $this->forUpdate = new FOFDatabaseQueryElement('FOR UPDATE', 'OF ' . $table_name, "$glue ");
+ }
+ else
+ {
+ $this->forUpdate->append($table_name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the FOR SHARE lock on select's output row
+ *
+ * @param string $table_name The table to lock
+ * @param string $glue The glue by which to join the conditions. Defaults to ',' .
+ *
+ * @return FOFDatabaseQueryPostgresql FOR SHARE query element
+ *
+ * @since 11.3
+ */
+ public function forShare($table_name, $glue = ',')
+ {
+ $this->type = 'forShare';
+
+ if (is_null($this->forShare))
+ {
+ $glue = strtoupper($glue);
+ $this->forShare = new FOFDatabaseQueryElement('FOR SHARE', 'OF ' . $table_name, "$glue ");
+ }
+ else
+ {
+ $this->forShare->append($table_name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Used to get a string to extract year from date column.
+ *
+ * Usage:
+ * $query->select($query->year($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing year to be extracted.
+ *
+ * @return string Returns string to extract year from a date.
+ *
+ * @since 12.1
+ */
+ public function year($date)
+ {
+ return 'EXTRACT (YEAR FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract month from date column.
+ *
+ * Usage:
+ * $query->select($query->month($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing month to be extracted.
+ *
+ * @return string Returns string to extract month from a date.
+ *
+ * @since 12.1
+ */
+ public function month($date)
+ {
+ return 'EXTRACT (MONTH FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract day from date column.
+ *
+ * Usage:
+ * $query->select($query->day($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing day to be extracted.
+ *
+ * @return string Returns string to extract day from a date.
+ *
+ * @since 12.1
+ */
+ public function day($date)
+ {
+ return 'EXTRACT (DAY FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract hour from date column.
+ *
+ * Usage:
+ * $query->select($query->hour($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing hour to be extracted.
+ *
+ * @return string Returns string to extract hour from a date.
+ *
+ * @since 12.1
+ */
+ public function hour($date)
+ {
+ return 'EXTRACT (HOUR FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract minute from date column.
+ *
+ * Usage:
+ * $query->select($query->minute($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing minute to be extracted.
+ *
+ * @return string Returns string to extract minute from a date.
+ *
+ * @since 12.1
+ */
+ public function minute($date)
+ {
+ return 'EXTRACT (MINUTE FROM ' . $date . ')';
+ }
+
+ /**
+ * Used to get a string to extract seconds from date column.
+ *
+ * Usage:
+ * $query->select($query->second($query->quoteName('dateColumn')));
+ *
+ * @param string $date Date column containing second to be extracted.
+ *
+ * @return string Returns string to extract second from a date.
+ *
+ * @since 12.1
+ */
+ public function second($date)
+ {
+ return 'EXTRACT (SECOND FROM ' . $date . ')';
+ }
+
+ /**
+ * Sets the NOWAIT lock on select's output row
+ *
+ * @return FOFDatabaseQueryPostgresql NO WAIT query element
+ *
+ * @since 11.3
+ */
+ public function noWait ()
+ {
+ $this->type = 'noWait';
+
+ if (is_null($this->noWait))
+ {
+ $this->noWait = new FOFDatabaseQueryElement('NOWAIT', null);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the LIMIT clause to the query
+ *
+ * @param integer $limit An int of how many row will be returned
+ *
+ * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function limit($limit = 0)
+ {
+ if (is_null($this->limit))
+ {
+ $this->limit = new FOFDatabaseQueryElement('LIMIT', (int) $limit);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the OFFSET clause to the query
+ *
+ * @param integer $offset An int for skipping row
+ *
+ * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function offset($offset = 0)
+ {
+ if (is_null($this->offset))
+ {
+ $this->offset = new FOFDatabaseQueryElement('OFFSET', (int) $offset);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add the RETURNING element to INSERT INTO statement.
+ *
+ * @param mixed $pkCol The name of the primary key column.
+ *
+ * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
+ *
+ * @since 11.3
+ */
+ public function returning($pkCol)
+ {
+ if (is_null($this->returning))
+ {
+ $this->returning = new FOFDatabaseQueryElement('RETURNING', $pkCol);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQueryPostgresql Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ if ($limit > 0)
+ {
+ $query .= ' LIMIT ' . $limit;
+ }
+
+ if ($offset > 0)
+ {
+ $query .= ' OFFSET ' . $offset;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Add to the current date and time in Postgresql.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ *
+ * @param datetime $date The date to add to
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @since 13.1
+ * @note Not all drivers support all units. Check appropriate references
+ * @link http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ if (substr($interval, 0, 1) != '-')
+ {
+ return "timestamp '" . $date . "' + interval '" . $interval . " " . $datePart . "'";
+ }
+ else
+ {
+ return "timestamp '" . $date . "' - interval '" . ltrim($interval, '-') . " " . $datePart . "'";
+ }
+ }
+
+ /**
+ * Return correct regexp operator for Postgresql.
+ *
+ * Ensure that the regexp operator is Postgresql compatible.
+ *
+ * Usage:
+ * $query->where('field ' . $query->regexp($search));
+ *
+ * @param string $value The regex pattern.
+ *
+ * @return string Returns the regex operator.
+ *
+ * @since 11.3
+ */
+ public function regexp($value)
+ {
+ return ' ~* ' . $value;
+ }
+
+ /**
+ * Return correct rand() function for Postgresql.
+ *
+ * Ensure that the rand() function is Postgresql compatible.
+ *
+ * Usage:
+ * $query->Rand();
+ *
+ * @return string The correct rand function.
+ *
+ * @since 3.5
+ */
+ public function Rand()
+ {
+ return ' RANDOM() ';
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/preparable.php b/deployed/akeeba/libraries/fof/database/query/preparable.php
new file mode 100644
index 00000000..8f58bfa8
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/preparable.php
@@ -0,0 +1,62 @@
+bounded = array();
+
+ return $this;
+ }
+
+ // Case 2: Key Provided, null value (unset key from $bounded array)
+ if (is_null($value))
+ {
+ if (isset($this->bounded[$key]))
+ {
+ unset($this->bounded[$key]);
+ }
+
+ return $this;
+ }
+
+ $obj = new stdClass;
+
+ $obj->value = &$value;
+ $obj->dataType = $dataType;
+ $obj->length = $length;
+ $obj->driverOptions = $driverOptions;
+
+ // Case 3: Simply add the Key/Value into the bounded array
+ $this->bounded[$key] = $obj;
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
+ * returned.
+ *
+ * @param mixed $key The bounded variable key to retrieve.
+ *
+ * @return mixed
+ *
+ * @since 12.1
+ */
+ public function &getBounded($key = null)
+ {
+ if (empty($key))
+ {
+ return $this->bounded;
+ }
+ else
+ {
+ if (isset($this->bounded[$key]))
+ {
+ return $this->bounded[$key];
+ }
+ }
+ }
+
+ /**
+ * Gets the number of characters in a string.
+ *
+ * Note, use 'length' to find the number of bytes in a string.
+ *
+ * Usage:
+ * $query->select($query->charLength('a'));
+ *
+ * @param string $field A value.
+ * @param string $operator Comparison operator between charLength integer value and $condition
+ * @param string $condition Integer value to compare charLength with.
+ *
+ * @return string The required char length call.
+ *
+ * @since 13.1
+ */
+ public function charLength($field, $operator = null, $condition = null)
+ {
+ return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
+ }
+
+ /**
+ * Clear data from the query or a specific clause of the query.
+ *
+ * @param string $clause Optionally, the name of the clause to clear, or nothing to clear the whole query.
+ *
+ * @return FOFDatabaseQuerySqlite Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function clear($clause = null)
+ {
+ switch ($clause)
+ {
+ case null:
+ $this->bounded = array();
+ break;
+ }
+
+ parent::clear($clause);
+
+ return $this;
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * Usage:
+ * $query->select($query->concatenate(array('a', 'b')));
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 11.1
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return implode(' || ' . $this->quote($separator) . ' || ', $values);
+ }
+ else
+ {
+ return implode(' || ', $values);
+ }
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset. This method is used
+ * automatically by the __toString() method if it detects that the
+ * query implements the FOFDatabaseQueryLimitable interface.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ if ($limit > 0 || $offset > 0)
+ {
+ $query .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQuerySqlite Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+
+ /**
+ * Add to the current date and time.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ *
+ * @param datetime $date The date or datetime to add to
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @since 13.1
+ * @link http://www.sqlite.org/lang_datefunc.html
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ // SQLite does not support microseconds as a separate unit. Convert the interval to seconds
+ if (strcasecmp($datePart, 'microseconds') == 0)
+ {
+ $interval = .001 * $interval;
+ $datePart = 'seconds';
+ }
+
+ if (substr($interval, 0, 1) != '-')
+ {
+ return "datetime('" . $date . "', '+" . $interval . " " . $datePart . "')";
+ }
+ else
+ {
+ return "datetime('" . $date . "', '" . $interval . " " . $datePart . "')";
+ }
+ }
+
+ /**
+ * Gets the current date and time.
+ *
+ * Usage:
+ * $query->where('published_up < '.$query->currentTimestamp());
+ *
+ * @return string
+ *
+ * @since 3.4
+ */
+ public function currentTimestamp()
+ {
+ return 'CURRENT_TIMESTAMP';
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/database/query/sqlsrv.php b/deployed/akeeba/libraries/fof/database/query/sqlsrv.php
new file mode 100644
index 00000000..bee0f807
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/database/query/sqlsrv.php
@@ -0,0 +1,380 @@
+type)
+ {
+ case 'select':
+ $query .= (string) $this->select;
+ $query .= (string) $this->from;
+
+ if ($this->join)
+ {
+ // Special case for joins
+ foreach ($this->join as $join)
+ {
+ $query .= (string) $join;
+ }
+ }
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+
+ if ($this->group)
+ {
+ $query .= (string) $this->group;
+ }
+
+ if ($this->order)
+ {
+ $query .= (string) $this->order;
+ }
+
+ if ($this->having)
+ {
+ $query .= (string) $this->having;
+ }
+
+ if ($this instanceof FOFDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0))
+ {
+ $query = $this->processLimit($query, $this->limit, $this->offset);
+ }
+
+ break;
+
+ case 'insert':
+ $query .= (string) $this->insert;
+
+ // Set method
+ if ($this->set)
+ {
+ $query .= (string) $this->set;
+ }
+ // Columns-Values method
+ elseif ($this->values)
+ {
+ if ($this->columns)
+ {
+ $query .= (string) $this->columns;
+ }
+
+ $elements = $this->insert->getElements();
+ $tableName = array_shift($elements);
+
+ $query .= 'VALUES ';
+ $query .= (string) $this->values;
+
+ if ($this->autoIncrementField)
+ {
+ $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;';
+ }
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+ }
+
+ break;
+
+ case 'delete':
+ $query .= (string) $this->delete;
+ $query .= (string) $this->from;
+
+ if ($this->join)
+ {
+ // Special case for joins
+ foreach ($this->join as $join)
+ {
+ $query .= (string) $join;
+ }
+ }
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+
+ if ($this->order)
+ {
+ $query .= (string) $this->order;
+ }
+
+ break;
+
+ case 'update':
+ $query .= (string) $this->update;
+
+ if ($this->join)
+ {
+ // Special case for joins
+ foreach ($this->join as $join)
+ {
+ $query .= (string) $join;
+ }
+ }
+
+ $query .= (string) $this->set;
+
+ if ($this->where)
+ {
+ $query .= (string) $this->where;
+ }
+
+ if ($this->order)
+ {
+ $query .= (string) $this->order;
+ }
+
+ break;
+
+ default:
+ $query = parent::__toString();
+ break;
+ }
+
+ return $query;
+ }
+
+ /**
+ * Casts a value to a char.
+ *
+ * Ensure that the value is properly quoted before passing to the method.
+ *
+ * @param string $value The value to cast as a char.
+ *
+ * @return string Returns the cast value.
+ *
+ * @since 11.1
+ */
+ public function castAsChar($value)
+ {
+ return 'CAST(' . $value . ' as NVARCHAR(10))';
+ }
+
+ /**
+ * Gets the function to determine the length of a character string.
+ *
+ * @param string $field A value.
+ * @param string $operator Comparison operator between charLength integer value and $condition
+ * @param string $condition Integer value to compare charLength with.
+ *
+ * @return string The required char length call.
+ *
+ * @since 11.1
+ */
+ public function charLength($field, $operator = null, $condition = null)
+ {
+ return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
+ }
+
+ /**
+ * Concatenates an array of column names or values.
+ *
+ * @param array $values An array of values to concatenate.
+ * @param string $separator As separator to place between each value.
+ *
+ * @return string The concatenated values.
+ *
+ * @since 11.1
+ */
+ public function concatenate($values, $separator = null)
+ {
+ if ($separator)
+ {
+ return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')';
+ }
+ else
+ {
+ return '(' . implode('+', $values) . ')';
+ }
+ }
+
+ /**
+ * Gets the current date and time.
+ *
+ * @return string
+ *
+ * @since 11.1
+ */
+ public function currentTimestamp()
+ {
+ return 'GETDATE()';
+ }
+
+ /**
+ * Get the length of a string in bytes.
+ *
+ * @param string $value The string to measure.
+ *
+ * @return integer
+ *
+ * @since 11.1
+ */
+ public function length($value)
+ {
+ return 'LEN(' . $value . ')';
+ }
+
+ /**
+ * Add to the current date and time.
+ * Usage:
+ * $query->select($query->dateAdd());
+ * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
+ *
+ * @param datetime $date The date to add to; type may be time or datetime.
+ * @param string $interval The string representation of the appropriate number of units
+ * @param string $datePart The part of the date to perform the addition on
+ *
+ * @return string The string with the appropriate sql for addition of dates
+ *
+ * @since 13.1
+ * @note Not all drivers support all units.
+ * @link http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information
+ */
+ public function dateAdd($date, $interval, $datePart)
+ {
+ return "DATEADD('" . $datePart . "', '" . $interval . "', '" . $date . "'" . ')';
+ }
+
+ /**
+ * Method to modify a query already in string format with the needed
+ * additions to make the query limited to a particular number of
+ * results, or start at a particular offset.
+ *
+ * @param string $query The query in string format
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return string
+ *
+ * @since 12.1
+ */
+ public function processLimit($query, $limit, $offset = 0)
+ {
+ if ($limit == 0 && $offset == 0)
+ {
+ return $query;
+ }
+
+ $start = $offset + 1;
+ $end = $offset + $limit;
+
+ $orderBy = stristr($query, 'ORDER BY');
+
+ if (is_null($orderBy) || empty($orderBy))
+ {
+ $orderBy = 'ORDER BY (select 0)';
+ }
+
+ $query = str_ireplace($orderBy, '', $query);
+
+ $rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM ';
+
+ $query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1);
+ $query = 'SELECT * FROM (' . $query . ') A WHERE A.RowNumber BETWEEN ' . $start . ' AND ' . $end;
+
+ return $query;
+ }
+
+ /**
+ * Sets the offset and limit for the result set, if the database driver supports it.
+ *
+ * Usage:
+ * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
+ * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
+ *
+ * @param integer $limit The limit for the result set
+ * @param integer $offset The offset for the result set
+ *
+ * @return FOFDatabaseQuery Returns this object to allow chaining.
+ *
+ * @since 12.1
+ */
+ public function setLimit($limit = 0, $offset = 0)
+ {
+ $this->limit = (int) $limit;
+ $this->offset = (int) $offset;
+
+ return $this;
+ }
+
+ /**
+ * Return correct rand() function for MSSQL.
+ *
+ * Ensure that the rand() function is MSSQL compatible.
+ *
+ * Usage:
+ * $query->Rand();
+ *
+ * @return string The correct rand function.
+ *
+ * @since 3.5
+ */
+ public function Rand()
+ {
+ return ' NEWID() ';
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/dispatcher/dispatcher.php b/deployed/akeeba/libraries/fof/dispatcher/dispatcher.php
new file mode 100644
index 00000000..e253b15d
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/dispatcher/dispatcher.php
@@ -0,0 +1,718 @@
+getCmd('option', 'com_foobar');
+ $config['view'] = !is_null($view) ? $view : $input->getCmd('view', '');
+
+ $input->set('option', $config['option']);
+ $input->set('view', $config['view']);
+
+ $config['input'] = $input;
+
+ $className = ucfirst(str_replace('com_', '', $config['option'])) . 'Dispatcher';
+
+ if (!class_exists($className))
+ {
+ $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);
+
+ $searchPaths = array(
+ $componentPaths['main'],
+ $componentPaths['main'] . '/dispatchers',
+ $componentPaths['admin'],
+ $componentPaths['admin'] . '/dispatchers'
+ );
+
+ if (array_key_exists('searchpath', $config))
+ {
+ array_unshift($searchPaths, $config['searchpath']);
+ }
+
+ $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');
+
+ $path = $filesystem->pathFind(
+ $searchPaths, 'dispatcher.php'
+ );
+
+ if ($path)
+ {
+ require_once $path;
+ }
+ }
+
+ if (!class_exists($className))
+ {
+ $className = 'FOFDispatcher';
+ }
+
+ $instance = new $className($config);
+
+ return $instance;
+ }
+
+ /**
+ * Public constructor
+ *
+ * @param array $config The configuration variables
+ */
+ public function __construct($config = array())
+ {
+ // Cache the config
+ $this->config = $config;
+
+ // Get the input for this MVC triad
+ if (array_key_exists('input', $config))
+ {
+ $this->input = $config['input'];
+ }
+ else
+ {
+ $this->input = new FOFInput;
+ }
+
+ // Get the default values for the component name
+ $this->component = $this->input->getCmd('option', 'com_foobar');
+
+ // Load the component's fof.xml configuration file
+ $configProvider = new FOFConfigProvider;
+ $this->defaultView = $configProvider->get($this->component . '.dispatcher.default_view', $this->defaultView);
+
+ // Get the default values for the view name
+ $this->view = $this->input->getCmd('view', null);
+
+ if (empty($this->view))
+ {
+ // Do we have a task formatted as controller.task?
+ $task = $this->input->getCmd('task', '');
+
+ if (!empty($task) && (strstr($task, '.') !== false))
+ {
+ list($this->view, $task) = explode('.', $task, 2);
+ $this->input->set('task', $task);
+ }
+ }
+
+ if (empty($this->view))
+ {
+ $this->view = $this->defaultView;
+ }
+
+ $this->layout = $this->input->getCmd('layout', null);
+
+ // Overrides from the config
+ if (array_key_exists('option', $config))
+ {
+ $this->component = $config['option'];
+ }
+
+ if (array_key_exists('view', $config))
+ {
+ $this->view = empty($config['view']) ? $this->view : $config['view'];
+ }
+
+ if (array_key_exists('layout', $config))
+ {
+ $this->layout = $config['layout'];
+ }
+
+ $this->input->set('option', $this->component);
+ $this->input->set('view', $this->view);
+ $this->input->set('layout', $this->layout);
+
+ if (array_key_exists('authTimeStep', $config))
+ {
+ $this->fofAuth_timeStep = empty($config['authTimeStep']) ? 6 : $config['authTimeStep'];
+ }
+ }
+
+ /**
+ * The main code of the Dispatcher. It spawns the necessary controller and
+ * runs it.
+ *
+ * @throws Exception
+ *
+ * @return void|Exception
+ */
+ public function dispatch()
+ {
+ $platform = FOFPlatform::getInstance();
+
+ if (!$platform->authorizeAdmin($this->input->getCmd('option', 'com_foobar')))
+ {
+ return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
+ }
+
+ $this->transparentAuthentication();
+
+ // Merge English and local translations
+ $platform->loadTranslations($this->component);
+
+ $canDispatch = true;
+
+ if ($platform->isCli())
+ {
+ $canDispatch = $canDispatch && $this->onBeforeDispatchCLI();
+ }
+
+ $canDispatch = $canDispatch && $this->onBeforeDispatch();
+
+ if (!$canDispatch)
+ {
+ // We can set header only if we're not in CLI
+ if(!$platform->isCli())
+ {
+ $platform->setHeader('Status', '403 Forbidden', true);
+ }
+
+ return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
+ }
+
+ // Get and execute the controller
+ $option = $this->input->getCmd('option', 'com_foobar');
+ $view = $this->input->getCmd('view', $this->defaultView);
+ $task = $this->input->getCmd('task', null);
+
+ if (empty($task))
+ {
+ $task = $this->getTask($view);
+ }
+
+ // Pluralise/sungularise the view name for typical tasks
+ if (in_array($task, array('edit', 'add', 'read')))
+ {
+ $view = FOFInflector::singularize($view);
+ }
+ elseif (in_array($task, array('browse')))
+ {
+ $view = FOFInflector::pluralize($view);
+ }
+
+ $this->input->set('view', $view);
+ $this->input->set('task', $task);
+
+ $config = $this->config;
+ $config['input'] = $this->input;
+
+ $controller = FOFController::getTmpInstance($option, $view, $config);
+ $status = $controller->execute($task);
+
+ if (!$this->onAfterDispatch())
+ {
+ // We can set header only if we're not in CLI
+ if(!$platform->isCli())
+ {
+ $platform->setHeader('Status', '403 Forbidden', true);
+ }
+
+ return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
+ }
+
+ $format = $this->input->get('format', 'html', 'cmd');
+ $format = empty($format) ? 'html' : $format;
+
+ if ($controller->hasRedirect())
+ {
+ $controller->redirect();
+ }
+ }
+
+ /**
+ * Tries to guess the controller task to execute based on the view name and
+ * the HTTP request method.
+ *
+ * @param string $view The name of the view
+ *
+ * @return string The best guess of the task to execute
+ */
+ protected function getTask($view)
+ {
+ // Get a default task based on plural/singular view
+ $request_task = $this->input->getCmd('task', null);
+ $task = FOFInflector::isPlural($view) ? 'browse' : 'edit';
+
+ // Get a potential ID, we might need it later
+ $id = $this->input->get('id', null, 'int');
+
+ if ($id == 0)
+ {
+ $ids = $this->input->get('ids', array(), 'array');
+
+ if (!empty($ids))
+ {
+ $id = array_shift($ids);
+ }
+ }
+
+ // Check the request method
+
+ if (!isset($_SERVER['REQUEST_METHOD']))
+ {
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ }
+
+ $requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);
+
+ switch ($requestMethod)
+ {
+ case 'POST':
+ case 'PUT':
+ if (!is_null($id))
+ {
+ $task = 'save';
+ }
+ break;
+
+ case 'DELETE':
+ if ($id != 0)
+ {
+ $task = 'delete';
+ }
+ break;
+
+ case 'GET':
+ default:
+ // If it's an edit without an ID or ID=0, it's really an add
+ if (($task == 'edit') && ($id == 0))
+ {
+ $task = 'add';
+ }
+
+ // If it's an edit in the frontend, it's really a read
+ elseif (($task == 'edit') && FOFPlatform::getInstance()->isFrontend())
+ {
+ $task = 'read';
+ }
+ break;
+ }
+
+ return $task;
+ }
+
+ /**
+ * Executes right before the dispatcher tries to instantiate and run the
+ * controller.
+ *
+ * @return boolean Return false to abort
+ */
+ public function onBeforeDispatch()
+ {
+ return true;
+ }
+
+ /**
+ * Sets up some environment variables, so we can work as usually on CLI, too.
+ *
+ * @return boolean Return false to abort
+ */
+ public function onBeforeDispatchCLI()
+ {
+ JLoader::import('joomla.environment.uri');
+ JLoader::import('joomla.application.component.helper');
+
+ // Trick to create a valid url used by JURI
+ $this->_originalPhpScript = '';
+
+ // We have no Application Helper (there is no Application!), so I have to define these constants manually
+ $option = $this->input->get('option', '', 'cmd');
+
+ if ($option)
+ {
+ $componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($option);
+
+ if (!defined('JPATH_COMPONENT'))
+ {
+ define('JPATH_COMPONENT', $componentPaths['main']);
+ }
+
+ if (!defined('JPATH_COMPONENT_SITE'))
+ {
+ define('JPATH_COMPONENT_SITE', $componentPaths['site']);
+ }
+
+ if (!defined('JPATH_COMPONENT_ADMINISTRATOR'))
+ {
+ define('JPATH_COMPONENT_ADMINISTRATOR', $componentPaths['admin']);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Executes right after the dispatcher runs the controller.
+ *
+ * @return boolean Return false to abort
+ */
+ public function onAfterDispatch()
+ {
+ // If we have to log out the user, please do so now
+ if ($this->fofAuth_LogoutOnReturn && $this->_fofAuth_isLoggedIn)
+ {
+ FOFPlatform::getInstance()->logoutUser();
+ }
+
+ return true;
+ }
+
+ /**
+ * Transparently authenticates a user
+ *
+ * @return void
+ */
+ public function transparentAuthentication()
+ {
+ // Only run when there is no logged in user
+ if (!FOFPlatform::getInstance()->getUser()->guest)
+ {
+ return;
+ }
+
+ // @todo Check the format
+ $format = $this->input->getCmd('format', 'html');
+
+ if (!in_array($format, $this->fofAuth_Formats))
+ {
+ return;
+ }
+
+ foreach ($this->fofAuth_AuthMethods as $method)
+ {
+ // If we're already logged in, don't bother
+ if ($this->_fofAuth_isLoggedIn)
+ {
+ continue;
+ }
+
+ // This will hold our authentication data array (username, password)
+ $authInfo = null;
+
+ switch ($method)
+ {
+ case 'HTTPBasicAuth_TOTP':
+
+ if (empty($this->fofAuth_Key))
+ {
+ continue;
+ }
+
+ if (!isset($_SERVER['PHP_AUTH_USER']))
+ {
+ continue;
+ }
+
+ if (!isset($_SERVER['PHP_AUTH_PW']))
+ {
+ continue;
+ }
+
+ if ($_SERVER['PHP_AUTH_USER'] != '_fof_auth')
+ {
+ continue;
+ }
+
+ $encryptedData = $_SERVER['PHP_AUTH_PW'];
+
+ $authInfo = $this->_decryptWithTOTP($encryptedData);
+ break;
+
+ case 'QueryString_TOTP':
+ $encryptedData = $this->input->get('_fofauthentication', '', 'raw');
+
+ if (empty($encryptedData))
+ {
+ continue;
+ }
+
+ $authInfo = $this->_decryptWithTOTP($encryptedData);
+ break;
+
+ case 'HTTPBasicAuth_Plaintext':
+ if (!isset($_SERVER['PHP_AUTH_USER']))
+ {
+ continue;
+ }
+
+ if (!isset($_SERVER['PHP_AUTH_PW']))
+ {
+ continue;
+ }
+
+ $authInfo = array(
+ 'username' => $_SERVER['PHP_AUTH_USER'],
+ 'password' => $_SERVER['PHP_AUTH_PW']
+ );
+ break;
+
+ case 'QueryString_Plaintext':
+ $jsonencoded = $this->input->get('_fofauthentication', '', 'raw');
+
+ if (empty($jsonencoded))
+ {
+ continue;
+ }
+
+ $authInfo = json_decode($jsonencoded, true);
+
+ if (!is_array($authInfo))
+ {
+ $authInfo = null;
+ }
+ elseif (!array_key_exists('username', $authInfo) || !array_key_exists('password', $authInfo))
+ {
+ $authInfo = null;
+ }
+ break;
+
+ case 'SplitQueryString_Plaintext':
+ $authInfo = array(
+ 'username' => $this->input->get('_fofauthentication_username', '', 'raw'),
+ 'password' => $this->input->get('_fofauthentication_password', '', 'raw'),
+ );
+
+ if (empty($authInfo['username']))
+ {
+ $authInfo = null;
+ }
+
+ break;
+
+ default:
+ continue;
+
+ break;
+ }
+
+ // No point trying unless we have a username and password
+ if (!is_array($authInfo))
+ {
+ continue;
+ }
+
+ $this->_fofAuth_isLoggedIn = FOFPlatform::getInstance()->loginUser($authInfo);
+ }
+ }
+
+ /**
+ * Decrypts a transparent authentication message using a TOTP
+ *
+ * @param string $encryptedData The encrypted data
+ *
+ * @codeCoverageIgnore
+ * @return array The decrypted data
+ */
+ private function _decryptWithTOTP($encryptedData)
+ {
+ if (empty($this->fofAuth_Key))
+ {
+ $this->_fofAuth_CryptoKey = null;
+
+ return null;
+ }
+
+ $totp = new FOFEncryptTotp($this->fofAuth_timeStep);
+ $period = $totp->getPeriod();
+ $period--;
+
+ for ($i = 0; $i <= 2; $i++)
+ {
+ $time = ($period + $i) * $this->fofAuth_timeStep;
+ $otp = $totp->getCode($this->fofAuth_Key, $time);
+ $this->_fofAuth_CryptoKey = hash('sha256', $this->fofAuth_Key . $otp);
+
+ $aes = new FOFEncryptAes($this->_fofAuth_CryptoKey);
+ $ret = $aes->decryptString($encryptedData);
+ $ret = rtrim($ret, "\000");
+
+ $ret = json_decode($ret, true);
+
+ if (!is_array($ret))
+ {
+ continue;
+ }
+
+ if (!array_key_exists('username', $ret))
+ {
+ continue;
+ }
+
+ if (!array_key_exists('password', $ret))
+ {
+ continue;
+ }
+
+ // Successful decryption!
+ return $ret;
+ }
+
+ // Obviously if we're here we could not decrypt anything. Bail out.
+ $this->_fofAuth_CryptoKey = null;
+
+ return null;
+ }
+
+ /**
+ * Creates a decryption key for use with the TOTP decryption method
+ *
+ * @param integer $time The timestamp used for TOTP calculation, leave empty to use current timestamp
+ *
+ * @codeCoverageIgnore
+ * @return string THe encryption key
+ */
+ private function _createDecryptionKey($time = null)
+ {
+ $totp = new FOFEncryptTotp($this->fofAuth_timeStep);
+ $otp = $totp->getCode($this->fofAuth_Key, $time);
+
+ $key = hash('sha256', $this->fofAuth_Key . $otp);
+
+ return $key;
+ }
+
+ /**
+ * Main function to detect if we're running in a CLI environment and we're admin
+ *
+ * @return array isCLI and isAdmin. It's not an associtive array, so we can use list.
+ */
+ public static function isCliAdmin()
+ {
+ static $isCLI = null;
+ static $isAdmin = null;
+
+ if (is_null($isCLI) && is_null($isAdmin))
+ {
+ $isCLI = FOFPlatform::getInstance()->isCli();
+ $isAdmin = FOFPlatform::getInstance()->isBackend();
+ }
+
+ return array($isCLI, $isAdmin);
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/download/adapter/abstract.php b/deployed/akeeba/libraries/fof/download/adapter/abstract.php
new file mode 100644
index 00000000..19f46f2f
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/adapter/abstract.php
@@ -0,0 +1,113 @@
+supportsChunkDownload;
+ }
+
+ /**
+ * Does this download adapter support reading the size of a remote file?
+ *
+ * @return boolean True if remote file size determination is supported
+ */
+ public function supportsFileSize()
+ {
+ return $this->supportsFileSize;
+ }
+
+ /**
+ * Is this download class supported in the current server environment?
+ *
+ * @return boolean True if this server environment supports this download class
+ */
+ public function isSupported()
+ {
+ return $this->isSupported;
+ }
+
+ /**
+ * Get the priority of this adapter. If multiple download adapters are
+ * supported on a site, the one with the highest priority will be
+ * used.
+ *
+ * @return boolean
+ */
+ public function getPriority()
+ {
+ return $this->priority;
+ }
+
+ /**
+ * Returns the name of this download adapter in use
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Download a part (or the whole) of a remote URL and return the downloaded
+ * data. You are supposed to check the size of the returned data. If it's
+ * smaller than what you expected you've reached end of file. If it's empty
+ * you have tried reading past EOF. If it's larger than what you expected
+ * the server doesn't support chunk downloads.
+ *
+ * If this class' supportsChunkDownload returns false you should assume
+ * that the $from and $to parameters will be ignored.
+ *
+ * @param string $url The remote file's URL
+ * @param integer $from Byte range to start downloading from. Use null for start of file.
+ * @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is ignored)
+ * @param array $params Additional params that will be added before performing the download
+ *
+ * @return string The raw file data retrieved from the remote URL.
+ *
+ * @throws Exception A generic exception is thrown on error
+ */
+ public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
+ {
+ return '';
+ }
+
+ /**
+ * Get the size of a remote file in bytes
+ *
+ * @param string $url The remote file's URL
+ *
+ * @return integer The file size, or -1 if the remote server doesn't support this feature
+ */
+ public function getFileSize($url)
+ {
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/download/adapter/cacert.pem b/deployed/akeeba/libraries/fof/download/adapter/cacert.pem
new file mode 100644
index 00000000..f9126f8f
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/adapter/cacert.pem
@@ -0,0 +1,5104 @@
+##
+## Bundle of CA Root Certificates
+##
+## Certificate data from CentOS as of Oct 3 2015
+##
+## This is a bundle of X.509 certificates of public Certificate Authorities
+## (CA). These were automatically extracted from CentOS /etc/pki/tls/certs/ca-bundle.crt
+##
+## 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.
+##
+
+-----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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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-----
+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=
+-----END CERTIFICATE-----
+
+-----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=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
+MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
+ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
+BAMTFOFkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
+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-----
+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-----
+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-----
+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
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----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-----
+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-----
+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-----
+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-----
+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-----
+MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB
+lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt
+T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc
+BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3
+dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP
+HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO
+KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo
+5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+
+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb
+kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC
+AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov
+L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV
+HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN
+AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw
+NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB
+mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU
+4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5
+81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR
+Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g=
+-----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-----
+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-----
+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-----
+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-----
+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=
+-----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-----
+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-----
+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-----
+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-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
+AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
+CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
+BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
+VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
+qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
+HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
+G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
+lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
+IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
+0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
+k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
+4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
+m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
+cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
+uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
+KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
+ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
+AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
+VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
+CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
+cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
+QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
+7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
+cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
+QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
+czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
+aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
+aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
+BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
+D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
+JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
+AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
+vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
+tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
+7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
+h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
+d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
+pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
+AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
+CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
+MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
+RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
+AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
+09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
+XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
+Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
+t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
+X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
+MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
+fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
+2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
+K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
+ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
+BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
+MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
+RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
+fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
+gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
+I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
+5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
+ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
+MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
+o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
+zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
+GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
+r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
+Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx
+CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp
+ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa
+QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw
+NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft
+ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu
+QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG
+qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL
+fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ
+Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4
+Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ
+54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b
+MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j
+ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej
+YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt
+A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF
+rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ
+pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB
+lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy
+YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50
+7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs
+YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6
+xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc
+unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/
+Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp
+ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42
+gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0
+jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+
+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD
+W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/
+RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r
+MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk
+BYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
+BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
+MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
+SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
+ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
+UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
+4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
+KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
+gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
+rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
+51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
+be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
+KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
+v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
+fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
+jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
+ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
+e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
+jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
+WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
+SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
+pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
+X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
+fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
+K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
+ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
+LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
+LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
+Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
+ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
+MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
+yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
+VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
+nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
+XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
+vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
+Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
+N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
+nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
+YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
+kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
+QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
+6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
+yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
+QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
+tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
+QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
+Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
+olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
+x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
+dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
+A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
+cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
+qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
+JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
+s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
+HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
+70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
+V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
+qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
+5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
+C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
+OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
+FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
+KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
+8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
+MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
+0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
+u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
+u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
+YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
+GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
+RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
+KeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
+VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
+cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
+BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
+VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
+0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
+ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
+A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
+aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
+flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
+MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
+b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
+aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
+j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
+f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
+IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
+FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
+QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
+/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
+k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
+MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
+seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
+hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
+DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
+B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
+AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
+EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
+FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
+REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
+Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
+VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
+SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
+4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
+cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
+eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
+A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
+vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
+DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
+maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
+lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
+KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB
+VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp
+bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R
+dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw
+MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy
+dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52
+ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM
+EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj
+lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ
+znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH
+2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1
+k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs
+2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD
+VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
+AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG
+KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+
+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R
+FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE
+DNuxUCAKGkq6ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
+ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
+l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
+HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
+5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
+WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
+gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
+DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
+BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
+h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
+LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
+6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
+L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
+1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
+MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
+QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
+arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
+Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
+FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
+P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
+9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
+uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
+9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
+OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
+KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
+DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
+H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
+I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
+5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
+3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
+Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
+Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL
+MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
+VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg
+isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z
+NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI
++MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R
+hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+
+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP
+Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s
+EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2
+mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC
+e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow
+dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
+ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
+N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
+tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
+0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
+/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
+KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
+zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
+O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
+34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
+K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
+Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
+QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
+IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
+HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
+O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
+033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
+dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
+kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
+3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
+u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
+4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
+MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
+AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
+CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
+YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
+Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
+mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
+XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
+S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
+FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
+AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
+ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
+ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
+Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
+DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
+yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
+EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
+EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
+PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
+MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
+D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
+OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
+fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
+IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
+oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
+/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
+rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
+3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
+7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
+yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
+qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
+hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
+SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
+HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
+emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
+AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
+7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
+DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
+F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
+a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
+Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
+MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
+NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
+PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
+x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
+QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
+yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
+QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
+H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
+QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
+i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
+nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
+rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
+hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
+GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
+lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
+TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
+nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
+gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
+G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
+zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
+L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
+BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
+DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
+BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
+QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
+gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
+zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
+130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
+JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
+ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
+AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
+AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
+9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
+bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
+fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
+HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
+t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
+MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
+BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
+Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
+cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
+aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
+F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
+8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
+rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
+/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
+7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
+28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
+lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
+nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
+0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
+5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
+WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
+jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
+ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
+OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
+619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
+2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
+o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
+nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
+5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
+pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
+dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
+BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
+-----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-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
+AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
+QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
+MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
+0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
+UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
+RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
+OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
+JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
+AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
+BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
+LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
+MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
+44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
+Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
+i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
+9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
+MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
+ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
+cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
+WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
+Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
+IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
+UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
+TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
+BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
+kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
+AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
+HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
+sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
+I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
+J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
+VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
+TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
+MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
+aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
+T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
+sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
+TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
+/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
+7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
+EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
+hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
+a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
+aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
+TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
+PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
+cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
+tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
+BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
+ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
+ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
+jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
+ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
+P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
+xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
+Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
+5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
+/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
+AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
+5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
+IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
+MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
+dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
+EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
+MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
+28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
+VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
+DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
+5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
+ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
+Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
+UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
+Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
+hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
+HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
+YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
+L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
+ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
+IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
+HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
+DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
+PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
+5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
+glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
+FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
+pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
+xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
+tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
+jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
+fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
+d0jQ
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
+Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
+Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
+aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
+Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
+SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
+aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
+ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
+7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
+DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
+zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
+hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
+4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
+gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
+NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
+j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
+52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
+echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
+zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
+wy39FCqQmbkHzJ8=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
+TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
+MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
+Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
+IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
+dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
+V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
+GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
+v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
+AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
+Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
+76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
+OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
+ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
+yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
+buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
+2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
+-----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-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0
+MRMwEQYDVQQDEwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQG
+EwJJTDAeFw0wNDAzMjQxMTMyMThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMT
+CkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNpZ24xCzAJBgNVBAYTAklMMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49qROR+WCf4C9DklBKK
+8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTyP2Q2
+98CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb
+2CEJKHxNGGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxC
+ejVb7Us6eva1jsz/D3zkYDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7Kpi
+Xd3DTKaCQeQzC6zJMw9kglcq/QytNuEMrkvF7zuZ2SOzW120V+x0cAwqTwIDAQAB
+o4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2Zl
+ZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0PAQH/BAQD
+AgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRL
+AZs+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWd
+foPPbrxHbvUanlR2QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0M
+cXS6hMTXcpuEfDhOZAYnKuGntewImbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq
+8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb/627HOkthIDYIb6FUtnUdLlp
+hbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VGzT2ouvDzuFYk
+Res3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U
+AGegcQCCSA==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw
+PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu
+MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx
+GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL
+MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf
+HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh
+gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW
+v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue
+Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr
+9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt
+6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7
+MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl
+Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58
+ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq
+hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p
+iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC
+dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL
+kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL
+hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
+A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
+bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
+ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
+b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
+7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
+J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
+HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
+t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
+FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
+XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
+MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
+hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
+MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
+A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
+Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
+XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
+omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
+A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+
+-----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
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
+n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
+biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
+EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
+bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
+YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
+BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
+QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
+0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
+lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
+B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
+ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
+IhNzbM8m9Yop5w==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
+RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
+Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
+RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
+AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
+JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
+6pZjamVFkpUBtA==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----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-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
+ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
+HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
+UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
+tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
+ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
+lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
+/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
+A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
+dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
+MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
+cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
+L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
+BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
+acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
+zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
+PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
+Johw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
+NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
+BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
+ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
+3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
+qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
+p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
+HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
+ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
+HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
+Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
+c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
+RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
+dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
+Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
+3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
+CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
+xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
+KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
+BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
+ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
+MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
+SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
+a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
+4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
+tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
+tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
+dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
+c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
+TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
++kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
+Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
+OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
+fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
+l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
+FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
+8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
+6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
+TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
+wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
+Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
+xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
+DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
+Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
+hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
+7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
+QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
+8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
+dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
+YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
+dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
+IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
+LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
+EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
+KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
+ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
+bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
+ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
+85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
+4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
+HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
+QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
+lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
+o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
+opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
+dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
+ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
+AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
+/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
+SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
+Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
+Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
+nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
+MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
+czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
+CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
+MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
+ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
+b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
+euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
+bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
+WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
+MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
+1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
+zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
+BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
+BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
+v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
+E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
+iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
+GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
+MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
+j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
+U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
+u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
+bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
+fF6adulZkMV8gzURZVE=
+-----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-----
+MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
+A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
+d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
+dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
+RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
+MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
+VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
+L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
+Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
+A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
+ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
+Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
+R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
+hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
+cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
+IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
+dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
+NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
+dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
+dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
+aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
+RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
+cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
+wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
+U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
+jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
+BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
+jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
+Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
+1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
+nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
+VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
+IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
+SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
+SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
+ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
+DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
+TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
+fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
+sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
+WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
+nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
+dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
+NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
+AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
+MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
+uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
+PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
+JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
+gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
+j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
+5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
+o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
+/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
+Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
+W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
+hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
+BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
+aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
+BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
+Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
+MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
+em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
+B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
+D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
+Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
+q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
+k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
+fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
+dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
+ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
+zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
+U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
+Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
+XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
+Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
+HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
+GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
+77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
+vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
+FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
+yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
+AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
+y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
+NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
+-----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-----
+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-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
+MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
+KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
+MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
+NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
+BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
+So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
+tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
+CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
+qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
+rD6ogRLQy7rQkgu2npaqBA+K
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
+mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
+MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
+BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
+BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
+hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
+5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
+JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
+DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
+huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
+zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
+kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
+SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
+spki4cErx5z481+oghLrGREt
+-----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-----
+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-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
+VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
+IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
+MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
+MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
+cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
+A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
+BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
+KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
+G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
+zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
+ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
+HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
+Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
+yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
+beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
+6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
+zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
+BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
+ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
+ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
+cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
+YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
+CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
+KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
+hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
+UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
+X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
+fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
+a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
+Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
+SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
+AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
+M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
+v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
+FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
+uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
+kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
+ewv4n4Q=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
+8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
+hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
+KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
+515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
+xwy8p2Fp8fc74SrL+SvzZpA3
+-----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
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
+RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
+dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
+YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
+NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
+EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
+cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
+dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
+fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
+bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
+75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
+FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
+HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
+5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
+b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
+A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
+6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
+dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
+Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
+l7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
+FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
+Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
+A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
+b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
+jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
+PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
+ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
+nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
+q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
+MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
+mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
+7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
+oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
+EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
+fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
+AmvZWg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
+VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
+MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
+JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
+3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
+S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
+bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
+T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
+vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
+Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
+dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
+c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
+l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
+iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
+ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
+6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
+LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
+nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
+W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
+AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
+l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
+4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
+mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
+7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
+VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
+MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
+MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
+ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
+RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
+bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
+/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
+3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
+EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
+9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
+GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
+2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
+WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
+W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
+AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
+t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
+DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
+TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
+lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
+mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
+WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
+tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
+GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
+8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
+-----END CERTIFICATE-----
+
+-----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==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
+AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
+dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
+MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
+CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
+MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
+SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
+ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
+LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
+PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
+2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
+ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
+MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
+AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
+AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
+AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
+AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
+BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
+P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
+CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
+kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
+HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
+na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
+qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
+TbvGRNs2yyqcjg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
+cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
+b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
+ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
+NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
+TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
+Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
+uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
+vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
+Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
+62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
+AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
+LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
+BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
+AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
+MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
+ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
+AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
+ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
+AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
+AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
+bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
+Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
+PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
+Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
+EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
+w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
+cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
+HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
+VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
+BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
+b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
+8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
+ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
+7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
+hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
+MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----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-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
+ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
+aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
+ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
+NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
+A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
+VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
+SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
+VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
+w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
+mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
+4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
+4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
+EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
+SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
+ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
+vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
+Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
+/L7fCg0=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
+dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
+YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
+dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
+aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
+IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
+KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
+MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
+b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
+KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
+A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
+aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
+7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
+BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
+ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
+JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
+PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
+0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
+6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
+v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
+K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
+bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
+MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
+MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
+gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
+b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
+bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
+cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
+ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
+ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
+hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
+AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
+MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
+RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
+UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
+cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
+AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
+AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
+1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
+3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
+Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
+HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
+pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
+sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
+qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
+mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
+opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
+YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
+MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
+wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
+rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
+68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
+4Pw5qlPafX7PGglTvFOFBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
+UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
+abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
+3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
+KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
+hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
+Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
+zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
+ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
+cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
+qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
+YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
+b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
+8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
+NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
+ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
+q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
+nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
+-----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-----
+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
+MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
+qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
+n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
+c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
+o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
+IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
+IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
+8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
+vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
+7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
+cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
+ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
+roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
+W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
+lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
+csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
+dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
+KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
+HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
+WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
+-----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-----
+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
+MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
+/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
+FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
+U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
+ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
+FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
+A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
+eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
+sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
+VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
+A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
+ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
+FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
+oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
+u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
+0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
+3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
+8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
+PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
+ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
+UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
+R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
+MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
+JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
+WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
+SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
+u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
+A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
+Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
+MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
+aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
+IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
+cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
+YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
+bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
+bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
+aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
+ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
+YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
+ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
+LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
+Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
+eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
+CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
+A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
+Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
+lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
+b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
+9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
+ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
+IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----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-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
+MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
+A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
+MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
+Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
+QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
+i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
+h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
+MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
+UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
+8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
+h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
+KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
+X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
+QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
+pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
+QSdJQO7e5iNEOdyhIta6A/I=
+-----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-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
+MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
+IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
+bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
+RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
+zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
+bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
+MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
+VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
+OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
+tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
+q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
+EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
+VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
+DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
+dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
+YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
+OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
+zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
+VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
+hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
+ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
+awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
+OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
+coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
+okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
+t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
+1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
+SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIGGTCCBAGgAwIBAgIIPtVRGeZNzn4wDQYJKoZIhvcNAQELBQAwajEhMB8GA1UE
+AxMYU0cgVFJVU1QgU0VSVklDRVMgUkFDSU5FMRwwGgYDVQQLExMwMDAyIDQzNTI1
+Mjg5NTAwMDIyMRowGAYDVQQKExFTRyBUUlVTVCBTRVJWSUNFUzELMAkGA1UEBhMC
+RlIwHhcNMTAwOTA2MTI1MzQyWhcNMzAwOTA1MTI1MzQyWjBqMSEwHwYDVQQDExhT
+RyBUUlVTVCBTRVJWSUNFUyBSQUNJTkUxHDAaBgNVBAsTEzAwMDIgNDM1MjUyODk1
+MDAwMjIxGjAYBgNVBAoTEVNHIFRSVVNUIFNFUlZJQ0VTMQswCQYDVQQGEwJGUjCC
+AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANqoVgLsfJXwTukK0rcHoyKL
+ULO5Lhk9V9sZqtIr5M5C4myh5F0lHjMdtkXRtPpZilZwyW0IdmlwmubHnAgwE/7m
+0ZJoYT5MEfJu8rF7V1ZLCb3cD9lxDOiaN94iEByZXtaxFwfTpDktwhpz/cpLKQfC
+eSnIyCauLMT8I8hL4oZWDyj9tocbaF85ZEX9aINsdSQePHWZYfrSFPipS7HYfad4
+0hNiZbXWvn5qA7y1svxkMMPQwpk9maTTzdGxxFOHe0wTE2Z/v9VlU2j5XB7ltP82
+mUWjn2LAfxGCAVTeD2WlOa6dSEyJoxA74OaD9bDaLB56HFwfAKzMq6dgZLPGxXvH
+VUZ0PJCBDkqOWZ1UsEixUkw7mO6r2jS3U81J2i/rlb4MVxH2lkwEeVyZ1eXkvm/q
+R+5RS+8iJq612BGqQ7t4vwt+tN3PdB0lqYljseI0gcSINTjiAg0PE8nVKoIV8IrE
+QzJW5FMdHay2z32bll0eZOl0c8RW5BZKUm2SOdPhTQ4/YrnerbUdZbldUv5dCamc
+tKQM2S9FdqXPjmqanqqwEaHrYcbrPx78ZrQSnUZ/MhaJvnFFr5Eh2f2Tv7QCkUL/
+SR/tixVo3R+OrJvdggWcRGkWZBdWX0EPSk8ED2VQhpOX7EW/XcIc3M/E2DrmeAXQ
+xVVVqV7+qzohu+VyFPcLAgMBAAGjgcIwgb8wHQYDVR0OBBYEFCkgy/HDD9oGjhOT
+h/5fYBopu/O2MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUKSDL8cMP2gaO
+E5OH/l9gGim787YwEQYDVR0gBAowCDAGBgRVHSAAMEkGA1UdHwRCMEAwPqA8oDqG
+OGh0dHA6Ly9jcmwuc2d0cnVzdHNlcnZpY2VzLmNvbS9yYWNpbmUtR3JvdXBlU0cv
+TGF0ZXN0Q1JMMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEATEZn
+4ERQ9cW2urJRCiUTHbfHiC4fuStkoMuTiFJZqmD1zClSF/8E5ze0MRFGfisebKeL
+PEeaXvSqXZA7RT2fSsmKe47A7j55i5KjyJRKuCgRa6YlX129x8j7g09VMeZc8BN8
+471/Kiw3N5RJr4QfFCeiWBCPCjk3GhIgQY8Z9qkfGe2yNLKtfTNEi18KB0PydkVF
+La3kjQ4A/QQIqudr+xe9sAhWDjUqcvCz5006Tw3c82ASszhkjNv54SaNL+9O6CRH
+PjY0imkPKGuLh8a9hSb50+tpIVZgkdb34GLCqHGuLt5mI7VSRqakSDcsfwEWVxH3
+Jw0O5Q/WkEXhHj8h3NL8FhgTPk1qsiZqQF4leP049KxYejcbmEAEx47J1MRnYbGY
+rvDNDty5r2WDewoEij9hqvddQYbmxkzCTzpcVuooO6dEz8hKZPVyYC3jQ7hK4HU8
+MuSqFtcRucFF2ZtmY2blIrc07rrVdC8lZPOBVMt33lfUk+OsBzE6PlwDg1dTx/D+
+aNglUE0SyObhlY1nqzyTPxcCujjXnvcwpT09RAEzGpqfjtCf8e4wiHPvriQZupdz
+FcHscQyEZLV77LxpPqRtCRY2yko5isune8YdfucziMm+MG2chZUh6Uc7Bn6B4upG
+5nBYgOao8p0LadEziVkw82TTC/bOKwn7fRB2LhA=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y
+MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg
+TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS
+b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS
+M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC
+UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d
+Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p
+rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l
+pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb
+j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC
+KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS
+/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X
+cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH
+1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP
+px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7
+MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
+eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u
+2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS
+v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC
+wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy
+CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e
+vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6
+Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa
+Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL
+eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8
+FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc
+7uzXLg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
+DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
+ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
+b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
+qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
+uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
+Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
+pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
+5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
+UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
+GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
+5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
+6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
+eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
+B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
+BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
+L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
+SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
+CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
+5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
+IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
+gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
+vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
+bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
+N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
+Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
+ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX
+DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
+ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
+b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP
+cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW
+IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX
+xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy
+KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR
+9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az
+5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8
+6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7
+Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP
+bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt
+BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt
+XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF
+MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd
+INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
+U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp
+LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8
+Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp
+gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh
+/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw
+0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A
+fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq
+4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR
+1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/
+QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM
+94B7IWcnMFk=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
+ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
+dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
+OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
+8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
+Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
+hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
+6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
+AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
+bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
+ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
+qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
+0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
+sSi6
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
+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
+AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
+ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
+ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
+aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
+YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
+c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
+d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
+CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
+wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
+Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
+0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
+pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
+CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
+P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
+1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
+KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
+8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
+fyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
+OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
+A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
+CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
+JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
+vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
+D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
+Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
+RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
+HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
+nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
+0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
+UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
+Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
+TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
+BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
+UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
+6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
+9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
+HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
+wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
+XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
+IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
+hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
+so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCB
+rjELMAkGA1UEBhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcp
+MRIwEAYDVQQHEwlTdHV0dGdhcnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fz
+c2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVTLVRSVVNUIEF1dGhlbnRpY2F0aW9u
+IGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0wNTA2MjIwMDAwMDBa
+Fw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFkZW4t
+V3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMg
+RGV1dHNjaGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJV
+U1QgQXV0aGVudGljYXRpb24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBO
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1
+toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob4QSwI7+Vio5bG0F/WsPo
+TUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXLg3KSwlOy
+ggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1
+XgqfeN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteF
+hy+S8dF2g08LOlk3KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm
+7QIDAQABo4GSMIGPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG
+MCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJvbmxpbmUxLTIwNDgtNTAdBgNV
+HQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAUD8oeXHngovMp
+ttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD
+pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFo
+LtU96G7m1R08P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersF
+iXOMy6ZNwPv2AtawB6MDwidAnwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0y
+h9WUUpY6RsZxlj33mA6ykaqP2vROJAA5VeitF7nTNCtKqUDMFypVZUF0Qn71wK/I
+k63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8bHz2eBIPdltkdOpQ=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID2DCCAsCgAwIBAgIQYFbFSyNAW2TU7SXa2dYeHjANBgkqhkiG9w0BAQsFADCB
+hTELMAkGA1UEBhMCREUxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fzc2VuIFZl
+cmxhZyBHbWJIMScwJQYDVQQLEx5TLVRSVVNUIENlcnRpZmljYXRpb24gU2Vydmlj
+ZXMxIjAgBgNVBAMTGVMtVFJVU1QgVW5pdmVyc2FsIFJvb3QgQ0EwHhcNMTMxMDIy
+MDAwMDAwWhcNMzgxMDIxMjM1OTU5WjCBhTELMAkGA1UEBhMCREUxKTAnBgNVBAoT
+IERldXRzY2hlciBTcGFya2Fzc2VuIFZlcmxhZyBHbWJIMScwJQYDVQQLEx5TLVRS
+VVNUIENlcnRpZmljYXRpb24gU2VydmljZXMxIjAgBgNVBAMTGVMtVFJVU1QgVW5p
+dmVyc2FsIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo
+4wvfETeFgpq1bGZ8YT/ARxodRuOwVWTluII5KAd+F//0m4rwkYHqOD8heGxI7Gsv
+otOKcrKn19nqf7TASWswJYmM67fVQGGY4tw8IJLNZUpynxqOjPolFb/zIYMoDYuv
+WRGCQ1ybTSVRf1gYY2A7s7WKi1hjN0hIkETCQN1d90NpKZhcEmVeq5CSS2bf1XUS
+U1QYpt6K1rtXAzlZmRgFDPn9FcaQZEYXgtfCSkE9/QC+V3IYlHcbU1qJAfYzcg6T
+OtzoHv0FBda8c+CI3KtP7LUYhk95hA5IKmYq3TLIeGXIC51YAQVx7YH1aBduyw20
+S9ih7K446xxYL6FlAzQvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
+AQH/BAQDAgEGMB0GA1UdDgQWBBSafdfr639UmEUptCCrbQuWIxmkwjANBgkqhkiG
+9w0BAQsFAAOCAQEATpYS2353XpInniEXGIJ22D+8pQkEZoiJrdtVszNqxmXEj03z
+MjbceQSWqXcy0Zf1GGuMuu3OEdBEx5LxtESO7YhSSJ7V/Vn4ox5R+wFS5V/let2q
+JE8ii912RvaloA812MoPmLkwXSBvwoEevb3A/hXTOCoJk5gnG5N70Cs0XmilFU/R
+UsOgyqCDRR319bdZc11ZAY+qwkcvFHHVKeMQtUeTJcwjKdq3ctiR1OwbSIoi5MEq
+9zpok59FGW5Dt8z+uJGaYRo2aWNkkijzb2GShROfyQcsi1fc65551cLeCNVUsldO
+KjKNoeI60RAgIjl9NEVvcTvDHfz/sk+o4vYwHg==
+-----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-----
+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
+MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
+Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
+AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
+Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
+jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
+0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
+2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
+ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
+y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
+tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
+6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
+uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
+acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
+k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
+VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
+FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
+b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
+fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
+/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
+REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
+srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
+aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
+woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
+Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
+t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
+8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
+9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
+wSsSnqaeG8XmDtkx2Q==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
+ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
+dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
+IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
+VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
+dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
+MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
+UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
+1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
+oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
+HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
+5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
+idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
+OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
+NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
+46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
+UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
+7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
+A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
+bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
+XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
+PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
+Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
+WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
+Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
+7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
+nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
+vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
+WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
+fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
+I+2ksx0WckNLIOFZfsLorSa/ovc=
+-----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-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
+tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
+uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
+XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
+8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
+5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
+kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
+GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
+ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
+au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
+hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
+dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
+BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
+Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
+OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
+SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
+VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
+Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
+Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
+1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
+ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
+Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
+XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
+dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
+Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
+JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
+Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
+irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
+TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
+g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
+95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
+S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
+MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
+BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
+c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
+MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
+R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
+VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
+JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
+fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
+jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
+wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
+fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
+VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
+CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
+7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
+8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
+ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
+2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
+NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
+b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
+VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
+VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
+7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
+Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
+/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
+81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
+dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
+Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
+sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
+pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
+slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
+arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
+VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
+9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
+dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
+TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
+Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
+Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
+OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
+vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
+t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
+HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
+SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDNjCCAp+gAwIBAgIQNhIilsXjOKUgodJfTNcJVDANBgkqhkiG9w0BAQUFADCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIxMDEwMTIzNTk1OVow
+gc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcT
+CUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNV
+BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRo
+YXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1z
+ZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
+aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560
+ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
++ao6hnO2RlNYyIkFvYMRuHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/
+BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBlkKyID1bZ5jA01CbH0FDxkt5r1DmI
+CSLGpmODA/eZd9iy5Ri4XWPz1HP7bJyZePFLeH0ZJMMrAoT4vCLZiiLXoPxx7JGH
+IPG47LHlVYCsPVLIOQ7C8MAFT9aCdYy9X9LcdpoFEsmvcsPcJX6kTY4XpeCHf+Ga
+WuFg3GQjPEIuTQ==
+-----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-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
+IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
+BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
+MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
+YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
+dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
+BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
+papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
+DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
+KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
+XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
+MdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
+ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
+MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
+MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
+iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
+vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
+0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
+OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
+BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
+FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
+GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
+zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
+1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
+f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
+jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
+ZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
+AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
+FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
+1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
+jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
+wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
+WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
+NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
+uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
+IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
+g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
+BSeOE6Fuwg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
+8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
+RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
+hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
+ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
+EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
+A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
+WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
+1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
+6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
+91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
+TpPDpFQUWw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
+MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
+bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
+VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
+YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
+dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
+Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
+aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
+QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
+xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
+aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
+IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
+gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
+O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
+fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
+lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
+AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
+NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
+wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
+7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
+gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
+oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
+yZyQ2uypQjyttgI=
+-----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-----
+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
+UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
+c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
+S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
+SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
+OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
+b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
+VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
+sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
+ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
+KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
++7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
+HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
+IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
+733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
+Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
+AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
+mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
+XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
+qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
+-----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-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
+EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
+VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
+NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
+B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
+10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
+0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
+MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
+zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
+46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
+yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
+laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
+oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
+BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
+qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
+4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
+1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
+H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
+RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
+15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
+6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
+nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
+wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
+aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
+KwbQBM0=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
+MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
+V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
+WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
+LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
+AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
+K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
+RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
+rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
+3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
+hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
+MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
+XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
+lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
+aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
+YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
+eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
+JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
+VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
+I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
+o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
+A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
+zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
+RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
+zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
+TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBAFgVKTk8d6PaXCUDfGD67gmZPCcQcMgMCeazh88K4hiW
+NWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n0a3hUKw8fGJLj7qE1xIV
+Gx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZRjXZ+Hxb
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
+U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
+SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
+biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
+GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
+fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
+aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
+aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
+kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
+4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
+FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
+vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
+ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
+IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
+IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
+bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
+9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
+H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
+LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
+/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
+rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
+WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
+exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
+sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
+seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
+4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
+lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
+7M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+-----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=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
+BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
+MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
+b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
+rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
+fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
+f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
+ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
+x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
+aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
+zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
+uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
+mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
+Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
+HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
+EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
+LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
+MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
+JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
+g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
+dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
+R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
+PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
+xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
+J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
+OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
+ee5Ehr7XHuQe+w==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
+MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
+BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
+MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
+ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
+D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
+9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
+v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
+UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
+NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
+qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
+yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
+AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
+J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
+AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
+WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
+yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
+/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
+jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
+ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
+X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
+FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
+u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
+O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
+ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
+2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIEMDCCA5mgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBxzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMR8wHQYDVQQHExZSZXNlYXJjaCBUcmlh
+bmdsZSBQYXJrMRYwFAYDVQQKEw1SZWQgSGF0LCBJbmMuMSEwHwYDVQQLExhSZWQg
+SGF0IE5ldHdvcmsgU2VydmljZXMxIzAhBgNVBAMTGlJITlMgQ2VydGlmaWNhdGUg
+QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9yaG5zQHJlZGhhdC5jb20wHhcNMDAw
+ODIzMjI0NTU1WhcNMDMwODI4MjI0NTU1WjCBxzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAgTDk5vcnRoIENhcm9saW5hMR8wHQYDVQQHExZSZXNlYXJjaCBUcmlhbmdsZSBQ
+YXJrMRYwFAYDVQQKEw1SZWQgSGF0LCBJbmMuMSEwHwYDVQQLExhSZWQgSGF0IE5l
+dHdvcmsgU2VydmljZXMxIzAhBgNVBAMTGlJITlMgQ2VydGlmaWNhdGUgQXV0aG9y
+aXR5MR4wHAYJKoZIhvcNAQkBFg9yaG5zQHJlZGhhdC5jb20wgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBAMBoKxIw4iEtIsZycVu/F6CTEOmb48mNOy2sxLuVO+DK
+VTLclcIQswSyUfvohWEWNKW0HWdcp3f08JLatIuvlZNi82YprsCIt2SEDkiQYPhg
+PgB/VN0XpqwY4ELefL6Qgff0BYUKCMzV8p/8JIt3pT3pSKnvDztjo/6mg0zo3At3
+AgMBAAGjggEoMIIBJDAdBgNVHQ4EFgQUVBXNnyz37A0f0qi+TAesiD77mwowgfQG
+A1UdIwSB7DCB6YAUVBXNnyz37A0f0qi+TAesiD77mwqhgc2kgcowgccxCzAJBgNV
+BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEfMB0GA1UEBxMWUmVzZWFy
+Y2ggVHJpYW5nbGUgUGFyazEWMBQGA1UEChMNUmVkIEhhdCwgSW5jLjEhMB8GA1UE
+CxMYUmVkIEhhdCBOZXR3b3JrIFNlcnZpY2VzMSMwIQYDVQQDExpSSE5TIENlcnRp
+ZmljYXRlIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJARYPcmhuc0ByZWRoYXQuY29t
+ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAkwGIiGdnkYye0BIU
+kHESh1UK8lIbrfLTBx2vcJm7sM2AI8ntK3PpY7HQs4xgxUJkpsGVVpDFNQYDWPWO
+K9n5qaAQqZn3FUKSpVDXEQfxAtXgcORVbirOJfhdzQsvEGH49iBCzMOJ+IpPgiQS
+zzl/IagsjVKXUsX3X0KlhwlmsMw=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID7jCCA1egAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsTELMAkGA1UEBhMCVVMx
+FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRAwDgYDVQQHEwdSYWxlaWdoMRYwFAYD
+VQQKEw1SZWQgSGF0LCBJbmMuMRgwFgYDVQQLEw9SZWQgSGF0IE5ldHdvcmsxIjAg
+BgNVBAMTGVJITiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEW
+EnJobi1ub2NAcmVkaGF0LmNvbTAeFw0wMjA5MDUyMDQ1MTZaFw0wNzA5MDkyMDQ1
+MTZaMIGxMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAO
+BgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsT
+D1JlZCBIYXQgTmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhv
+cml0eTEhMB8GCSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tMIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQCzFrfF9blpUR/NtD1wz2BXhaQqp10oIg7sGeKS
+90iXpqYfUZWDEY+amKKQ4MtKJBmUqIpLiLQGbM531xU7PM1mg88jHQ28CgzLH8tA
++/PZ/iq0hSx7yaH+849oHfISsaQWGc4PuJqc2bxfSWKylZPOXS7deTzxW6a3orU5
+DY4SMQIDAQABo4IBEjCCAQ4wHQYDVR0OBBYEFH8bZKEuAsWofbjRsYsGnaOpUGOS
+MIHeBgNVHSMEgdYwgdOAFH8bZKEuAsWofbjRsYsGnaOpUGOSoYG3pIG0MIGxMQsw
+CQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcTB1Jh
+bGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsTD1JlZCBIYXQg
+TmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhvcml0eTEhMB8G
+CSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEEBQADgYEAKE1C5TQi3caGYwR1UmcXRXLyOyErRVlyc/dZNp1X
+Q8bclA8O/xNcT1A3hbLkwh81n3T051P7oQa4Oc7kCoZ7XyhdxxGeEqXWuWzpGAnV
+8ELnVLWRniOtEnqqcnw5PIP4daR7A5L/KtTFdhkS+rQ7sIkslYwBkA3YugYFYQCs
+ldo=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIID7jCCA1egAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsTELMAkGA1UEBhMCVVMx
+FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRAwDgYDVQQHEwdSYWxlaWdoMRYwFAYD
+VQQKEw1SZWQgSGF0LCBJbmMuMRgwFgYDVQQLEw9SZWQgSGF0IE5ldHdvcmsxIjAg
+BgNVBAMTGVJITiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEW
+EnJobi1ub2NAcmVkaGF0LmNvbTAeFw0wMzA4MjkwMjEwNTVaFw0xMzA4MjYwMjEw
+NTVaMIGxMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAO
+BgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsT
+D1JlZCBIYXQgTmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhv
+cml0eTEhMB8GCSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tMIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQC/YWPrPYsrRUjmwvt80iEhuOyQk0EwfCyNedUU
+6Q5+P+/WCpsKpgJSAS0mlqTtvameqggDwWEKQYDqrnTMYSbQBZFVPmYUoiCz1p1x
+DKt3zPTwEbUlM4pOIpoQNmf6EW1Idjof0uNEe4lmvrSF+y+mqhP6mm3JuxjEBK9P
+FWmJmwIDAQABo4IBEjCCAQ4wHQYDVR0OBBYEFGlEJwXcLu2l9IHE13hF50Rd+IdH
+MIHeBgNVHSMEgdYwgdOAFGlEJwXcLu2l9IHE13hF50Rd+IdHoYG3pIG0MIGxMQsw
+CQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcTB1Jh
+bGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsTD1JlZCBIYXQg
+TmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhvcml0eTEhMB8G
+CSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEEBQADgYEAI8nKB59eljmD4E7a3UeEMMrU1TiG+d6Ig8osRyY2
+q/QUHigp3n0QSl6RPlqZBwypLuP7eERJxTLW6HqX/ynQM64munYGfnmXFwxPLSqL
+iqxBWa7pxFUtuYjfm3tB+DIu7snAWeIwV143RynALXgz086jK9yE2r87Lku2s7ZO
+noA=
+-----END CERTIFICATE-----
+
diff --git a/deployed/akeeba/libraries/fof/download/adapter/curl.php b/deployed/akeeba/libraries/fof/download/adapter/curl.php
new file mode 100644
index 00000000..70035bb6
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/adapter/curl.php
@@ -0,0 +1,226 @@
+priority = 110;
+ $this->supportsFileSize = true;
+ $this->supportsChunkDownload = true;
+ $this->name = 'curl';
+ $this->isSupported = function_exists('curl_init') && function_exists('curl_exec') && function_exists('curl_close');
+ }
+
+ /**
+ * Download a part (or the whole) of a remote URL and return the downloaded
+ * data. You are supposed to check the size of the returned data. If it's
+ * smaller than what you expected you've reached end of file. If it's empty
+ * you have tried reading past EOF. If it's larger than what you expected
+ * the server doesn't support chunk downloads.
+ *
+ * If this class' supportsChunkDownload returns false you should assume
+ * that the $from and $to parameters will be ignored.
+ *
+ * @param string $url The remote file's URL
+ * @param integer $from Byte range to start downloading from. Use null for start of file.
+ * @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is ignored)
+ * @param array $params Additional params that will be added before performing the download
+ *
+ * @return string The raw file data retrieved from the remote URL.
+ *
+ * @throws Exception A generic exception is thrown on error
+ */
+ public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
+ {
+ $ch = curl_init();
+
+ if (empty($from))
+ {
+ $from = 0;
+ }
+
+ if (empty($to))
+ {
+ $to = 0;
+ }
+
+ if ($to < $from)
+ {
+ $temp = $to;
+ $to = $from;
+ $from = $temp;
+
+ unset($temp);
+ }
+
+ // Default cURL options
+ $options = array(
+ CURLOPT_AUTOREFERER => 1,
+ CURLOPT_SSL_VERIFYPEER => 1,
+ CURLOPT_SSL_VERIFYHOST => 2,
+ CURLOPT_SSLVERSION => 0,
+ CURLOPT_AUTOREFERER => 1,
+ CURLOPT_URL => $url,
+ CURLOPT_BINARYTRANSFER => 1,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_FOLLOWLOCATION => 1,
+ CURLOPT_CAINFO => __DIR__ . '/cacert.pem',
+ CURLOPT_HEADERFUNCTION => array($this, 'reponseHeaderCallback')
+ );
+
+ if (!(empty($from) && empty($to)))
+ {
+ $options[CURLOPT_RANGE] = "$from-$to";
+ }
+
+ // Add any additional options: Since they are numeric, we must use the array operator. If the jey exists in both
+ // arrays, only the first one will be used while the second one will be ignored
+ $options = $params + $options;
+
+ @curl_setopt_array($ch, $options);
+
+ $this->headers = array();
+
+ $result = curl_exec($ch);
+
+ $errno = curl_errno($ch);
+ $errmsg = curl_error($ch);
+ $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+ if ($result === false)
+ {
+ $error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_CURL_ERROR', $errno, $errmsg);
+ }
+ elseif (($http_status >= 300) && ($http_status <= 399) && isset($this->headers['Location']) && !empty($this->headers['Location']))
+ {
+ return $this->downloadAndReturn($this->headers['Location'], $from, $to, $params);
+ }
+ elseif ($http_status > 399)
+ {
+ $result = false;
+ $errno = $http_status;
+ $error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR', $http_status);
+ }
+
+ curl_close($ch);
+
+ if ($result === false)
+ {
+ throw new Exception($error, $errno);
+ }
+ else
+ {
+ return $result;
+ }
+ }
+
+ /**
+ * Get the size of a remote file in bytes
+ *
+ * @param string $url The remote file's URL
+ *
+ * @return integer The file size, or -1 if the remote server doesn't support this feature
+ */
+ public function getFileSize($url)
+ {
+ $result = -1;
+
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
+ curl_setopt($ch, CURLOPT_SSLVERSION, 0);
+
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_NOBODY, true );
+ curl_setopt($ch, CURLOPT_HEADER, true );
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
+ @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true );
+ @curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem');
+
+ $data = curl_exec($ch);
+ curl_close($ch);
+
+ if ($data)
+ {
+ $content_length = "unknown";
+ $status = "unknown";
+ $redirection = null;
+
+ if (preg_match( "/^HTTP\/1\.[01] (\d\d\d)/", $data, $matches))
+ {
+ $status = (int)$matches[1];
+ }
+
+ if (preg_match( "/Content-Length: (\d+)/", $data, $matches))
+ {
+ $content_length = (int)$matches[1];
+ }
+
+ if (preg_match( "/Location: (.*)/", $data, $matches))
+ {
+ $redirection = (int)$matches[1];
+ }
+
+ if ($status == 200)
+ {
+ $result = $content_length;
+ }
+
+ if (($status > 300) && ($status <= 308))
+ {
+ if (!empty($redirection))
+ {
+ return $this->getFileSize($redirection);
+ }
+
+ return -1;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Handles the HTTP headers returned by cURL
+ *
+ * @param resource $ch cURL resource handle (unused)
+ * @param string $data Each header line, as returned by the server
+ *
+ * @return int The length of the $data string
+ */
+ protected function reponseHeaderCallback(&$ch, &$data)
+ {
+ $strlen = strlen($data);
+
+ if (($strlen) <= 2)
+ {
+ return $strlen;
+ }
+
+ if (substr($data, 0, 4) == 'HTTP')
+ {
+ return $strlen;
+ }
+
+ list($header, $value) = explode(': ', trim($data), 2);
+
+ $this->headers[$header] = $value;
+
+ return $strlen;
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/download/adapter/fopen.php b/deployed/akeeba/libraries/fof/download/adapter/fopen.php
new file mode 100644
index 00000000..aa9cb3df
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/adapter/fopen.php
@@ -0,0 +1,123 @@
+priority = 100;
+ $this->supportsFileSize = false;
+ $this->supportsChunkDownload = true;
+ $this->name = 'fopen';
+
+ // If we are not allowed to use ini_get, we assume that URL fopen is
+ // disabled.
+ if (!function_exists('ini_get'))
+ {
+ $this->isSupported = false;
+ }
+ else
+ {
+ $this->isSupported = ini_get('allow_url_fopen');
+ }
+ }
+
+ /**
+ * Download a part (or the whole) of a remote URL and return the downloaded
+ * data. You are supposed to check the size of the returned data. If it's
+ * smaller than what you expected you've reached end of file. If it's empty
+ * you have tried reading past EOF. If it's larger than what you expected
+ * the server doesn't support chunk downloads.
+ *
+ * If this class' supportsChunkDownload returns false you should assume
+ * that the $from and $to parameters will be ignored.
+ *
+ * @param string $url The remote file's URL
+ * @param integer $from Byte range to start downloading from. Use null for start of file.
+ * @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is ignored)
+ * @param array $params Additional params that will be added before performing the download
+ *
+ * @return string The raw file data retrieved from the remote URL.
+ *
+ * @throws Exception A generic exception is thrown on error
+ */
+ public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
+ {
+ if (empty($from))
+ {
+ $from = 0;
+ }
+
+ if (empty($to))
+ {
+ $to = 0;
+ }
+
+ if ($to < $from)
+ {
+ $temp = $to;
+ $to = $from;
+ $from = $temp;
+
+ unset($temp);
+ }
+
+ if (!(empty($from) && empty($to)))
+ {
+ $options = array(
+ 'http' => array(
+ 'method' => 'GET',
+ 'header' => "Range: bytes=$from-$to\r\n"
+ ),
+ 'ssl' => array(
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/cacert.pem',
+ 'verify_depth' => 5,
+ )
+ );
+
+ $options = array_merge($options, $params);
+
+ $context = stream_context_create($options);
+ $result = @file_get_contents($url, false, $context, $from - $to + 1);
+ }
+ else
+ {
+ $options = array(
+ 'http' => array(
+ 'method' => 'GET',
+ ),
+ 'ssl' => array(
+ 'verify_peer' => true,
+ 'cafile' => __DIR__ . '/cacert.pem',
+ 'verify_depth' => 5,
+ )
+ );
+
+ $options = array_merge($options, $params);
+
+ $context = stream_context_create($options);
+ $result = @file_get_contents($url, false, $context);
+ }
+
+ if ($result === false)
+ {
+ $error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR');
+ throw new Exception($error, 1);
+ }
+ else
+ {
+ return $result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/download/download.php b/deployed/akeeba/libraries/fof/download/download.php
new file mode 100644
index 00000000..3d346b95
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/download.php
@@ -0,0 +1,495 @@
+isSupported())
+ {
+ continue;
+ }
+
+ if ($adapter->priority > $priority)
+ {
+ $this->adapter = $adapter;
+ $priority = $adapter->priority;
+ }
+ }
+
+ // Load the language strings
+ FOFPlatform::getInstance()->loadTranslations('lib_f0f');
+ }
+
+ /**
+ * Forces the use of a specific adapter
+ *
+ * @param string $className The name of the class or the name of the adapter, e.g. 'FOFDownloadAdapterCurl' or
+ * 'curl'
+ */
+ public function setAdapter($className)
+ {
+ $adapter = null;
+
+ if (class_exists($className, true))
+ {
+ $adapter = new $className;
+ }
+ elseif (class_exists('FOFDownloadAdapter' . ucfirst($className)))
+ {
+ $className = 'FOFDownloadAdapter' . ucfirst($className);
+ $adapter = new $className;
+ }
+
+ if (is_object($adapter) && ($adapter instanceof FOFDownloadInterface))
+ {
+ $this->adapter = $adapter;
+ }
+ }
+
+ /**
+ * Returns the name of the current adapter
+ *
+ * @return string
+ */
+ public function getAdapterName()
+ {
+ if(is_object($this->adapter))
+ {
+ $class = get_class($this->adapter);
+
+ return strtolower(str_ireplace('FOFDownloadAdapter', '', $class));
+ }
+
+ return '';
+ }
+
+ /**
+ * Sets the additional options for the adapter
+ *
+ * @param array $options
+ */
+ public function setAdapterOptions(array $options)
+ {
+ $this->adapterOptions = $options;
+ }
+
+ /**
+ * Returns the additional options for the adapter
+ *
+ * @return array
+ */
+ public function getAdapterOptions()
+ {
+ return $this->adapterOptions;
+ }
+
+ /**
+ * Used to decode the $params array
+ *
+ * @param string $key The parameter key you want to retrieve the value for
+ * @param mixed $default The default value, if none is specified
+ *
+ * @return mixed The value for this parameter key
+ */
+ private function getParam($key, $default = null)
+ {
+ if (array_key_exists($key, $this->params))
+ {
+ return $this->params[$key];
+ }
+ else
+ {
+ return $default;
+ }
+ }
+
+ /**
+ * Download data from a URL and return it
+ *
+ * @param string $url The URL to download from
+ *
+ * @return bool|string The downloaded data or false on failure
+ */
+ public function getFromURL($url)
+ {
+ try
+ {
+ return $this->adapter->downloadAndReturn($url, null, null, $this->adapterOptions);
+ }
+ catch (Exception $e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Performs the staggered download of file. The downloaded file will be stored in Joomla!'s temp-path using the
+ * basename of the URL as a filename
+ *
+ * The $params array can have any of the following keys
+ * url The file being downloaded
+ * frag Rolling counter of the file fragment being downloaded
+ * totalSize The total size of the file being downloaded, in bytes
+ * doneSize How many bytes we have already downloaded
+ * maxExecTime Maximum execution time downloading file fragments, in seconds
+ * length How many bytes to download at once
+ *
+ * The array returned is in the following format:
+ *
+ * status True if there are no errors, false if there are errors
+ * error A string with the error message if there are errors
+ * frag The next file fragment to download
+ * totalSize The total size of the downloaded file in bytes, if the server supports HEAD requests
+ * doneSize How many bytes have already been downloaded
+ * percent % of the file already downloaded (if totalSize could be determined)
+ * localfile The name of the local file, without the path
+ *
+ * @param array $params A parameters array, as sent by the user interface
+ *
+ * @return array A return status array
+ */
+ public function importFromURL($params)
+ {
+ $this->params = $params;
+
+ // Fetch data
+ $url = $this->getParam('url');
+ $localFilename = $this->getParam('localFilename');
+ $frag = $this->getParam('frag', -1);
+ $totalSize = $this->getParam('totalSize', -1);
+ $doneSize = $this->getParam('doneSize', -1);
+ $maxExecTime = $this->getParam('maxExecTime', 5);
+ $runTimeBias = $this->getParam('runTimeBias', 75);
+ $length = $this->getParam('length', 1048576);
+
+ if (empty($localFilename))
+ {
+ $localFilename = basename($url);
+
+ if (strpos($localFilename, '?') !== false)
+ {
+ $paramsPos = strpos($localFilename, '?');
+ $localFilename = substr($localFilename, 0, $paramsPos - 1);
+ }
+ }
+
+ $tmpDir = JFactory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
+ $tmpDir = rtrim($tmpDir, '/\\');
+
+ // Init retArray
+ $retArray = array(
+ "status" => true,
+ "error" => '',
+ "frag" => $frag,
+ "totalSize" => $totalSize,
+ "doneSize" => $doneSize,
+ "percent" => 0,
+ "localfile" => $localFilename
+ );
+
+ try
+ {
+ $timer = new FOFUtilsTimer($maxExecTime, $runTimeBias);
+ $start = $timer->getRunningTime(); // Mark the start of this download
+ $break = false; // Don't break the step
+
+ // Figure out where on Earth to put that file
+ $local_file = $tmpDir . '/' . $localFilename;
+
+ while (($timer->getTimeLeft() > 0) && !$break)
+ {
+ // Do we have to initialize the file?
+ if ($frag == -1)
+ {
+ // Currently downloaded size
+ $doneSize = 0;
+
+ if (@file_exists($local_file))
+ {
+ @unlink($local_file);
+ }
+
+ // Delete and touch the output file
+ $fp = @fopen($local_file, 'wb');
+
+ if ($fp !== false)
+ {
+ @fclose($fp);
+ }
+
+ // Init
+ $frag = 0;
+
+ //debugMsg("-- First frag, getting the file size");
+ $retArray['totalSize'] = $this->adapter->getFileSize($url);
+ $totalSize = $retArray['totalSize'];
+ }
+
+ // Calculate from and length
+ $from = $frag * $length;
+ $to = $length + $from - 1;
+
+ // Try to download the first frag
+ $required_time = 1.0;
+
+ try
+ {
+ $result = $this->adapter->downloadAndReturn($url, $from, $to, $this->adapterOptions);
+
+ if ($result === false)
+ {
+ throw new Exception(JText::sprintf('LIB_FOF_DOWNLOAD_ERR_COULDNOTDOWNLOADFROMURL', $url), 500);
+ }
+ }
+ catch (Exception $e)
+ {
+ $result = false;
+ $error = $e->getMessage();
+ }
+
+ if ($result === false)
+ {
+ // Failed download
+ if ($frag == 0)
+ {
+ // Failure to download first frag = failure to download. Period.
+ $retArray['status'] = false;
+ $retArray['error'] = $error;
+
+ //debugMsg("-- Download FAILED");
+
+ return $retArray;
+ }
+ else
+ {
+ // Since this is a staggered download, consider this normal and finish
+ $frag = -1;
+ //debugMsg("-- Import complete");
+ $totalSize = $doneSize;
+ $break = true;
+ }
+ }
+
+ // Add the currently downloaded frag to the total size of downloaded files
+ if ($result)
+ {
+ $filesize = strlen($result);
+ //debugMsg("-- Successful download of $filesize bytes");
+ $doneSize += $filesize;
+
+ // Append the file
+ $fp = @fopen($local_file, 'ab');
+
+ if ($fp === false)
+ {
+ //debugMsg("-- Can't open local file $local_file for writing");
+ // Can't open the file for writing
+ $retArray['status'] = false;
+ $retArray['error'] = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_COULDNOTWRITELOCALFILE', $local_file);
+
+ return $retArray;
+ }
+
+ fwrite($fp, $result);
+ fclose($fp);
+
+ //debugMsg("-- Appended data to local file $local_file");
+
+ $frag++;
+
+ //debugMsg("-- Proceeding to next fragment, frag $frag");
+
+ if (($filesize < $length) || ($filesize > $length))
+ {
+ // A partial download or a download larger than the frag size means we are done
+ $frag = -1;
+ //debugMsg("-- Import complete (partial download of last frag)");
+ $totalSize = $doneSize;
+ $break = true;
+ }
+ }
+
+ // Advance the frag pointer and mark the end
+ $end = $timer->getRunningTime();
+
+ // Do we predict that we have enough time?
+ $required_time = max(1.1 * ($end - $start), $required_time);
+
+ if ($required_time > (10 - $end + $start))
+ {
+ $break = true;
+ }
+
+ $start = $end;
+ }
+
+ if ($frag == -1)
+ {
+ $percent = 100;
+ }
+ elseif ($doneSize <= 0)
+ {
+ $percent = 0;
+ }
+ else
+ {
+ if ($totalSize > 0)
+ {
+ $percent = 100 * ($doneSize / $totalSize);
+ }
+ else
+ {
+ $percent = 0;
+ }
+ }
+
+ // Update $retArray
+ $retArray = array(
+ "status" => true,
+ "error" => '',
+ "frag" => $frag,
+ "totalSize" => $totalSize,
+ "doneSize" => $doneSize,
+ "percent" => $percent,
+ );
+ }
+ catch (Exception $e)
+ {
+ //debugMsg("EXCEPTION RAISED:");
+ //debugMsg($e->getMessage());
+ $retArray['status'] = false;
+ $retArray['error'] = $e->getMessage();
+ }
+
+ return $retArray;
+ }
+
+ /**
+ * This method will crawl a starting directory and get all the valid files
+ * that will be analyzed by __construct. Then it organizes them into an
+ * associative array.
+ *
+ * @param string $path Folder where we should start looking
+ * @param array $ignoreFolders Folder ignore list
+ * @param array $ignoreFiles File ignore list
+ *
+ * @return array Associative array, where the `fullpath` key contains the path to the file,
+ * and the `classname` key contains the name of the class
+ */
+ protected static function getFiles($path, array $ignoreFolders = array(), array $ignoreFiles = array())
+ {
+ $return = array();
+
+ $files = self::scanDirectory($path, $ignoreFolders, $ignoreFiles);
+
+ // Ok, I got the files, now I have to organize them
+ foreach ($files as $file)
+ {
+ $clean = str_replace($path, '', $file);
+ $clean = trim(str_replace('\\', '/', $clean), '/');
+
+ $parts = explode('/', $clean);
+
+ $return[] = array(
+ 'fullpath' => $file,
+ 'classname' => 'FOFDownloadAdapter' . ucfirst(basename($parts[0], '.php'))
+ );
+ }
+
+ return $return;
+ }
+
+ /**
+ * Recursive function that will scan every directory unless it's in the
+ * ignore list. Files that aren't in the ignore list are returned.
+ *
+ * @param string $path Folder where we should start looking
+ * @param array $ignoreFolders Folder ignore list
+ * @param array $ignoreFiles File ignore list
+ *
+ * @return array List of all the files
+ */
+ protected static function scanDirectory($path, array $ignoreFolders = array(), array $ignoreFiles = array())
+ {
+ $return = array();
+
+ $handle = @opendir($path);
+
+ if ( !$handle)
+ {
+ return $return;
+ }
+
+ while (($file = readdir($handle)) !== false)
+ {
+ if ($file == '.' || $file == '..')
+ {
+ continue;
+ }
+
+ $fullpath = $path . '/' . $file;
+
+ if ((is_dir($fullpath) && in_array($file, $ignoreFolders)) || (is_file($fullpath) && in_array($file, $ignoreFiles)))
+ {
+ continue;
+ }
+
+ if (is_dir($fullpath))
+ {
+ $return = array_merge(self::scanDirectory($fullpath, $ignoreFolders, $ignoreFiles), $return);
+ }
+ else
+ {
+ $return[] = $path . '/' . $file;
+ }
+ }
+
+ return $return;
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/download/interface.php b/deployed/akeeba/libraries/fof/download/interface.php
new file mode 100644
index 00000000..b5089fa5
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/download/interface.php
@@ -0,0 +1,79 @@
+adapter = new FOFEncryptAesOpenssl();
+
+ if (!$this->adapter->isSupported($phpfunc))
+ {
+ $this->adapter = new FOFEncryptAesMcrypt();
+ }
+ }
+ else
+ {
+ $this->adapter = new FOFEncryptAesMcrypt();
+
+ if (!$this->adapter->isSupported($phpfunc))
+ {
+ $this->adapter = new FOFEncryptAesOpenssl();
+ }
+ }
+
+ $this->adapter->setEncryptionMode($mode, $strength);
+ $this->setPassword($key, true);
+ }
+
+ /**
+ * Sets the password for this instance.
+ *
+ * WARNING: Do not use the legacy mode, it's insecure
+ *
+ * @param string $password The password (either user-provided password or binary encryption key) to use
+ * @param bool $legacyMode True to use the legacy key expansion. We recommend against using it.
+ */
+ public function setPassword($password, $legacyMode = false)
+ {
+ $this->key = $password;
+
+ $passLength = strlen($password);
+
+ if (function_exists('mb_strlen'))
+ {
+ $passLength = mb_strlen($password, 'ASCII');
+ }
+
+ // Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
+ if ($legacyMode && ($passLength != 32))
+ {
+ // Legacy mode: use the sha256 of the password
+ $this->key = hash('sha256', $password, true);
+ // We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
+ $this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
+ }
+ }
+
+ /**
+ * Encrypts a string using AES
+ *
+ * @param string $stringToEncrypt The plaintext to encrypt
+ * @param bool $base64encoded Should I Base64-encode the result?
+ *
+ * @return string The cryptotext. Please note that the first 16 bytes of
+ * the raw string is the IV (initialisation vector) which
+ * is necessary for decoding the string.
+ */
+ public function encryptString($stringToEncrypt, $base64encoded = true)
+ {
+ $blockSize = $this->adapter->getBlockSize();
+ $randVal = new FOFEncryptRandval();
+ $iv = $randVal->generate($blockSize);
+
+ $key = $this->getExpandedKey($blockSize, $iv);
+ $cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);
+
+ // Optionally pass the result through Base64 encoding
+ if ($base64encoded)
+ {
+ $cipherText = base64_encode($cipherText);
+ }
+
+ // Return the result
+ return $cipherText;
+ }
+
+ /**
+ * Decrypts a ciphertext into a plaintext string using AES
+ *
+ * @param string $stringToDecrypt The ciphertext to decrypt. The first 16 bytes of the raw string must contain
+ * the IV (initialisation vector).
+ * @param bool $base64encoded Should I Base64-decode the data before decryption?
+ *
+ * @return string The plain text string
+ */
+ public function decryptString($stringToDecrypt, $base64encoded = true)
+ {
+ if ($base64encoded)
+ {
+ $stringToDecrypt = base64_decode($stringToDecrypt);
+ }
+
+ // Extract IV
+ $iv_size = $this->adapter->getBlockSize();
+ $iv = substr($stringToDecrypt, 0, $iv_size);
+ $key = $this->getExpandedKey($iv_size, $iv);
+
+ // Decrypt the data
+ $plainText = $this->adapter->decrypt($stringToDecrypt, $key);
+
+ return $plainText;
+ }
+
+ /**
+ * Is AES encryption supported by this PHP installation?
+ *
+ * @param FOFUtilsPhpfunc $phpfunc
+ *
+ * @return boolean
+ */
+ public static function isSupported(FOFUtilsPhpfunc $phpfunc = null)
+ {
+ if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
+ {
+ $phpfunc = new FOFUtilsPhpfunc();
+ }
+
+ $adapter = new FOFEncryptAesMcrypt();
+
+ if (!$adapter->isSupported($phpfunc))
+ {
+ $adapter = new FOFEncryptAesOpenssl();
+ }
+
+ if (!$adapter->isSupported($phpfunc))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('base64_encode'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('base64_decode'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('hash_algos'))
+ {
+ return false;
+ }
+
+ $algorightms = $phpfunc->hash_algos();
+
+ if (!in_array('sha256', $algorightms))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param $blockSize
+ * @param $iv
+ *
+ * @return string
+ */
+ public function getExpandedKey($blockSize, $iv)
+ {
+ $key = $this->key;
+ $passLength = strlen($key);
+
+ if (function_exists('mb_strlen'))
+ {
+ $passLength = mb_strlen($key, 'ASCII');
+ }
+
+ if ($passLength != $blockSize)
+ {
+ $iterations = 1000;
+ $salt = $this->adapter->resizeKey($iv, 16);
+ $key = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
+ }
+
+ return $key;
+ }
+}
+
+if (!function_exists('hash_pbkdf2'))
+{
+ function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $raw_output = false)
+ {
+ if (!in_array(strtolower($algo), hash_algos()))
+ {
+ trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
+ }
+
+ if (!is_numeric($count))
+ {
+ trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . gettype($count) . ' given', E_USER_WARNING);
+ }
+
+ if (!is_numeric($length))
+ {
+ trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . gettype($length) . ' given', E_USER_WARNING);
+ }
+
+ if ($count <= 0)
+ {
+ trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
+ }
+
+ if ($length < 0)
+ {
+ trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
+ }
+
+ $output = '';
+ $block_count = $length ? ceil($length / strlen(hash($algo, '', $raw_output))) : 1;
+
+ for ($i = 1; $i <= $block_count; $i++)
+ {
+ $last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);
+
+ for ($j = 1; $j < $count; $j++)
+ {
+ $xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
+ }
+
+ $output .= $xorsum;
+ }
+
+ if (!$raw_output)
+ {
+ $output = bin2hex($output);
+ }
+
+ return $length ? substr($output, 0, $length) : $output;
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/encrypt/aes/abstract.php b/deployed/akeeba/libraries/fof/encrypt/aes/abstract.php
new file mode 100644
index 00000000..981f653b
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/aes/abstract.php
@@ -0,0 +1,88 @@
+ $size)
+ {
+ if (function_exists('mb_substr'))
+ {
+ return mb_substr($key, 0, $size, 'ASCII');
+ }
+
+ return substr($key, 0, $size);
+ }
+
+ return $key . str_repeat("\0", ($size - $keyLength));
+ }
+
+ /**
+ * Returns null bytes to append to the string so that it's zero padded to the specified block size
+ *
+ * @param string $string The binary string which will be zero padded
+ * @param int $blockSize The block size
+ *
+ * @return string The zero bytes to append to the string to zero pad it to $blockSize
+ */
+ protected function getZeroPadding($string, $blockSize)
+ {
+ $stringSize = strlen($string);
+
+ if (function_exists('mb_strlen'))
+ {
+ $stringSize = mb_strlen($string, 'ASCII');
+ }
+
+ if ($stringSize == $blockSize)
+ {
+ return '';
+ }
+
+ if ($stringSize < $blockSize)
+ {
+ return str_repeat("\0", $blockSize - $stringSize);
+ }
+
+ $paddingBytes = $stringSize % $blockSize;
+
+ return str_repeat("\0", $blockSize - $paddingBytes);
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/encrypt/aes/interface.php b/deployed/akeeba/libraries/fof/encrypt/aes/interface.php
new file mode 100644
index 00000000..e8edaebe
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/aes/interface.php
@@ -0,0 +1,83 @@
+cipherType = MCRYPT_RIJNDAEL_128;
+ break;
+
+ case '192':
+ $this->cipherType = MCRYPT_RIJNDAEL_192;
+ break;
+
+ case '256':
+ $this->cipherType = MCRYPT_RIJNDAEL_256;
+ break;
+ }
+
+ switch (strtolower($mode))
+ {
+ case 'ecb':
+ $this->cipherMode = MCRYPT_MODE_ECB;
+ break;
+
+ default:
+ case 'cbc':
+ $this->cipherMode = MCRYPT_MODE_CBC;
+ break;
+ }
+
+ }
+
+ public function encrypt($plainText, $key, $iv = null)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = $this->resizeKey($iv, $iv_size);
+
+ if (empty($iv))
+ {
+ $randVal = new FOFEncryptRandval();
+ $iv = $randVal->generate($iv_size);
+ }
+
+ $cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv);
+ $cipherText = $iv . $cipherText;
+
+ return $cipherText;
+ }
+
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);
+
+ return $plainText;
+ }
+
+ public function isSupported(FOFUtilsPhpfunc $phpfunc = null)
+ {
+ if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
+ {
+ $phpfunc = new FOFUtilsPhpfunc();
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_get_key_size'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_get_iv_size'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_create_iv'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_encrypt'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_decrypt'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('mcrypt_list_algorithms'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('hash'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('hash_algos'))
+ {
+ return false;
+ }
+
+ $algorightms = $phpfunc->mcrypt_list_algorithms();
+
+ if (!in_array('rijndael-128', $algorightms))
+ {
+ return false;
+ }
+
+ if (!in_array('rijndael-192', $algorightms))
+ {
+ return false;
+ }
+
+ if (!in_array('rijndael-256', $algorightms))
+ {
+ return false;
+ }
+
+ $algorightms = $phpfunc->hash_algos();
+
+ if (!in_array('sha256', $algorightms))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getBlockSize()
+ {
+ return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/encrypt/aes/openssl.php b/deployed/akeeba/libraries/fof/encrypt/aes/openssl.php
new file mode 100644
index 00000000..094b1e50
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/aes/openssl.php
@@ -0,0 +1,172 @@
+openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
+ }
+
+ public function setEncryptionMode($mode = 'cbc', $strength = 128)
+ {
+ static $availableAlgorithms = null;
+ static $defaultAlgo = 'aes-128-cbc';
+
+ if (!is_array($availableAlgorithms))
+ {
+ $availableAlgorithms = openssl_get_cipher_methods();
+
+ foreach (array('aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
+ 'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb') as $algo)
+ {
+ if (in_array($algo, $availableAlgorithms))
+ {
+ $defaultAlgo = $algo;
+ break;
+ }
+ }
+ }
+
+ $strength = (int) $strength;
+ $mode = strtolower($mode);
+
+ if (!in_array($strength, array(128, 192, 256)))
+ {
+ $strength = 256;
+ }
+
+ if (!in_array($mode, array('cbc', 'ebc')))
+ {
+ $mode = 'cbc';
+ }
+
+ $algo = 'aes-' . $strength . '-' . $mode;
+
+ if (!in_array($algo, $availableAlgorithms))
+ {
+ $algo = $defaultAlgo;
+ }
+
+ $this->method = $algo;
+ }
+
+ public function encrypt($plainText, $key, $iv = null)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = $this->resizeKey($iv, $iv_size);
+
+ if (empty($iv))
+ {
+ $randVal = new FOFEncryptRandval();
+ $iv = $randVal->generate($iv_size);
+ }
+
+ $plainText .= $this->getZeroPadding($plainText, $iv_size);
+ $cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
+ $cipherText = $iv . $cipherText;
+
+ return $cipherText;
+ }
+
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
+
+ return $plainText;
+ }
+
+ public function isSupported(FOFUtilsPhpfunc $phpfunc = null)
+ {
+ if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
+ {
+ $phpfunc = new FOFUtilsPhpfunc();
+ }
+
+ if (!$phpfunc->function_exists('openssl_get_cipher_methods'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('openssl_random_pseudo_bytes'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('openssl_cipher_iv_length'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('openssl_encrypt'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('openssl_decrypt'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('hash'))
+ {
+ return false;
+ }
+
+ if (!$phpfunc->function_exists('hash_algos'))
+ {
+ return false;
+ }
+
+ $algorightms = $phpfunc->openssl_get_cipher_methods();
+
+ if (!in_array('aes-128-cbc', $algorightms))
+ {
+ return false;
+ }
+
+ $algorightms = $phpfunc->hash_algos();
+
+ if (!in_array('sha256', $algorightms))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return int
+ */
+ public function getBlockSize()
+ {
+ return openssl_cipher_iv_length($this->method);
+ }
+}
\ No newline at end of file
diff --git a/deployed/akeeba/libraries/fof/encrypt/base32.php b/deployed/akeeba/libraries/fof/encrypt/base32.php
new file mode 100644
index 00000000..768e064d
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/base32.php
@@ -0,0 +1,222 @@
+ 0)
+ {
+ throw new Exception('Length must be divisible by 8');
+ }
+
+ if (!preg_match('/^[01]+$/', $str))
+ {
+ throw new Exception('Only 0\'s and 1\'s are permitted');
+ }
+
+ preg_match_all('/.{8}/', $str, $chrs);
+ $chrs = array_map('bindec', $chrs[0]);
+
+ // I'm just being slack here
+ array_unshift($chrs, 'C*');
+
+ return call_user_func_array('pack', $chrs);
+ }
+
+ /**
+ * fromBin
+ *
+ * Converts a correct binary string to base32
+ *
+ * @param string $str The string of 0's and 1's you want to convert
+ *
+ * @return string String encoded as base32
+ *
+ * @throws exception
+ */
+ private function fromBin($str)
+ {
+ if (strlen($str) % 8 > 0)
+ {
+ throw new Exception('Length must be divisible by 8');
+ }
+
+ if (!preg_match('/^[01]+$/', $str))
+ {
+ throw new Exception('Only 0\'s and 1\'s are permitted');
+ }
+
+ // Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
+ $str = preg_replace('/(.{5})/', '000$1', $str);
+
+ // We need a string divisible by 5
+ $length = strlen($str);
+ $rbits = $length & 7;
+
+ if ($rbits > 0)
+ {
+ // Excessive bits need to be padded
+ $ebits = substr($str, $length - $rbits);
+ $str = substr($str, 0, $length - $rbits);
+ $str .= "000$ebits" . str_repeat('0', 5 - strlen($ebits));
+ }
+
+ preg_match_all('/.{8}/', $str, $chrs);
+ $chrs = array_map(array($this, '_mapcharset'), $chrs[0]);
+
+ return join('', $chrs);
+ }
+
+ /**
+ * toBin
+ *
+ * Accepts a base32 string and returns an ascii binary string
+ *
+ * @param string $str The base32 string to convert
+ *
+ * @return string Ascii binary string
+ *
+ * @throws Exception
+ */
+ private function toBin($str)
+ {
+ if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str))
+ {
+ throw new Exception('Must match character set');
+ }
+
+ // Convert the base32 string back to a binary string
+ $str = join('', array_map(array($this, '_mapbin'), str_split($str)));
+
+ // Remove the extra 0's we added
+ $str = preg_replace('/000(.{5})/', '$1', $str);
+
+ // Unpad if nessicary
+ $length = strlen($str);
+ $rbits = $length & 7;
+
+ if ($rbits > 0)
+ {
+ $str = substr($str, 0, $length - $rbits);
+ }
+
+ return $str;
+ }
+
+ /**
+ * fromString
+ *
+ * Convert any string to a base32 string
+ * This should be binary safe...
+ *
+ * @param string $str The string to convert
+ *
+ * @return string The converted base32 string
+ */
+ public function encode($str)
+ {
+ return $this->fromBin($this->str2bin($str));
+ }
+
+ /**
+ * toString
+ *
+ * Convert any base32 string to a normal sctring
+ * This should be binary safe...
+ *
+ * @param string $str The base32 string to convert
+ *
+ * @return string The normal string
+ */
+ public function decode($str)
+ {
+ $str = strtoupper($str);
+
+ return $this->bin2str($this->tobin($str));
+ }
+
+ /**
+ * _mapcharset
+ *
+ * Used with array_map to map the bits from a binary string
+ * directly into a base32 character set
+ *
+ * @param string $str The string of 0's and 1's you want to convert
+ *
+ * @return string Resulting base32 character
+ *
+ * @access private
+ */
+ private function _mapcharset($str)
+ {
+ // Huh!
+ $x = self::CSRFC3548;
+
+ return $x[bindec($str)];
+ }
+
+ /**
+ * _mapbin
+ *
+ * Used with array_map to map the characters from a base32
+ * character set directly into a binary string
+ *
+ * @param string $chr The caracter to map
+ *
+ * @return string String of 0's and 1's
+ *
+ * @access private
+ */
+ private function _mapbin($chr)
+ {
+ return sprintf('%08b', strpos(self::CSRFC3548, $chr));
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/encrypt/randval.php b/deployed/akeeba/libraries/fof/encrypt/randval.php
new file mode 100644
index 00000000..d6a51156
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/randval.php
@@ -0,0 +1,196 @@
+phpfunc = $phpfunc;
+ }
+
+ /**
+ *
+ * Returns a cryptographically secure random value.
+ *
+ * @param integer $bytes How many bytes to return
+ *
+ * @return string
+ */
+ public function generate($bytes = 32)
+ {
+ if ($this->phpfunc->extension_loaded('openssl') && (version_compare(PHP_VERSION, '5.3.4') >= 0 || IS_WIN))
+ {
+ $strong = false;
+ $randBytes = openssl_random_pseudo_bytes($bytes, $strong);
+
+ if ($strong)
+ {
+ return $randBytes;
+ }
+ }
+
+ if ($this->phpfunc->extension_loaded('mcrypt'))
+ {
+ return $this->phpfunc->mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
+ }
+
+ return $this->genRandomBytes($bytes);
+ }
+
+ /**
+ * Generate random bytes. Adapted from Joomla! 3.2.
+ *
+ * @param integer $length Length of the random data to generate
+ *
+ * @return string Random binary data
+ */
+ public function genRandomBytes($length = 32)
+ {
+ $length = (int) $length;
+ $sslStr = '';
+
+ /*
+ * Collect any entropy available in the system along with a number
+ * of time measurements of operating system randomness.
+ */
+ $bitsPerRound = 2;
+ $maxTimeMicro = 400;
+ $shaHashLength = 20;
+ $randomStr = '';
+ $total = $length;
+
+ // Check if we can use /dev/urandom.
+ $urandom = false;
+ $handle = null;
+
+ // This is PHP 5.3.3 and up
+ if ($this->phpfunc->function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
+ {
+ $handle = @fopen('/dev/urandom', 'rb');
+
+ if ($handle)
+ {
+ $urandom = true;
+ }
+ }
+
+ while ($length > strlen($randomStr))
+ {
+ $bytes = ($total > $shaHashLength)? $shaHashLength : $total;
+ $total -= $bytes;
+
+ /*
+ * Collect any entropy available from the PHP system and filesystem.
+ * If we have ssl data that isn't strong, we use it once.
+ */
+ $entropy = rand() . uniqid(mt_rand(), true) . $sslStr;
+ $entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
+ $entropy .= memory_get_usage();
+ $sslStr = '';
+
+ if ($urandom)
+ {
+ stream_set_read_buffer($handle, 0);
+ $entropy .= @fread($handle, $bytes);
+ }
+ else
+ {
+ /*
+ * There is no external source of entropy so we repeat calls
+ * to mt_rand until we are assured there's real randomness in
+ * the result.
+ *
+ * Measure the time that the operations will take on average.
+ */
+ $samples = 3;
+ $duration = 0;
+
+ for ($pass = 0; $pass < $samples; ++$pass)
+ {
+ $microStart = microtime(true) * 1000000;
+ $hash = sha1(mt_rand(), true);
+
+ for ($count = 0; $count < 50; ++$count)
+ {
+ $hash = sha1($hash, true);
+ }
+
+ $microEnd = microtime(true) * 1000000;
+ $entropy .= $microStart . $microEnd;
+
+ if ($microStart >= $microEnd)
+ {
+ $microEnd += 1000000;
+ }
+
+ $duration += $microEnd - $microStart;
+ }
+
+ $duration = $duration / $samples;
+
+ /*
+ * Based on the average time, determine the total rounds so that
+ * the total running time is bounded to a reasonable number.
+ */
+ $rounds = (int) (($maxTimeMicro / $duration) * 50);
+
+ /*
+ * Take additional measurements. On average we can expect
+ * at least $bitsPerRound bits of entropy from each measurement.
+ */
+ $iter = $bytes * (int) ceil(8 / $bitsPerRound);
+
+ for ($pass = 0; $pass < $iter; ++$pass)
+ {
+ $microStart = microtime(true);
+ $hash = sha1(mt_rand(), true);
+
+ for ($count = 0; $count < $rounds; ++$count)
+ {
+ $hash = sha1($hash, true);
+ }
+
+ $entropy .= $microStart . microtime(true);
+ }
+ }
+
+ $randomStr .= sha1($entropy, true);
+ }
+
+ if ($urandom)
+ {
+ @fclose($handle);
+ }
+
+ return substr($randomStr, 0, $length);
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/encrypt/randvalinterface.php b/deployed/akeeba/libraries/fof/encrypt/randvalinterface.php
new file mode 100644
index 00000000..85cd6bd7
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/encrypt/randvalinterface.php
@@ -0,0 +1,22 @@
+_timeStep = $timeStep;
+ $this->_passCodeLength = $passCodeLength;
+ $this->_secretLength = $secretLength;
+ $this->_pinModulo = pow(10, $this->_passCodeLength);
+
+ if (is_null($base32))
+ {
+ $this->_base32 = new FOFEncryptBase32;
+ }
+ else
+ {
+ $this->_base32 = $base32;
+ }
+ }
+
+ /**
+ * Get the time period based on the $time timestamp and the Time Step
+ * defined. If $time is skipped or set to null the current timestamp will
+ * be used.
+ *
+ * @param int|null $time Timestamp
+ *
+ * @return int The time period since the UNIX Epoch
+ */
+ public function getPeriod($time = null)
+ {
+ if (is_null($time))
+ {
+ $time = time();
+ }
+
+ $period = floor($time / $this->_timeStep);
+
+ return $period;
+ }
+
+ /**
+ * Check is the given passcode $code is a valid TOTP generated using secret
+ * key $secret
+ *
+ * @param string $secret The Base32-encoded secret key
+ * @param string $code The passcode to check
+ *
+ * @return boolean True if the code is valid
+ */
+ public function checkCode($secret, $code)
+ {
+ $time = $this->getPeriod();
+
+ for ($i = -1; $i <= 1; $i++)
+ {
+ if ($this->getCode($secret, ($time + $i) * $this->_timeStep) == $code)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the TOTP passcode for a given secret key $secret and a given UNIX
+ * timestamp $time
+ *
+ * @param string $secret The Base32-encoded secret key
+ * @param int $time UNIX timestamp
+ *
+ * @return string
+ */
+ public function getCode($secret, $time = null)
+ {
+ $period = $this->getPeriod($time);
+ $secret = $this->_base32->decode($secret);
+
+ $time = pack("N", $period);
+ $time = str_pad($time, 8, chr(0), STR_PAD_LEFT);
+
+ $hash = hash_hmac('sha1', $time, $secret, true);
+ $offset = ord(substr($hash, -1));
+ $offset = $offset & 0xF;
+
+ $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
+ $pinValue = str_pad($truncatedHash % $this->_pinModulo, $this->_passCodeLength, "0", STR_PAD_LEFT);
+
+ return $pinValue;
+ }
+
+ /**
+ * Extracts a part of a hash as an integer
+ *
+ * @param string $bytes The hash
+ * @param string $start The char to start from (0 = first char)
+ *
+ * @return string
+ */
+ protected function hashToInt($bytes, $start)
+ {
+ $input = substr($bytes, $start, strlen($bytes) - $start);
+ $val2 = unpack("N", substr($input, 0, 4));
+
+ return $val2[1];
+ }
+
+ /**
+ * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
+ *
+ * @param string $user User
+ * @param string $hostname Hostname
+ * @param string $secret Secret string
+ *
+ * @return string
+ */
+ public function getUrl($user, $hostname, $secret)
+ {
+ $url = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
+ $encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
+ $encoderURL = $encoder . urlencode($url);
+
+ return $encoderURL;
+ }
+
+ /**
+ * Generates a (semi-)random Secret Key for TOTP generation
+ *
+ * @return string
+ */
+ public function generateSecret()
+ {
+ $secret = "";
+
+ for ($i = 1; $i <= $this->_secretLength; $i++)
+ {
+ $c = rand(0, 255);
+ $secret .= pack("c", $c);
+ }
+ $base32 = new FOFEncryptBase32;
+
+ return $this->_base32->encode($secret);
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/form/field.php b/deployed/akeeba/libraries/fof/form/field.php
new file mode 100644
index 00000000..3f6df7b4
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/form/field.php
@@ -0,0 +1,38 @@
+static))
+ {
+ $this->static = $this->getStatic();
+ }
+
+ return $this->static;
+ break;
+
+ case 'repeatable':
+ if (empty($this->repeatable))
+ {
+ $this->repeatable = $this->getRepeatable();
+ }
+
+ return $this->repeatable;
+ break;
+
+ default:
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Get the rendering of this field type for static display, e.g. in a single
+ * item view (typically a "read" task).
+ *
+ * @since 2.0
+ *
+ * @return string The field HTML
+ */
+ public function getStatic()
+ {
+ $class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
+
+ $params = $this->getOptions();
+
+ $db = FOFPlatform::getInstance()->getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select('a.id AS value, a.title AS text');
+ $query->from('#__viewlevels AS a');
+ $query->group('a.id, a.title, a.ordering');
+ $query->order('a.ordering ASC');
+ $query->order($query->qn('title') . ' ASC');
+
+ // Get the options.
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
+
+ // If params is an array, push these options to the array
+ if (is_array($params))
+ {
+ $options = array_merge($params, $options);
+ }
+
+ // If all levels is allowed, push it into the array.
+ elseif ($params)
+ {
+ array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
+ }
+
+ return '' .
+ htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
+ '';
+ }
+
+ /**
+ * Get the rendering of this field type for a repeatable (grid) display,
+ * e.g. in a view listing many item (typically a "browse" task)
+ *
+ * @since 2.0
+ *
+ * @return string The field HTML
+ */
+ public function getRepeatable()
+ {
+ $class = $this->element['class'] ? (string) $this->element['class'] : '';
+
+ $params = $this->getOptions();
+
+ $db = FOFPlatform::getInstance()->getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select('a.id AS value, a.title AS text');
+ $query->from('#__viewlevels AS a');
+ $query->group('a.id, a.title, a.ordering');
+ $query->order('a.ordering ASC');
+ $query->order($query->qn('title') . ' ASC');
+
+ // Get the options.
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
+
+ // If params is an array, push these options to the array
+ if (is_array($params))
+ {
+ $options = array_merge($params, $options);
+ }
+
+ // If all levels is allowed, push it into the array.
+ elseif ($params)
+ {
+ array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
+ }
+
+ return '' .
+ htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
+ '';
+ }
+}
diff --git a/deployed/akeeba/libraries/fof/form/field/actions.php b/deployed/akeeba/libraries/fof/form/field/actions.php
new file mode 100644
index 00000000..fc232da7
--- /dev/null
+++ b/deployed/akeeba/libraries/fof/form/field/actions.php
@@ -0,0 +1,250 @@
+static))
+ {
+ $this->static = $this->getStatic();
+ }
+
+ return $this->static;
+ break;
+
+ case 'repeatable':
+ if (empty($this->repeatable))
+ {
+ $this->repeatable = $this->getRepeatable();
+ }
+
+ return $this->repeatable;
+ break;
+
+ default:
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Get the field configuration
+ *
+ * @return array
+ */
+ protected function getConfig()
+ {
+ // If no custom options were defined let's figure out which ones of the
+ // defaults we shall use...
+ $config = array(
+ 'published' => 1,
+ 'unpublished' => 1,
+ 'archived' => 0,
+ 'trash' => 0,
+ 'all' => 0,
+ );
+
+ $stack = array();
+
+ if (isset($this->element['show_published']))
+ {
+ $config['published'] = FOFStringUtils::toBool($this->element['show_published']);
+ }
+
+ if (isset($this->element['show_unpublished']))
+ {
+ $config['unpublished'] = FOFStringUtils::toBool($this->element['show_unpublished']);
+ }
+
+ if (isset($this->element['show_archived']))
+ {
+ $config['archived'] = FOFStringUtils::toBool($this->element['show_archived']);
+ }
+
+ if (isset($this->element['show_trash']))
+ {
+ $config['trash'] = FOFStringUtils::toBool($this->element['show_trash']);
+ }
+
+ if (isset($this->element['show_all']))
+ {
+ $config['all'] = FOFStringUtils::toBool($this->element['show_all']);
+ }
+
+ return $config;
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @since 2.0
+ *
+ * @return array The field option objects.
+ */
+ protected function getOptions()
+ {
+ return null;
+ }
+
+ /**
+ * Method to get a
+ *
+ * @param string $enabledFieldName Name of the enabled/published field
+ *
+ * @return FOFFormFieldPublished Field
+ */
+ protected function getPublishedField($enabledFieldName)
+ {
+ $attributes = array(
+ 'name' => $enabledFieldName,
+ 'type' => 'published',
+ );
+
+ if ($this->element['publish_up'])
+ {
+ $attributes['publish_up'] = (string) $this->element['publish_up'];
+ }
+
+ if ($this->element['publish_down'])
+ {
+ $attributes['publish_down'] = (string) $this->element['publish_down'];
+ }
+
+ foreach ($attributes as $name => $value)
+ {
+ if (!is_null($value))
+ {
+ $renderedAttributes[] = $name . '="' . $value . '"';
+ }
+ }
+
+ $publishedXml = new SimpleXMLElement('' . JText::_('JLIB_RULES_SETTINGS_DESC') . '
'; + + // Begin tabs + $html[] = '| '; + $html[] = '' . JText::_('JLIB_RULES_ACTION') . ''; + $html[] = ' | '; + + $html[] = ''; + $html[] = '' . JText::_('JLIB_RULES_SELECT_SETTING') . ''; + $html[] = ' | '; + + // The calculated setting is not shown for the root group of global configuration. + $canCalculateSettings = ($group->parent_id || !empty($component)); + + if ($canCalculateSettings) + { + $html[] = ''; + $html[] = '' . JText::_('JLIB_RULES_CALCULATED_SETTING') . ''; + $html[] = ' | '; + } + + $html[] = '
|---|---|---|
| '; + $html[] = ''; + $html[] = ' | '; + + $html[] = ''; + + $html[] = ' '; + + // If this asset's rule is allowed, but the inherited rule is deny, we have a conflict. + if (($assetRule === true) && ($inheritedRule === false)) + { + $html[] = JText::_('JLIB_RULES_CONFLICT'); + } + + $html[] = ' | '; + + // Build the Calculated Settings column. + // The inherited settings column is not displayed for the root group in global configuration. + if ($canCalculateSettings) + { + $html[] = ''; + + // This is where we show the current effective settings considering currrent group, path and cascade. + // Check whether this is a component or global. Change the text slightly. + + if (JAccess::checkGroup($group->value, 'core.admin', $assetId) !== true) + { + if ($inheritedRule === null) + { + $html[] = '' . JText::_('JLIB_RULES_NOT_ALLOWED') . ''; + } + elseif ($inheritedRule === true) + { + $html[] = '' . JText::_('JLIB_RULES_ALLOWED') . ''; + } + elseif ($inheritedRule === false) + { + if ($assetRule === false) + { + $html[] = '' . JText::_('JLIB_RULES_NOT_ALLOWED') . ''; + } + else + { + $html[] = ' ' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED') + . ''; + } + } + } + elseif (!empty($component)) + { + $html[] = ' ' . JText::_('JLIB_RULES_ALLOWED_ADMIN') + . ''; + } + else + { + // Special handling for groups that have global admin because they can't be denied. + // The admin rights can be changed. + if ($action->name === 'core.admin') + { + $html[] = '' . JText::_('JLIB_RULES_ALLOWED') . ''; + } + elseif ($inheritedRule === false) + { + // Other actions cannot be changed. + $html[] = ' ' + . JText::_('JLIB_RULES_NOT_ALLOWED_ADMIN_CONFLICT') . ''; + } + else + { + $html[] = ' ' . JText::_('JLIB_RULES_ALLOWED_ADMIN') + . ''; + } + } + + $html[] = ' | '; + } + + $html[] = '
"+a.message+"")},function(a){var b=document.getElementById("ak_editor_notifier_content");document.getElementById("ak_editor_notifier").className="akeeba-block--failure";b.textContent=Joomla.JText._("COM_AKEEBA_MULTIDB_GUI_LBL_CONNECTFAIL");"object"==typeof akeeba.Multidb.modalDialog&&akeeba.Multidb.modalDialog.close&&akeeba.Multidb.modalDialog.close();
+akeeba.System.params.errorCallback(a)},!1,15E3)});d=document.getElementById("akEditorBtnSave");f=d.cloneNode(!0);d.parentNode.replaceChild(f,d);akeeba.System.addEventListener(f,"click",function(){try{var a=document.getElementById("ak_editor_notifier");a.parentNode.removeChild(a)}catch(d){}a=document.createElement("div");a.className="akeeba-block--info";a.id="ak_editor_notifier";var c=document.createElement("p");c.id="ak_editor_notifier_content";a.appendChild(c);var f=document.createElement("img");
+f.setAttribute("border",0);f.setAttribute("src",akeeba.Multidb.loadingGif);c.appendChild(f);f=document.createElement("span");f.textContent=Joomla.JText._("COM_AKEEBA_MULTIDB_GUI_LBL_LOADING");c.appendChild(f);document.getElementById("ak_editor_table").insertAdjacentHTML("beforebegin",a.outerHTML);var a=document.getElementById("ake_driver"),a=a.options[a.selectedIndex],a=null==a?"":a.value,g={verb:"set",root:e,data:{host:document.getElementById("ake_host").value,driver:a,port:document.getElementById("ake_port").value,
+username:document.getElementById("ake_username").value,password:document.getElementById("ake_password").value,database:document.getElementById("ake_database").value,prefix:document.getElementById("ake_prefix").value,dumpFile:String(e).substr(0,9)+document.getElementById("ake_database").value+".sql"}},a={action:JSON.stringify(g)};akeeba.System.doAjax(a,function(a){!0!=a?document.getElementById("ak_editor_notifier_content").textContent=Joomla.JText._("COM_AKEEBA_MULTIDB_GUI_LBL_SAVEFAIL"):(akeeba.System.data.set(b,
+"def",JSON.stringify(g.data)),b.querySelectorAll("td"),b.querySelector("span.ak_dbhost").textContent=g.data.host,b.querySelector("span.ak_dbname").textContent=g.data.database,-1==b.querySelector("span.editbutton").firstChild.className.indexOf("akion-edit")&&(b.querySelector("span.deletebutton").parentNode.style.display="inline-block",b.querySelector("span.editbutton").firstChild.className="akion-edit",akeeba.Multidb.addNewRecordButton(b.parentNode)),"object"==typeof akeeba.Multidb.modalDialog&&akeeba.Multidb.modalDialog.close&&
+akeeba.Multidb.modalDialog.close())},function(a){document.getElementById("ak_editor_notifier_content").textContent=Joomla.JText._("COM_AKEEBA_MULTIDB_GUI_LBL_SAVEFAIL");"object"==typeof akeeba.Multidb.modalDialog&&akeeba.Multidb.modalDialog.close&&akeeba.Multidb.modalDialog.close();akeeba.System.params.errorCallback(a)},!1,15E3)});d=document.getElementById("akEditorBtnCancel");f=d.cloneNode(!0);d.parentNode.replaceChild(f,d);akeeba.System.addEventListener(f,"click",function(){"object"==typeof akeeba.Multidb.modalDialog&&
+akeeba.Multidb.modalDialog.close&&akeeba.Multidb.modalDialog.close()});akeeba.Multidb.modalDialog=akeeba.Modal.open({inherit:c,width:"80%"});akeeba.System.triggerEvent(c.querySelector("span"),"focus")});var g=document.createElement("span");g.className="editbutton ak-toggle-button";g.insertAdjacentHTML("beforeend",'');c.appendChild(g);k.appendChild(c);c=document.createElement("td");c.className="ak_filter_item";g=document.createElement("span");g.className="ak_filter_name ak_dbhost";
+g.textContent=b.host;c.appendChild(g);g=document.createElement("td");g.className="ak_filter_item";var m=document.createElement("span");m.className="ak_filter_name ak_dbname";m.textContent=b.database;g.appendChild(m);h.appendChild(l);h.appendChild(k);h.appendChild(c);h.appendChild(g);n.appendChild(h)};
+akeeba.Multidb.addNewRecordButton=function(e){var b=Math.uuid();akeeba.Multidb.addRow(b,{host:"",port:"",username:"",password:"",database:"",prefix:""},e);e=document.getElementById("ak_list_contents").children;e=e[e.length-1].querySelectorAll("td");e[0].querySelector("span").style.display="none";e=e[1].querySelectorAll("span");b=e[e.length-1];console.log(e,b);b.className="akion-plus"}; //# sourceMappingURL=MultipleDatabases.min.map
diff --git a/deployed/akeeba/media/com_akeeba/js/RegExDatabaseFilters.min.js b/deployed/akeeba/media/com_akeeba/js/RegExDatabaseFilters.min.js
new file mode 100644
index 00000000..39e6c2a4
--- /dev/null
+++ b/deployed/akeeba/media/com_akeeba/js/RegExDatabaseFilters.min.js
@@ -0,0 +1,15 @@
+/*
+ Copyright (c)2006-2018 Nicholas K. Dionysopoulos / Akeeba Ltd
+ @license GNU General Public License version 3, or later
+*/
+if("undefined"==typeof akeeba)var akeeba={};"undefined"==typeof akeeba.Regexdbfilters&&(akeeba.Regexdbfilters={currentRoot:null});akeeba.Regexdbfilters.activeRootChanged=function(){var a=document.getElementById("active-root");akeeba.Regexdbfilters.load(a.options[a.selectedIndex].value)};akeeba.Regexdbfilters.load=function(a){a={action:JSON.stringify({root:a,verb:"list"})};akeeba.System.doAjax(a,function(a){akeeba.Regexdbfilters.render(a)},null,!1,15E3)};
+akeeba.Regexdbfilters.render=function(a){var l=document.getElementById("ak_list_contents");l.innerHTML="";for(var d=0;d\n"+c+"\n";null==e?null!=akeeba.Restore.errorCallback?akeeba.Restore.errorCallback(errorMessage):akeeba.Restore.errorCallbackDefault(errorMessage):e(errorMessage);return}b(d)}},error:function(a,c,b){b=a.responseText?a.responseText:"";var d="AJAX Loading Error
\n"+b+"\n";null==akeeba.System.params.iFrameCallbackError?null!=akeeba.System.params.errorCallback&&akeeba.System.params.errorCallback(a):akeeba.System.params.iFrameCallbackError(a);return}akeeba.System.params.iFrameCallbackSuccess(d)}}; +akeeba.System.doAjax=function(a,b,c,d,f){if(akeeba.System.params.useIFrame)akeeba.System.doIframeCall(a,b,c);else{null==d&&(d=!0);var e=(new Date).getTime()/1E3,g=parseInt(e,10),e=Math.round(1E3*(e-g))/1E3;a._cacheBustingJunk=e;null==f&&(f=6E5);a={type:"POST",url:akeeba.System.params.AjaxURL,cache:!1,data:a,timeout:f,success:function(a){var e="",d=a.indexOf("###");if(-1==d)a=akeeba.System.sanitizeErrorMessage(a),a="Invalid AJAX data: "+a,null==c?null!=akeeba.System.params.errorCallback&&akeeba.System.params.errorCallback(a): +c(a);else{0!=d?(a.substr(0,d),e=a.substr(d)):e=a;e=e.substr(3);d=e.lastIndexOf("###");e=e.substr(0,d);try{var f=JSON.parse(e)}catch(g){e=akeeba.System.sanitizeErrorMessage(e);a=g.message+"\n
\n"+e+"\n";null==c?null!=akeeba.System.params.errorCallback&&akeeba.System.params.errorCallback(a):c(a);return}b(f)}},error:function(a,b,e){e=a.responseText?a.responseText:"";var d="AJAX Loading Error
b;b++)e[b]||(g=0|16*Math.random(),e[b]=a[19==b?g&3|8:g])}return e.join("")}}();function basename(a,c){var d=a.replace(/^.*[\/\\]/g,"");"string"==typeof c&&d.substr(d.length-c.length)==c&&(d=d.substr(0,d.length-c.length));return d}
+function number_format(a,c,d,e){var b=isNaN(c=Math.abs(c))?2:c;c=void 0==d?",":d;e=void 0==e?".":e;d=0>a?"-":"";var g=parseInt(a=Math.abs(+a||0).toFixed(b))+"",f=3<(f=g.length)?f%3:0;return d+(f?g.substr(0,f)+e:"")+g.substr(f).replace(/(\d{3})(?=\d)/g,"$1"+e)+(b?c+Math.abs(a-g).toFixed(b).slice(2):"")}function size_format(a){return a=1073741824<=a?number_format(a/1073741824,2,".","")+" GB":1048576<=a?number_format(a/1048576,2,".","")+" MB":number_format(a/1024,2,".","")+" KB"}
+function empty(a){var c;if(""===a||0===a||"0"===a||null===a||!1===a||"undefined"===typeof a)return!0;if("object"==typeof a){for(c in a)return!1;return!0}return!1}function ltrim(a,c){c=!c?" \\s\u00a0":(c+"").replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g,"$1");return(a+"").replace(RegExp("^["+c+"]+","g"),"")}function array_shift(a){if(0===a.length)return null;if(0HD$GGb%461y1)9a295^5MzF@PCW~gj7L!)A)}^+xcCq%GE`x5b
z?x`M`UZ7sJ-XDE{K8L=lezbm}ey9G3{fXsl&K-9q7Ak*O1P|{G}u-5R)
zh|I{+sM=`E=-e39nA_OgxX^gY7-+&^B5#swGH7yW3NR%yl{U3BjWo?R?KQnNLpRel
z^EK-=J2nTHcbgwr5LmES*ji*;tXP6u5?I<=rdsw|&RHQ_iCfuQWn0}^(^;!qJ6oq(
zw_9&pf7y`P2-{fOB->Qm{I*56m9=%Xt+YM0
lvT)GA`I**Tr|9=X{<9L)U|52BDElVs6@kj2`_i_hxQ
zXkU|xY^<@&VL@Y;Wv7-fx>cZC!COO)%>*aRcL#DzV`FLEr=}Om6Gg!VF<
=sNl4c9%nXeSzTk?@>{Bh)`<)%H~x`u{o>t@
zID_W~>T}d|h4NfM9Jedy45NF9WK_(;9fWlufPt5?Ko}ALo%->K#gq{9L?)yjdSMnY
zcKT|q-fzSv*5vXPLXdgAFKoI;sq+hH2>R5P?fGDxjG8@XX;Z_V3)zX0=oc