diff --git a/core/includes/token.inc b/core/includes/token.inc
index a6f8064..8c8fe56 100644
--- a/core/includes/token.inc
+++ b/core/includes/token.inc
@@ -62,19 +62,21 @@
  *   - langcode: A language code to be used when generating locale-sensitive
  *     tokens.
  *   - callback: A callback function that will be used to post-process the array
- *     of token replacements after they are generated. For example, a module
- *     using tokens in a text-only email might provide a callback to strip HTML
- *     entities from token values before they are inserted into the final text.
+ *     of token replacements after they are generated. Can be used when modules
+ *     require special formatting of token text, for example URL encoding or
+ *     truncation to a specific length.
  *   - clear: A boolean flag indicating that tokens should be removed from the
  *     final text if no replacement value can be generated.
- *   - sanitize: A boolean flag indicating that tokens should be sanitized for
- *     display to a web browser. Defaults to TRUE. Developers who set this
- *     option to FALSE assume responsibility for running filter_xss(),
- *     check_plain() or other appropriate scrubbing functions before displaying
- *     data to users.
+ *   - html: A boolean flag indicating whether to return filtered HTML or just
+ *     plain-text. Defaults to TRUE, meaning HTML that has been filtered using
+ *     filter_xss_admin() or equivalent. Set this to FALSE, if you need the
+ *     result in a plain-text context, e.g. in a CSV file or a mail subject; the
+ *     result is not safe for output in an HTML context without proper escaping
+ *     using check_plain().
  *
  * @return
- *   Text with tokens replaced.
+ *   An HTML string (or plain-text, if the html option is FALSE) with tokens
+ *   replaced.
  */
 function token_replace($text, array $data = array(), array $options = array()) {
   $text_tokens = token_scan($text);
@@ -160,10 +162,12 @@ function token_scan($text) {
  *     array of token replacements after they are generated. Can be used when
  *     modules require special formatting of token text, for example URL
  *     encoding or truncation to a specific length.
- *   - sanitize: A boolean flag indicating that tokens should be sanitized for
- *     display to a web browser. Developers who set this option to FALSE assume
- *     responsibility for running filter_xss(), check_plain() or other
- *     appropriate scrubbing functions before displaying data to users.
+ *   - html: A boolean flag indicating whether tokens should be returned as
+ *     filtered HTML or plain-text. Defaults to TRUE, meaning HTML that has been
+ *     filtered using filter_xss_admin() or equivalent Set this to FALSE, if you
+ *     need the result in a plain-text context, e.g. in a CSV file or a mail
+ *     subject; the result is not safe for output in an HTML context without
+ *     proper escaping using check_plain().
  *
  * @return
  *   An associative array of replacement values, keyed by the original 'raw'
@@ -174,7 +178,7 @@ function token_scan($text) {
  * @see hook_tokens_alter()
  */
 function token_generate($type, array $tokens, array $data = array(), array $options = array()) {
-  $options += array('sanitize' => TRUE);
+  $options += array('html' => TRUE);
   $replacements = module_invoke_all('tokens', $type, $tokens, $data, $options);
 
   // Allow other modules to alter the replacements.
diff --git a/core/modules/action/action.module b/core/modules/action/action.module
index 6ebf1fb..124b2fc 100644
--- a/core/modules/action/action.module
+++ b/core/modules/action/action.module
@@ -596,7 +596,7 @@ function action_send_email_action($entity, $context) {
     $context['node'] = $entity;
   }
 
-  $recipient = token_replace($context['recipient'], $context);
+  $recipient = token_replace($context['recipient'], $context, array('html' => FALSE));
 
   // If the recipient is a registered user with a language preference, use
   // the recipient's preferred language. Otherwise, use the system default
@@ -709,5 +709,5 @@ function action_goto_action_submit($form, $form_state) {
  * @ingroup actions.
  */
 function action_goto_action($entity, $context) {
-  drupal_goto(token_replace($context['url'], $context));
+  drupal_goto(token_replace($context['url'], $context, array('html' => FALSE)));
 }
diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc
index c77cb67..fdf1b9e 100644
--- a/core/modules/comment/comment.tokens.inc
+++ b/core/modules/comment/comment.tokens.inc
@@ -111,7 +111,6 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = 
   else {
     $langcode = NULL;
   }
-  $sanitize = !empty($options['sanitize']);
 
   $replacements = array();
 
@@ -127,12 +126,12 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = 
 
         // Poster identity information for comments
         case 'hostname':
-          $replacements[$original] = $sanitize ? check_plain($comment->hostname) : $comment->hostname;
+          $replacements[$original] = $options['html'] ? check_plain($comment->hostname) : $comment->hostname;
           break;
 
         case 'name':
           $name = ($comment->uid == 0) ? config('user.settings')->get('anonymous') : $comment->name;
-          $replacements[$original] = $sanitize ? filter_xss($name) : $name;
+          $replacements[$original] = $options['html'] ? check_plain($name) : $name;
           break;
 
         case 'mail':
@@ -143,60 +142,65 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = 
           else {
             $mail = $comment->mail;
           }
-          $replacements[$original] = $sanitize ? check_plain($mail) : $mail;
+          $replacements[$original] = $options['html'] ? check_plain($mail) : $mail;
           break;
 
         case 'homepage':
-          $replacements[$original] = $sanitize ? check_url($comment->homepage) : $comment->homepage;
+          $replacements[$original] = $options['html'] ? check_url($comment->homepage) : $comment->homepage;
           break;
 
         case 'title':
-          $replacements[$original] = $sanitize ? filter_xss($comment->subject) : $comment->subject;
+          $replacements[$original] = $options['html'] ? check_plain($comment->subject) : $comment->subject;
           break;
 
         case 'body':
           if ($items = field_get_items('comment', $comment, 'comment_body', $langcode)) {
             $instance = field_info_instance('comment', 'body', 'comment_body');
             $field_langcode = field_language('comment', $comment, 'comment_body', $langcode);
-            $replacements[$original] = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], 'value') : $items[0]['value'];
+            $body = _text_sanitize($instance, $field_langcode, $items[0], 'value');
+            $replacements[$original] = $options['html'] ? $body : decode_entities(strip_tags($body));
           }
           break;
 
         // Comment related URLs.
         case 'url':
           $url_options['fragment']  = 'comment-' . $comment->cid;
-          $replacements[$original] = url('comment/' . $comment->cid, $url_options);
+          $url = url('comment/' . $comment->cid, $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'edit-url':
           $url_options['fragment'] = NULL;
-          $replacements[$original] = url('comment/' . $comment->cid . '/edit', $url_options);
+          $url = url('comment/' . $comment->cid . '/edit', $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         // Default values for the chained tokens handled below.
         case 'author':
-          $replacements[$original] = $sanitize ? filter_xss($comment->name) : $comment->name;
+          $replacements[$original] = $options['html'] ? check_plain($comment->name) : $comment->name;
           break;
 
         case 'parent':
           if (!empty($comment->pid)) {
             $parent = comment_load($comment->pid);
-            $replacements[$original] = $sanitize ? filter_xss($parent->subject) : $parent->subject;
+            $replacements[$original] = $options['html'] ? check_plain($parent->subject) : $parent->subject;
           }
           break;
 
         case 'created':
-          $replacements[$original] = format_date($comment->created, 'medium', '', NULL, $langcode);
+          $date = format_date($comment->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'changed':
-          $replacements[$original] = format_date($comment->changed, 'medium', '', NULL, $langcode);
+          $date = format_date($comment->changed, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'node':
           $node = node_load($comment->nid);
           $title = $node->label();
-          $replacements[$original] = $sanitize ? filter_xss($title) : $title;
+          $replacements[$original] = $options['html'] ? check_plain($title) : $title;
           break;
       }
     }
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php
index 34674e8..1769524 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php
@@ -44,52 +44,45 @@ function testCommentTokenReplacement() {
     $comment = comment_load($child_comment->id);
     $comment->homepage = 'http://example.org/';
 
-    // Add HTML to ensure that sanitation of some fields tested directly.
+    // Add HTML to test that tokens are escaped.
     $comment->subject = '<blink>Blinking Comment</blink>';
     $instance = field_info_instance('comment', 'body', 'comment_body');
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
     $tests['[comment:cid]'] = $comment->cid;
-    $tests['[comment:hostname]'] = check_plain($comment->hostname);
-    $tests['[comment:name]'] = filter_xss($comment->name);
-    $tests['[comment:mail]'] = check_plain($this->admin_user->mail);
-    $tests['[comment:homepage]'] = check_url($comment->homepage);
-    $tests['[comment:title]'] = filter_xss($comment->subject);
-    $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NOT_SPECIFIED, $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0], 'value');
+    $tests['[comment:hostname]'] = $comment->hostname;
+    $tests['[comment:name]'] = $comment->name;
+    $tests['[comment:mail]'] = $this->admin_user->mail;
+    $tests['[comment:homepage]'] = $comment->homepage;
+    $tests['[comment:title]'] = $comment->subject;
+    $tests['[comment:body]'] = decode_entities(strip_tags(_text_sanitize($instance, LANGUAGE_NOT_SPECIFIED, $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0], 'value')));
     $tests['[comment:url]'] = url('comment/' . $comment->cid, $url_options + array('fragment' => 'comment-' . $comment->cid));
     $tests['[comment:edit-url]'] = url('comment/' . $comment->cid . '/edit', $url_options);
     $tests['[comment:created:since]'] = format_interval(REQUEST_TIME - $comment->created, 2, $language_interface->langcode);
     $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language_interface->langcode);
     $tests['[comment:parent:cid]'] = $comment->pid;
-    $tests['[comment:parent:title]'] = check_plain($parent_comment->subject);
+    $tests['[comment:parent:title]'] = $parent_comment->subject;
     $tests['[comment:node:nid]'] = $comment->nid;
-    $tests['[comment:node:title]'] = check_plain($node->title);
+    $tests['[comment:node:title]'] = $node->title;
     $tests['[comment:author:uid]'] = $comment->uid;
-    $tests['[comment:author:name]'] = check_plain($this->admin_user->name);
+    $tests['[comment:author:name]'] = $this->admin_user->name;
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('comment' => $comment), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized comment token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('comment' => $comment), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text comment token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[comment:hostname]'] = $comment->hostname;
-    $tests['[comment:name]'] = $comment->name;
-    $tests['[comment:mail]'] = $this->admin_user->mail;
-    $tests['[comment:homepage]'] = $comment->homepage;
-    $tests['[comment:title]'] = $comment->subject;
-    $tests['[comment:body]'] = $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0]['value'];
-    $tests['[comment:parent:title]'] = $parent_comment->subject;
-    $tests['[comment:node:title]'] = $node->title;
-    $tests['[comment:author:name]'] = $this->admin_user->name;
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
+    $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NOT_SPECIFIED, $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0], 'value');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('comment' => $comment), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized comment token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('comment' => $comment), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML comment token %token replaced.', array('%token' => $input)));
     }
 
     // Load node so comment_count gets computed.
diff --git a/core/modules/field/modules/link/link.module b/core/modules/field/modules/link/link.module
index 0e204df..d6b118d 100644
--- a/core/modules/field/modules/link/link.module
+++ b/core/modules/field/modules/link/link.module
@@ -349,9 +349,9 @@ function link_field_formatter_view($entity_type, $entity, $field, $instance, $la
 
     // If the title field value is available, use it for the link title.
     if (empty($settings['url_only']) && !empty($item['title'])) {
-      // Unsanitizied token replacement here because $options['html'] is FALSE
-      // by default in theme_link().
-      $link_title = token_replace($item['title'], array($entity_type => $entity), array('sanitize' => FALSE, 'clear' => TRUE));
+      // Plain-text (non-HTML) token replacement here because $options['html']
+      // is FALSE by default in theme_link().
+      $link_title = token_replace($item['title'], array($entity_type => $entity), array('html' => FALSE, 'clear' => TRUE));
     }
 
     // Trim the link title to the desired length.
diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc
index a43849d..2952fe5 100644
--- a/core/modules/file/file.field.inc
+++ b/core/modules/file/file.field.inc
@@ -359,7 +359,7 @@ function file_field_widget_uri($field, $instance, $data = array()) {
   $destination = trim($instance['settings']['file_directory'], '/');
 
   // Replace tokens.
-  $destination = token_replace($destination, $data);
+  $destination = token_replace($destination, $data, array('html' => FALSE));
 
   return $field['settings']['uri_scheme'] . '://' . $destination;
 }
diff --git a/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php b/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
index 4d56b7b..10d9b9d 100644
--- a/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/FileTokenReplaceTest.php
@@ -48,36 +48,33 @@ function testFileTokenReplacement() {
     $node = node_load($nid, TRUE);
     $file = file_load($node->{$field_name}[LANGUAGE_NOT_SPECIFIED][0]['fid']);
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
     $tests['[file:fid]'] = $file->fid;
-    $tests['[file:name]'] = check_plain($file->filename);
-    $tests['[file:path]'] = check_plain($file->uri);
-    $tests['[file:mime]'] = check_plain($file->filemime);
+    $tests['[file:name]'] = $file->filename;
+    $tests['[file:path]'] = $file->uri;
+    $tests['[file:mime]'] = $file->filemime;
     $tests['[file:size]'] = format_size($file->filesize);
-    $tests['[file:url]'] = check_plain(file_create_url($file->uri));
+    $tests['[file:url]'] = file_create_url($file->uri);
     $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language_interface->langcode);
     $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language_interface->langcode);
-    $tests['[file:owner]'] = check_plain(user_format_name($this->admin_user));
+    $tests['[file:owner]'] = user_format_name($this->admin_user);
     $tests['[file:owner:uid]'] = $file->uid;
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('file' => $file), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, t('Sanitized file token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('file' => $file), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, t('Plain-text file token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[file:name]'] = $file->filename;
-    $tests['[file:path]'] = $file->uri;
-    $tests['[file:mime]'] = $file->filemime;
-    $tests['[file:size]'] = format_size($file->filesize);
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('file' => $file), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, t('Unsanitized file token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('file' => $file), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, t('HTML file token %token replaced.', array('%token' => $input)));
     }
   }
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php
index 452f0f4..7b8884b 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php
@@ -43,22 +43,22 @@ function testNodeTokenReplacement() {
     $node = node_load($node->nid);
     $instance = field_info_instance('node', 'body', $node->type);
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
     $tests['[node:nid]'] = $node->nid;
     $tests['[node:vid]'] = $node->vid;
     $tests['[node:tnid]'] = $node->tnid;
     $tests['[node:type]'] = 'article';
     $tests['[node:type-name]'] = 'Article';
-    $tests['[node:title]'] = check_plain($node->title);
-    $tests['[node:body]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value');
-    $tests['[node:summary]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'summary');
-    $tests['[node:langcode]'] = check_plain($node->langcode);
+    $tests['[node:title]'] = $node->title;
+    $tests['[node:body]'] = decode_entities(strip_tags(_text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value')));
+    $tests['[node:summary]'] = decode_entities(strip_tags(_text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'summary')));
+    $tests['[node:langcode]'] = $node->langcode;
     $tests['[node:url]'] = url('node/' . $node->nid, $url_options);
     $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options);
-    $tests['[node:author]'] = check_plain(user_format_name($account));
+    $tests['[node:author]'] = user_format_name($account);
     $tests['[node:author:uid]'] = $node->uid;
-    $tests['[node:author:name]'] = check_plain(user_format_name($account));
+    $tests['[node:author:name]'] = user_format_name($account);
     $tests['[node:created:since]'] = format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode);
     $tests['[node:changed:since]'] = format_interval(REQUEST_TIME - $node->changed, 2, $language_interface->langcode);
 
@@ -66,20 +66,18 @@ function testNodeTokenReplacement() {
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('node' => $node), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('node' => $node), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text node token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[node:title]'] = $node->title;
-    $tests['[node:body]'] = $node->body[$node->langcode][0]['value'];
-    $tests['[node:summary]'] = $node->body[$node->langcode][0]['summary'];
-    $tests['[node:langcode]'] = $node->langcode;
-    $tests['[node:author:name]'] = user_format_name($account);
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
+    $tests['[node:body]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value');
+    $tests['[node:summary]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'summary');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('node' => $node), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('node' => $node), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML node token %token replaced.', array('%token' => $input)));
     }
 
     // Repeat for a node without a summary.
@@ -91,7 +89,7 @@ function testNodeTokenReplacement() {
     $node = node_load($node->nid);
     $instance = field_info_instance('node', 'body', $node->type);
 
-    // Generate and test sanitized token - use full body as expected value.
+    // Generate and test HTML token - use full body as expected value.
     $tests = array();
     $tests['[node:summary]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value');
 
@@ -100,15 +98,15 @@ function testNodeTokenReplacement() {
 
     foreach ($tests as $input => $expected) {
       $output = token_replace($input, array('node' => $node), array('language' => $language_interface));
-      $this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+      $this->assertEqual($output, $expected, format_string('HTML node token %token replaced for node without a summary.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[node:summary]'] = $node->body[$node->langcode][0]['value'];
+    // Generate and test plain-text tokens.
+    $tests['[node:summary]'] = decode_entities(strip_tags(_text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value')));
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('node' => $node), array('language' => $language_interface, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized node token %token replaced for node without a summary.', array('%token' => $input)));
+      $output = token_replace($input, array('node' => $node), array('language' => $language_interface, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text node token %token replaced for node without a summary.', array('%token' => $input)));
     }
   }
 }
diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 2d783c9..fc342e9 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -98,7 +98,6 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
   else {
     $langcode = NULL;
   }
-  $sanitize = !empty($options['sanitize']);
 
   $replacements = array();
 
@@ -121,16 +120,16 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'type':
-          $replacements[$original] = $sanitize ? check_plain($node->type) : $node->type;
+          $replacements[$original] = $options['html'] ? check_plain($node->type) : $node->type;
           break;
 
         case 'type-name':
           $type_name = node_get_type_label($node);
-          $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
+          $replacements[$original] = $options['html'] ? check_plain($type_name) : $type_name;
           break;
 
         case 'title':
-          $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
+          $replacements[$original] = $options['html'] ? check_plain($node->title) : $node->title;
           break;
 
         case 'body':
@@ -141,46 +140,50 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
 
             // If the summary was requested and is not empty, use it.
             if ($name == 'summary' && !empty($items[0]['summary'])) {
-              $output = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], 'summary') : $items[0]['summary'];
+              $output = _text_sanitize($instance, $field_langcode, $items[0], 'summary');
             }
             // Attempt to provide a suitable version of the 'body' field.
             else {
-              $output = $sanitize ? _text_sanitize($instance, $field_langcode, $items[0], 'value') : $items[0]['value'];
+              $output = _text_sanitize($instance, $field_langcode, $items[0], 'value');
               // A summary was requested.
               if ($name == 'summary') {
                 // Generate an optionally trimmed summary of the body field.
                 $output = text_summary($output, $instance['settings']['text_processing'] ? $items[0]['format'] : NULL, $instance['display']['teaser']['settings']['trim_length']);
               }
             }
-            $replacements[$original] = $output;
+            $replacements[$original] = $options['html'] ? $output : decode_entities(strip_tags($output));
           }
           break;
 
         case 'langcode':
-          $replacements[$original] = $sanitize ? check_plain($node->langcode) : $node->langcode;
+          $replacements[$original] = $options['html'] ? check_plain($node->langcode) : $node->langcode;
           break;
 
         case 'url':
-          $replacements[$original] = url('node/' . $node->nid, $url_options);
+          $url = url('node/' . $node->nid, $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'edit-url':
-          $replacements[$original] = url('node/' . $node->nid . '/edit', $url_options);
+          $url = url('node/' . $node->nid . '/edit', $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         // Default values for the chained tokens handled below.
         case 'author':
           $account = user_load($node->uid);
           $name = user_format_name($account);
-          $replacements[$original] = $sanitize ? check_plain($name) : $name;
+          $replacements[$original] = $options['html'] ? check_plain($name) : $name;
           break;
 
         case 'created':
-          $replacements[$original] = format_date($node->created, 'medium', '', NULL, $langcode);
+          $date = format_date($node->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'changed':
-          $replacements[$original] = format_date($node->changed, 'medium', '', NULL, $langcode);
+          $date = format_date($node->changed, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
       }
     }
diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php
index 13f2f79..215e4ab 100644
--- a/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php
+++ b/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php
@@ -66,10 +66,10 @@ function testPollTokenReplacement() {
 
     $poll = node_load($poll_nid, TRUE);
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
     $tests['[node:poll-votes]'] = 4;
-    $tests['[node:poll-winner]'] = filter_xss($poll->choice[1]['chtext']);
+    $tests['[node:poll-winner]'] = $poll->choice[1]['chtext'];
     $tests['[node:poll-winner-votes]'] = 2;
     $tests['[node:poll-winner-percent]'] = 50;
     $tests['[node:poll-duration]'] = format_interval($poll->runtime, 1, $language_interface->langcode);
@@ -78,16 +78,16 @@ function testPollTokenReplacement() {
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('node' => $poll), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized poll token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('node' => $poll), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text poll token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[node:poll-winner]'] = $poll->choice[1]['chtext'];
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('node' => $poll), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized poll token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('node' => $poll), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML poll token %token replaced.', array('%token' => $input)));
     }
   }
 }
diff --git a/core/modules/poll/poll.tokens.inc b/core/modules/poll/poll.tokens.inc
index e0f9b2b..daf7782 100644
--- a/core/modules/poll/poll.tokens.inc
+++ b/core/modules/poll/poll.tokens.inc
@@ -39,7 +39,6 @@ function poll_token_info() {
  * Implements hook_tokens().
  */
 function poll_tokens($type, $tokens, array $data = array(), array $options = array()) {
-  $sanitize = !empty($options['sanitize']);
   if (isset($options['langcode'])) {
     $url_options['language'] = language_load($options['langcode']);
     $langcode = $options['langcode'];
@@ -70,7 +69,7 @@ function poll_tokens($type, $tokens, array $data = array(), array $options = arr
 
         case 'poll-winner':
           if (isset($winner)) {
-            $replacements[$original] = $sanitize ? filter_xss($winner['chtext']) : $winner['chtext'];
+            $replacements[$original] = $options['html'] ? check_plain($winner['chtext']) : $winner['chtext'];
           }
           else {
             $replacements[$original] = '';
@@ -97,7 +96,8 @@ function poll_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'poll-duration':
-          $replacements[$original] = format_interval($node->runtime, 1, $langcode);
+          $interval = format_interval($node->runtime, 1, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($interval) : $interval;
           break;
       }
     }
diff --git a/core/modules/statistics/statistics.tokens.inc b/core/modules/statistics/statistics.tokens.inc
index c2c8fc3..2156fc6 100644
--- a/core/modules/statistics/statistics.tokens.inc
+++ b/core/modules/statistics/statistics.tokens.inc
@@ -49,7 +49,8 @@ function statistics_tokens($type, $tokens, array $data = array(), array $options
       }
       elseif ($name == 'last-view') {
         $statistics = statistics_get($node->nid);
-        $replacements[$original] = format_date($statistics['timestamp']);
+        $date = format_date($statistics['timestamp']);
+        $replacements[$original] = $options['html'] ? check_plain($date) : $date;
       }
     }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php b/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
index 3859041..61e41a5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/TokenReplaceTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\System;
 
 use Drupal\simpletest\WebTestBase;
+use Drupal\Core\Datetime\DrupalDateTime;
 
 /**
  * Test token replacement in strings.
@@ -42,9 +43,9 @@ function testTokenReplacement() {
 
     $target  = check_plain($node->title);
     $target .= check_plain($account->name);
-    $target .= format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode);
+    $target .= check_plain(format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode));
     $target .= check_plain($user->name);
-    $target .= format_date(REQUEST_TIME, 'short', '', NULL, $language_interface->langcode);
+    $target .= check_plain(format_date(REQUEST_TIME, 'short', '', NULL, $language_interface->langcode));
 
     // Test that the clear parameter cleans out non-existent tokens.
     $result = token_replace($source, array('node' => $node), array('langcode' => $language_interface->langcode, 'clear' => TRUE));
@@ -56,16 +57,19 @@ function testTokenReplacement() {
     $result = token_replace($source, array('node' => $node), array('langcode' => $language_interface->langcode));
     $this->assertEqual($target, $result, 'Valid tokens replaced while invalid tokens ignored.');
 
-    // Check that the results of token_generate are sanitized properly. This does NOT
+    // Check that the results of token_generate are escaped properly. This does NOT
     // test the cleanliness of every token -- just that the $sanitize flag is being
     // passed properly through the call stack and being handled correctly by a 'known'
     // token, [node:title].
     $raw_tokens = array('title' => '[node:title]');
     $generated = token_generate('node', $raw_tokens, array('node' => $node));
-    $this->assertEqual($generated['[node:title]'], check_plain($node->title), 'Token sanitized.');
+    $this->assertEqual($generated['[node:title]'], check_plain($node->title), 'HTML token generated properly (using default value).');
 
-    $generated = token_generate('node', $raw_tokens, array('node' => $node), array('sanitize' => FALSE));
-    $this->assertEqual($generated['[node:title]'], $node->title, 'Unsanitized token generated properly.');
+    $generated = token_generate('node', $raw_tokens, array('node' => $node), array('html' => TRUE));
+    $this->assertEqual($generated['[node:title]'], check_plain($node->title), 'HTML token generated properly.');
+
+    $generated = token_generate('node', $raw_tokens, array('node' => $node), array('html' => FALSE));
+    $this->assertEqual($generated['[node:title]'], $node->title, 'Plain-text token generated properly.');
 
     // Test token replacement when the string contains no tokens.
     $this->assertEqual(token_replace('No tokens here.'), 'No tokens here.');
@@ -116,10 +120,10 @@ function testSystemSiteTokenReplacement() {
       ->set('slogan', '<blink>Slogan</blink>')
       ->save();
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
-    $tests['[site:name]'] = check_plain(config('system.site')->get('name'));
-    $tests['[site:slogan]'] = filter_xss_admin(config('system.site')->get('slogan'));
+    $tests['[site:name]'] = config('system.site')->get('name');
+    $tests['[site:slogan]'] = decode_entities(strip_tags(filter_xss_admin(config('system.site')->get('slogan'))));
     $tests['[site:mail]'] = 'simpletest@example.com';
     $tests['[site:url]'] = url('<front>', $url_options);
     $tests['[site:url-brief]'] = preg_replace(array('!^https?://!', '!/$!'), '', url('<front>', $url_options));
@@ -129,17 +133,17 @@ function testSystemSiteTokenReplacement() {
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array(), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized system site information token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array(), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text system site information token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[site:name]'] = config('system.site')->get('name');
-    $tests['[site:slogan]'] = config('system.site')->get('slogan');
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
+    $tests['[site:slogan]'] = filter_xss_admin(config('system.site')->get('slogan'));
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array(), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized system site information token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array(), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML system site information token %token replaced.', array('%token' => $input)));
     }
   }
 
@@ -152,21 +156,35 @@ function testSystemDateTokenReplacement() {
     // Set time to one hour before request.
     $date = REQUEST_TIME - 3600;
 
+    // Add characters that needs escaping in HTML to medium date format.
+    config('system.date')->set('formats.medium.pattern.' . DrupalDateTime::PHP, '<D, m/d/Y - H:i>');
+    config('system.date')->set('formats.medium.pattern.' . DrupalDateTime::INTL, '<ccc, MM/dd/yyyy - kk:mm>');
+
     // Generate and test tokens.
     $tests = array();
     $tests['[date:short]'] = format_date($date, 'short', '', NULL, $language_interface->langcode);
     $tests['[date:medium]'] = format_date($date, 'medium', '', NULL, $language_interface->langcode);
     $tests['[date:long]'] = format_date($date, 'long', '', NULL, $language_interface->langcode);
     $tests['[date:custom:m/j/Y]'] = format_date($date, 'custom', 'm/j/Y', NULL, $language_interface->langcode);
+    $tests['[date:custom:<Y>]'] = '<' . format_date($date, 'custom', 'Y', NULL, $language_interface->langcode) . '>';
     $tests['[date:since]'] = format_interval((REQUEST_TIME - $date), 2, $language_interface->langcode);
-    $tests['[date:raw]'] = filter_xss($date);
+    $tests['[date:raw]'] = $date;
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
+      $output = token_replace($input, array('date' => $date), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text date token %token replaced.', array('%token' => $input)));
+    }
+
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
+
+    foreach ($tests as $input => $expected) {
       $output = token_replace($input, array('date' => $date), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Date token %token replaced.', array('%token' => $input)));
+      $this->assertEqual($output, $expected, format_string('HTML date token %token replaced.', array('%token' => $input)).$output. $expected);
     }
+
   }
 }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 078d9f5..32cb355 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -3425,8 +3425,6 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
   else {
     $langcode = NULL;
   }
-  $sanitize = !empty($options['sanitize']);
-
   $replacements = array();
 
   if ($type == 'node' && !empty($data['node'])) {
@@ -3440,21 +3438,36 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
           break;
 
         case 'title':
-          $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
+          $replacements[$original] = $options['html'] ? check_plain($node->title) : $node->title;
+          break;
+
+        case 'body':
+          if ($items = field_get_items('node', $node, 'body', $langcode)) {
+            $instance = field_info_instance('node', 'body', $node->type);
+            $field_langcode = field_language('node', $node, 'body', $langcode);
+
+            // Get HTML version of the 'body' field with filters applied.
+            $body = _text_sanitize($instance, $field_langcode, $items[0], 'value');
+
+            // If plain-text was requested, convert from HTML.
+            $replacements[$original] = $options['html'] ? $body : decode_entities(strip_tags($body));
+          }
           break;
 
         case 'edit-url':
-          $replacements[$original] = url('node/' . $node->nid . '/edit', $url_options);
+          $url = url('node/' . $node->nid . '/edit', $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         // Default values for the chained tokens handled below.
         case 'author':
-          $name = ($node->uid == 0) ? config('user.settings')->get('anonymous') : $node->name;
-          $replacements[$original] = $sanitize ? filter_xss($name) : $name;
-          break;
+          $account = user_load($node->uid);
+          $name = user_format_name($account);
+          $replacements[$original] = $options['html'] ? check_plain($name) : $name;
 
         case 'created':
-          $replacements[$original] = format_date($node->created, 'medium', '', NULL, $langcode);
+          $date = format_date($node->created, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
       }
     }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 26fee90..885e0c1 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3491,7 +3491,7 @@ function system_cache_flush() {
 function system_mail($key, &$message, $params) {
   $context = $params['context'];
 
-  $subject = token_replace($context['subject'], $context);
+  $subject = token_replace($context['subject'], $context, array('html' => FALSE));
   $body = token_replace($context['message'], $context);
 
   $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc
index a5e7ad2..f0482ab 100644
--- a/core/modules/system/system.tokens.inc
+++ b/core/modules/system/system.tokens.inc
@@ -137,7 +137,6 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
   else {
     $langcode = NULL;
   }
-  $sanitize = !empty($options['sanitize']);
 
   $replacements = array();
 
@@ -146,28 +145,32 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
       switch ($name) {
         case 'name':
           $site_name = config('system.site')->get('name');
-          $replacements[$original] = $sanitize ? check_plain($site_name) : $site_name;
+          $replacements[$original] = $options['html'] ? check_plain($site_name) : $site_name;
           break;
 
         case 'slogan':
           $slogan = config('system.site')->get('slogan');
-          $replacements[$original] = $sanitize ? filter_xss_admin($slogan) : $slogan;
+          $replacements[$original] = $options['html'] ? filter_xss_admin($slogan) : decode_entities(strip_tags($slogan));
           break;
 
         case 'mail':
-          $replacements[$original] = config('system.site')->get('mail');
+          $mail = config('system.site')->get('mail');
+          $replacements[$original] = $options['html'] ? check_plain($mail) : $mail;
           break;
 
         case 'url':
-          $replacements[$original] = url('<front>', $url_options);
+          $url = url('<front>', $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'url-brief':
-          $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', url('<front>', $url_options));
+          $url = preg_replace(array('!^https?://!', '!/$!'), '', url('<front>', $url_options));
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'login-url':
-          $replacements[$original] = url('user', $url_options);
+          $url = url('user', $url_options);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
       }
     }
@@ -175,39 +178,44 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
 
   elseif ($type == 'date') {
     if (empty($data['date'])) {
-      $date = REQUEST_TIME;
+      $timestamp = REQUEST_TIME;
     }
     else {
-      $date = $data['date'];
+      $timestamp = $data['date'];
     }
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'short':
-          $replacements[$original] = format_date($date, 'short', '', NULL, $langcode);
+          $date = format_date($timestamp, 'short', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'medium':
-          $replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
+          $date = format_date($timestamp, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'long':
-          $replacements[$original] = format_date($date, 'long', '', NULL, $langcode);
+          $date = format_date($timestamp, 'long', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'since':
-          $replacements[$original] = format_interval((REQUEST_TIME - $date), 2, $langcode);
+          $interval = format_interval((REQUEST_TIME - $timestamp), 2, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($interval) : $interval;
           break;
 
         case 'raw':
-          $replacements[$original] = $sanitize ? check_plain($date) : $date;
+          $replacements[$original] = $options['html'] ? check_plain($timestamp) : $timestamp;
           break;
       }
     }
 
     if ($created_tokens = token_find_with_prefix($tokens, 'custom')) {
       foreach ($created_tokens as $name => $original) {
-        $replacements[$original] = format_date($date, 'custom', $name, NULL, $langcode);
+        $date = format_date($timestamp, 'custom', $name, NULL, $langcode);
+        $replacements[$original] = $options['html'] ? check_plain($date) : $date;
       }
     }
   }
@@ -224,34 +232,37 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
 
         // Essential file data
         case 'name':
-          $replacements[$original] = $sanitize ? check_plain($file->filename) : $file->filename;
+          $replacements[$original] = $options['html'] ? check_plain($file->filename) : $file->filename;
           break;
 
         case 'path':
-          $replacements[$original] = $sanitize ? check_plain($file->uri) : $file->uri;
+          $replacements[$original] = $options['html'] ? check_plain($file->uri) : $file->uri;
           break;
 
         case 'mime':
-          $replacements[$original] = $sanitize ? check_plain($file->filemime) : $file->filemime;
+          $replacements[$original] = $options['html'] ? check_plain($file->filemime) : $file->filemime;
           break;
 
         case 'size':
-          $replacements[$original] = format_size($file->filesize);
+          $size = format_size($file->filesize);
+          $replacements[$original] = $options['html'] ? check_plain($size) : $size;
           break;
 
         case 'url':
-          $replacements[$original] = $sanitize ? check_plain(file_create_url($file->uri)) : file_create_url($file->uri);
+          $url = file_create_url($file->uri);
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         // These tokens are default variations on the chained tokens handled below.
         case 'timestamp':
-          $replacements[$original] = format_date($file->timestamp, 'medium', '', NULL, $langcode);
+          $date = format_date($file->timestamp, 'medium', '', NULL, $langcode);
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'owner':
           $account = user_load($file->uid);
           $name = user_format_name($account);
-          $replacements[$original] = $sanitize ? check_plain($name) : $name;
+          $replacements[$original] = $options['html'] ? check_plain($name) : $name;
           break;
       }
     }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
index e273c1b..38e633c 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TokenReplaceTest.php
@@ -80,57 +80,55 @@ function testTaxonomyTokenReplacement() {
     $edit[$this->instance['field_name'] . '[' . $this->langcode . '][]'] = $term2->tid;
     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
 
-    // Generate and test sanitized tokens for term1.
+    // Generate and test plain-text tokens for term1.
     $tests = array();
     $tests['[term:tid]'] = $term1->tid;
-    $tests['[term:name]'] = check_plain($term1->name);
-    $tests['[term:description]'] = check_markup($term1->description, $term1->format);
+    $tests['[term:name]'] = $term1->name;
+    $tests['[term:description]'] = decode_entities(strip_tags(check_markup($term1->description, $term1->format)));
     $tests['[term:url]'] = url('taxonomy/term/' . $term1->tid, array('absolute' => TRUE));
     $tests['[term:node-count]'] = 0;
     $tests['[term:parent:name]'] = '[term:parent:name]';
-    $tests['[term:vocabulary:name]'] = check_plain($this->vocabulary->name);
+    $tests['[term:vocabulary:name]'] = $this->vocabulary->name;
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('term' => $term1), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('term' => $term1), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text taxonomy term token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test sanitized tokens for term2.
+    // Generate and test plain-text tokens for term2.
     $tests = array();
     $tests['[term:tid]'] = $term2->tid;
-    $tests['[term:name]'] = check_plain($term2->name);
-    $tests['[term:description]'] = check_markup($term2->description, $term2->format);
+    $tests['[term:name]'] = $term2->name;
+    $tests['[term:description]'] = decode_entities(strip_tags(check_markup($term2->description, $term2->format)));
     $tests['[term:url]'] = url('taxonomy/term/' . $term2->tid, array('absolute' => TRUE));
     $tests['[term:node-count]'] = 1;
-    $tests['[term:parent:name]'] = check_plain($term1->name);
+    $tests['[term:parent:name]'] = $term1->name;
     $tests['[term:parent:url]'] = url('taxonomy/term/' . $term1->tid, array('absolute' => TRUE));
     $tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
-    $tests['[term:vocabulary:name]'] = check_plain($this->vocabulary->name);
+    $tests['[term:vocabulary:name]'] = $this->vocabulary->name;
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('term' => $term2), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('term' => $term2), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text taxonomy term token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[term:name]'] = $term2->name;
-    $tests['[term:description]'] = $term2->description;
-    $tests['[term:parent:name]'] = $term1->name;
-    $tests['[term:vocabulary:name]'] = $this->vocabulary->name;
+    // Generate and test HTML tokens for term2.
+    $tests = array_map('check_plain', $tests);
+    $tests['[term:description]'] = check_markup($term2->description, $term2->format);
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('term' => $term2), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy term token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('term' => $term2), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML taxonomy term token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens for vocabulary.
     $tests = array();
     $tests['[vocabulary:vid]'] = $this->vocabulary->vid;
-    $tests['[vocabulary:name]'] = check_plain($this->vocabulary->name);
-    $tests['[vocabulary:description]'] = filter_xss($this->vocabulary->description);
+    $tests['[vocabulary:name]'] = $this->vocabulary->name;
+    $tests['[vocabulary:description]'] = decode_entities(strip_tags(filter_xss($this->vocabulary->description)));
     $tests['[vocabulary:node-count]'] = 1;
     $tests['[vocabulary:term-count]'] = 2;
 
@@ -138,16 +136,15 @@ function testTaxonomyTokenReplacement() {
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->langcode));
+      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->langcode, 'html' => FALSE));
       $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[vocabulary:name]'] = $this->vocabulary->name;
-    $tests['[vocabulary:description]'] = $this->vocabulary->description;
+    // Generate and test HTML tokens for vocabulary.
+    $tests['[vocabulary:description]'] = filter_xss($this->vocabulary->description);
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
+      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->langcode));
       $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
     }
   }
diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc
index c7847b3..a79d43c 100644
--- a/core/modules/taxonomy/taxonomy.tokens.inc
+++ b/core/modules/taxonomy/taxonomy.tokens.inc
@@ -90,7 +90,6 @@ function taxonomy_token_info() {
  */
 function taxonomy_tokens($type, $tokens, array $data = array(), array $options = array()) {
   $replacements = array();
-  $sanitize = !empty($options['sanitize']);
 
   if ($type == 'term' && !empty($data['term'])) {
     $term = $data['term'];
@@ -102,16 +101,18 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
           break;
 
         case 'name':
-          $replacements[$original] = $sanitize ? check_plain($term->name) : $term->name;
+          $replacements[$original] = $options['html'] ? check_plain($term->name) : $term->name;
           break;
 
         case 'description':
-          $replacements[$original] = $sanitize ? check_markup($term->description, $term->format, '', TRUE) : $term->description;
+          $description = check_markup($term->description, $term->format, '', TRUE);
+          $replacements[$original] = $options['html'] ? $description : decode_entities(strip_tags($description)) ;
           break;
 
         case 'url':
           $uri = $term->uri();
-          $replacements[$original] = url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE)));
+          $url = url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE)));
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'node-count':
@@ -124,13 +125,13 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
 
         case 'vocabulary':
           $vocabulary = taxonomy_vocabulary_load($term->vid);
-          $replacements[$original] = check_plain($vocabulary->name);
+          $replacements[$original] = $options['html'] ? check_plain($vocabulary->name) : $vocabulary->name;
           break;
 
         case 'parent':
           if ($parents = taxonomy_term_load_parents($term->tid)) {
             $parent = array_pop($parents);
-            $replacements[$original] = check_plain($parent->name);
+            $replacements[$original] = $options['html'] ? check_plain($parent->name) : $parent->name;
           }
           break;
       }
@@ -157,11 +158,11 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
           break;
 
         case 'name':
-          $replacements[$original] = $sanitize ? check_plain($vocabulary->name) : $vocabulary->name;
+          $replacements[$original] = $options['html'] ? check_plain($vocabulary->name) : $vocabulary->name;
           break;
 
         case 'description':
-          $replacements[$original] = $sanitize ? filter_xss($vocabulary->description) : $vocabulary->description;
+          $replacements[$original] = $options['html'] ? filter_xss($vocabulary->description) : decode_entities(strip_tags($vocabulary->description));
           break;
 
         case 'term-count':
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
index d14e826..bfcbbc7 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php
@@ -58,35 +58,33 @@ function testUserTokenReplacement() {
     $account = user_load($user1->uid);
     $global_account = user_load($GLOBALS['user']->uid);
 
-    // Generate and test sanitized tokens.
+    // Generate and test plain-text tokens.
     $tests = array();
     $tests['[user:uid]'] = $account->uid;
-    $tests['[user:name]'] = check_plain(user_format_name($account));
-    $tests['[user:mail]'] = check_plain($account->mail);
+    $tests['[user:name]'] = user_format_name($account);
+    $tests['[user:mail]'] = $account->mail;
     $tests['[user:url]'] = url("user/$account->uid", $url_options);
     $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options);
     $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language_interface->langcode);
     $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language_interface->langcode);
     $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language_interface->langcode);
     $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language_interface->langcode);
-    $tests['[current-user:name]'] = check_plain(user_format_name($global_account));
+    $tests['[current-user:name]'] = user_format_name($global_account);
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode));
-      $this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode, 'html' => FALSE));
+      $this->assertEqual($output, $expected, format_string('Plain-text user token %token replaced.', array('%token' => $input)));
     }
 
-    // Generate and test unsanitized tokens.
-    $tests['[user:name]'] = user_format_name($account);
-    $tests['[user:mail]'] = $account->mail;
-    $tests['[current-user:name]'] = user_format_name($global_account);
+    // Generate and test HTML tokens.
+    $tests = array_map('check_plain', $tests);
 
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode, 'sanitize' => FALSE));
-      $this->assertEqual($output, $expected, format_string('Unsanitized user token %token replaced.', array('%token' => $input)));
+      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode));
+      $this->assertEqual($output, $expected, format_string('HTML user token %token replaced.', array('%token' => $input)));
     }
 
     // Generate login and cancel link.
@@ -97,7 +95,7 @@ function testUserTokenReplacement() {
     // Generate tokens with interface language.
     $link = url('user', array('absolute' => TRUE));
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+      $output = token_replace($input, array('user' => $account), array('langcode' => $language_interface->langcode, 'callback' => 'user_mail_tokens', 'html' => FALSE, 'clear' => TRUE));
       $this->assertTrue(strpos($output, $link) === 0, 'Generated URL is in interface language.');
     }
 
@@ -106,7 +104,7 @@ function testUserTokenReplacement() {
     $account->save();
     $link = url('user', array('language' => language_load($account->preferred_langcode), 'absolute' => TRUE));
     foreach ($tests as $input => $expected) {
-      $output = token_replace($input, array('user' => $account), array('callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+      $output = token_replace($input, array('user' => $account), array('callback' => 'user_mail_tokens', 'html' => FALSE, 'clear' => TRUE));
       $this->assertTrue(strpos($output, $link) === 0, "Generated URL is in the user's preferred language.");
     }
 
@@ -114,7 +112,7 @@ function testUserTokenReplacement() {
     $link = url('user', array('language' => language_load('de'), 'absolute' => TRUE));
     foreach ($tests as $input => $expected) {
       foreach (array($user1, $user2) as $account) {
-        $output = token_replace($input, array('user' => $account), array('langcode' => 'de', 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+        $output = token_replace($input, array('user' => $account), array('langcode' => 'de', 'callback' => 'user_mail_tokens', 'html' => FALSE, 'clear' => TRUE));
         $this->assertTrue(strpos($output, $link) === 0, "Generated URL in in the requested language.");
       }
     }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 7dabb6d..11eceb0 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1989,8 +1989,8 @@ function user_view_multiple($accounts, $view_mode = 'full', $langcode = NULL) {
 function user_mail($key, &$message, $params) {
   $langcode = $message['langcode'];
   $variables = array('user' => $params['account']);
-  $message['subject'] .= _user_mail_text($key . '.subject', $langcode, $variables);
-  $message['body'][] = _user_mail_text($key . '.body', $langcode, $variables);
+  $message['subject'] .= _user_mail_text($key . '.subject', $langcode, $variables, FALSE);
+  $message['body'][] = _user_mail_text($key . '.body', $langcode, $variables, TRUE);
 }
 
 /**
@@ -2006,10 +2006,10 @@ function user_mail($key, &$message, $params) {
  * @return
  *   A string value containing the text for the user.mail config key.
  */
-function _user_mail_text($key, $langcode = NULL, $variables = array()) {
+function _user_mail_text($key, $langcode = NULL, $variables = array(), $html = TRUE) {
   // We do not sanitize the token replacement, since the output of this
   // replacement is intended for an e-mail message, not a web browser.
-  return token_replace(config('user.mail')->get($key), $variables, array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+  return token_replace(config('user.mail')->get($key), $variables, array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'html' => $html, 'clear' => TRUE));
 }
 
 /**
@@ -2033,8 +2033,11 @@ function _user_mail_text($key, $langcode = NULL, $variables = array()) {
  */
 function user_mail_tokens(&$replacements, $data, $options) {
   if (isset($data['user'])) {
-    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
-    $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
+    $url = user_pass_reset_url($data['user'], $options);
+    $replacements['[user:one-time-login-url]'] = $options['html'] ? check_plain($url) : $url;
+
+    $url = user_cancel_url($data['user'], $options);
+    $replacements['[user:cancel-url]'] = $options['html'] ? check_plain($url) : $url;
   }
 }
 
diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc
index bc37434..d8a8b7c 100644
--- a/core/modules/user/user.tokens.inc
+++ b/core/modules/user/user.tokens.inc
@@ -70,7 +70,6 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
   else {
     $langcode = NULL;
   }
-  $sanitize = !empty($options['sanitize']);
 
   $replacements = array();
 
@@ -86,29 +85,33 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
 
         case 'name':
           $name = user_format_name($account);
-          $replacements[$original] = $sanitize ? check_plain($name) : $name;
+          $replacements[$original] = $options['html'] ? check_plain($name) : $name;
           break;
 
         case 'mail':
-          $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail;
+          $replacements[$original] = $options['html'] ? check_plain($account->mail) : $account->mail;
           break;
 
         case 'url':
-          $replacements[$original] = !empty($account->uid) ? url("user/$account->uid", $url_options) : t('not yet assigned');
+          $url = !empty($account->uid) ? url("user/$account->uid", $url_options) : t('not yet assigned');
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         case 'edit-url':
-          $replacements[$original] = !empty($account->uid) ? url("user/$account->uid/edit", $url_options) : t('not yet assigned');
+          $url = !empty($account->uid) ? url("user/$account->uid/edit", $url_options) : t('not yet assigned');
+          $replacements[$original] = $options['html'] ? check_plain($url) : $url;
           break;
 
         // These tokens are default variations on the chained tokens handled below.
         case 'last-login':
-          $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $langcode) : t('never');
+          $date = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $langcode) : t('never');
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
 
         case 'created':
           // In the case of user_presave the created date may not yet be set.
-          $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $langcode) : t('not yet created');
+          $date = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $langcode) : t('not yet created');
+          $replacements[$original] = $options['html'] ? check_plain($date) : $date;
           break;
       }
     }
