diff options
Diffstat (limited to 'lib/phpmailer/extras')
-rw-r--r-- | lib/phpmailer/extras/EasyPeasyICS.php | 148 | ||||
-rw-r--r-- | lib/phpmailer/extras/README.md | 17 | ||||
-rw-r--r-- | lib/phpmailer/extras/htmlfilter.php | 1166 | ||||
-rw-r--r-- | lib/phpmailer/extras/ntlm_sasl_client.php | 185 |
4 files changed, 1516 insertions, 0 deletions
diff --git a/lib/phpmailer/extras/EasyPeasyICS.php b/lib/phpmailer/extras/EasyPeasyICS.php new file mode 100644 index 0000000..d8bfcfa --- /dev/null +++ b/lib/phpmailer/extras/EasyPeasyICS.php @@ -0,0 +1,148 @@ +<?php +/** + * EasyPeasyICS Simple ICS/vCal data generator. + * @author Marcus Bointon <phpmailer@synchromedia.co.uk> + * @author Manuel Reinhard <manu@sprain.ch> + * + * Built with inspiration from + * http://stackoverflow.com/questions/1463480/how-can-i-use-php-to-dynamically-publish-an-ical-file-to-be-read-by-google-calend/1464355#1464355 + * History: + * 2010/12/17 - Manuel Reinhard - when it all started + * 2014 PHPMailer project becomes maintainer + */ + +/** + * Class EasyPeasyICS. + * Simple ICS data generator + * @package phpmailer + * @subpackage easypeasyics + */ +class EasyPeasyICS +{ + /** + * The name of the calendar + * @var string + */ + protected $calendarName; + /** + * The array of events to add to this calendar + * @var array + */ + protected $events = array(); + + /** + * Constructor + * @param string $calendarName + */ + public function __construct($calendarName = "") + { + $this->calendarName = $calendarName; + } + + /** + * Add an event to this calendar. + * @param string $start The start date and time as a unix timestamp + * @param string $end The end date and time as a unix timestamp + * @param string $summary A summary or title for the event + * @param string $description A description of the event + * @param string $url A URL for the event + * @param string $uid A unique identifier for the event - generated automatically if not provided + * @return array An array of event details, including any generated UID + */ + public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '') + { + if (empty($uid)) { + $uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS'; + } + $event = array( + 'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z', + 'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z', + 'summary' => $summary, + 'description' => $description, + 'url' => $url, + 'uid' => $uid + ); + $this->events[] = $event; + return $event; + } + + /** + * @return array Get the array of events. + */ + public function getEvents() + { + return $this->events; + } + + /** + * Clear all events. + */ + public function clearEvents() + { + $this->events = array(); + } + + /** + * Get the name of the calendar. + * @return string + */ + public function getName() + { + return $this->calendarName; + } + + /** + * Set the name of the calendar. + * @param $name + */ + public function setName($name) + { + $this->calendarName = $name; + } + + /** + * Render and optionally output a vcal string. + * @param bool $output Whether to output the calendar data directly (the default). + * @return string The complete rendered vlal + */ + public function render($output = true) + { + //Add header + $ics = 'BEGIN:VCALENDAR +METHOD:PUBLISH +VERSION:2.0 +X-WR-CALNAME:' . $this->calendarName . ' +PRODID:-//hacksw/handcal//NONSGML v1.0//EN'; + + //Add events + foreach ($this->events as $event) { + $ics .= ' +BEGIN:VEVENT +UID:' . $event['uid'] . ' +DTSTAMP:' . gmdate('Ymd') . 'T' . gmdate('His') . 'Z +DTSTART:' . $event['start'] . ' +DTEND:' . $event['end'] . ' +SUMMARY:' . str_replace("\n", "\\n", $event['summary']) . ' +DESCRIPTION:' . str_replace("\n", "\\n", $event['description']) . ' +URL;VALUE=URI:' . $event['url'] . ' +END:VEVENT'; + } + + //Add footer + $ics .= ' +END:VCALENDAR'; + + if ($output) { + //Output + $filename = $this->calendarName; + //Filename needs quoting if it contains spaces + if (strpos($filename, ' ') !== false) { + $filename = '"'.$filename.'"'; + } + header('Content-type: text/calendar; charset=utf-8'); + header('Content-Disposition: inline; filename=' . $filename . '.ics'); + echo $ics; + } + return $ics; + } +} diff --git a/lib/phpmailer/extras/README.md b/lib/phpmailer/extras/README.md new file mode 100644 index 0000000..dac79e0 --- /dev/null +++ b/lib/phpmailer/extras/README.md @@ -0,0 +1,17 @@ +#PHPMailer Extras + +These classes provide optional additional functions to PHPMailer. + +These are not loaded by the PHPMailer autoloader, so in some cases you may need to `require` them yourself before using them. + +##EasyPeasyICS + +This class was originally written by Manuel Reinhard and provides a simple means of generating ICS/vCal files that are used in sending calendar events. PHPMailer does not use it directly, but you can use it to generate content appropriate for placing in the `Ical` property of PHPMailer. The PHPMailer project is now its official home as Manuel has given permission for that and is no longer maintaining it himself. + +##htmlfilter + +This class by Konstantin Riabitsev and Jim Jagielski implements HTML filtering to remove potentially malicious tags, such as `<script>` or `onclick=` attributes that can result in XSS attacks. This is a simple filter and is not as comprehensive as [HTMLawed](http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/) or [HTMLPurifier](http://htmlpurifier.org), but it's easier to use and considerably better than nothing! PHPMailer does not use it directly, but you may want to apply it to user-supplied HTML before using it as a message body. + +##NTLM_SASL_client + +This class by Manuel Lemos (bundled with permission) adds the ability to authenticate with Microsoft Windows mail servers that use NTLM-based authentication. It is used by PHPMailer if you send via SMTP and set the `AuthType` property to `NTLM`; you will also need to use the `Realm` and `Workstation` properties. The original source is [here](http://www.phpclasses.org/browse/file/7495.html). diff --git a/lib/phpmailer/extras/htmlfilter.php b/lib/phpmailer/extras/htmlfilter.php new file mode 100644 index 0000000..5ac7d13 --- /dev/null +++ b/lib/phpmailer/extras/htmlfilter.php @@ -0,0 +1,1166 @@ +<?php +/** + * htmlfilter.inc + * --------------- + * This set of functions allows you to filter html in order to remove + * any malicious tags from it. Useful in cases when you need to filter + * user input for any cross-site-scripting attempts. + * + * Copyright (C) 2002-2004 by Duke University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @Author Konstantin Riabitsev <icon@linux.duke.edu> + * @Author Jim Jagielski <jim@jaguNET.com / jimjag@gmail.com> + * @Version 1.1 ($Date$) + */ + +/** + * This function returns the final tag out of the tag name, an array + * of attributes, and the type of the tag. This function is called by + * tln_sanitize internally. + * + * @param string $tagname the name of the tag. + * @param array $attary the array of attributes and their values + * @param integer $tagtype The type of the tag (see in comments). + * @return string A string with the final tag representation. + */ +function tln_tagprint($tagname, $attary, $tagtype) +{ + if ($tagtype == 2) { + $fulltag = '</' . $tagname . '>'; + } else { + $fulltag = '<' . $tagname; + if (is_array($attary) && sizeof($attary)) { + $atts = array(); + while (list($attname, $attvalue) = each($attary)) { + array_push($atts, "$attname=$attvalue"); + } + $fulltag .= ' ' . join(' ', $atts); + } + if ($tagtype == 3) { + $fulltag .= ' /'; + } + $fulltag .= '>'; + } + return $fulltag; +} + +/** + * A small helper function to use with array_walk. Modifies a by-ref + * value and makes it lowercase. + * + * @param string $val a value passed by-ref. + * @return void since it modifies a by-ref value. + */ +function tln_casenormalize(&$val) +{ + $val = strtolower($val); +} + +/** + * This function skips any whitespace from the current position within + * a string and to the next non-whitespace value. + * + * @param string $body the string + * @param integer $offset the offset within the string where we should start + * looking for the next non-whitespace character. + * @return integer the location within the $body where the next + * non-whitespace char is located. + */ +function tln_skipspace($body, $offset) +{ + preg_match('/^(\s*)/s', substr($body, $offset), $matches); + if (sizeof($matches[1])) { + $count = strlen($matches[1]); + $offset += $count; + } + return $offset; +} + +/** + * This function looks for the next character within a string. It's + * really just a glorified "strpos", except it catches the failures + * nicely. + * + * @param string $body The string to look for needle in. + * @param integer $offset Start looking from this position. + * @param string $needle The character/string to look for. + * @return integer location of the next occurrence of the needle, or + * strlen($body) if needle wasn't found. + */ +function tln_findnxstr($body, $offset, $needle) +{ + $pos = strpos($body, $needle, $offset); + if ($pos === false) { + $pos = strlen($body); + } + return $pos; +} + +/** + * This function takes a PCRE-style regexp and tries to match it + * within the string. + * + * @param string $body The string to look for needle in. + * @param integer $offset Start looking from here. + * @param string $reg A PCRE-style regex to match. + * @return array|boolean Returns a false if no matches found, or an array + * with the following members: + * - integer with the location of the match within $body + * - string with whatever content between offset and the match + * - string with whatever it is we matched + */ +function tln_findnxreg($body, $offset, $reg) +{ + $matches = array(); + $retarr = array(); + $preg_rule = '%^(.*?)(' . $reg . ')%s'; + preg_match($preg_rule, substr($body, $offset), $matches); + if (!isset($matches[0]) || !$matches[0]) { + $retarr = false; + } else { + $retarr[0] = $offset + strlen($matches[1]); + $retarr[1] = $matches[1]; + $retarr[2] = $matches[2]; + } + return $retarr; +} + +/** + * This function looks for the next tag. + * + * @param string $body String where to look for the next tag. + * @param integer $offset Start looking from here. + * @return array|boolean false if no more tags exist in the body, or + * an array with the following members: + * - string with the name of the tag + * - array with attributes and their values + * - integer with tag type (1, 2, or 3) + * - integer where the tag starts (starting "<") + * - integer where the tag ends (ending ">") + * first three members will be false, if the tag is invalid. + */ +function tln_getnxtag($body, $offset) +{ + if ($offset > strlen($body)) { + return false; + } + $lt = tln_findnxstr($body, $offset, '<'); + if ($lt == strlen($body)) { + return false; + } + /** + * We are here: + * blah blah <tag attribute="value"> + * \---------^ + */ + $pos = tln_skipspace($body, $lt + 1); + if ($pos >= strlen($body)) { + return array(false, false, false, $lt, strlen($body)); + } + /** + * There are 3 kinds of tags: + * 1. Opening tag, e.g.: + * <a href="blah"> + * 2. Closing tag, e.g.: + * </a> + * 3. XHTML-style content-less tag, e.g.: + * <img src="blah"/> + */ + switch (substr($body, $pos, 1)) { + case '/': + $tagtype = 2; + $pos++; + break; + case '!': + /** + * A comment or an SGML declaration. + */ + if (substr($body, $pos + 1, 2) == '--') { + $gt = strpos($body, '-->', $pos); + if ($gt === false) { + $gt = strlen($body); + } else { + $gt += 2; + } + return array(false, false, false, $lt, $gt); + } else { + $gt = tln_findnxstr($body, $pos, '>'); + return array(false, false, false, $lt, $gt); + } + break; + default: + /** + * Assume tagtype 1 for now. If it's type 3, we'll switch values + * later. + */ + $tagtype = 1; + break; + } + + /** + * Look for next [\W-_], which will indicate the end of the tag name. + */ + $regary = tln_findnxreg($body, $pos, '[^\w\-_]'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $tagname, $match) = $regary; + $tagname = strtolower($tagname); + + /** + * $match can be either of these: + * '>' indicating the end of the tag entirely. + * '\s' indicating the end of the tag name. + * '/' indicating that this is type-3 xhtml tag. + * + * Whatever else we find there indicates an invalid tag. + */ + switch ($match) { + case '/': + /** + * This is an xhtml-style tag with a closing / at the + * end, like so: <img src="blah"/>. Check if it's followed + * by the closing bracket. If not, then this tag is invalid + */ + if (substr($body, $pos, 2) == '/>') { + $pos++; + $tagtype = 3; + } else { + $gt = tln_findnxstr($body, $pos, '>'); + $retary = array(false, false, false, $lt, $gt); + return $retary; + } + //intentional fall-through + case '>': + return array($tagname, false, $tagtype, $lt, $pos); + break; + default: + /** + * Check if it's whitespace + */ + if (!preg_match('/\s/', $match)) { + /** + * This is an invalid tag! Look for the next closing ">". + */ + $gt = tln_findnxstr($body, $lt, '>'); + return array(false, false, false, $lt, $gt); + } + break; + } + + /** + * At this point we're here: + * <tagname attribute='blah'> + * \-------^ + * + * At this point we loop in order to find all attributes. + */ + $attary = array(); + + while ($pos <= strlen($body)) { + $pos = tln_skipspace($body, $pos); + if ($pos == strlen($body)) { + /** + * Non-closed tag. + */ + return array(false, false, false, $lt, $pos); + } + /** + * See if we arrived at a ">" or "/>", which means that we reached + * the end of the tag. + */ + $matches = array(); + if (preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches)) { + /** + * Yep. So we did. + */ + $pos += strlen($matches[1]); + if ($matches[2] == '/>') { + $tagtype = 3; + $pos++; + } + return array($tagname, $attary, $tagtype, $lt, $pos); + } + + /** + * There are several types of attributes, with optional + * [:space:] between members. + * Type 1: + * attrname[:space:]=[:space:]'CDATA' + * Type 2: + * attrname[:space:]=[:space:]"CDATA" + * Type 3: + * attr[:space:]=[:space:]CDATA + * Type 4: + * attrname + * + * We leave types 1 and 2 the same, type 3 we check for + * '"' and convert to """ if needed, then wrap in + * double quotes. Type 4 we convert into: + * attrname="yes". + */ + $regary = tln_findnxreg($body, $pos, '[^\w\-_]'); + if ($regary == false) { + /** + * Looks like body ended before the end of tag. + */ + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $attname, $match) = $regary; + $attname = strtolower($attname); + /** + * We arrived at the end of attribute name. Several things possible + * here: + * '>' means the end of the tag and this is attribute type 4 + * '/' if followed by '>' means the same thing as above + * '\s' means a lot of things -- look what it's followed by. + * anything else means the attribute is invalid. + */ + switch ($match) { + case '/': + /** + * This is an xhtml-style tag with a closing / at the + * end, like so: <img src="blah"/>. Check if it's followed + * by the closing bracket. If not, then this tag is invalid + */ + if (substr($body, $pos, 2) == '/>') { + $pos++; + $tagtype = 3; + } else { + $gt = tln_findnxstr($body, $pos, '>'); + $retary = array(false, false, false, $lt, $gt); + return $retary; + } + //intentional fall-through + case '>': + $attary{$attname} = '"yes"'; + return array($tagname, $attary, $tagtype, $lt, $pos); + break; + default: + /** + * Skip whitespace and see what we arrive at. + */ + $pos = tln_skipspace($body, $pos); + $char = substr($body, $pos, 1); + /** + * Two things are valid here: + * '=' means this is attribute type 1 2 or 3. + * \w means this was attribute type 4. + * anything else we ignore and re-loop. End of tag and + * invalid stuff will be caught by our checks at the beginning + * of the loop. + */ + if ($char == '=') { + $pos++; + $pos = tln_skipspace($body, $pos); + /** + * Here are 3 possibilities: + * "'" attribute type 1 + * '"' attribute type 2 + * everything else is the content of tag type 3 + */ + $quot = substr($body, $pos, 1); + if ($quot == '\'') { + $regary = tln_findnxreg($body, $pos + 1, '\''); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $attval, $match) = $regary; + $pos++; + $attary{$attname} = '\'' . $attval . '\''; + } elseif ($quot == '"') { + $regary = tln_findnxreg($body, $pos + 1, '\"'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $attval, $match) = $regary; + $pos++; + $attary{$attname} = '"' . $attval . '"'; + } else { + /** + * These are hateful. Look for \s, or >. + */ + $regary = tln_findnxreg($body, $pos, '[\s>]'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $attval, $match) = $regary; + /** + * If it's ">" it will be caught at the top. + */ + $attval = preg_replace('/\"/s', '"', $attval); + $attary{$attname} = '"' . $attval . '"'; + } + } elseif (preg_match('|[\w/>]|', $char)) { + /** + * That was attribute type 4. + */ + $attary{$attname} = '"yes"'; + } else { + /** + * An illegal character. Find next '>' and return. + */ + $gt = tln_findnxstr($body, $pos, '>'); + return array(false, false, false, $lt, $gt); + } + break; + } + } + /** + * The fact that we got here indicates that the tag end was never + * found. Return invalid tag indication so it gets stripped. + */ + return array(false, false, false, $lt, strlen($body)); +} + +/** + * Translates entities into literal values so they can be checked. + * + * @param string $attvalue the by-ref value to check. + * @param string $regex the regular expression to check against. + * @param boolean $hex whether the entites are hexadecimal. + * @return boolean True or False depending on whether there were matches. + */ +function tln_deent(&$attvalue, $regex, $hex = false) +{ + preg_match_all($regex, $attvalue, $matches); + if (is_array($matches) && sizeof($matches[0]) > 0) { + $repl = array(); + for ($i = 0; $i < sizeof($matches[0]); $i++) { + $numval = $matches[1][$i]; + if ($hex) { + $numval = hexdec($numval); + } + $repl{$matches[0][$i]} = chr($numval); + } + $attvalue = strtr($attvalue, $repl); + return true; + } else { + return false; + } +} + +/** + * This function checks attribute values for entity-encoded values + * and returns them translated into 8-bit strings so we can run + * checks on them. + * + * @param string $attvalue A string to run entity check against. + * @return Void, modifies a reference value. + */ +function tln_defang(&$attvalue) +{ + /** + * Skip this if there aren't ampersands or backslashes. + */ + if (strpos($attvalue, '&') === false + && strpos($attvalue, '\\') === false + ) { + return; + } + do { + $m = false; + $m = $m || tln_deent($attvalue, '/\�*(\d+);*/s'); + $m = $m || tln_deent($attvalue, '/\�*((\d|[a-f])+);*/si', true); + $m = $m || tln_deent($attvalue, '/\\\\(\d+)/s', true); + } while ($m == true); + $attvalue = stripslashes($attvalue); +} + +/** + * Kill any tabs, newlines, or carriage returns. Our friends the + * makers of the browser with 95% market value decided that it'd + * be funny to make "java[tab]script" be just as good as "javascript". + * + * @param string $attvalue The attribute value before extraneous spaces removed. + * @return Void, modifies a reference value. + */ +function tln_unspace(&$attvalue) +{ + if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)) { + $attvalue = str_replace( + array("\t", "\r", "\n", "\0", " "), + array('', '', '', '', ''), + $attvalue + ); + } +} + +/** + * This function runs various checks against the attributes. + * + * @param string $tagname String with the name of the tag. + * @param array $attary Array with all tag attributes. + * @param array $rm_attnames See description for tln_sanitize + * @param array $bad_attvals See description for tln_sanitize + * @param array $add_attr_to_tag See description for tln_sanitize + * @param string $trans_image_path + * @param boolean $block_external_images + * @return Array with modified attributes. + */ +function tln_fixatts( + $tagname, + $attary, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images +) { + while (list($attname, $attvalue) = each($attary)) { + /** + * See if this attribute should be removed. + */ + foreach ($rm_attnames as $matchtag => $matchattrs) { + if (preg_match($matchtag, $tagname)) { + foreach ($matchattrs as $matchattr) { + if (preg_match($matchattr, $attname)) { + unset($attary{$attname}); + continue; + } + } + } + } + /** + * Remove any backslashes, entities, or extraneous whitespace. + */ + $oldattvalue = $attvalue; + tln_defang($attvalue); + if ($attname == 'style' && $attvalue !== $oldattvalue) { + $attvalue = "idiocy"; + $attary{$attname} = $attvalue; + } + tln_unspace($attvalue); + + /** + * Now let's run checks on the attvalues. + * I don't expect anyone to comprehend this. If you do, + * get in touch with me so I can drive to where you live and + * shake your hand personally. :) + */ + foreach ($bad_attvals as $matchtag => $matchattrs) { + if (preg_match($matchtag, $tagname)) { + foreach ($matchattrs as $matchattr => $valary) { + if (preg_match($matchattr, $attname)) { + /** + * There are two arrays in valary. + * First is matches. + * Second one is replacements + */ + list($valmatch, $valrepl) = $valary; + $newvalue = preg_replace($valmatch, $valrepl, $attvalue); + if ($newvalue != $attvalue) { + $attary{$attname} = $newvalue; + $attvalue = $newvalue; + } + } + } + } + } + if ($attname == 'style') { + if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) { + $attary{$attname} = '"disallowed character"'; + } + preg_match_all("/url\s*\((.+)\)/si", $attvalue, $aMatch); + if (count($aMatch)) { + foreach($aMatch[1] as $sMatch) { + $urlvalue = $sMatch; + tln_fixurl($attname, $urlvalue, $trans_image_path, $block_external_images); + $attary{$attname} = str_replace($sMatch, $urlvalue, $attvalue); + } + } + } + } + /** + * See if we need to append any attributes to this tag. + */ + foreach ($add_attr_to_tag as $matchtag => $addattary) { + if (preg_match($matchtag, $tagname)) { + $attary = array_merge($attary, $addattary); + } + } + return $attary; +} + +function tln_fixurl($attname, &$attvalue, $trans_image_path, $block_external_images) +{ + $sQuote = '"'; + $attvalue = trim($attvalue); + if ($attvalue && ($attvalue[0] =='"'|| $attvalue[0] == "'")) { + // remove the double quotes + $sQuote = $attvalue[0]; + $attvalue = trim(substr($attvalue,1,-1)); + } + + /** + * Replace empty src tags with the blank image. src is only used + * for frames, images, and image inputs. Doing a replace should + * not affect them working as should be, however it will stop + * IE from being kicked off when src for img tags are not set + */ + if ($attvalue == '') { + $attvalue = $sQuote . $trans_image_path . $sQuote; + } else { + // first, disallow 8 bit characters and control characters + if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) { + switch ($attname) { + case 'href': + $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote; + break; + default: + $attvalue = $sQuote . $trans_image_path . $sQuote; + break; + } + } else { + $aUrl = parse_url($attvalue); + if (isset($aUrl['scheme'])) { + switch(strtolower($aUrl['scheme'])) { + case 'mailto': + case 'http': + case 'https': + case 'ftp': + if ($attname != 'href') { + if ($block_external_images == true) { + $attvalue = $sQuote . $trans_image_path . $sQuote; + } else { + if (!isset($aUrl['path'])) { + $attvalue = $sQuote . $trans_image_path . $sQuote; + } + } + } else { + $attvalue = $sQuote . $attvalue . $sQuote; + } + break; + case 'outbind': + $attvalue = $sQuote . $attvalue . $sQuote; + break; + case 'cid': + $attvalue = $sQuote . $attvalue . $sQuote; + break; + default: + $attvalue = $sQuote . $trans_image_path . $sQuote; + break; + } + } else { + if (!isset($aUrl['path']) || $aUrl['path'] != $trans_image_path) { + $$attvalue = $sQuote . $trans_image_path . $sQuote; + } + } + } + } +} + +function tln_fixstyle($body, $pos, $trans_image_path, $block_external_images) +{ + $me = 'tln_fixstyle'; + // workaround for </style> in between comments + $iCurrentPos = $pos; + $content = ''; + $sToken = ''; + $bSucces = false; + $bEndTag = false; + for ($i=$pos,$iCount=strlen($body);$i<$iCount;++$i) { + $char = $body{$i}; + switch ($char) { + case '<': + $sToken = $char; + break; + case '/': + if ($sToken == '<') { + $sToken .= $char; + $bEndTag = true; + } else { + $content .= $char; + } + break; + case '>': + if ($bEndTag) { + $sToken .= $char; + if (preg_match('/\<\/\s*style\s*\>/i',$sToken,$aMatch)) { + $newpos = $i + 1; + $bSucces = true; + break 2; + } else { + $content .= $sToken; + } + $bEndTag = false; + } else { + $content .= $char; + } + break; + case '!': + if ($sToken == '<') { + // possible comment + if (isset($body{$i+2}) && substr($body,$i,3) == '!--') { + $i = strpos($body,'-->',$i+3); + if ($i === false) { // no end comment + $i = strlen($body); + } + $sToken = ''; + } + } else { + $content .= $char; + } + break; + default: + if ($bEndTag) { + $sToken .= $char; + } else { + $content .= $char; + } + break; + } + } + if ($bSucces == FALSE){ + return array(FALSE, strlen($body)); + } + + + + /** + * First look for general BODY style declaration, which would be + * like so: + * body {background: blah-blah} + * and change it to .bodyclass so we can just assign it to a <div> + */ + $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content); + + $trans_image_path = $trans_image_path; + + /** + * Fix url('blah') declarations. + */ + // $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si", + // "url(\\1$trans_image_path\\2)", $content); + + // first check for 8bit sequences and disallowed control characters + if (preg_match('/[\16-\37\200-\377]+/',$content)) { + $content = '<!-- style block removed by html filter due to presence of 8bit characters -->'; + return array($content, $newpos); + } + + // remove @import line + $content = preg_replace("/^\s*(@import.*)$/mi","\n<!-- @import rules forbidden -->\n",$content); + + $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content); + preg_match_all("/url\s*\((.+)\)/si",$content,$aMatch); + if (count($aMatch)) { + $aValue = $aReplace = array(); + foreach($aMatch[1] as $sMatch) { + // url value + $urlvalue = $sMatch; + tln_fixurl('style',$urlvalue, $trans_image_path, $block_external_images); + $aValue[] = $sMatch; + $aReplace[] = $urlvalue; + } + $content = str_replace($aValue,$aReplace,$content); + } + + /** + * Remove any backslashes, entities, and extraneous whitespace. + */ + $contentTemp = $content; + tln_defang($contentTemp); + tln_unspace($contentTemp); + + $match = Array('/\/\*.*\*\//', + '/expression/i', + '/behaviou*r/i', + '/binding/i', + '/include-source/i', + '/javascript/i', + '/script/i', + '/position/i'); + $replace = Array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', ''); + $contentNew = preg_replace($match, $replace, $contentTemp); + if ($contentNew !== $contentTemp) { + $content = $contentNew; + } + return array($content, $newpos); +} + +function tln_body2div($attary, $trans_image_path) +{ + $me = 'tln_body2div'; + $divattary = array('class' => "'bodyclass'"); + $text = '#000000'; + $has_bgc_stl = $has_txt_stl = false; + $styledef = ''; + if (is_array($attary) && sizeof($attary) > 0){ + foreach ($attary as $attname=>$attvalue){ + $quotchar = substr($attvalue, 0, 1); + $attvalue = str_replace($quotchar, "", $attvalue); + switch ($attname){ + case 'background': + $styledef .= "background-image: url('$trans_image_path'); "; + break; + case 'bgcolor': + $has_bgc_stl = true; + $styledef .= "background-color: $attvalue; "; + break; + case 'text': + $has_txt_stl = true; + $styledef .= "color: $attvalue; "; + break; + } + } + // Outlook defines a white bgcolor and no text color. This can lead to + // white text on a white bg with certain themes. + if ($has_bgc_stl && !$has_txt_stl) { + $styledef .= "color: $text; "; + } + if (strlen($styledef) > 0){ + $divattary{"style"} = "\"$styledef\""; + } + } + return $divattary; +} + +/** + * + * @param string $body The HTML you wish to filter + * @param array $tag_list see description above + * @param array $rm_tags_with_content see description above + * @param array $self_closing_tags see description above + * @param boolean $force_tag_closing see description above + * @param array $rm_attnames see description above + * @param array $bad_attvals see description above + * @param array $add_attr_to_tag see description above + * @param string $trans_image_path + * @param boolean $block_external_images + + * @return string Sanitized html safe to show on your pages. + */ +function tln_sanitize( + $body, + $tag_list, + $rm_tags_with_content, + $self_closing_tags, + $force_tag_closing, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images +) { + /** + * Normalize rm_tags and rm_tags_with_content. + */ + $rm_tags = array_shift($tag_list); + @array_walk($tag_list, 'tln_casenormalize'); + @array_walk($rm_tags_with_content, 'tln_casenormalize'); + @array_walk($self_closing_tags, 'tln_casenormalize'); + /** + * See if tag_list is of tags to remove or tags to allow. + * false means remove these tags + * true means allow these tags + */ + $curpos = 0; + $open_tags = array(); + $trusted = "<!-- begin tln_sanitized html -->\n"; + $skip_content = false; + /** + * Take care of netscape's stupid javascript entities like + * &{alert('boo')}; + */ + $body = preg_replace('/&(\{.*?\};)/si', '&\\1', $body); + while (($curtag = tln_getnxtag($body, $curpos)) != false) { + list($tagname, $attary, $tagtype, $lt, $gt) = $curtag; + $free_content = substr($body, $curpos, $lt-$curpos); + /** + * Take care of <style> + */ + if ($tagname == "style" && $tagtype == 1){ + list($free_content, $curpos) = + tln_fixstyle($body, $gt+1, $trans_image_path, $block_external_images); + if ($free_content != FALSE){ + if ( !empty($attary) ) { + $attary = tln_fixatts($tagname, + $attary, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images + ); + } + $trusted .= tln_tagprint($tagname, $attary, $tagtype); + $trusted .= $free_content; + $trusted .= tln_tagprint($tagname, false, 2); + } + continue; + } + if ($skip_content == false){ + $trusted .= $free_content; + } + if ($tagname != false) { + if ($tagtype == 2) { + if ($skip_content == $tagname) { + /** + * Got to the end of tag we needed to remove. + */ + $tagname = false; + $skip_content = false; + } else { + if ($skip_content == false) { + if ($tagname == "body") { + $tagname = "div"; + } + if (isset($open_tags{$tagname}) && + $open_tags{$tagname} > 0 + ) { + $open_tags{$tagname}--; + } else { + $tagname = false; + } + } + } + } else { + /** + * $rm_tags_with_content + */ + if ($skip_content == false) { + /** + * See if this is a self-closing type and change + * tagtype appropriately. + */ + if ($tagtype == 1 + && in_array($tagname, $self_closing_tags) + ) { + $tagtype = 3; + } + /** + * See if we should skip this tag and any content + * inside it. + */ + if ($tagtype == 1 + && in_array($tagname, $rm_tags_with_content) + ) { + $skip_content = $tagname; + } else { + if (($rm_tags == false + && in_array($tagname, $tag_list)) || + ($rm_tags == true + && !in_array($tagname, $tag_list)) + ) { + $tagname = false; + } else { + /** + * Convert body into div. + */ + if ($tagname == "body"){ + $tagname = "div"; + $attary = tln_body2div($attary, $trans_image_path); + } + if ($tagtype == 1) { + if (isset($open_tags{$tagname})) { + $open_tags{$tagname}++; + } else { + $open_tags{$tagname} = 1; + } + } + /** + * This is where we run other checks. + */ + if (is_array($attary) && sizeof($attary) > 0) { + $attary = tln_fixatts( + $tagname, + $attary, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images + ); + } + } + } + } + } + if ($tagname != false && $skip_content == false) { + $trusted .= tln_tagprint($tagname, $attary, $tagtype); + } + } + $curpos = $gt + 1; + } + $trusted .= substr($body, $curpos, strlen($body) - $curpos); + if ($force_tag_closing == true) { + foreach ($open_tags as $tagname => $opentimes) { + while ($opentimes > 0) { + $trusted .= '</' . $tagname . '>'; + $opentimes--; + } + } + $trusted .= "\n"; + } + $trusted .= "<!-- end tln_sanitized html -->\n"; + return $trusted; +} + +// +// Use the nifty htmlfilter library +// + + +function HTMLFilter($body, $trans_image_path, $block_external_images = false) +{ + + $tag_list = array( + false, + "object", + "meta", + "html", + "head", + "base", + "link", + "frame", + "iframe", + "plaintext", + "marquee" + ); + + $rm_tags_with_content = array( + "script", + "applet", + "embed", + "title", + "frameset", + "xmp", + "xml" + ); + + $self_closing_tags = array( + "img", + "br", + "hr", + "input", + "outbind" + ); + + $force_tag_closing = true; + + $rm_attnames = array( + "/.*/" => + array( + // "/target/i", + "/^on.*/i", + "/^dynsrc/i", + "/^data.*/i", + "/^lowsrc.*/i" + ) + ); + + $bad_attvals = array( + "/.*/" => + array( + "/^src|background/i" => + array( + array( + '/^([\'"])\s*\S+script\s*:.*([\'"])/si', + '/^([\'"])\s*mocha\s*:*.*([\'"])/si', + '/^([\'"])\s*about\s*:.*([\'"])/si' + ), + array( + "\\1$trans_image_path\\2", + "\\1$trans_image_path\\2", + "\\1$trans_image_path\\2" + ) + ), + "/^href|action/i" => + array( + array( + '/^([\'"])\s*\S+script\s*:.*([\'"])/si', + '/^([\'"])\s*mocha\s*:*.*([\'"])/si', + '/^([\'"])\s*about\s*:.*([\'"])/si' + ), + array( + "\\1#\\1", + "\\1#\\1", + "\\1#\\1" + ) + ), + "/^style/i" => + array( + array( + "/\/\*.*\*\//", + "/expression/i", + "/binding/i", + "/behaviou*r/i", + "/include-source/i", + '/position\s*:/i', + '/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i', + '/url\s*\(\s*([\'"])\s*\S+script\s*:.*([\'"])\s*\)/si', + '/url\s*\(\s*([\'"])\s*mocha\s*:.*([\'"])\s*\)/si', + '/url\s*\(\s*([\'"])\s*about\s*:.*([\'"])\s*\)/si', + '/(.*)\s*:\s*url\s*\(\s*([\'"]*)\s*\S+script\s*:.*([\'"]*)\s*\)/si' + ), + array( + "", + "idiocy", + "idiocy", + "idiocy", + "idiocy", + "idiocy", + "url", + "url(\\1#\\1)", + "url(\\1#\\1)", + "url(\\1#\\1)", + "\\1:url(\\2#\\3)" + ) + ) + ) + ); + + if ($block_external_images) { + array_push( + $bad_attvals{'/.*/'}{'/^src|background/i'}[0], + '/^([\'\"])\s*https*:.*([\'\"])/si' + ); + array_push( + $bad_attvals{'/.*/'}{'/^src|background/i'}[1], + "\\1$trans_image_path\\1" + ); + array_push( + $bad_attvals{'/.*/'}{'/^style/i'}[0], + '/url\(([\'\"])\s*https*:.*([\'\"])\)/si' + ); + array_push( + $bad_attvals{'/.*/'}{'/^style/i'}[1], + "url(\\1$trans_image_path\\1)" + ); + } + + $add_attr_to_tag = array( + "/^a$/i" => + array('target' => '"_blank"') + ); + + $trusted = tln_sanitize( + $body, + $tag_list, + $rm_tags_with_content, + $self_closing_tags, + $force_tag_closing, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images + ); + return $trusted; +} diff --git a/lib/phpmailer/extras/ntlm_sasl_client.php b/lib/phpmailer/extras/ntlm_sasl_client.php new file mode 100644 index 0000000..4a08561 --- /dev/null +++ b/lib/phpmailer/extras/ntlm_sasl_client.php @@ -0,0 +1,185 @@ +<?php +/* + * ntlm_sasl_client.php + * + * @(#) $Id: ntlm_sasl_client.php,v 1.3 2004/11/17 08:00:37 mlemos Exp $ + * + */ + +define("SASL_NTLM_STATE_START", 0); +define("SASL_NTLM_STATE_IDENTIFY_DOMAIN", 1); +define("SASL_NTLM_STATE_RESPOND_CHALLENGE", 2); +define("SASL_NTLM_STATE_DONE", 3); +define("SASL_FAIL", -1); +define("SASL_CONTINUE", 1); + +class ntlm_sasl_client_class +{ + public $credentials = array(); + public $state = SASL_NTLM_STATE_START; + + public function initialize(&$client) + { + if (!function_exists($function = "mcrypt_encrypt") + || !function_exists($function = "mhash") + ) { + $extensions = array( + "mcrypt_encrypt" => "mcrypt", + "mhash" => "mhash" + ); + $client->error = "the extension " . $extensions[$function] . + " required by the NTLM SASL client class is not available in this PHP configuration"; + return (0); + } + return (1); + } + + public function ASCIIToUnicode($ascii) + { + for ($unicode = "", $a = 0; $a < strlen($ascii); $a++) { + $unicode .= substr($ascii, $a, 1) . chr(0); + } + return ($unicode); + } + + public function typeMsg1($domain, $workstation) + { + $domain_length = strlen($domain); + $workstation_length = strlen($workstation); + $workstation_offset = 32; + $domain_offset = $workstation_offset + $workstation_length; + return ( + "NTLMSSP\0" . + "\x01\x00\x00\x00" . + "\x07\x32\x00\x00" . + pack("v", $domain_length) . + pack("v", $domain_length) . + pack("V", $domain_offset) . + pack("v", $workstation_length) . + pack("v", $workstation_length) . + pack("V", $workstation_offset) . + $workstation . + $domain + ); + } + + public function NTLMResponse($challenge, $password) + { + $unicode = $this->ASCIIToUnicode($password); + $md4 = mhash(MHASH_MD4, $unicode); + $padded = $md4 . str_repeat(chr(0), 21 - strlen($md4)); + $iv_size = mcrypt_get_iv_size(MCRYPT_DES, MCRYPT_MODE_ECB); + $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); + for ($response = "", $third = 0; $third < 21; $third += 7) { + for ($packed = "", $p = $third; $p < $third + 7; $p++) { + $packed .= str_pad(decbin(ord(substr($padded, $p, 1))), 8, "0", STR_PAD_LEFT); + } + for ($key = "", $p = 0; $p < strlen($packed); $p += 7) { + $s = substr($packed, $p, 7); + $b = $s . ((substr_count($s, "1") % 2) ? "0" : "1"); + $key .= chr(bindec($b)); + } + $ciphertext = mcrypt_encrypt(MCRYPT_DES, $key, $challenge, MCRYPT_MODE_ECB, $iv); + $response .= $ciphertext; + } + return $response; + } + + public function typeMsg3($ntlm_response, $user, $domain, $workstation) + { + $domain_unicode = $this->ASCIIToUnicode($domain); + $domain_length = strlen($domain_unicode); + $domain_offset = 64; + $user_unicode = $this->ASCIIToUnicode($user); + $user_length = strlen($user_unicode); + $user_offset = $domain_offset + $domain_length; + $workstation_unicode = $this->ASCIIToUnicode($workstation); + $workstation_length = strlen($workstation_unicode); + $workstation_offset = $user_offset + $user_length; + $lm = ""; + $lm_length = strlen($lm); + $lm_offset = $workstation_offset + $workstation_length; + $ntlm = $ntlm_response; + $ntlm_length = strlen($ntlm); + $ntlm_offset = $lm_offset + $lm_length; + $session = ""; + $session_length = strlen($session); + $session_offset = $ntlm_offset + $ntlm_length; + return ( + "NTLMSSP\0" . + "\x03\x00\x00\x00" . + pack("v", $lm_length) . + pack("v", $lm_length) . + pack("V", $lm_offset) . + pack("v", $ntlm_length) . + pack("v", $ntlm_length) . + pack("V", $ntlm_offset) . + pack("v", $domain_length) . + pack("v", $domain_length) . + pack("V", $domain_offset) . + pack("v", $user_length) . + pack("v", $user_length) . + pack("V", $user_offset) . + pack("v", $workstation_length) . + pack("v", $workstation_length) . + pack("V", $workstation_offset) . + pack("v", $session_length) . + pack("v", $session_length) . + pack("V", $session_offset) . + "\x01\x02\x00\x00" . + $domain_unicode . + $user_unicode . + $workstation_unicode . + $lm . + $ntlm + ); + } + + public function start(&$client, &$message, &$interactions) + { + if ($this->state != SASL_NTLM_STATE_START) { + $client->error = "NTLM authentication state is not at the start"; + return (SASL_FAIL); + } + $this->credentials = array( + "user" => "", + "password" => "", + "realm" => "", + "workstation" => "" + ); + $defaults = array(); + $status = $client->GetCredentials($this->credentials, $defaults, $interactions); + if ($status == SASL_CONTINUE) { + $this->state = SASL_NTLM_STATE_IDENTIFY_DOMAIN; + } + unset($message); + return ($status); + } + + public function step(&$client, $response, &$message, &$interactions) + { + switch ($this->state) { + case SASL_NTLM_STATE_IDENTIFY_DOMAIN: + $message = $this->TypeMsg1($this->credentials["realm"], $this->credentials["workstation"]); + $this->state = SASL_NTLM_STATE_RESPOND_CHALLENGE; + break; + case SASL_NTLM_STATE_RESPOND_CHALLENGE: + $ntlm_response = $this->NTLMResponse(substr($response, 24, 8), $this->credentials["password"]); + $message = $this->TypeMsg3( + $ntlm_response, + $this->credentials["user"], + $this->credentials["realm"], + $this->credentials["workstation"] + ); + $this->state = SASL_NTLM_STATE_DONE; + break; + case SASL_NTLM_STATE_DONE: + $client->error = "NTLM authentication was finished without success"; + return (SASL_FAIL); + default: + $client->error = "invalid NTLM authentication step state"; + return (SASL_FAIL); + } + return (SASL_CONTINUE); + } +} |