Merge branch 'develop' into task/3954-move-auth-to-src

This commit is contained in:
Hypolite Petovan 2018-01-02 19:30:41 -05:00
commit 9a3e773a9a
54 changed files with 2172 additions and 2028 deletions

View file

@ -37,6 +37,7 @@ class App {
public $query_string;
public $config;
public $page;
public $page_offset;
public $profile;
public $profile_uid;
public $user;

355
src/Content/OEmbed.php Normal file
View file

@ -0,0 +1,355 @@
<?php
/**
* @file src/Content/OEmbed.php
*/
namespace Friendica\Content;
use Friendica\Core\Cache;
use Friendica\Core\System;
use Friendica\ParseUrl;
use Friendica\Core\Config;
use Friendica\Database\DBM;
use dba;
use DOMDocument;
use DOMXPath;
use DOMNode;
require_once 'include/dba.php';
require_once 'mod/proxy.php';
/**
* Handles all OEmbed content fetching and replacement
*
* OEmbed is a standard used to allow an embedded representation of a URL on
* third party sites
*
* @see https://oembed.com
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
class OEmbed
{
public static function replaceCallback($matches)
{
$embedurl = $matches[1];
$j = self::fetchURL($embedurl);
$s = self::formatObject($j);
return $s;
}
/**
* @brief Get data from an URL to embed its content.
*
* @param string $embedurl The URL from which the data should be fetched.
* @param bool $no_rich_type If set to true rich type content won't be fetched.
*
* @return bool|object Returns object with embed content or false if no embedable
* content exists
*/
public static function fetchURL($embedurl, $no_rich_type = false)
{
$embedurl = trim($embedurl, "'");
$embedurl = trim($embedurl, '"');
$a = get_app();
$condition = array('url' => normalise_link($embedurl));
$r = dba::select('oembed', array('content'), $condition, array('limit' => 1));
if (DBM::is_result($r)) {
$txt = $r["content"];
} else {
$txt = Cache::get($a->videowidth . $embedurl);
}
// These media files should now be caught in bbcode.php
// left here as a fallback in case this is called from another source
$noexts = array("mp3", "mp4", "ogg", "ogv", "oga", "ogm", "webm");
$ext = pathinfo(strtolower($embedurl), PATHINFO_EXTENSION);
if (is_null($txt)) {
$txt = "";
if (!in_array($ext, $noexts)) {
// try oembed autodiscovery
$redirects = 0;
$html_text = fetch_url($embedurl, false, $redirects, 15, "text/*");
if ($html_text) {
$dom = @DOMDocument::loadHTML($html_text);
if ($dom) {
$xpath = new DOMXPath($dom);
$entries = $xpath->query("//link[@type='application/json+oembed']");
foreach ($entries as $e) {
$href = $e->getAttributeNode("href")->nodeValue;
$txt = fetch_url($href . '&maxwidth=' . $a->videowidth);
break;
}
$entries = $xpath->query("//link[@type='text/json+oembed']");
foreach ($entries as $e) {
$href = $e->getAttributeNode("href")->nodeValue;
$txt = fetch_url($href . '&maxwidth=' . $a->videowidth);
break;
}
}
}
}
$txt = trim($txt);
if (!$txt || $txt[0] != "{") {
$txt = '{"type":"error"}';
} else { //save in cache
$j = json_decode($txt);
if ($j->type != "error") {
dba::insert('oembed', array('url' => normalise_link($embedurl),
'content' => $txt, 'created' => datetime_convert()), true);
}
Cache::set($a->videowidth . $embedurl, $txt, CACHE_DAY);
}
}
$j = json_decode($txt);
if (!is_object($j)) {
return false;
}
// Always embed the SSL version
if (isset($j->html)) {
$j->html = str_replace(array("http://www.youtube.com/", "http://player.vimeo.com/"), array("https://www.youtube.com/", "https://player.vimeo.com/"), $j->html);
}
$j->embedurl = $embedurl;
// If fetching information doesn't work, then improve via internal functions
if (($j->type == "error") || ($no_rich_type && ($j->type == "rich"))) {
$data = ParseUrl::getSiteinfoCached($embedurl, true, false);
$j->type = $data["type"];
if ($j->type == "photo") {
$j->url = $data["url"];
//$j->width = $data["images"][0]["width"];
//$j->height = $data["images"][0]["height"];
}
if (isset($data["title"])) {
$j->title = $data["title"];
}
if (isset($data["text"])) {
$j->description = $data["text"];
}
if (is_array($data["images"])) {
$j->thumbnail_url = $data["images"][0]["src"];
$j->thumbnail_width = $data["images"][0]["width"];
$j->thumbnail_height = $data["images"][0]["height"];
}
}
call_hooks('oembed_fetch_url', $embedurl, $j);
return $j;
}
public static function formatObject($j)
{
$embedurl = $j->embedurl;
$jhtml = self::iframe($j->embedurl, (isset($j->width) ? $j->width : null), (isset($j->height) ? $j->height : null));
$ret = "<span class='oembed " . $j->type . "'>";
switch ($j->type) {
case "video":
if (isset($j->thumbnail_url)) {
$tw = (isset($j->thumbnail_width) && intval($j->thumbnail_width)) ? $j->thumbnail_width : 200;
$th = (isset($j->thumbnail_height) && intval($j->thumbnail_height)) ? $j->thumbnail_height : 180;
// make sure we don't attempt divide by zero, fallback is a 1:1 ratio
$tr = (($th) ? $tw / $th : 1);
$th = 120;
$tw = $th * $tr;
$tpl = get_markup_template('oembed_video.tpl');
$ret.=replace_macros($tpl, array(
'$baseurl' => System::baseUrl(),
'$embedurl' => $embedurl,
'$escapedhtml' => base64_encode($jhtml),
'$tw' => $tw,
'$th' => $th,
'$turl' => $j->thumbnail_url,
));
} else {
$ret = $jhtml;
}
//$ret.="<br>";
break;
case "photo":
$ret.= "<img width='" . $j->width . "' src='" . proxy_url($j->url) . "'>";
break;
case "link":
break;
case "rich":
// not so safe..
if (!Config::get("system", "no_oembed_rich_content")) {
$ret.= proxy_parse_html($jhtml);
}
break;
}
// add link to source if not present in "rich" type
if ($j->type != 'rich' || !strpos($j->html, $embedurl)) {
$ret .= "<h4>";
if (isset($j->title)) {
if (isset($j->provider_name)) {
$ret .= $j->provider_name . ": ";
}
$embedlink = (isset($j->title)) ? $j->title : $embedurl;
$ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";
if (isset($j->author_name)) {
$ret.=" (" . $j->author_name . ")";
}
} elseif (isset($j->provider_name) || isset($j->author_name)) {
$embedlink = "";
if (isset($j->provider_name)) {
$embedlink .= $j->provider_name;
}
if (isset($j->author_name)) {
if ($embedlink != "") {
$embedlink .= ": ";
}
$embedlink .= $j->author_name;
}
if (trim($embedlink) == "") {
$embedlink = $embedurl;
}
$ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>";
}
//if (isset($j->author_name)) $ret.=" by ".$j->author_name;
//if (isset($j->provider_name)) $ret.=" on ".$j->provider_name;
$ret .= "</h4>";
} else {
// add <a> for html2bbcode conversion
$ret .= "<a href='$embedurl' rel='oembed'>$embedurl</a>";
}
$ret.="</span>";
$ret = str_replace("\n", "", $ret);
return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret));
}
public static function BBCode2HTML($text)
{
$stopoembed = Config::get("system", "no_oembed");
if ($stopoembed == true) {
return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "<!-- oembed $1 --><i>" . t('Embedding disabled') . " : $1</i><!-- /oembed $1 -->", $text);
}
return preg_replace_callback("/\[embed\](.+?)\[\/embed\]/is", ['self', 'replaceCallback'], $text);
}
/**
* Find <span class='oembed'>..<a href='url' rel='oembed'>..</a></span>
* and replace it with [embed]url[/embed]
*/
public static function HTML2BBCode($text)
{
// start parser only if 'oembed' is in text
if (strpos($text, "oembed")) {
// convert non ascii chars to html entities
$html_text = mb_convert_encoding($text, 'HTML-ENTITIES', mb_detect_encoding($text));
// If it doesn't parse at all, just return the text.
$dom = @DOMDocument::loadHTML($html_text);
if (!$dom) {
return $text;
}
$xpath = new DOMXPath($dom);
$xattr = self::buildXPath("class", "oembed");
$entries = $xpath->query("//span[$xattr]");
$xattr = "@rel='oembed'"; //oe_build_xpath("rel","oembed");
foreach ($entries as $e) {
$href = $xpath->evaluate("a[$xattr]/@href", $e)->item(0)->nodeValue;
if (!is_null($href)) {
$e->parentNode->replaceChild(new DOMText("[embed]" . $href . "[/embed]"), $e);
}
}
return self::getInnerHTML($dom->getElementsByTagName("body")->item(0));
} else {
return $text;
}
}
/**
* @brief Generates the iframe HTML for an oembed attachment.
*
* Width and height are given by the remote, and are regularly too small for
* the generated iframe.
*
* The width is entirely discarded for the actual width of the post, while fixed
* height is used as a starting point before the inevitable resizing.
*
* Since the iframe is automatically resized on load, there are no need for ugly
* and impractical scrollbars.
*
* @param string $src Original remote URL to embed
* @param string $width
* @param string $height
* @return string formatted HTML
*
* @see oembed_format_object()
*/
private static function iframe($src, $width, $height)
{
$a = get_app();
if (!$height || strstr($height, '%')) {
$height = '200';
}
$width = '100%';
$s = System::baseUrl() . '/oembed/' . base64url_encode($src);
return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $s . '" allowfullscreen scrolling="no" frameborder="no">' . t('Embedded content') . '</iframe>';
}
/**
* Generates an XPath query to select elements whose provided attribute contains
* the provided value in a space-separated list.
*
* @brief Generates attribute search XPath string
*
* @param string $attr Name of the attribute to seach
* @param string $value Value to search in a space-separated list
* @return string
*/
private static function buildXPath($attr, $value)
{
// https://www.westhoffswelt.de/blog/2009/6/9/select-html-elements-with-more-than-one-css-class-using-xpath
return "contains(normalize-space(@$attr), ' $value ') or substring(normalize-space(@$attr), 1, string-length('$value') + 1) = '$value ' or substring(normalize-space(@$attr), string-length(@$attr) - string-length('$value')) = ' $value' or @$attr = '$value'";
}
/**
* Returns the inner XML string of a provided DOMNode
*
* @brief Returns the inner XML string of a provided DOMNode
*
* @param DOMNode $node
* @return string
*/
private static function getInnerHTML(DOMNode $node)
{
$innerHTML = '';
$children = $node->childNodes;
foreach ($children as $child) {
$innerHTML .= $child->ownerDocument->saveXML($child);
}
return $innerHTML;
}
}

View file

@ -606,6 +606,7 @@ class Worker
$exponent = 3;
$slope = $maxworkers / pow($maxsysload, $exponent);
$queues = ceil($slope * pow(max(0, $maxsysload - $load), $exponent));
$processlist = '';
if (Config::get('system', 'worker_debug')) {
// Create a list of queue entries grouped by their priority

View file

@ -662,7 +662,7 @@ class Contact extends BaseObject
if (!DBM::is_result($contact)) {
// The link could be provided as http although we stored it as https
$ssl_url = str_replace('http://', 'https://', $url);
$r = dba::select('contact', array('id', 'avatar-date'), array('`alias` IN (?, ?, ?) AND `uid` = ?', $url, normalise_link($url), $ssl_url, $uid), array('limit' => 1));
$r = dba::select('contact', array('id', 'avatar', 'avatar-date'), array('`alias` IN (?, ?, ?) AND `uid` = ?', $url, normalise_link($url), $ssl_url, $uid), array('limit' => 1));
$contact = dba::fetch($r);
dba::close($r);
}
@ -674,7 +674,7 @@ class Contact extends BaseObject
$update_contact = ($contact['avatar-date'] < datetime_convert('', '', 'now -7 days'));
// We force the update if the avatar is empty
if ($contact['avatar'] == '') {
if (!x($contact, 'avatar')) {
$update_contact = true;
}

View file

@ -16,11 +16,11 @@ use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Photo;
use Friendica\Object\Image;
use Friendica\Util\Crypto;
use dba;
use Exception;
require_once 'boot.php';
require_once 'include/crypto.php';
require_once 'include/dba.php';
require_once 'include/enotify.php';
require_once 'include/network.php';
@ -297,7 +297,7 @@ class User
$return['password'] = $new_password;
$keys = new_keypair(4096);
$keys = Crypto::newKeypair(4096);
if ($keys === false) {
throw new Exception(t('SERIOUS ERROR: Generation of security keys failed.'));
}
@ -306,7 +306,7 @@ class User
$pubkey = $keys['pubkey'];
// Create another keypair for signing/verifying salmon protocol messages.
$sres = new_keypair(512);
$sres = Crypto::newKeypair(512);
$sprvkey = $sres['prvkey'];
$spubkey = $sres['pubkey'];

59
src/Module/Feed.php Normal file
View file

@ -0,0 +1,59 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\OStatus;
/**
* Provides public Atom feeds
*
* Currently supported:
* - /feed/[nickname]/ => posts
* - /feed/[nickname]/posts => posts
* - /feed/[nickname]/comments => comments
* - /feed/[nickname]/replies => comments
* - /feed/[nickname]/activity => activity
*
* The nocache GET parameter is provided mainly for debug purposes, requires auth
*
* @brief Provides public Atom feeds
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
class Feed extends BaseModule
{
public static function content()
{
$a = self::getApp();
$last_update = x($_GET, 'last_update') ? $_GET['last_update'] : '';
$nocache = x($_GET, 'nocache') && local_user();
if ($a->argc < 2) {
http_status_exit(400);
}
$type = null;
if ($a->argc > 2) {
$type = $a->argv[2];
}
switch ($type) {
case 'posts':
case 'comments':
case 'activity':
break;
case 'replies':
$type = 'comments';
break;
default:
$type = 'posts';
}
$nickname = $a->argv[1];
header("Content-type: application/atom+xml");
echo OStatus::feed($nickname, $last_update, 10, $type, $nocache);
killme();
}
}

53
src/Module/Oembed.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Content;
/**
* Oembed module
*
* Displays stored embed content based on a base64 hash of a remote URL
*
* Example: /oembed/aHR0cHM6Ly9...
*
* @author Hypolite Petovan <mrpetovan@gmail.com>
*/
class Oembed extends BaseModule
{
public static function content()
{
$a = self::getApp();
// Unused form: /oembed/b2h?url=...
if ($a->argv[1] == 'b2h') {
$url = array("", trim(hex2bin($_REQUEST['url'])));
echo Content\OEmbed::replaceCallback($url);
killme();
}
// Unused form: /oembed/h2b?text=...
if ($a->argv[1] == 'h2b') {
$text = trim(hex2bin($_REQUEST['text']));
echo Content\OEmbed::HTML2BBCode($text);
killme();
}
if ($a->argc == 2) {
echo '<html><body>';
$url = base64url_decode($a->argv[1]);
$j = Content\OEmbed::fetchURL($url);
// workaround for media.ccc.de (and any other endpoint that return size 0)
if (substr($j->html, 0, 7) == "<iframe" && strstr($j->html, 'width="0"')) {
$j->html = '<style>html,body{margin:0;padding:0;} iframe{width:100%;height:100%;}</style>' . $j->html;
$j->html = str_replace('width="0"', '', $j->html);
$j->html = str_replace('height="0"', '', $j->html);
}
echo $j->html;
echo '</body></html>';
}
killme();
}
}

View file

@ -17,6 +17,7 @@ use Friendica\Database\DBM;
use Friendica\Model\Profile;
use Friendica\Protocol\Email;
use Friendica\Protocol\Feed;
use Friendica\Util\Crypto;
use Friendica\Util\XML;
use dba;
@ -25,7 +26,6 @@ use DOMDocument;
require_once 'include/dba.php';
require_once 'include/network.php';
require_once "include/crypto.php";
/**
* @brief This class contain functions for probing URL
@ -330,7 +330,7 @@ class Probe
$data["url"] = $uri;
}
if ($data["photo"] != "") {
if (x($data, "photo")) {
$data["baseurl"] = matching_url(normalise_link($data["baseurl"]), normalise_link($data["photo"]));
} else {
$data["photo"] = System::baseUrl().'/images/person-175.jpg';
@ -341,7 +341,7 @@ class Probe
$data["name"] = $data["nick"];
}
if ($data["name"] == "") {
if (!x($data, "name")) {
$data["name"] = $data["url"];
}
}
@ -944,7 +944,7 @@ class Probe
//if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
if (strstr($data["pubkey"], 'RSA ')) {
$data["pubkey"] = rsatopem($data["pubkey"]);
$data["pubkey"] = Crypto::rsaToPem($data["pubkey"]);
}
}
}
@ -1043,7 +1043,7 @@ class Probe
if ($search->length > 0) {
$data["pubkey"] = $search->item(0)->nodeValue;
if (strstr($data["pubkey"], 'RSA ')) {
$data["pubkey"] = rsatopem($data["pubkey"]);
$data["pubkey"] = Crypto::rsaToPem($data["pubkey"]);
}
}
@ -1133,7 +1133,7 @@ class Probe
//if (strstr($data["pubkey"], 'RSA ') || ($link["type"] == "RSA"))
if (strstr($data["pubkey"], 'RSA ')) {
$data["pubkey"] = rsatopem($data["pubkey"]);
$data["pubkey"] = Crypto::rsaToPem($data["pubkey"]);
}
}
}
@ -1244,7 +1244,7 @@ class Probe
if (sizeof($key) >= 3) {
$m = base64url_decode($key[1]);
$e = base64url_decode($key[2]);
$data["pubkey"] = metopem($m, $e);
$data["pubkey"] = Crypto::meToPem($m, $e);
}
}
}

View file

@ -1,7 +1,9 @@
<?php
/**
* @file src/Object/Post.php
*/
namespace Friendica\Object;
use Friendica\BaseObject;
@ -52,9 +54,9 @@ class Post extends BaseObject
$this->data = $data;
$this->setTemplate('wall');
$this->toplevel = ($this->getId() == $this->getDataValue('parent'));
$this->toplevel = $this->getId() == $this->getDataValue('parent');
if (is_array($_SESSION['remote'])) {
if (x($_SESSION, 'remote') && is_array($_SESSION['remote'])) {
foreach ($_SESSION['remote'] as $visitor) {
if ($visitor['cid'] == $this->getDataValue('contact-id')) {
$this->visiting = true;
@ -63,9 +65,7 @@ class Post extends BaseObject
}
}
$this->writable = ($this->getDataValue('writable') || $this->getDataValue('self'));
$ssl_state = ((local_user()) ? true : false);
$this->writable = $this->getDataValue('writable') || $this->getDataValue('self');
$this->redirect_url = 'redir/' . $this->getDataValue('cid');
if (!$this->isToplevel()) {
@ -75,12 +75,10 @@ class Post extends BaseObject
// Prepare the children
if (count($data['children'])) {
foreach ($data['children'] as $item) {
/*
* Only add will be displayed
*/
// Only add will be displayed
if ($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) {
continue;
} elseif (! visible_activity($item)) {
} elseif (!visible_activity($item)) {
continue;
}
@ -145,13 +143,13 @@ class Post extends BaseObject
|| strlen($item['deny_cid']) || strlen($item['deny_gid']))))
? t('Private Message')
: false);
$shareable = ((in_array($conv->getProfileOwner(), [0, local_user()]) && ($item['private'] != 1)) ? true : false);
$shareable = in_array($conv->getProfileOwner(), [0, local_user()]) && $item['private'] != 1;
if (local_user() && link_compare($a->contact['url'], $item['author-link'])) {
if ($item["event-id"] != 0) {
$edpost = array("events/event/".$item['event-id'], t("Edit"));
$edpost = array("events/event/" . $item['event-id'], t("Edit"));
} else {
$edpost = array("editpost/".$item['id'], t("Edit"));
$edpost = array("editpost/" . $item['id'], t("Edit"));
}
} else {
$edpost = false;
@ -170,9 +168,9 @@ class Post extends BaseObject
$filer = (($conv->getProfileOwner() == local_user()) ? t("save to folder") : false);
$diff_author = ((link_compare($item['url'], $item['author-link'])) ? false : true);
$profile_name = htmlentities(((strlen($item['author-name'])) && $diff_author) ? $item['author-name'] : $item['name']);
if ($item['author-link'] && (! $item['author-name'])) {
$diff_author = !link_compare($item['url'], $item['author-link']);
$profile_name = htmlentities(((strlen($item['author-name'])) && $diff_author) ? $item['author-name'] : $item['name']);
if ($item['author-link'] && (!$item['author-name'])) {
$profile_name = $item['author-link'];
}
@ -210,39 +208,25 @@ class Post extends BaseObject
call_hooks('render_location', $locate);
$location = ((strlen($locate['html'])) ? $locate['html'] : render_location_dummy($locate));
$tags=array();
$hashtags = array();
$mentions = array();
/*foreach(explode(',',$item['tag']) as $tag){
$tag = trim($tag);
if ($tag!="") {
$t = bbcode($tag);
$tags[] = $t;
if($t[0] == '#')
$hashtags[] = $t;
elseif($t[0] == '@')
$mentions[] = $t;
}
}*/
// process action responses - e.g. like/dislike/attend/agree/whatever
$response_verbs = array('like', 'dislike');
$isevent = false;
$attend = [];
if ($item['object-type'] === ACTIVITY_OBJ_EVENT) {
$response_verbs[] = 'attendyes';
$response_verbs[] = 'attendno';
$response_verbs[] = 'attendmaybe';
if ($conv->isWritable()) {
$isevent = true;
$attend = array( t('I will attend'), t('I will not attend'), t('I might attend'));
$attend = array(t('I will attend'), t('I will not attend'), t('I might attend'));
}
}
$responses = get_responses($conv_responses, $response_verbs, $this, $item);
foreach ($response_verbs as $value => $verbs) {
$responses[$verbs]['output'] = ((x($conv_responses[$verbs], $item['uri'])) ? format_like($conv_responses[$verbs][$item['uri']], $conv_responses[$verbs][$item['uri'] . '-l'], $verbs, $item['uri']) : '');
$responses[$verbs]['output'] = x($conv_responses[$verbs], $item['uri']) ? format_like($conv_responses[$verbs][$item['uri']], $conv_responses[$verbs][$item['uri'] . '-l'], $verbs, $item['uri']) : '';
}
/*
@ -256,6 +240,8 @@ class Post extends BaseObject
$osparkle = ' sparkle';
}
$tagger = '';
if ($this->isToplevel()) {
if ($conv->getProfileOwner() == local_user()) {
$isstarred = (($item['starred']) ? "starred" : "unstarred");
@ -264,9 +250,9 @@ class Post extends BaseObject
'do' => t("add star"),
'undo' => t("remove star"),
'toggle' => t("toggle star status"),
'classdo' => (($item['starred']) ? "hidden" : ""),
'classundo' => (($item['starred']) ? "" : "hidden"),
'starred' => t('starred'),
'classdo' => $item['starred'] ? "hidden" : "",
'classundo' => $item['starred'] ? "" : "hidden",
'starred' => t('starred'),
);
$r = dba::select('thread', array('ignored'), array('uid' => $item['uid'], 'iid' => $item['id']), array('limit' => 1));
if (DBM::is_result($r)) {
@ -274,13 +260,12 @@ class Post extends BaseObject
'do' => t("ignore thread"),
'undo' => t("unignore thread"),
'toggle' => t("toggle ignore status"),
'classdo' => (($r['ignored']) ? "hidden" : ""),
'classundo' => (($r['ignored']) ? "" : "hidden"),
'ignored' => t('ignored'),
'classdo' => $r['ignored'] ? "hidden" : "",
'classundo' => $r['ignored'] ? "" : "hidden",
'ignored' => t('ignored'),
);
}
$tagger = '';
if (Feature::isEnabled($conv->getProfileOwner(), 'commtag')) {
$tagger = array(
'add' => t("add tag"),
@ -294,11 +279,11 @@ class Post extends BaseObject
if ($conv->isWritable()) {
$buttons = array(
'like' => array( t("I like this \x28toggle\x29"), t("like")),
'dislike' => ((Feature::isEnabled($conv->getProfileOwner(), 'dislike')) ? array( t("I don't like this \x28toggle\x29"), t("dislike")) : ''),
'like' => array(t("I like this \x28toggle\x29"), t("like")),
'dislike' => Feature::isEnabled($conv->getProfileOwner(), 'dislike') ? array(t("I don't like this \x28toggle\x29"), t("dislike")) : '',
);
if ($shareable) {
$buttons['share'] = array( t('Share this'), t('share'));
$buttons['share'] = array(t('Share this'), t('share'));
}
}
@ -322,10 +307,10 @@ class Post extends BaseObject
$owner_name_e = $this->getOwnerName();
// Disable features that aren't available in several networks
/// @todo Add NETWORK_DIASPORA when it will pass this information
if (!in_array($item["item_network"], array(NETWORK_DFRN)) && isset($buttons["dislike"])) {
unset($buttons["dislike"], $isevent);
unset($buttons["dislike"]);
$isevent = false;
$tagger = '';
}
@ -355,8 +340,8 @@ class Post extends BaseObject
'guid' => urlencode($item['guid']),
'isevent' => $isevent,
'attend' => $attend,
'linktitle' => sprintf(t('View %s\'s profile @ %s'), $profile_name, ((strlen($item['author-link'])) ? $item['author-link'] : $item['url'])),
'olinktitle' => sprintf(t('View %s\'s profile @ %s'), htmlentities($this->getOwnerName()), ((strlen($item['owner-link'])) ? $item['owner-link'] : $item['url'])),
'linktitle' => t('View %s\'s profile @ %s', $profile_name, defaults($item, 'author-link', $item['url'])),
'olinktitle' => t('View %s\'s profile @ %s', htmlentities($this->getOwnerName()), defaults($item, 'owner-link', $item['url'])),
'to' => t('to'),
'via' => t('via'),
'wall' => t('Wall-to-Wall'),
@ -369,7 +354,7 @@ class Post extends BaseObject
'sparkle' => $sparkle,
'title' => $title_e,
'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'),
'ago' => (($item['app']) ? sprintf(t('%s from %s'), relative_date($item['created']), $item['app']) : relative_date($item['created'])),
'ago' => $item['app'] ? t('%s from %s', relative_date($item['created']), $item['app']) : relative_date($item['created']),
'app' => $item['app'],
'created' => relative_date($item['created']),
'lock' => $lock,
@ -380,12 +365,12 @@ class Post extends BaseObject
'owner_photo' => $a->remove_baseurl(proxy_url($item['owner-thumb'], false, PROXY_SIZE_THUMB)),
'owner_name' => htmlentities($owner_name_e),
'plink' => get_plink($item),
'edpost' => ((Feature::isEnabled($conv->getProfileOwner(), 'edit_posts')) ? $edpost : ''),
'edpost' => Feature::isEnabled($conv->getProfileOwner(), 'edit_posts') ? $edpost : '',
'isstarred' => $isstarred,
'star' => ((Feature::isEnabled($conv->getProfileOwner(), 'star_posts')) ? $star : ''),
'ignore' => ((Feature::isEnabled($conv->getProfileOwner(), 'ignore_posts')) ? $ignore : ''),
'star' => Feature::isEnabled($conv->getProfileOwner(), 'star_posts') ? $star : '',
'ignore' => Feature::isEnabled($conv->getProfileOwner(), 'ignore_posts') ? $ignore : '',
'tagger' => $tagger,
'filer' => ((Feature::isEnabled($conv->getProfileOwner(), 'filing')) ? $filer : ''),
'filer' => Feature::isEnabled($conv->getProfileOwner(), 'filing') ? $filer : '',
'drop' => $drop,
'vote' => $buttons,
'like' => $responses['like']['output'],
@ -393,7 +378,7 @@ class Post extends BaseObject
'responses' => $responses,
'switchcomment' => t('Comment'),
'comment' => $comment,
'previewing' => ($conv->isPreview() ? ' preview ' : ''),
'previewing' => $conv->isPreview() ? ' preview ' : '',
'wait' => t('Please wait'),
'thread_level' => $thread_level,
'edited' => $edited,
@ -419,7 +404,7 @@ class Post extends BaseObject
// Collapse
if (($nb_children > 2) || ($thread_level > 1)) {
$result['children'][0]['comment_firstcollapsed'] = true;
$result['children'][0]['num_comments'] = sprintf(tt('%d comment', '%d comments', $total_children), $total_children);
$result['children'][0]['num_comments'] = tt('%d comment', '%d comments', $total_children);
$result['children'][0]['hidden_comments_num'] = $total_children;
$result['children'][0]['hidden_comments_text'] = tt('comment', 'comments', $total_children);
$result['children'][0]['hide_text'] = t('show more');
@ -480,7 +465,7 @@ class Post extends BaseObject
logger('[ERROR] Post::addChild : Item has no ID!!', LOGGER_DEBUG);
return false;
} elseif ($this->getChild($item->getId())) {
logger('[WARN] Post::addChild : Item already exists ('. $item->getId() .').', LOGGER_DEBUG);
logger('[WARN] Post::addChild : Item already exists (' . $item->getId() . ').', LOGGER_DEBUG);
return false;
}
/*
@ -574,7 +559,7 @@ class Post extends BaseObject
return true;
}
}
logger('[WARN] Item::removeChild : Item is not a child ('. $id .').', LOGGER_DEBUG);
logger('[WARN] Item::removeChild : Item is not a child (' . $id . ').', LOGGER_DEBUG);
return false;
}
@ -657,7 +642,7 @@ class Post extends BaseObject
private function setTemplate($name)
{
if (!x($this->available_templates, $name)) {
logger('[ERROR] Item::setTemplate : Template not available ("'. $name .'").', LOGGER_DEBUG);
logger('[ERROR] Item::setTemplate : Template not available ("' . $name . '").', LOGGER_DEBUG);
return false;
}
@ -696,7 +681,6 @@ class Post extends BaseObject
if ($conv) {
// This will allow us to comment on wall-to-wall items owned by our friends
// and community forums even if somebody else wrote the post.
// bug #517 - this fixes for conversation owner
if ($conv->getMode() == 'profile' && $conv->getProfileOwner() == local_user()) {
return true;
@ -750,14 +734,13 @@ class Post extends BaseObject
$comment_box = '';
$conv = $this->getThread();
$template = get_markup_template($this->getCommentBoxTemplate());
$ww = '';
if (($conv->getMode() === 'network') && $this->isWallToWall()) {
$ww = 'ww';
}
if ($conv->isWritable() && $this->isWritable()) {
$qc = $qcomment = null;
$qc = $qcomment = null;
/*
* Hmmm, code depending on the presence of a particular plugin?
@ -768,18 +751,16 @@ class Post extends BaseObject
$qcomment = (($qc) ? explode("\n", $qc) : null);
}
$comment_box = replace_macros(
$template,
array(
$template = get_markup_template($this->getCommentBoxTemplate());
$comment_box = replace_macros($template, array(
'$return_path' => $a->query_string,
'$threaded' => $this->isThreaded(),
// '$jsreload' => (($conv->getMode() === 'display') ? $_SESSION['return_url'] : ''),
'$jsreload' => '',
'$type' => (($conv->getMode() === 'profile') ? 'wall-comment' : 'net-comment'),
'$type' => $conv->getMode() === 'profile' ? 'wall-comment' : 'net-comment',
'$id' => $this->getId(),
'$parent' => $this->getId(),
'$qcomment' => $qcomment,
'$profile_uid' => $conv->getProfileOwner(),
'$profile_uid' => $conv->getProfileOwner(),
'$mylink' => $a->remove_baseurl($a->contact['url']),
'$mytitle' => t('This is you'),
'$myphoto' => $a->remove_baseurl($a->contact['thumb']),
@ -796,9 +777,9 @@ class Post extends BaseObject
'$preview' => ((Feature::isEnabled($conv->getProfileOwner(), 'preview')) ? t('Preview') : ''),
'$indent' => $indent,
'$sourceapp' => t($a->sourcename),
'$ww' => (($conv->getMode() === 'network') ? $ww : ''),
'$rand_num' => random_digits(12))
);
'$ww' => $conv->getMode() === 'network' ? $ww : '',
'$rand_num' => random_digits(12)
));
}
return $comment_box;
@ -839,14 +820,13 @@ class Post extends BaseObject
$alias_linkmatch = (($this->getDataValue('alias')) && link_compare($this->getDataValue('alias'), $this->getDataValue('author-link')));
$owner_namematch = (($this->getDataValue('owner-name')) && $this->getDataValue('owner-name') == $this->getDataValue('author-name'));
if ((! $owner_linkmatch) && (! $alias_linkmatch) && (! $owner_namematch)) {
if ((!$owner_linkmatch) && (!$alias_linkmatch) && (!$owner_namematch)) {
// The author url doesn't match the owner (typically the contact)
// and also doesn't match the contact alias.
// The name match is a hack to catch several weird cases where URLs are
// all over the park. It can be tricked, but this prevents you from
// seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn
// well that it's the same Bob Smith.
// But it could be somebody else with the same name. It just isn't highly likely.
@ -854,8 +834,8 @@ class Post extends BaseObject
$this->owner_name = $this->getDataValue('owner-name');
$this->wall_to_wall = true;
// If it is our contact, use a friendly redirect link
if ((link_compare($this->getDataValue('owner-link'), $this->getDataValue('url')))
&& ($this->getDataValue('network') === NETWORK_DFRN)
if ($this->getDataValue('network') === NETWORK_DFRN
&& link_compare($this->getDataValue('owner-link'), $this->getDataValue('url'))
) {
$this->owner_url = $this->getRedirectUrl();
} else {

View file

@ -5,7 +5,7 @@
*/
namespace Friendica;
use Friendica\Core\Config;
use Friendica\Content\OEmbed;
use Friendica\Object\Image;
use Friendica\Util\XML;
@ -15,7 +15,6 @@ use DOMDocument;
require_once 'include/dba.php';
require_once "include/network.php";
require_once "include/oembed.php";
/**
* @brief Class with methods for extracting certain content from an url
@ -164,7 +163,7 @@ class ParseUrl
$body = $data["body"];
if ($do_oembed) {
$oembed_data = oembed_fetch_url($url);
$oembed_data = OEmbed::fetchURL($url);
if (!in_array($oembed_data->type, array("error", "rich", ""))) {
$siteinfo["type"] = $oembed_data->type;

View file

@ -8,6 +8,7 @@
*/
namespace Friendica\Protocol;
use Friendica\Content\OEmbed;
use Friendica\Core\Config;
use Friendica\Core\System;
use Friendica\Core\Worker;
@ -34,7 +35,6 @@ require_once "include/tags.php";
require_once "include/files.php";
require_once "include/event.php";
require_once "include/text.php";
require_once "include/oembed.php";
require_once "include/html2bbcode.php";
require_once "include/bbcode.php";
@ -464,7 +464,7 @@ class DFRN
/* get site pubkey. this could be a new installation with no site keys*/
$pubkey = Config::get('system', 'site_pubkey');
if (! $pubkey) {
$res = new_keypair(1024);
$res = Crypto::newKeypair(1024);
Config::set('system', 'site_prvkey', $res['prvkey']);
Config::set('system', 'site_pubkey', $res['pubkey']);
}
@ -2503,7 +2503,7 @@ class DFRN
$item['body'] = html2bb_video($item['body']);
$item['body'] = oembed_html2bbcode($item['body']);
$item['body'] = OEmbed::HTML2BBCode($item['body']);
$config = \HTMLPurifier_Config::createDefault();
$config->set('Cache.DefinitionImpl', null);

View file

@ -22,6 +22,7 @@ use Friendica\Model\Group;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Network\Probe;
use Friendica\Util\Crypto;
use Friendica\Util\XML;
use dba;
@ -173,7 +174,7 @@ class Diaspora
$key = self::key($handle);
$verify = rsa_verify($signable_data, $sig, $key);
$verify = Crypto::rsaVerify($signable_data, $sig, $key);
if (!$verify) {
logger('Message did not verify. Discarding.');
return false;
@ -273,7 +274,7 @@ class Diaspora
$author_addr = base64_decode($key_id);
$key = self::key($author_addr);
$verify = rsa_verify($signed_data, $signature, $key);
$verify = Crypto::rsaVerify($signed_data, $signature, $key);
if (!$verify) {
logger('Message did not verify. Discarding.');
http_status_exit(400);
@ -406,7 +407,7 @@ class Diaspora
http_status_exit(400);
}
$verify = rsa_verify($signed_data, $signature, $key);
$verify = Crypto::rsaVerify($signed_data, $signature, $key);
if (!$verify) {
logger('Message did not verify. Discarding.');
@ -699,7 +700,7 @@ class Diaspora
$key = self::key($msg["author"]);
if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) {
if (!Crypto::rsaVerify($signed_data, $parent_author_signature, $key, "sha256")) {
logger("No valid parent author signature for parent author ".$msg["author"]. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$parent_author_signature, LOGGER_DEBUG);
return false;
}
@ -709,7 +710,7 @@ class Diaspora
$key = self::key($fields->author);
if (!rsa_verify($signed_data, $author_signature, $key, "sha256")) {
if (!Crypto::rsaVerify($signed_data, $author_signature, $key, "sha256")) {
logger("No valid author signature for author ".$fields->author. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$author_signature, LOGGER_DEBUG);
return false;
} else {
@ -1432,7 +1433,7 @@ class Diaspora
// Check signature
$signed_text = 'AccountMigration:'.$old_handle.':'.$new_handle;
$key = self::key($old_handle);
if (!rsa_verify($signed_text, $signature, $key, "sha256")) {
if (!Crypto::rsaVerify($signed_text, $signature, $key, "sha256")) {
logger('No valid signature for migration.');
return false;
}
@ -3032,7 +3033,7 @@ class Diaspora
$user['uprvkey'] = $user['prvkey'];
}
$signature = rsa_sign($signable_data, $user["uprvkey"]);
$signature = Crypto::rsaSign($signable_data, $user["uprvkey"]);
$sig = base64url_encode($signature);
$xmldata = array("me:env" => array("me:data" => $data,
@ -3088,7 +3089,7 @@ class Diaspora
$signed_text = implode(";", $sigmsg);
return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256"));
return base64_encode(Crypto::rsaSign($signed_text, $owner["uprvkey"], "sha256"));
}
/**
@ -3282,7 +3283,7 @@ class Diaspora
$profile = self::createProfileData($uid);
$signed_text = 'AccountMigration:'.$old_handle.':'.$profile['author'];
$signature = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256"));
$signature = base64_encode(Crypto::rsaSign($signed_text, $owner["uprvkey"], "sha256"));
$message = array("author" => $old_handle,
"profile" => $profile,

View file

@ -1235,12 +1235,13 @@ class OStatus
/**
* @brief Adds the header elements to the XML document
*
* @param object $doc XML document
* @param array $owner Contact data of the poster
* @param object $doc XML document
* @param array $owner Contact data of the poster
* @param string $filter The related feed filter (activity, posts or comments)
*
* @return object header root element
*/
private static function addHeader($doc, $owner)
private static function addHeader($doc, $owner, $filter)
{
$a = get_app();
@ -1256,10 +1257,16 @@ class OStatus
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
$root->setAttribute("xmlns:mastodon", NAMESPACE_MASTODON);
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
switch ($filter) {
case 'activity': $title = t('%s\'s timeline', $owner['name']); break;
case 'posts' : $title = t('%s\'s posts' , $owner['name']); break;
case 'comments': $title = t('%s\'s comments', $owner['name']); break;
}
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION . "-" . DB_UPDATE_VERSION);
XML::addElement($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
XML::addElement($doc, $root, "id", System::baseUrl()."/profile/".$owner["nick"]);
XML::addElement($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
XML::addElement($doc, $root, "id", System::baseUrl() . "/profile/" . $owner["nick"]);
XML::addElement($doc, $root, "title", $title);
XML::addElement($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
XML::addElement($doc, $root, "logo", $owner["photo"]);
XML::addElement($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
@ -1278,17 +1285,17 @@ class OStatus
self::hublinks($doc, $root, $owner["nick"]);
$attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "salmon");
$attributes = array("href" => System::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "salmon");
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
$attributes = array("href" => System::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
$attributes = array("href" => System::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = array("href" => System::baseUrl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
"rel" => "self", "type" => "application/atom+xml");
$attributes = array("href" => System::baseUrl() . "/api/statuses/user_timeline/" . $owner["nick"] . ".atom",
"rel" => "self", "type" => "application/atom+xml");
XML::addElement($doc, $root, "link", "", $attributes);
return $root;
@ -2067,42 +2074,51 @@ class OStatus
}
/**
* Creates the XML feed for a given nickname
*
* Supported filters:
* - activity (default): all the public posts
* - posts: all the public top-level posts
* - comments: all the public replies
*
* Updates the provided last_update parameter if the result comes from the
* cache or it is empty
*
* @brief Creates the XML feed for a given nickname
*
* @param object $a The application class
* @param string $owner_nick Nickname of the feed owner
* @param string $last_update Date of the last update
* @param integer $max_items Number of maximum items to fetch
* @param string $filter Feed items filter (activity, posts or comments)
* @param boolean $nocache Wether to bypass caching
*
* @return string XML feed
*/
public static function feed(App $a, $owner_nick, &$last_update, $max_items = 300)
public static function feed($owner_nick, &$last_update, $max_items = 300, $filter = 'activity', $nocache = false)
{
$stamp = microtime(true);
$cachekey = "ostatus:feed:".$owner_nick.":".$last_update;
$cachekey = "ostatus:feed:" . $owner_nick . ":" . $filter . ":" . $last_update;
$previous_created = $last_update;
$result = Cache::get($cachekey);
if (!is_null($result)) {
logger('Feed duration: '.number_format(microtime(true) - $stamp, 3).' - '.$owner_nick.' - '.$previous_created.' (cached)', LOGGER_DEBUG);
if (!$nocache && !is_null($result)) {
logger('Feed duration: ' . number_format(microtime(true) - $stamp, 3) . ' - ' . $owner_nick . ' - ' . $filter . ' - ' . $previous_created . ' (cached)', LOGGER_DEBUG);
$last_update = $result['last_update'];
return $result['feed'];
}
$r = q(
$owner = dba::fetch_first(
"SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
dbesc($owner_nick)
WHERE `contact`.`self` AND `user`.`nickname` = ? LIMIT 1",
$owner_nick
);
if (!DBM::is_result($r)) {
if (!DBM::is_result($owner)) {
return;
}
$owner = $r[0];
if (!strlen($last_update)) {
$last_update = 'now -30 days';
}
@ -2110,23 +2126,40 @@ class OStatus
$check_date = datetime_convert('UTC', 'UTC', $last_update, 'Y-m-d H:i:s');
$authorid = Contact::getIdForURL($owner["url"], 0);
$sql_extra = '';
if ($filter === 'posts') {
$sql_extra .= ' AND `item`.`id` = `item`.`parent` ';
}
if ($filter === 'comments') {
$sql_extra .= sprintf(" AND `item`.`object-type` = '%s' ", dbesc(ACTIVITY_OBJ_COMMENT));
}
$items = q(
"SELECT `item`.*, `item`.`id` AS `item_id` FROM `item` USE INDEX (`uid_contactid_created`)
STRAIGHT_JOIN `thread` ON `thread`.`iid` = `item`.`parent`
WHERE `item`.`uid` = %d AND `item`.`contact-id` = %d AND
`item`.`author-id` = %d AND `item`.`created` > '%s' AND
NOT `item`.`deleted` AND NOT `item`.`private` AND
`thread`.`network` IN ('%s', '%s')
WHERE `item`.`uid` = %d
AND `item`.`contact-id` = %d
AND `item`.`author-id` = %d
AND `item`.`created` > '%s'
AND NOT `item`.`deleted`
AND NOT `item`.`private`
AND `thread`.`network` IN ('%s', '%s')
$sql_extra
ORDER BY `item`.`created` DESC LIMIT %d",
intval($owner["uid"]), intval($owner["id"]),
intval($authorid), dbesc($check_date),
dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), intval($max_items)
intval($owner["uid"]),
intval($owner["id"]),
intval($authorid),
dbesc($check_date),
dbesc(NETWORK_OSTATUS),
dbesc(NETWORK_DFRN),
intval($max_items)
);
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$root = self::addHeader($doc, $owner);
$root = self::addHeader($doc, $owner, $filter);
foreach ($items as $item) {
if (Config::get('system', 'ostatus_debug')) {
@ -2145,7 +2178,7 @@ class OStatus
$msg = array('feed' => $feeddata, 'last_update' => $last_update);
Cache::set($cachekey, $msg, CACHE_QUARTER_HOUR);
logger('Feed duration: '.number_format(microtime(true) - $stamp, 3).' - '.$owner_nick.' - '.$previous_created, LOGGER_DEBUG);
logger('Feed duration: ' . number_format(microtime(true) - $stamp, 3) . ' - ' . $owner_nick . ' - ' . $filter . ' - ' . $previous_created, LOGGER_DEBUG);
return $feeddata;
}

View file

@ -5,10 +5,9 @@
namespace Friendica\Protocol;
use Friendica\Network\Probe;
use Friendica\Util\Crypto;
use Friendica\Util\XML;
require_once 'include/crypto.php';
/**
* @brief Salmon Protocol class
* The Salmon Protocol is a message exchange protocol running over HTTP designed to decentralize commentary
@ -107,18 +106,18 @@ class Salmon
$data_type = 'application/atom+xml';
$encoding = 'base64url';
$algorithm = 'RSA-SHA256';
$keyhash = base64url_encode(hash('sha256', salmon_key($owner['spubkey'])), true);
$keyhash = base64url_encode(hash('sha256', self::salmonKey($owner['spubkey'])), true);
$precomputed = '.' . base64url_encode($data_type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($algorithm);
// GNU Social format
$signature = base64url_encode(rsa_sign($data . $precomputed, $owner['sprvkey']));
$signature = base64url_encode(Crypto::rsaSign($data . $precomputed, $owner['sprvkey']));
// Compliant format
$signature2 = base64url_encode(rsa_sign(str_replace('=', '', $data . $precomputed), $owner['sprvkey']));
$signature2 = base64url_encode(Crypto::rsaSign(str_replace('=', '', $data . $precomputed), $owner['sprvkey']));
// Old Status.net format
$signature3 = base64url_encode(rsa_sign($data, $owner['sprvkey']));
$signature3 = base64url_encode(Crypto::rsaSign($data, $owner['sprvkey']));
// At first try the non compliant method that works for GNU Social
$xmldata = array("me:env" => array("me:data" => $data,
@ -201,4 +200,14 @@ class Salmon
return (($return_code >= 200) && ($return_code < 300)) ? 0 : 1;
}
/**
* @param string $pubkey public key
* @return string
*/
public static function salmonKey($pubkey)
{
Crypto::pemToMe($pubkey, $m, $e);
return 'RSA' . '.' . base64url_encode($m, true) . '.' . base64url_encode($e, true);
}
}

252
src/Util/Crypto.php Normal file
View file

@ -0,0 +1,252 @@
<?php
/**
* @file src/Util/Crypto.php
*/
namespace Friendica\Util;
use Friendica\Core\Config;
use ASN_BASE;
use ASNValue;
require_once 'library/ASNValue.class.php';
require_once 'library/asn1.php';
/**
* @brief Crypto class
*/
class Crypto
{
// supported algorithms are 'sha256', 'sha1'
/**
* @param string $data data
* @param string $key key
* @param string $alg algorithm
* @return string
*/
public static function rsaSign($data, $key, $alg = 'sha256')
{
openssl_sign($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
return $sig;
}
/**
* @param string $data data
* @param string $sig signature
* @param string $key key
* @param string $alg algorithm
* @return boolean
*/
public static function rsaVerify($data, $sig, $key, $alg = 'sha256')
{
return openssl_verify($data, $sig, $key, (($alg == 'sha1') ? OPENSSL_ALGO_SHA1 : $alg));
}
/**
* @param string $Der der formatted string
* @param string $Private key type optional, default false
* @return string
*/
private static function DerToPem($Der, $Private = false)
{
//Encode:
$Der = base64_encode($Der);
//Split lines:
$lines = str_split($Der, 65);
$body = implode("\n", $lines);
//Get title:
$title = $Private ? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
//Add wrapping:
$result = "-----BEGIN {$title}-----\n";
$result .= $body . "\n";
$result .= "-----END {$title}-----\n";
return $result;
}
/**
* @param string $Der der formatted string
* @return string
*/
private static function DerToRsa($Der)
{
//Encode:
$Der = base64_encode($Der);
//Split lines:
$lines = str_split($Der, 64);
$body = implode("\n", $lines);
//Get title:
$title = 'RSA PUBLIC KEY';
//Add wrapping:
$result = "-----BEGIN {$title}-----\n";
$result .= $body . "\n";
$result .= "-----END {$title}-----\n";
return $result;
}
/**
* @param string $Modulus modulo
* @param string $PublicExponent exponent
* @return string
*/
private static function pkcs8Encode($Modulus, $PublicExponent)
{
//Encode key sequence
$modulus = new ASNValue(ASNValue::TAG_INTEGER);
$modulus->SetIntBuffer($Modulus);
$publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
$publicExponent->SetIntBuffer($PublicExponent);
$keySequenceItems = array($modulus, $publicExponent);
$keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
$keySequence->SetSequence($keySequenceItems);
//Encode bit string
$bitStringValue = $keySequence->Encode();
$bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
$bitString = new ASNValue(ASNValue::TAG_BITSTRING);
$bitString->Value = $bitStringValue;
//Encode body
$bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
$body = new ASNValue(ASNValue::TAG_SEQUENCE);
$body->Value = $bodyValue;
//Get DER encoded public key:
$PublicDER = $body->Encode();
return $PublicDER;
}
/**
* @param string $Modulus modulo
* @param string $PublicExponent exponent
* @return string
*/
private static function pkcs1Encode($Modulus, $PublicExponent)
{
//Encode key sequence
$modulus = new ASNValue(ASNValue::TAG_INTEGER);
$modulus->SetIntBuffer($Modulus);
$publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
$publicExponent->SetIntBuffer($PublicExponent);
$keySequenceItems = array($modulus, $publicExponent);
$keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
$keySequence->SetSequence($keySequenceItems);
//Encode bit string
$bitStringValue = $keySequence->Encode();
return $bitStringValue;
}
/**
* @param string $m modulo
* @param string $e exponent
* @return string
*/
public static function meToPem($m, $e)
{
$der = self::pkcs8Encode($m, $e);
$key = self::DerToPem($der, false);
return $key;
}
/**
* @param string $key key
* @param string $m modulo reference
* @param object $e exponent reference
* @return void
*/
private static function pubRsaToMe($key, &$m, &$e)
{
$lines = explode("\n", $key);
unset($lines[0]);
unset($lines[count($lines)]);
$x = base64_decode(implode('', $lines));
$r = ASN_BASE::parseASNString($x);
$m = base64url_decode($r[0]->asnData[0]->asnData);
$e = base64url_decode($r[0]->asnData[1]->asnData);
}
/**
* @param string $key key
* @return string
*/
public static function rsaToPem($key)
{
self::pubRsaToMe($key, $m, $e);
return self::meToPem($m, $e);
}
/**
* @param string $key key
* @return string
*/
private static function pemToRsa($key)
{
self::pemToMe($key, $m, $e);
return self::meToRsa($m, $e);
}
/**
* @param string $key key
* @param string $m modulo reference
* @param string $e exponent reference
* @return void
*/
public static function pemToMe($key, &$m, &$e)
{
$lines = explode("\n", $key);
unset($lines[0]);
unset($lines[count($lines)]);
$x = base64_decode(implode('', $lines));
$r = ASN_BASE::parseASNString($x);
$m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
$e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
}
/**
* @param string $m modulo
* @param string $e exponent
* @return string
*/
private static function meToRsa($m, $e)
{
$der = self::pkcs1Encode($m, $e);
$key = self::DerToRsa($der);
return $key;
}
/**
* @param integer $bits number of bits
* @return mixed
*/
public static function newKeypair($bits)
{
$openssl_options = array(
'digest_alg' => 'sha1',
'private_key_bits' => $bits,
'encrypt_key' => false
);
$conf = Config::get('system', 'openssl_conf_file');
if ($conf) {
$openssl_options['config'] = $conf;
}
$result = openssl_pkey_new($openssl_options);
if (empty($result)) {
logger('new_keypair: failed');
return false;
}
// Get private key
$response = array('prvkey' => '', 'pubkey' => '');
openssl_pkey_export($result, $response['prvkey']);
// Get public key
$pkey = openssl_pkey_get_details($result);
$response['pubkey'] = $pkey["key"];
return $response;
}
}

View file

@ -52,7 +52,7 @@ class PubSubPublish {
logger("Generate feed of user ".$rr['nickname']." to ".$rr['callback_url']." - last updated ".$rr['last_update'], LOGGER_DEBUG);
$last_update = $rr['last_update'];
$params = OStatus::feed($a, $rr['nickname'], $last_update);
$params = OStatus::feed($rr['nickname'], $last_update);
if (!$params) {
return;