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/* avatar/
files/* files/
file/* file/
local/* local/
_darcs/* _darcs/
logs/* logs/
log/* log/
run/* run/
config.php config.php
.htaccess .htaccess
httpd.conf httpd.conf

View File

@ -497,9 +497,9 @@ Profile management.
biolimit: max character length of bio; 0 means no limit; null means to use biolimit: max character length of bio; 0 means no limit; null means to use
the site text limit default. 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 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. delete: whether users can delete their own accounts. Defaults to false.
move: whether users can move their accounts to another server. Defaults move: whether users can move their accounts to another server. Defaults
to true. to true.

37
INSTALL
View File

@ -26,16 +26,12 @@ PHP modules
The following software packages are *required* for this software to The following software packages are *required* for this software to
run correctly. run correctly.
- PHP 5.5+ For newer versions, some functions that are used may be - PHP 5.6+ PHP7.x is also supported.
disabled by default, such as the pcntl_* family. See the - MariaDB 5+ MariaDB 10.x is also supported.
section on 'Queues and daemons' for more information. - Web server Apache, lighttpd and nginx will all work, see sample
- MariaDB 5+ GNU Social uses, by default, a MariaDB server for data configuration files in the web root. Please use PHP-FPM
storage. Versions 5.x and 10.x have both reportedly and configure mod_rewrite (or equivalent) for an optimal
worked well. It is also possible to run MySQL 5.5+. experience.
- 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.
Your PHP installation must include the following PHP extensions for a Your PHP installation must include the following PHP extensions for a
functional setup of GNU Social: 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 - php5-mysqlnd The native driver for PHP5 MariaDB connections. If you
use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough. 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 Or, for PHP7, some or all of these will be necessary. PHP7 works and on
experimental and not necessarily working: 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-bcmath
php7.0-curl php7.0-curl
php7.0-exif php7.0-exif
php7.0-gd php7.0-gd
php7.0-intl php7.0-intl
php7.0-mbstring php7.0-mbstring
php7.0-mysqlnd php7.0-mysql
php7.0-opcache php7.0-opcache
php7.0-readline php7.0-readline
php7.0-xmlwriter php7.0-xmlwriter
The above package names are for Debian based systems. In the case of NOTE: In Arch Linux, at least PHP5 requires manual enabling in the
Arch Linux, PHP is compiled with support for most extensions but they relevant php.ini for some modules, most notably 'gmp'.
require manual enabling in the relevant php.ini file (mostly php5-gmp).
Better performance 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 - opcache Improves performance a _lot_. Included in PHP, must be
enabled manually in php.ini for most distributions. Find enabled manually in php.ini for most distributions. Find
and set at least: opcache.enable=1 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; - gettext For multiple languages. Default on many PHP installs;
will be emulated if not present. will be emulated if not present.
- exif For thumbnails to be properly oriented. - 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 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. - Backing up a user's account is more and more complete.
- Emojis 😸 (utf8mb4 support) - Emojis 😸 (utf8mb4 support)
- Fully qualified group mentions (!group@example.com)
The last release, 1.1.3, gave us these improvements: The last release, 1.1.3, gave us these improvements:

View File

@ -46,7 +46,7 @@
/api/statuses/update.:format /api/statuses/update.:format
@par Formats (:format) @par Formats (:format)
xml, json xml, json, atom
@par HTTP Method(s) @par HTTP Method(s)
POST POST
@ -339,6 +339,8 @@ class ApiStatusesUpdateAction extends ApiAuthAction
$this->showSingleXmlStatus($this->notice); $this->showSingleXmlStatus($this->notice);
} elseif ($this->format == 'json') { } elseif ($this->format == 'json') {
$this->show_single_json_status($this->notice); $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; $this->group = $group;
} }
function newListItem($profile) function newListItem(Profile $profile)
{ {
return new GroupBlockListItem($profile, $this->group, $this->action); return new GroupBlockListItem($profile, $this->group, $this->action);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,6 +113,18 @@ class ShowstreamAction extends NoticestreamAction
$this->target->getNickname(), $this->tag))); $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, return array(new Feed(Feed::JSON,
common_local_url('ApiTimelineUser', common_local_url('ApiTimelineUser',
array( array(
@ -139,10 +151,7 @@ class ShowstreamAction extends NoticestreamAction
sprintf(_('Notice feed for %s (RSS 2.0)'), sprintf(_('Notice feed for %s (RSS 2.0)'),
$this->target->getNickname())), $this->target->getNickname())),
new Feed(Feed::ATOM, new Feed(Feed::ATOM,
common_local_url('ApiTimelineUser', $this->target->getAtomFeed(),
array(
'id' => $this->target->getID(),
'format' => 'atom')),
// TRANS: Title for link to notice feed. // TRANS: Title for link to notice feed.
// TRANS: %s is a user nickname. // TRANS: %s is a user nickname.
sprintf(_('Notice feed for %s (Atom)'), sprintf(_('Notice feed for %s (Atom)'),
@ -221,13 +230,14 @@ class ShowstreamAction extends NoticestreamAction
$this->showEmptyListMessage(); $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)) if (!empty($this->tag))
{ {
$args['tag'] = $this->tag; $args['tag'] = $this->tag;
} }
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page, $this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', $args); $this->getActionName(), $args);
} }
function showAnonymousMessage() function showAnonymousMessage()

View File

@ -36,6 +36,7 @@ class Conversation extends Managed_DataObject
public $__table = 'conversation'; // table name public $__table = 'conversation'; // table name
public $id; // int(4) primary_key not_null auto_increment public $id; // int(4) primary_key not_null auto_increment
public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space 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 $created; // datetime not_null
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
@ -45,6 +46,7 @@ class Conversation extends Managed_DataObject
'fields' => array( 'fields' => array(
'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'), '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'), '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'), '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'), '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 * @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! // Be aware that the Notice does not have an id yet since it's not inserted!
$conv = new Conversation(); $conv = new Conversation();
$conv->created = $created ?: common_sql_now(); $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(), TagURI::mint(),
'objectType', 'thread', 'objectType', 'thread',
'nonce', common_random_hexstr(8)); 'nonce', common_random_hexstr(8));
$conv->url = null; // locally generated Conversation objects don't get static URLs stored
}
// This insert throws exceptions on failure // This insert throws exceptions on failure
$conv->insert(); $conv->insert();

View File

@ -194,10 +194,14 @@ class File extends Managed_DataObject
} }
$redir = File_redirection::where($given_url); $redir = File_redirection::where($given_url);
$file = $redir->getFile(); try {
$file = $redir->getFile();
if (!$file instanceof File || empty($file->id)) { } 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 // 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'); throw new ServerException('URL processing failed without new File object');
} }
@ -731,16 +735,18 @@ class File extends Managed_DataObject
$dupfile = new File(); $dupfile = new File();
// First we find file entries that would be duplicates of this when shortened // 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. // ... 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) // Leave one of the URLs in the database by using ->find(true) (fetches first entry)
if ($dupfile->find(true)) { if ($dupfile->find(true)) {
print "\nShortening url entry for $table id: {$file->id} ["; print "\nShortening url entry for $table id: {$file->id} [";
$orig = clone($dupfile); $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->url = $file->shortenedurl; // make sure it's only 191 chars from now on
$dupfile->update($orig); $dupfile->update($orig);
print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} ["; print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
// only start deleting with this fetch. // only start deleting with this fetch.
while($dupfile->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 "."; print ".";
$dupfile->delete(); $dupfile->delete();
} }

View File

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

View File

@ -89,7 +89,7 @@ class Foreign_link extends Managed_DataObject
return $flink; return $flink;
} }
function set_flags($noticesend, $noticerecv, $replysync, $friendsync) function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync)
{ {
if ($noticesend) { if ($noticesend) {
$this->noticesync |= FOREIGN_NOTICE_SEND; $this->noticesync |= FOREIGN_NOTICE_SEND;
@ -109,6 +109,12 @@ class Foreign_link extends Managed_DataObject
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY; $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
} }
if ($repeatsync) {
$this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT;
} else {
$this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT;
}
if ($friendsync) { if ($friendsync) {
$this->friendsync |= FOREIGN_FRIEND_RECV; $this->friendsync |= FOREIGN_FRIEND_RECV;
} else { } 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) { public function getObjectType($canonical=false) {
if (is_null($this->object_type) || $this->object_type==='') { if (is_null($this->object_type) || $this->object_type==='') {
throw new NoObjectTypeException($this); throw new NoObjectTypeException($this);
@ -442,6 +457,7 @@ class Notice extends Managed_DataObject
static function saveNew($profile_id, $content, $source, array $options=null) { static function saveNew($profile_id, $content, $source, array $options=null) {
$defaults = array('uri' => null, $defaults = array('uri' => null,
'url' => null, 'url' => null,
'self' => null,
'conversation' => null, // URI of conversation 'conversation' => null, // URI of conversation
'reply_to' => null, // This will override convo URI if the parent is known '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 'repeat_of' => null, // This will override convo URI if the repeated notice is known
@ -624,8 +640,13 @@ class Notice extends Managed_DataObject
} else { } else {
// Conversation entry with specified URI was not found, so we must create it. // 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']); 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 // 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(); $notice->conversation = $conv->getID();
unset($conv); 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 // Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc. // the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves. // reasonably handle notifications themselves.
@ -765,6 +790,9 @@ class Notice extends Managed_DataObject
// implied object // implied object
$options['uri'] = $act->id; $options['uri'] = $act->id;
$options['url'] = $act->link; $options['url'] = $act->link;
if ($act->selfLink) {
$options['self'] = $act->selfLink;
}
} else { } else {
$actobj = count($act->objects)===1 ? $act->objects[0] : null; $actobj = count($act->objects)===1 ? $act->objects[0] : null;
if (!is_null($actobj) && !empty($actobj->id)) { if (!is_null($actobj) && !empty($actobj->id)) {
@ -775,6 +803,9 @@ class Notice extends Managed_DataObject
$options['url'] = $actobj->id; $options['url'] = $actobj->id;
} }
} }
if ($actobj->selfLink) {
$options['self'] = $actobj->selfLink;
}
} }
$defaults = array( $defaults = array(
@ -784,6 +815,7 @@ class Notice extends Managed_DataObject
'reply_to' => null, 'reply_to' => null,
'repeat_of' => null, 'repeat_of' => null,
'scope' => null, 'scope' => null,
'self' => null,
'source' => 'unknown', 'source' => 'unknown',
'tags' => array(), 'tags' => array(),
'uri' => null, 'uri' => null,
@ -921,7 +953,7 @@ class Notice extends Managed_DataObject
// Conversation entry with specified URI was not found, so we must create it. // 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); 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 // 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(); $stored->conversation = $conv->getID();
unset($conv); unset($conv);
@ -1020,6 +1052,14 @@ class Notice extends Managed_DataObject
throw new ServerException('Supposedly saved Notice has no ID.'); 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 // Only save 'attention' and metadata stuff (URLs, tags...) stuff if
// the activityverb is a POST (since stuff like repeat, favorite etc. // the activityverb is a POST (since stuff like repeat, favorite etc.
// reasonably handle notifications themselves. // reasonably handle notifications themselves.
@ -1578,12 +1618,12 @@ class Notice extends Managed_DataObject
if (common_config('group', 'addtag')) { if (common_config('group', 'addtag')) {
// we automatically add a tag for every group name, too // we automatically add a tag for every group name, too
common_debug('Adding hashtag matching group nickname: '._ve($group->getNickname()));
$tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->nickname), $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->getNickname()),
'notice_id' => $this->id)); 'notice_id' => $this->getID()));
if (is_null($tag)) { 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); $conv = Conversation::getKV('id', $this->conversation);
if ($conv instanceof Conversation) { if ($conv instanceof Conversation) {
$ctx->conversation = $conv->uri; $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()) { if ($this->isLocal()) {
$act->selfLink = common_local_url('ApiStatusesShow', array('id' => $this->id,
'format' => 'atom'));
$act->editLink = $act->selfLink; $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->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname());
$object->content = $this->getRendered(); $object->content = $this->getRendered();
$object->link = $this->getUrl(); $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)); 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() public function getUser()
{ {
if (!isset($this->_user[$this->id])) { if (!isset($this->_user[$this->id])) {
$user = User::getKV('id', $this->id); $cur_user = common_current_user();
if (!$user instanceof User) { if (($cur_user instanceof User) && $cur_user->sameAs($this)) {
throw new NoSuchUserException(array('id'=>$this->id)); $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; $this->_user[$this->id] = $user;
} }
@ -941,11 +946,6 @@ class Profile extends Managed_DataObject
function delete($useWhere=false) 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->_deleteNotices();
$this->_deleteSubscriptions(); $this->_deleteSubscriptions();
$this->_deleteTags(); $this->_deleteTags();
@ -957,6 +957,7 @@ class Profile extends Managed_DataObject
// not on individual objects. // not on individual objects.
$related = array('Reply', $related = array('Reply',
'Group_member', 'Group_member',
'Profile_role'
); );
Event::handle('ProfileDeleteRelated', array($this, &$related)); Event::handle('ProfileDeleteRelated', array($this, &$related));
@ -965,6 +966,8 @@ class Profile extends Managed_DataObject
$inst->profile_id = $this->id; $inst->profile_id = $this->id;
$inst->delete(); $inst->delete();
} }
$this->grantRole(Profile_role::DELETED);
$localuser = User::getKV('id', $this->id); $localuser = User::getKV('id', $this->id);
if ($localuser instanceof User) { if ($localuser instanceof User) {
@ -1532,6 +1535,14 @@ class Profile extends Managed_DataObject
} }
return $url; return $url;
} }
public function getHtmlTitle()
{
try {
return $this->getAcctUri(false);
} catch (ProfileNoAcctUriException $e) {
return $this->getNickname();
}
}
public function getNickname() public function getNickname()
{ {
@ -1612,14 +1623,13 @@ class Profile extends Managed_DataObject
return !empty($block); return !empty($block);
} }
function getAtomFeed() public function getAtomFeed()
{ {
$feed = null; $feed = null;
if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) { if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) {
$user = User::getKV('id', $this->id); if ($this->isLocal()) {
if (!empty($user)) { $feed = common_local_url('ApiTimelineUser', array('id' => $this->getID(),
$feed = common_local_url('ApiTimelineUser', array('id' => $user->id,
'format' => 'atom')); 'format' => 'atom'));
} }
Event::handle('EndProfileGetAtomFeed', array($this, $feed)); 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. // TRANS: Profile data could not be inserted for some reason.
throw new ServerException(_m('Could not insert profile data for new user.')); 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; $user->id = $id;

View File

@ -41,6 +41,7 @@ $classes = array('Schema_version',
'Notice', 'Notice',
'Notice_location', 'Notice_location',
'Notice_source', 'Notice_source',
'Notice_prefs',
'Reply', 'Reply',
'Consumer', 'Consumer',
'Token', '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 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 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 ## JSONP callbacks

View File

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

View File

@ -426,7 +426,7 @@ define('DB_PORTABILITY_ALL', 63);
* @author Daniel Convissor <danielc@php.net> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB class DB
@ -577,7 +577,7 @@ class DB
*/ */
function apiVersion() function apiVersion()
{ {
return '1.8.2'; return '1.9.2';
} }
// }}} // }}}
@ -772,7 +772,7 @@ class DB
$parsed['dbsyntax'] = $str; $parsed['dbsyntax'] = $str;
} }
if (!count($dsn)) { if (!strlen($dsn)) {
return $parsed; return $parsed;
} }
@ -941,7 +941,7 @@ class DB
* @author Stig Bakken <ssb@php.net> * @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_Error extends PEAR_Error class DB_Error extends PEAR_Error
@ -959,18 +959,32 @@ class DB_Error extends PEAR_Error
* *
* @see 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) $level = E_USER_NOTICE, $debuginfo = null)
{ {
if (is_int($code)) { if (is_int($code)) {
$this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, parent::__construct('DB Error: ' . DB::errorMessage($code), $code,
$mode, $level, $debuginfo, common_log(LOG_ERR, var_export($debuginfo,true))); $mode, $level, $debuginfo);
} else { } else {
$this->PEAR_Error("DB Error: $code", DB_ERROR, parent::__construct("DB Error: $code", DB_ERROR,
$mode, $level, $debuginfo); $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> * @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_result class DB_result
@ -1095,7 +1109,7 @@ class DB_result
* *
* @return void * @return void
*/ */
function DB_result(&$dbh, $result, $options = array()) function __construct(&$dbh, $result, $options = array())
{ {
$this->autofree = $dbh->options['autofree']; $this->autofree = $dbh->options['autofree'];
$this->dbh = &$dbh; $this->dbh = &$dbh;
@ -1453,7 +1467,7 @@ class DB_result
* @author Stig Bakken <ssb@php.net> * @author Stig Bakken <ssb@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
* @see DB_common::setFetchMode() * @see DB_common::setFetchMode()
*/ */
@ -1468,7 +1482,7 @@ class DB_row
* *
* @return void * @return void
*/ */
function DB_row(&$arr) function __construct(&$arr)
{ {
foreach ($arr as $key => $value) { foreach ($arr as $key => $value) {
$this->$key = &$arr[$key]; $this->$key = &$arr[$key];

View File

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

View File

@ -15,7 +15,7 @@
* @author Alan Knowles <alan@akbkhome.com> * @author Alan Knowles <alan@akbkhome.com>
* @copyright 1997-2006 The PHP Group * @copyright 1997-2006 The PHP Group
* @license http://www.php.net/license/3_01.txt PHP License 3.01 * @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 * @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 * Currenly only works with mysql / mysqli / posgtreas
* to use, you must set option: generate_links=true * to use, you must set option: generate_links=true
* *
* @author Pascal Schöni * @author Pascal Sch<EFBFBD>ni
*/ */
function _createForiegnKeys() function _createForiegnKeys()
@ -507,7 +507,7 @@ class DB_DataObject_Generator extends DB_DataObject
* Currenly only works with mysql / mysqli * Currenly only works with mysql / mysqli
* to use, you must set option: generate_links=true * to use, you must set option: generate_links=true
* *
* @author Pascal Schöni * @author Pascal Sch<EFBFBD>ni
*/ */
function generateForeignKeys() function generateForeignKeys()
{ {
@ -895,7 +895,7 @@ class DB_DataObject_Generator extends DB_DataObject
$options = &PEAR::getStaticProperty('DB_DataObject','options'); $options = &PEAR::getStaticProperty('DB_DataObject','options');
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; $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) { foreach($this->tables as $this->table) {
@ -976,8 +976,12 @@ class DB_DataObject_Generator extends DB_DataObject
$head .= $this->derivedHookExtendsDocBlock(); $head .= $this->derivedHookExtendsDocBlock();
// requires // requires - if you set extends_location = (blank) then no require line will be set
$head .= "require_once '{$this->_extendsFile}';\n\n"; // 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... // add dummy class header in...
// class // class
$head .= $this->derivedHookClassDocBlock(); $head .= $this->derivedHookClassDocBlock();
@ -1039,10 +1043,11 @@ class DB_DataObject_Generator extends DB_DataObject
continue; 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.')'; $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. // can not do set as PEAR::DB table info doesnt support it.
//if (substr($t->Type,0,3) == "set") //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']; $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
$this->_extends = empty($options['extends']) ? $this->_extends : $options['extends']; $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); $classname = $this->classname = $this->getClassNameFromTableName($this->table);

View File

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

View File

@ -41,7 +41,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_dbase extends DB_common class DB_dbase extends DB_common
@ -140,13 +140,13 @@ class DB_dbase extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
* @since Class functional since Release 1.7.0 * @since Class functional since Release 1.7.0
*/ */
@ -124,13 +124,13 @@ class DB_fbsql extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
* @since Class became stable in Release 1.7.0 * @since Class became stable in Release 1.7.0
*/ */
@ -180,13 +180,13 @@ class DB_ibase extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_ifx extends DB_common class DB_ifx extends DB_common
@ -167,13 +167,13 @@ class DB_ifx extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
* @since Class not functional until Release 1.7.0 * @since Class not functional until Release 1.7.0
*/ */
@ -126,13 +126,13 @@ class DB_msql extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_mssql extends DB_common class DB_mssql extends DB_common
@ -179,13 +179,13 @@ class DB_mssql extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_mysql extends DB_common class DB_mysql extends DB_common
@ -162,13 +162,13 @@ class DB_mysql extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
* @since Class functional since Release 1.6.3 * @since Class functional since Release 1.6.3
*/ */
@ -224,13 +224,13 @@ class DB_mysqli extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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) 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] ? $this->mysqli_types[$tmp->type]
: 'unknown', : 'unknown',
// http://bugs.php.net/?id=36579 // 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, 'len' => $tmp->length,
'flags' => $flags, 'flags' => $flags,
); );

View File

@ -47,7 +47,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_oci8 extends DB_common class DB_oci8 extends DB_common
@ -173,13 +173,13 @@ class DB_oci8 extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_odbc extends DB_common class DB_odbc extends DB_common
@ -153,13 +153,13 @@ class DB_odbc extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_pgsql extends DB_common class DB_pgsql extends DB_common
@ -148,13 +148,13 @@ class DB_pgsql extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_sqlite extends DB_common class DB_sqlite extends DB_common
@ -152,13 +152,13 @@ class DB_sqlite extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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> * @author Stig Bakken <stig@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_storage extends PEAR class DB_storage extends PEAR
@ -94,7 +94,7 @@ class DB_storage extends PEAR
* a reference to this object * 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->PEAR('DB_Error');
$this->_table = $table; $this->_table = $table;

View File

@ -46,7 +46,7 @@ require_once 'DB/common.php';
* @author Daniel Convissor <danielc@php.net> * @author Daniel Convissor <danielc@php.net>
* @copyright 1997-2007 The PHP Group * @copyright 1997-2007 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0 * @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 * @link http://pear.php.net/package/DB
*/ */
class DB_sybase extends DB_common class DB_sybase extends DB_common
@ -141,13 +141,13 @@ class DB_sybase extends DB_common
// {{{ constructor // {{{ constructor
/** /**
* This constructor calls <kbd>$this->DB_common()</kbd> * This constructor calls <kbd>parent::__construct()</kbd>
* *
* @return void * @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 * 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. * FILE, changes will be overwritten the next time the script is run.
* *
* @version 4.7.0 * @version 4.9.3
* *
* @warning * @warning
* You must *not* include any other HTML Purifier files before this file, * 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/SafeParam.php';
require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
require 'HTMLPurifier/AttrTransform/TargetBlank.php'; require 'HTMLPurifier/AttrTransform/TargetBlank.php';
require 'HTMLPurifier/AttrTransform/TargetNoopener.php';
require 'HTMLPurifier/AttrTransform/TargetNoreferrer.php';
require 'HTMLPurifier/AttrTransform/Textarea.php'; require 'HTMLPurifier/AttrTransform/Textarea.php';
require 'HTMLPurifier/ChildDef/Chameleon.php'; require 'HTMLPurifier/ChildDef/Chameleon.php';
require 'HTMLPurifier/ChildDef/Custom.php'; require 'HTMLPurifier/ChildDef/Custom.php';
@ -175,6 +177,8 @@ require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
require 'HTMLPurifier/HTMLModule/Tables.php'; require 'HTMLPurifier/HTMLModule/Tables.php';
require 'HTMLPurifier/HTMLModule/Target.php'; require 'HTMLPurifier/HTMLModule/Target.php';
require 'HTMLPurifier/HTMLModule/TargetBlank.php'; require 'HTMLPurifier/HTMLModule/TargetBlank.php';
require 'HTMLPurifier/HTMLModule/TargetNoopener.php';
require 'HTMLPurifier/HTMLModule/TargetNoreferrer.php';
require 'HTMLPurifier/HTMLModule/Text.php'; require 'HTMLPurifier/HTMLModule/Text.php';
require 'HTMLPurifier/HTMLModule/Tidy.php'; require 'HTMLPurifier/HTMLModule/Tidy.php';
require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
@ -225,5 +229,6 @@ require 'HTMLPurifier/URIScheme/https.php';
require 'HTMLPurifier/URIScheme/mailto.php'; require 'HTMLPurifier/URIScheme/mailto.php';
require 'HTMLPurifier/URIScheme/news.php'; require 'HTMLPurifier/URIScheme/news.php';
require 'HTMLPurifier/URIScheme/nntp.php'; require 'HTMLPurifier/URIScheme/nntp.php';
require 'HTMLPurifier/URIScheme/tel.php';
require 'HTMLPurifier/VarParser/Flexible.php'; require 'HTMLPurifier/VarParser/Flexible.php';
require 'HTMLPurifier/VarParser/Native.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 Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
@ -58,12 +58,12 @@ class HTMLPurifier
* Version of HTML Purifier. * Version of HTML Purifier.
* @type string * @type string
*/ */
public $version = '4.7.0'; public $version = '4.9.3';
/** /**
* Constant with version of HTML Purifier. * Constant with version of HTML Purifier.
*/ */
const VERSION = '4.7.0'; const VERSION = '4.9.3';
/** /**
* Global configuration object. * Global configuration object.
@ -104,7 +104,7 @@ class HTMLPurifier
/** /**
* Initializes the purifier. * 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 * for all instances of the purifier, if omitted, a default
* configuration is supplied (which can be overridden on a * configuration is supplied (which can be overridden on a
* per-use basis). * 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/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.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/AttrTransform/Textarea.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.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/Tables.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.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/Text.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.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/mailto.php';
require_once $__dir . '/HTMLPurifier/URIScheme/news.php'; require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
require_once $__dir . '/HTMLPurifier/URIScheme/nntp.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/Flexible.php';
require_once $__dir . '/HTMLPurifier/VarParser/Native.php'; require_once $__dir . '/HTMLPurifier/VarParser/Native.php';

View File

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

View File

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

View File

@ -86,7 +86,13 @@ abstract class HTMLPurifier_AttrDef
*/ */
protected function mungeRgb($string) 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); $css = $this->parseCDATA($css);
$definition = $config->getCSSDefinition(); $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(); $propvalues = array();
$new_declarations = '';
/** /**
* Name of the current CSS property being validated. * Name of the current CSS property being validated.
@ -83,7 +110,11 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
if ($result === false) { if ($result === false) {
continue; continue;
} }
$propvalues[$property] = $result; if ($allow_duplicates) {
$new_declarations .= "$property:$result;";
} else {
$propvalues[$property] = $result;
}
} }
$context->destroy('CurrentCSSProperty'); $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 // slightly inefficient, but it's the only way of getting rid of
// duplicates. Perhaps config to optimize it, but not now. // duplicates. Perhaps config to optimize it, but not now.
$new_declarations = '';
foreach ($propvalues as $prop => $value) { foreach ($propvalues as $prop => $value) {
$new_declarations .= "$prop:$value;"; $new_declarations .= "$prop:$value;";
} }

View File

@ -6,6 +6,16 @@
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef 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 string $color
* @param HTMLPurifier_Config $config * @param HTMLPurifier_Config $config
@ -29,59 +39,104 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
return $colors[$lower]; return $colors[$lower];
} }
if (strpos($color, 'rgb(') !== false) { if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
// rgb literal handling
$length = strlen($color); $length = strlen($color);
if (strpos($color, ')') !== $length - 1) { if (strpos($color, ')') !== $length - 1) {
return false; return false;
} }
$triad = substr($color, 4, $length - 4 - 1);
$parts = explode(',', $triad); // get used function : rgb, rgba, hsl or hsla
if (count($parts) !== 3) { $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; return false;
} }
$type = false; // to ensure that they're all the same type
$type = false;
$new_parts = array(); $new_parts = array();
$i = 0;
foreach ($parts as $part) { foreach ($parts as $part) {
$i++;
$part = trim($part); $part = trim($part);
if ($part === '') { if ($part === '') {
return false; return false;
} }
$length = strlen($part);
if ($part[$length - 1] === '%') { // different check for alpha channel
// handle percents if ($alpha_channel === true && $i === count($parts)) {
if (!$type) { $result = $this->alpha->validate($part, $config, $context);
$type = 'percentage';
} elseif ($type !== 'percentage') { if ($result === false) {
return false; return false;
} }
$num = (float)substr($part, 0, $length - 1);
if ($num < 0) { $new_parts[] = (string)$result;
$num = 0; continue;
} }
if ($num > 100) {
$num = 100; if (substr($part, -1) === '%') {
} $current_type = 'percentage';
$new_parts[] = "$num%";
} else { } else {
// handle integers $current_type = 'integer';
if (!$type) { }
$type = 'integer';
} elseif ($type !== 'integer') { if (!array_key_exists($current_type, $allowed_types[$i])) {
return false; return false;
} }
$num = (int)$part;
if ($num < 0) { if (!$type) {
$num = 0; $type = $current_type;
} }
if ($num > 255) {
$num = 255; if ($allow_different_types === false && $type != $current_type) {
} return false;
$new_parts[] = (string)$num; }
$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 { } else {
// hexadecimal handling // hexadecimal handling
if ($color[0] === '#') { if ($color[0] === '#') {
@ -100,6 +155,7 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
} }
return $color; return $color;
} }
} }
// vim: et sw=4 sts=4 // 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 // <http://ja.wikipedia.org/wiki/MS_明朝>. See
// the CSS3 spec for more examples: // the CSS3 spec for more examples:
// <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png> // <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: // However, most of these fonts have ASCII equivalents:
// for example, 'MS Mincho', and it's considered // for example, 'MS Mincho', and it's considered
// professional to use ASCII font names instead of // 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; return false;
} }
$uri_string = substr($uri_string, 4); $uri_string = substr($uri_string, 4);
if (strlen($uri_string) == 0) {
return false;
}
$new_length = strlen($uri_string) - 1; $new_length = strlen($uri_string) - 1;
if ($uri_string[$new_length] != ')') { if ($uri_string[$new_length] != ')') {
return false; 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 // we purposely avoid using regex, hopefully this is faster
if (ctype_alpha($id)) { if ($config->get('Attr.ID.HTML5') === true) {
$result = true; if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
} else {
if (!ctype_alpha(@$id[0])) {
return false; return false;
} }
// primitive style of regexps, I suppose } else {
$trim = trim( if (ctype_alpha($id)) {
$id, // OK
'A..Za..z0..9:-._' } else {
); if (!ctype_alpha(@$id[0])) {
$result = ($trim === ''); return false;
}
// primitive style of regexps, I suppose
$trim = trim(
$id,
'A..Za..z0..9:-._'
);
if ($trim !== '') {
return false;
}
}
} }
$regexp = $config->get('Attr.IDBlacklistRegexp'); $regexp = $config->get('Attr.IDBlacklistRegexp');
@ -91,14 +99,14 @@ class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
return false; return false;
} }
if (!$this->selector && $result) { if (!$this->selector) {
$id_accumulator->add($id); $id_accumulator->add($id);
} }
// if no change was made to the ID, return the result // if no change was made to the ID, return the result
// else, return the new id if stripping whitespace made it // else, return the new id if stripping whitespace made it
// valid, or return false. // 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. // fairly well supported.
$underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; $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: // The productions describing this are:
$a = '[a-z]'; // alpha $a = '[a-z]'; // alpha
$an = '[a-z0-9]'; // alphanum $an = '[a-z0-9]'; // alphanum
$and = "[a-z0-9-$underscore]"; // alphanum | "-" $and = "[a-z0-9-$underscore]"; // alphanum | "-"
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
$domainlabel = "$an($and*$an)?"; $domainlabel = "$an(?:$and*$an)?";
// toplabel = alpha | alpha *( alphanum | "-" ) alphanum // AMENDED as per RFC 3696
$toplabel = "$a($and*$an)?"; // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum
// side condition: not all numeric
$toplabel = "$an(?:$and*$an)?";
// hostname = *( domainlabel "." ) toplabel [ "." ] // hostname = *( domainlabel "." ) toplabel [ "." ]
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
return $string; 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 // If we have Net_IDNA2 support, we can support IRIs by
// punycoding them. (This is the most portable thing to do, // punycoding them. (This is the most portable thing to do,
// since otherwise we have to assume browsers support // since otherwise we have to assume browsers support
} elseif ($config->get('Core.EnableIDNA')) {
if ($config->get('Core.EnableIDNA')) {
$idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
// we need to encode each period separately // we need to encode each period separately
$parts = explode('.', $string); $parts = explode('.', $string);
@ -114,13 +123,14 @@ class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
} }
} }
$string = implode('.', $new_parts); $string = implode('.', $new_parts);
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
} catch (Exception $e) { } catch (Exception $e) {
// XXX error reporting // XXX error reporting
} }
} }
// Try again
if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
return $string;
}
return false; return false;
} }
} }

View File

@ -32,8 +32,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
if ($src) { if ($src) {
$alt = $config->get('Attr.DefaultImageAlt'); $alt = $config->get('Attr.DefaultImageAlt');
if ($alt === null) { if ($alt === null) {
// truncate if the alt is too long $attr['alt'] = basename($attr['src']);
$attr['alt'] = substr(basename($attr['src']), 0, 40);
} else { } else {
$attr['alt'] = $alt; $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'); $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['width'] =
$this->info['height'] = $this->info['height'] =
$max === null ? $max === null ?
@ -370,6 +374,19 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
); );
$this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); $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; 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 // the new set of children
$result = array(); $result = array();
// a little sanity check to make sure it's not ALL whitespace // a little sanity check to make sure it's not ALL whitespace
$all_whitespace = true; $all_whitespace = true;
$current_li = false; $current_li = null;
foreach ($children as $node) { foreach ($children as $node) {
if (!empty($node->is_whitespace)) { 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 // to handle non-list elements; non-list elements should
// not be appended to an existing li; only li created // not be appended to an existing li; only li created
// for non-list. This distinction is not currently made. // for non-list. This distinction is not currently made.
if ($current_li === false) { if ($current_li === null) {
$current_li = new HTMLPurifier_Node_Element('li'); $current_li = new HTMLPurifier_Node_Element('li');
$result[] = $current_li; $result[] = $current_li;
} }

View File

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

View File

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

View File

@ -24,11 +24,11 @@ class HTMLPurifier_ConfigSchema
* *
* array( * array(
* 'Namespace' => 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: * - If isAlias isn't set:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
@ -39,8 +39,8 @@ class HTMLPurifier_ConfigSchema
* - namespace: Namespace this directive aliases to * - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to * - name: Directive name this directive aliases to
* *
* In certain degenerate cases, stdclass will actually be an integer. In * In certain degenerate cases, stdClass will actually be an integer. In
* that case, the value is equivalent to an stdclass with the type * that case, the value is equivalent to an stdClass with the type
* property set to the integer. If the integer is negative, type is * property set to the integer. If the integer is negative, type is
* equal to the absolute value of integer, and allow_null is true. * 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) public function add($key, $default, $type, $allow_null)
{ {
$obj = new stdclass(); $obj = new stdClass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) { if ($allow_null) {
$obj->allow_null = true; $obj->allow_null = true;
@ -152,14 +152,14 @@ class HTMLPurifier_ConfigSchema
*/ */
public function addAlias($key, $new_key) public function addAlias($key, $new_key)
{ {
$obj = new stdclass; $obj = new stdClass;
$obj->key = $new_key; $obj->key = $new_key;
$obj->isAlias = true; $obj->isAlias = true;
$this->info[$key] = $obj; $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() 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 Cache.SerializerPermissions
TYPE: int TYPE: int/null
VERSION: 4.3.0 VERSION: 4.3.0
DEFAULT: 0755 DEFAULT: 0755
--DESCRIPTION-- --DESCRIPTION--
@ -8,4 +8,9 @@ DEFAULT: 0755
Directory permissions of the files and directories created inside Directory permissions of the files and directories created inside
the DefinitionCache/Serializer or other custom serializer path. the DefinitionCache/Serializer or other custom serializer path.
</p> </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 --# 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, 'ftp' => true,
'nntp' => true, 'nntp' => true,
'news' => true, 'news' => true,
'tel' => true,
) )
--DESCRIPTION-- --DESCRIPTION--
Whitelist that defines the schemes that a URI is allowed to have. This Whitelist that defines the schemes that a URI is allowed to have. This

View File

@ -1,5 +1,5 @@
URI.DefaultScheme URI.DefaultScheme
TYPE: string TYPE: string/null
DEFAULT: 'http' DEFAULT: 'http'
--DESCRIPTION-- --DESCRIPTION--
@ -7,4 +7,9 @@ DEFAULT: 'http'
Defines through what scheme the output will be served, in order to Defines through what scheme the output will be served, in order to
select the proper object validator when no scheme information is present. select the proper object validator when no scheme information is present.
</p> </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 --# vim: et sw=4 sts=4

View File

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

View File

@ -97,6 +97,12 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} }
$dir = $this->generateDirectoryPath($config); $dir = $this->generateDirectoryPath($config);
$dh = opendir($dir); $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))) { while (false !== ($filename = readdir($dh))) {
if (empty($filename)) { if (empty($filename)) {
continue; continue;
@ -106,6 +112,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} }
unlink($dir . '/' . $filename); unlink($dir . '/' . $filename);
} }
closedir($dh);
return true;
} }
/** /**
@ -119,6 +127,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} }
$dir = $this->generateDirectoryPath($config); $dir = $this->generateDirectoryPath($config);
$dh = opendir($dir); $dh = opendir($dir);
// See #49 (and above).
if (false === $dh) {
return false;
}
while (false !== ($filename = readdir($dh))) { while (false !== ($filename = readdir($dh))) {
if (empty($filename)) { if (empty($filename)) {
continue; continue;
@ -131,6 +143,8 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
unlink($dir . '/' . $filename); unlink($dir . '/' . $filename);
} }
} }
closedir($dh);
return true;
} }
/** /**
@ -186,11 +200,9 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
if ($result !== false) { if ($result !== false) {
// set permissions of the new file (no execute) // set permissions of the new file (no execute)
$chmod = $config->get('Cache.SerializerPermissions'); $chmod = $config->get('Cache.SerializerPermissions');
if (!$chmod) { if ($chmod !== null) {
$chmod = 0644; // invalid config or simpletest chmod($file, $chmod & 0666);
} }
$chmod = $chmod & 0666;
chmod($file, $chmod);
} }
return $result; return $result;
} }
@ -204,8 +216,10 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
{ {
$directory = $this->generateDirectoryPath($config); $directory = $this->generateDirectoryPath($config);
$chmod = $config->get('Cache.SerializerPermissions'); $chmod = $config->get('Cache.SerializerPermissions');
if (!$chmod) { if ($chmod === null) {
$chmod = 0755; // invalid config or simpletest // TODO: This races
if (is_dir($directory)) return true;
return mkdir($directory);
} }
if (!is_dir($directory)) { if (!is_dir($directory)) {
$base = $this->generateBaseDirectoryPath($config); $base = $this->generateBaseDirectoryPath($config);
@ -219,15 +233,16 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
} elseif (!$this->_testPermissions($base, $chmod)) { } elseif (!$this->_testPermissions($base, $chmod)) {
return false; return false;
} }
mkdir($directory, $chmod); if (!mkdir($directory, $chmod)) {
if (!$this->_testPermissions($directory, $chmod)) {
trigger_error( trigger_error(
'Base directory ' . $base . ' does not exist, 'Could not create directory ' . $directory . '',
please create or change using %Cache.SerializerPath',
E_USER_WARNING E_USER_WARNING
); );
return false; return false;
} }
if (!$this->_testPermissions($directory, $chmod)) {
return false;
}
} elseif (!$this->_testPermissions($directory, $chmod)) { } elseif (!$this->_testPermissions($directory, $chmod)) {
return false; return false;
} }
@ -256,7 +271,7 @@ class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCac
); );
return false; return false;
} }
if (function_exists('posix_getuid')) { if (function_exists('posix_getuid') && $chmod !== null) {
// POSIX system, we can give more specific advice // POSIX system, we can give more specific advice
if (fileowner($dir) === posix_getuid()) { if (fileowner($dir) === posix_getuid()) {
// we can chmod it ourselves // 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 * It will parse according to UTF-8 and return a valid UTF8 string, with
* non-SGML codepoints excluded. * 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 string $str The string to clean
* @param bool $force_php * @param bool $force_php
* @return string * @return string
@ -122,15 +130,12 @@ class HTMLPurifier_Encoder
* function that needs to be able to understand UTF-8 characters. * function that needs to be able to understand UTF-8 characters.
* As of right now, only smart lossless character encoding converters * As of right now, only smart lossless character encoding converters
* would need that, and I'm probably not going to implement them. * 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) public static function cleanUTF8($str, $force_php = false)
{ {
// UTF-8 validity is checked since PHP 4.3.5 // UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no // 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. // 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( 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', '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
$str $str
@ -255,6 +260,7 @@ class HTMLPurifier_Encoder
// 7F-9F is not strictly prohibited by XML, // 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it // but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
(0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
) )
) { ) {

View File

@ -16,6 +16,138 @@ class HTMLPurifier_EntityParser
*/ */
protected $_entity_lookup; 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. * Callback regex string for parsing entities.
* @type string * @type string
@ -144,7 +276,7 @@ class HTMLPurifier_EntityParser
$entity; $entity;
} else { } else {
return isset($this->_special_ent2dec[$matches[3]]) ? return isset($this->_special_ent2dec[$matches[3]]) ?
$this->_special_ent2dec[$matches[3]] : $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
$entity; $entity;
} }
} }

View File

@ -95,7 +95,10 @@ class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
if ($tidy !== null) { if ($tidy !== null) {
$this->_tidy = $tidy; $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; $style_blocks = $this->_styleMatches;
$this->_styleMatches = array(); // reset $this->_styleMatches = array(); // reset
$context->register('StyleBlocks', $style_blocks); // $context must not be reused $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); $attr = $this->generateAttributes($token->attr, $token->name);
if ($this->_flashCompat) { if ($this->_flashCompat) {
if ($token->name == "object") { if ($token->name == "object") {
$flash = new stdclass(); $flash = new stdClass();
$flash->attr = $token->attr; $flash->attr = $token->attr;
$flash->param = array(); $flash->param = array();
$this->_flashStack[] = $flash; $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')) { if ($config->get('HTML.TargetBlank')) {
$modules[] = '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 // merge in custom modules
$modules = array_merge($modules, $this->userModules); $modules = array_merge($modules, $this->userModules);

View File

@ -27,13 +27,18 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
if (strpos($token->data, '://') === false) { if (strpos($token->data, '://') === false) {
// our really quick heuristic failed, abort // our really quick heuristic failed, abort
// this may not work so well if we want to match things like // 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; return;
} }
// there is/are URL(s). Let's split the string: // there is/are URL(s). Let's split the string.
// Note: this regex is extremely permissive // We use this regex:
$bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); // 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(); $token = array();

View File

@ -46,6 +46,12 @@ class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
$this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
$this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
$this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate'); $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(); $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 * @type array
*/ */
protected $allowedParam = array( protected $allowedParam = array(
@ -43,7 +44,7 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
'movie' => true, 'movie' => true,
'flashvars' => true, 'flashvars' => true,
'src' => 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]) { $token->attr['name'] === $this->addParam[$n]) {
// keep token, and add to param stack // keep token, and add to param stack
$this->paramStack[$i][$n] = true; $this->paramStack[$i][$n] = true;
} elseif (isset($this->allowedParam[$n])) { } elseif (isset($this->allowedParam[strtolower($n)])) {
// keep token, don't do anything to it // keep token, don't do anything to it
// (could possibly check for duplicates here) // (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 { } else {
$token = false; $token = false;
} }

View File

@ -96,7 +96,7 @@ class HTMLPurifier_Lexer
break; break;
} }
if (class_exists('DOMDocument') && if (class_exists('DOMDocument', false) &&
method_exists('DOMDocument', 'loadHTML') && method_exists('DOMDocument', 'loadHTML') &&
!extension_loaded('domxml') !extension_loaded('domxml')
) { ) {
@ -169,21 +169,24 @@ class HTMLPurifier_Lexer
'&#x27;' => "'" '&#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. * Parses special entities into the proper characters.
* *
* This string will translate escaped versions of the special characters * This string will translate escaped versions of the special characters
* into the correct ones. * 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. * @param string $string String character data to be parsed.
* @return string Parsed character data. * @return string Parsed character data.
*/ */
public function parseData($string) public function parseData($string, $is_attr, $config)
{ {
// following functions require at least one character // following functions require at least one character
if ($string === '') { if ($string === '') {
@ -209,7 +212,15 @@ class HTMLPurifier_Lexer
} }
// hmm... now we have some uncommon entities. Use the callback. // 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; return $string;
} }
@ -323,7 +334,9 @@ class HTMLPurifier_Lexer
} }
// expand entities that aren't the big five // 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 // clean into wellformed UTF-8 string for an SGML context: this has
// to be done after entity expansion because the entities sometimes // to be done after entity expansion because the entities sometimes
@ -335,6 +348,13 @@ class HTMLPurifier_Lexer
$html = preg_replace('#<\?.+?\?>#s', '', $html); $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; return $html;
} }
@ -345,12 +365,17 @@ class HTMLPurifier_Lexer
public function extractBody($html) public function extractBody($html)
{ {
$matches = array(); $matches = array();
$result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches); $result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);
if ($result) { if ($result) {
return $matches[1]; // Make sure it's not in a comment
} else { $comment_start = strrpos($matches[1], '<!--');
return $html; $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); $doc->loadHTML($html);
restore_error_handler(); restore_error_handler();
$body = $doc->getElementsByTagName('html')->item(0)-> // <html>
getElementsByTagName('body')->item(0); // <body>
$div = $body->getElementsByTagName('div')->item(0); // <div>
$tokens = array(); $tokens = array();
$this->tokenizeDOM( $this->tokenizeDOM($div, $tokens, $config);
$doc->getElementsByTagName('html')->item(0)-> // <html> // If the div has a sibling, that means we tripped across
getElementsByTagName('body')->item(0), // <body> // a premature </div> tag. So remove the div we parsed,
$tokens // 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; return $tokens;
} }
@ -88,7 +96,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
* @return HTMLPurifier_Token of node appended to previously passed tokens. * @return HTMLPurifier_Token of node appended to previously passed tokens.
*/ */
protected function tokenizeDOM($node, &$tokens) protected function tokenizeDOM($node, &$tokens, $config)
{ {
$level = 0; $level = 0;
$nodes = array($level => new HTMLPurifier_Queue(array($node))); $nodes = array($level => new HTMLPurifier_Queue(array($node)));
@ -97,7 +105,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
while (!$nodes[$level]->isEmpty()) { while (!$nodes[$level]->isEmpty()) {
$node = $nodes[$level]->shift(); // FIFO $node = $nodes[$level]->shift(); // FIFO
$collect = $level > 0 ? true : false; $collect = $level > 0 ? true : false;
$needEndingTag = $this->createStartNode($node, $tokens, $collect); $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
if ($needEndingTag) { if ($needEndingTag) {
$closingNodes[$level][] = $node; $closingNodes[$level][] = $node;
} }
@ -127,7 +135,7 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
* @return bool if the token needs an endtoken * @return bool if the token needs an endtoken
* @todo data and tagName properties don't seem to exist in DOMNode? * @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, // intercept non element nodes. WE MUST catch all of them,
// but we're not getting the character reference nodes because // 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; return false;
} elseif ($node->nodeType === XML_COMMENT_NODE) { } elseif ($node->nodeType === XML_COMMENT_NODE) {
// this is code is only invoked for comments in script/style in versions // 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 * @param HTMLPurifier_Context $context
* @return string * @return string
*/ */
protected function wrapHTML($html, $config, $context) protected function wrapHTML($html, $config, $context, $use_div = true)
{ {
$def = $config->getDefinition('HTML'); $def = $config->getDefinition('HTML');
$ret = ''; $ret = '';
@ -271,7 +279,11 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
$ret .= '<html><head>'; $ret .= '<html><head>';
$ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />'; $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
// No protection if $html contains a stray </div>! // 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; 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 // We are not inside tag and there still is another tag to parse
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
$this->parseData( $this->parseText(
substr( substr(
$html, $html,
$cursor, $cursor,
$position_next_lt - $cursor $position_next_lt - $cursor
) ), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -154,11 +154,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
// Create Text of rest of string // Create Text of rest of string
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
$this->parseData( $this->parseText(
substr( substr(
$html, $html,
$cursor $cursor
) ), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -324,8 +324,8 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
$token = new $token = new
HTMLPurifier_Token_Text( HTMLPurifier_Token_Text(
'<' . '<' .
$this->parseData( $this->parseText(
substr($html, $cursor) substr($html, $cursor), $config
) )
); );
if ($maintain_line_numbers) { if ($maintain_line_numbers) {
@ -429,7 +429,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) { if ($value === false) {
$value = ''; $value = '';
} }
return array($key => $this->parseData($value)); return array($key => $this->parseAttr($value, $config));
} }
// setup loop environment // setup loop environment
@ -518,7 +518,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
if ($value === false) { if ($value === false) {
$value = ''; $value = '';
} }
$array[$key] = $this->parseData($value); $array[$key] = $this->parseAttr($value, $config);
$cursor++; $cursor++;
} else { } else {
// boolattr // boolattr

View File

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

View File

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

View File

@ -165,7 +165,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if (empty($zipper->front)) break; if (empty($zipper->front)) break;
$token = $zipper->prev($token); $token = $zipper->prev($token);
// indicate that other injectors should not process this 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]); unset($token->skip[$i]);
$token->rewind = $i; $token->rewind = $i;
if ($token instanceof HTMLPurifier_Token_Start) { if ($token instanceof HTMLPurifier_Token_Start) {
@ -210,6 +210,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($token instanceof HTMLPurifier_Token_Text) { if ($token instanceof HTMLPurifier_Token_Text) {
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -367,6 +368,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($ok) { if ($ok) {
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -422,6 +424,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
$token->start = $current_parent; $token->start = $current_parent;
foreach ($this->injectors as $i => $injector) { foreach ($this->injectors as $i => $injector) {
if (isset($token->skip[$i])) { if (isset($token->skip[$i])) {
// See Note [Injector skips]
continue; continue;
} }
if ($token->rewind !== null && $token->rewind !== $i) { if ($token->rewind !== null && $token->rewind !== $i) {
@ -534,12 +537,17 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
*/ */
protected function processToken($token, $injector = -1) 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 // normalize forms of token
if (is_object($token)) { if (is_object($token)) {
$token = array(1, $token); $tmp = $token;
$token = array(1, $tmp);
} }
if (is_int($token)) { if (is_int($token)) {
$token = array($token); $tmp = $token;
$token = array($tmp);
} }
if ($token === false) { if ($token === false) {
$token = array(1); $token = array(1);
@ -561,7 +569,12 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
list($old, $r) = $this->zipper->splice($this->token, $delete, $token); list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
if ($injector > -1) { 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(); $oldskip = isset($old[0]) ? $old[0]->skip : array();
foreach ($token as $object) { foreach ($token as $object) {
$object->skip = $oldskip; $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 // vim: et sw=4 sts=4

View File

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

View File

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

View File

@ -79,9 +79,18 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
} else { } else {
$raw_data = $data; $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 // XXX probably want to refactor this into a general mechanism
// for filtering arbitrary content types // 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); file_put_contents($file, $raw_data);
if (function_exists('exif_imagetype')) { if (function_exists('exif_imagetype')) {
$image_code = exif_imagetype($file); $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 * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @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 * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
@ -21,7 +21,9 @@
/** /**
* A class representing an URL as per RFC 3986. * 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 * Exception class for HTTP_Request2 package
@ -35,7 +37,7 @@ require_once 'HTTP/Request2/Exception.php';
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @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://pear.php.net/package/HTTP_Request2
* @link http://tools.ietf.org/html/rfc2616#section-5 * @link http://tools.ietf.org/html/rfc2616#section-5
*/ */
@ -213,7 +215,7 @@ class HTTP_Request2 implements SplSubject
$this->setMethod($method); $this->setMethod($method);
} }
$this->setHeader( $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() '(http://pear.php.net/package/http_request2) PHP/' . phpversion()
); );
} }
@ -794,6 +796,11 @@ class HTTP_Request2 implements SplSubject
* encoded by Content-Encoding</li> * encoded by Content-Encoding</li>
* <li>'receivedBody' - after receiving the complete response * <li>'receivedBody' - after receiving the complete response
* body, data is HTTP_Request2_Response object</li> * 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> * </ul>
* Different adapters may not send all the event types. Mock adapter does * Different adapters may not send all the event types. Mock adapter does
* not send any events to the observers. * not send any events to the observers.
@ -1022,7 +1029,7 @@ class HTTP_Request2 implements SplSubject
} }
// (deprecated) mime_content_type function available // (deprecated) mime_content_type function available
if (empty($info) && function_exists('mime_content_type')) { 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; return empty($info)? 'application/octet-stream': $info;
} }

View File

@ -13,7 +13,7 @@
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @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 * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
@ -34,7 +34,7 @@ require_once 'HTTP/Request2/Response.php';
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @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://pear.php.net/package/HTTP_Request2
*/ */
abstract class HTTP_Request2_Adapter abstract class HTTP_Request2_Adapter

View File

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

View File

@ -13,7 +13,7 @@
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @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 * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
@ -44,7 +44,7 @@ require_once 'HTTP/Request2/Adapter.php';
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @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://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter

View File

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

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