| 1 |
<?php
|
| 2 |
// $Id: embedfilter.module,v 1.6 2007/09/25 17:09:45 darthsteven Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Enter description here...
|
| 6 |
*
|
| 7 |
* @param string $section
|
| 8 |
* A Drupal path
|
| 9 |
* @return string
|
| 10 |
* The help text
|
| 11 |
*/
|
| 12 |
function embedfilter_help($section) {
|
| 13 |
switch ($section) {
|
| 14 |
case 'admin/help#embedfilter':
|
| 15 |
$output = '<p>'. t('Many great sites like <a href="http://www.youtube.com">YouTube</a>, <a href="http://www.ifilm.com">iFilm</a> and <a href="http://www.nowpublic.com">NowPublic</a> allow their media assets to be served to 3rd party websites (like this one). They usually offer snippets of code to be embedded in a web page that will then load a media object (usually Flash) from their servers. This module lets your site users embed such snippets into posts but gives you the power to decide which hosts to trust.') .'</p>';
|
| 16 |
$output .= '<p>'. t('Here are some examples of code snippets from the sites mentioned above:') .'</p>';
|
| 17 |
$output .= <<<EOF
|
| 18 |
<h3>YouTube.com</h3>
|
| 19 |
<pre>
|
| 20 |
<object width="425" height="350"><param name="movie"
|
| 21 |
value="http://www.youtube.com/v/BqLvBUSJucg">
|
| 22 |
</param><embed src="http://www.youtube.com/v/BqLvBUSJucg"
|
| 23 |
type="application/x-shockwave-flash" width="425" height="350">
|
| 24 |
</embed></object>
|
| 25 |
</pre>
|
| 26 |
<h3>iFilm</h3>
|
| 27 |
<pre>
|
| 28 |
<embed allowScriptAccess="never" width="448" height="365"
|
| 29 |
src="http://www.ifilm.com/efp" quality="high" bgcolor="000000"
|
| 30 |
name="efp" align="middle" type="application/x-shockwave-flash"
|
| 31 |
pluginspage="http://www.macromedia.com/go/getflashplayer"
|
| 32 |
flashvars="flvbaseclip=2761045" ></embed>
|
| 33 |
</pre>
|
| 34 |
<h3>NowPublic</h3>
|
| 35 |
<pre>
|
| 36 |
<script language="JavaScript"
|
| 37 |
src="http://media.nowpublic.com/js/7ae70d3f8c890cebc5c9633f49607ae2.js"></script>
|
| 38 |
</pre>
|
| 39 |
EOF;
|
| 40 |
|
| 41 |
$output .= '<p>'. t('To use this module, start by entering a number of hosts that you wish to allow. This is done from the <a href="/?q=admin/settings/embedfilter">settings page</a>. Examples taken from the sites mentioned above would be: <ul><li>youtube.com</li><li>ifilm.com</li><li>nowpublic.com</li></ul> You can define some HTML that will be embedded before and after any tags (in case you want to be able to visually identify them on your site), as well as some filter tip instructions to your users.') .'</p>';
|
| 42 |
$output .= '<p>'. t('After you have configured the module from the settings page, you need to add the "Object and embed" filter to the appropriate input formats (such as "Filtered HTML") on the <a href="/?q=admin/filters">input formats</a> administration page. Configure your HTML filter to accept <object>, <embed>, <param> and <script> tags by adding them to the list of accepted tags. Configure the order of the filters so that the "Object and embed tag" filter comes directly after the "HTML" filter. After this you should be ready to go!'). '</p>';
|
| 43 |
return $output;
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
| 47 |
/**
|
| 48 |
* Implemention of hook_menu
|
| 49 |
*
|
| 50 |
*/
|
| 51 |
function embedfilter_menu() {
|
| 52 |
$items = array();
|
| 53 |
$items['admin/settings/embedfilter'] = array(
|
| 54 |
'title' => t('Embed filter'),
|
| 55 |
'description' => t('Settings for Embed filter.'),
|
| 56 |
'page callback' => 'drupal_get_form',
|
| 57 |
'page arguments' => array('embedfilter_admin_settings'),
|
| 58 |
'access arguments' => array('administer filters'),
|
| 59 |
'type' => MENU_NORMAL_ITEM,
|
| 60 |
);
|
| 61 |
return $items;
|
| 62 |
}
|
| 63 |
|
| 64 |
/**
|
| 65 |
* Implementation of hook_settings
|
| 66 |
*
|
| 67 |
* @return a $form array
|
| 68 |
*/
|
| 69 |
function embedfilter_admin_settings() {
|
| 70 |
$form['embedfilter_host_whitelist'] = array(
|
| 71 |
'#type' => 'textarea',
|
| 72 |
'#title' => t('Allowed hosts'),
|
| 73 |
'#default_value' => variable_get('embedfilter_host_whitelist', "http://www.youtube.com\nhttp://media.nowpublic.com"),
|
| 74 |
'#description' => t('Each host name from which <object>, <embed>, <param> or <script> tags should be accepted. One host per line. This module is patched to expect only domain.tld format and ignores subdomains. Example: youtube.com'),
|
| 75 |
);
|
| 76 |
|
| 77 |
$form['extramarkup'] = array(
|
| 78 |
'#type' => 'fieldset',
|
| 79 |
'#title' => t('Extra markup'),
|
| 80 |
'#collapsible' => true,
|
| 81 |
'#collapsed' => true,
|
| 82 |
);
|
| 83 |
$form['extramarkup']['embedfilter_pre_markup'] = array(
|
| 84 |
'#type' => 'textarea',
|
| 85 |
'#default_value' => variable_get('embedfilter_pre_markup', ''),
|
| 86 |
'#title' => t('Pre tag markup'),
|
| 87 |
'#description' => t('Markup that should appear directly before an <object>, <embed> or <script> tag. This only applies to the outermost tag in the case where nested tags are found.'),
|
| 88 |
);
|
| 89 |
$form['extramarkup']['embedfilter_post_markup'] = array(
|
| 90 |
'#type' => 'textarea',
|
| 91 |
'#default_value' => variable_get('embedfilter_post_markup', ''),
|
| 92 |
'#title' => t('Post tag markup'),
|
| 93 |
'#description' => t('Markup that should appear directly after an </object>, </embed> or </script> tag. This only applies to the outermost tag in the case where nested tags are found.'),
|
| 94 |
);
|
| 95 |
|
| 96 |
$form['filtertip'] = array(
|
| 97 |
'#type' => 'fieldset',
|
| 98 |
'#title' => t('Filter tip'),
|
| 99 |
'#collapsible' => true,
|
| 100 |
'#collapsed' => true,
|
| 101 |
);
|
| 102 |
$form['filtertip']['embedfilter_filtertip'] = array(
|
| 103 |
'#type' => 'textarea',
|
| 104 |
'#default_value' => variable_get('embedfilter_filtertip', t('You can use <object>, <embed> and <script> tags from the following sites to add media to your posts:')),
|
| 105 |
'#title' => t('Custom filter tips'),
|
| 106 |
'#description' => t('Instructions to your site users about how to use these tags. The whitelist will also be displayed.'),
|
| 107 |
);
|
| 108 |
|
| 109 |
return system_settings_form($form);
|
| 110 |
}
|
| 111 |
|
| 112 |
function embedfilter_get_whitelist() {
|
| 113 |
static $whitelist;
|
| 114 |
if (empty($whitelist)) {
|
| 115 |
$var = variable_get('embedfilter_host_whitelist', '');
|
| 116 |
$whitelist = array_filter(split("[\n|\r]", $var), 'trim');
|
| 117 |
}
|
| 118 |
return $whitelist;
|
| 119 |
}
|
| 120 |
|
| 121 |
/**
|
| 122 |
* Implementation of hook_filter_tips()
|
| 123 |
*
|
| 124 |
* @param int $delta
|
| 125 |
* Used when a module defines more than one filter
|
| 126 |
* @param unknown_type $format
|
| 127 |
* @param boolean $long
|
| 128 |
* Determines whether the long or the short tip version is displayed
|
| 129 |
* @return string
|
| 130 |
* The tip to be displayed
|
| 131 |
*/
|
| 132 |
function embedfilter_filter_tips($delta, $format, $long = false) {
|
| 133 |
$output .= '<p>'. variable_get('embedfilter_filtertip', t('You can use <object>, <embed> and <script> tags from the following sites to add media to your posts:')). "\n";
|
| 134 |
$output .= theme('item_list', embedfilter_get_whitelist()). "</p>\n";
|
| 135 |
return $output;
|
| 136 |
}
|
| 137 |
|
| 138 |
/**
|
| 139 |
* Implentation of hook_filter. Defines a filter, "Object and embed tag filter",
|
| 140 |
* that can be used in conjunction with the built in HTML filter to allow
|
| 141 |
* users to include <object>, <embed> and <script> tags within their posts,
|
| 142 |
* as long as any href or src elements point to trusted hosts defined on the
|
| 143 |
* whitelist (see admin/settings/embedfilter).
|
| 144 |
*
|
| 145 |
* @param string $op
|
| 146 |
* @param int $delta
|
| 147 |
* @param int $format
|
| 148 |
* @param string $text
|
| 149 |
* The text to be filtered
|
| 150 |
* @return string
|
| 151 |
*/
|
| 152 |
function embedfilter_filter($op, $delta = 0, $format = -1, $text = '', $langcode = '', $cache_id = 0) {
|
| 153 |
switch ($op) {
|
| 154 |
case 'list':
|
| 155 |
return array(0 => t('Object and embed tag filter'));
|
| 156 |
|
| 157 |
case 'description':
|
| 158 |
return t('Lets users safely add <code><object></code>, <code><embed></code>, and <code><script></code> tags to posts.');
|
| 159 |
|
| 160 |
case 'process':
|
| 161 |
$text = embedfilter_process($text);
|
| 162 |
return $text;
|
| 163 |
|
| 164 |
default:
|
| 165 |
return $text;
|
| 166 |
}
|
| 167 |
}
|
| 168 |
|
| 169 |
/**
|
| 170 |
* A recursive function that finds all <embed>, <object> and <script>
|
| 171 |
* tags and either approves them (based on the whitelist) or removes them.
|
| 172 |
*
|
| 173 |
* No text is allowed between <script> and </script> tags.
|
| 174 |
*
|
| 175 |
* No on*= attributes are allowed (to prevent unwanted scripting)
|
| 176 |
*
|
| 177 |
* @param string or array $input
|
| 178 |
* @return string
|
| 179 |
* The processed input string.
|
| 180 |
*/
|
| 181 |
function embedfilter_process($input) {
|
| 182 |
static $count;
|
| 183 |
|
| 184 |
// This function is called recursively to handle <embed> tags in <object> tags.
|
| 185 |
|
| 186 |
// If $input is an array, we are coming from preg_replace_callback.
|
| 187 |
if (is_array($input)) {
|
| 188 |
$count++;
|
| 189 |
$output = '';
|
| 190 |
|
| 191 |
// Check to see if the host is on the whitelist.
|
| 192 |
if (embedfilter_approve($input[2])) {
|
| 193 |
// The first time through we add the pre-markup.
|
| 194 |
if ($count === 1) {
|
| 195 |
$output = variable_get('embedfilter_pre_markup', '');
|
| 196 |
}
|
| 197 |
$output .= '<'. $input[1]. embedfilter_noevents(embedfilter_sanitize($input[2]));
|
| 198 |
|
| 199 |
// if there are opening and closing <script> tags, and there is
|
| 200 |
// anything in between them, deny.
|
| 201 |
if (count($input) == 4 && strtolower($input[1]) == 'script' && strlen(trim($input[3])) > 0) {
|
| 202 |
$count--;
|
| 203 |
return '';
|
| 204 |
}
|
| 205 |
// 4 part arrays have closing tags: <embed.... ></embed>
|
| 206 |
if (count($input) == 4) {
|
| 207 |
$output .= '>'. embedfilter_process($input[3]). '</'. $input[1]. '>';
|
| 208 |
}
|
| 209 |
// 3 part arrays are single, closed tags: <embed.... />
|
| 210 |
else if (count($input) == 3) {
|
| 211 |
$output .= '/>';
|
| 212 |
}
|
| 213 |
if ($count === 1) {
|
| 214 |
$output .= variable_get('embedfilter_post_markup', '');
|
| 215 |
}
|
| 216 |
$count--;
|
| 217 |
} else {
|
| 218 |
return '';
|
| 219 |
}
|
| 220 |
// If the embedfilter_approve step failed, we return an empty string.
|
| 221 |
$input = $output;
|
| 222 |
}
|
| 223 |
else {
|
| 224 |
// find open tag/close tag pairs.
|
| 225 |
$input = preg_replace_callback('@<(embed|object|script)([^>]*)>(.*?)</\1>@si', 'embedfilter_process', $input, 5);
|
| 226 |
|
| 227 |
// find single, closed tags
|
| 228 |
$input = preg_replace_callback('@<(embed|object|script)([^>]*)/>@si', 'embedfilter_process', $input, 5);
|
| 229 |
}
|
| 230 |
|
| 231 |
return embedfilter_media_resize($input, 550);
|
| 232 |
}
|
| 233 |
|
| 234 |
/**
|
| 235 |
* Checks a string for the presence of src and href attributes.
|
| 236 |
* If found, the URLs within those attributes are compared to the
|
| 237 |
* hosts in whitelist.
|
| 238 |
*
|
| 239 |
* @param string $input
|
| 240 |
* @return boolean
|
| 241 |
* true if all the hosts are on the whitelist, false if any host is not.
|
| 242 |
*/
|
| 243 |
function embedfilter_approve($input) {
|
| 244 |
$pattern = '@(src|href|data)=([\'"])([^"]+)\\2@i';
|
| 245 |
$matches = array();
|
| 246 |
preg_match_all($pattern, $input, $matches);
|
| 247 |
foreach ($matches[3] as $url) {
|
| 248 |
$parts = parse_url($url);
|
| 249 |
//first check if there is no subdomain
|
| 250 |
if (preg_match('/^\.(com|co\.uk|tv|net|org|gov)/',strstr($parts['host'],'.'))) {
|
| 251 |
if (!in_array($parts['host'], embedfilter_get_whitelist())) {
|
| 252 |
return false;
|
| 253 |
}
|
| 254 |
//otherwise check if domain and subdomain are both missing from the permitted list
|
| 255 |
}
|
| 256 |
elseif ((!in_array(ltrim(strstr($parts['host'],'.'),'.'), embedfilter_get_whitelist())) && (!in_array($parts['host'], embedfilter_get_whitelist()))) {
|
| 257 |
return false;
|
| 258 |
}
|
| 259 |
}
|
| 260 |
return true;
|
| 261 |
}
|
| 262 |
|
| 263 |
/**
|
| 264 |
* Prevents XSS attacks on from the href or src attributes.
|
| 265 |
*
|
| 266 |
* @param string or array $input
|
| 267 |
* @return string
|
| 268 |
* A sanitized string where all href and src attributes have been run through
|
| 269 |
* check_url.
|
| 270 |
*/
|
| 271 |
function embedfilter_sanitize($input) {
|
| 272 |
// arrays are the product of the preg_replace_callback function
|
| 273 |
if (is_array($input)) {
|
| 274 |
$output = $input[1]. '='. $input[2]. check_url($input[3]). $input[2];
|
| 275 |
}
|
| 276 |
else {
|
| 277 |
$pattern = '@(src|href|data)=([\'"])([^"]+)\\2@i';
|
| 278 |
$output = preg_replace_callback($pattern, 'embedfilter_sanitize', $input);
|
| 279 |
}
|
| 280 |
|
| 281 |
//removes any errant slashes from the end of $output
|
| 282 |
$last = $input[strlen($output)-1];
|
| 283 |
if ($last == '/') {
|
| 284 |
$output = substr($output, 0, -1);
|
| 285 |
}
|
| 286 |
|
| 287 |
return $output;
|
| 288 |
}
|
| 289 |
|
| 290 |
/**
|
| 291 |
* Removes any on* attributes so that no scripting can be done.
|
| 292 |
* NOTE: The presence of these attributes does not cause the whole
|
| 293 |
* <object>, <embed> or <script> tag to be removed; only the offending
|
| 294 |
* attributes are removed.
|
| 295 |
*
|
| 296 |
* @param string $input
|
| 297 |
* @return string
|
| 298 |
*/
|
| 299 |
function embedfilter_noevents($input) {
|
| 300 |
$output = '';
|
| 301 |
|
| 302 |
// If $input is an array, we return an empty string because we want to
|
| 303 |
// get rid of whatever matched.
|
| 304 |
if (!is_array($input)) {
|
| 305 |
$pattern = '@( on[^=]+?=([\'"])([^\\2]+?)\\2)?@i';
|
| 306 |
$output = preg_replace_callback($pattern, 'embedfilter_noevents', $input);
|
| 307 |
}
|
| 308 |
return $output;
|
| 309 |
}
|
| 310 |
|
| 311 |
/**
|
| 312 |
* Resizes over-sized media
|
| 313 |
*
|
| 314 |
* @param $input
|
| 315 |
* string of HTML with width and height attributes
|
| 316 |
*
|
| 317 |
* @param $maxwidth
|
| 318 |
* the maximum permitted width on the website for embedded media
|
| 319 |
*
|
| 320 |
* @return
|
| 321 |
* replacement $input HTML string containing revised width and height
|
| 322 |
*/
|
| 323 |
function embedfilter_media_resize($input = '', $maxwidth = 550) {
|
| 324 |
preg_match('/height="([0-9]*)"/',$input,$fetchheight);
|
| 325 |
preg_match('/width="([0-9]*)"/',$input,$fetchwidth);
|
| 326 |
if ($fetchheight && $fetchwidth) {
|
| 327 |
$height = $fetchheight[1];
|
| 328 |
$width = $fetchwidth[1];
|
| 329 |
}
|
| 330 |
|
| 331 |
if ($width > $maxwidth) {
|
| 332 |
$diff = $width-$maxwidth;
|
| 333 |
$ratio = $diff/$width;
|
| 334 |
$heightdiff = $ratio*$height;
|
| 335 |
$height = round($height-$heightdiff);
|
| 336 |
$width = $maxwidth;
|
| 337 |
|
| 338 |
$patterns[0] = '/height="([0-9]*)"/';
|
| 339 |
$patterns[1] = '/width="([0-9]*)"/';
|
| 340 |
$replacements[1] = 'height="'.$height.'"';
|
| 341 |
$replacements[0] = 'width="'.$width.'"';
|
| 342 |
|
| 343 |
$input = preg_replace($patterns, $replacements, $input);
|
| 344 |
}
|
| 345 |
return $input;
|
| 346 |
}
|
| 347 |
|