diff --git a/actions/deleteuser.php b/actions/deleteuser.php
index 6b74575ab4..6e0c6ebf7f 100644
--- a/actions/deleteuser.php
+++ b/actions/deleteuser.php
@@ -27,9 +27,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET') && !defined('LACONICA')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Delete a user
@@ -44,33 +42,30 @@ class DeleteuserAction extends ProfileFormAction
{
var $user = null;
- /**
- * Take arguments for running
- *
- * @param array $args $_REQUEST args
- *
- * @return boolean success flag
- */
- function prepare($args)
+ function prepare(array $args=array())
{
if (!parent::prepare($args)) {
return false;
}
- $cur = common_current_user();
+ assert($this->scoped instanceof Profile);
- assert(!empty($cur)); // checked by parent
-
- if (!$cur->hasRight(Right::DELETEUSER)) {
+ if (!$this->scoped->hasRight(Right::DELETEUSER)) {
// TRANS: Client error displayed when trying to delete a user without having the right to delete users.
- $this->clientError(_('You cannot delete users.'));
+ throw new AuthorizationException(_('You cannot delete users.'));
}
- $this->user = User::getKV('id', $this->profile->id);
-
- if (empty($this->user)) {
+ try {
+ $this->user = $this->profile->getUser();
+ } catch (NoSuchUserException $e) {
// TRANS: Client error displayed when trying to delete a non-local user.
- $this->clientError(_('You can only delete local users.'));
+ throw new ClientException(_('You can only delete local users.'));
+ }
+
+ // Only administrators can delete other privileged users (such as others who have the right to silence).
+ if ($this->profile->isPrivileged() && !$this->scoped->hasRole(Profile_role::ADMINISTRATOR)) {
+ // TRANS: Client error displayed when trying to delete a user that has been granted moderation privileges
+ throw new AuthorizationException(_('You cannot delete other privileged users.'));
}
return true;
diff --git a/actions/foaf.php b/actions/foaf.php
index 260388ba44..bf9cf1b957 100644
--- a/actions/foaf.php
+++ b/actions/foaf.php
@@ -90,7 +90,7 @@ class FoafAction extends ManagedAction
// Would be nice to tell if they were a Person or not (e.g. a #person usertag?)
$this->elementStart('Agent', array('rdf:about' => $this->user->getUri()));
- if ($this->user->email) {
+ if (common_config('foaf', 'mbox_sha1sum') && $this->user->email) {
$this->element('mbox_sha1sum', null, sha1('mailto:' . $this->user->email));
}
if ($this->profile->fullname) {
diff --git a/actions/newnotice.php b/actions/newnotice.php
index 298361d600..6ee2092061 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -190,6 +190,9 @@ class NewnoticeAction extends FormAction
// and maybe even directly save whether they're local or not!
$act->context->attention = common_get_attentions($content, $this->scoped, $parent);
+ // $options gets filled with possible scoping settings
+ ToSelector::fillActivity($this, $act, $options);
+
$actobj = new ActivityObject();
$actobj->type = ActivityObject::NOTE;
$actobj->content = common_render_content($content, $this->scoped, $parent);
diff --git a/actions/profilesettings.php b/actions/profilesettings.php
index 5804f21ca5..a1d947530c 100644
--- a/actions/profilesettings.php
+++ b/actions/profilesettings.php
@@ -110,7 +110,10 @@ class ProfilesettingsAction extends SettingsAction
$this->elementStart('li');
// TRANS: Field label in form for profile settings.
$this->input('fullname', _('Full name'),
- $this->trimmed('fullname') ?: $this->scoped->getFullname());
+ $this->trimmed('fullname') ?: $this->scoped->getFullname(),
+ // TRANS: Instructions for full name text field on profile settings
+ _('A full name is required, if empty it will be set to your nickname.'),
+ null, true);
$this->elementEnd('li');
$this->elementStart('li');
// TRANS: Field label in form for profile settings.
@@ -204,13 +207,15 @@ class ProfilesettingsAction extends SettingsAction
(empty($user->subscribe_policy)) ? User::SUBSCRIBE_POLICY_OPEN : $user->subscribe_policy);
$this->elementEnd('li');
}
- $this->elementStart('li');
- $this->checkbox('private_stream',
- // TRANS: Checkbox label in profile settings.
- _('Make updates visible only to my followers'),
- ($this->arg('private_stream')) ?
- $this->boolean('private_stream') : $user->private_stream);
- $this->elementEnd('li');
+ if (common_config('profile', 'allowprivate') || $user->private_stream) {
+ $this->elementStart('li');
+ $this->checkbox('private_stream',
+ // TRANS: Checkbox label in profile settings.
+ _('Make updates visible only to my followers'),
+ ($this->arg('private_stream')) ?
+ $this->boolean('private_stream') : $user->private_stream);
+ $this->elementEnd('li');
+ }
$this->elementEnd('ul');
// TRANS: Button to save input in profile settings.
$this->submit('save', _m('BUTTON','Save'));
@@ -252,7 +257,6 @@ class ProfilesettingsAction extends SettingsAction
$location = $this->trimmed('location');
$autosubscribe = $this->booleanintstring('autosubscribe');
$subscribe_policy = $this->trimmed('subscribe_policy');
- $private_stream = $this->booleanintstring('private_stream');
$language = $this->trimmed('language');
$timezone = $this->trimmed('timezone');
$tagstring = $this->trimmed('tags');
@@ -307,6 +311,15 @@ class ProfilesettingsAction extends SettingsAction
$user = $this->scoped->getUser();
$user->query('BEGIN');
+ // Only allow setting private_stream if site policy allows it
+ // (or user already _has_ a private stream, then you can unset it)
+ if (common_config('profile', 'allowprivate') || $user->private_stream) {
+ $private_stream = $this->booleanintstring('private_stream');
+ } else {
+ // if not allowed, we set to the existing value
+ $private_stream = $user->private_stream;
+ }
+
// $user->nickname is updated through Profile->update();
// XXX: XOR
@@ -345,7 +358,7 @@ class ProfilesettingsAction extends SettingsAction
$this->scoped->nickname = $nickname;
$this->scoped->profileurl = common_profile_url($this->scoped->getNickname());
}
- $this->scoped->fullname = $fullname;
+ $this->scoped->fullname = (mb_strlen($fullname)>0 ? $fullname : $this->scoped->nickname);
$this->scoped->homepage = $homepage;
$this->scoped->bio = $bio;
$this->scoped->location = $location;
diff --git a/actions/shownotice.php b/actions/shownotice.php
index 64cf38afa7..b2385ec1d7 100644
--- a/actions/shownotice.php
+++ b/actions/shownotice.php
@@ -74,6 +74,7 @@ class ShownoticeAction extends ManagedAction
}
$this->notice = $this->getNotice();
+ $this->target = $this->notice;
if (!$this->notice->inScope($this->scoped)) {
// TRANS: Client exception thrown when trying a view a notice the user has no access to.
@@ -213,12 +214,24 @@ class ShownoticeAction extends ManagedAction
{
}
- /**
- * Don't show aside
- *
- * @return void
- */
- function showAside() {
+ function getFeeds()
+ {
+ return array(new Feed(Feed::JSON,
+ common_local_url('ApiStatusesShow',
+ array(
+ 'id' => $this->target->getID(),
+ 'format' => 'json')),
+ // TRANS: Title for link to single notice representation.
+ // TRANS: %s is a user nickname.
+ sprintf(_('Single notice (JSON)'))),
+ new Feed(Feed::ATOM,
+ common_local_url('ApiStatusesShow',
+ array(
+ 'id' => $this->target->getID(),
+ 'format' => 'atom')),
+ // TRANS: Title for link to notice feed.
+ // TRANS: %s is a user nickname.
+ sprintf(_('Single notice (Atom)'))));
}
/**
diff --git a/actions/silence.php b/actions/silence.php
index 6a4f84deb9..dccaf70a37 100644
--- a/actions/silence.php
+++ b/actions/silence.php
@@ -27,9 +27,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Silence a user.
@@ -42,45 +40,11 @@ if (!defined('STATUSNET')) {
*/
class SilenceAction extends ProfileFormAction
{
- /**
- * Check parameters
- *
- * @param array $args action arguments (URL, GET, POST)
- *
- * @return boolean success flag
- */
- function prepare($args)
- {
- if (!parent::prepare($args)) {
- return false;
- }
-
- $cur = common_current_user();
-
- assert(!empty($cur)); // checked by parent
-
- if (!$cur->hasRight(Right::SILENCEUSER)) {
- // TRANS: Client error displayed trying to silence a user on a site where the feature is not enabled.
- $this->clientError(_('You cannot silence users on this site.'));
- }
-
- assert(!empty($this->profile)); // checked by parent
-
- if ($this->profile->isSilenced()) {
- // TRANS: Client error displayed trying to silence an already silenced user.
- $this->clientError(_('User is already silenced.'));
- }
-
- return true;
- }
-
- /**
- * Silence a user.
- *
- * @return void
- */
function handlePost()
{
- $this->profile->silence();
+ assert($this->scoped instanceof Profile);
+ assert($this->profile instanceof Profile);
+
+ $this->profile->silenceAs($this->scoped);
}
}
diff --git a/actions/unsilence.php b/actions/unsilence.php
index c01c141b1c..f1305373df 100644
--- a/actions/unsilence.php
+++ b/actions/unsilence.php
@@ -27,12 +27,10 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
- * Silence a user.
+ * Unsilence a user.
*
* @category Action
* @package StatusNet
@@ -42,45 +40,11 @@ if (!defined('STATUSNET')) {
*/
class UnsilenceAction extends ProfileFormAction
{
- /**
- * Check parameters
- *
- * @param array $args action arguments (URL, GET, POST)
- *
- * @return boolean success flag
- */
- function prepare($args)
- {
- if (!parent::prepare($args)) {
- return false;
- }
-
- $cur = common_current_user();
-
- assert(!empty($cur)); // checked by parent
-
- if (!$cur->hasRight(Right::SILENCEUSER)) {
- // TRANS: Client error on page to unsilence a user when the feature is not enabled.
- $this->clientError(_('You cannot silence users on this site.'));
- }
-
- assert(!empty($this->profile)); // checked by parent
-
- if (!$this->profile->isSilenced()) {
- // TRANS: Client error on page to unsilence a user when the to be unsilenced user has not been silenced.
- $this->clientError(_('User is not silenced.'));
- }
-
- return true;
- }
-
- /**
- * Silence a user.
- *
- * @return void
- */
function handlePost()
{
- $this->profile->unsilence();
+ assert($this->scoped instanceof Profile);
+ assert($this->profile instanceof Profile);
+
+ $this->profile->unsilenceAs($this->scoped);
}
}
diff --git a/classes/Managed_DataObject.php b/classes/Managed_DataObject.php
index 31ae6614fb..0857bb11f6 100644
--- a/classes/Managed_DataObject.php
+++ b/classes/Managed_DataObject.php
@@ -412,6 +412,60 @@ abstract class Managed_DataObject extends Memcached_DataObject
return intval($this->id);
}
+ /**
+ * WARNING: Only use this on Profile and Notice. We should probably do
+ * this with traits/"implements" or whatever, but that's over the top
+ * right now, I'm just throwing this in here to avoid code duplication
+ * in Profile and Notice classes.
+ */
+ public function getAliases()
+ {
+ return array_keys($this->getAliasesWithIDs());
+ }
+
+ public function getAliasesWithIDs()
+ {
+ $aliases = array();
+ $aliases[$this->getUri()] = $this->getID();
+
+ try {
+ $aliases[$this->getUrl()] = $this->getID();
+ } catch (InvalidUrlException $e) {
+ // getUrl failed because no valid URL could be returned, just ignore it
+ }
+
+ if (common_config('fix', 'fancyurls')) {
+ /**
+ * Here we add some hacky hotfixes for remote lookups that have been taught the
+ * (at least now) wrong URI but it's still obviously the same user. Such as:
+ * - https://site.example/user/1 even if the client requests https://site.example/index.php/user/1
+ * - https://site.example/user/1 even if the client requests https://site.example//index.php/user/1
+ * - https://site.example/index.php/user/1 even if the client requests https://site.example/user/1
+ * - https://site.example/index.php/user/1 even if the client requests https://site.example///index.php/user/1
+ */
+ foreach ($aliases as $alias=>$id) {
+ try {
+ // get a "fancy url" version of the alias, even without index.php/
+ $alt_url = common_fake_local_fancy_url($alias);
+ // store this as well so remote sites can be sure we really are the same profile
+ $aliases[$alt_url] = $id;
+ } catch (Exception $e) {
+ // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+ }
+
+ try {
+ // get a non-"fancy url" version of the alias, i.e. add index.php/
+ $alt_url = common_fake_local_nonfancy_url($alias);
+ // store this as well so remote sites can be sure we really are the same profile
+ $aliases[$alt_url] = $id;
+ } catch (Exception $e) {
+ // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+ }
+ }
+ }
+ return $aliases;
+ }
+
// 'update' won't write key columns, so we have to do it ourselves.
// This also automatically calls "update" _before_ it sets the keys.
// FIXME: This only works with single-column primary keys so far! Beware!
diff --git a/classes/Profile.php b/classes/Profile.php
index 875ad9ade1..7aae98fb5f 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -1174,6 +1174,22 @@ class Profile extends Managed_DataObject
}
}
+ function silenceAs(Profile $actor)
+ {
+ if (!$actor->hasRight(Right::SILENCEUSER)) {
+ throw new AuthorizationException(_('You cannot silence users on this site.'));
+ }
+ // Only administrators can silence other privileged users (such as others who have the right to silence).
+ if ($this->isPrivileged() && !$actor->hasRole(Profile_role::ADMINISTRATOR)) {
+ throw new AuthorizationException(_('You cannot silence other privileged users.'));
+ }
+ if ($this->isSilenced()) {
+ // TRANS: Client error displayed trying to silence an already silenced user.
+ throw new AlreadyFulfilledException(_('User is already silenced.'));
+ }
+ return $this->silence();
+ }
+
function unsilence()
{
$this->revokeRole(Profile_role::SILENCED);
@@ -1182,6 +1198,19 @@ class Profile extends Managed_DataObject
}
}
+ function unsilenceAs(Profile $actor)
+ {
+ if (!$actor->hasRight(Right::SILENCEUSER)) {
+ // TRANS: Client error displayed trying to unsilence a user when the user does not have the right.
+ throw new AuthorizationException(_('You cannot unsilence users on this site.'));
+ }
+ if (!$this->isSilenced()) {
+ // TRANS: Client error displayed trying to unsilence a user when the target user has not been silenced.
+ throw new AlreadyFulfilledException(_('User is not silenced.'));
+ }
+ return $this->unsilence();
+ }
+
function flushVisibility()
{
// Get all notices
@@ -1192,6 +1221,22 @@ class Profile extends Managed_DataObject
}
}
+ public function isPrivileged()
+ {
+ // TODO: An Event::handle so plugins can report if users are privileged.
+ // The ModHelper is the only one I care about when coding this, and that
+ // can be tested with Right::SILENCEUSER which I do below:
+ switch (true) {
+ case $this->hasRight(Right::SILENCEUSER):
+ case $this->hasRole(Profile_role::MODERATOR):
+ case $this->hasRole(Profile_role::ADMINISTRATOR):
+ case $this->hasRole(Profile_role::OWNER):
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Does this user have the right to do X?
*
@@ -1628,6 +1673,15 @@ class Profile extends Managed_DataObject
return $profile;
}
+ static function ensureCurrent()
+ {
+ $profile = self::current();
+ if (!$profile instanceof Profile) {
+ throw new AuthorizationException('A currently scoped profile is required.');
+ }
+ return $profile;
+ }
+
/**
* Magic function called at serialize() time.
*
diff --git a/classes/User.php b/classes/User.php
index c232b2b12f..40e1a1b644 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -140,6 +140,16 @@ class User extends Managed_DataObject
return $this->uri;
}
+ static function getByUri($uri)
+ {
+ $user = new User();
+ $user->uri = $uri;
+ if (!$user->find(true)) {
+ throw new NoResultException($user);
+ }
+ return $user;
+ }
+
public function getNickname()
{
return $this->getProfile()->getNickname();
diff --git a/lib/apiauthaction.php b/lib/apiauthaction.php
index 0e81082c35..a3deccd3da 100644
--- a/lib/apiauthaction.php
+++ b/lib/apiauthaction.php
@@ -85,8 +85,10 @@ class ApiAuthAction extends ApiAction
// NOTE: $this->scoped and $this->auth_user has to get set in
// prepare(), not handle(), as subclasses use them in prepares.
- // Allow regular login session
- if (common_logged_in()) {
+ // Allow regular login session, but we have to double-check the
+ // HTTP_REFERER value to avoid cross domain POSTing since the API
+ // doesn't use the "token" form field.
+ if (common_logged_in() && common_local_referer()) {
$this->scoped = Profile::current();
$this->auth_user = $this->scoped->getUser();
if (!$this->auth_user->hasRight(Right::API)) {
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
index 4d4b451167..0ce19b0b1e 100644
--- a/lib/attachmentlist.php
+++ b/lib/attachmentlist.php
@@ -85,6 +85,12 @@ class AttachmentList extends Widget
return 0;
}
+ if ($this->notice->getProfile()->isSilenced()) {
+ // TRANS: Message for inline attachments list in notices when the author has been silenced.
+ $this->element('div', ['class'=>'error'], _('Attachments are hidden because this profile has been silenced.'));
+ return 0;
+ }
+
$this->showListStart();
foreach ($attachments as $att) {
diff --git a/lib/conversationnoticestream.php b/lib/conversationnoticestream.php
index 9c32159d42..21b2d7f0be 100644
--- a/lib/conversationnoticestream.php
+++ b/lib/conversationnoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Notice stream for a conversation
@@ -96,9 +92,7 @@ class RawConversationNoticeStream extends NoticeStream
$notice->limit($offset, $limit);
}
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
// ORDER BY
// currently imitates the previously used "_reverseChron" sorting
diff --git a/lib/default.php b/lib/default.php
index f90892e169..f8ce3bd4fe 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -81,6 +81,9 @@ $default =
'log_queries' => false, // true to log all DB queries
'log_slow_queries' => 0, // if set, log queries taking over N seconds
'mysql_foreign_keys' => false), // if set, enables experimental foreign key support on MySQL
+ 'fix' =>
+ array('fancyurls' => true, // makes sure aliases in WebFinger etc. are not f'd by index.php/ URLs
+ ),
'syslog' =>
array('appname' => 'statusnet', # for syslog
'priority' => 'debug', # XXX: currently ignored
@@ -129,6 +132,7 @@ $default =
array('banned' => array(),
'biolimit' => null,
'changenick' => false,
+ 'allowprivate' => false, // whether to allow setting stream to private ("only followers can read")
'backup' => false, // can cause DoS, so should be done via CLI
'restore' => false,
'delete' => false,
@@ -141,6 +145,10 @@ $default =
'path' => $_path . '/avatar/',
'ssl' => null,
'maxsize' => 300),
+ 'foaf' =>
+ array(
+ 'mbox_sha1sum' => false,
+ ),
'public' =>
array('localonly' => false,
'blacklist' => array(),
@@ -233,6 +241,7 @@ $default =
'application/vnd.oasis.opendocument.text-web' => 'oth',
'application/pdf' => 'pdf',
'application/zip' => 'zip',
+ 'application/xml' => 'xml',
'image/png' => 'png',
'image/jpeg' => 'jpg',
'image/gif' => 'gif',
@@ -289,6 +298,7 @@ $default =
),
'notice' =>
array('contentlimit' => null,
+ 'allowprivate' => false, // whether to allow users to "check the padlock" to publish notices available for their subscribers.
'defaultscope' => null, // null means 1 if site/private, 0 otherwise
'hidespam' => true), // Whether to hide silenced users from timelines
'message' =>
diff --git a/lib/fullnoticestream.php b/lib/fullnoticestream.php
new file mode 100644
index 0000000000..2f83007469
--- /dev/null
+++ b/lib/fullnoticestream.php
@@ -0,0 +1,11 @@
+target = $target;
- $this->unselectVerbs = array(ActivityVerb::DELETE);
}
/**
@@ -119,12 +119,9 @@ class RawInboxNoticeStream extends NoticeStream
if (!empty($max_id)) {
$notice->whereAdd(sprintf('notice.id <= %d', $max_id));
}
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
- }
+
+ self::filterVerbs($notice, $this->selectVerbs);
+
$notice->limit($offset, $limit);
// notice.id will give us even really old posts, which were
// recently imported. For example if a remote instance had
diff --git a/lib/networkpublicnoticestream.php b/lib/networkpublicnoticestream.php
index 3320b7cd5a..bd4da5d075 100644
--- a/lib/networkpublicnoticestream.php
+++ b/lib/networkpublicnoticestream.php
@@ -23,7 +23,7 @@ class NetworkPublicNoticeStream extends ScopingNoticeStream
* @link http://status.net/
*/
-class RawNetworkPublicNoticeStream extends NoticeStream
+class RawNetworkPublicNoticeStream extends FullNoticeStream
{
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
@@ -46,9 +46,7 @@ class RawNetworkPublicNoticeStream extends NoticeStream
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$ids = array();
diff --git a/lib/nickname.php b/lib/nickname.php
index 1ed0abbe78..2dd08efc3f 100644
--- a/lib/nickname.php
+++ b/lib/nickname.php
@@ -180,18 +180,24 @@ class Nickname
// All directory and file names in site root should be blacklisted
$d = dir(INSTALLDIR);
while (false !== ($entry = $d->read())) {
- $paths[] = $entry;
+ $paths[$entry] = true;
}
$d->close();
// All top level names in the router should be blacklisted
$router = Router::get();
- foreach (array_keys($router->m->getPaths()) as $path) {
- if (preg_match('/^\/(.*?)[\/\?]/',$path,$matches)) {
- $paths[] = $matches[1];
+ foreach ($router->m->getPaths() as $path) {
+ if (preg_match('/^([^\/\?]+)[\/\?]/',$path,$matches) && isset($matches[1])) {
+ $paths[$matches[1]] = true;
}
}
- return in_array($str, $paths);
+
+ // FIXME: this assumes the 'path' is in the first-level directory, though common it's not certain
+ foreach (['avatar', 'attachments'] as $cat) {
+ $paths[basename(common_config($cat, 'path'))] = true;
+ }
+
+ return in_array($str, array_keys($paths));
}
/**
diff --git a/lib/noticestream.php b/lib/noticestream.php
index 01c5ee4a72..2b04a89ca4 100644
--- a/lib/noticestream.php
+++ b/lib/noticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Class for notice streams
@@ -46,16 +42,15 @@ if (!defined('STATUSNET')) {
*/
abstract class NoticeStream
{
- protected $selectVerbs = null; // must be set to array
- protected $unselectVerbs = null; // must be set to array
+ protected $selectVerbs = array(ActivityVerb::POST => true,
+ ActivityVerb::SHARE => true);
public function __construct()
{
- if ($this->selectVerbs === null) {
- $this->selectVerbs = array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true));
- }
- if ($this->unselectVerbs === null) {
- $this->unselectVerbs = array(ActivityVerb::DELETE);
+ foreach ($this->selectVerbs as $key=>$val) {
+ // to avoid database inconsistency issues we select both relative and absolute verbs
+ $this->selectVerbs[ActivityUtils::resolveUri($key)] = $val;
+ $this->selectVerbs[ActivityUtils::resolveUri($key, true)] = $val;
}
}
@@ -74,4 +69,21 @@ abstract class NoticeStream
{
return Notice::multiGet('id', $ids);
}
+
+ static function filterVerbs(Notice $notice, array $selectVerbs)
+ {
+ $filter = array_keys(array_filter($selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $notice->whereAddIn('verb', $filter, $notice->columnType('verb'));
+ }
+
+ $filter = array_keys(array_filter($selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $notice->whereAddIn('!verb', $filter, $notice->columnType('verb'));
+ }
+
+ unset($filter);
+ }
}
diff --git a/lib/profileformaction.php b/lib/profileformaction.php
index 9ace6676c3..1e00e6f12b 100644
--- a/lib/profileformaction.php
+++ b/lib/profileformaction.php
@@ -101,7 +101,11 @@ class ProfileFormAction extends RedirectingAction
parent::handle($args);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- $this->handlePost();
+ try {
+ $this->handlePost();
+ } catch (AlreadyFulfilledException $e) {
+ // 'tis alright
+ }
$this->returnToPrevious();
}
}
diff --git a/lib/profilenoticestream.php b/lib/profilenoticestream.php
index a31fb585d1..7ff4163fcb 100644
--- a/lib/profilenoticestream.php
+++ b/lib/profilenoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of notices by a profile
@@ -134,12 +130,7 @@ class RawProfileNoticeStream extends NoticeStream
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$notice->orderBy('created DESC, id DESC');
diff --git a/lib/publicnoticestream.php b/lib/publicnoticestream.php
index 757c2164c0..4a16cbd235 100644
--- a/lib/publicnoticestream.php
+++ b/lib/publicnoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Public stream
@@ -66,7 +62,7 @@ class PublicNoticeStream extends ScopingNoticeStream
* @link http://status.net/
*/
-class RawPublicNoticeStream extends NoticeStream
+class RawPublicNoticeStream extends FullNoticeStream
{
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
@@ -87,9 +83,7 @@ class RawPublicNoticeStream extends NoticeStream
Notice::addWhereSinceId($notice, $since_id);
Notice::addWhereMaxId($notice, $max_id);
- if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
+ self::filterVerbs($notice, $this->selectVerbs);
$ids = array();
diff --git a/lib/replynoticestream.php b/lib/replynoticestream.php
index 9fea5cac1e..9eb188d54d 100644
--- a/lib/replynoticestream.php
+++ b/lib/replynoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of mentions of me
@@ -92,8 +88,20 @@ class RawReplyNoticeStream extends NoticeStream
Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'reply.modified');
if (!empty($this->selectVerbs)) {
+ // this is a little special since we have to join in Notice
$reply->joinAdd(array('notice_id', 'notice:id'));
- $reply->whereAddIn('notice.verb', $this->selectVerbs, 'string');
+
+ $filter = array_keys(array_filter($this->selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $reply->whereAddIn('notice.verb', $filter, 'string');
+ }
+
+ $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $reply->whereAddIn('!notice.verb', $filter, 'string');
+ }
}
$reply->orderBy('reply.modified DESC, reply.notice_id DESC');
diff --git a/lib/tagnoticestream.php b/lib/tagnoticestream.php
index d24907fa7e..28f5d0e824 100644
--- a/lib/tagnoticestream.php
+++ b/lib/tagnoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Stream of notices with a given tag
@@ -90,13 +86,22 @@ class RawTagNoticeStream extends NoticeStream
Notice::addWhereMaxId($nt, $max_id, 'notice_id');
if (!empty($this->selectVerbs)) {
- $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
- }
- if (!empty($this->unselectVerbs)) {
- $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
+ $nt->joinAdd(array('notice_id', 'notice:id'));
+
+ $filter = array_keys(array_filter($this->selectVerbs));
+ if (!empty($filter)) {
+ // include verbs in selectVerbs with values that equate to true
+ $nt->whereAddIn('notice.verb', $filter, 'string');
+ }
+
+ $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+ if (!empty($filter)) {
+ // exclude verbs in selectVerbs with values that equate to false
+ $nt->whereAddIn('!notice.verb', $filter, 'string');
+ }
}
- $nt->orderBy('created DESC, notice_id DESC');
+ $nt->orderBy('notice.created DESC, notice_id DESC');
if (!is_null($offset)) {
$nt->limit($offset, $limit);
diff --git a/lib/toselector.php b/lib/toselector.php
index 153d9001b5..7a959ff8b5 100644
--- a/lib/toselector.php
+++ b/lib/toselector.php
@@ -80,44 +80,59 @@ class ToSelector extends Widget
function show()
{
$choices = array();
- $default = 'public:site';
-
- if (!common_config('site', 'private')) {
- // TRANS: Option in drop-down of potential addressees.
- $choices['public:everyone'] = _m('SENDTO','Everyone');
- $default = 'public:everyone';
- }
- // TRANS: Option in drop-down of potential addressees.
- // TRANS: %s is a StatusNet sitename.
- $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+ $default = common_config('site', 'private') ? 'public:site' : 'public:everyone';
$groups = $this->user->getGroups();
while ($groups instanceof User_group && $groups->fetch()) {
- $value = 'group:'.$groups->id;
+ $value = 'group:'.$groups->getID();
if (($this->to instanceof User_group) && $this->to->id == $groups->id) {
$default = $value;
}
- $choices[$value] = $groups->getBestName();
+ $choices[$value] = "!{$groups->getNickname()} [{$groups->getBestName()}]";
}
// Add subscribed users to dropdown menu
$users = $this->user->getSubscribed();
while ($users->fetch()) {
- $value = 'profile:'.$users->id;
- if ($this->user->streamNicknames()) {
- $choices[$value] = $users->getNickname();
- } else {
- $choices[$value] = $users->getBestName();
+ $value = 'profile:'.$users->getID();
+ try {
+ $choices[$value] = substr($users->getAcctUri(), 5) . " [{$users->getBestName()}]";
+ } catch (ProfileNoAcctUriException $e) {
+ $choices[$value] = "[?@?] " . $e->profile->getBestName();
}
}
if ($this->to instanceof Profile) {
- $value = 'profile:'.$this->to->id;
+ $value = 'profile:'.$this->to->getID();
$default = $value;
- $choices[$value] = $this->to->getBestName();
+ try {
+ $choices[$value] = substr($this->to->getAcctUri(), 5) . " [{$this->to->getBestName()}]";
+ } catch (ProfileNoAcctUriException $e) {
+ $choices[$value] = "[?@?] " . $e->profile->getBestName();
+ }
}
+ // alphabetical order
+ asort($choices);
+
+ // Reverse so we can add entries at the end (can't unshift with a key)
+ $choices = array_reverse($choices);
+
+ if (common_config('notice', 'allowprivate')) {
+ // TRANS: Option in drop-down of potential addressees.
+ // TRANS: %s is a StatusNet sitename.
+ $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+ }
+
+ if (!common_config('site', 'private')) {
+ // TRANS: Option in drop-down of potential addressees.
+ $choices['public:everyone'] = _m('SENDTO','Everyone');
+ }
+
+ // Return the order
+ $choices = array_reverse($choices);
+
$this->out->dropdown($this->id,
// TRANS: Label for drop-down of potential addressees.
_m('LABEL','To:'),
@@ -127,18 +142,40 @@ class ToSelector extends Widget
$default);
$this->out->elementStart('span', 'checkbox-wrapper');
- $this->out->checkbox('notice_private',
- // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
- _('Private?'),
- $this->private);
+ if (common_config('notice', 'allowprivate')) {
+ $this->out->checkbox('notice_private',
+ // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
+ _('Private?'),
+ $this->private);
+ }
$this->out->elementEnd('span');
}
+ static function fillActivity(Action $action, Activity $act, array &$options)
+ {
+ if (!$act->context instanceof ActivityContext) {
+ $act->context = new ActivityContext();
+ }
+ self::fillOptions($action, $options);
+ if (isset($options['groups'])) {
+ foreach ($options['groups'] as $group_id) {
+ $group = User_group::getByID($group_id);
+ $act->context->attention[$group->getUri()] = $group->getObjectType();
+ }
+ }
+ if (isset($options['replies'])) {
+ foreach ($options['replies'] as $profile_uri) {
+ $profile = Profile::fromUri($profile_uri);
+ $act->context->attention[$profile->getUri()] = $profile->getObjectType();
+ }
+ }
+ }
+
static function fillOptions($action, &$options)
{
// XXX: make arg name selectable
$toArg = $action->trimmed('notice_to');
- $private = $action->boolean('notice_private');
+ $private = common_config('notice', 'allowprivate') ? $action->boolean('notice_private') : false;
if (empty($toArg)) {
return;
diff --git a/lib/urlmapper.php b/lib/urlmapper.php
index ae31147203..931b5c3c2a 100644
--- a/lib/urlmapper.php
+++ b/lib/urlmapper.php
@@ -66,7 +66,7 @@ class URLMapper
throw new Exception(sprintf("Can't connect %s; path has no action.", $path));
}
- $allpaths[] = $path;
+ $this->allpaths[] = $path;
$action = $args[self::ACTION];
diff --git a/lib/util.php b/lib/util.php
index 6a5c310193..c87b0f1bf6 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -264,6 +264,11 @@ function common_logged_in()
return (!is_null(common_current_user()));
}
+function common_local_referer()
+{
+ return parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server');
+}
+
function common_have_session()
{
return (0 != strcmp(session_id(), ''));
@@ -1391,6 +1396,74 @@ function common_path($relative, $ssl=false, $addSession=true)
return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
}
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_fancy_url($url)
+{
+ /**
+ * This is a hacky fix to make URIs generated with "index.php/" match against
+ * locally stored URIs without that. So for example if the remote site is looking
+ * up the webfinger for some user and for some reason knows about https://some.example/user/1
+ * but we locally store and report only https://some.example/index.php/user/1 then they would
+ * dismiss the profile for not having an identified alias.
+ *
+ * There are various live instances where these issues occur, for various reasons.
+ * Most of them being users fiddling with configuration while already having
+ * started federating (distributing the URI to other servers) or maybe manually
+ * editing the local database.
+ */
+ if (!preg_match(
+ // [1] protocol part, we can only rewrite http/https anyway.
+ '/^(https?:\/\/)' .
+ // [2] site name.
+ // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+ '('.preg_quote(common_config('site', 'server'), '/').')' .
+ // [3] site path, or if that is empty just '/' (to retain the /)
+ '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+ // [4] + [5] extract index.php (+ possible leading double /) and the rest of the URL separately.
+ '(\/?index\.php\/)(.*)$/', $url, $matches)) {
+ // if preg_match failed to match
+ throw new Exception('No known change could be made to the URL.');
+ }
+
+ // now reconstruct the URL with everything except the "index.php/" part
+ $fancy_url = '';
+ foreach ([1,2,3,5] as $idx) {
+ $fancy_url .= $matches[$idx];
+ }
+ return $fancy_url;
+}
+
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_nonfancy_url($url)
+{
+ /**
+ * This is a hacky fix to make URIs NOT generated with "index.php/" match against
+ * locally stored URIs WITH that. The reverse from the above.
+ *
+ * It will also "repair" index.php URLs with multiple / prepended. Like https://some.example///index.php/user/1
+ */
+ if (!preg_match(
+ // [1] protocol part, we can only rewrite http/https anyway.
+ '/^(https?:\/\/)' .
+ // [2] site name.
+ // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+ '('.preg_quote(common_config('site', 'server'), '/').')' .
+ // [3] site path, or if that is empty just '/' (to retain the /)
+ '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+ // [4] should be empty (might contain one or more / and then maybe also index.php). Will be overwritten.
+ // [5] will have the extracted actual URL part (besides site path)
+ '((?!index.php\/)\/*(?:index.php\/)?)(.*)$/', $url, $matches)) {
+ // if preg_match failed to match
+ throw new Exception('No known change could be made to the URL.');
+ }
+
+ $matches[4] = 'index.php/'; // inject the index.php/ rewritethingy
+
+ // remove the first element, which is the full matching string
+ array_shift($matches);
+ return implode($matches);
+}
+
function common_inject_session($url, $serverpart = null)
{
if (!common_have_session()) {
diff --git a/plugins/Favorite/lib/favenoticestream.php b/plugins/Favorite/lib/favenoticestream.php
index 6294c8cdda..d10272ac91 100644
--- a/plugins/Favorite/lib/favenoticestream.php
+++ b/plugins/Favorite/lib/favenoticestream.php
@@ -28,11 +28,7 @@
* @link http://status.net/
*/
-if (!defined('STATUSNET')) {
- // This check helps protect against security problems;
- // your code file can't be executed directly from the web.
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* Notice stream for favorites
@@ -77,14 +73,14 @@ class RawFaveNoticeStream extends NoticeStream
protected $user_id;
protected $own;
+ protected $selectVerbs = array();
+
function __construct($user_id, $own)
{
parent::__construct();
$this->user_id = $user_id;
$this->own = $own;
-
- $this->selectVerbs = array();
}
/**
diff --git a/plugins/HTMLPurifierSchemes/lib/htmlpurifier/urischeme/xmpp.php b/plugins/HTMLPurifierSchemes/lib/htmlpurifier/urischeme/xmpp.php
new file mode 100644
index 0000000000..6d8dcc2bcf
--- /dev/null
+++ b/plugins/HTMLPurifierSchemes/lib/htmlpurifier/urischeme/xmpp.php
@@ -0,0 +1,35 @@
+userinfo = null;
+ $uri->host = null;
+ $uri->port = null;
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/plugins/ModHelper/ModHelperPlugin.php b/plugins/ModHelper/ModHelperPlugin.php
index 2752a21539..88f2f2a731 100644
--- a/plugins/ModHelper/ModHelperPlugin.php
+++ b/plugins/ModHelper/ModHelperPlugin.php
@@ -17,9 +17,7 @@
* along with this program. If not, see .
*/
-if (!defined('STATUSNET')) {
- exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
/**
* @package ModHelperPlugin
@@ -45,7 +43,9 @@ class ModHelperPlugin extends Plugin
function onUserRightsCheck($profile, $right, &$result)
{
if (in_array($right, self::$rights)) {
- // Hrm.... really we should confirm that the *other* user isn't privleged. :)
+ // To silence a profile without accidentally silencing other
+ // privileged users, always call Profile->silenceAs($actor)
+ // since it checks target's privileges too.
if ($profile->hasRole('modhelper')) {
$result = true;
return false;
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index ec2b8351ea..8c7be80a60 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -1297,7 +1297,7 @@ class Ostatus_profile extends Managed_DataObject
try {
$this->updateAvatar($avatar);
} catch (Exception $ex) {
- common_log(LOG_WARNING, "Exception saving OStatus profile avatar: " . $ex->getMessage());
+ common_log(LOG_WARNING, "Exception updating OStatus profile avatar: " . $ex->getMessage());
}
}
}
diff --git a/plugins/OpenID/actions/openidserver.php b/plugins/OpenID/actions/openidserver.php
index b50a9129d7..d4bb6e25f4 100644
--- a/plugins/OpenID/actions/openidserver.php
+++ b/plugins/OpenID/actions/openidserver.php
@@ -63,8 +63,7 @@ class OpenidserverAction extends Action
$request = $this->oserver->decodeRequest();
if (in_array($request->mode, array('checkid_immediate',
'checkid_setup'))) {
- $user = common_current_user();
- if(!$user){
+ if (!$this->scoped instanceof Profile) {
if($request->immediate){
//cannot prompt the user to login in immediate mode, so answer false
$response = $this->generateDenyResponse($request);
@@ -77,9 +76,9 @@ class OpenidserverAction extends Action
common_set_returnto($_SERVER['REQUEST_URI']);
common_redirect(common_local_url('login'), 303);
}
- }else if(common_profile_url($user->nickname) == $request->identity || $request->idSelect()){
+ } elseif (in_array($request->identity, $this->scoped->getAliases()) || $request->idSelect()) {
$user_openid_trustroot = User_openid_trustroot::pkeyGet(
- array('user_id'=>$user->id, 'trustroot'=>$request->trust_root));
+ array('user_id'=>$this->scoped->getID(), 'trustroot'=>$request->trust_root));
if(empty($user_openid_trustroot)){
if($request->immediate){
//cannot prompt the user to trust this trust root in immediate mode, so answer false
@@ -87,7 +86,7 @@ class OpenidserverAction extends Action
}else{
common_ensure_session();
$_SESSION['openid_trust_root'] = $request->trust_root;
- $allowResponse = $this->generateAllowResponse($request, $user);
+ $allowResponse = $this->generateAllowResponse($request, $this->scoped);
$this->oserver->encodeResponse($allowResponse); //sign the response
$denyResponse = $this->generateDenyResponse($request);
$this->oserver->encodeResponse($denyResponse); //sign the response
@@ -101,12 +100,11 @@ class OpenidserverAction extends Action
// were POSTed here.
common_redirect(common_local_url('openidtrust'), 303);
}
- }else{
+ } else {
//user has previously authorized this trust root
- $response = $this->generateAllowResponse($request, $user);
- //$response = $request->answer(true, null, common_profile_url($user->nickname));
+ $response = $this->generateAllowResponse($request, $this->scoped);
}
- } else if ($request->immediate) {
+ } elseif ($request->immediate) {
$response = $this->generateDenyResponse($request);
} else {
//invalid
@@ -137,14 +135,14 @@ class OpenidserverAction extends Action
}
}
- function generateAllowResponse($request, $user){
- $response = $request->answer(true, null, common_profile_url($user->nickname));
+ function generateAllowResponse($request, Profile $profile){
+ $response = $request->answer(true, null, $profile->getUrl());
+ $user = $profile->getUser();
- $profile = $user->getProfile();
$sreg_data = array(
- 'fullname' => $profile->fullname,
- 'nickname' => $user->nickname,
- 'email' => $user->email,
+ 'fullname' => $profile->getFullname(),
+ 'nickname' => $profile->getNickname(),
+ 'email' => $user->email, // FIXME: Should we make the email optional?
'language' => $user->language,
'timezone' => $user->timezone);
$sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
diff --git a/plugins/RegisterThrottle/RegisterThrottlePlugin.php b/plugins/RegisterThrottle/RegisterThrottlePlugin.php
index 9d3be3b8a2..552420d8f6 100644
--- a/plugins/RegisterThrottle/RegisterThrottlePlugin.php
+++ b/plugins/RegisterThrottle/RegisterThrottlePlugin.php
@@ -81,6 +81,13 @@ class RegisterThrottlePlugin extends Plugin
return true;
}
+ public function onRouterInitialized(URLMapper $m)
+ {
+ $m->connect('main/ipregistrations/:ipaddress',
+ array('action' => 'ipregistrations'),
+ array('ipaddress' => '[0-9a-f\.\:]+'));
+ }
+
/**
* Called when someone tries to register.
*
@@ -134,6 +141,52 @@ class RegisterThrottlePlugin extends Plugin
return true;
}
+ function onEndShowSections(Action $action)
+ {
+ if (!$action instanceof ShowstreamAction) {
+ // early return for actions we're not interested in
+ return true;
+ }
+
+ $target = $action->getTarget();
+ if (!$target->isSilenced()) {
+ // Only show the IP of users who are not silenced.
+ return true;
+ }
+
+ $scoped = $action->getScoped();
+ if (!$scoped->hasRight(Right::SILENCEUSER)) {
+ // only show registration IP if we have the right to silence users
+ return true;
+ }
+
+ $ri = Registration_ip::getKV('user_id', $target->getID());
+ $ipaddress = null;
+ if ($ri instanceof Registration_ip) {
+ $ipaddress = $ri->ipaddress;
+ unset($ri);
+ }
+
+ $action->elementStart('div', array('id' => 'entity_mod_log',
+ 'class' => 'section'));
+
+ $action->element('h2', null, _('Registration IP'));
+
+ // TRANS: Label for the information about which IP a users registered from.
+ $action->element('strong', null, _('Registered from:'));
+ $el = 'span';
+ $attrs = ['class'=>'ipaddress'];
+ if (!is_null($ipaddress)) {
+ $el = 'a';
+ $attrs['href'] = common_local_url('ipregistrations', array('ipaddress'=>$ipaddress));
+ }
+ $action->element($el, $attrs,
+ // TRANS: Unknown IP address.
+ $ipaddress ?: _('unknown'));
+
+ $action->elementEnd('div');
+ }
+
/**
* Called after someone registers, by any means.
*
@@ -154,8 +207,8 @@ class RegisterThrottlePlugin extends Plugin
$reg = new Registration_ip();
- $reg->user_id = $profile->id;
- $reg->ipaddress = $ipaddress;
+ $reg->user_id = $profile->getID();
+ $reg->ipaddress = mb_strtolower($ipaddress);
$reg->created = common_sql_now();
$result = $reg->insert();
diff --git a/plugins/RegisterThrottle/actions/ipregistrations.php b/plugins/RegisterThrottle/actions/ipregistrations.php
new file mode 100644
index 0000000000..46f1ed854f
--- /dev/null
+++ b/plugins/RegisterThrottle/actions/ipregistrations.php
@@ -0,0 +1,41 @@
+ipaddress);
+ }
+
+ protected function doPreparation()
+ {
+ if (!$this->scoped->hasRight(Right::SILENCEUSER) && !$this->scoped->hasRole(Profile_role::ADMINISTRATOR)) {
+ throw new AuthorizationException(_('You are not authorized to view this page.'));
+ }
+
+ $this->ipaddress = $this->trimmed('ipaddress');
+ $this->profile_ids = Registration_ip::usersByIP($this->ipaddress);
+ }
+
+ public function showContent()
+ {
+ $this->elementStart('ul');
+ $profile = Profile::multiGet('id', $this->profile_ids);
+ while ($profile->fetch()) {
+ $this->elementStart('li');
+ try {
+ $this->element('a', ['href'=>$profile->getUrl()], $profile->getFancyName());
+ } catch (InvalidUrlException $e) {
+ $this->element('span', null, $profile->getFancyName());
+ }
+ $this->elementEnd('li');
+ }
+ $this->elementEnd('ul');
+ }
+}
diff --git a/plugins/WebFinger/WebFingerPlugin.php b/plugins/WebFinger/WebFingerPlugin.php
index ce8c847aa7..d902947d93 100644
--- a/plugins/WebFinger/WebFingerPlugin.php
+++ b/plugins/WebFinger/WebFingerPlugin.php
@@ -100,15 +100,36 @@ class WebFingerPlugin extends Plugin
}
}
} else {
- $user = User::getKV('uri', $resource);
- if ($user instanceof User) {
+ try {
+ $user = User::getByUri($resource);
$profile = $user->getProfile();
- } else {
- // try and get it by profile url
- $profile = Profile::getKV('profileurl', $resource);
+ } catch (NoResultException $e) {
+ if (common_config('fix', 'fancyurls')) {
+ try {
+ try { // if it's a /index.php/ url
+ // common_fake_local_fancy_url can throw an exception
+ $alt_url = common_fake_local_fancy_url($resource);
+ } catch (Exception $e) { // let's try to create a fake local /index.php/ url
+ // this too if it can't do anything about the URL
+ $alt_url = common_fake_local_nonfancy_url($resource);
+ }
+
+ // and this will throw a NoResultException if not found
+ $user = User::getByUri($alt_url);
+ $profile = $user->getProfile();
+ } catch (Exception $e) {
+ // apparently we didn't get any matches with that, so continue...
+ }
+ }
}
}
+ // if we still haven't found a match...
+ if (!$profile instanceof Profile) {
+ // if our rewrite hack didn't work, try to get something by profile URL
+ $profile = Profile::getKV('profileurl', $resource);
+ }
+
if ($profile instanceof Profile) {
$target = new WebFingerResource_Profile($profile);
return false; // We got our target, stop handler execution
@@ -144,8 +165,8 @@ class WebFingerPlugin extends Plugin
public function onStartShowHTML($action)
{
if ($action instanceof ShowstreamAction) {
- $acct = 'acct:'. $action->getTarget()->getNickname() .'@'. common_config('site', 'server');
- $url = common_local_url('webfinger') . '?resource='.$acct;
+ $resource = $action->getTarget()->getUri();
+ $url = common_local_url('webfinger') . '?resource='.urlencode($resource);
foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"', false);
diff --git a/plugins/WebFinger/lib/webfingerresource.php b/plugins/WebFinger/lib/webfingerresource.php
index 61b2cc09ad..b7bace36d2 100644
--- a/plugins/WebFinger/lib/webfingerresource.php
+++ b/plugins/WebFinger/lib/webfingerresource.php
@@ -31,23 +31,23 @@ abstract class WebFingerResource
public function getAliases()
{
- $aliases = array();
+ $aliases = $this->object->getAliasesWithIDs();
- // Add the URI as an identity, this is _not_ necessarily an HTTP url
- $uri = $this->object->getUri();
- $aliases[] = $uri;
- if (common_config('webfinger', 'http_alias')
- && strtolower(parse_url($uri, PHP_URL_SCHEME)) === 'https') {
- $aliases[] = preg_replace('/^https:/', 'http:', $uri, 1);
+ // Some sites have changed from http to https and still want
+ // (because remote sites look for it) verify that they are still
+ // the same identity as they were on HTTP. Should NOT be used if
+ // you've run HTTPS all the time!
+ if (common_config('webfinger', 'http_alias')) {
+ foreach ($aliases as $alias=>$id) {
+ if (!strtolower(parse_url($alias, PHP_URL_SCHEME)) === 'https') {
+ continue;
+ }
+ $aliases[preg_replace('/^https:/', 'http:', $alias, 1)] = $id;
+ }
}
- try {
- $aliases[] = $this->object->getUrl();
- } catch (InvalidUrlException $e) {
- // getUrl failed because no valid URL could be returned, just ignore it
- }
-
- return $aliases;
+ // return a unique set of aliases by extracting only the keys
+ return array_keys($aliases);
}
abstract public function updateXRD(XML_XRD $xrd);
diff --git a/scripts/delete_orphan_files.php b/scripts/delete_orphan_files.php
new file mode 100755
index 0000000000..2c49c10a50
--- /dev/null
+++ b/scripts/delete_orphan_files.php
@@ -0,0 +1,83 @@
+#!/usr/bin/env php
+.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = 'y';
+$longoptions = array('yes');
+
+$helptext = <<query($sql) !== false) {
+ print " {$file->N} found.\n";
+ if ($file->N == 0) {
+ exit(0);
+ }
+} else {
+ print "FAILED";
+ exit(1);
+}
+
+if (!have_option('y', 'yes')) {
+ print "About to delete the entries along with locally stored files. Are you sure? [y/N] ";
+ $response = fgets(STDIN);
+ if (strtolower(trim($response)) != 'y') {
+ print "Aborting.\n";
+ exit(0);
+ }
+}
+
+print "\nDeleting: ";
+while ($file->fetch()) {
+ try {
+ $file->getPath();
+ $file->delete();
+ print 'x';
+ } catch (Exception $e) {
+ // either FileNotFound exception or ClientException
+ $file->delete();
+ print '.';
+ }
+}
+print "\nDONE.\n";
diff --git a/socialfy-your-domain/README.txt b/socialfy-your-domain/README.txt
index 3e2688f86b..b7691abe8d 100644
--- a/socialfy-your-domain/README.txt
+++ b/socialfy-your-domain/README.txt
@@ -4,65 +4,46 @@ Initial simple way to Webfinger enable your domain -- needs PHP.
Step 1
======
-First, put the folders 'xrd' and 'dot-well-known' on your website, so
-they load at:
+Put the 'dot-well-known' on your website, so it loads at:
- http://yourname.com/xrd/
+ https://example.com/.well-known/
- and
-
- http://yourname.com/.well-known/
-
- (Remember the . at the beginning of this one)
-
-NOTE: If you're using https, make sure each instance of http:// for
- your own domain ("example.com") is replaced with https://
+(Remember the . at the beginning of this one, which is common practice
+for "hidden" files and why we have renamed it "dot-")
Step 2
======
-Next, edit xrd/index.php and enter a secret in this line:
-
-$s = "";
-
-This can be anything you like...
-
-$s = "johnny5";
-
-or
-
-$s = "12345";
-
-It really doesn't matter too much.
+Edit the .well-known/host-meta file and replace "example.com" with the
+domain name you're hosting the .well-known directory on.
+Using vim you can do this as a quick method:
+ $ vim .well-known/host-meta [ENTER]
+ :%s/example.com/domain.com/ [ENTER]
+ :wq [ENTER]
Step 3
======
-Edit the .well-known/host-meta file and replace all occurrences of
-"example.com" with your domain name.
-
-Step 4
-======
-
For each user on your site, and this might only be you...
-In the xrd directory, make a copy of the example@example.com.xml file
-so that it's called...
+In the webfinger directory, make a copy of the example@example.com.xml file
+so that it's called (replace username and example.com with appropriate
+values, the domain name should be the same as you're "socialifying"):
- yoursecretusername@domain.com.xml
+ username@example.com.xml
-So, if your secret from step 2 is 'johnny5' and your name is 'ben' and
-your domain is 'titanictoycorp.biz', your file should be called
-johnny5ben@titanictoycorp.biz.xml
-
-Then edit the file, replacing "social.example.com" with your GNU
-social instance's base path, and change the user ID number (and
+Then edit the file contents, replacing "social.example.com" with your
+GNU social instance's base path, and change the user ID number (and
nickname for the FOAF link) to that of your account on your social
site. If you don't know your user ID number, you can see this on your
GNU social profile page by looking at the destination URLs in the
Feeds links.
+PROTIP: You can get the bulk of the contents (note the element though)
+ from curling down your real webfinger data:
+$ curl https://social.example.com/.well-known/webfinger?resource=acct:username@social.example.com
+
Finally
=======
diff --git a/socialfy-your-domain/dot-well-known/host-meta b/socialfy-your-domain/dot-well-known/host-meta
index 1929b2eb8e..bba942f673 100644
--- a/socialfy-your-domain/dot-well-known/host-meta
+++ b/socialfy-your-domain/dot-well-known/host-meta
@@ -1,8 +1,5 @@
-
- example.com
-
- WebFinger resource descriptor
-
+
+
diff --git a/socialfy-your-domain/xrd/example@example.com.xml b/socialfy-your-domain/dot-well-known/webfinger/example@example.com.xml
similarity index 54%
rename from socialfy-your-domain/xrd/example@example.com.xml
rename to socialfy-your-domain/dot-well-known/webfinger/example@example.com.xml
index b713efe95b..e95662b6fe 100644
--- a/socialfy-your-domain/xrd/example@example.com.xml
+++ b/socialfy-your-domain/dot-well-known/webfinger/example@example.com.xml
@@ -1,35 +1,35 @@
- acct:example@example.com
- acct:example@social.example.com
- http://social.example.com/user/1
+ acct:username@example.com
+ acct:username@social.example.com
+ https://social.example.com/user/1
+ href="https://social.example.com/user/1"/>
+ href="https://social.example.com/api/statuses/user_timeline/1.atom"/>
+ href="https://social.example.com/hcard"/> -->
+ href="https://social.example.com/user/1"/>
+ href="https://social.example.com/username/foaf"/>
+ href="https://social.example.com/main/salmon/user/1"/>
+ href="https://social.example.com/main/salmon/user/1"/>
+ template="https://social.example.com/main/ostatussub?profile={uri}"/>
diff --git a/socialfy-your-domain/xrd/index.php b/socialfy-your-domain/dot-well-known/webfinger/index.php
similarity index 59%
rename from socialfy-your-domain/xrd/index.php
rename to socialfy-your-domain/dot-well-known/webfinger/index.php
index 25f1d8bf3c..91071bc4c3 100644
--- a/socialfy-your-domain/xrd/index.php
+++ b/socialfy-your-domain/dot-well-known/webfinger/index.php
@@ -19,23 +19,25 @@
*/
-$s = "";
+// basename should make sure we can't escape this directory
+$u = basename($_GET['resource']);
-/* this should be a secret */
-
-$u = $_GET['uri'];
-
-$u = substr($u, 5);
-
-$f = $s . $u . ".xml";
-
-if (file_exists($f)) {
- $fh = fopen($f, 'r');
- $c = fread($fh, filesize($f));
- fclose($fh);
- header('Content-type: text/xml');
- echo $c;
+if (!strpos($u, '@')) {
+ throw new Exception('Bad resource');
+ exit(1);
}
+if (mb_strpos($u, 'acct:')===0) {
+ $u = substr($u, 5);
+}
-?>
\ No newline at end of file
+// Just to be a little bit safer, you know, with all the unicode stuff going on
+$u = filter_var($u, FILTER_SANITIZE_EMAIL);
+
+$f = $u . ".xml";
+
+if (file_exists($f)) {
+ header('Content-Disposition: attachment; filename="'.urlencode($f).'"');
+ header('Content-type: application/xrd+xml');
+ echo file_get_contents($f);
+}
diff --git a/theme/base/css/display.css b/theme/base/css/display.css
index 61696e6f11..295916d78e 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -503,6 +503,10 @@ address .poweredby {
z-index: 99;
}
+.form_notice .to-selector > select {
+ max-width: 300px;
+}
+
.form_settings label[for=notice_to] {
left: 5px;
margin-left: 0px;