webpage content-type -- needs cleaning up and a security check once all the important bits are in place.

This commit is contained in:
friendica 2013-09-02 01:38:17 -07:00
parent a35d440ff1
commit 8b7757e033
8 changed files with 206 additions and 126 deletions

View File

@ -1009,9 +1009,16 @@ function status_editor($a,$x,$popup=false) {
$geotag = (($x['allow_location']) ? replace_macros(get_markup_template('jot_geotag.tpl'), array()) : '');
$plaintext = true;
if(feature_enabled(local_user(),'richtext'))
$plaintext = false;
if(intval($x['plaintext']))
$plaintext = true;
if(intval($x['mimeselect']))
$mimeselect = mimetype_select($x['profile_uid']);
$tpl = get_markup_template('jot-header.tpl');
$a->page['htmlhead'] .= replace_macros($tpl, array(
@ -1079,6 +1086,7 @@ function status_editor($a,$x,$popup=false) {
'$emtitle' => t('Example: bob@example.com, mary@example.com'),
'$lockstate' => $x['lockstate'],
'$acl' => $x['acl'],
'$mimeselect' => $mimeselect,
'$showacl' => ((array_key_exists('showacl',$x)) ? $x['showacl'] : 'yes'),
'$bang' => $x['bang'],
'$profile_uid' => $x['profile_uid'],

View File

@ -1397,14 +1397,10 @@ function item_store($arr,$allow_exec = false) {
$arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 );
$arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 );
// this is a bit messy - we really need an input filter chain that temporarily undoes obscuring
if($arr['mimetype'] != 'text/html' && $arr['mimetype'] != 'application/x-php') {
if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false))
$arr['body'] = escape_tags($arr['body']);
if((strpos($arr['title'],'<') !== false) || (strpos($arr['title'],'>') !== false))
$arr['title'] = escape_tags($arr['title']);
}
$arr['body'] = z_input_filter($arr['uid'],$arr['body'],$arr['mimetype']);
$arr['title'] = escape_tags($arr['title']);
// only detect language if we have text content, and if the post is private but not yet
// obscured, make it so.

View File

@ -81,6 +81,34 @@ function escape_tags($string) {
}
function z_input_filter($channel_id,$s,$type = 'text/bbcode') {
if($type === 'text/bbcode')
return escape_tags($s);
if($type === 'text/markdown')
return escape_tags($s);
if($type == 'text/plain')
return escape_tags($s);
$r = q("select account_id, account_roles from account left join channel on channel_account_id = account_id where channel_id = %d limit 1",
intval($channel_id)
);
if($r && ($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWEXEC)) {
if(local_user() && (get_account_id() == $r[0]['account_id'])) {
return $s;
}
}
if($type === 'text/html')
return purify_html($s);
return escape_tags($s);
}
function purify_html($s) {
require_once('library/HTMLPurifier.auto.php');
require_once('include/html2bbcode.php');
@ -1127,6 +1155,7 @@ function prepare_body(&$item,$attach = false) {
function prepare_text($text,$content_type = 'text/bbcode') {
switch($content_type) {
case 'text/plain':
@ -1291,6 +1320,37 @@ function unamp($s) {
}
function mimetype_select($channel_id, $current = 'text/bbcode') {
$x = array(
'text/bbcode',
'text/html',
'text/markdown',
'text/plain'
);
$r = q("select account_flags from account left join channel on account_id = channel_account_id where
channel_id = %d limit 1",
intval($channel_id)
);
if($r) {
if($r[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) {
$x[] = 'application/x-php';
}
}
$o = t('Page content type: ');
$o .= '<select name="mimetype" id="mimetype-select">';
foreach($x as $y) {
$select = (($y == $current) ? ' selected="selected" ' : '');
$o .= '<option name="' . $y . '"' . $select . '>' . $y . '</option>';
}
$o .= '</select>';
return $o;
}

View File

@ -1,6 +1,6 @@
<?php
function webpages_content(&$a) {
function blocks_content(&$a) {
if(argc() > 1)
$which = argv(1);

View File

@ -44,7 +44,7 @@ function item_post(&$a) {
call_hooks('post_local_start', $_REQUEST);
// logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA);
logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA);
$api_source = ((x($_REQUEST,'api_source') && $_REQUEST['api_source']) ? true : false);
@ -221,7 +221,7 @@ function item_post(&$a) {
$verb = $orig_post['verb'];
$app = $orig_post['app'];
$title = escape_tags(trim($_REQUEST['title']));
$body = escape_tags(trim($_REQUEST['body']));
$body = $_REQUEST['body'];
$private = $orig_post['item_private'];
}
@ -255,7 +255,7 @@ function item_post(&$a) {
$coord = notags(trim($_REQUEST['coord']));
$verb = notags(trim($_REQUEST['verb']));
$title = escape_tags(trim($_REQUEST['title']));
$body = escape_tags(trim($_REQUEST['body']));
$body = $_REQUEST['body'];
$private = (
( strlen($str_group_allow)
@ -310,154 +310,164 @@ function item_post(&$a) {
$post_type = notags(trim($_REQUEST['type']));
$content_type = notags(trim($_REQUEST['content_type']));
if(! $content_type)
$content_type = 'text/bbcode';
$mimetype = notags(trim($_REQUEST['mimetype']));
if(! $mimetype)
$mimetype = 'text/bbcode';
// Verify ability to use html or php!!!
// BBCODE alert: the following functions assume bbcode input
// and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.)
// we may need virtual or template classes to implement the possible alternatives
// Work around doubled linefeeds in Tinymce 3.5b2
// First figure out if it's a status post that would've been
// created using tinymce. Otherwise leave it alone.
$plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true);
if((! $parent) && (! $api_source) && (! $plaintext)) {
$body = fix_mce_lf($body);
if($preview) {
$body = z_input_filter($profile_uid,$body,$mimetype);
}
// If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it.
logger('body: ' . $body);
if((! $parent) && (substr_count($str_contact_allow,'<') == 1) && ($str_group_allow == '') && ($str_contact_deny == '') && ($str_group_deny == '')) {
$x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc(str_replace(array('<','>'),array('',''),$str_contact_allow)),
intval($profile_uid)
);
if($x && ($x[0]['abook_their_perms'] & PERMS_W_TAGWALL))
$body .= "\n\n@group+" . $x[0]['abook_id'] . "\n";
}
if($mimetype === 'text/bbcode') {
/**
* fix naked links by passing through a callback to see if this is a red site
* (already known to us) which will get a zrl, otherwise link with url
*/
// BBCODE alert: the following functions assume bbcode input
// and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.)
// we may need virtual or template classes to implement the possible alternatives
$body = preg_replace_callback("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\+\,]+)/ism", 'red_zrl_callback', $body);
// Work around doubled linefeeds in Tinymce 3.5b2
// First figure out if it's a status post that would've been
// created using tinymce. Otherwise leave it alone.
/**
*
* When a photo was uploaded into the message using the (profile wall) ajax
* uploader, The permissions are initially set to disallow anybody but the
* owner from seeing it. This is because the permissions may not yet have been
* set for the post. If it's private, the photo permissions should be set
* appropriately. But we didn't know the final permissions on the post until
* now. So now we'll look for links of uploaded messages that are in the
* post and set them to the same permissions as the post itself.
*
*/
$plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true);
if((! $parent) && (! $api_source) && (! $plaintext)) {
$body = fix_mce_lf($body);
}
if(! $preview) {
fix_attached_photo_permissions($profile_uid,$owner_xchan['xchan_hash'],$body,
$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny);
// If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it.
fix_attached_file_permissions($channel,$observer['xchan_hash'],$body,
$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny);
if((! $parent) && (substr_count($str_contact_allow,'<') == 1) && ($str_group_allow == '') && ($str_contact_deny == '') && ($str_group_deny == '')) {
$x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc(str_replace(array('<','>'),array('',''),$str_contact_allow)),
intval($profile_uid)
);
if($x && ($x[0]['abook_their_perms'] & PERMS_W_TAGWALL))
$body .= "\n\n@group+" . $x[0]['abook_id'] . "\n";
}
}
/**
* fix naked links by passing through a callback to see if this is a red site
* (already known to us) which will get a zrl, otherwise link with url
*/
$body = preg_replace_callback("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\+\,]+)/ism", 'red_zrl_callback', $body);
/**
*
* When a photo was uploaded into the message using the (profile wall) ajax
* uploader, The permissions are initially set to disallow anybody but the
* owner from seeing it. This is because the permissions may not yet have been
* set for the post. If it's private, the photo permissions should be set
* appropriately. But we didn't know the final permissions on the post until
* now. So now we'll look for links of uploaded messages that are in the
* post and set them to the same permissions as the post itself.
*
*/
if(! $preview) {
fix_attached_photo_permissions($profile_uid,$owner_xchan['xchan_hash'],$body,
$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny);
fix_attached_file_permissions($channel,$observer['xchan_hash'],$body,
$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny);
}
$body = bb_translate_video($body);
$body = bb_translate_video($body);
/**
* Fold multi-line [code] sequences
*/
/**
* Fold multi-line [code] sequences
*/
$body = preg_replace('/\[\/code\]\s*\[code\]/ism',"\n",$body);
$body = preg_replace('/\[\/code\]\s*\[code\]/ism',"\n",$body);
$body = scale_external_images($body,false);
$body = scale_external_images($body,false);
/**
* Look for any tags and linkify them
*/
/**
* Look for any tags and linkify them
*/
$str_tags = '';
$inform = '';
$post_tags = array();
$str_tags = '';
$inform = '';
$post_tags = array();
$tags = get_tags($body);
$tags = get_tags($body);
$tagged = array();
$tagged = array();
$private_forum = false;
$private_forum = false;
if(count($tags)) {
foreach($tags as $tag) {
if(count($tags)) {
foreach($tags as $tag) {
// If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
// Robert Johnson should be first in the $tags array
// If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
// Robert Johnson should be first in the $tags array
$fullnametagged = false;
for($x = 0; $x < count($tagged); $x ++) {
if(stristr($tagged[$x],$tag . ' ')) {
$fullnametagged = true;
break;
$fullnametagged = false;
for($x = 0; $x < count($tagged); $x ++) {
if(stristr($tagged[$x],$tag . ' ')) {
$fullnametagged = true;
break;
}
}
if($fullnametagged)
continue;
$success = handle_tag($a, $body, $inform, $str_tags, (local_user()) ? local_user() : $profile_uid , $tag);
logger('handle_tag: ' . print_r($success,tue));
if($success['replaced']) {
$tagged[] = $tag;
$post_tags[] = array(
'uid' => $profile_uid,
'type' => $success['termtype'],
'otype' => TERM_OBJ_POST,
'term' => $success['term'],
'url' => $success['url']
);
}
if(is_array($success['contact']) && intval($success['contact']['prv'])) {
$private_forum = true;
$private_id = $success['contact']['id'];
}
}
if($fullnametagged)
continue;
$success = handle_tag($a, $body, $inform, $str_tags, (local_user()) ? local_user() : $profile_uid , $tag);
logger('handle_tag: ' . print_r($success,tue));
if($success['replaced']) {
$tagged[] = $tag;
$post_tags[] = array(
'uid' => $profile_uid,
'type' => $success['termtype'],
'otype' => TERM_OBJ_POST,
'term' => $success['term'],
'url' => $success['url']
);
}
if(is_array($success['contact']) && intval($success['contact']['prv'])) {
$private_forum = true;
$private_id = $success['contact']['id'];
}
}
}
// logger('post_tags: ' . print_r($post_tags,true));
if(($private_forum) && (! $parent) && (! $private)) {
// we tagged a private forum in a top level post and the message was public.
// Restrict it.
$private = 1;
$str_contact_allow = '<' . $private_id . '>';
}
if(($private_forum) && (! $parent) && (! $private)) {
// we tagged a private forum in a top level post and the message was public.
// Restrict it.
$private = 1;
$str_contact_allow = '<' . $private_id . '>';
}
$attachments = '';
$match = false;
$attachments = '';
$match = false;
if(preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/',$body,$match)) {
$attachments = array();
foreach($match[2] as $mtch) {
$hash = substr($mtch,0,strpos($mtch,','));
$rev = intval(substr($mtch,strpos($mtch,',')));
$r = attach_by_hash_nodata($hash,$rev);
if($r['success']) {
$attachments[] = array(
'href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'],
'length' => $r['data']['filesize'],
'type' => $r['data']['filetype'],
'title' => urlencode($r['data']['filename']),
'revision' => $r['data']['revision']
);
if(preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/',$body,$match)) {
$attachments = array();
foreach($match[2] as $mtch) {
$hash = substr($mtch,0,strpos($mtch,','));
$rev = intval(substr($mtch,strpos($mtch,',')));
$r = attach_by_hash_nodata($hash,$rev);
if($r['success']) {
$attachments[] = array(
'href' => $a->get_baseurl() . '/attach/' . $r['data']['hash'],
'length' => $r['data']['filesize'],
'type' => $r['data']['filetype'],
'title' => urlencode($r['data']['filename']),
'revision' => $r['data']['revision']
);
}
$body = str_replace($match[1],'',$body);
}
$body = str_replace($match[1],'',$body);
}
}
@ -530,7 +540,7 @@ function item_post(&$a) {
$datarray['changed'] = datetime_convert();
$datarray['mid'] = $mid;
$datarray['parent_mid'] = $parent_mid;
$datarray['mimetype'] = $content_type;
$datarray['mimetype'] = $mimetype;
$datarray['title'] = $title;
$datarray['body'] = $body;
$datarray['app'] = $app;

View File

@ -52,10 +52,13 @@ require_once ('include/conversation.php');
'bang' => (($group || $cid) ? '!' : ''),
'visitor' => 'block',
'profile_uid' => intval($owner),
'plaintext' => 1,
'mimeselect' => 1,
);
$o .= status_editor($a,$x);
//Get a list of webpages. We can't display all them because endless scroll makes that unusable, so just list titles and an edit link.
//TODO - this should be replaced with pagelist_widget

View File

@ -1 +1 @@
2013-09-01.423
2013-09-02.424

View File

@ -9,6 +9,9 @@
<input type="hidden" name="post_id" value="{{$post_id}}" />
<input type="hidden" name="webpage" value="{{$webpage}}" />
<input type="hidden" name="preview" id="jot-preview" value="0" />
{{$mimeselect}}
<div id="jot-title-wrap"><input name="title" id="jot-title" type="text" placeholder="{{$placeholdertitle}}" value="{{$title}}" class="jothidden" style="display:none"></div>
{{if $catsenabled}}
<div id="jot-category-wrap"><input name="category" id="jot-category" type="text" placeholder="{{$placeholdercategory}}" value="{{$category}}" class="jothidden" style="display:none" /></div>