Merge remote-tracking branch 'upstream/nightly' into nightly

This commit is contained in:
vinz 2017-11-08 22:47:23 +01:00
commit 250221ff7f
280 changed files with 4157 additions and 32382 deletions

16
.gitignore vendored
View File

@ -1,11 +1,11 @@
avatar/*
files/*
file/*
local/*
_darcs/*
logs/*
log/*
run/*
avatar/
files/
file/
local/
_darcs/
logs/
log/
run/
config.php
.htaccess
httpd.conf

View File

@ -497,9 +497,9 @@ Profile management.
biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default.
backup: whether users can backup their own profiles. Defaults to true.
backup: whether users can backup their own profiles. Defaults to false.
restore: whether users can restore their profiles from backup files. Defaults
to true.
to false.
delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults
to true.

37
INSTALL
View File

@ -26,16 +26,12 @@ PHP modules
The following software packages are *required* for this software to
run correctly.
- PHP 5.5+ For newer versions, some functions that are used may be
disabled by default, such as the pcntl_* family. See the
section on 'Queues and daemons' for more information.
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data
storage. Versions 5.x and 10.x have both reportedly
worked well. It is also possible to run MySQL 5.5+.
- Web server Apache, lighttpd and nginx will all work. CGI mode is
recommended and also some variant of 'suexec' (or a
proper setup php-fpm pool)
NOTE: mod_rewrite or its equivalent is extremely useful.
- PHP 5.6+ PHP7.x is also supported.
- MariaDB 5+ MariaDB 10.x is also supported.
- Web server Apache, lighttpd and nginx will all work, see sample
configuration files in the web root. Please use PHP-FPM
and configure mod_rewrite (or equivalent) for an optimal
experience.
Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social:
@ -49,22 +45,22 @@ functional setup of GNU Social:
- php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough.
Or, for PHP7, some or all of these will be necessary. PHP7 support is still
experimental and not necessarily working:
Or, for PHP7, some or all of these will be necessary. PHP7 works and on
the development servers we are successful running PHP7.2. This is a good
list of PHP modules you will want installed with PHP7:
php7.0-bcmath
php7.0-curl
php7.0-exif
php7.0-gd
php7.0-intl
php7.0-mbstring
php7.0-mysqlnd
php7.0-mysql
php7.0-opcache
php7.0-readline
php7.0-xmlwriter
The above package names are for Debian based systems. In the case of
Arch Linux, PHP is compiled with support for most extensions but they
require manual enabling in the relevant php.ini file (mostly php5-gmp).
NOTE: In Arch Linux, at least PHP5 requires manual enabling in the
relevant php.ini for some modules, most notably 'gmp'.
Better performance
------------------
@ -74,19 +70,10 @@ For some functionality, you will also need the following extensions:
- opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1
- mailparse Efficient parsing of email requires this extension.
Submission by email or SMS-over-email uses this.
- sphinx A client for the sphinx server, an alternative to MySQL
or Postgresql fulltext search. You will also need a
Sphinx server to serve the search queries.
- gettext For multiple languages. Default on many PHP installs;
will be emulated if not present.
- exif For thumbnails to be properly oriented.
You may also experience better performance from your site if you configure
a PHP cache/accelerator. Most distributions come with "opcache" support.
Enable it in your php.ini where it is documented together with its settings.
Installation
============

View File

@ -107,6 +107,7 @@ So far it includes the following changes:
- Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support)
- Fully qualified group mentions (!group@example.com)
The last release, 1.1.3, gave us these improvements:

View File

@ -46,7 +46,7 @@
/api/statuses/update.:format
@par Formats (:format)
xml, json
xml, json, atom
@par HTTP Method(s)
POST
@ -339,6 +339,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice);
} elseif ($this->format == 'atom') {
$this->showSingleAtomStatus($this->notice);
}
}
}

View File

@ -151,7 +151,7 @@ class GroupBlockList extends ProfileList
$this->group = $group;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new GroupBlockListItem($profile, $this->group, $this->action);
}

View File

@ -153,7 +153,7 @@ class GroupqueueAction extends GroupAction
// @todo FIXME: documentation missing.
class GroupQueueList extends GroupMemberList
{
function newListItem($profile)
function newListItem(Profile $profile)
{
return new GroupQueueListItem($profile, $this->group, $this->action);
}

View File

@ -47,6 +47,8 @@ class NewnoticeAction extends FormAction
{
protected $form = 'Notice';
protected $inreplyto = null;
/**
* Title of the page
*
@ -75,6 +77,11 @@ class NewnoticeAction extends FormAction
}
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$this->inreplyto = Notice::getByID($this->int('inreplyto'));
}
// Backwards compatibility for "share this" widget things.
// If no 'content', use 'status_textarea'
$this->formOpts['content'] = $this->trimmed('content') ?: $this->trimmed('status_textarea');
@ -132,13 +139,6 @@ class NewnoticeAction extends FormAction
return;
}
if ($this->int('inreplyto')) {
// Throws exception if the inreplyto Notice is given but not found.
$parent = Notice::getByID($this->int('inreplyto'));
} else {
$parent = null;
}
$act = new Activity();
$act->verb = ActivityVerb::POST;
$act->time = time();
@ -157,9 +157,9 @@ class NewnoticeAction extends FormAction
$act->context = new ActivityContext();
if ($parent instanceof Notice) {
$act->context->replyToID = $parent->getUri();
$act->context->replyToUrl = $parent->getUrl(true); // maybe we don't have to send true here to force a URL?
if ($this->inreplyto instanceof Notice) {
$act->context->replyToID = $this->inreplyto->getUri();
$act->context->replyToUrl = $this->inreplyto->getUrl(true); // maybe we don't have to send true here to force a URL?
}
if ($this->scoped->shareLocation()) {
@ -188,14 +188,14 @@ class NewnoticeAction extends FormAction
// FIXME: We should be able to get the attentions from common_render_content!
// and maybe even directly save whether they're local or not!
$act->context->attention = common_get_attentions($content, $this->scoped, $parent);
$act->context->attention = common_get_attentions($content, $this->scoped, $this->inreplyto);
// $options gets filled with possible scoping settings
ToSelector::fillActivity($this, $act, $options);
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $this->scoped, $parent);
$actobj->content = common_render_content($content, $this->scoped, $this->inreplyto);
// Finally add the activity object to our activity
$act->objects[] = $actobj;
@ -224,6 +224,9 @@ class NewnoticeAction extends FormAction
if ($this->getInfo() && $this->stored instanceof Notice) {
$this->showNotice($this->stored);
} elseif (!$this->getError()) {
if (!GNUsocial::isAjax() && $this->inreplyto instanceof Notice) {
$this->showNotice($this->inreplyto);
}
parent::showContent();
}
}

View File

@ -185,7 +185,7 @@ class SearchNoticeList extends NoticeList {
$this->terms = $terms;
}
function newListItem($notice)
function newListItem(Notice $notice)
{
return new SearchNoticeListItem($notice, $this->out, $this->terms);
}

View File

@ -167,7 +167,7 @@ class PeopletagMemberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagMemberListItem($profile, $this->peopletag, $this->action);
}

View File

@ -167,7 +167,7 @@ class PeopletagSubscriberList extends ProfileList
$this->peopletag = $peopletag;
}
function newListItem($profile)
function newListItem(Profile $profile)
{
return new PeopletagSubscriberListItem($profile, $this->peopletag, $this->action);
}

View File

@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag)));
}
if (!$this->target->isLocal()) {
// remote profiles at least have Atom, but we can't guarantee anything else
return array(
new Feed(Feed::ATOM,
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
$this->target->getNickname()))
);
}
return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser',
array(
@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())),
new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser',
array(
'id' => $this->target->getID(),
'format' => 'atom')),
$this->target->getAtomFeed(),
// TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'),
@ -221,13 +230,14 @@ class ShowstreamAction extends NoticestreamAction
$this->showEmptyListMessage();
}
$args = array('nickname' => $this->target->getNickname());
// either nickname or id will be used, depending on which action (showstream, userbyid...)
$args = array('nickname' => $this->target->getNickname(), 'id' => $this->target->getID());
if (!empty($this->tag))
{
$args['tag'] = $this->tag;
}
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', $args);
$this->getActionName(), $args);
}
function showAnonymousMessage()

View File

@ -36,6 +36,7 @@ class Conversation extends Managed_DataObject
public $__table = 'conversation'; // table name
public $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $url; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'),
'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'),
'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
@ -89,15 +91,21 @@ class Conversation extends Managed_DataObject
*
* @return Conversation the new conversation DO
*/
static function create($uri=null, $created=null)
static function create(ActivityContext $ctx=null, $created=null)
{
// Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation();
$conv->created = $created ?: common_sql_now();
$conv->uri = $uri ?: sprintf('%s%s=%s:%s=%s',
if ($ctx instanceof ActivityContext) {
$conv->uri = $ctx->conversation;
$conv->url = $ctx->conversation_url;
} else {
$conv->uri = sprintf('%s%s=%s:%s=%s',
TagURI::mint(),
'objectType', 'thread',
'nonce', common_random_hexstr(8));
$conv->url = null; // locally generated Conversation objects don't get static URLs stored
}
// This insert throws exceptions on failure
$conv->insert();

View File

@ -194,10 +194,14 @@ class File extends Managed_DataObject
}
$redir = File_redirection::where($given_url);
$file = $redir->getFile();
if (!$file instanceof File || empty($file->id)) {
try {
$file = $redir->getFile();
} catch (EmptyPkeyValueException $e) {
common_log(LOG_ERR, 'File_redirection::where gave object with empty file_id for given_url '._ve($given_url));
throw new ServerException('URL processing failed without new File object');
} catch (NoResultException $e) {
// This should not happen
common_log(LOG_ERR, 'File_redirection after discovery could still not return a File object.');
throw new ServerException('URL processing failed without new File object');
}
@ -731,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File();
// First we find file entries that would be duplicates of this when shortened
// ... and we'll just throw the dupes out the window for now! It's already so borken.
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = "%1$s"', $file->shortenedurl));
$dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = %1$s', $dupfile->_quote($file->shortenedurl)));
// Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile);
$origurl = $dupfile->url; // save for logging purposes
$dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch.
while($dupfile->fetch()) {
common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
print ".";
$dupfile->delete();
}

View File

@ -445,8 +445,8 @@ class File_redirection extends Managed_DataObject
}
public function getFile() {
if(empty($this->file) && $this->file_id) {
$this->file = File::getKV('id', $this->file_id);
if (!$this->file instanceof File) {
$this->file = File::getByID($this->file_id);
}
return $this->file;

View File

@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject
return $flink;
}
function set_flags($noticesend, $noticerecv, $replysync, $friendsync)
function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync)
{
if ($noticesend) {
$this->noticesync |= FOREIGN_NOTICE_SEND;
@ -109,6 +109,12 @@ class Foreign_link extends Managed_DataObject
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
}
if ($repeatsync) {
$this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT;
} else {
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT;
}
if ($friendsync) {
$this->friendsync |= FOREIGN_FRIEND_RECV;
} else {

View File

@ -320,6 +320,21 @@ class Notice extends Managed_DataObject
}
}
public function getSelfLink()
{
if ($this->isLocal()) {
return common_local_url('ApiStatusesShow', array('id' => $this->getID(), 'format' => 'atom'));
}
$selfLink = $this->getPref('ostatus', 'self');
if (!common_valid_http_url($selfLink)) {
throw new InvalidUrlException($selfLink);
}
return $selfLink;
}
public function getObjectType($canonical=false) {
if (is_null($this->object_type) || $this->object_type==='') {
throw new NoObjectTypeException($this);
@ -442,6 +457,7 @@ class Notice extends Managed_DataObject
static function saveNew($profile_id, $content, $source, array $options=null) {
$defaults = array('uri' => null,
'url' => null,
'self' => null,
'conversation' => null, // URI of conversation
'reply_to' => null, // This will override convo URI if the parent is known
'repeat_of' => null, // This will override convo URI if the repeated notice is known
@ -624,8 +640,13 @@ class Notice extends Managed_DataObject
} else {
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the options to Notice::saveNew: '.$options['conversation']);
$convctx = new ActivityContext();
$convctx->conversation = $options['conversation'];
if (array_key_exists('conversation_url', $options)) {
$convctx->conversation_url = $options['conversation_url'];
}
// The insert in Conversation::create throws exception on failure
$conv = Conversation::create($options['conversation'], $notice->created);
$conv = Conversation::create($convctx, $notice->created);
}
$notice->conversation = $conv->getID();
unset($conv);
@ -706,6 +727,10 @@ class Notice extends Managed_DataObject
}
}
if ($self && common_valid_http_url($self)) {
$notice->setPref('ostatus', 'self', $self);
}
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
@ -765,6 +790,9 @@ class Notice extends Managed_DataObject
// implied object
$options['uri'] = $act->id;
$options['url'] = $act->link;
if ($act->selfLink) {
$options['self'] = $act->selfLink;
}
} else {
$actobj = count($act->objects)===1 ? $act->objects[0] : null;
if (!is_null($actobj) && !empty($actobj->id)) {
@ -775,6 +803,9 @@ class Notice extends Managed_DataObject
$options['url'] = $actobj->id;
}
}
if ($actobj->selfLink) {
$options['self'] = $actobj->selfLink;
}
}
$defaults = array(
@ -784,6 +815,7 @@ class Notice extends Managed_DataObject
'reply_to' => null,
'repeat_of' => null,
'scope' => null,
'self' => null,
'source' => 'unknown',
'tags' => array(),
'uri' => null,
@ -921,7 +953,7 @@ class Notice extends Managed_DataObject
// Conversation entry with specified URI was not found, so we must create it.
common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: '.$act->context->conversation);
// The insert in Conversation::create throws exception on failure
$conv = Conversation::create($act->context->conversation, $stored->created);
$conv = Conversation::create($act->context, $stored->created);
}
$stored->conversation = $conv->getID();
unset($conv);
@ -1020,6 +1052,14 @@ class Notice extends Managed_DataObject
throw new ServerException('Supposedly saved Notice has no ID.');
}
if ($self && common_valid_http_url($self)) {
$stored->setPref('ostatus', 'self', $self);
}
if ($self && common_valid_http_url($self)) {
$stored->setPref('ostatus', 'self', $self);
}
// Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves.
@ -1578,12 +1618,12 @@ class Notice extends Managed_DataObject
if (common_config('group', 'addtag')) {
// we automatically add a tag for every group name, too
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->nickname),
'notice_id' => $this->id));
common_debug('Adding hashtag matching group nickname: '._ve($group->getNickname()));
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->getNickname()),
'notice_id' => $this->getID()));
if (is_null($tag)) {
$this->saveTag($group->nickname);
$this->saveTag($group->getNickname());
}
}
@ -2008,6 +2048,7 @@ class Notice extends Managed_DataObject
$conv = Conversation::getKV('id', $this->conversation);
if ($conv instanceof Conversation) {
$ctx->conversation = $conv->uri;
$ctx->conversation_url = $conv->url;
}
}
@ -2070,9 +2111,12 @@ class Notice extends Managed_DataObject
}
}
try {
$act->selfLink = $this->getSelfLink();
} catch (InvalidUrlException $e) {
$act->selfLink = null;
}
if ($this->isLocal()) {
$act->selfLink = common_local_url('ApiStatusesShow', array('id' => $this->id,
'format' => 'atom'));
$act->editLink = $act->selfLink;
}
@ -2170,8 +2214,13 @@ class Notice extends Managed_DataObject
$object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname());
$object->content = $this->getRendered();
$object->link = $this->getUrl();
try {
$object->selfLink = $this->getSelfLink();
} catch (InvalidUrlException $e) {
$object->selfLink = null;
}
$object->extra[] = array('status_net', array('notice_id' => $this->id));
$object->extra[] = array('statusnet:notice_id', null, $this->id);
Event::handle('EndActivityObjectFromNotice', array($this, &$object));
}
@ -3195,4 +3244,27 @@ class Notice extends Managed_DataObject
}
}
}
public function delPref($namespace, $topic) {
return Notice_prefs::setData($this, $namespace, $topic, null);
}
public function getPref($namespace, $topic, $default=null) {
// If you want an exception to be thrown, call Notice_prefs::getData directly
try {
return Notice_prefs::getData($this, $namespace, $topic, $default);
} catch (NoResultException $e) {
return null;
}
}
// The same as getPref but will fall back to common_config value for the same namespace/topic
public function getConfigPref($namespace, $topic)
{
return Notice_prefs::getConfigData($this, $namespace, $topic);
}
public function setPref($namespace, $topic, $data) {
return Notice_prefs::setData($this, $namespace, $topic, $data);
}
}

172
classes/Notice_prefs.php Normal file
View File

@ -0,0 +1,172 @@
<?php
/**
* GNU social
*
* Data class for Notice preferences
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Data
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://www.gnu.org/software/social/
*/
class Notice_prefs extends Managed_DataObject
{
public $__table = 'notice_prefs'; // table name
public $notice_id; // int(4) primary_key not_null
public $namespace; // varchar(191) not_null
public $topic; // varchar(191) not_null
public $data; // text
public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
public static function schemaDef()
{
return array(
'fields' => array(
'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'),
'namespace' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'),
'topic' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'),
'data' => array('type' => 'blob', 'description' => 'topic data, may be anything'),
'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'),
'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'),
),
'primary key' => array('notice_id', 'namespace', 'topic'),
'foreign keys' => array(
'notice_prefs_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
),
'indexes' => array(
'notice_prefs_notice_id_idx' => array('notice_id'),
),
);
}
static function getNamespacePrefs(Notice $notice, $namespace, array $topic=array())
{
if (empty($topic)) {
$prefs = new Notice_prefs();
$prefs->notice_id = $notice->getID();
$prefs->namespace = $namespace;
$prefs->find();
} else {
$prefs = self::pivotGet('notice_id', $notice->getID(), array('namespace'=>$namespace, 'topic'=>$topic));
}
if (empty($prefs->N)) {
throw new NoResultException($prefs);
}
return $prefs;
}
static function getNamespace(Notice $notice, $namespace, array $topic=array())
{
$prefs = self::getNamespacePrefs($notice, $namespace, $topic);
return $prefs->fetchAll();
}
static function getAll(Notice $notice)
{
try {
$prefs = self::listFind('notice_id', array($notice->getID()));
} catch (NoResultException $e) {
return array();
}
$list = array();
while ($prefs->fetch()) {
if (!isset($list[$prefs->namespace])) {
$list[$prefs->namespace] = array();
}
$list[$prefs->namespace][$prefs->topic] = $prefs->data;
}
return $list;
}
static function getTopic(Notice $notice, $namespace, $topic) {
return self::getByPK(array('notice_id' => $notice->getID(),
'namespace' => $namespace,
'topic' => $topic));
}
static function getData(Notice $notice, $namespace, $topic, $def=null) {
try {
$pref = self::getTopic($notice, $namespace, $topic);
} catch (NoResultException $e) {
if ($def === null) {
// If no default value was set, continue the exception.
throw $e;
}
// If there was a default value, return that.
return $def;
}
return $pref->data;
}
static function getConfigData(Notice $notice, $namespace, $topic) {
try {
$data = self::getData($notice, $namespace, $topic);
} catch (NoResultException $e) {
$data = common_config($namespace, $topic);
}
return $data;
}
/*
* Sets a notice preference based on Notice, namespace and topic
*
* @param Notice $notice Which notice this is for
* @param string $namespace Under which namespace (pluginname etc.)
* @param string $topic Preference name (think key in key-val store)
* @param string $data Data to be put into preference storage, null means delete
*
* @return true if changes are made, false if no action taken
* @throws ServerException if preference could not be saved
*/
static function setData(Notice $notice, $namespace, $topic, $data=null) {
try {
$pref = self::getTopic($notice, $namespace, $topic);
if (is_null($data)) {
$pref->delete();
} else {
$orig = clone($pref);
$pref->data = $data;
$pref->update($orig);
}
return true;
} catch (NoResultException $e) {
if (is_null($data)) {
return false; // No action taken
}
}
$pref = new Notice_prefs();
$pref->notice_id = $notice->getID();
$pref->namespace = $namespace;
$pref->topic = $topic;
$pref->data = $data;
$pref->created = common_sql_now();
if ($pref->insert() === false) {
throw new ServerException('Could not save notice preference.');
}
return true;
}
}

View File

@ -89,9 +89,14 @@ class Profile extends Managed_DataObject
public function getUser()
{
if (!isset($this->_user[$this->id])) {
$user = User::getKV('id', $this->id);
if (!$user instanceof User) {
throw new NoSuchUserException(array('id'=>$this->id));
$cur_user = common_current_user();
if (($cur_user instanceof User) && $cur_user->sameAs($this)) {
$user = $cur_user;
} else {
$user = User::getKV('id', $this->id);
if (!$user instanceof User) {
throw new NoSuchUserException(array('id'=>$this->id));
}
}
$this->_user[$this->id] = $user;
}
@ -941,11 +946,6 @@ class Profile extends Managed_DataObject
function delete($useWhere=false)
{
// just in case it hadn't been done before... (usually set before adding deluser to queue handling!)
if (!$this->hasRole(Profile_role::DELETED)) {
$this->grantRole(Profile_role::DELETED);
}
$this->_deleteNotices();
$this->_deleteSubscriptions();
$this->_deleteTags();
@ -957,6 +957,7 @@ class Profile extends Managed_DataObject
// not on individual objects.
$related = array('Reply',
'Group_member',
'Profile_role'
);
Event::handle('ProfileDeleteRelated', array($this, &$related));
@ -965,6 +966,8 @@ class Profile extends Managed_DataObject
$inst->profile_id = $this->id;
$inst->delete();
}
$this->grantRole(Profile_role::DELETED);
$localuser = User::getKV('id', $this->id);
if ($localuser instanceof User) {
@ -1532,6 +1535,14 @@ class Profile extends Managed_DataObject
}
return $url;
}
public function getHtmlTitle()
{
try {
return $this->getAcctUri(false);
} catch (ProfileNoAcctUriException $e) {
return $this->getNickname();
}
}
public function getNickname()
{
@ -1612,14 +1623,13 @@ class Profile extends Managed_DataObject
return !empty($block);
}
function getAtomFeed()
public function getAtomFeed()
{
$feed = null;
if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
$user = User::getKV('id', $this->id);
if (!empty($user)) {
$feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
if ($this->isLocal()) {
$feed = common_local_url('ApiTimelineUser', array('id' => $this->getID(),
'format' => 'atom'));
}
Event::handle('EndProfileGetAtomFeed', array($this, $feed));

View File

@ -289,6 +289,11 @@ class User extends Managed_DataObject
// TRANS: Profile data could not be inserted for some reason.
throw new ServerException(_m('Could not insert profile data for new user.'));
}
// Necessary because id has been known to be reissued.
if ($profile->hasRole(Profile_role::DELETED)) {
$profile->revokeRole(Profile_role::DELETED);
}
$user->id = $id;

View File

@ -41,6 +41,7 @@ $classes = array('Schema_version',
'Notice',
'Notice_location',
'Notice_source',
'Notice_prefs',
'Reply',
'Consumer',
'Token',

View File

@ -14,7 +14,7 @@ check out [Beginners Guide to OAuth](http://hueniverse.com/oauth/)).
To use OAuth, you'll need to register your client application via the web interface
and obtain a consumer key and secret. You can find the interface for application
registration at [http://%%site.server%%/%%site.path%%settings/oauthapps](http://%%site.server%%/%%site.path%%settings/oauthapps).
registration at [%%action.oauthappssettings%%](%%action.oauthappssettings%%).
## JSONP callbacks

View File

@ -218,21 +218,11 @@ class Auth_OpenID_Parse {
function match($regexp, $text, &$match)
{
if (!is_callable('mb_ereg_search_init')) {
if (!preg_match($regexp, $text, $match)) {
return false;
}
$match = $match[0];
return true;
if (preg_match($regexp, $text, $match)) {
return true;
}
$regexp = substr($regexp, 1, strlen($regexp) - 2 - strlen($this->_re_flags));
mb_ereg_search_init($text);
if (!mb_ereg_search($regexp)) {
return false;
}
$match = mb_ereg_search_getregs();
return true;
return false;
}
/**

View File

@ -426,7 +426,7 @@ define('DB_PORTABILITY_ALL', 63);
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB
@ -577,7 +577,7 @@ class DB
*/
function apiVersion()
{
return '1.8.2';
return '1.9.2';
}
// }}}
@ -772,7 +772,7 @@ class DB
$parsed['dbsyntax'] = $str;
}
if (!count($dsn)) {
if (!strlen($dsn)) {
return $parsed;
}
@ -941,7 +941,7 @@ class DB
* @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_Error extends PEAR_Error
@ -959,18 +959,32 @@ class DB_Error extends PEAR_Error
*
* @see PEAR_Error
*/
function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
function __construct($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
$level = E_USER_NOTICE, $debuginfo = null)
{
if (is_int($code)) {
$this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
$mode, $level, $debuginfo, common_log(LOG_ERR, var_export($debuginfo,true)));
parent::__construct('DB Error: ' . DB::errorMessage($code), $code,
$mode, $level, $debuginfo);
} else {
$this->PEAR_Error("DB Error: $code", DB_ERROR,
parent::__construct("DB Error: $code", DB_ERROR,
$mode, $level, $debuginfo);
}
}
/**
* Workaround to both avoid the "Redefining already defined constructor"
* PHP error and provide backward compatibility in case someone is calling
* DB_Error() dynamically
*/
public function __call($method, $arguments)
{
if ($method == 'DB_Error') {
return call_user_func_array(array($this, '__construct'), $arguments);
}
trigger_error(
'Call to undefined method DB_Error::' . $method . '()', E_USER_ERROR
);
}
// }}}
}
@ -988,7 +1002,7 @@ class DB_Error extends PEAR_Error
* @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_result
@ -1095,7 +1109,7 @@ class DB_result
*
* @return void
*/
function DB_result(&$dbh, $result, $options = array())
function __construct(&$dbh, $result, $options = array())
{
$this->autofree = $dbh->options['autofree'];
$this->dbh = &$dbh;
@ -1453,7 +1467,7 @@ class DB_result
* @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @see DB_common::setFetchMode()
*/
@ -1468,7 +1482,7 @@ class DB_row
*
* @return void
*/
function DB_row(&$arr)
function __construct(&$arr)
{
foreach ($arr as $key => $value) {
$this->$key = &$arr[$key];

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: DataObject.php 320069 2011-11-28 04:34:08Z alan_k $
* @version CVS: $Id: DataObject.php 336751 2015-05-12 04:39:50Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
@ -410,7 +410,7 @@ class DB_DataObject extends DB_DataObject_Overload
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
$this->debug($n, "find",1);
}
if (!$this->__table) {
if (!strlen($this->tableName())) {
// xdebug can backtrace this!
trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
}
@ -2073,6 +2073,9 @@ class DB_DataObject extends DB_DataObject_Overload
if (count($args)) {
$this->__table = $args[0];
}
if (empty($this->__table)) {
return '';
}
if (!empty($_DB_DATAOBJECT['CONFIG']['portability']) && $_DB_DATAOBJECT['CONFIG']['portability'] & 1) {
return strtolower($this->__table);
}
@ -2421,7 +2424,7 @@ class DB_DataObject extends DB_DataObject_Overload
$dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
if (!$dsn) {
if (!$this->_database && !empty($this->__table)) {
if (!$this->_database && !strlen($this->tableName())) {
$this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null;
}
if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
@ -3522,7 +3525,7 @@ class DB_DataObject extends DB_DataObject_Overload
if ($joinCol !== false) {
$this->raiseError(
"joinAdd: You cannot target a join column in the " .
"'link from' table ({$obj->__table}). " .
"'link from' table ({$obj->tableName()}). " .
"Either remove the fourth argument to joinAdd() ".
"({$joinCol}), or alter your links.ini file.",
DB_DATAOBJECT_ERROR_NODATA);
@ -3605,7 +3608,7 @@ class DB_DataObject extends DB_DataObject_Overload
if (!$items) {
$this->raiseError(
"joinAdd: No table definition for {$obj->__table}",
"joinAdd: No table definition for {$obj->tableName()}",
DB_DATAOBJECT_ERROR_INVALIDCONFIG);
return false;
}
@ -3800,6 +3803,7 @@ class DB_DataObject extends DB_DataObject_Overload
*/
function autoJoin($cfg = array())
{
global $_DB_DATAOBJECT;
//var_Dump($cfg);exit;
$pre_links = $this->links();
if (!empty($cfg['links'])) {
@ -3807,7 +3811,8 @@ class DB_DataObject extends DB_DataObject_Overload
}
$map = $this->links( );
$this->databaseStructure();
$dbstructure = $_DB_DATAOBJECT['INI'][$this->_database];
//print_r($map);
$tabdef = $this->table();
@ -3874,6 +3879,12 @@ class DB_DataObject extends DB_DataObject_Overload
list($tab,$col) = explode(':', $info);
// what about multiple joins on the same table!!!
// if links point to a table that does not exist - ignore.
if (!isset($dbstructure[$tab])) {
continue;
}
$xx = DB_DataObject::factory($tab);
if (!is_object($xx) || !is_a($xx, 'DB_DataObject')) {
continue;

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01
* @version CVS: $Id: Generator.php 315531 2011-08-26 02:21:29Z alan_k $
* @version CVS: $Id: Generator.php 336719 2015-05-05 10:37:33Z alan_k $
* @link http://pear.php.net/package/DB_DataObject
*/
@ -406,7 +406,7 @@ class DB_DataObject_Generator extends DB_DataObject
* Currenly only works with mysql / mysqli / posgtreas
* to use, you must set option: generate_links=true
*
* @author Pascal Schöni
* @author Pascal Sch<EFBFBD>ni
*/
function _createForiegnKeys()
@ -507,7 +507,7 @@ class DB_DataObject_Generator extends DB_DataObject
* Currenly only works with mysql / mysqli
* to use, you must set option: generate_links=true
*
* @author Pascal Schöni
* @author Pascal Sch<EFBFBD>ni
*/
function generateForeignKeys()
{
@ -895,7 +895,7 @@ class DB_DataObject_Generator extends DB_DataObject
$options = &PEAR::getStaticProperty('DB_DataObject','options');
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
$this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
foreach($this->tables as $this->table) {
@ -976,8 +976,12 @@ class DB_DataObject_Generator extends DB_DataObject
$head .= $this->derivedHookExtendsDocBlock();
// requires
$head .= "require_once '{$this->_extendsFile}';\n\n";
// requires - if you set extends_location = (blank) then no require line will be set
// this can be used if you have an autoloader
if (!empty($this->_extendsFile)) {
$head .= "require_once '{$this->_extendsFile}';\n\n";
}
// add dummy class header in...
// class
$head .= $this->derivedHookClassDocBlock();
@ -1039,10 +1043,11 @@ class DB_DataObject_Generator extends DB_DataObject
continue;
}
$p = str_repeat(' ',max(2, (30 - strlen($t->name))));
$pad = str_repeat(' ',max(2, (30 - strlen($t->name))));
$length = empty($t->len) ? '' : '('.$t->len.')';
$body .=" {$var} \${$t->name}; {$p}// {$t->type}$length {$t->flags}\n";
$flags = strlen($t->flags) ? (' '. trim($t->flags)) : '';
$body .=" {$var} \${$t->name}; {$pad}// {$t->type}{$length}{$flags}\n";
// can not do set as PEAR::DB table info doesnt support it.
//if (substr($t->Type,0,3) == "set")
@ -1283,7 +1288,7 @@ class DB_DataObject_Generator extends DB_DataObject
$class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends'];
$this->_extendsFile = empty($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$this->_extendsFile = !isset($options['extends_location']) ? $this->_extendsFile : $options['extends_location'];
$classname = $this->classname = $this->getClassNameFromTableName($this->table);

View File

@ -42,7 +42,7 @@ require_once 'PEAR.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_common extends PEAR
@ -145,7 +145,7 @@ class DB_common extends PEAR
*
* @return void
*/
function DB_common()
function __construct()
{
$this->PEAR('DB_Error');
}

View File

@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_dbase extends DB_common
@ -140,13 +140,13 @@ class DB_dbase extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_dbase()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.7.0
*/
@ -124,13 +124,13 @@ class DB_fbsql extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_fbsql()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -49,7 +49,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class became stable in Release 1.7.0
*/
@ -180,13 +180,13 @@ class DB_ibase extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_ibase()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -48,7 +48,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_ifx extends DB_common
@ -167,13 +167,13 @@ class DB_ifx extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_ifx()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class not functional until Release 1.7.0
*/
@ -126,13 +126,13 @@ class DB_msql extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_msql()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -49,7 +49,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_mssql extends DB_common
@ -179,13 +179,13 @@ class DB_mssql extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_mssql()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_mysql extends DB_common
@ -162,13 +162,13 @@ class DB_mysql extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_mysql()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -43,7 +43,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
* @since Class functional since Release 1.6.3
*/
@ -224,13 +224,13 @@ class DB_mysqli extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_mysqli()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}
@ -497,7 +497,11 @@ class DB_mysqli extends DB_common
*/
function freeResult($result)
{
return is_resource($result) ? mysqli_free_result($result) : false;
if (! $result instanceof mysqli_result) {
return false;
}
mysqli_free_result($result);
return true;
}
// }}}
@ -1031,6 +1035,10 @@ class DB_mysqli extends DB_common
? $this->mysqli_types[$tmp->type]
: 'unknown',
// http://bugs.php.net/?id=36579
// Doc Bug #36579: mysqli_fetch_field length handling
// https://bugs.php.net/bug.php?id=62426
// Bug #62426: mysqli_fetch_field_direct returns incorrect
// length on UTF8 fields
'len' => $tmp->length,
'flags' => $flags,
);

View File

@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_oci8 extends DB_common
@ -173,13 +173,13 @@ class DB_oci8 extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_oci8()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -44,7 +44,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_odbc extends DB_common
@ -153,13 +153,13 @@ class DB_odbc extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_odbc()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -43,7 +43,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_pgsql extends DB_common
@ -148,13 +148,13 @@ class DB_pgsql extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_pgsql()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_sqlite extends DB_common
@ -152,13 +152,13 @@ class DB_sqlite extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_sqlite()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -38,7 +38,7 @@ require_once 'DB.php';
* @author Stig Bakken <stig@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_storage extends PEAR
@ -94,7 +94,7 @@ class DB_storage extends PEAR
* a reference to this object
*
*/
function DB_storage($table, $keycolumn, &$dbh, $validator = null)
function __construct($table, $keycolumn, &$dbh, $validator = null)
{
$this->PEAR('DB_Error');
$this->_table = $table;

View File

@ -46,7 +46,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 1.8.2
* @version Release: 1.9.2
* @link http://pear.php.net/package/DB
*/
class DB_sybase extends DB_common
@ -141,13 +141,13 @@ class DB_sybase extends DB_common
// {{{ constructor
/**
* This constructor calls <kbd>$this->DB_common()</kbd>
* This constructor calls <kbd>parent::__construct()</kbd>
*
* @return void
*/
function DB_sybase()
function __construct()
{
$this->DB_common();
parent::__construct();
}
// }}}

View File

@ -7,7 +7,7 @@
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run.
*
* @version 4.7.0
* @version 4.9.3
*
* @warning
* You must *not* include any other HTML Purifier files before this file,
@ -137,6 +137,8 @@ require 'HTMLPurifier/AttrTransform/SafeObject.php';
require 'HTMLPurifier/AttrTransform/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/TargetBlank.php';
require 'HTMLPurifier/AttrTransform/TargetNoopener.php';
require 'HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php';
@ -175,6 +177,8 @@ require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/TargetBlank.php';
require 'HTMLPurifier/HTMLModule/TargetNoopener.php';
require 'HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@ -225,5 +229,6 @@ require 'HTMLPurifier/URIScheme/https.php';
require 'HTMLPurifier/URIScheme/mailto.php';
require 'HTMLPurifier/URIScheme/news.php';
require 'HTMLPurifier/URIScheme/nntp.php';
require 'HTMLPurifier/URIScheme/tel.php';
require 'HTMLPurifier/VarParser/Flexible.php';
require 'HTMLPurifier/VarParser/Native.php';

View File

@ -19,7 +19,7 @@
*/
/*
HTML Purifier 4.7.0 - Standards Compliant HTML Filtering
HTML Purifier 4.9.3 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
@ -58,12 +58,12 @@ class HTMLPurifier
* Version of HTML Purifier.
* @type string
*/
public $version = '4.7.0';
public $version = '4.9.3';
/**
* Constant with version of HTML Purifier.
*/
const VERSION = '4.7.0';
const VERSION = '4.9.3';
/**
* Global configuration object.
@ -104,7 +104,7 @@ class HTMLPurifier
/**
* Initializes the purifier.
*
* @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
* @param HTMLPurifier_Config|mixed $config Optional HTMLPurifier_Config object
* for all instances of the purifier, if omitted, a default
* configuration is supplied (which can be overridden on a
* per-use basis).

View File

@ -131,6 +131,8 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoopener.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
@ -169,6 +171,8 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoopener.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@ -219,5 +223,6 @@ require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php';
require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/tel.php';
require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php';
require_once $__dir . '/HTMLPurifier/VarParser/Native.php';

View File

@ -19,8 +19,8 @@ class HTMLPurifier_Arborize
if ($token instanceof HTMLPurifier_Token_End) {
$token->start = null; // [MUT]
$r = array_pop($stack);
assert($r->name === $token->name);
assert(empty($token->attr));
//assert($r->name === $token->name);
//assert(empty($token->attr));
$r->endCol = $token->col;
$r->endLine = $token->line;
$r->endArmor = $token->armor;
@ -32,7 +32,7 @@ class HTMLPurifier_Arborize
$stack[] = $node;
}
}
assert(count($stack) == 1);
//assert(count($stack) == 1);
return $stack[0];
}

View File

@ -21,6 +21,11 @@ class HTMLPurifier_AttrCollections
* @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
*/
public function __construct($attr_types, $modules)
{
$this->doConstruct($attr_types, $modules);
}
public function doConstruct($attr_types, $modules)
{
// load extensions from the modules
foreach ($modules as $module) {

View File

@ -86,7 +86,13 @@ abstract class HTMLPurifier_AttrDef
*/
protected function mungeRgb($string)
{
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
$p = '\s*(\d+(\.\d+)?([%]?))\s*';
if (preg_match('/(rgba|hsla)\(/', $string)) {
return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
}
return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
}
/**

View File

@ -25,15 +25,42 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$css = $this->parseCDATA($css);
$definition = $config->getCSSDefinition();
$allow_duplicates = $config->get("CSS.AllowDuplicates");
// we're going to break the spec and explode by semicolons.
// This is because semicolon rarely appears in escaped form
// Doing this is generally flaky but fast
// IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
// for details
$declarations = explode(';', $css);
// According to the CSS2.1 spec, the places where a
// non-delimiting semicolon can appear are in strings
// escape sequences. So here is some dumb hack to
// handle quotes.
$len = strlen($css);
$accum = "";
$declarations = array();
$quoted = false;
for ($i = 0; $i < $len; $i++) {
$c = strcspn($css, ";'\"", $i);
$accum .= substr($css, $i, $c);
$i += $c;
if ($i == $len) break;
$d = $css[$i];
if ($quoted) {
$accum .= $d;
if ($d == $quoted) {
$quoted = false;
}
} else {
if ($d == ";") {
$declarations[] = $accum;
$accum = "";
} else {
$accum .= $d;
$quoted = $d;
}
}
}
if ($accum != "") $declarations[] = $accum;
$propvalues = array();
$new_declarations = '';
/**
* Name of the current CSS property being validated.
@ -83,7 +110,11 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
if ($result === false) {
continue;
}
$propvalues[$property] = $result;
if ($allow_duplicates) {
$new_declarations .= "$property:$result;";
} else {
$propvalues[$property] = $result;
}
}
$context->destroy('CurrentCSSProperty');
@ -92,7 +123,6 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
// slightly inefficient, but it's the only way of getting rid of
// duplicates. Perhaps config to optimize it, but not now.
$new_declarations = '';
foreach ($propvalues as $prop => $value) {
$new_declarations .= "$prop:$value;";
}

View File

@ -6,6 +6,16 @@
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
{
/**
* @type HTMLPurifier_AttrDef_CSS_AlphaValue
*/
protected $alpha;
public function __construct()
{
$this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
}
/**
* @param string $color
* @param HTMLPurifier_Config $config
@ -29,59 +39,104 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
return $colors[$lower];
}
if (strpos($color, 'rgb(') !== false) {
// rgb literal handling
if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
$length = strlen($color);
if (strpos($color, ')') !== $length - 1) {
return false;
}
$triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad);
if (count($parts) !== 3) {
// get used function : rgb, rgba, hsl or hsla
$function = $matches[1];
$parameters_size = 3;
$alpha_channel = false;
if (substr($function, -1) === 'a') {
$parameters_size = 4;
$alpha_channel = true;
}
/*
* Allowed types for values :
* parameter_position => [type => max_value]
*/
$allowed_types = array(
1 => array('percentage' => 100, 'integer' => 255),
2 => array('percentage' => 100, 'integer' => 255),
3 => array('percentage' => 100, 'integer' => 255),
);
$allow_different_types = false;
if (strpos($function, 'hsl') !== false) {
$allowed_types = array(
1 => array('integer' => 360),
2 => array('percentage' => 100),
3 => array('percentage' => 100),
);
$allow_different_types = true;
}
$values = trim(str_replace($function, '', $color), ' ()');
$parts = explode(',', $values);
if (count($parts) !== $parameters_size) {
return false;
}
$type = false; // to ensure that they're all the same type
$type = false;
$new_parts = array();
$i = 0;
foreach ($parts as $part) {
$i++;
$part = trim($part);
if ($part === '') {
return false;
}
$length = strlen($part);
if ($part[$length - 1] === '%') {
// handle percents
if (!$type) {
$type = 'percentage';
} elseif ($type !== 'percentage') {
// different check for alpha channel
if ($alpha_channel === true && $i === count($parts)) {
$result = $this->alpha->validate($part, $config, $context);
if ($result === false) {
return false;
}
$num = (float)substr($part, 0, $length - 1);
if ($num < 0) {
$num = 0;
}
if ($num > 100) {
$num = 100;
}
$new_parts[] = "$num%";
$new_parts[] = (string)$result;
continue;
}
if (substr($part, -1) === '%') {
$current_type = 'percentage';
} else {
// handle integers
if (!$type) {
$type = 'integer';
} elseif ($type !== 'integer') {
return false;
}
$num = (int)$part;
if ($num < 0) {
$num = 0;
}
if ($num > 255) {
$num = 255;
}
$new_parts[] = (string)$num;
$current_type = 'integer';
}
if (!array_key_exists($current_type, $allowed_types[$i])) {
return false;
}
if (!$type) {
$type = $current_type;
}
if ($allow_different_types === false && $type != $current_type) {
return false;
}
$max_value = $allowed_types[$i][$current_type];
if ($current_type == 'integer') {
// Return value between range 0 -> $max_value
$new_parts[] = (int)max(min($part, $max_value), 0);
} elseif ($current_type == 'percentage') {
$new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
}
}
$new_triad = implode(',', $new_parts);
$color = "rgb($new_triad)";
$new_values = implode(',', $new_parts);
$color = $function . '(' . $new_values . ')';
} else {
// hexadecimal handling
if ($color[0] === '#') {
@ -100,6 +155,7 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
}
return $color;
}
}
// vim: et sw=4 sts=4

View File

@ -130,6 +130,8 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
// <http://ja.wikipedia.org/wiki/MS_明朝>. See
// the CSS3 spec for more examples:
// <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
// You can see live samples of these on the Internet:
// <http://www.google.co.jp/search?q=font-family++明朝|ゴシック>
// However, most of these fonts have ASCII equivalents:
// for example, 'MS Mincho', and it's considered
// professional to use ASCII font names instead of

View File

@ -33,6 +33,9 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
return false;
}
$uri_string = substr($uri_string, 4);
if (strlen($uri_string) == 0) {
return false;
}
$new_length = strlen($uri_string) - 1;
if ($uri_string[$new_length] != ')') {
return false;

View File

@ -72,18 +72,26 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
// we purposely avoid using regex, hopefully this is faster
if (ctype_alpha($id)) {
$result = true;
} else {
if (!ctype_alpha(@$id[0])) {
if ($config->get('Attr.ID.HTML5') === true) {
if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
return false;
}
// primitive style of regexps, I suppose
$trim = trim(
$id,
'A..Za..z0..9:-._'
);
$result = ($trim === '');
} else {
if (ctype_alpha($id)) {
// OK
} else {
if (!ctype_alpha(@$id[0])) {
return false;
}
// primitive style of regexps, I suppose
$trim = trim(
$id,
'A..Za..z0..9:-._'
);
if ($trim !== '') {
return false;
}
}
}
$regexp = $config->get('Attr.IDBlacklistRegexp');
@ -91,14 +99,14 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
return false;
}
if (!$this->selector && $result) {
if (!$this->selector) {
$id_accumulator->add($id);
}
// if no change was made to the ID, return the result
// else, return the new id if stripping whitespace made it
// valid, or return false.
return $result ? $id : false;
return $id;
}
}

View File

@ -76,24 +76,33 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
// fairly well supported.
$underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
// Based off of RFC 1738, but amended so that
// as per RFC 3696, the top label need only not be all numeric.
// The productions describing this are:
$a = '[a-z]'; // alpha
$an = '[a-z0-9]'; // alphanum
$and = "[a-z0-9-$underscore]"; // alphanum | "-"
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
$domainlabel = "$an($and*$an)?";
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
$toplabel = "$a($and*$an)?";
$domainlabel = "$an(?:$and*$an)?";
// AMENDED as per RFC 3696
// toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum
// side condition: not all numeric
$toplabel = "$an(?:$and*$an)?";
// hostname = *( domainlabel "." ) toplabel [ "." ]
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
if (!ctype_digit($matches[1])) {
return $string;
}
}
// PHP 5.3 and later support this functionality natively
if (function_exists('idn_to_ascii')) {
$string = idn_to_ascii($string);
// If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do,
// since otherwise we have to assume browsers support
if ($config->get('Core.EnableIDNA')) {
} elseif ($config->get('Core.EnableIDNA')) {
$idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
// we need to encode each period separately
$parts = explode('.', $string);
@ -114,13 +123,14 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
}
}
$string = implode('.', $new_parts);
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
} catch (Exception $e) {
// XXX error reporting
}
}
// Try again
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
return false;
}
}

View File

@ -32,8 +32,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
if ($src) {
$alt = $config->get('Attr.DefaultImageAlt');
if ($alt === null) {
// truncate if the alt is too long
$attr['alt'] = substr(basename($attr['src']), 0, 40);
$attr['alt'] = basename($attr['src']);
} else {
$attr['alt'] = $alt;
}

View File

@ -0,0 +1,37 @@
<?php
// must be called POST validation
/**
* Adds rel="noopener" to any links which target a different window
* than the current one. This is used to prevent malicious websites
* from silently replacing the original window, which could be used
* to do phishing.
* This transform is controlled by %HTML.TargetNoopener.
*/
class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform
{
/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (isset($attr['rel'])) {
$rels = explode(' ', $attr['rel']);
} else {
$rels = array();
}
if (isset($attr['target']) && !in_array('noopener', $rels)) {
$rels[] = 'noopener';
}
if (!empty($rels) || isset($attr['rel'])) {
$attr['rel'] = implode(' ', $rels);
}
return $attr;
}
}

View File

@ -0,0 +1,37 @@
<?php
// must be called POST validation
/**
* Adds rel="noreferrer" to any links which target a different window
* than the current one. This is used to prevent malicious websites
* from silently replacing the original window, which could be used
* to do phishing.
* This transform is controlled by %HTML.TargetNoreferrer.
*/
class HTMLPurifier_AttrTransform_TargetNoreferrer extends HTMLPurifier_AttrTransform
{
/**
* @param array $attr
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return array
*/
public function transform($attr, $config, $context)
{
if (isset($attr['rel'])) {
$rels = explode(' ', $attr['rel']);
} else {
$rels = array();
}
if (isset($attr['target']) && !in_array('noreferrer', $rels)) {
$rels[] = 'noreferrer';
}
if (!empty($rels) || isset($attr['rel'])) {
$attr['rel'] = implode(' ', $rels);
}
return $attr;
}
}

View File

@ -225,6 +225,10 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
);
$max = $config->get('CSS.MaxImgLength');
$this->info['min-width'] =
$this->info['max-width'] =
$this->info['min-height'] =
$this->info['max-height'] =
$this->info['width'] =
$this->info['height'] =
$max === null ?
@ -370,6 +374,19 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
);
$this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
$border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
array(
new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
));
$this->info['border-top-left-radius'] =
$this->info['border-top-right-radius'] =
$this->info['border-bottom-right-radius'] =
$this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
// TODO: support SLASH syntax
$this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
}
/**

View File

@ -38,13 +38,19 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
return false;
}
// if li is not allowed, delete parent node
if (!isset($config->getHTMLDefinition()->info['li'])) {
trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
return false;
}
// the new set of children
$result = array();
// a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true;
$current_li = false;
$current_li = null;
foreach ($children as $node) {
if (!empty($node->is_whitespace)) {
@ -65,7 +71,7 @@ class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
// to handle non-list elements; non-list elements should
// not be appended to an existing li; only li created
// for non-list. This distinction is not currently made.
if ($current_li === false) {
if ($current_li === null) {
$current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li;
}

View File

@ -203,7 +203,7 @@ class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
$current_tr_tbody->children[] = $node;
break;
case '#PCDATA':
assert($node->is_whitespace);
//assert($node->is_whitespace);
if ($current_tr_tbody === null) {
$ret[] = $node;
} else {

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Config
* HTML Purifier's version
* @type string
*/
public $version = '4.7.0';
public $version = '4.9.3';
/**
* Whether or not to automatically finalize
@ -333,7 +333,7 @@ class HTMLPurifier_Config
}
// Raw type might be negative when using the fully optimized form
// of stdclass, which indicates allow_null == true
// of stdClass, which indicates allow_null == true
$rtype = is_int($def) ? $def : $def->type;
if ($rtype < 0) {
$type = -$rtype;

View File

@ -24,11 +24,11 @@ class HTMLPurifier_ConfigSchema
*
* array(
* 'Namespace' => array(
* 'Directive' => new stdclass(),
* 'Directive' => new stdClass(),
* )
* )
*
* The stdclass may have the following properties:
* The stdClass may have the following properties:
*
* - If isAlias isn't set:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
@ -39,8 +39,8 @@ class HTMLPurifier_ConfigSchema
* - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to
*
* In certain degenerate cases, stdclass will actually be an integer. In
* that case, the value is equivalent to an stdclass with the type
* In certain degenerate cases, stdClass will actually be an integer. In
* that case, the value is equivalent to an stdClass with the type
* property set to the integer. If the integer is negative, type is
* equal to the absolute value of integer, and allow_null is true.
*
@ -105,7 +105,7 @@ class HTMLPurifier_ConfigSchema
*/
public function add($key, $default, $type, $allow_null)
{
$obj = new stdclass();
$obj = new stdClass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) {
$obj->allow_null = true;
@ -152,14 +152,14 @@ class HTMLPurifier_ConfigSchema
*/
public function addAlias($key, $new_key)
{
$obj = new stdclass;
$obj = new stdClass;
$obj->key = $new_key;
$obj->isAlias = true;
$this->info[$key] = $obj;
}
/**
* Replaces any stdclass that only has the type property with type integer.
* Replaces any stdClass that only has the type property with type integer.
*/
public function postProcess()
{

View File

@ -0,0 +1,10 @@
Attr.ID.HTML5
TYPE: bool/null
DEFAULT: null
VERSION: 4.8.0
--DESCRIPTION--
In HTML5, restrictions on the format of the id attribute have been significantly
relaxed, such that any string is valid so long as it contains no spaces and
is at least one character. In lieu of a general HTML5 compatibility flag,
set this configuration directive to true to use the relaxed rules.
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,11 @@
CSS.AllowDuplicates
TYPE: bool
DEFAULT: false
VERSION: 4.8.0
--DESCRIPTION--
<p>
By default, HTML Purifier removes duplicate CSS properties,
like <code>color:red; color:blue</code>. If this is set to
true, duplicate properties are allowed.
</p>
--# vim: et sw=4 sts=4

View File

@ -1,5 +1,5 @@
Cache.SerializerPermissions
TYPE: int
TYPE: int/null
VERSION: 4.3.0
DEFAULT: 0755
--DESCRIPTION--
@ -8,4 +8,9 @@ DEFAULT: 0755
Directory permissions of the files and directories created inside
the DefinitionCache/Serializer or other custom serializer path.
</p>
<p>
In HTML Purifier 4.8.0, this also supports <code>NULL</code>,
which means that no chmod'ing or directory creation shall
occur.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,16 @@
Core.AggressivelyRemoveScript
TYPE: bool
VERSION: 4.9.0
DEFAULT: true
--DESCRIPTION--
<p>
This directive enables aggressive pre-filter removal of
script tags. This is not necessary for security,
but it can help work around a bug in libxml where embedded
HTML elements inside script sections cause the parser to
choke. To revert to pre-4.9.0 behavior, set this to false.
This directive has no effect if %Core.Trusted is true,
%Core.RemoveScriptContents is false, or %Core.HiddenElements
does not contain script.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,36 @@
Core.LegacyEntityDecoder
TYPE: bool
VERSION: 4.9.0
DEFAULT: false
--DESCRIPTION--
<p>
Prior to HTML Purifier 4.9.0, entities were decoded by performing
a global search replace for all entities whose decoded versions
did not have special meanings under HTML, and replaced them with
their decoded versions. We would match all entities, even if they did
not have a trailing semicolon, but only if there weren't any trailing
alphanumeric characters.
</p>
<table>
<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yena</td><td>&amp;yena</td><td>&amp;yena</td></tr>
<tr><td>&amp;yen=</td><td>&yen;=</td><td>&yen;=</td></tr>
</table>
<p>
In HTML Purifier 4.9.0, we changed the behavior of entity parsing
to match entities that had missing trailing semicolons in less
cases, to more closely match HTML5 parsing behavior:
</p>
<table>
<tr><th>Original</th><th>Text</th><th>Attribute</th></tr>
<tr><td>&amp;yen;</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yen</td><td>&yen;</td><td>&yen;</td></tr>
<tr><td>&amp;yena</td><td>&yen;a</td><td>&amp;yena</td></tr>
<tr><td>&amp;yen=</td><td>&yen;=</td><td>&amp;yen=</td></tr>
</table>
<p>
This flag reverts back to pre-HTML Purifier 4.9.0 behavior.
</p>
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,10 @@
--# vim: et sw=4 sts=4
HTML.TargetNoopener
TYPE: bool
VERSION: 4.8.0
DEFAULT: TRUE
--DESCRIPTION--
If enabled, noopener rel attributes are added to links which have
a target attribute associated with them. This prevents malicious
destinations from overwriting the original window.
--# vim: et sw=4 sts=4

View File

@ -0,0 +1,9 @@
HTML.TargetNoreferrer
TYPE: bool
VERSION: 4.8.0
DEFAULT: TRUE
--DESCRIPTION--
If enabled, noreferrer rel attributes are added to links which have
a target attribute associated with them. This prevents malicious
destinations from overwriting the original window.
--# vim: et sw=4 sts=4

View File

@ -8,6 +8,7 @@ array (
'ftp' => true,
'nntp' => true,
'news' => true,
'tel' => true,
)
--DESCRIPTION--
Whitelist that defines the schemes that a URI is allowed to have. This

View File

@ -1,5 +1,5 @@
URI.DefaultScheme
TYPE: string
TYPE: string/null
DEFAULT: 'http'
--DESCRIPTION--
@ -7,4 +7,9 @@ DEFAULT: 'http'
Defines through what scheme the output will be served, in order to
select the proper object validator when no scheme information is present.
</p>
<p>
Starting with HTML Purifier 4.9.0, the default scheme can be null, in
which case we reject all URIs which do not have explicit schemes.
</p>
--# vim: et sw=4 sts=4

View File

@ -9,75 +9,75 @@ DEFAULT: NULL
absolute URIs into another URI, usually a URI redirection service.
This directive accepts a URI, formatted with a <code>%s</code> where
the url-encoded original URI should be inserted (sample:
<code>https://searx.laquadrature.net/?q=%s</code>).
</p>
<p>
<code>http://www.google.com/url?q=%s</code>).
</p>
<p>
Uses for this directive:
</p>
<ul>
</p>
<ul>
<li>
Prevent PageRank leaks, while being fairly transparent
to users (you may also want to add some client side JavaScript to
override the text in the statusbar). <strong>Notice</strong>:
Many security experts believe that this form of protection does not deter spam-bots.
Prevent PageRank leaks, while being fairly transparent
to users (you may also want to add some client side JavaScript to
override the text in the statusbar). <strong>Notice</strong>:
Many security experts believe that this form of protection does not deter spam-bots.
</li>
<li>
Redirect users to a splash page telling them they are leaving your
website. While this is poor usability practice, it is often mandated
in corporate environments.
Redirect users to a splash page telling them they are leaving your
website. While this is poor usability practice, it is often mandated
in corporate environments.
</li>
</ul>
<p>
</ul>
<p>
Prior to HTML Purifier 3.1.1, this directive also enabled the munging
of browsable external resources, which could break things if your redirection
script was a splash page or used <code>meta</code> tags. To revert to
previous behavior, please use %URI.MungeResources.
</p>
<p>
</p>
<p>
You may want to also use %URI.MungeSecretKey along with this directive
in order to enforce what URIs your redirector script allows. Open
redirector scripts can be a security risk and negatively affect the
reputation of your domain name.
</p>
<p>
</p>
<p>
Starting with HTML Purifier 3.1.1, there is also these substitutions:
</p>
<table>
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Description</th>
<th>Example <code>&lt;a href=""&gt;</code></th>
</tr>
<tr>
<th>Key</th>
<th>Description</th>
<th>Example <code>&lt;a href=""&gt;</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>%r</td>
<td>1 - The URI embeds a resource<br />(blank) - The URI is merely a link</td>
<td></td>
</tr>
<tr>
<td>%n</td>
<td>The name of the tag this URI came from</td>
<td>a</td>
</tr>
<tr>
<td>%m</td>
<td>The name of the attribute this URI came from</td>
<td>href</td>
</tr>
<tr>
<td>%p</td>
<td>The name of the CSS property this URI came from, or blank if irrelevant</td>
<td></td>
</tr>
<tr>
<td>%r</td>
<td>1 - The URI embeds a resource<br />(blank) - The URI is merely a link</td>
<td></td>
</tr>
<tr>
<td>%n</td>
<td>The name of the tag this URI came from</td>
<td>a</td>
</tr>
<tr>
<td>%m</td>
<td>The name of the attribute this URI came from</td>
<td>href</td>
</tr>
<tr>
<td>%p</td>
<td>The name of the CSS property this URI came from, or blank if irrelevant</td>
<td></td>
</tr>
</tbody>
</table>
<p>
</table>
<p>
Admittedly, these letters are somewhat arbitrary; the only stipulation
was that they couldn't be a through f. r is for resource (I would have preferred
e, but you take what you can get), n is for name, m
was picked because it came after n (and I couldn't use a), p is for
property.
</p>
--# vim: et sw=4 sts=4
</p>
--# vim: et sw=4 sts=4

View File

@ -118,7 +118,7 @@ abstract class HTMLPurifier_DefinitionCache
/**
* Clears all expired (older version or revision) objects from cache
* @note Be carefuly implementing this method as flush. Flush must
* @note Be careful implementing this method as flush. Flush must
* not interfere with other Definition types, and cleanup()
* should not be repeatedly called by userland code.
* @param HTMLPurifier_Config $config

View File

@ -97,6 +97,12 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
$dir = $this->generateDirectoryPath($config);
$dh = opendir($dir);
// Apparently, on some versions of PHP, readdir will return
// an empty string if you pass an invalid argument to readdir.
// So you need this test. See #49.
if (false === $dh) {
return false;
}
while (false !== ($filename = readdir($dh))) {
if (empty($filename)) {
continue;
@ -106,6 +112,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
unlink($dir . '/' . $filename);
}
closedir($dh);
return true;
}
/**
@ -119,6 +127,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
}
$dir = $this->generateDirectoryPath($config);
$dh = opendir($dir);
// See #49 (and above).
if (false === $dh) {
return false;
}
while (false !== ($filename = readdir($dh))) {
if (empty($filename)) {
continue;
@ -131,6 +143,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
unlink($dir . '/' . $filename);
}
}
closedir($dh);
return true;
}
/**
@ -186,11 +200,9 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
if ($result !== false) {
// set permissions of the new file (no execute)
$chmod = $config->get('Cache.SerializerPermissions');
if (!$chmod) {
$chmod = 0644; // invalid config or simpletest
if ($chmod !== null) {
chmod($file, $chmod & 0666);
}
$chmod = $chmod & 0666;
chmod($file, $chmod);
}
return $result;
}
@ -204,8 +216,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
{
$directory = $this->generateDirectoryPath($config);
$chmod = $config->get('Cache.SerializerPermissions');
if (!$chmod) {
$chmod = 0755; // invalid config or simpletest
if ($chmod === null) {
// TODO: This races
if (is_dir($directory)) return true;
return mkdir($directory);
}
if (!is_dir($directory)) {
$base = $this->generateBaseDirectoryPath($config);
@ -219,15 +233,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} elseif (!$this->_testPermissions($base, $chmod)) {
return false;
}
mkdir($directory, $chmod);
if (!$this->_testPermissions($directory, $chmod)) {
if (!mkdir($directory, $chmod)) {
trigger_error(
'Base directory ' . $base . ' does not exist,
please create or change using %Cache.SerializerPath',
'Could not create directory ' . $directory . '',
E_USER_WARNING
);
return false;
}
if (!$this->_testPermissions($directory, $chmod)) {
return false;
}
} elseif (!$this->_testPermissions($directory, $chmod)) {
return false;
}
@ -256,7 +271,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
);
return false;
}
if (function_exists('posix_getuid')) {
if (function_exists('posix_getuid') && $chmod !== null) {
// POSIX system, we can give more specific advice
if (fileowner($dir) === posix_getuid()) {
// we can chmod it ourselves

View File

@ -101,6 +101,14 @@ class HTMLPurifier_Encoder
* It will parse according to UTF-8 and return a valid UTF8 string, with
* non-SGML codepoints excluded.
*
* Specifically, it will permit:
* \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
* Source: https://www.w3.org/TR/REC-xml/#NT-Char
* Arguably this function should be modernized to the HTML5 set
* of allowed characters:
* https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
* which simultaneously expand and restrict the set of allowed characters.
*
* @param string $str The string to clean
* @param bool $force_php
* @return string
@ -122,15 +130,12 @@ class HTMLPurifier_Encoder
* function that needs to be able to understand UTF-8 characters.
* As of right now, only smart lossless character encoding converters
* would need that, and I'm probably not going to implement them.
* Once again, PHP 6 should solve all our problems.
*/
public static function cleanUTF8($str, $force_php = false)
{
// UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no
// need to do PHP stuff. 99% of the time, this will be the case.
// The regexp matches the XML char production, as well as well as excluding
// non-SGML codepoints U+007F to U+009F
if (preg_match(
'/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
$str
@ -255,6 +260,7 @@ class HTMLPurifier_Encoder
// 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
(0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
)
) {

View File

@ -16,6 +16,138 @@ class HTMLPurifier_EntityParser
*/
protected $_entity_lookup;
/**
* Callback regex string for entities in text.
* @type string
*/
protected $_textEntitiesRegex;
/**
* Callback regex string for entities in attributes.
* @type string
*/
protected $_attrEntitiesRegex;
/**
* Tests if the beginning of a string is a semi-optional regex
*/
protected $_semiOptionalPrefixRegex;
public function __construct() {
// From
// http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
$semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";
// NB: three empty captures to put the fourth match in the right
// place
$this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";
$this->_textEntitiesRegex =
'/&(?:'.
// hex
'[#]x([a-fA-F0-9]+);?|'.
// dec
'[#]0*(\d+);?|'.
// string (mandatory semicolon)
// NB: order matters: match semicolon preferentially
'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
// string (optional semicolon)
"($semi_optional)".
')/';
$this->_attrEntitiesRegex =
'/&(?:'.
// hex
'[#]x([a-fA-F0-9]+);?|'.
// dec
'[#]0*(\d+);?|'.
// string (mandatory semicolon)
// NB: order matters: match semicolon preferentially
'([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
// string (optional semicolon)
// don't match if trailing is equals or alphanumeric (URL
// like)
"($semi_optional)(?![=;A-Za-z0-9])".
')/';
}
/**
* Substitute entities with the parsed equivalents. Use this on
* textual data in an HTML document (as opposed to attributes.)
*
* @param string $string String to have entities parsed.
* @return string Parsed string.
*/
public function substituteTextEntities($string)
{
return preg_replace_callback(
$this->_textEntitiesRegex,
array($this, 'entityCallback'),
$string
);
}
/**
* Substitute entities with the parsed equivalents. Use this on
* attribute contents in documents.
*
* @param string $string String to have entities parsed.
* @return string Parsed string.
*/
public function substituteAttrEntities($string)
{
return preg_replace_callback(
$this->_attrEntitiesRegex,
array($this, 'entityCallback'),
$string
);
}
/**
* Callback function for substituteNonSpecialEntities() that does the work.
*
* @param array $matches PCRE matches array, with 0 the entire match, and
* either index 1, 2 or 3 set with a hex value, dec value,
* or string (respectively).
* @return string Replacement string.
*/
protected function entityCallback($matches)
{
$entity = $matches[0];
$hex_part = @$matches[1];
$dec_part = @$matches[2];
$named_part = empty($matches[3]) ? @$matches[4] : $matches[3];
if ($hex_part !== NULL && $hex_part !== "") {
return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
} elseif ($dec_part !== NULL && $dec_part !== "") {
return HTMLPurifier_Encoder::unichr((int) $dec_part);
} else {
if (!$this->_entity_lookup) {
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
}
if (isset($this->_entity_lookup->table[$named_part])) {
return $this->_entity_lookup->table[$named_part];
} else {
// exact match didn't match anything, so test if
// any of the semicolon optional match the prefix.
// Test that this is an EXACT match is important to
// prevent infinite loop
if (!empty($matches[3])) {
return preg_replace_callback(
$this->_semiOptionalPrefixRegex,
array($this, 'entityCallback'),
$entity
);
}
return $entity;
}
}
}
// LEGACY CODE BELOW
/**
* Callback regex string for parsing entities.
* @type string
@ -144,7 +276,7 @@ class HTMLPurifier_EntityParser
$entity;
} else {
return isset($this->_special_ent2dec[$matches[3]]) ?
$this->_special_ent2dec[$matches[3]] :
$this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
$entity;
}
}

View File

@ -95,7 +95,10 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
if ($tidy !== null) {
$this->_tidy = $tidy;
}
$html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html);
// NB: this must be NON-greedy because if we have
// <style>foo</style> <style>bar</style>
// we must not grab foo</style> <style>bar
$html = preg_replace_callback('#<style(?:\s.*)?>(.*)<\/style>#isU', array($this, 'styleCallback'), $html);
$style_blocks = $this->_styleMatches;
$this->_styleMatches = array(); // reset
$context->register('StyleBlocks', $style_blocks); // $context must not be reused

View File

@ -146,7 +146,7 @@ class HTMLPurifier_Generator
$attr = $this->generateAttributes($token->attr, $token->name);
if ($this->_flashCompat) {
if ($token->name == "object") {
$flash = new stdclass();
$flash = new stdClass();
$flash->attr = $token->attr;
$flash->param = array();
$this->_flashStack[] = $flash;

View File

@ -0,0 +1,21 @@
<?php
/**
* Module adds the target-based noopener attribute transformation to a tags. It
* is enabled by HTML.TargetNoopener
*/
class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule
{
/**
* @type string
*/
public $name = 'TargetNoopener';
/**
* @param HTMLPurifier_Config $config
*/
public function setup($config) {
$a = $this->addBlankElement('a');
$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Module adds the target-based noreferrer attribute transformation to a tags. It
* is enabled by HTML.TargetNoreferrer
*/
class HTMLPurifier_HTMLModule_TargetNoreferrer extends HTMLPurifier_HTMLModule
{
/**
* @type string
*/
public $name = 'TargetNoreferrer';
/**
* @param HTMLPurifier_Config $config
*/
public function setup($config) {
$a = $this->addBlankElement('a');
$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer();
}
}

View File

@ -271,6 +271,14 @@ class HTMLPurifier_HTMLModuleManager
if ($config->get('HTML.TargetBlank')) {
$modules[] = 'TargetBlank';
}
// NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank
// so that its post-attr-transform gets run afterwards.
if ($config->get('HTML.TargetNoreferrer')) {
$modules[] = 'TargetNoreferrer';
}
if ($config->get('HTML.TargetNoopener')) {
$modules[] = 'TargetNoopener';
}
// merge in custom modules
$modules = array_merge($modules, $this->userModules);

View File

@ -27,13 +27,18 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
if (strpos($token->data, '://') === false) {
// our really quick heuristic failed, abort
// this may not work so well if we want to match things like
// "domainname.com", but then again, most people don't
// "google.com", but then again, most people don't
return;
}
// there is/are URL(s). Let's split the string:
// Note: this regex is extremely permissive
$bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
// there is/are URL(s). Let's split the string.
// We use this regex:
// https://gist.github.com/gruber/249502
// but with @cscott's backtracking fix and also
// the Unicode characters un-Unicodified.
$bits = preg_split(
'/\\b((?:[a-z][\\w\\-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]|\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\))+(?:\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?\x{00ab}\x{00bb}\x{201c}\x{201d}\x{2018}\x{2019}]))/iu',
$token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
$token = array();

View File

@ -46,6 +46,12 @@ class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
$this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
$this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
$this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
foreach ($this->exclude as $key => $attrs) {
if (!is_array($attrs)) {
// HACK, see HTMLPurifier/Printer/ConfigForm.php
$this->exclude[$key] = explode(';', $attrs);
}
}
$this->attrValidator = new HTMLPurifier_AttrValidator();
}

View File

@ -36,6 +36,7 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
);
/**
* These are all lower-case keys.
* @type array
*/
protected $allowedParam = array(
@ -43,7 +44,7 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
'movie' => true,
'flashvars' => true,
'src' => true,
'allowFullScreen' => true, // if omitted, assume to be 'false'
'allowfullscreen' => true, // if omitted, assume to be 'false'
);
/**
@ -93,9 +94,11 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
$token->attr['name'] === $this->addParam[$n]) {
// keep token, and add to param stack
$this->paramStack[$i][$n] = true;
} elseif (isset($this->allowedParam[$n])) {
} elseif (isset($this->allowedParam[strtolower($n)])) {
// keep token, don't do anything to it
// (could possibly check for duplicates here)
// Note: In principle, parameters should be case sensitive.
// But it seems they are not really; so accept any case.
} else {
$token = false;
}

View File

@ -96,7 +96,7 @@ class HTMLPurifier_Lexer
break;
}
if (class_exists('DOMDocument') &&
if (class_exists('DOMDocument', false) &&
method_exists('DOMDocument', 'loadHTML') &&
!extension_loaded('domxml')
) {
@ -169,21 +169,24 @@ class HTMLPurifier_Lexer
'&#x27;' => "'"
);
public function parseText($string, $config) {
return $this->parseData($string, false, $config);
}
public function parseAttr($string, $config) {
return $this->parseData($string, true, $config);
}
/**
* Parses special entities into the proper characters.
*
* This string will translate escaped versions of the special characters
* into the correct ones.
*
* @warning
* You should be able to treat the output of this function as
* completely parsed, but that's only because all other entities should
* have been handled previously in substituteNonSpecialEntities()
*
* @param string $string String character data to be parsed.
* @return string Parsed character data.
*/
public function parseData($string)
public function parseData($string, $is_attr, $config)
{
// following functions require at least one character
if ($string === '') {
@ -209,7 +212,15 @@ class HTMLPurifier_Lexer
}
// hmm... now we have some uncommon entities. Use the callback.
$string = $this->_entity_parser->substituteSpecialEntities($string);
if ($config->get('Core.LegacyEntityDecoder')) {
$string = $this->_entity_parser->substituteSpecialEntities($string);
} else {
if ($is_attr) {
$string = $this->_entity_parser->substituteAttrEntities($string);
} else {
$string = $this->_entity_parser->substituteTextEntities($string);
}
}
return $string;
}
@ -323,7 +334,9 @@ class HTMLPurifier_Lexer
}
// expand entities that aren't the big five
$html = $this->_entity_parser->substituteNonSpecialEntities($html);
if ($config->get('Core.LegacyEntityDecoder')) {
$html = $this->_entity_parser->substituteNonSpecialEntities($html);
}
// clean into wellformed UTF-8 string for an SGML context: this has
// to be done after entity expansion because the entities sometimes
@ -335,6 +348,13 @@ class HTMLPurifier_Lexer
$html = preg_replace('#<\?.+?\?>#s', '', $html);
}
$hidden_elements = $config->get('Core.HiddenElements');
if ($config->get('Core.AggressivelyRemoveScript') &&
!($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')
|| empty($hidden_elements["script"]))) {
$html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);
}
return $html;
}
@ -345,12 +365,17 @@ class HTMLPurifier_Lexer
public function extractBody($html)
{
$matches = array();
$result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches);
$result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);
if ($result) {
return $matches[1];
} else {
return $html;
// Make sure it's not in a comment
$comment_start = strrpos($matches[1], '<!--');
$comment_end = strrpos($matches[1], '-->');
if ($comment_start === false ||
($comment_end !== false && $comment_end > $comment_start)) {
return $matches[2];
}
}
return $html;
}
}

View File

@ -72,12 +72,20 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$doc->loadHTML($html);
restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0); // <body>
$div = $body->getElementsByTagName('div')->item(0); // <div>
$tokens = array();
$this->tokenizeDOM(
$doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0), // <body>
$tokens
);
$this->tokenizeDOM($div, $tokens, $config);
// If the div has a sibling, that means we tripped across
// a premature </div> tag. So remove the div we parsed,
// and then tokenize the rest of body. We can't tokenize
// the sibling directly as we'll lose the tags in that case.
if ($div->nextSibling) {
$body->removeChild($div);
$this->tokenizeDOM($body, $tokens, $config);
}
return $tokens;
}
@ -88,7 +96,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
* @return HTMLPurifier_Token of node appended to previously passed tokens.
*/
protected function tokenizeDOM($node, &$tokens)
protected function tokenizeDOM($node, &$tokens, $config)
{
$level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node)));
@ -97,7 +105,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO
$collect = $level > 0 ? true : false;
$needEndingTag = $this->createStartNode($node, $tokens, $collect);
$needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
if ($needEndingTag) {
$closingNodes[$level][] = $node;
}
@ -127,7 +135,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @return bool if the token needs an endtoken
* @todo data and tagName properties don't seem to exist in DOMNode?
*/
protected function createStartNode($node, &$tokens, $collect)
protected function createStartNode($node, &$tokens, $collect, $config)
{
// intercept non element nodes. WE MUST catch all of them,
// but we're not getting the character reference nodes because
@ -151,7 +159,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
}
}
}
$tokens[] = $this->factory->createText($this->parseData($data));
$tokens[] = $this->factory->createText($this->parseText($data, $config));
return false;
} elseif ($node->nodeType === XML_COMMENT_NODE) {
// this is code is only invoked for comments in script/style in versions
@ -252,7 +260,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Context $context
* @return string
*/
protected function wrapHTML($html, $config, $context)
protected function wrapHTML($html, $config, $context, $use_div = true)
{
$def = $config->getDefinition('HTML');
$ret = '';
@ -271,7 +279,11 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
// No protection if $html contains a stray </div>!
$ret .= '</head><body>' . $html . '</body></html>';
$ret .= '</head><body>';
if ($use_div) $ret .= '<div>';
$ret .= $html;
if ($use_div) $ret .= '</div>';
$ret .= '</body></html>';
return $ret;
}
}

View File

@ -129,12 +129,12 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// We are not inside tag and there still is another tag to parse
$token = new
HTMLPurifier_Token_Text(
$this->parseData(
$this->parseText(
substr(
$html,
$cursor,
$position_next_lt - $cursor
)
), $config
)
);
if ($maintain_line_numbers) {
@ -154,11 +154,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// Create Text of rest of string
$token = new
HTMLPurifier_Token_Text(
$this->parseData(
$this->parseText(
substr(
$html,
$cursor
)
), $config
)
);
if ($maintain_line_numbers) {
@ -324,8 +324,8 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
$token = new
HTMLPurifier_Token_Text(
'<' .
$this->parseData(
substr($html, $cursor)
$this->parseText(
substr($html, $cursor), $config
)
);
if ($maintain_line_numbers) {
@ -429,7 +429,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) {
$value = '';
}
return array($key => $this->parseData($value));
return array($key => $this->parseAttr($value, $config));
}
// setup loop environment
@ -518,7 +518,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) {
$value = '';
}
$array[$key] = $this->parseData($value);
$array[$key] = $this->parseAttr($value, $config);
$cursor++;
} else {
// boolattr

View File

@ -21,7 +21,7 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
public function tokenizeHTML($html, $config, $context)
{
$new_html = $this->normalize($html, $config, $context);
$new_html = $this->wrapHTML($new_html, $config, $context);
$new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */);
try {
$parser = new HTML5($new_html);
$doc = $parser->save();
@ -34,9 +34,9 @@ class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex
$tokens = array();
$this->tokenizeDOM(
$doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0) // <body>
getElementsByTagName('body')->item(0) // <body>
,
$tokens
$tokens, $config
);
return $tokens;
}
@ -1515,6 +1515,7 @@ class HTML5
// Consume the maximum number of characters possible, with the
// consumed characters case-sensitively matching one of the
// identifiers in the first column of the entities table.
$e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
$len = strlen($e_name);
@ -1547,7 +1548,7 @@ class HTML5
// Return a character token for the character corresponding to the
// entity name (as given by the second column of the entities table).
return html_entity_decode('&' . $entity . ';', ENT_QUOTES, 'UTF-8');
return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8');
}
private function emitToken($token)

View File

@ -327,6 +327,10 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer
case HTMLPurifier_VarParser::HASH:
$nvalue = '';
foreach ($value as $i => $v) {
if (is_array($v)) {
// HACK
$v = implode(";", $v);
}
$nvalue .= "$i:$v" . PHP_EOL;
}
$value = $nvalue;

View File

@ -165,7 +165,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if (empty($zipper->front)) break;
$token = $zipper->prev($token);
// indicate that other injectors should not process this token,
// but we need to reprocess it
// but we need to reprocess it. See Note [Injector skips]
unset($token->skip[$i]);
$token->rewind = $i;
if ($token instanceof HTMLPurifier_Token_Start) {
@ -210,6 +210,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($token instanceof HTMLPurifier_Token_Text) {
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -367,6 +368,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($ok) {
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -422,6 +424,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
$token->start = $current_parent;
foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue;
}
if ($token->rewind !== null && $token->rewind !== $i) {
@ -534,12 +537,17 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
*/
protected function processToken($token, $injector = -1)
{
// Zend OpCache miscompiles $token = array($token), so
// avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108
// normalize forms of token
if (is_object($token)) {
$token = array(1, $token);
$tmp = $token;
$token = array(1, $tmp);
}
if (is_int($token)) {
$token = array($token);
$tmp = $token;
$token = array($tmp);
}
if ($token === false) {
$token = array(1);
@ -561,7 +569,12 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
if ($injector > -1) {
// determine appropriate skips
// See Note [Injector skips]
// Determine appropriate skips. Here's what the code does:
// *If* we deleted one or more tokens, copy the skips
// of those tokens into the skips of the new tokens (in $token).
// Also, mark the newly inserted tokens as having come from
// $injector.
$oldskip = isset($old[0]) ? $old[0]->skip : array();
foreach ($token as $object) {
$object->skip = $oldskip;
@ -597,4 +610,50 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
}
}
// Note [Injector skips]
// ~~~~~~~~~~~~~~~~~~~~~
// When I originally designed this class, the idea behind the 'skip'
// property of HTMLPurifier_Token was to help avoid infinite loops
// in injector processing. For example, suppose you wrote an injector
// that bolded swear words. Naively, you might write it so that
// whenever you saw ****, you replaced it with <strong>****</strong>.
//
// When this happens, we will reprocess all of the tokens with the
// other injectors. Now there is an opportunity for infinite loop:
// if we rerun the swear-word injector on these tokens, we might
// see **** and then reprocess again to get
// <strong><strong>****</strong></strong> ad infinitum.
//
// Thus, the idea of a skip is that once we process a token with
// an injector, we mark all of those tokens as having "come from"
// the injector, and we never run the injector again on these
// tokens.
//
// There were two more complications, however:
//
// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
// you had <b><i></i></b>, after you removed the <i></i>, you
// really would like this injector to go back and reprocess
// the <b> tag, discovering that it is now empty and can be
// removed. So we reintroduced the possibility of infinite looping
// by adding a "rewind" function, which let you go back to an
// earlier point in the token stream and reprocess it with injectors.
// Needless to say, we need to UN-skip the token so it gets
// reprocessed.
//
// - Suppose that you successfuly process a token, replace it with
// one with your skip mark, but now another injector wants to
// process the skipped token with another token. Should you continue
// to skip that new token, or reprocess it? If you reprocess,
// you can end up with an infinite loop where one injector converts
// <a> to <b>, and then another injector converts it back. So
// we inherit the skips, but for some reason, I thought that we
// should inherit the skip from the first token of the token
// that we deleted. Why? Well, it seems to work OK.
//
// If I were to redesign this functionality, I would absolutely not
// go about doing it this way: the semantics are just not very well
// defined, and in any case you probably wanted to operate on trees,
// not token streams.
// vim: et sw=4 sts=4

View File

@ -26,7 +26,7 @@ abstract class HTMLPurifier_Token
public $armor = array();
/**
* Used during MakeWellFormed.
* Used during MakeWellFormed. See Note [Injector skips]
* @type
*/
public $skip;

View File

@ -85,11 +85,13 @@ class HTMLPurifier_URI
$def = $config->getDefinition('URI');
$scheme_obj = $def->getDefaultScheme($config, $context);
if (!$scheme_obj) {
// something funky happened to the default scheme object
trigger_error(
'Default scheme object "' . $def->defaultScheme . '" was not readable',
E_USER_WARNING
);
if ($def->defaultScheme !== null) {
// something funky happened to the default scheme object
trigger_error(
'Default scheme object "' . $def->defaultScheme . '" was not readable',
E_USER_WARNING
);
} // suppress error if it's null
return false;
}
}

View File

@ -79,9 +79,18 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
} else {
$raw_data = $data;
}
if ( strlen($raw_data) < 12 ) {
// error; exif_imagetype throws exception with small files,
// and this likely indicates a corrupt URI/failed parse anyway
return false;
}
// XXX probably want to refactor this into a general mechanism
// for filtering arbitrary content types
$file = tempnam("/tmp", "");
if (function_exists('sys_get_temp_dir')) {
$file = tempnam(sys_get_temp_dir(), "");
} else {
$file = tempnam("/tmp", "");
}
file_put_contents($file, $raw_data);
if (function_exists('exif_imagetype')) {
$image_code = exif_imagetype($file);

View File

@ -0,0 +1,46 @@
<?php
/**
* Validates tel (for phone numbers).
*
* The relevant specifications for this protocol are RFC 3966 and RFC 5341,
* but this class takes a much simpler approach: we normalize phone
* numbers so that they only include (possibly) a leading plus,
* and then any number of digits and x'es.
*/
class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme
{
/**
* @type bool
*/
public $browsable = false;
/**
* @type bool
*/
public $may_omit_host = true;
/**
* @param HTMLPurifier_URI $uri
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool
*/
public function doValidate(&$uri, $config, $context)
{
$uri->userinfo = null;
$uri->host = null;
$uri->port = null;
// Delete all non-numeric characters, non-x characters
// from phone number, EXCEPT for a leading plus sign.
$uri->path = preg_replace('/(?!^\+)[^\dx]/', '',
// Normalize e(x)tension to lower-case
str_replace('X', 'x', $uri->path));
return true;
}
}
// vim: et sw=4 sts=4

View File

@ -1 +1 @@
4.7.0
4.9.3

View File

@ -13,7 +13,7 @@
* @category HTTP
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
@ -21,7 +21,9 @@
/**
* A class representing an URL as per RFC 3986.
*/
require_once 'Net/URL2.php';
if (!class_exists('Net_URL2', true)) {
require_once 'Net/URL2.php';
}
/**
* Exception class for HTTP_Request2 package
@ -35,7 +37,7 @@ require_once 'HTTP/Request2/Exception.php';
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
* @link http://tools.ietf.org/html/rfc2616#section-5
*/
@ -213,7 +215,7 @@ class HTTP_Request2 implements SplSubject
$this->setMethod($method);
}
$this->setHeader(
'user-agent', 'HTTP_Request2/2.2.1 ' .
'user-agent', 'HTTP_Request2/2.3.0 ' .
'(http://pear.php.net/package/http_request2) PHP/' . phpversion()
);
}
@ -794,6 +796,11 @@ class HTTP_Request2 implements SplSubject
* encoded by Content-Encoding</li>
* <li>'receivedBody' - after receiving the complete response
* body, data is HTTP_Request2_Response object</li>
* <li>'warning' - a problem arose during the request
* that is not severe enough to throw
* an Exception, data is the warning
* message (string). Currently dispatched if
* response body was received incompletely.</li>
* </ul>
* Different adapters may not send all the event types. Mock adapter does
* not send any events to the observers.
@ -1022,7 +1029,7 @@ class HTTP_Request2 implements SplSubject
}
// (deprecated) mime_content_type function available
if (empty($info) && function_exists('mime_content_type')) {
return mime_content_type($filename);
$info = mime_content_type($filename);
}
return empty($info)? 'application/octet-stream': $info;
}

View File

@ -13,7 +13,7 @@
* @category HTTP
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
@ -34,7 +34,7 @@ require_once 'HTTP/Request2/Response.php';
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
*/
abstract class HTTP_Request2_Adapter

View File

@ -13,7 +13,7 @@
* @category HTTP
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
@ -30,7 +30,7 @@ require_once 'HTTP/Request2/Adapter.php';
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
*/
class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
@ -116,6 +116,12 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
*/
protected $eventReceivedHeaders = false;
/**
* Whether 'sentBoody' event was sent to observers
* @var boolean
*/
protected $eventSentBody = false;
/**
* Position within request body
* @var integer
@ -171,6 +177,7 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
$this->position = 0;
$this->eventSentHeaders = false;
$this->eventReceivedHeaders = false;
$this->eventSentBody = false;
try {
if (false === curl_exec($ch = $this->createCurlHandle())) {
@ -180,6 +187,9 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
}
if (isset($ch)) {
$this->lastInfo = curl_getinfo($ch);
if (CURLE_OK !== curl_errno($ch)) {
$this->request->setLastEvent('warning', curl_error($ch));
}
curl_close($ch);
}
@ -191,7 +201,7 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
}
if ($jar = $request->getCookieJar()) {
$jar->addCookiesFromResponse($response, $request->getUrl());
$jar->addCookiesFromResponse($response);
}
if (0 < $this->lastInfo['size_download']) {
@ -400,9 +410,12 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
protected function workaroundPhpBug47204($ch, &$headers)
{
// no redirects, no digest auth -> probably no rewind needed
// also apply workaround only for POSTs, othrerwise we get
// https://pear.php.net/bugs/bug.php?id=20440 for PUTs
if (!$this->request->getConfig('follow_redirects')
&& (!($auth = $this->request->getAuth())
|| HTTP_Request2::AUTH_DIGEST != $auth['scheme'])
|| HTTP_Request2::METHOD_POST !== $this->request->getMethod()
) {
curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));
@ -469,40 +482,36 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
*/
protected function callbackWriteHeader($ch, $string)
{
// we may receive a second set of headers if doing e.g. digest auth
if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
// don't bother with 100-Continue responses (bug #15785)
if (!$this->eventSentHeaders
|| $this->response->getStatus() >= 200
) {
$this->request->setLastEvent(
'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
);
}
if (!$this->eventSentHeaders
// we may receive a second set of headers if doing e.g. digest auth
// but don't bother with 100-Continue responses (bug #15785)
|| $this->eventReceivedHeaders && $this->response->getStatus() >= 200
) {
$this->request->setLastEvent(
'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
);
}
if (!$this->eventSentBody) {
$upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
// if body wasn't read by a callback, send event with total body size
// if body wasn't read by the callback, send event with total body size
if ($upload > $this->position) {
$this->request->setLastEvent(
'sentBodyPart', $upload - $this->position
);
$this->position = $upload;
}
if ($upload && (!$this->eventSentHeaders
|| $this->response->getStatus() >= 200)
) {
if ($upload > 0) {
$this->request->setLastEvent('sentBody', $upload);
}
$this->eventSentHeaders = true;
// we'll need a new response object
if ($this->eventReceivedHeaders) {
$this->eventReceivedHeaders = false;
$this->response = null;
}
}
if (empty($this->response)) {
$this->response = new HTTP_Request2_Response(
$this->eventSentHeaders = true;
$this->eventSentBody = true;
if ($this->eventReceivedHeaders || empty($this->response)) {
$this->eventReceivedHeaders = false;
$this->response = new HTTP_Request2_Response(
$string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
);
} else {
$this->response->parseHeaderLine($string);
if ('' == trim($string)) {
@ -522,7 +531,7 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
}
if ($jar = $this->request->getCookieJar()) {
$jar->addCookiesFromResponse($this->response, $this->request->getUrl());
$jar->addCookiesFromResponse($this->response);
if (!$redirectUrl->isAbsolute()) {
$redirectUrl = $this->request->getUrl()->resolve($redirectUrl);
}
@ -532,6 +541,7 @@ class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
}
}
$this->eventReceivedHeaders = true;
$this->eventSentBody = false;
}
}
return strlen($string);

View File

@ -13,7 +13,7 @@
* @category HTTP
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
@ -44,7 +44,7 @@ require_once 'HTTP/Request2/Adapter.php';
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
*/
class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter

View File

@ -13,7 +13,7 @@
* @category HTTP
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
@ -34,7 +34,7 @@ require_once 'HTTP/Request2/SocketWrapper.php';
* @package HTTP_Request2
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
*/
class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
@ -147,7 +147,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
if ($jar = $request->getCookieJar()) {
$jar->addCookiesFromResponse($response, $request->getUrl());
$jar->addCookiesFromResponse($response);
}
if (!$this->canKeepAlive($keepAlive, $response)) {
@ -261,9 +261,16 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
foreach ($this->request->getConfig() as $name => $value) {
if ('ssl_' == substr($name, 0, 4) && null !== $value) {
if ('ssl_verify_host' == $name) {
if ($value) {
$options['ssl']['CN_match'] = $reqHost;
if (version_compare(phpversion(), '5.6', '<')) {
if ($value) {
$options['ssl']['CN_match'] = $reqHost;
}
} else {
$options['ssl']['verify_peer_name'] = $value;
$options['ssl']['peer_name'] = $reqHost;
}
} else {
$options['ssl'][substr($name, 4)] = $value;
}
@ -281,7 +288,7 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
// Changing SSL context options after connection is established does *not*
// work, we need a new connection if options change
$remote = ((!$secure || $httpProxy || $socksProxy)? 'tcp://': 'ssl://')
$remote = ((!$secure || $httpProxy || $socksProxy)? 'tcp://': 'tls://')
. $host . ':' . $port;
$socketKey = $remote . (
($secure && $httpProxy || $socksProxy)
@ -312,12 +319,12 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
$conninfo = "tcp://{$reqHost}:{$reqPort} via {$remote}";
} else {
$this->socket->enableCrypto();
$conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
$conninfo = "tls://{$reqHost}:{$reqPort} via {$remote}";
}
} elseif ($secure && $httpProxy && !$tunnel) {
$this->establishTunnel();
$conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
$conninfo = "tls://{$reqHost}:{$reqPort} via {$remote}";
} else {
$this->socket = new HTTP_Request2_SocketWrapper(
@ -1043,14 +1050,14 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
$chunked = 'chunked' == $response->getHeader('transfer-encoding');
$length = $response->getHeader('content-length');
$hasBody = false;
if ($chunked || null === $length || 0 < intval($length)) {
// RFC 2616, section 4.4:
// 3. ... If a message is received with both a
// Transfer-Encoding header field and a Content-Length header field,
// the latter MUST be ignored.
$toRead = ($chunked || null === $length)? null: $length;
$this->chunkLength = 0;
// RFC 2616, section 4.4:
// 3. ... If a message is received with both a
// Transfer-Encoding header field and a Content-Length header field,
// the latter MUST be ignored.
$toRead = ($chunked || null === $length)? null: $length;
$this->chunkLength = 0;
if ($chunked || null === $length || 0 < intval($length)) {
while (!$this->socket->eof() && (is_null($toRead) || 0 < $toRead)) {
if ($chunked) {
$data = $this->readChunked($bufferSize);
@ -1075,6 +1082,11 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
}
}
}
if (0 !== $this->chunkLength || null !== $toRead && $toRead > 0) {
$this->request->setLastEvent(
'warning', 'transfer closed with outstanding read data remaining'
);
}
if ($hasBody) {
$this->request->setLastEvent('receivedBody', $response);
@ -1095,11 +1107,16 @@ class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
// at start of the next chunk?
if (0 == $this->chunkLength) {
$line = $this->socket->readLine($bufferSize);
if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
if ('' === $line && $this->socket->eof()) {
$this->chunkLength = -1; // indicate missing chunk
return '';
} elseif (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
throw new HTTP_Request2_MessageException(
"Cannot decode chunked response, invalid chunk length '{$line}'",
HTTP_Request2_Exception::DECODE_ERROR
);
} else {
$this->chunkLength = hexdec($matches[1]);
// Chunk with zero length indicates the end

Some files were not shown because too many files have changed in this diff Show More