diff --git a/wysiwyg_filter.admin.inc b/wysiwyg_filter.admin.inc index 38fb424..44ca9cc 100644 --- a/wysiwyg_filter.admin.inc +++ b/wysiwyg_filter.admin.inc @@ -16,7 +16,7 @@ * * remove format suffix, will be first array index * * care for pre- and post-processing, or remove it like parsed-elements * * rewrite validate to get correct values like $form['filters']['settings'][$name] / $form_state['values']['filters'][$name]['settings'] - * + * */ function wysiwyg_filter_filter_wysiwyg_settings(&$form, &$form_state, $filter, $format, $defaults, $filters) { global $base_url; @@ -26,10 +26,10 @@ function wysiwyg_filter_filter_wysiwyg_settings(&$form, &$form_state, $filter, $ $settings = $filter->settings; $settings += $defaults; - + // carry over settings for other formats $filterform = array(); - + // *** valid elements *** $valid_elements = $settings['valid_elements']; $valid_elements_rows = min(20, max(5, substr_count($valid_elements, "\n") + 2)); @@ -111,6 +111,7 @@ This option allows you to specify which HTML elements and attributes are allowed $valid_elements_parsed = wysiwyg_filter_parse_valid_elements($settings['valid_elements']); foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { $field_name = "rule_$rule_key"; + $field_bypass_name = "rule_bypass_$rule_key"; $default_value = wysiwyg_filter_array2csv($settings[$field_name]); $filterform[$field_name] = array( '#type' => 'textarea', @@ -120,10 +121,16 @@ This option allows you to specify which HTML elements and attributes are allowed '#rows' => min(10, max(2, substr_count($default_value, "\n") + 2)), '#description' => $rule_info['description'], ); + $filterform[$field_bypass_name] = array( + '#type' => 'checkbox', + '#title' => t('Bypass %rule', array('%rule' => $rule_info['title'])), + '#default_value' => !empty($settings[$field_bypass_name]), + '#description' => t('Bypassing this rule may lead to security vulnerabilities. Only grant this filter to trusted roles.'), + ); // Display warning if the field is empty but the rule definition is not // complete. - if (empty($default_value) && !_wysiwyg_filter_is_rule_definition_complete($rule_info, $valid_elements_parsed, $enabled_style_properties)) { + if (empty($settings[$field_bypass_name]) && empty($default_value) && !_wysiwyg_filter_is_rule_definition_complete($rule_info, $valid_elements_parsed, $enabled_style_properties)) { drupal_set_message($rule_info['required_by_message'], 'warning'); } } @@ -163,7 +170,7 @@ This option allows you to specify which HTML elements and attributes are allowed /* * Implements hook_form_FORM_ID_alter - * + * * add validate and submit handlers */ function wysiwyg_filter_form_filter_admin_format_form_alter(&$form, &$form_state, $form_id) { @@ -215,7 +222,9 @@ function _wysiwyg_filter_clear_messages() { $messages = drupal_get_messages('warning'); if (!empty($messages)) { foreach (wysiwyg_filter_get_advanced_rules() as $rule_info) { - $my_messages[] = $rule_info['required_by_message']; + if (empty($settings["rule_bypass_$rule_key"])) { + $my_messages[] = $rule_info['required_by_message']; + } } foreach ($messages['warning'] as $warning) { if (!in_array($warning, $my_messages)) { @@ -248,22 +257,24 @@ function wysiwyg_filter_filter_wysiwyg_settings_validate($form, &$form_state) { form_set_error('valid_elements', t('The following elements cannot be allowed: %elements.', array('%elements' => implode(', ', $forbidden_elements)))); } - // *** validate nofollow_domains *** + // *** validate advanced rules *** foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { - $field_name = "rule_$rule_key"; - $expressions = array_filter(explode(',', preg_replace('#\s+#', ',', trim($values[$field_name])))); // form2db - $errors = array(); - foreach ($expressions as $expression) { - if (preg_match('`[*?]\*|\*\?`', $expression)) { - $errors[] = t('Invalid expression %expression. Please, do not use more than one consecutive asterisk (**) or one that is next to a question mark wildcard (?* or *?).', array('%expression' => $expression)); + if (empty($settings["rule_bypass_$rule_key"])) { + $field_name = "rule_$rule_key"; + $expressions = array_filter(explode(',', preg_replace('#\s+#', ',', trim($values[$field_name])))); // form2db + $errors = array(); + foreach ($expressions as $expression) { + if (preg_match('`[*?]\*|\*\?`', $expression)) { + $errors[] = t('Invalid expression %expression. Please, do not use more than one consecutive asterisk (**) or one that is next to a question mark wildcard (?* or *?).', array('%expression' => $expression)); + } + if (!preg_match($rule_info['validate_regexp'], $expression)) { + $errors[] = t('Invalid expression %expression. Please, check the syntax of the %field field.', array('%expression' => $expression, '%field' => $rule_info['title'])); + } } - if (!preg_match($rule_info['validate_regexp'], $expression)) { - $errors[] = t('Invalid expression %expression. Please, check the syntax of the %field field.', array('%expression' => $expression, '%field' => $rule_info['title'])); + if (!empty($errors)) { + form_set_error($field_name, implode('
', $errors)); } } - if (!empty($errors)) { - form_set_error($field_name, implode('
', $errors)); - } } // *** validate nofollow_domains *** @@ -285,10 +296,10 @@ function wysiwyg_filter_filter_wysiwyg_settings_validate($form, &$form_state) { */ function wysiwyg_filter_filter_wysiwyg_settings_submit($form, &$form_state) { $values =& $form_state['values']['filters']['wysiwyg']['settings']; - + // *** prepare valid_elements - just trim *** $values['valid_elements'] = trim($values['valid_elements']); - + // *** prepare rules - csv2array *** foreach (array_keys(wysiwyg_filter_get_advanced_rules()) as $rule_key) { $field_name = "rule_$rule_key"; @@ -301,7 +312,7 @@ function wysiwyg_filter_filter_wysiwyg_settings_submit($form, &$form_state) { /* * CSV to Array - * + * * @param atring $v * @param bool $space2comma - shall we convet whitespace to commas before processing? * @return array @@ -312,7 +323,7 @@ function wysiwyg_filter_csv2array($v, $space2comma = TRUE) { } /* * Array to CSV - * + * * @param array $v * @return string */ diff --git a/wysiwyg_filter.inc b/wysiwyg_filter.inc index 18f68c9..f10ea81 100644 --- a/wysiwyg_filter.inc +++ b/wysiwyg_filter.inc @@ -493,9 +493,12 @@ function wysiwyg_filter_get_filter_options($format_name, $settings) { 'nofollow_domains' => $settings['nofollow_domains'], ); foreach (wysiwyg_filter_get_advanced_rules() as $rule_key => $rule_info) { - $filter_options[$rule_key] = array(); - foreach ($settings["rule_$rule_key"] as $rule) { - $filter_options[$rule_key][] = '`^' . str_replace("\xFF", $rule_info['asterisk_expansion'], preg_quote(str_replace('*', "\xFF", $rule), '`')) . '$`'; + $filter_options["rule_bypass_$rule_key"] = !empty($settings["rule_bypass_$rule_key"]); + if (!$filter_options["rule_bypass_$rule_key"]) { + $filter_options[$rule_key] = array(); + foreach ($settings["rule_$rule_key"] as $rule) { + $filter_options[$rule_key][] = '`^' . str_replace("\xFF", $rule_info['asterisk_expansion'], preg_quote(str_replace('*', "\xFF", $rule), '`')) . '$`'; + } } } return $filter_options; diff --git a/wysiwyg_filter.module b/wysiwyg_filter.module index 5eca4ea..1473dd4 100644 --- a/wysiwyg_filter.module +++ b/wysiwyg_filter.module @@ -21,7 +21,7 @@ function wysiwyg_filter_filter_info() { $filters = array(); global $base_url; $parts = parse_url($base_url); - + $defaults = array( 'valid_elements' => wysiwyg_filter_default_valid_elements(), 'allow_comments' => 0, @@ -33,13 +33,14 @@ function wysiwyg_filter_filter_info() { endforeach; foreach(wysiwyg_filter_get_advanced_rules() as $rule => $stuff): $defaults["rule_$rule"] = array(); + $defaults["rule_bypass_$rule"] = 0; endforeach; - + $filters['wysiwyg'] = array( - 'title' => t('WYSIWYG Filter'), - 'description' => t('Allows you to restrict whether users can post HTML and which tags and attributes per HTML tag to filter out.'), - 'process callback' => 'wysiwyg_filter_filter_wysiwyg_process', - 'settings callback' => 'wysiwyg_filter_filter_wysiwyg_settings', + 'title' => t('WYSIWYG Filter'), + 'description' => t('Allows you to restrict whether users can post HTML and which tags and attributes per HTML tag to filter out.'), + 'process callback' => 'wysiwyg_filter_filter_wysiwyg_process', + 'settings callback' => 'wysiwyg_filter_filter_wysiwyg_settings', 'tips callback' => 'wysiwyg_filter_filter_wysiwyg_tips', 'default settings' => $defaults ); @@ -78,7 +79,7 @@ function wysiwyg_filter_wysiwyg_editor_settings_alter(&$editor_settings, $contex // Filter is enabled in the input format related to the current given context. if ($context['profile']->editor == 'tinymce'): // first get the filters and their settings - if (isset($context['profile']->format)): + if (isset($context['profile']->format)): $format_name = $context['profile']->format; $filters = filter_list_format($format_name); if($filters && array_key_exists('wysiwyg', $filters)): diff --git a/wysiwyg_filter.pages.inc b/wysiwyg_filter.pages.inc index 40b5e3e..a61b47f 100644 --- a/wysiwyg_filter.pages.inc +++ b/wysiwyg_filter.pages.inc @@ -152,13 +152,27 @@ function _wysiwyg_filter_xss_attributes($attr, $element = '') { $filter_options = $attr; return; } - // Shortcuts for filter options. $allowed_attributes = &$filter_options['valid_elements'][$element]; $allowed_properties = &$filter_options['style_properties']; - $allowed_style_urls = &$filter_options['style_urls']; - $allowed_class_names = &$filter_options['valid_classes']; - $allowed_element_ids = &$filter_options['valid_ids']; + if ($filter_options['rule_bypass_style_urls']) { + $allowed_style_urls = array(); + } + else { + $allowed_style_urls = &$filter_options['style_urls']; + } + $bypass_valid_classes = $filter_options['rule_bypass_valid_classes']; + if (!$bypass_valid_classes) { + $allowed_class_names = &$filter_options['valid_classes']; + } + $bypass_valid_ids = $filter_options['rule_bypass_valid_ids']; + if ($bypass_valid_ids) { + $allowed_element_ids = array('/.*/'); + } + else { + $allowed_element_ids = &$filter_options['valid_ids']; + } + $nofollow_policy = &$filter_options['nofollow_policy']; $nofollow_domains = &$filter_options['nofollow_domains']; @@ -391,13 +405,19 @@ function _wysiwyg_filter_xss_attributes($attr, $element = '') { // sign is not allowed, there's no need here to check for bad protocols. $dirty_names = array_filter(array_map('trim', explode(' ', decode_entities($attrinfo['value'])))); $valid_names = array(); - foreach ($dirty_names as $dirty_name) { - foreach ($allowed_class_names as $regexp) { - if (preg_match($regexp, $dirty_name)) { - $valid_names[] = $dirty_name; + if ($bypass_valid_classes) { + $valid_names = $dirty_names; + } + else { + foreach ($dirty_names as $dirty_name) { + foreach ($allowed_class_names as $regexp) { + if (preg_match($regexp, $dirty_name)) { + $valid_names[] = $dirty_name; + } } } } + if (empty($valid_names)) { // Ignore attribute if no class name remains after validation. continue; @@ -426,6 +446,7 @@ function _wysiwyg_filter_xss_attributes($attr, $element = '') { // Ignore attribute if it contains invalid value. continue; } + // Element ID is valid, check_plain result. $attrinfo['value'] = check_plain($attrinfo['value']); }