formatOutput = true; $root = self::add_header($doc, $owner, "dfrn:owner", "", false); if(! count($items)) return trim($doc->saveXML()); foreach($items as $item) { $entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]); $root->appendChild($entry); } return(trim($doc->saveXML())); } /** * @brief Generate an atom feed for the given user * * This function is called when another server is pulling data from the user feed. * * @param string $dfrn_id DFRN ID from the requesting party * @param string $owner_nick Owner nick name * @param string $last_update Date of the last update * @param int $direction Can be -1, 0 or 1. * * @return string DFRN feed entries */ function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) { $a = get_app(); $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic $public_feed = (($dfrn_id) ? false : true); $starred = false; // not yet implemented, possible security issues $converse = false; if($public_feed && $a->argc > 2) { for($x = 2; $x < $a->argc; $x++) { if($a->argv[$x] == 'converse') $converse = true; if($a->argv[$x] == 'starred') $starred = true; if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) $category = $a->argv[$x+1]; } } // default permissions - anonymous user $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' "; $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1", dbesc($owner_nick) ); if(! count($r)) killme(); $owner = $r[0]; $owner_id = $owner['user_uid']; $owner_nick = $owner['nickname']; $sql_post_table = ""; $visibility = ""; if(! $public_feed) { $sql_extra = ''; switch($direction) { case (-1): $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id)); $my_id = $dfrn_id; break; case 0: $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); $my_id = '1:' . $dfrn_id; break; case 1: $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); $my_id = '0:' . $dfrn_id; break; default: return false; break; // NOTREACHED } $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1", intval($owner_id) ); if(! count($r)) killme(); $contact = $r[0]; require_once('include/security.php'); $groups = init_groups_visitor($contact['id']); if(count($groups)) { for($x = 0; $x < count($groups); $x ++) $groups[$x] = '<' . intval($groups[$x]) . '>' ; $gs = implode('|', $groups); } else $gs = '<<>>' ; // Impossible to match $sql_extra = sprintf(" AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' ) AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' ) AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' ) AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s') ", intval($contact['id']), intval($contact['id']), dbesc($gs), dbesc($gs) ); } if($public_feed) $sort = 'DESC'; else $sort = 'ASC'; $date_field = "`changed`"; $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC"; if(! strlen($last_update)) $last_update = 'now -30 days'; if(isset($category)) { $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ", dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id)); //$sql_extra .= file_tag_file_query('item',$category,'category'); } if($public_feed) { if(! $converse) $sql_extra .= " AND `contact`.`self` = 1 "; } $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' ) // dbesc($check_date), $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`, `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`, `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`, `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer` FROM `item` $sql_post_table INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0 AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s' $sql_extra ORDER BY $sql_order LIMIT 0, 300", intval($owner_id), dbesc($check_date), dbesc($sort) ); // Will check further below if this actually returned results. // We will provide an empty feed if that is the case. $items = $r; $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; $alternatelink = $owner['url']; if(isset($category)) $alternatelink .= "/category/".$category; if ($public_feed) $author = "dfrn:owner"; else $author = "author"; $root = self::add_header($doc, $owner, $author, $alternatelink, true); // This hook can't work anymore // call_hooks('atom_feed', $atom); if(! count($items)) { $atom = trim($doc->saveXML()); call_hooks('atom_feed_end', $atom); return $atom; } foreach($items as $item) { // prevent private email from leaking. if($item['network'] === NETWORK_MAIL) continue; // public feeds get html, our own nodes use bbcode if($public_feed) { $type = 'html'; // catch any email that's in a public conversation and make sure it doesn't leak if($item['private']) continue; } else $type = 'text'; $entry = self::entry($doc, $type, $item, $owner, true); $root->appendChild($entry); } $atom = trim($doc->saveXML()); call_hooks('atom_feed_end', $atom); return $atom; } /** * @brief Create XML text for DFRN mails * * @param array $item message elements * @param array $owner Owner record * * @return string DFRN mail */ function mail($item, $owner) { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; $root = self::add_header($doc, $owner, "dfrn:owner", "", false); $mail = $doc->createElement("dfrn:mail"); $sender = $doc->createElement("dfrn:sender"); xml_add_element($doc, $sender, "dfrn:name", $owner['name']); xml_add_element($doc, $sender, "dfrn:uri", $owner['url']); xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']); $mail->appendChild($sender); xml_add_element($doc, $mail, "dfrn:id", $item['uri']); xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']); xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)); xml_add_element($doc, $mail, "dfrn:subject", $item['title']); xml_add_element($doc, $mail, "dfrn:content", $item['body']); $root->appendChild($mail); return(trim($doc->saveXML())); } /** * @brief Create XML text for DFRN friend suggestions * * @param array $item suggestion elements * @param array $owner Owner record * * @return string DFRN suggestions */ function fsuggest($item, $owner) { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; $root = self::add_header($doc, $owner, "dfrn:owner", "", false); $suggest = $doc->createElement("dfrn:suggest"); xml_add_element($doc, $suggest, "dfrn:url", $item['url']); xml_add_element($doc, $suggest, "dfrn:name", $item['name']); xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']); xml_add_element($doc, $suggest, "dfrn:request", $item['request']); xml_add_element($doc, $suggest, "dfrn:note", $item['note']); $root->appendChild($suggest); return(trim($doc->saveXML())); } /** * @brief Create XML text for DFRN relocations * * @param array $owner Owner record * @param int $uid User ID * * @return string DFRN relocations */ function relocate($owner, $uid) { /* get site pubkey. this could be a new installation with no site keys*/ $pubkey = get_config('system','site_pubkey'); if(! $pubkey) { $res = new_keypair(1024); set_config('system','site_prvkey', $res['prvkey']); set_config('system','site_pubkey', $res['pubkey']); } $rp = q("SELECT `resource-id` , `scale`, type FROM `photo` WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid); $photos = array(); $ext = Photo::supportedTypes(); foreach($rp as $p) $photos[$p['scale']] = app::get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']]; unset($rp, $ext); $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; $root = self::add_header($doc, $owner, "dfrn:owner", "", false); $relocate = $doc->createElement("dfrn:relocate"); xml_add_element($doc, $relocate, "dfrn:url", $owner['url']); xml_add_element($doc, $relocate, "dfrn:name", $owner['name']); xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]); xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]); xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]); xml_add_element($doc, $relocate, "dfrn:request", $owner['request']); xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']); xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']); xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']); xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey')); $root->appendChild($relocate); return(trim($doc->saveXML())); } /** * @brief Adds the header elements for the DFRN protocol * * @param object $doc XML document * @param array $owner Owner record * @param string $authorelement Element name for the author * @param string $alternatelink link to profile or category * @param bool $public Is it a header for public posts? * * @return object XML root object */ private function add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) { if ($alternatelink == "") $alternatelink = $owner['url']; $root = $doc->createElementNS(NS_ATOM, 'feed'); $doc->appendChild($root); $root->setAttribute("xmlns:thr", NS_THR); $root->setAttribute("xmlns:at", "http://purl.org/atompub/tombstones/1.0"); $root->setAttribute("xmlns:media", NS_MEDIA); $root->setAttribute("xmlns:dfrn", "http://purl.org/macgirvin/dfrn/1.0"); $root->setAttribute("xmlns:activity", NS_ACTIVITY); $root->setAttribute("xmlns:georss", NS_GEORSS); $root->setAttribute("xmlns:poco", NS_POCO); $root->setAttribute("xmlns:ostatus", NS_OSTATUS); $root->setAttribute("xmlns:statusnet", NS_STATUSNET); xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]); xml_add_element($doc, $root, "title", $owner["name"]); $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); $attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/"); xml_add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink); xml_add_element($doc, $root, "link", "", $attributes); if ($public) { // DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed. ostatus_hublinks($doc, $root); $attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); xml_add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); xml_add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); xml_add_element($doc, $root, "link", "", $attributes); } if ($owner['page-flags'] == PAGE_COMMUNITY) xml_add_element($doc, $root, "dfrn:community", 1); /// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP" xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); $author = self::add_author($doc, $owner, $authorelement, $public); $root->appendChild($author); return $root; } /** * @brief Adds the author element in the header for the DFRN protocol * * @param object $doc XML document * @param array $owner Owner record * @param string $authorelement Element name for the author * * @return object XML author object */ private function add_author($doc, $owner, $authorelement, $public) { $author = $doc->createElement($authorelement); $namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME); $uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME); $picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME); $attributes = array("dfrn:updated" => $namdate); xml_add_element($doc, $author, "name", $owner["name"], $attributes); $attributes = array("dfrn:updated" => $namdate); xml_add_element($doc, $author, "uri", app::get_baseurl().'/profile/'.$owner["nickname"], $attributes); $attributes = array("dfrn:updated" => $namdate); xml_add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes); $attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate, "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); xml_add_element($doc, $author, "link", "", $attributes); $attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate, "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); xml_add_element($doc, $author, "link", "", $attributes); $birthday = feed_birthday($owner['user_uid'], $owner['timezone']); if ($birthday) xml_add_element($doc, $author, "dfrn:birthday", $birthday); // The following fields will only be generated if this isn't for a public feed if ($public) return $author; // Only show contact details when we are allowed to $r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`, `profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob` FROM `profile` INNER JOIN `user` ON `user`.`uid` = `profile`.`uid` WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d", intval($owner['user_uid'])); if ($r) { $profile = $r[0]; xml_add_element($doc, $author, "poco:displayName", $profile["name"]); xml_add_element($doc, $author, "poco:updated", $namdate); if (trim($profile["dob"]) != "0000-00-00") xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"]))); xml_add_element($doc, $author, "poco:note", $profile["about"]); xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]); $savetz = date_default_timezone_get(); date_default_timezone_set($profile["timezone"]); xml_add_element($doc, $author, "poco:utcOffset", date("P")); date_default_timezone_set($savetz); if (trim($profile["homepage"]) != "") { $urls = $doc->createElement("poco:urls"); xml_add_element($doc, $urls, "poco:type", "homepage"); xml_add_element($doc, $urls, "poco:value", $profile["homepage"]); xml_add_element($doc, $urls, "poco:primary", "true"); $author->appendChild($urls); } if (trim($profile["pub_keywords"]) != "") { $keywords = explode(",", $profile["pub_keywords"]); foreach ($keywords AS $keyword) xml_add_element($doc, $author, "poco:tags", trim($keyword)); } /// @todo When we are having the XMPP address in the profile we should propagate it here $xmpp = ""; if (trim($xmpp) != "") { $ims = $doc->createElement("poco:ims"); xml_add_element($doc, $ims, "poco:type", "xmpp"); xml_add_element($doc, $ims, "poco:value", $xmpp); xml_add_element($doc, $ims, "poco:primary", "true"); $author->appendChild($ims); } if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") { $element = $doc->createElement("poco:address"); xml_add_element($doc, $element, "poco:formatted", formatted_location($profile)); if (trim($profile["locality"]) != "") xml_add_element($doc, $element, "poco:locality", $profile["locality"]); if (trim($profile["region"]) != "") xml_add_element($doc, $element, "poco:region", $profile["region"]); if (trim($profile["country-name"]) != "") xml_add_element($doc, $element, "poco:country", $profile["country-name"]); $author->appendChild($element); } } return $author; } /** * @brief Adds the author elements in the "entry" elements of the DFRN protocol * * @param object $doc XML document * @param string $element Element name for the author * @param string $contact_url Link of the contact * @param array $items Item elements * * @return object XML author object */ private function add_entry_author($doc, $element, $contact_url, $item) { $contact = get_contact_details_by_url($contact_url, $item["uid"]); $author = $doc->createElement($element); xml_add_element($doc, $author, "name", $contact["name"]); xml_add_element($doc, $author, "uri", $contact["url"]); xml_add_element($doc, $author, "dfrn:handle", $contact["addr"]); /// @Todo /// - Check real image type and image size /// - Check which of these boths elements we should use $attributes = array( "rel" => "photo", "type" => "image/jpeg", "media:width" => 80, "media:height" => 80, "href" => $contact["photo"]); xml_add_element($doc, $author, "link", "", $attributes); $attributes = array( "rel" => "avatar", "type" => "image/jpeg", "media:width" => 80, "media:height" => 80, "href" => $contact["photo"]); xml_add_element($doc, $author, "link", "", $attributes); return $author; } /** * @brief Adds the activity elements * * @param object $doc XML document * @param string $element Element name for the activity * @param string $activity activity value * * @return object XML activity object */ private function create_activity($doc, $element, $activity) { if($activity) { $entry = $doc->createElement($element); $r = parse_xml_string($activity, false); if(!$r) return false; if($r->type) xml_add_element($doc, $entry, "activity:object-type", $r->type); if($r->id) xml_add_element($doc, $entry, "id", $r->id); if($r->title) xml_add_element($doc, $entry, "title", $r->title); if($r->link) { if(substr($r->link,0,1) === '<') { if(strstr($r->link,'&') && (! strstr($r->link,'&'))) $r->link = str_replace('&','&', $r->link); $r->link = preg_replace('/\/','',$r->link); $data = parse_xml_string($r->link, false); foreach ($data->attributes() AS $parameter => $value) $attributes[$parameter] = $value; } else $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link); xml_add_element($doc, $entry, "link", "", $attributes); } if($r->content) xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); return $entry; } return false; } /** * @brief Adds the elements for attachments * * @param object $doc XML document * @param object $root XML root * @param array $item Item element * * @return object XML attachment object */ private function get_attachment($doc, $root, $item) { $arr = explode('[/attach],',$item['attach']); if(count($arr)) { foreach($arr as $r) { $matches = false; $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); if($cnt) { $attributes = array("rel" => "enclosure", "href" => $matches[1], "type" => $matches[3]); if(intval($matches[2])) $attributes["length"] = intval($matches[2]); if(trim($matches[4]) != "") $attributes["title"] = trim($matches[4]); xml_add_element($doc, $root, "link", "", $attributes); } } } } /** * @brief Adds the "entry" elements for the DFRN protocol * * @param object $doc XML document * @param string $type "text" or "html" * @param array $item Item element * @param array $owner Owner record * @param bool $comment Trigger the sending of the "comment" element * @param int $cid Contact ID of the recipient * * @return object XML entry object */ private function entry($doc, $type, $item, $owner, $comment = false, $cid = 0) { $mentioned = array(); if(!$item['parent']) return; if($item['deleted']) { $attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)); return xml_create_element($doc, "at:deleted-entry", "", $attributes); } $entry = $doc->createElement("entry"); if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid); else $body = $item['body']; if ($type == 'html') { $htmlbody = $body; if ($item['title'] != "") $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody; $htmlbody = bbcode($htmlbody, false, false, 7); } $author = self::add_entry_author($doc, "author", $item["author-link"], $item); $entry->appendChild($author); $dfrnowner = self::add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item); $entry->appendChild($dfrnowner); if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"])); $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); $attributes = array("ref" => $parent_item, "type" => "text/html", "href" => app::get_baseurl().'/display/'.$parent[0]['guid'], "dfrn:diaspora_guid" => $parent[0]['guid']); xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes); } xml_add_element($doc, $entry, "id", $item["uri"]); xml_add_element($doc, $entry, "title", $item["title"]); xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); // "dfrn:env" is used to read the content xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true)); // The "content" field is not read by the receiver. We could remove it when the type is "text" // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env" xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type)); // We save this value in "plink". Maybe we should read it from there as well? xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", "href" => app::get_baseurl()."/display/".$item["guid"])); // "comment-allow" is some old fashioned stuff for old Friendica versions. // It is included in the rewritten code for completeness if ($comment) xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child'])); if($item['location']) xml_add_element($doc, $entry, "dfrn:location", $item['location']); if($item['coord']) xml_add_element($doc, $entry, "georss:point", $item['coord']); if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1)); if($item['extid']) xml_add_element($doc, $entry, "dfrn:extid", $item['extid']); if($item['bookmark']) xml_add_element($doc, $entry, "dfrn:bookmark", "true"); if($item['app']) xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app'])); xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]); // The signed text contains the content in Markdown, the sender handle and the signatur for the content // It is needed for relayed comments to Diaspora. if($item['signed_text']) { $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']))); xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign); } xml_add_element($doc, $entry, "activity:verb", construct_verb($item)); if ($item['object-type'] != "") xml_add_element($doc, $entry, "activity:object-type", $item['object-type']); elseif ($item['id'] == $item['parent']) xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); else xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT); $actobj = self::create_activity($doc, "activity:object", $item['object']); if ($actobj) $entry->appendChild($actobj); $actarg = self::create_activity($doc, "activity:target", $item['target']); if ($actarg) $entry->appendChild($actarg); $tags = item_getfeedtags($item); if(count($tags)) { foreach($tags as $t) if (($type != 'html') OR ($t[0] != "@")) xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2])); } if(count($tags)) foreach($tags as $t) if ($t[0] == "@") $mentioned[$t[1]] = $t[1]; foreach ($mentioned AS $mention) { $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", intval($owner["uid"]), dbesc(normalise_link($mention))); if ($r[0]["forum"] OR $r[0]["prv"]) xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", "ostatus:object-type" => ACTIVITY_OBJ_GROUP, "href" => $mention)); else xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", "ostatus:object-type" => ACTIVITY_OBJ_PERSON, "href" => $mention)); } self::get_attachment($doc, $entry, $item); return $entry; } /** * @brief Delivers the atom content to the contacts * * @param array $owner Owner record * @param array $contactr Contact record of the receiver * @param string $atom Content that will be transmitted * @param bool $dissolve (to be documented) * * @return int Deliver status. -1 means an error. */ function deliver($owner,$contact,$atom, $dissolve = false) { $a = get_app(); $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']); if($contact['duplex'] && $contact['dfrn-id']) $idtosend = '0:' . $orig_id; if($contact['duplex'] && $contact['issued-id']) $idtosend = '1:' . $orig_id; $rino = get_config('system','rino_encrypt'); $rino = intval($rino); // use RINO1 if mcrypt isn't installed and RINO2 was selected if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1; logger("Local rino version: ". $rino, LOGGER_DEBUG); $ssl_val = intval(get_config('system','ssl_policy')); $ssl_policy = ''; switch($ssl_val){ case SSL_POLICY_FULL: $ssl_policy = 'full'; break; case SSL_POLICY_SELFSIGN: $ssl_policy = 'self'; break; case SSL_POLICY_NONE: default: $ssl_policy = 'none'; break; } $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : ''); logger('dfrn_deliver: ' . $url); $xml = fetch_url($url); $curl_stat = $a->get_curl_code(); if(! $curl_stat) return(-1); // timed out logger('dfrn_deliver: ' . $xml, LOGGER_DATA); if(! $xml) return 3; if(strpos($xml,'status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) return (($res->status) ? $res->status : 3); $postvars = array(); $sent_dfrn_id = hex2bin((string) $res->dfrn_id); $challenge = hex2bin((string) $res->challenge); $perm = (($res->perm) ? $res->perm : null); $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0); $rino_remote_version = intval($res->rino); $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0); logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG); if($owner['page-flags'] == PAGE_PRVGROUP) $page = 2; $final_dfrn_id = ''; if($perm) { if((($perm == 'rw') && (! intval($contact['writable']))) || (($perm == 'r') && (intval($contact['writable'])))) { q("update contact set writable = %d where id = %d", intval(($perm == 'rw') ? 1 : 0), intval($contact['id']) ); $contact['writable'] = (string) 1 - intval($contact['writable']); } } if(($contact['duplex'] && strlen($contact['pubkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']); } else { openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']); openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']); } $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.')); if(strpos($final_dfrn_id,':') == 1) $final_dfrn_id = substr($final_dfrn_id,2); if($final_dfrn_id != $orig_id) { logger('dfrn_deliver: wrong dfrn_id.'); // did not decode properly - cannot trust this site return 3; } $postvars['dfrn_id'] = $idtosend; $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION; if($dissolve) $postvars['dissolve'] = '1'; if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { $postvars['data'] = $atom; $postvars['perm'] = 'rw'; } else { $postvars['data'] = str_replace('1','0',$atom); $postvars['perm'] = 'r'; } $postvars['ssl_policy'] = $ssl_policy; if($page) $postvars['page'] = $page; if($rino>0 && $rino_remote_version>0 && (! $dissolve)) { logger('rino version: '. $rino_remote_version); switch($rino_remote_version) { case 1: // Deprecated rino version! $key = substr(random_string(),0,16); $data = aes_encrypt($postvars['data'],$key); break; case 2: // RINO 2 based on php-encryption try { $key = Crypto::createNewRandomKey(); } catch (CryptoTestFailed $ex) { logger('Cannot safely create a key'); return -1; } catch (CannotPerformOperation $ex) { logger('Cannot safely create a key'); return -1; } try { $data = Crypto::encrypt($postvars['data'], $key); } catch (CryptoTestFailed $ex) { logger('Cannot safely perform encryption'); return -1; } catch (CannotPerformOperation $ex) { logger('Cannot safely perform encryption'); return -1; } break; default: logger("rino: invalid requested verision '$rino_remote_version'"); return -1; } $postvars['rino'] = $rino_remote_version; $postvars['data'] = bin2hex($data); #logger('rino: sent key = ' . $key, LOGGER_DEBUG); if($dfrn_version >= 2.1) { if(($contact['duplex'] && strlen($contact['pubkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); else openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); } else { if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); else openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); } logger('md5 rawkey ' . md5($postvars['key'])); $postvars['key'] = bin2hex($postvars['key']); } logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA); $xml = post_url($contact['notify'],$postvars); logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA); $curl_stat = $a->get_curl_code(); if((! $curl_stat) || (! strlen($xml))) return(-1); // timed out if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after'))) return(-1); if(strpos($xml,'status; } }