mirror of
https://github.com/friendica/friendica
synced 2024-11-19 10:23:41 +00:00
Merge develop into 201820_-_fix_mod_redir
This commit is contained in:
commit
22816c49a1
38 changed files with 1356 additions and 200 deletions
2
boot.php
2
boot.php
|
@ -41,7 +41,7 @@ define('FRIENDICA_PLATFORM', 'Friendica');
|
||||||
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
|
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
|
||||||
define('FRIENDICA_VERSION', '2018.08-dev');
|
define('FRIENDICA_VERSION', '2018.08-dev');
|
||||||
define('DFRN_PROTOCOL_VERSION', '2.23');
|
define('DFRN_PROTOCOL_VERSION', '2.23');
|
||||||
define('DB_UPDATE_VERSION', 1268);
|
define('DB_UPDATE_VERSION', 1269);
|
||||||
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
17
database.sql
17
database.sql
|
@ -1,6 +1,6 @@
|
||||||
-- ------------------------------------------
|
-- ------------------------------------------
|
||||||
-- Friendica 2018.08-dev (The Tazmans Flax-lily)
|
-- Friendica 2018.08-dev (The Tazmans Flax-lily)
|
||||||
-- DB_UPDATE_VERSION 1268
|
-- DB_UPDATE_VERSION 1269
|
||||||
-- ------------------------------------------
|
-- ------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -375,7 +375,7 @@ CREATE TABLE IF NOT EXISTS `group` (
|
||||||
CREATE TABLE IF NOT EXISTS `group_member` (
|
CREATE TABLE IF NOT EXISTS `group_member` (
|
||||||
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
|
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
|
||||||
`gid` int unsigned NOT NULL DEFAULT 0 COMMENT 'groups.id of the associated group',
|
`gid` int unsigned NOT NULL DEFAULT 0 COMMENT 'groups.id of the associated group',
|
||||||
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id of the member assigned to the associated group',
|
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id of the member assigned to the associated group',
|
||||||
PRIMARY KEY(`id`),
|
PRIMARY KEY(`id`),
|
||||||
INDEX `contactid` (`contact-id`),
|
INDEX `contactid` (`contact-id`),
|
||||||
UNIQUE INDEX `gid_contactid` (`gid`,`contact-id`)
|
UNIQUE INDEX `gid_contactid` (`gid`,`contact-id`)
|
||||||
|
@ -1084,6 +1084,19 @@ CREATE TABLE IF NOT EXISTS `user-item` (
|
||||||
PRIMARY KEY(`uid`,`iid`)
|
PRIMARY KEY(`uid`,`iid`)
|
||||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data';
|
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data';
|
||||||
|
|
||||||
|
--
|
||||||
|
-- TABLE openwebauth-token
|
||||||
|
--
|
||||||
|
CREATE TABLE IF NOT EXISTS `openwebauth-token` (
|
||||||
|
`id` int(10) NOT NULL auto_increment COMMENT 'sequential ID',
|
||||||
|
`uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
|
||||||
|
`type` varchar(32) DEFAULT '' COMMENT 'Verify type',
|
||||||
|
`token` varchar(255) DEFAULT '' COMMENT 'A generated token',
|
||||||
|
`meta` varchar(255) DEFAULT '' COMMENT '',
|
||||||
|
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime of creation',
|
||||||
|
PRIMARY KEY(`id`)
|
||||||
|
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Store OpenWebAuth token to verify contacts';
|
||||||
|
|
||||||
--
|
--
|
||||||
-- TABLE worker-ipc
|
-- TABLE worker-ipc
|
||||||
--
|
--
|
||||||
|
|
|
@ -72,12 +72,12 @@ JavaScript addon hooks
|
||||||
---
|
---
|
||||||
|
|
||||||
#### PHP part
|
#### PHP part
|
||||||
Make sure your JavaScript addon file (addon/*addon_name*/*addon_name*.js) is listed in the document response.
|
Make sure your JavaScript addon file (addon/*addon_name*/*addon_name*.js) is listed in the document response.
|
||||||
|
|
||||||
In your addon install function, add:
|
In your addon install function, add:
|
||||||
|
|
||||||
Addon::registerHook('template_vars', 'addon/<addon_name>/<addon_name>.php', '<addon_name>_template_vars');
|
Addon::registerHook('template_vars', 'addon/<addon_name>/<addon_name>.php', '<addon_name>_template_vars');
|
||||||
|
|
||||||
In your addon uninstall function, add:
|
In your addon uninstall function, add:
|
||||||
|
|
||||||
Addon::unregisterHook('template_vars', 'addon/<addon_name>/<addon_name>.php', '<addon_name>_template_vars');
|
Addon::unregisterHook('template_vars', 'addon/<addon_name>/<addon_name>.php', '<addon_name>_template_vars');
|
||||||
|
@ -104,7 +104,7 @@ Register your addon hooks in file 'addon/*addon_name*/*addon_name*.js'.
|
||||||
No arguments are provided to your JavaScript callback function. Example:
|
No arguments are provided to your JavaScript callback function. Example:
|
||||||
|
|
||||||
function myhook_function() {
|
function myhook_function() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Modules
|
Modules
|
||||||
|
@ -357,6 +357,12 @@ Hook data:
|
||||||
'item' => item array (input)
|
'item' => item array (input)
|
||||||
'html' => converted item body (input/output)
|
'html' => converted item body (input/output)
|
||||||
|
|
||||||
|
### 'magic_auth_success'
|
||||||
|
Called when a magic-auth was successful.
|
||||||
|
Hook data:
|
||||||
|
'visitor' => array with the contact record of the visitor
|
||||||
|
'url' => the query string
|
||||||
|
|
||||||
Current JavaScript hooks
|
Current JavaScript hooks
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -557,6 +563,7 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr-
|
||||||
Addon::callHooks('profile_sidebar', $arr);
|
Addon::callHooks('profile_sidebar', $arr);
|
||||||
Addon::callHooks('profile_tabs', $arr);
|
Addon::callHooks('profile_tabs', $arr);
|
||||||
Addon::callHooks('zrl_init', $arr);
|
Addon::callHooks('zrl_init', $arr);
|
||||||
|
Addon::callHooks('magic_auth_success', $arr);
|
||||||
|
|
||||||
### src/Model/Event.php
|
### src/Model/Event.php
|
||||||
|
|
||||||
|
@ -668,4 +675,4 @@ Here is a complete list of all hook callbacks with file locations (as of 01-Apr-
|
||||||
|
|
||||||
### view/js/main.js
|
### view/js/main.js
|
||||||
|
|
||||||
callAddonHooks("postprocess_liveupdate");
|
callAddonHooks("postprocess_liveupdate");
|
||||||
|
|
|
@ -18,7 +18,7 @@ You can tag **persons who are in your social circle** by adding the "@"-sign in
|
||||||
* @mike+151 - this form is used by the drop-down tag completion tool. It indicates the contact whose nickname is mike and whose contact identifier number is 151. The drop-down tool may be used to resolve people with duplicate nicknames.
|
* @mike+151 - this form is used by the drop-down tag completion tool. It indicates the contact whose nickname is mike and whose contact identifier number is 151. The drop-down tool may be used to resolve people with duplicate nicknames.
|
||||||
|
|
||||||
You can tag a person on a different network or one that is **not in your social circle** by using the following notation:
|
You can tag a person on a different network or one that is **not in your social circle** by using the following notation:
|
||||||
|
|
||||||
* @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL.
|
* @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL.
|
||||||
|
|
||||||
Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts.
|
Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts.
|
||||||
|
@ -27,7 +27,7 @@ The exception is an ongoing conversation started from a contact of both you and
|
||||||
This is a spam prevention measure.
|
This is a spam prevention measure.
|
||||||
|
|
||||||
Remote mentions are delivered using the OStatus protocol.
|
Remote mentions are delivered using the OStatus protocol.
|
||||||
This protocol is used by Friendica and GNU Social and several other systems like Mastodon, but is not currently implemented in Diaspora.
|
This protocol is used by Friendica and GNU Social and several other systems like Mastodon, but is not currently implemented in Diaspora.
|
||||||
As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node.
|
As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node.
|
||||||
These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message.
|
These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message.
|
||||||
|
|
||||||
|
@ -52,5 +52,5 @@ The same rules apply as with names that spaces within tags are represented by th
|
||||||
It is therefore not possible to create a tag whose target contains an underscore.
|
It is therefore not possible to create a tag whose target contains an underscore.
|
||||||
|
|
||||||
Topical tags are also not linked if they are purely numeric, e.g. #1.
|
Topical tags are also not linked if they are purely numeric, e.g. #1.
|
||||||
If you wish to use a numerica hashtag, please add some descriptive text such as #2012-elections.
|
If you wish to use a numerica hashtag, please add some descriptive text such as #2012-elections.
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ Example: To set the automatic database cleanup process add this line to your .ht
|
||||||
* **db_loglimit_index** - Number of index rows needed to be logged for indexes on the watchlist
|
* **db_loglimit_index** - Number of index rows needed to be logged for indexes on the watchlist
|
||||||
* **db_loglimit_index_high** - Number of index rows to be logged anyway (for any index)
|
* **db_loglimit_index_high** - Number of index rows to be logged anyway (for any index)
|
||||||
* **db_log_index_blacklist** - Blacklist of indexes that shouldn't be watched
|
* **db_log_index_blacklist** - Blacklist of indexes that shouldn't be watched
|
||||||
|
* **dbclean_expire_conversation** (Integer) - When DBClean is enabled, any entry in the conversation table will be deleted after this many days. These data are normally needed only for debugging purposes and they are safe to delete. Default 90.
|
||||||
* **diaspora_test** (Boolean) - For development only. Disables the message transfer.
|
* **diaspora_test** (Boolean) - For development only. Disables the message transfer.
|
||||||
* **disable_email_validation** (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS.
|
* **disable_email_validation** (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS.
|
||||||
* **disable_url_validation** (Boolean) - Disables the DNS lookup of an URL.
|
* **disable_url_validation** (Boolean) - Disables the DNS lookup of an URL.
|
||||||
|
|
|
@ -1547,7 +1547,7 @@ function api_search($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$data['status'] = api_format_items(dba::inArray($statuses), $user_info);
|
$data['status'] = api_format_items(Item::inArray($statuses), $user_info);
|
||||||
|
|
||||||
return api_format_data("statuses", $type, $data);
|
return api_format_data("statuses", $type, $data);
|
||||||
}
|
}
|
||||||
|
@ -1614,7 +1614,7 @@ function api_statuses_home_timeline($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$items = dba::inArray($statuses);
|
$items = Item::inArray($statuses);
|
||||||
|
|
||||||
$ret = api_format_items($items, $user_info, false, $type);
|
$ret = api_format_items($items, $user_info, false, $type);
|
||||||
|
|
||||||
|
@ -1691,7 +1691,7 @@ function api_statuses_public_timeline($type)
|
||||||
$params = ['order' => ['iid' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['iid' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectThreadForUser(api_user(), Item::DISPLAY_FIELDLIST, $condition, $params);
|
$statuses = Item::selectThreadForUser(api_user(), Item::DISPLAY_FIELDLIST, $condition, $params);
|
||||||
|
|
||||||
$r = dba::inArray($statuses);
|
$r = Item::inArray($statuses);
|
||||||
} else {
|
} else {
|
||||||
$condition = ["`verb` = ? AND `id` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND `item`.`origin`",
|
$condition = ["`verb` = ? AND `id` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND `item`.`origin`",
|
||||||
ACTIVITY_POST, $since_id];
|
ACTIVITY_POST, $since_id];
|
||||||
|
@ -1708,7 +1708,7 @@ function api_statuses_public_timeline($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$r = dba::inArray($statuses);
|
$r = Item::inArray($statuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret = api_format_items($r, $user_info, false, $type);
|
$ret = api_format_items($r, $user_info, false, $type);
|
||||||
|
@ -1767,7 +1767,7 @@ function api_statuses_networkpublic_timeline($type)
|
||||||
$params = ['order' => ['iid' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['iid' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectThreadForUser(api_user(), Item::DISPLAY_FIELDLIST, $condition, $params);
|
$statuses = Item::selectThreadForUser(api_user(), Item::DISPLAY_FIELDLIST, $condition, $params);
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
|
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -1843,7 +1843,7 @@ function api_statuses_show($type)
|
||||||
throw new BadRequestException("There is no status with this id.");
|
throw new BadRequestException("There is no status with this id.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
|
|
||||||
if ($conversation) {
|
if ($conversation) {
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
|
@ -1923,7 +1923,7 @@ function api_conversation_show($type)
|
||||||
throw new BadRequestException("There is no status with id $id.");
|
throw new BadRequestException("There is no status with id $id.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
|
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
return api_format_data("statuses", $type, $data);
|
return api_format_data("statuses", $type, $data);
|
||||||
|
@ -2089,7 +2089,7 @@ function api_statuses_mentions($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
|
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -2169,7 +2169,7 @@ function api_statuses_user_timeline($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, true, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, true, $type);
|
||||||
|
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -2311,7 +2311,7 @@ function api_favorites($type)
|
||||||
|
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$ret = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$ret = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = ['status' => $ret];
|
$data = ['status' => $ret];
|
||||||
|
@ -2728,7 +2728,7 @@ function api_format_items_activities(&$item, $type = "json")
|
||||||
$condition = ['uid' => $item['uid'], 'thr-parent' => $item['uri']];
|
$condition = ['uid' => $item['uid'], 'thr-parent' => $item['uri']];
|
||||||
$ret = Item::selectForUser($item['uid'], ['author-id', 'verb'], $condition);
|
$ret = Item::selectForUser($item['uid'], ['author-id', 'verb'], $condition);
|
||||||
|
|
||||||
while ($item = dba::fetch($ret)) {
|
while ($item = Item::fetch($ret)) {
|
||||||
// not used as result should be structured like other user data
|
// not used as result should be structured like other user data
|
||||||
//builtin_activity_puller($i, $activities);
|
//builtin_activity_puller($i, $activities);
|
||||||
|
|
||||||
|
@ -3117,7 +3117,7 @@ function api_lists_statuses($type)
|
||||||
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
$statuses = Item::selectForUser(api_user(), [], $condition, $params);
|
||||||
|
|
||||||
$items = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$items = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
|
|
||||||
$data = ['status' => $items];
|
$data = ['status' => $items];
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -4636,7 +4636,7 @@ function prepare_photo_data($type, $scale, $photo_id)
|
||||||
$statuses = Item::selectForUser(api_user(), [], $condition);
|
$statuses = Item::selectForUser(api_user(), [], $condition);
|
||||||
|
|
||||||
// prepare output of comments
|
// prepare output of comments
|
||||||
$commentData = api_format_items(dba::inArray($statuses), $user_info, false, $type);
|
$commentData = api_format_items(Item::inArray($statuses), $user_info, false, $type);
|
||||||
$comments = [];
|
$comments = [];
|
||||||
if ($type == "xml") {
|
if ($type == "xml") {
|
||||||
$k = 0;
|
$k = 0;
|
||||||
|
|
|
@ -779,7 +779,7 @@ function conversation_add_children($parents, $block_authors, $order, $uid) {
|
||||||
}
|
}
|
||||||
$thread_items = Item::selectForUser(local_user(), [], $condition, $params);
|
$thread_items = Item::selectForUser(local_user(), [], $condition, $params);
|
||||||
|
|
||||||
$comments = dba::inArray($thread_items);
|
$comments = Item::inArray($thread_items);
|
||||||
|
|
||||||
if (count($comments) != 0) {
|
if (count($comments) != 0) {
|
||||||
$items = array_merge($items, $comments);
|
$items = array_merge($items, $comments);
|
||||||
|
|
|
@ -427,7 +427,12 @@ class dba {
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($args AS $param => $value) {
|
foreach ($args AS $param => $value) {
|
||||||
$stmt->bindParam($param, $args[$param]);
|
if (is_int($args[$param])) {
|
||||||
|
$data_type = PDO::PARAM_INT;
|
||||||
|
} else {
|
||||||
|
$data_type = PDO::PARAM_STR;
|
||||||
|
}
|
||||||
|
$stmt->bindParam($param, $args[$param], $data_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$stmt->execute()) {
|
if (!$stmt->execute()) {
|
||||||
|
|
|
@ -474,7 +474,7 @@ function perms2str($p) {
|
||||||
*/
|
*/
|
||||||
function load_view_file($s) {
|
function load_view_file($s) {
|
||||||
global $lang, $a;
|
global $lang, $a;
|
||||||
if (! isset($lang)) {
|
if (!isset($lang)) {
|
||||||
$lang = 'en';
|
$lang = 'en';
|
||||||
}
|
}
|
||||||
$b = basename($s);
|
$b = basename($s);
|
||||||
|
@ -519,7 +519,7 @@ function get_intltext_template($s) {
|
||||||
$engine = "/smarty3";
|
$engine = "/smarty3";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! isset($lang)) {
|
if (!isset($lang)) {
|
||||||
$lang = 'en';
|
$lang = 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,8 +621,8 @@ function logger($msg, $level = 0) {
|
||||||
$loglevel = intval(Config::get('system','loglevel'));
|
$loglevel = intval(Config::get('system','loglevel'));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
! $debugging
|
!$debugging
|
||||||
|| ! $logfile
|
|| !$logfile
|
||||||
|| $level > $loglevel
|
|| $level > $loglevel
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
@ -689,7 +689,7 @@ function dlogger($msg, $level = 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$logfile = Config::get('system', 'dlogfile');
|
$logfile = Config::get('system', 'dlogfile');
|
||||||
if (! $logfile) {
|
if (!$logfile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1253,7 +1253,7 @@ function prepare_body(array &$item, $attach = false, $is_preview = false)
|
||||||
$s = $hook_data['html'];
|
$s = $hook_data['html'];
|
||||||
unset($hook_data);
|
unset($hook_data);
|
||||||
|
|
||||||
if (! $attach) {
|
if (!$attach) {
|
||||||
// Replace the blockquotes with quotes that are used in mails.
|
// Replace the blockquotes with quotes that are used in mails.
|
||||||
$mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
|
$mailquote = '<blockquote type="cite" class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">';
|
||||||
$s = str_replace(['<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'], [$mailquote, $mailquote, $mailquote], $s);
|
$s = str_replace(['<blockquote>', '<blockquote class="spoiler">', '<blockquote class="author">'], [$mailquote, $mailquote, $mailquote], $s);
|
||||||
|
@ -1553,7 +1553,7 @@ function generate_user_guid() {
|
||||||
$x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
|
$x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1",
|
||||||
dbesc($guid)
|
dbesc($guid)
|
||||||
);
|
);
|
||||||
if (! DBM::is_result($x)) {
|
if (!DBM::is_result($x)) {
|
||||||
$found = false;
|
$found = false;
|
||||||
}
|
}
|
||||||
} while ($found == true);
|
} while ($found == true);
|
||||||
|
@ -1595,7 +1595,7 @@ function base64url_decode($s) {
|
||||||
* // Uncomment if you find you need it.
|
* // Uncomment if you find you need it.
|
||||||
*
|
*
|
||||||
* $l = strlen($s);
|
* $l = strlen($s);
|
||||||
* if (! strpos($s,'=')) {
|
* if (!strpos($s,'=')) {
|
||||||
* $m = $l % 4;
|
* $m = $l % 4;
|
||||||
* if ($m == 2)
|
* if ($m == 2)
|
||||||
* $s .= '==';
|
* $s .= '==';
|
||||||
|
@ -1818,7 +1818,7 @@ function file_tag_update_pconfig($uid, $file_old, $file_new, $type = 'file') {
|
||||||
$check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
|
$check_new_tags = explode(",",file_tag_file_to_list($file_new,$type));
|
||||||
|
|
||||||
foreach ($check_new_tags as $tag) {
|
foreach ($check_new_tags as $tag) {
|
||||||
if (! stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket)) {
|
if (!stristr($saved,$lbracket . file_tag_encode($tag) . $rbracket)) {
|
||||||
$new_tags[] = $tag;
|
$new_tags[] = $tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1830,7 +1830,7 @@ function file_tag_update_pconfig($uid, $file_old, $file_new, $type = 'file') {
|
||||||
$check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
|
$check_deleted_tags = explode(",",file_tag_file_to_list($file_old,$type));
|
||||||
|
|
||||||
foreach ($check_deleted_tags as $tag) {
|
foreach ($check_deleted_tags as $tag) {
|
||||||
if (! stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket)) {
|
if (!stristr($file_new,$lbracket . file_tag_encode($tag) . $rbracket)) {
|
||||||
$deleted_tags[] = $tag;
|
$deleted_tags[] = $tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1859,20 +1859,17 @@ function file_tag_update_pconfig($uid, $file_old, $file_new, $type = 'file') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function file_tag_save_file($uid, $item, $file)
|
function file_tag_save_file($uid, $item_id, $file)
|
||||||
{
|
{
|
||||||
if (! intval($uid)) {
|
if (!intval($uid)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
|
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
|
||||||
intval($item),
|
if (DBM::is_result($item)) {
|
||||||
intval($uid)
|
if (!stristr($item['file'],'[' . file_tag_encode($file) . ']')) {
|
||||||
);
|
$fields = ['file' => $item['file'] . '[' . file_tag_encode($file) . ']'];
|
||||||
if (DBM::is_result($r)) {
|
Item::update($fields, ['id' => $item_id]);
|
||||||
if (!stristr($r[0]['file'],'[' . file_tag_encode($file) . ']')) {
|
|
||||||
$fields = ['file' => $r[0]['file'] . '[' . file_tag_encode($file) . ']'];
|
|
||||||
Item::update($fields, ['id' => $item]);
|
|
||||||
}
|
}
|
||||||
$saved = PConfig::get($uid, 'system', 'filetags');
|
$saved = PConfig::get($uid, 'system', 'filetags');
|
||||||
if (!strlen($saved) || !stristr($saved, '[' . file_tag_encode($file) . ']')) {
|
if (!strlen($saved) || !stristr($saved, '[' . file_tag_encode($file) . ']')) {
|
||||||
|
@ -1883,9 +1880,9 @@ function file_tag_save_file($uid, $item, $file)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function file_tag_unsave_file($uid, $item, $file, $cat = false)
|
function file_tag_unsave_file($uid, $item_id, $file, $cat = false)
|
||||||
{
|
{
|
||||||
if (! intval($uid)) {
|
if (!intval($uid)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1897,16 +1894,13 @@ function file_tag_unsave_file($uid, $item, $file, $cat = false)
|
||||||
$termtype = TERM_FILE;
|
$termtype = TERM_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$r = q("SELECT `file` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
|
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
|
||||||
intval($item),
|
if (!DBM::is_result($item)) {
|
||||||
intval($uid)
|
|
||||||
);
|
|
||||||
if (! DBM::is_result($r)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields = ['file' => str_replace($pattern,'',$r[0]['file'])];
|
$fields = ['file' => str_replace($pattern,'',$item['file'])];
|
||||||
Item::update($fields, ['id' => $item]);
|
Item::update($fields, ['id' => $item_id]);
|
||||||
|
|
||||||
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
||||||
dbesc($file),
|
dbesc($file),
|
||||||
|
@ -1970,6 +1964,7 @@ function is_a_date_arg($s) {
|
||||||
*/
|
*/
|
||||||
function deindent($text, $chr = "[\t ]", $count = NULL) {
|
function deindent($text, $chr = "[\t ]", $count = NULL) {
|
||||||
$lines = explode("\n", $text);
|
$lines = explode("\n", $text);
|
||||||
|
|
||||||
if (is_null($count)) {
|
if (is_null($count)) {
|
||||||
$m = [];
|
$m = [];
|
||||||
$k = 0;
|
$k = 0;
|
||||||
|
|
42
index.php
42
index.php
|
@ -121,25 +121,35 @@ if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== $lang)) {
|
||||||
L10n::loadTranslationTable($lang);
|
L10n::loadTranslationTable($lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((x($_GET, 'zrl')) && $a->mode == App::MODE_NORMAL) {
|
if ((x($_GET,'zrl')) && $a->mode == App::MODE_NORMAL) {
|
||||||
// Only continue when the given profile link seems valid
|
$a->query_string = Profile::stripZrls($a->query_string);
|
||||||
// Valid profile links contain a path with "/profile/" and no query parameters
|
if (!local_user()) {
|
||||||
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "")
|
// Only continue when the given profile link seems valid
|
||||||
&& strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")
|
// Valid profile links contain a path with "/profile/" and no query parameters
|
||||||
) {
|
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == "") &&
|
||||||
$_SESSION['my_url'] = $_GET['zrl'];
|
strstr(parse_url($_GET['zrl'], PHP_URL_PATH), "/profile/")) {
|
||||||
$a->query_string = preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $a->query_string);
|
if ($_SESSION["visitor_home"] != $_GET["zrl"]) {
|
||||||
Profile::zrlInit($a);
|
$_SESSION['my_url'] = $_GET['zrl'];
|
||||||
} else {
|
$_SESSION['authenticated'] = 0;
|
||||||
// Someone came with an invalid parameter, maybe as a DDoS attempt
|
}
|
||||||
// We simply stop processing here
|
Profile::zrlInit($a);
|
||||||
logger("Invalid ZRL parameter ".$_GET['zrl'], LOGGER_DEBUG);
|
} else {
|
||||||
header('HTTP/1.1 403 Forbidden');
|
// Someone came with an invalid parameter, maybe as a DDoS attempt
|
||||||
echo "<h1>403 Forbidden</h1>";
|
// We simply stop processing here
|
||||||
killme();
|
logger("Invalid ZRL parameter " . $_GET['zrl'], LOGGER_DEBUG);
|
||||||
|
header('HTTP/1.1 403 Forbidden');
|
||||||
|
echo "<h1>403 Forbidden</h1>";
|
||||||
|
killme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((x($_GET,'owt')) && $a->mode == App::MODE_NORMAL) {
|
||||||
|
$token = $_GET['owt'];
|
||||||
|
$a->query_string = Profile::stripQueryParam($a->query_string, 'owt');
|
||||||
|
Profile::openWebAuthInit($token);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header.
|
* For Mozilla auth manager - still needs sorting, and this might conflict with LRDD header.
|
||||||
* Apache/PHP lumps the Link: headers into one - and other services might not be able to parse it
|
* Apache/PHP lumps the Link: headers into one - and other services might not be able to parse it
|
||||||
|
|
|
@ -141,6 +141,7 @@ function message_content(App $a)
|
||||||
'$cancel' => L10n::t('Cancel'),
|
'$cancel' => L10n::t('Cancel'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check how the user responded to the confirmation query
|
// Now check how the user responded to the confirmation query
|
||||||
if ($_REQUEST['canceled']) {
|
if ($_REQUEST['canceled']) {
|
||||||
goaway($_SESSION['return_url']);
|
goaway($_SESSION['return_url']);
|
||||||
|
@ -151,6 +152,7 @@ function message_content(App $a)
|
||||||
if (dba::delete('mail', ['id' => $a->argv[2], 'uid' => local_user()])) {
|
if (dba::delete('mail', ['id' => $a->argv[2], 'uid' => local_user()])) {
|
||||||
info(L10n::t('Message deleted.') . EOL);
|
info(L10n::t('Message deleted.') . EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
//goaway(System::baseUrl(true) . '/message' );
|
//goaway(System::baseUrl(true) . '/message' );
|
||||||
goaway($_SESSION['return_url']);
|
goaway($_SESSION['return_url']);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1231,6 +1231,7 @@ function photos_content(App $a)
|
||||||
*/
|
*/
|
||||||
if (!Config::get('system', 'no_count', false)) {
|
if (!Config::get('system', 'no_count', false)) {
|
||||||
$order_field = defaults($_GET, 'order', '');
|
$order_field = defaults($_GET, 'order', '');
|
||||||
|
|
||||||
if ($order_field === 'posted') {
|
if ($order_field === 'posted') {
|
||||||
$order = 'ASC';
|
$order = 'ASC';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,7 +21,7 @@ function search_saved_searches() {
|
||||||
$o = '';
|
$o = '';
|
||||||
$search = ((x($_GET,'search')) ? notags(trim(rawurldecode($_GET['search']))) : '');
|
$search = ((x($_GET,'search')) ? notags(trim(rawurldecode($_GET['search']))) : '');
|
||||||
|
|
||||||
if (! Feature::isEnabled(local_user(),'savedsearch'))
|
if (!Feature::isEnabled(local_user(),'savedsearch'))
|
||||||
return $o;
|
return $o;
|
||||||
|
|
||||||
$r = q("SELECT `id`,`term` FROM `search` WHERE `uid` = %d",
|
$r = q("SELECT `id`,`term` FROM `search` WHERE `uid` = %d",
|
||||||
|
@ -184,7 +184,7 @@ function search_content(App $a) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $search)
|
if (!$search)
|
||||||
return $o;
|
return $o;
|
||||||
|
|
||||||
if (Config::get('system','only_tag_search'))
|
if (Config::get('system','only_tag_search'))
|
||||||
|
@ -211,8 +211,13 @@ function search_content(App $a) {
|
||||||
}
|
}
|
||||||
dba::close($terms);
|
dba::close($terms);
|
||||||
|
|
||||||
$items = Item::selectForUser(local_user(), [], ['id' => array_reverse($itemids)]);
|
if (!empty($itemids)) {
|
||||||
$r = dba::inArray($items);
|
$params = ['order' => ['id' => true]];
|
||||||
|
$items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params);
|
||||||
|
$r = dba::inArray($items);
|
||||||
|
} else {
|
||||||
|
$r = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger("Start fulltext search for '".$search."'", LOGGER_DEBUG);
|
logger("Start fulltext search for '".$search."'", LOGGER_DEBUG);
|
||||||
|
|
||||||
|
@ -250,4 +255,3 @@ function search_content(App $a) {
|
||||||
|
|
||||||
return $o;
|
return $o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,32 +11,29 @@ function starred_init(App $a) {
|
||||||
$starred = 0;
|
$starred = 0;
|
||||||
$message_id = null;
|
$message_id = null;
|
||||||
|
|
||||||
if (! local_user()) {
|
if (!local_user()) {
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
if ($a->argc > 1) {
|
if ($a->argc > 1) {
|
||||||
$message_id = intval($a->argv[1]);
|
$message_id = intval($a->argv[1]);
|
||||||
}
|
}
|
||||||
if (! $message_id) {
|
if (!$message_id) {
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
|
|
||||||
$r = q("SELECT `starred` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
|
$item = Item::selectForUser(local_user(), ['starred'], ['uid' => local_user(), 'id' => $message_id]);
|
||||||
intval(local_user()),
|
if (!DBM::is_result($item)) {
|
||||||
intval($message_id)
|
|
||||||
);
|
|
||||||
if (! DBM::is_result($r)) {
|
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! intval($r[0]['starred'])) {
|
if (!intval($item['starred'])) {
|
||||||
$starred = 1;
|
$starred = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Item::update(['starred' => $starred], ['id' => $message_id]);
|
Item::update(['starred' => $starred], ['id' => $message_id]);
|
||||||
|
|
||||||
// See if we've been passed a return path to redirect to
|
// See if we've been passed a return path to redirect to
|
||||||
$return_path = ((x($_REQUEST,'return')) ? $_REQUEST['return'] : '');
|
$return_path = (x($_REQUEST,'return') ? $_REQUEST['return'] : '');
|
||||||
if ($return_path) {
|
if ($return_path) {
|
||||||
$rand = '_=' . time();
|
$rand = '_=' . time();
|
||||||
if (strpos($return_path, '?')) {
|
if (strpos($return_path, '?')) {
|
||||||
|
|
|
@ -175,23 +175,19 @@ EOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the original post is on this site, update it.
|
// if the original post is on this site, update it.
|
||||||
|
$original_item = Item::selectFirst(['tag', 'id', 'uid'], ['origin' => true, 'uri' => $item['uri']]);
|
||||||
$r = q("SELECT `tag`,`id`,`uid` FROM `item` WHERE `origin`=1 AND `uri`='%s' LIMIT 1",
|
if (DBM::is_result($original_item)) {
|
||||||
dbesc($item['uri'])
|
|
||||||
);
|
|
||||||
|
|
||||||
if (DBM::is_result($r)) {
|
|
||||||
$x = q("SELECT `blocktags` FROM `user` WHERE `uid`=%d LIMIT 1",
|
$x = q("SELECT `blocktags` FROM `user` WHERE `uid`=%d LIMIT 1",
|
||||||
intval($r[0]['uid'])
|
intval($original_item['uid'])
|
||||||
);
|
);
|
||||||
$t = q("SELECT COUNT(`tid`) AS `tcount` FROM `term` WHERE `oid`=%d AND `term`='%s'",
|
$t = q("SELECT COUNT(`tid`) AS `tcount` FROM `term` WHERE `oid`=%d AND `term`='%s'",
|
||||||
intval($r[0]['id']),
|
intval($original_item['id']),
|
||||||
dbesc($term)
|
dbesc($term)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (DBM::is_result($x) && !$x[0]['blocktags'] && $t[0]['tcount'] == 0){
|
if (DBM::is_result($x) && !$x[0]['blocktags'] && $t[0]['tcount'] == 0){
|
||||||
q("INSERT INTO term (`oid`, `otype`, `type`, `term`, `url`, `uid`) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
q("INSERT INTO term (`oid`, `otype`, `type`, `term`, `url`, `uid`) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
||||||
intval($r[0]['id']),
|
intval($original_item['id']),
|
||||||
$term_objtype,
|
$term_objtype,
|
||||||
TERM_HASHTAG,
|
TERM_HASHTAG,
|
||||||
dbesc($term),
|
dbesc($term),
|
||||||
|
|
34
mod/xrd.php
34
mod/xrd.php
|
@ -66,20 +66,23 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
|
||||||
header("Content-type: application/json; charset=utf-8");
|
header("Content-type: application/json; charset=utf-8");
|
||||||
|
|
||||||
$json = ['subject' => $uri,
|
$json = ['subject' => $uri,
|
||||||
'aliases' => [$alias, $profile_url],
|
'aliases' => [$alias, $profile_url],
|
||||||
'links' => [['rel' => NAMESPACE_DFRN, 'href' => $profile_url],
|
'links' => [
|
||||||
['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']],
|
['rel' => NAMESPACE_DFRN, 'href' => $profile_url],
|
||||||
['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url],
|
['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']],
|
||||||
['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']],
|
['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url],
|
||||||
['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']],
|
['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']],
|
||||||
['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'],
|
['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']],
|
||||||
['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()],
|
['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'],
|
||||||
['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']],
|
['rel' => 'http://joindiaspora.com/seed_location', 'type' => 'text/html', 'href' => System::baseUrl()],
|
||||||
['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']],
|
['rel' => 'salmon', 'href' => System::baseUrl().'/salmon/'.$r['nickname']],
|
||||||
['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'],
|
['rel' => 'http://salmon-protocol.org/ns/salmon-replies', 'href' => System::baseUrl().'/salmon/'.$r['nickname']],
|
||||||
['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'],
|
['rel' => 'http://salmon-protocol.org/ns/salmon-mention', 'href' => System::baseUrl().'/salmon/'.$r['nickname'].'/mention'],
|
||||||
['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key]
|
['rel' => 'http://ostatus.org/schema/1.0/subscribe', 'template' => System::baseUrl().'/follow?url={uri}'],
|
||||||
]];
|
['rel' => 'magic-public-key', 'href' => 'data:application/magic-public-key,'.$salmon_key],
|
||||||
|
['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa']
|
||||||
|
]
|
||||||
|
];
|
||||||
echo json_encode($json);
|
echo json_encode($json);
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
|
@ -102,10 +105,11 @@ function xrd_xml($a, $uri, $alias, $profile_url, $r)
|
||||||
'$atom' => System::baseUrl() . '/dfrn_poll/' . $r['nickname'],
|
'$atom' => System::baseUrl() . '/dfrn_poll/' . $r['nickname'],
|
||||||
'$poco_url' => System::baseUrl() . '/poco/' . $r['nickname'],
|
'$poco_url' => System::baseUrl() . '/poco/' . $r['nickname'],
|
||||||
'$photo' => System::baseUrl() . '/photo/profile/' . $r['uid'] . '.jpg',
|
'$photo' => System::baseUrl() . '/photo/profile/' . $r['uid'] . '.jpg',
|
||||||
'$baseurl' => System::baseUrl(),
|
'$baseurl' => System::baseUrl(),
|
||||||
'$salmon' => System::baseUrl() . '/salmon/' . $r['nickname'],
|
'$salmon' => System::baseUrl() . '/salmon/' . $r['nickname'],
|
||||||
'$salmen' => System::baseUrl() . '/salmon/' . $r['nickname'] . '/mention',
|
'$salmen' => System::baseUrl() . '/salmon/' . $r['nickname'] . '/mention',
|
||||||
'$subscribe' => System::baseUrl() . '/follow?url={uri}',
|
'$subscribe' => System::baseUrl() . '/follow?url={uri}',
|
||||||
|
'$openwebauth' => System::baseUrl() . '/owa',
|
||||||
'$modexp' => 'data:application/magic-public-key,' . $salmon_key]
|
'$modexp' => 'data:application/magic-public-key,' . $salmon_key]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -83,10 +83,9 @@ server {
|
||||||
# rewrite to front controller as default rule
|
# rewrite to front controller as default rule
|
||||||
location / {
|
location / {
|
||||||
if (!-e $request_filename) {
|
if (!-e $request_filename) {
|
||||||
rewrite ^(.*)$ /index.php?pagename=$1;
|
rewrite ^(.*)$ /index.php?pagename=$1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# make sure webfinger and other well known services aren't blocked
|
# make sure webfinger and other well known services aren't blocked
|
||||||
# by denying dot files and rewrite request to the front controller
|
# by denying dot files and rewrite request to the front controller
|
||||||
|
@ -96,7 +95,7 @@ server {
|
||||||
rewrite ^(.*)$ /index.php?pagename=$1;
|
rewrite ^(.*)$ /index.php?pagename=$1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include mime.types;
|
include mime.types;
|
||||||
|
|
||||||
# block these file types
|
# block these file types
|
||||||
|
|
|
@ -163,17 +163,17 @@ EOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Encodes content to json
|
* @brief Encodes content to json.
|
||||||
*
|
*
|
||||||
* This function encodes an array to json format
|
* This function encodes an array to json format
|
||||||
* and adds an application/json HTTP header to the output.
|
* and adds an application/json HTTP header to the output.
|
||||||
* After finishing the process is getting killed.
|
* After finishing the process is getting killed.
|
||||||
*
|
*
|
||||||
* @param array $x The input content
|
* @param array $x The input content.
|
||||||
|
* @param string $content_type Type of the input (Default: 'application/json').
|
||||||
*/
|
*/
|
||||||
public static function jsonExit($x)
|
public static function jsonExit($x, $content_type = 'application/json') {
|
||||||
{
|
header("Content-type: $content_type");
|
||||||
header("content-type: application/json");
|
|
||||||
echo json_encode($x);
|
echo json_encode($x);
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1818,6 +1818,20 @@ class DBStructure
|
||||||
"PRIMARY" => ["uid", "iid"],
|
"PRIMARY" => ["uid", "iid"],
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
$database["openwebauth-token"] = [
|
||||||
|
"comment" => "Store OpenWebAuth token to verify contacts",
|
||||||
|
"fields" => [
|
||||||
|
"id" => ["type" => "int(10)", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||||
|
"uid" => ["type" => "int(10) unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||||
|
"type" => ["type" => "varchar(32)", "not_null", "default" => "", "comment" => "Verify type"],
|
||||||
|
"token" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => "A generated token"],
|
||||||
|
"meta" => ["type" => "varchar(255)", "not_null" => "1", "default" => "", "comment" => ""],
|
||||||
|
"created" => ["type" => "datetime", "not null" => "1", "default" => NULL_DATE, "comment" => "datetime of creation"],
|
||||||
|
],
|
||||||
|
"indexes" => [
|
||||||
|
"PRIMARY" => ["id"],
|
||||||
|
]
|
||||||
|
];
|
||||||
$database["worker-ipc"] = [
|
$database["worker-ipc"] = [
|
||||||
"comment" => "Inter process communication between the frontend and the worker",
|
"comment" => "Inter process communication between the frontend and the worker",
|
||||||
"fields" => [
|
"fields" => [
|
||||||
|
|
|
@ -56,6 +56,40 @@ class Item extends BaseObject
|
||||||
'author-id', 'author-link', 'owner-link', 'contact-uid',
|
'author-id', 'author-link', 'owner-link', 'contact-uid',
|
||||||
'signed_text', 'signature', 'signer'];
|
'signed_text', 'signature', 'signer'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fetch a single item row
|
||||||
|
*
|
||||||
|
* @param mixed $stmt statement object
|
||||||
|
* @return array current row
|
||||||
|
*/
|
||||||
|
public static function fetch($stmt)
|
||||||
|
{
|
||||||
|
$row = dba::fetch($stmt);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fills an array with data from an item query
|
||||||
|
*
|
||||||
|
* @param object $stmt statement object
|
||||||
|
* @return array Data array
|
||||||
|
*/
|
||||||
|
public static function inArray($stmt, $do_close = true) {
|
||||||
|
if (is_bool($stmt)) {
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
while ($row = self::fetch($stmt)) {
|
||||||
|
$data[] = $row;
|
||||||
|
}
|
||||||
|
if ($do_close) {
|
||||||
|
dba::close($stmt);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a single record from the item table for a given user and returns it in an associative array
|
* Retrieve a single record from the item table for a given user and returns it in an associative array
|
||||||
*
|
*
|
||||||
|
@ -118,7 +152,7 @@ class Item extends BaseObject
|
||||||
if (is_bool($result)) {
|
if (is_bool($result)) {
|
||||||
return $result;
|
return $result;
|
||||||
} else {
|
} else {
|
||||||
$row = dba::fetch($result);
|
$row = self::fetch($result);
|
||||||
dba::close($result);
|
dba::close($result);
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
@ -225,7 +259,7 @@ class Item extends BaseObject
|
||||||
if (is_bool($result)) {
|
if (is_bool($result)) {
|
||||||
return $result;
|
return $result;
|
||||||
} else {
|
} else {
|
||||||
$row = dba::fetch($result);
|
$row = self::fetch($result);
|
||||||
dba::close($result);
|
dba::close($result);
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
73
src/Model/OpenWebAuthToken.php
Normal file
73
src/Model/OpenWebAuthToken.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file src/Model/OpenWebAuthToken.php
|
||||||
|
*/
|
||||||
|
namespace Friendica\Model;
|
||||||
|
|
||||||
|
use Friendica\Database\DBM;
|
||||||
|
use Friendica\Util\DateTimeFormat;
|
||||||
|
use dba;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods to deal with entries of the 'openwebauth-token' table.
|
||||||
|
*/
|
||||||
|
class OpenWebAuthToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an entry in the 'openwebauth-token' table.
|
||||||
|
*
|
||||||
|
* @param string $type Verify type.
|
||||||
|
* @param int $uid The user ID.
|
||||||
|
* @param string $token
|
||||||
|
* @param string $meta
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function create($type, $uid, $token, $meta)
|
||||||
|
{
|
||||||
|
$fields = [
|
||||||
|
"type" => $type,
|
||||||
|
"uid" => $uid,
|
||||||
|
"token" => $token,
|
||||||
|
"meta" => $meta,
|
||||||
|
"created" => DateTimeFormat::utcNow()
|
||||||
|
];
|
||||||
|
return dba::insert("openwebauth-token", $fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the "meta" field of an entry in the openwebauth-token table.
|
||||||
|
*
|
||||||
|
* @param string $type Verify type.
|
||||||
|
* @param int $uid The user ID.
|
||||||
|
* @param string $token
|
||||||
|
*
|
||||||
|
* @return string|boolean The meta enry or false if not found.
|
||||||
|
*/
|
||||||
|
public static function getMeta($type, $uid, $token)
|
||||||
|
{
|
||||||
|
$condition = ["type" => $type, "uid" => $uid, "token" => $token];
|
||||||
|
|
||||||
|
$entry = dba::selectFirst("openwebauth-token", ["id", "meta"], $condition);
|
||||||
|
if (DBM::is_result($entry)) {
|
||||||
|
dba::delete("openwebauth-token", ["id" => $entry["id"]]);
|
||||||
|
|
||||||
|
return $entry["meta"];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge entries of a verify-type older than interval.
|
||||||
|
*
|
||||||
|
* @param string $type Verify type.
|
||||||
|
* @param string $interval SQL compatible time interval
|
||||||
|
*/
|
||||||
|
public static function purge($type, $interval)
|
||||||
|
{
|
||||||
|
$condition = ["`type` = ? AND `created` < ?", $type, DateTimeFormat::utcNow() . " - INTERVAL " . $interval];
|
||||||
|
dba::delete("openwebauth-token", $condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ use Friendica\Core\System;
|
||||||
use Friendica\Core\Worker;
|
use Friendica\Core\Worker;
|
||||||
use Friendica\Database\DBM;
|
use Friendica\Database\DBM;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Model\OpenWebAuthToken;
|
||||||
use Friendica\Protocol\Diaspora;
|
use Friendica\Protocol\Diaspora;
|
||||||
use Friendica\Util\DateTimeFormat;
|
use Friendica\Util\DateTimeFormat;
|
||||||
use Friendica\Util\Network;
|
use Friendica\Util\Network;
|
||||||
|
@ -978,27 +979,128 @@ class Profile
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the 'zrl' parameter and initiate the remote authentication.
|
||||||
|
*
|
||||||
|
* This method checks if the visitor has a public contact entry and
|
||||||
|
* redirects the visitor to his/her instance to start the magic auth (Authentication)
|
||||||
|
* process.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php
|
||||||
|
*
|
||||||
|
* @param App $a Application instance.
|
||||||
|
*/
|
||||||
public static function zrlInit(App $a)
|
public static function zrlInit(App $a)
|
||||||
{
|
{
|
||||||
$my_url = self::getMyURL();
|
$my_url = self::getMyURL();
|
||||||
$my_url = Network::isUrlValid($my_url);
|
$my_url = Network::isUrlValid($my_url);
|
||||||
|
|
||||||
if ($my_url) {
|
if ($my_url) {
|
||||||
// Is it a DDoS attempt?
|
if (!local_user()) {
|
||||||
// The check fetches the cached value from gprobe to reduce the load for this system
|
// Is it a DDoS attempt?
|
||||||
$urlparts = parse_url($my_url);
|
// The check fetches the cached value from gprobe to reduce the load for this system
|
||||||
|
$urlparts = parse_url($my_url);
|
||||||
|
|
||||||
$result = Cache::get('gprobe:' . $urlparts['host']);
|
$result = Cache::get('gprobe:' . $urlparts['host']);
|
||||||
if ((!is_null($result)) && (in_array($result['network'], [NETWORK_FEED, NETWORK_PHANTOM]))) {
|
if ((!is_null($result)) && (in_array($result['network'], [NETWORK_FEED, NETWORK_PHANTOM]))) {
|
||||||
logger('DDoS attempt detected for ' . $urlparts['host'] . ' by ' . $_SERVER['REMOTE_ADDR'] . '. server data: ' . print_r($_SERVER, true), LOGGER_DEBUG);
|
logger('DDoS attempt detected for ' . $urlparts['host'] . ' by ' . $_SERVER['REMOTE_ADDR'] . '. server data: ' . print_r($_SERVER, true), LOGGER_DEBUG);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
|
||||||
|
$arr = ['zrl' => $my_url, 'url' => $a->cmd];
|
||||||
|
Addon::callHooks('zrl_init', $arr);
|
||||||
|
|
||||||
|
// Try to find the public contact entry of the visitor.
|
||||||
|
$cid = Contact::getIdForURL($my_url);
|
||||||
|
if (!$cid) {
|
||||||
|
logger('No contact record found for ' . $my_url, LOGGER_DEBUG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contact = dba::selectFirst('contact',['id', 'url'], ['id' => $cid]);
|
||||||
|
|
||||||
|
if (DBM::is_result($contact) && remote_user() && remote_user() == $contact['id']) {
|
||||||
|
// The visitor is already authenticated.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger('Not authenticated. Invoking reverse magic-auth for ' . $my_url, LOGGER_DEBUG);
|
||||||
|
|
||||||
|
// Try to avoid recursion - but send them home to do a proper magic auth.
|
||||||
|
$query = str_replace(array('?zrl=', '&zid='), array('?rzrl=', '&rzrl='), $a->query_string);
|
||||||
|
// The other instance needs to know where to redirect.
|
||||||
|
$dest = urlencode(System::baseUrl() . '/' . $query);
|
||||||
|
|
||||||
|
// We need to extract the basebath from the profile url
|
||||||
|
// to redirect the visitors '/magic' module.
|
||||||
|
// Note: We should have the basepath of a contact also in the contact table.
|
||||||
|
$urlarr = explode('/profile/', $contact['url']);
|
||||||
|
$basepath = $urlarr[0];
|
||||||
|
|
||||||
|
if ($basepath != System::baseUrl() && !strstr($dest, '/magic') && !strstr($dest, '/rmagic')) {
|
||||||
|
goaway($basepath . '/magic' . '?f=&owa=1&dest=' . $dest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker::add(PRIORITY_LOW, 'GProbe', $my_url);
|
|
||||||
$arr = ['zrl' => $my_url, 'url' => $a->cmd];
|
|
||||||
Addon::callHooks('zrl_init', $arr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenWebAuth authentication.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php
|
||||||
|
*
|
||||||
|
* @param string $token
|
||||||
|
*/
|
||||||
|
public static function openWebAuthInit($token)
|
||||||
|
{
|
||||||
|
$a = get_app();
|
||||||
|
|
||||||
|
// Clean old OpenWebAuthToken entries.
|
||||||
|
OpenWebAuthToken::purge('owt', '3 MINUTE');
|
||||||
|
|
||||||
|
// Check if the token we got is the same one
|
||||||
|
// we have stored in the database.
|
||||||
|
$visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token);
|
||||||
|
|
||||||
|
if($visitor_handle === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the public contact entry of the visitor.
|
||||||
|
$cid = Contact::getIdForURL($visitor_handle);
|
||||||
|
if(!$cid) {
|
||||||
|
logger('owt: unable to finger ' . $visitor_handle, LOGGER_DEBUG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visitor = dba::selectFirst('contact', [], ['id' => $cid]);
|
||||||
|
|
||||||
|
// Authenticate the visitor.
|
||||||
|
$_SESSION['authenticated'] = 1;
|
||||||
|
$_SESSION['visitor_id'] = $visitor['id'];
|
||||||
|
$_SESSION['visitor_handle'] = $visitor['addr'];
|
||||||
|
$_SESSION['visitor_home'] = $visitor['url'];
|
||||||
|
|
||||||
|
$arr = [
|
||||||
|
'visitor' => $visitor,
|
||||||
|
'url' => $a->query_string
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* @hooks magic_auth_success
|
||||||
|
* Called when a magic-auth was successful.
|
||||||
|
* * \e array \b visitor
|
||||||
|
* * \e string \b url
|
||||||
|
*/
|
||||||
|
Addon::callHooks('magic_auth_success', $arr);
|
||||||
|
|
||||||
|
$a->contact = $arr['visitor'];
|
||||||
|
|
||||||
|
info(L10n::t('OpenWebAuth: %1$s welcomes %2$s', $a->get_hostname(), $visitor['name']));
|
||||||
|
|
||||||
|
logger('OpenWebAuth: auth success from ' . $visitor['addr'], LOGGER_DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
public static function zrl($s, $force = false)
|
public static function zrl($s, $force = false)
|
||||||
{
|
{
|
||||||
if (!strlen($s)) {
|
if (!strlen($s)) {
|
||||||
|
@ -1042,4 +1144,26 @@ class Profile
|
||||||
|
|
||||||
return $uid;
|
return $uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stip zrl parameter from a string.
|
||||||
|
*
|
||||||
|
* @param string $s The input string.
|
||||||
|
* @return string The zrl.
|
||||||
|
*/
|
||||||
|
public static function stripZrls($s)
|
||||||
|
{
|
||||||
|
return preg_replace('/[\?&]zrl=(.*?)([\?&]|$)/is', '', $s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stip query parameter from a string.
|
||||||
|
*
|
||||||
|
* @param string $s The input string.
|
||||||
|
* @return string The query parameter.
|
||||||
|
*/
|
||||||
|
public static function stripQueryParam($s, $param)
|
||||||
|
{
|
||||||
|
return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism', '$2', $s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
121
src/Module/Magic.php
Normal file
121
src/Module/Magic.php
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @file src/Module/Magic.php
|
||||||
|
*/
|
||||||
|
namespace Friendica\Module;
|
||||||
|
|
||||||
|
use Friendica\BaseModule;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Util\HTTPSignature;
|
||||||
|
use Friendica\Util\Network;
|
||||||
|
|
||||||
|
use dba;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic Auth (remote authentication) module.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Module/Magic.php
|
||||||
|
*/
|
||||||
|
class Magic extends BaseModule
|
||||||
|
{
|
||||||
|
public static function init()
|
||||||
|
{
|
||||||
|
$a = self::getApp();
|
||||||
|
$ret = ['success' => false, 'url' => '', 'message' => ''];
|
||||||
|
logger('magic mdule: invoked', LOGGER_DEBUG);
|
||||||
|
|
||||||
|
logger('args: ' . print_r($_REQUEST, true), LOGGER_DATA);
|
||||||
|
|
||||||
|
$addr = ((x($_REQUEST, 'addr')) ? $_REQUEST['addr'] : '');
|
||||||
|
$dest = ((x($_REQUEST, 'dest')) ? $_REQUEST['dest'] : '');
|
||||||
|
$test = ((x($_REQUEST, 'test')) ? intval($_REQUEST['test']) : 0);
|
||||||
|
$owa = ((x($_REQUEST, 'owa')) ? intval($_REQUEST['owa']) : 0);
|
||||||
|
|
||||||
|
// NOTE: I guess $dest isn't just the profile url (could be also
|
||||||
|
// other profile pages e.g. photo). We need to find a solution
|
||||||
|
// to be able to redirct to other pages than the contact profile.
|
||||||
|
$cid = Contact::getIdForURL($dest);
|
||||||
|
|
||||||
|
if (!$cid && !empty($addr)) {
|
||||||
|
$cid = Contact::getIdForURL($addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$cid) {
|
||||||
|
logger('No contact record found: ' . print_r($_REQUEST, true), LOGGER_DEBUG);
|
||||||
|
goaway($dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contact = dba::selectFirst('contact', ['id', 'nurl', 'url'], ['id' => $cid]);
|
||||||
|
|
||||||
|
// Redirect if the contact is already authenticated on this site.
|
||||||
|
if (array_key_exists('id', $a->contact) && strpos($contact['nurl'], normalise_link(self::getApp()->get_baseurl())) !== false) {
|
||||||
|
if($test) {
|
||||||
|
$ret['success'] = true;
|
||||||
|
$ret['message'] .= 'Local site - you are already authenticated.' . EOL;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger('Contact is already authenticated', LOGGER_DEBUG);
|
||||||
|
goaway($dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local_user()) {
|
||||||
|
$user = $a->user;
|
||||||
|
|
||||||
|
// OpenWebAuth
|
||||||
|
if ($owa) {
|
||||||
|
// Extract the basepath
|
||||||
|
// NOTE: we need another solution because this does only work
|
||||||
|
// for friendica contacts :-/ . We should have the basepath
|
||||||
|
// of a contact also in the contact table.
|
||||||
|
$exp = explode('/profile/', $contact['url']);
|
||||||
|
$basepath = $exp[0];
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
$headers['Accept'] = 'application/x-dfrn+json';
|
||||||
|
$headers['X-Open-Web-Auth'] = random_string();
|
||||||
|
|
||||||
|
// Create a header that is signed with the local users private key.
|
||||||
|
$headers = HTTPSignature::createSig(
|
||||||
|
'',
|
||||||
|
$headers,
|
||||||
|
$user['prvkey'],
|
||||||
|
'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->path ? '/' . $a->path : ''),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
'sha512'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to get an authentication token from the other instance.
|
||||||
|
$x = Network::curl($basepath . '/owa', false, $redirects, ['headers' => $headers]);
|
||||||
|
|
||||||
|
if ($x['success']) {
|
||||||
|
$j = json_decode($x['body'], true);
|
||||||
|
|
||||||
|
if ($j['success']) {
|
||||||
|
$token = '';
|
||||||
|
if ($j['encrypted_token']) {
|
||||||
|
// The token is encrypted. If the local user is really the one the other instance
|
||||||
|
// thinks he/she is, the token can be decrypted with the local users public key.
|
||||||
|
openssl_private_decrypt(base64url_decode($j['encrypted_token']), $token, $user['prvkey']);
|
||||||
|
} else {
|
||||||
|
$token = $j['token'];
|
||||||
|
}
|
||||||
|
$x = strpbrk($dest, '?&');
|
||||||
|
$args = (($x) ? '&owt=' . $token : '?f=&owt=' . $token);
|
||||||
|
|
||||||
|
goaway($dest . $args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goaway($dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($test) {
|
||||||
|
$ret['message'] = 'Not authenticated or invalid arguments' . EOL;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
goaway($dest);
|
||||||
|
}
|
||||||
|
}
|
91
src/Module/Owa.php
Normal file
91
src/Module/Owa.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @file src/Module/Owa.php
|
||||||
|
*/
|
||||||
|
namespace Friendica\Module;
|
||||||
|
|
||||||
|
use Friendica\BaseModule;
|
||||||
|
use Friendica\Core\System;
|
||||||
|
use Friendica\Database\DBM;
|
||||||
|
use Friendica\Model\Contact;
|
||||||
|
use Friendica\Model\OpenWebAuthToken;
|
||||||
|
use Friendica\Util\HTTPSignature;
|
||||||
|
|
||||||
|
use dba;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenWebAuth verifier and token generator
|
||||||
|
*
|
||||||
|
* See https://macgirvin.com/wiki/mike/OpenWebAuth/Home
|
||||||
|
* Requests to this endpoint should be signed using HTTP Signatures
|
||||||
|
* using the 'Authorization: Signature' authentication method
|
||||||
|
* If the signature verifies a token is returned.
|
||||||
|
*
|
||||||
|
* This token may be exchanged for an authenticated cookie.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Module/Owa.php
|
||||||
|
*/
|
||||||
|
class Owa extends BaseModule
|
||||||
|
{
|
||||||
|
public static function init()
|
||||||
|
{
|
||||||
|
|
||||||
|
$ret = [ 'success' => false ];
|
||||||
|
|
||||||
|
foreach (['REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION'] as $head) {
|
||||||
|
if (array_key_exists($head, $_SERVER) && substr(trim($_SERVER[$head]), 0, 9) === 'Signature') {
|
||||||
|
if ($head !== 'HTTP_AUTHORIZATION') {
|
||||||
|
$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[$head];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sigblock = HTTPSignature::parseSigheader($_SERVER[$head]);
|
||||||
|
if ($sigblock) {
|
||||||
|
$keyId = $sigblock['keyId'];
|
||||||
|
|
||||||
|
if ($keyId) {
|
||||||
|
// Try to find the public contact entry of the handle.
|
||||||
|
$handle = str_replace('acct:', '', $keyId);
|
||||||
|
|
||||||
|
$cid = Contact::getIdForURL($handle);
|
||||||
|
$fields = ['id', 'url', 'addr', 'pubkey'];
|
||||||
|
$condition = ['id' => $cid];
|
||||||
|
|
||||||
|
$contact = dba::selectFirst('contact', $fields, $condition);
|
||||||
|
|
||||||
|
if (DBM::is_result($contact)) {
|
||||||
|
// Try to verify the signed header with the public key of the contact record
|
||||||
|
// we have found.
|
||||||
|
$verified = HTTPSignature::verify('', $contact['pubkey']);
|
||||||
|
|
||||||
|
if ($verified && $verified['header_signed'] && $verified['header_valid']) {
|
||||||
|
logger('OWA header: ' . print_r($verified, true), LOGGER_DATA);
|
||||||
|
logger('OWA success: ' . $contact['addr'], LOGGER_DATA);
|
||||||
|
|
||||||
|
$ret['success'] = true;
|
||||||
|
$token = random_string(32);
|
||||||
|
|
||||||
|
// Store the generated token in the databe.
|
||||||
|
OpenWebAuthToken::create('owt', 0, $token, $contact['addr']);
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
// Encrypt the token with the public contacts publik key.
|
||||||
|
// Only the specific public contact will be able to encrypt it.
|
||||||
|
// At a later time, we will compare weather the token we're getting
|
||||||
|
// is really the same token we have stored in the database.
|
||||||
|
openssl_public_encrypt($token, $result, $contact['pubkey']);
|
||||||
|
$ret['encrypted_token'] = base64url_encode($result);
|
||||||
|
} else {
|
||||||
|
logger('OWA fail: ' . $contact['id'] . ' ' . $contact['addr'] . ' ' . $contact['url'], LOGGER_DEBUG);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger('Contact not found: ' . $handle, LOGGER_DEBUG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System::jsonExit($ret, 'application/x-dfrn+json');
|
||||||
|
}
|
||||||
|
}
|
|
@ -246,7 +246,7 @@ class DFRN
|
||||||
|
|
||||||
if (!empty($ids)) {
|
if (!empty($ids)) {
|
||||||
$ret = Item::select(Item::DELIVER_FIELDLIST, ['id' => $ids]);
|
$ret = Item::select(Item::DELIVER_FIELDLIST, ['id' => $ids]);
|
||||||
$items = dba::inArray($ret);
|
$items = Item::inArray($ret);
|
||||||
} else {
|
} else {
|
||||||
$items = [];
|
$items = [];
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,7 @@ class DFRN
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret = Item::select(Item::DELIVER_FIELDLIST, $condition);
|
$ret = Item::select(Item::DELIVER_FIELDLIST, $condition);
|
||||||
$items = dba::inArray($ret);
|
$items = Item::inArray($ret);
|
||||||
if (!DBM::is_result($items)) {
|
if (!DBM::is_result($items)) {
|
||||||
killme();
|
killme();
|
||||||
}
|
}
|
||||||
|
@ -938,10 +938,10 @@ class DFRN
|
||||||
|
|
||||||
if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
||||||
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
||||||
$parent = q("SELECT `guid`,`plink` FROM `item` WHERE `uri` = '%s' AND `uid` = %d", dbesc($parent_item), intval($item['uid']));
|
$parent = Item::selectFirst(['guid', 'plink'], ['uri' => $parent_item, 'uid' => $item['uid']]);
|
||||||
$attributes = ["ref" => $parent_item, "type" => "text/html",
|
$attributes = ["ref" => $parent_item, "type" => "text/html",
|
||||||
"href" => $parent[0]['plink'],
|
"href" => $parent['plink'],
|
||||||
"dfrn:diaspora_guid" => $parent[0]['guid']];
|
"dfrn:diaspora_guid" => $parent['guid']];
|
||||||
XML::addElement($doc, $entry, "thr:in-reply-to", "", $attributes);
|
XML::addElement($doc, $entry, "thr:in-reply-to", "", $attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2080,9 +2080,7 @@ class DFRN
|
||||||
'confirm' => $relocate["confirm"], 'notify' => $relocate["notify"],
|
'confirm' => $relocate["confirm"], 'notify' => $relocate["notify"],
|
||||||
'poll' => $relocate["poll"], 'site-pubkey' => $relocate["sitepubkey"]];
|
'poll' => $relocate["poll"], 'site-pubkey' => $relocate["sitepubkey"]];
|
||||||
$condition = ["(`id` = ?) OR (`nurl` = ?)", $importer["id"], normalise_link($old["url"])];
|
$condition = ["(`id` = ?) OR (`nurl` = ?)", $importer["id"], normalise_link($old["url"])];
|
||||||
dba::update('contact', $fields, $condition);
|
|
||||||
|
|
||||||
// @TODO No dba:update here?
|
|
||||||
dba::update('contact', $fields, $condition);
|
dba::update('contact', $fields, $condition);
|
||||||
|
|
||||||
Contact::updateAvatar($relocate["avatar"], $importer["importer_uid"], $importer["id"], true);
|
Contact::updateAvatar($relocate["avatar"], $importer["importer_uid"], $importer["id"], true);
|
||||||
|
@ -2163,13 +2161,8 @@ class DFRN
|
||||||
|
|
||||||
$is_a_remote_action = false;
|
$is_a_remote_action = false;
|
||||||
|
|
||||||
$r = q(
|
$parent = Item::selectFirst(['parent-uri'], ['uri' => $item["parent-uri"]]);
|
||||||
"SELECT `item`.`parent-uri` FROM `item`
|
if (DBM::is_result($parent)) {
|
||||||
WHERE `item`.`uri` = '%s'
|
|
||||||
LIMIT 1",
|
|
||||||
dbesc($item["parent-uri"])
|
|
||||||
);
|
|
||||||
if (DBM::is_result($r)) {
|
|
||||||
$r = q(
|
$r = q(
|
||||||
"SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
|
"SELECT `item`.`forum_mode`, `item`.`wall` FROM `item`
|
||||||
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
|
||||||
|
@ -2177,9 +2170,9 @@ class DFRN
|
||||||
AND `item`.`uid` = %d
|
AND `item`.`uid` = %d
|
||||||
$sql_extra
|
$sql_extra
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
dbesc($r[0]["parent-uri"]),
|
dbesc($parent["parent-uri"]),
|
||||||
dbesc($r[0]["parent-uri"]),
|
dbesc($parent["parent-uri"]),
|
||||||
dbesc($r[0]["parent-uri"]),
|
dbesc($parent["parent-uri"]),
|
||||||
intval($importer["importer_uid"])
|
intval($importer["importer_uid"])
|
||||||
);
|
);
|
||||||
if (DBM::is_result($r)) {
|
if (DBM::is_result($r)) {
|
||||||
|
@ -2320,25 +2313,15 @@ class DFRN
|
||||||
$item["gravity"] = GRAVITY_LIKE;
|
$item["gravity"] = GRAVITY_LIKE;
|
||||||
// only one like or dislike per person
|
// only one like or dislike per person
|
||||||
// splitted into two queries for performance issues
|
// splitted into two queries for performance issues
|
||||||
$r = q(
|
$condition = ['uid' => $item["uid"], 'author-id' => $item["author-id"],
|
||||||
"SELECT `id` FROM `item` WHERE `uid` = %d AND `author-id` = %d AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1",
|
'verb' => $item["verb"], 'parent-uri' => $item["parent-uri"]];
|
||||||
intval($item["uid"]),
|
if (dba::exists('item', $condition)) {
|
||||||
intval($item["author-id"]),
|
|
||||||
dbesc($item["verb"]),
|
|
||||||
dbesc($item["parent-uri"])
|
|
||||||
);
|
|
||||||
if (DBM::is_result($r)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$r = q(
|
$condition = ['uid' => $item["uid"], 'author-id' => $item["author-id"],
|
||||||
"SELECT `id` FROM `item` WHERE `uid` = %d AND `author-id` = %d AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1",
|
'verb' => $item["verb"], 'thr-parent' => $item["parent-uri"]];
|
||||||
intval($item["uid"]),
|
if (dba::exists('item', $condition)) {
|
||||||
intval($item["author-id"]),
|
|
||||||
dbesc($item["verb"]),
|
|
||||||
dbesc($item["parent-uri"])
|
|
||||||
);
|
|
||||||
if (DBM::is_result($r)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2350,22 +2333,17 @@ class DFRN
|
||||||
$xt = XML::parseString($item["target"], false);
|
$xt = XML::parseString($item["target"], false);
|
||||||
|
|
||||||
if ($xt->type == ACTIVITY_OBJ_NOTE) {
|
if ($xt->type == ACTIVITY_OBJ_NOTE) {
|
||||||
$r = q(
|
$item_tag = Item::selectFirst(['id', 'tag'], ['uri' => $xt->id, 'uid' => $importer["importer_uid"]]);
|
||||||
"SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
|
if (!DBM::is_result($item_tag)) {
|
||||||
dbesc($xt->id),
|
|
||||||
intval($importer["importer_uid"])
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!DBM::is_result($r)) {
|
|
||||||
logger("Query failed to execute, no result returned in " . __FUNCTION__);
|
logger("Query failed to execute, no result returned in " . __FUNCTION__);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract tag, if not duplicate, add to parent item
|
// extract tag, if not duplicate, add to parent item
|
||||||
if ($xo->content) {
|
if ($xo->content) {
|
||||||
if (!stristr($r[0]["tag"], trim($xo->content))) {
|
if (!stristr($item_tag["tag"], trim($xo->content))) {
|
||||||
$tag = $r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
$tag = $item_tag["tag"] . (strlen($item_tag["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
||||||
Item::update(['tag' => $tag], ['id' => $r[0]["id"]]);
|
Item::update(['tag' => $tag], ['id' => $item_tag["id"]]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2218,7 +2218,7 @@ class Diaspora
|
||||||
|
|
||||||
// Send all existing comments and likes to the requesting server
|
// Send all existing comments and likes to the requesting server
|
||||||
$comments = Item::select(['id', 'verb', 'self'], ['parent' => $item['id']]);
|
$comments = Item::select(['id', 'verb', 'self'], ['parent' => $item['id']]);
|
||||||
while ($comment = dba::fetch($comments)) {
|
while ($comment = Item::fetch($comments)) {
|
||||||
if ($comment['id'] == $comment['parent']) {
|
if ($comment['id'] == $comment['parent']) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2771,7 +2771,7 @@ class Diaspora
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ($item = dba::fetch($r)) {
|
while ($item = Item::fetch($r)) {
|
||||||
// Fetch the parent item
|
// Fetch the parent item
|
||||||
$parent = Item::selectFirst(['author-link'], ['id' => $item["parent"]]);
|
$parent = Item::selectFirst(['author-link'], ['id' => $item["parent"]]);
|
||||||
|
|
||||||
|
|
|
@ -2152,7 +2152,7 @@ class OStatus
|
||||||
$ret = Item::select([], $condition, $params);
|
$ret = Item::select([], $condition, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = dba::inArray($ret);
|
$items = Item::inArray($ret);
|
||||||
|
|
||||||
$doc = new DOMDocument('1.0', 'utf-8');
|
$doc = new DOMDocument('1.0', 'utf-8');
|
||||||
$doc->formatOutput = true;
|
$doc->formatOutput = true;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
namespace Friendica\Util;
|
namespace Friendica\Util;
|
||||||
|
|
||||||
|
use Friendica\Core\Addon;
|
||||||
use Friendica\Core\Config;
|
use Friendica\Core\Config;
|
||||||
use ASN_BASE;
|
use ASN_BASE;
|
||||||
use ASNValue;
|
use ASNValue;
|
||||||
|
@ -246,4 +247,232 @@ class Crypto
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a string with 'aes-256-cbc' cipher method.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $key The key used for encryption.
|
||||||
|
* @param string $iv A non-NULL Initialization Vector.
|
||||||
|
*
|
||||||
|
* @return string|boolean Encrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function encryptAES256CBC($data, $key, $iv)
|
||||||
|
{
|
||||||
|
return openssl_encrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a string with 'aes-256-cbc' cipher method.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $key The key used for decryption.
|
||||||
|
* @param string $iv A non-NULL Initialization Vector.
|
||||||
|
*
|
||||||
|
* @return string|boolean Decrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function decryptAES256CBC($data, $key, $iv)
|
||||||
|
{
|
||||||
|
return openssl_decrypt($data, 'aes-256-cbc', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a string with 'aes-256-ctr' cipher method.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $key The key used for encryption.
|
||||||
|
* @param string $iv A non-NULL Initialization Vector.
|
||||||
|
*
|
||||||
|
* @return string|boolean Encrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function encryptAES256CTR($data, $key, $iv)
|
||||||
|
{
|
||||||
|
$key = substr($key, 0, 32);
|
||||||
|
$iv = substr($iv, 0, 16);
|
||||||
|
return openssl_encrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a string with 'aes-256-ctr' cipher method.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $key The key used for decryption.
|
||||||
|
* @param string $iv A non-NULL Initialization Vector.
|
||||||
|
*
|
||||||
|
* @return string|boolean Decrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function decryptAES256CTR($data, $key, $iv)
|
||||||
|
{
|
||||||
|
$key = substr($key, 0, 32);
|
||||||
|
$iv = substr($iv, 0, 16);
|
||||||
|
return openssl_decrypt($data, 'aes-256-ctr', str_pad($key, 32, "\0"), OPENSSL_RAW_DATA, str_pad($iv, 16, "\0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $pubkey The public key.
|
||||||
|
* @param string $alg The algorithm used for encryption.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function encapsulate($data, $pubkey, $alg = 'aes256cbc')
|
||||||
|
{
|
||||||
|
if ($alg === 'aes256cbc') {
|
||||||
|
return self::encapsulateAes($data, $pubkey);
|
||||||
|
}
|
||||||
|
return self::encapsulateOther($data, $pubkey, $alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param type $data
|
||||||
|
* @param type $pubkey The public key.
|
||||||
|
* @param type $alg The algorithm used for encryption.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function encapsulateOther($data, $pubkey, $alg)
|
||||||
|
{
|
||||||
|
if (!$pubkey) {
|
||||||
|
logger('no key. data: '.$data);
|
||||||
|
}
|
||||||
|
$fn = 'encrypt' . strtoupper($alg);
|
||||||
|
if (method_exists(__CLASS__, $fn)) {
|
||||||
|
$result = ['encrypted' => true];
|
||||||
|
$key = random_bytes(256);
|
||||||
|
$iv = random_bytes(256);
|
||||||
|
$result['data'] = base64url_encode(self::$fn($data, $key, $iv), true);
|
||||||
|
|
||||||
|
// log the offending call so we can track it down
|
||||||
|
if (!openssl_public_encrypt($key, $k, $pubkey)) {
|
||||||
|
$x = debug_backtrace();
|
||||||
|
logger('RSA failed. ' . print_r($x[0], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['alg'] = $alg;
|
||||||
|
$result['key'] = base64url_encode($k, true);
|
||||||
|
openssl_public_encrypt($iv, $i, $pubkey);
|
||||||
|
$result['iv'] = base64url_encode($i, true);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
} else {
|
||||||
|
$x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data];
|
||||||
|
Addon::callHooks('other_encapsulate', $x);
|
||||||
|
|
||||||
|
return $x['result'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $pubkey
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function encapsulateAes($data, $pubkey)
|
||||||
|
{
|
||||||
|
if (!$pubkey) {
|
||||||
|
logger('aes_encapsulate: no key. data: ' . $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = random_bytes(32);
|
||||||
|
$iv = random_bytes(16);
|
||||||
|
$result = ['encrypted' => true];
|
||||||
|
$result['data'] = base64url_encode(self::encryptAES256CBC($data, $key, $iv), true);
|
||||||
|
|
||||||
|
// log the offending call so we can track it down
|
||||||
|
if (!openssl_public_encrypt($key, $k, $pubkey)) {
|
||||||
|
$x = debug_backtrace();
|
||||||
|
logger('aes_encapsulate: RSA failed. ' . print_r($x[0], true));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['alg'] = 'aes256cbc';
|
||||||
|
$result['key'] = base64url_encode($k, true);
|
||||||
|
openssl_public_encrypt($iv, $i, $pubkey);
|
||||||
|
$result['iv'] = base64url_encode($i, true);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $prvkey The private key used for decryption.
|
||||||
|
*
|
||||||
|
* @return string|boolean The decrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
public static function unencapsulate($data, $prvkey)
|
||||||
|
{
|
||||||
|
if (!$data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alg = ((array_key_exists('alg', $data)) ? $data['alg'] : 'aes256cbc');
|
||||||
|
if ($alg === 'aes256cbc') {
|
||||||
|
return self::encapsulateAes($data, $prvkey);
|
||||||
|
}
|
||||||
|
return self::encapsulateOther($data, $prvkey, $alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param string $data
|
||||||
|
* @param string $prvkey The private key used for decryption.
|
||||||
|
* @param string $alg
|
||||||
|
*
|
||||||
|
* @return string|boolean The decrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function unencapsulateOther($data, $prvkey, $alg)
|
||||||
|
{
|
||||||
|
$fn = 'decrypt' . strtoupper($alg);
|
||||||
|
|
||||||
|
if (method_exists(__CLASS__, $fn)) {
|
||||||
|
openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
|
||||||
|
openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
|
||||||
|
|
||||||
|
return self::$fn(base64url_decode($data['data']), $k, $i);
|
||||||
|
} else {
|
||||||
|
$x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $alg, 'result' => $data];
|
||||||
|
Addon::callHooks('other_unencapsulate', $x);
|
||||||
|
|
||||||
|
return $x['result'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param string $prvkey The private key used for decryption.
|
||||||
|
*
|
||||||
|
* @return string|boolean The decrypted string or false on failure.
|
||||||
|
*/
|
||||||
|
private static function unencapsulateAes($data, $prvkey)
|
||||||
|
{
|
||||||
|
openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey);
|
||||||
|
openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey);
|
||||||
|
|
||||||
|
return self::decryptAES256CBC(base64url_decode($data['data']), $k, $i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
48
src/Util/HTTPHeaders.php
Normal file
48
src/Util/HTTPHeaders.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @file src/Util/HTTPHeaders.php
|
||||||
|
*/
|
||||||
|
namespace Friendica\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPHeaders.php
|
||||||
|
*/
|
||||||
|
class HTTPHeaders
|
||||||
|
{
|
||||||
|
private $in_progress = [];
|
||||||
|
private $parsed = [];
|
||||||
|
|
||||||
|
function __construct($headers)
|
||||||
|
{
|
||||||
|
$lines = explode("\n", str_replace("\r", '', $headers));
|
||||||
|
|
||||||
|
if ($lines) {
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (preg_match('/^\s+/', $line, $matches) && trim($line)) {
|
||||||
|
if (!empty($this->in_progress['k'])) {
|
||||||
|
$this->in_progress['v'] .= ' ' . ltrim($line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!empty($this->in_progress['k'])) {
|
||||||
|
$this->parsed[] = [$this->in_progress['k'] => $this->in_progress['v']];
|
||||||
|
$this->in_progress = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->in_progress['k'] = strtolower(substr($line, 0, strpos($line, ':')));
|
||||||
|
$this->in_progress['v'] = ltrim(substr($line, strpos($line, ':') + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->in_progress['k'])) {
|
||||||
|
$this->parsed[$this->in_progress['k']] = $this->in_progress['v'];
|
||||||
|
$this->in_progress = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch()
|
||||||
|
{
|
||||||
|
return $this->parsed;
|
||||||
|
}
|
||||||
|
}
|
409
src/Util/HTTPSignature.php
Normal file
409
src/Util/HTTPSignature.php
Normal file
|
@ -0,0 +1,409 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file src/Util/HTTPSignature.php
|
||||||
|
*/
|
||||||
|
namespace Friendica\Util;
|
||||||
|
|
||||||
|
use Friendica\Core\Config;
|
||||||
|
use Friendica\Database\DBM;
|
||||||
|
use Friendica\Util\Crypto;
|
||||||
|
use Friendica\Util\HTTPHeaders;
|
||||||
|
use dba;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Implements HTTP Signatures per draft-cavage-http-signatures-07.
|
||||||
|
*
|
||||||
|
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/draft-cavage-http-signatures-07
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HTTPSignature
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief RFC5843
|
||||||
|
*
|
||||||
|
* Disabled until Friendica's ActivityPub implementation
|
||||||
|
* is ready.
|
||||||
|
*
|
||||||
|
* @see https://tools.ietf.org/html/rfc5843
|
||||||
|
*
|
||||||
|
* @param string $body The value to create the digest for
|
||||||
|
* @param boolean $set (optional, default true)
|
||||||
|
* If set send a Digest HTTP header
|
||||||
|
*
|
||||||
|
* @return string The generated digest of $body
|
||||||
|
*/
|
||||||
|
// public static function generateDigest($body, $set = true)
|
||||||
|
// {
|
||||||
|
// $digest = base64_encode(hash('sha256', $body, true));
|
||||||
|
//
|
||||||
|
// if($set) {
|
||||||
|
// header('Digest: SHA-256=' . $digest);
|
||||||
|
// }
|
||||||
|
// return $digest;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// See draft-cavage-http-signatures-08
|
||||||
|
public static function verify($data, $key = '')
|
||||||
|
{
|
||||||
|
$body = $data;
|
||||||
|
$headers = null;
|
||||||
|
$spoofable = false;
|
||||||
|
$result = [
|
||||||
|
'signer' => '',
|
||||||
|
'header_signed' => false,
|
||||||
|
'header_valid' => false,
|
||||||
|
'content_signed' => false,
|
||||||
|
'content_valid' => false
|
||||||
|
];
|
||||||
|
|
||||||
|
// Decide if $data arrived via controller submission or curl.
|
||||||
|
if (is_array($data) && $data['header']) {
|
||||||
|
if (!$data['success']) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$h = new HTTPHeaders($data['header']);
|
||||||
|
$headers = $h->fetch();
|
||||||
|
$body = $data['body'];
|
||||||
|
} else {
|
||||||
|
$headers = [];
|
||||||
|
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI'];
|
||||||
|
|
||||||
|
foreach ($_SERVER as $k => $v) {
|
||||||
|
if (strpos($k, 'HTTP_') === 0) {
|
||||||
|
$field = str_replace('_', '-', strtolower(substr($k, 5)));
|
||||||
|
$headers[$field] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sig_block = null;
|
||||||
|
|
||||||
|
if (array_key_exists('signature', $headers)) {
|
||||||
|
$sig_block = self::parseSigheader($headers['signature']);
|
||||||
|
} elseif (array_key_exists('authorization', $headers)) {
|
||||||
|
$sig_block = self::parseSigheader($headers['authorization']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$sig_block) {
|
||||||
|
logger('no signature provided.');
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning: This log statement includes binary data
|
||||||
|
// logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA);
|
||||||
|
|
||||||
|
$result['header_signed'] = true;
|
||||||
|
|
||||||
|
$signed_headers = $sig_block['headers'];
|
||||||
|
if (!$signed_headers) {
|
||||||
|
$signed_headers = ['date'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$signed_data = '';
|
||||||
|
foreach ($signed_headers as $h) {
|
||||||
|
if (array_key_exists($h, $headers)) {
|
||||||
|
$signed_data .= $h . ': ' . $headers[$h] . "\n";
|
||||||
|
}
|
||||||
|
if (strpos($h, '.')) {
|
||||||
|
$spoofable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$signed_data = rtrim($signed_data, "\n");
|
||||||
|
|
||||||
|
$algorithm = null;
|
||||||
|
if ($sig_block['algorithm'] === 'rsa-sha256') {
|
||||||
|
$algorithm = 'sha256';
|
||||||
|
}
|
||||||
|
if ($sig_block['algorithm'] === 'rsa-sha512') {
|
||||||
|
$algorithm = 'sha512';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($key && function_exists($key)) {
|
||||||
|
$result['signer'] = $sig_block['keyId'];
|
||||||
|
$key = $key($sig_block['keyId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use Activity Pub at the moment.
|
||||||
|
// if (!$key) {
|
||||||
|
// $result['signer'] = $sig_block['keyId'];
|
||||||
|
// $key = self::getActivitypubKey($sig_block['keyId']);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!$key) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = Crypto::rsaVerify($signed_data, $sig_block['signature'], $key, $algorithm);
|
||||||
|
|
||||||
|
logger('verified: ' . $x, LOGGER_DEBUG);
|
||||||
|
|
||||||
|
if (!$x) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$spoofable) {
|
||||||
|
$result['header_valid'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('digest', $signed_headers)) {
|
||||||
|
$result['content_signed'] = true;
|
||||||
|
$digest = explode('=', $headers['digest']);
|
||||||
|
|
||||||
|
if ($digest[0] === 'SHA-256') {
|
||||||
|
$hashalg = 'sha256';
|
||||||
|
}
|
||||||
|
if ($digest[0] === 'SHA-512') {
|
||||||
|
$hashalg = 'sha512';
|
||||||
|
}
|
||||||
|
|
||||||
|
// The explode operation will have stripped the '=' padding, so compare against unpadded base64.
|
||||||
|
if (rtrim(base64_encode(hash($hashalg, $body, true)), '=') === $digest[1]) {
|
||||||
|
$result['content_valid'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger('Content_Valid: ' . $result['content_valid']);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the public key for Activity Pub contact.
|
||||||
|
*
|
||||||
|
* @param string|int The identifier (contact addr or contact ID).
|
||||||
|
* @return string|boolean The public key or false on failure.
|
||||||
|
*/
|
||||||
|
private static function getActivitypubKey($id)
|
||||||
|
{
|
||||||
|
if (strpos($id, 'acct:') === 0) {
|
||||||
|
$contact = dba::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]);
|
||||||
|
} else {
|
||||||
|
$contact = dba::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBM::is_result($contact)) {
|
||||||
|
return $contact['pubkey'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(function_exists('as_fetch')) {
|
||||||
|
$r = as_fetch($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($r) {
|
||||||
|
$j = json_decode($r, true);
|
||||||
|
|
||||||
|
if (array_key_exists('publicKey', $j) && array_key_exists('publicKeyPem', $j['publicKey'])) {
|
||||||
|
if ((array_key_exists('id', $j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $j['publicKey']['publicKeyPem'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @param string $request
|
||||||
|
* @param array $head
|
||||||
|
* @param string $prvkey
|
||||||
|
* @param string $keyid (optional, default 'Key')
|
||||||
|
* @param boolean $send_headers (optional, default false)
|
||||||
|
* If set send a HTTP header
|
||||||
|
* @param boolean $auth (optional, default false)
|
||||||
|
* @param string $alg (optional, default 'sha256')
|
||||||
|
* @param string $crypt_key (optional, default null)
|
||||||
|
* @param string $crypt_algo (optional, default 'aes256ctr')
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function createSig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr')
|
||||||
|
{
|
||||||
|
$return_headers = [];
|
||||||
|
|
||||||
|
if ($alg === 'sha256') {
|
||||||
|
$algorithm = 'rsa-sha256';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($alg === 'sha512') {
|
||||||
|
$algorithm = 'rsa-sha512';
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = self::sign($request, $head, $prvkey, $alg);
|
||||||
|
|
||||||
|
$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm
|
||||||
|
. '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"';
|
||||||
|
|
||||||
|
if ($crypt_key) {
|
||||||
|
$x = Crypto::encapsulate($headerval, $crypt_key, $crypt_algo);
|
||||||
|
$headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($auth) {
|
||||||
|
$sighead = 'Authorization: Signature ' . $headerval;
|
||||||
|
} else {
|
||||||
|
$sighead = 'Signature: ' . $headerval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($head) {
|
||||||
|
foreach ($head as $k => $v) {
|
||||||
|
if ($send_headers) {
|
||||||
|
// This is for ActivityPub implementation.
|
||||||
|
// Since the Activity Pub implementation isn't
|
||||||
|
// ready at the moment, we comment it out.
|
||||||
|
// header($k . ': ' . $v);
|
||||||
|
} else {
|
||||||
|
$return_headers[] = $k . ': ' . $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($send_headers) {
|
||||||
|
// This is for ActivityPub implementation.
|
||||||
|
// Since the Activity Pub implementation isn't
|
||||||
|
// ready at the moment, we comment it out.
|
||||||
|
// header($sighead);
|
||||||
|
} else {
|
||||||
|
$return_headers[] = $sighead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @param string $request
|
||||||
|
* @param array $head
|
||||||
|
* @param string $prvkey
|
||||||
|
* @param string $alg (optional) default 'sha256'
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function sign($request, $head, $prvkey, $alg = 'sha256')
|
||||||
|
{
|
||||||
|
$ret = [];
|
||||||
|
$headers = '';
|
||||||
|
$fields = '';
|
||||||
|
|
||||||
|
if ($request) {
|
||||||
|
$headers = '(request-target)' . ': ' . trim($request) . "\n";
|
||||||
|
$fields = '(request-target)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($head) {
|
||||||
|
foreach ($head as $k => $v) {
|
||||||
|
$headers .= strtolower($k) . ': ' . trim($v) . "\n";
|
||||||
|
if ($fields) {
|
||||||
|
$fields .= ' ';
|
||||||
|
}
|
||||||
|
$fields .= strtolower($k);
|
||||||
|
}
|
||||||
|
// strip the trailing linefeed
|
||||||
|
$headers = rtrim($headers, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg));
|
||||||
|
|
||||||
|
$ret['headers'] = $fields;
|
||||||
|
$ret['signature'] = $sig;
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @param string $header
|
||||||
|
* @return array associate array with
|
||||||
|
* - \e string \b keyID
|
||||||
|
* - \e string \b algorithm
|
||||||
|
* - \e array \b headers
|
||||||
|
* - \e string \b signature
|
||||||
|
*/
|
||||||
|
public static function parseSigheader($header)
|
||||||
|
{
|
||||||
|
$ret = [];
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
// if the header is encrypted, decrypt with (default) site private key and continue
|
||||||
|
if (preg_match('/iv="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$header = self::decryptSigheader($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/keyId="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$ret['keyId'] = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/algorithm="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$ret['algorithm'] = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/headers="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$ret['headers'] = explode(' ', $matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/signature="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$ret['signature'] = base64_decode(preg_replace('/\s+/', '', $matches[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($ret['signature']) && ($ret['algorithm']) && (!$ret['headers'])) {
|
||||||
|
$ret['headers'] = ['date'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*
|
||||||
|
* @param string $header
|
||||||
|
* @param string $prvkey (optional), if not set use site private key
|
||||||
|
*
|
||||||
|
* @return array|string associative array, empty string if failue
|
||||||
|
* - \e string \b iv
|
||||||
|
* - \e string \b key
|
||||||
|
* - \e string \b alg
|
||||||
|
* - \e string \b data
|
||||||
|
*/
|
||||||
|
private static function decryptSigheader($header, $prvkey = null)
|
||||||
|
{
|
||||||
|
$iv = $key = $alg = $data = null;
|
||||||
|
|
||||||
|
if (!$prvkey) {
|
||||||
|
$prvkey = Config::get('system', 'prvkey');
|
||||||
|
}
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
if (preg_match('/iv="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$iv = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/key="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$key = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/alg="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$alg = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/data="(.*?)"/ism', $header, $matches)) {
|
||||||
|
$data = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iv && $key && $alg && $data) {
|
||||||
|
return Crypto::unencapsulate(['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data], $prvkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
|
@ -323,11 +323,12 @@ class DBClean {
|
||||||
Config::set('system', 'dbclean-last-id-9', $last_id);
|
Config::set('system', 'dbclean-last-id-9', $last_id);
|
||||||
} elseif ($stage == 10) {
|
} elseif ($stage == 10) {
|
||||||
$last_id = Config::get('system', 'dbclean-last-id-10', 0);
|
$last_id = Config::get('system', 'dbclean-last-id-10', 0);
|
||||||
|
$days = intval(Config::get('system', 'dbclean_expire_conversation', 90));
|
||||||
|
|
||||||
logger("Deleting old conversations. Last created: ".$last_id);
|
logger("Deleting old conversations. Last created: ".$last_id);
|
||||||
$r = dba::p("SELECT `received`, `item-uri` FROM `conversation`
|
$r = dba::p("SELECT `received`, `item-uri` FROM `conversation`
|
||||||
WHERE `received` < UTC_TIMESTAMP() - INTERVAL 90 DAY
|
WHERE `received` < UTC_TIMESTAMP() - INTERVAL ? DAY
|
||||||
ORDER BY `received` LIMIT ".intval($limit));
|
ORDER BY `received` LIMIT ".intval($limit), $days);
|
||||||
$count = dba::num_rows($r);
|
$count = dba::num_rows($r);
|
||||||
if ($count > 0) {
|
if ($count > 0) {
|
||||||
logger("found old conversations: ".$count);
|
logger("found old conversations: ".$count);
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Delivery extends BaseObject
|
||||||
$itemdata = Item::select([], $condition, $params);
|
$itemdata = Item::select([], $condition, $params);
|
||||||
|
|
||||||
$items = [];
|
$items = [];
|
||||||
while ($item = dba::fetch($itemdata)) {
|
while ($item = Item::fetch($itemdata)) {
|
||||||
if ($item['id'] == $parent_id) {
|
if ($item['id'] == $parent_id) {
|
||||||
$parent = $item;
|
$parent = $item;
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ class Notifier {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = dba::inArray($ret);
|
$items = Item::inArray($ret);
|
||||||
|
|
||||||
// avoid race condition with deleting entries
|
// avoid race condition with deleting entries
|
||||||
if ($items[0]['deleted']) {
|
if ($items[0]['deleted']) {
|
||||||
|
|
|
@ -438,12 +438,10 @@ class OnePoll
|
||||||
$refs_arr[$x] = "'" . Email::msgid2iri(str_replace(['<', '>', ' '],['', '', ''],dbesc($refs_arr[$x]))) . "'";
|
$refs_arr[$x] = "'" . Email::msgid2iri(str_replace(['<', '>', ' '],['', '', ''],dbesc($refs_arr[$x]))) . "'";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$qstr = implode(',', $refs_arr);
|
$condition = ['uri' => $refs_arr, 'uid' => $importer_uid];
|
||||||
$r = q("SELECT `parent-uri` FROM `item` USE INDEX (`uid_uri`) WHERE `uri` IN ($qstr) AND `uid` = %d LIMIT 1",
|
$parent = Item::selectFirst(['parent-uri'], $condition);
|
||||||
intval($importer_uid)
|
if (DBM::is_result($parent)) {
|
||||||
);
|
$datarray['parent-uri'] = $parent['parent-uri']; // Set the parent as the top-level item
|
||||||
if (DBM::is_result($r)) {
|
|
||||||
$datarray['parent-uri'] = $r[0]['parent-uri']; // Set the parent as the top-level item
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,12 +470,11 @@ class OnePoll
|
||||||
|
|
||||||
// If it seems to be a reply but a header couldn't be found take the last message with matching subject
|
// If it seems to be a reply but a header couldn't be found take the last message with matching subject
|
||||||
if (empty($datarray['parent-uri']) && $reply) {
|
if (empty($datarray['parent-uri']) && $reply) {
|
||||||
$r = q("SELECT `parent-uri` FROM `item` WHERE `title` = \"%s\" AND `uid` = %d AND `network` = '%s' ORDER BY `created` DESC LIMIT 1",
|
$condition = ['title' => $datarray['title'], 'uid' => importer_uid, 'network' => NETWORK_MAIL];
|
||||||
dbesc(protect_sprintf($datarray['title'])),
|
$params = ['order' => ['created' => true]];
|
||||||
intval($importer_uid),
|
$parent = Item::selectFirst(['parent-uri'], $condition, $params);
|
||||||
dbesc(NETWORK_MAIL));
|
if (DBM::is_result($parent)) {
|
||||||
if (DBM::is_result($r)) {
|
$datarray['parent-uri'] = $parent['parent-uri'];
|
||||||
$datarray['parent-uri'] = $r[0]['parent-uri'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class TagUpdate
|
||||||
|
|
||||||
dba::close($messages);
|
dba::close($messages);
|
||||||
|
|
||||||
$messages = dba::p("SELECT `guid` FROM `item` WHERE `uid` = 0");
|
$messages = dba::select('item', ['guid'], ['uid' => 0]);
|
||||||
|
|
||||||
logger('fetched messages: ' . dba::num_rows($messages));
|
logger('fetched messages: ' . dba::num_rows($messages));
|
||||||
while ($message = dba::fetch(messages)) {
|
while ($message = dba::fetch(messages)) {
|
||||||
|
|
|
@ -33,4 +33,7 @@
|
||||||
template="{{$subscribe}}" />
|
template="{{$subscribe}}" />
|
||||||
<Link rel="magic-public-key"
|
<Link rel="magic-public-key"
|
||||||
href="{{$modexp}}" />
|
href="{{$modexp}}" />
|
||||||
|
<Link rel="http://purl.org/openwebauth/v1"
|
||||||
|
type="application/x-dfrn+json"
|
||||||
|
href="{{$openwebauth}}" />
|
||||||
</XRD>
|
</XRD>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
var introID = location.pathname.split("/").pop();
|
var introID = location.pathname.split("/").pop();
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
// Since only the DIV's inside the notification-list are marked
|
// Since only the DIV's inside the notification-list are marked
|
||||||
// with the class "unseen", we need some js to transfer this class
|
// with the class "unseen", we need some js to transfer this class
|
||||||
// to the parent li list-elements.
|
// to the parent li list-elements.
|
||||||
if($(".notif-item").hasClass("unseen")) {
|
if($(".notif-item").hasClass("unseen")) {
|
||||||
|
|
|
@ -66,22 +66,22 @@
|
||||||
<li id="nav-notifications-mark-all" class="toolbar"><a href="#" onclick="notifyMarkAll(); return false;" title="{{$nav.notifications.mark.3}}"><span class="icon s10 edit"></span></a></a><a href="{{$nav.notifications.all.0}}" title="{{$nav.notifications.all.1}}"><span class="icon s10 plugin"></span></a></li>
|
<li id="nav-notifications-mark-all" class="toolbar"><a href="#" onclick="notifyMarkAll(); return false;" title="{{$nav.notifications.mark.3}}"><span class="icon s10 edit"></span></a></a><a href="{{$nav.notifications.all.0}}" title="{{$nav.notifications.all.1}}"><span class="icon s10 plugin"></span></a></li>
|
||||||
<li class="empty">{{$emptynotifications}}</li>
|
<li class="empty">{{$emptynotifications}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<li id="nav-site-linkmenu" class="nav-menu-icon"><a href="#" rel="#nav-site-menu"><span class="icon s22 gear">Site</span></a>
|
<li id="nav-site-linkmenu" class="nav-menu-icon"><a href="#" rel="#nav-site-menu"><span class="icon s22 gear">Site</span></a>
|
||||||
<ul id="nav-site-menu" class="menu-popup">
|
<ul id="nav-site-menu" class="menu-popup">
|
||||||
{{if $nav.manage}}<li><a class="{{$nav.manage.2}}" href="{{$nav.manage.0}}" title="{{$nav.manage.3}}">{{$nav.manage.1}}</a></li>{{/if}}
|
{{if $nav.manage}}<li><a class="{{$nav.manage.2}}" href="{{$nav.manage.0}}" title="{{$nav.manage.3}}">{{$nav.manage.1}}</a></li>{{/if}}
|
||||||
|
|
||||||
{{if $nav.settings}}<li><a class="{{$nav.settings.2}}" href="{{$nav.settings.0}}" title="{{$nav.settings.3}}">{{$nav.settings.1}}</a></li>{{/if}}
|
{{if $nav.settings}}<li><a class="{{$nav.settings.2}}" href="{{$nav.settings.0}}" title="{{$nav.settings.3}}">{{$nav.settings.1}}</a></li>{{/if}}
|
||||||
{{if $nav.admin}}<li><a accesskey="a" class="{{$nav.admin.2}}" href="{{$nav.admin.0}}" title="{{$nav.admin.3}}" >{{$nav.admin.1}}</a></li>{{/if}}
|
{{if $nav.admin}}<li><a accesskey="a" class="{{$nav.admin.2}}" href="{{$nav.admin.0}}" title="{{$nav.admin.3}}" >{{$nav.admin.1}}</a></li>{{/if}}
|
||||||
|
|
||||||
{{if $nav.logout}}<li><a class="menu-sep {{$nav.logout.2}}" href="{{$nav.logout.0}}" title="{{$nav.logout.3}}" >{{$nav.logout.1}}</a></li>{{/if}}
|
{{if $nav.logout}}<li><a class="menu-sep {{$nav.logout.2}}" href="{{$nav.logout.0}}" title="{{$nav.logout.3}}" >{{$nav.logout.1}}</a></li>{{/if}}
|
||||||
{{if $nav.login}}<li><a class="{{$nav.login.2}}" href="{{$nav.login.0}}" title="{{$nav.login.3}}" >{{$nav.login.1}}</a><li>{{/if}}
|
{{if $nav.login}}<li><a class="{{$nav.login.2}}" href="{{$nav.login.0}}" title="{{$nav.login.3}}" >{{$nav.login.1}}</a><li>{{/if}}
|
||||||
{{if $nav.tos}}<li><a class="menu-sep {{$nav.tos.2}}" href="{{$nav.tos.0}}" title="{{$nav.tos.3}}">{{$nav.tos.1}}</a></li>{{/if}}
|
{{if $nav.tos}}<li><a class="menu-sep {{$nav.tos.2}}" href="{{$nav.tos.0}}" title="{{$nav.tos.3}}">{{$nav.tos.1}}</a></li>{{/if}}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{if $nav.help}}
|
{{if $nav.help}}
|
||||||
<li id="nav-help-link" class="nav-menu {{$sel.help}}">
|
<li id="nav-help-link" class="nav-menu {{$sel.help}}">
|
||||||
<a class="{{$nav.help.2}}" target="friendica-help" href="{{$nav.help.0}}" title="{{$nav.help.3}}" >{{$nav.help.1}}</a>
|
<a class="{{$nav.help.2}}" target="friendica-help" href="{{$nav.help.0}}" title="{{$nav.help.3}}" >{{$nav.help.1}}</a>
|
||||||
|
|
Loading…
Reference in a new issue