├── README.md ├── docs ├── email_builder.md ├── mime_parser.md ├── pop3.md └── smtp.md ├── support ├── Net │ ├── DNS2.php │ └── license.txt ├── cacert.pem ├── email_builder.php ├── ipaddr.php ├── mime_parser.php ├── pop3.php ├── smtp.php ├── tag_filter.php ├── utf8.php └── utf_utils.php └── test_suite ├── run.php ├── test_1.txt ├── test_2.txt ├── test_3.txt ├── test_4.txt ├── test_5.txt ├── test_6.txt └── test_newsletter_header.png /README.md: -------------------------------------------------------------------------------- 1 | Ultimate E-mail Toolkit 2 | ======================= 3 | 4 | A PHP library of functions designed to handle all of your one-off e-mail needs under a MIT or LGPL license. Instead of relying solely on the mostly broken PHP mail() function, this library directly talks to SMTP and POP3 servers just as a regular e-mail client would. The result is a high level of reliability in delivery of e-mail messages to recipients. Functions like `ConvertHTMLToText()` and `MakeValidEmailAddress()` make it easy to do complex tasks such as convert ugly HTML input into beautiful plain-text output and analyze an e-mail address to automatically correct common typing mistakes. All of that while following the various RFCs surrounding e-mail. 5 | 6 | [![Donate](https://cubiclesoft.com/res/donate-shield.png)](https://cubiclesoft.com/donate/) [![Discord](https://img.shields.io/discord/777282089980526602?label=chat&logo=discord)](https://cubiclesoft.com/product-support/github/) 7 | 8 | Features 9 | -------- 10 | 11 | * Carefully follows the many IETF RFC Standards surrounding e-mail (RFC822, RFC2822, RFC1341, RFC1342, RFC1081, RFC1939, RFC2045, etc). 12 | * Relatively complete and comprehensive, yet easy-to-use SMTP, POP3, and MIME libraries. Fully MIME and Unicode-aware. 13 | * Easy to emulate various e-mail client headers. 14 | * Rapidly build [great-looking, fully responsive HTML e-mails](https://github.com/cubiclesoft/ultimate-email/blob/master/docs/email_builder.md) with the included `EmailBuilder` class. 15 | * `SMTP::ConvertHTMLToText()` to convert ugly HTML into really nice-looking plain text suitable for multipart e-mails. 16 | * `SMTP::MakeValidEmailAddress()` to correctly parse e-mail addresses and automatically correct common typing mistakes. 17 | * Has a liberal open source license. MIT or LGPL, your choice. 18 | * Designed for relatively painless integration into your project. 19 | * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. 20 | 21 | Usage 22 | ----- 23 | 24 | Documentation and examples can be found in the 'docs' directory of this repository. 25 | 26 | * [EmailBuilder class](https://github.com/cubiclesoft/ultimate-email/blob/master/docs/email_builder.md) - Effortlessly design beautiful HTML e-mails. 27 | * [SMTP class](https://github.com/cubiclesoft/ultimate-email/blob/master/docs/smtp.md) - Send e-mail. 28 | * [POP3 class](https://github.com/cubiclesoft/ultimate-email/blob/master/docs/pop3.md) - Retrieve e-mail. 29 | * [MIMEParser class](https://github.com/cubiclesoft/ultimate-email/blob/master/docs/mime_parser.md) - Extract content from retrieved e-mail. 30 | -------------------------------------------------------------------------------- /docs/email_builder.md: -------------------------------------------------------------------------------- 1 | EmailBuilder Class: 'support/email_builder.php' 2 | ================================================ 3 | 4 | EmailBuilder is a powerful generator/builder class that creates beautiful, fully responsive, table-based HTML emails to be used later with the SMTP class. Uses a natural PHP arrays approach. 5 | 6 | Example: 7 | 8 | ```php 9 | require_once "support/email_builder.php"; 10 | 11 | // Write normal CSS. 12 | $styles = array( 13 | "a" => "text-decoration: none;", 14 | "#headerwrap" => "font-size: 0; line-height: 0;", 15 | "#contentwrap" => "font-family: Helvetica, Arial, sans-serif; font-size: 18px; line-height: 27px; color: #333333;", 16 | "#contentwrap a:not(.bigbutton)" => "color: #4E88C2;", 17 | "#contentwrap a.bigbutton" => "font-family: Helvetica, Arial, sans-serif; font-size: 18px; line-height: 27px; color: #FEFEFE;", 18 | "#footerwrap" => "font-family: Helvetica, Arial, sans-serif; font-size: 14px; line-height: 21px; color: #F0F0F0;", 19 | "#footerwrap a" => "color: #CCCCCC;" 20 | ); 21 | 22 | $content = array( 23 | // Header. 24 | array( 25 | "type" => "layout", 26 | "id" => "headerwrap", 27 | // "table-bgcolor" => "#FF0000", 28 | "width" => 600, 29 | "content" => array( 30 | array( 31 | "type" => "image", 32 | "width" => 600, 33 | // "src" => "http://localhost/ultimate-email/test_suite/test_newsletter_header.png", 34 | "file" => "test_suite/test_newsletter_header.png" 35 | ) 36 | ) 37 | ), 38 | 39 | // Main content. 40 | array( 41 | "type" => "layout", 42 | "width" => 600, 43 | "content" => array( 44 | array( 45 | "type" => "layout", 46 | "id" => "contentwrap", 47 | "width" => "90%", 48 | "content" => array( 49 | array("type" => "space", "height" => 1), 50 | 51 | "

Hello valued humanoid!

", 52 | 53 | // Float an image to the right. 54 | array( 55 | "type" => "layout", 56 | // "table-width" => "35%", 57 | "table-align" => "right", 58 | "width" => 100, 59 | "padding" => array(5, 0, 20, 20), 60 | "content" => array( 61 | array( 62 | "type" => "image", 63 | "width" => 100, 64 | "src" => "http://lorempixel.com/g/100/150/technics/1/", 65 | "alt" => "Circuitry" 66 | ) 67 | ) 68 | ), 69 | 70 | "

Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah

", 71 | "

Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah

", 72 | "

Blah blah blah

", 73 | "

Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah

", 74 | 75 | // Display a clickable link as a centered button (e.g. a call to action). 76 | array( 77 | "type" => "layout", 78 | "align" => "center", 79 | "content" => array( 80 | array( 81 | "type" => "button", 82 | "href" => "#", 83 | // "target" => "_blank", 84 | "class" => "bigbutton", 85 | "bgcolor" => "#4E88C2", 86 | "padding" => array(12, 18), 87 | "text" => "Take the survey" 88 | ) 89 | ) 90 | ), 91 | 92 | // Spacers and a splitter. 93 | array("type" => "space", "height" => 20), 94 | array("type" => "split", "bgcolor" => "#F0F0F0"), 95 | array("type" => "space", "height" => 5), 96 | 97 | "

Sincerely,

", 98 | "

Blah blah blah

", 99 | 100 | array("type" => "space", "height" => 1), 101 | ) 102 | ) 103 | ) 104 | ), 105 | 106 | // Footer. 107 | array( 108 | "type" => "layout", 109 | "width" => 600, 110 | "bgcolor" => "#182434", 111 | "content" => array( 112 | array( 113 | "type" => "layout", 114 | "id" => "footerwrap", 115 | "width" => "90%", 116 | "content" => array( 117 | array("type" => "space", "height" => 1), 118 | 119 | "

Blah blah blah

", 120 | "

Unsubscribe

", 121 | "

Blah blah blah

", 122 | 123 | array("type" => "space", "height" => 1), 124 | ) 125 | ) 126 | ) 127 | ) 128 | ); 129 | 130 | $result = EmailBuilder::Generate($styles, $content); 131 | 132 | echo $result["html"]; 133 | ?> 134 | ``` 135 | 136 | Output: 137 | 138 | ![EmailBuilder example](https://user-images.githubusercontent.com/1432111/62052744-f9903b00-b1ca-11e9-992c-d911af497be3.png) 139 | 140 | EmailBuilder::RegisterHandler($mode, $callback) 141 | ----------------------------------------------- 142 | 143 | Access: public static 144 | 145 | Parameters: 146 | 147 | * $mode - A string containing one of "init", "custom_type", or "finalize". 148 | * $callback - A valid callback function for the specified mode. The callback function must accept the correct number and type of inputs. 149 | 150 | Returns: Nothing. 151 | 152 | This static function is the basis of adding new functionality to the EmailBuilder class. Callbacks are executed in a specific sequence for managing field modifications in an efficient manner. 153 | 154 | EmailBuilder::InitContentOpts(&$contentopts) 155 | -------------------------------------------- 156 | 157 | Access: protected static 158 | 159 | Parameters: 160 | 161 | * $contentopts - An array containing options used to generate the HTML. 162 | 163 | Returns: Nothing. 164 | 165 | This internal static function initializes any registered "init" handlers. The $contentopts array may be modified. 166 | 167 | EmailBuilder::AlterOption(&$html, &$inlineimages, &$option, $prefix) 168 | -------------------------------------------------------------------- 169 | 170 | Access: protected static 171 | 172 | Parameters: 173 | 174 | * $html - A string containing the HTML generated so far. 175 | * $inlineimages - An array containing inline images added so far. 176 | * $option - An array containing the current option being processed. 177 | * $prefix - A string containing the current output prefix. 178 | 179 | Returns: Nothing. 180 | 181 | This internal static function allows any registered "custom_type" handlers to process the current option. 182 | 183 | EmailBuilder::Finalize(&$html, &$contentopts) 184 | --------------------------------------------- 185 | 186 | Access: protected static 187 | 188 | Parameters: 189 | 190 | * $html - A string containing the HTML generated so far. 191 | * $contentopts - An array containing options used to generate the HTML. 192 | 193 | Returns: Nothing. 194 | 195 | This internal static function runs any registered "finalize" handlers. 196 | 197 | EmailBuilder::ProcessContentOpts(&$html, &$inlineimages, &$contentopts, $prefix) 198 | -------------------------------------------------------------------------------- 199 | 200 | Access: _internal_ static 201 | 202 | Parameters: 203 | 204 | * $html - A string containing the HTML generated so far. 205 | * $inlineimages - An array containing inline images added so far. 206 | * $contentopts - An array containing the current level of options being processed. 207 | * $prefix - A string containing the current output prefix. 208 | 209 | Returns: Nothing. 210 | 211 | This mostly internal static function processes the current level of options to generate the correct HTML output. Options may be processed recursively. 212 | 213 | EmailBuilder::Generate($styles, $contentopts) 214 | --------------------------------------------- 215 | 216 | Access: public static 217 | 218 | Parameters: 219 | 220 | * $styles - An array of key-value pairs that maps CSS3 selectors to strings containing styles to apply to the targets. 221 | * $contentopts - An array of options that describe how to generate the HTML content. 222 | 223 | Returns: A standard array of information. 224 | 225 | This static function takes in two arrays of information containing CSS3 styles to apply inline and a set of options that defines how the HTML will be generated for the email. First, `EmailBuilder::ProcessContentOpts()` is called and the HTML is generated. Then inline styles are applied to the generated HTML using the $styles array, 'id' and 'class' attributes are removed, and the result is returned. 226 | 227 | The $contentopts array is very flexible and options vary depending on the option. An option can be a string or an array. When the option is a string, it is assumed to be valid HTML and included as-is (NOTE: TagFilter may correct invalid HTML later in the process). 228 | 229 | When the option is an array of key-value pairs, the "type" may be one of: 230 | 231 | * layout - A highly flexible horizontal layout with positioning, including float emulation support. 232 | * space - A vertical spacer. 233 | * split - A horizontal line/splitter. 234 | * button - A clickable link displayed as a button (e.g. call to action). 235 | * image - A responsive/scaled image with optional inline embedding support (i.e. `cid:`). 236 | 237 | Type-specific options: 238 | 239 | * padding (layout, button) - An integer or array of integers containing standard CSS padding declarations for top, right, bottom, left. 240 | * table-width (layout) - A string or integer containing the width of the table (Default is "100%"). Should be a percentage or in pixels. 241 | * table-bgcolor (layout) - A string containing the background color for the table. Should be in the format "#RRGGBB" for maximum compatibility. 242 | * table-class (layout) - A string containing one or more CSS classes to apply to the table element. 243 | * table-align (layout) - A string containing one of "left", "right", "center" to apply to the table element. Useful for simulating floats. 244 | * width (layout) - A string or integer containing the width of the main content area for the layout. When both 'width' and 'padding' are defined, 'table-width' is forced to be a precise pixel count. 245 | * align (layout) - A string containing one of "left", "right", "center" to align the content inside. 246 | * row-align (layout) - A string containing one of "left", "right", "center" to align the content region when 'width' is defined and 'padding' is not defined (Default is "center"). This creates a responsive content layout. 247 | * bgcolor (layout, space, split, button) - A string containing the background color to use. For layouts, this is the background color of the content area. Should be in the format "#RRGGBB" for maximum compatibility. 248 | * style (layout, button, image) - A string containing CSS styles to append to the style attribute. For layouts, this is applied to the content area. 249 | * id (layout, button, image) - A string containing a CSS ID to apply. For layouts, this is applied to the content area. 250 | * class (layout, button, image) - A string containing one or more CSS classes to apply. For layouts, this is applied to the content area. 251 | * content (layout) - An array containing more $contentopts options. `EmailBuilder::ProcessContentOpts()` is called recursively to process the options. 252 | * height (space, split) - An integer containing the number of vertical pixels for the spacer/splitter. 253 | * border-radius (button) - An integer containing the number of pixels to use for the corners of the button (Default is 4). 254 | * href (button) - A string containing the URL to link to. 255 | * target (button) - A string containing the window target (e.g. "_blank"). 256 | * text (button) - A string containing the text to use. Will be escaped. 257 | * html (button) - A string containing the HTML to use. 258 | * file (image) - A string containing the filename of the file to embed/inline as a "cid:" (Content-ID) image. The file must be JPEG, PNG, or GIF. May appear as a file attachment in some email clients in addition to being embedded. 259 | * src (image) - A string containing the URL of the image to load. Note that email clients won't automatically load this type of image. 260 | * alt (image) - A string containing alt text for the image. 261 | * width (image) - An integer containing the width, in pixels, to display the image. 262 | * height (image) - An integer containing the height, in pixels, to display the image. 263 | -------------------------------------------------------------------------------- /docs/mime_parser.md: -------------------------------------------------------------------------------- 1 | MIMEParser Class: 'support/mime_parser.php' 2 | =========================================== 3 | 4 | The MIMEParser class extracts content from e-mails retrieved with the POP3 class allowing for deep message analysis and processing. If you are extracting HTML with this, the [TagFilter class](https://github.com/cubiclesoft/ultimate-web-scraper) will prove useful for extracting content from the HTML. 5 | 6 | Example usage: 7 | 8 | ```php 9 | "[pop3.yourhost.com]", 17 | "port" => [110 or 995], 18 | "secure" => [true or false] 19 | ); 20 | 21 | $pop3 = new POP3(); 22 | $result = $pop3->Connect("[YOUR e-mail username]", "[YOUR e-mail password]", $pop3options); 23 | if (!$result["success"]) 24 | { 25 | echo "POP3 - Connect(): " . $result["error"] . "\n"; 26 | 27 | exit(); 28 | } 29 | 30 | $result = $pop3->GetMessageList(); 31 | if (!$result["success"]) 32 | { 33 | echo "POP3 - GetMessageList(): " . $result["error"] . "\n"; 34 | 35 | exit(); 36 | } 37 | 38 | $ids = $result["ids"]; 39 | foreach ($ids as $id => $size) 40 | { 41 | // Only retrieve messages under 1MB. 42 | if ($size < 1024768) 43 | { 44 | $result = $pop3->GetNextMessage($id); 45 | if (!$result["success"]) 46 | { 47 | echo "POP3 - GetNextMessage(): " . $result["error"] . "\n"; 48 | 49 | exit(); 50 | } 51 | 52 | $message = $result["message"]; 53 | 54 | // Process the message. 55 | $message = MIMEParser::Parse($result["message"]); 56 | var_dump($message); 57 | 58 | $content = MIMEParser::ExtractContent($message); 59 | var_dump($content); 60 | } 61 | 62 | // $result = $pop3->DeleteMessage($id); 63 | // if (!$result["success"]) 64 | // { 65 | // echo "POP3 - DeleteMessage(): " . $result["error"] . "\n"; 66 | // 67 | // exit(); 68 | // } 69 | } 70 | 71 | $pop3->Disconnect(); 72 | ?> 73 | ``` 74 | 75 | MIMEParser::ConvertFromRFC1341($data) 76 | ------------------------------------- 77 | 78 | Access: public static 79 | 80 | Parameters: 81 | 82 | * $data - A string containing the data to convert. 83 | 84 | Returns: A string containing the converted data. 85 | 86 | This static function converts RFC 1341 encoded data (also known as "Quoted Printable") into 8-bit clean data. 8-bit data (e.g. UTF-8) has to be converted into 7-bit clean ASCII for transport across the Internet. This function reverses the process. 87 | 88 | MIMEParser::ConvertFromRFC1342($data) 89 | ------------------------------------- 90 | 91 | Access: public static 92 | 93 | Parameters: 94 | 95 | * $data - A string containing the data to convert. 96 | 97 | Returns: A string containing the converted data. 98 | 99 | This static function converts RFC 1342 encoded header data into UTF-8 data. 8-bit headers have to be converted into 7-bit clean ASCII for transport across the Internet. This function reverses the process. 100 | 101 | MIMEParser::ConvertCharset($data, $incharset, $outcharset) 102 | ---------------------------------------------------------- 103 | 104 | Access: public static 105 | 106 | Parameters: 107 | 108 | * $data - A string containing the data to convert. 109 | * $incharset - A string containing the source character encoding. 110 | * $outcharset - A string containing the destination character encoding. 111 | 112 | Returns: A string containing the converted data if successful, a boolean of false on failure. 113 | 114 | This static function converts a string from one character set to another. Translation is done in the following order of preference: iconv(), mb_convert_encoding(), and then utf8_encode()/utf8_decode(). 115 | 116 | MIMEParser::ExplodeHeader($data) 117 | -------------------------------- 118 | 119 | Access: public static 120 | 121 | Parameters: 122 | 123 | * $data - A string containing a MIME header to parse. 124 | 125 | Returns: An array containing the parsed MIME header. 126 | 127 | This static function parses a single MIME header into its component pieces. The first piece's key is an empty string "". The rest are split up by key-value pairs. This function is called by `MIMEParser::Parse()` and `MIMEParser::ExtractContent()`. 128 | 129 | MIMEParser::Parse($data, $depth = 0) 130 | ------------------------------------ 131 | 132 | Access: public static 133 | 134 | Parameters: 135 | 136 | * $data - A string containing the MIME data to parse. 137 | * $depth - An internal integer to control recursive call depth (Default is 0). Do not use. 138 | 139 | Returns: An array containing the parsed MIME data. 140 | 141 | This static function parses MIME data into headers, body, and sub-MIME components. It recursively calls itself up to a depth of 10 to parse most MIME content. Do not use the `$depth` parameter when calling this function (second parameter). 142 | 143 | This function is typically used to parse an e-mail message retrieved from a POP3 server. This function can also handle non-MIME e-mail content. 144 | 145 | MIMEParser::ExtractContent($message, $depth = 0) 146 | ------------------------------------------------ 147 | 148 | Access: public static 149 | 150 | Parameters: 151 | 152 | * $message - An array from `MIMEParser::Parse()`. 153 | * $depth - An internal integer to control recursive call depth (Default is 0). Do not use. 154 | 155 | This function takes the output from `MIMEParser::Parse()` and extracts "text/plain" and "text/html" components from the message. It recursively calls itself up to a depth of 10 to parse the content. Do not use the `$depth` parameter when calling this function (second parameter). 156 | 157 | This function is typically used to extract just the text and HTML components of a MIME message that have been parsed by `MIMEParser::Parse()`. 158 | 159 | MIMEParser::ReplaceNewlines($replacewith, $data) 160 | ------------------------------------------------ 161 | 162 | Access: private static 163 | 164 | Parameters: 165 | 166 | * $replacewith - A string to replace newlines with. 167 | * $data - A string to replace newlines in. 168 | 169 | Returns: A string with newlines replaced. 170 | 171 | This static function replaces any newline combination within the input data with the target newline. All known (DOS, Mac, *NIX) and unknown newline combinations are handled to normalize on the replacement newline string. 172 | -------------------------------------------------------------------------------- /docs/pop3.md: -------------------------------------------------------------------------------- 1 | POP3 Class: 'support/pop3.php' 2 | =============================== 3 | 4 | The POP3 class provides routines to communicate with a POP3 server. These function are useful on servers that are configured to require "POP before SMTP" to send e-mail. They can also be used for automation of a script that watches a mailbox for incoming messages and then processes the incoming e-mail as it arrives. 5 | 6 | Example usage of POP before SMTP: 7 | 8 | ```php 9 | Your message goes here"; 15 | 16 | // Send the e-mail to the user. 17 | // Change the stuff in '[]' to your server settings. 18 | $smtpoptions = array( 19 | "headers" => SMTP::GetUserAgent("Thunderbird"), 20 | "htmlmessage" => $body, 21 | "textmessage" => SMTP::ConvertHTMLToText($body), 22 | "server" => "[smtp.yourhost.com]", 23 | "port" => [25 or 465], 24 | "secure" => [true or false], 25 | "username" => "[YOUR e-mail username]", 26 | "password" => "[YOUR e-mail password]" 27 | ); 28 | 29 | $pop3options = array( 30 | "server" => "[pop3.yourhost.com]", 31 | "port" => [110 or 995], 32 | "secure" => [true or false] 33 | ); 34 | 35 | $fromaddr = "[YOUR e-mail address]"; 36 | $subject = "Thanks for signing up!"; 37 | $result = SMTP::SendEmail($fromaddr, $toaddr, $subject, $smtpoptions); 38 | if (!$result["success"]) 39 | { 40 | // This is usually the correct thing 41 | // to do to implement POP-before-SMTP. 42 | if ($smtpoptions["username"] != "" && $smtpoptions["password"] != "") 43 | { 44 | $pop3 = new POP3; 45 | $result = $pop3->Connect($smtpoptions["username"], $smtpoptions["password"], $pop3options); 46 | if ($result["success"]) 47 | { 48 | $pop3->Disconnect(); 49 | 50 | $result = SMTP::SendEmail($fromaddr, $toaddr, $subject, $smtpoptions); 51 | } 52 | } 53 | 54 | if (!$result["success"]) 55 | { 56 | echo "Failed to send e-mail.\n"; 57 | 58 | exit(); 59 | } 60 | } 61 | ?> 62 | ``` 63 | 64 | Example usage of automation: 65 | 66 | ```php 67 | "[pop3.yourhost.com]", 75 | "port" => [110 or 995], 76 | "secure" => [true or false] 77 | ); 78 | 79 | $pop3 = new POP3(); 80 | $result = $pop3->Connect("[YOUR e-mail username]", "[YOUR e-mail password]", $pop3options); 81 | if (!$result["success"]) 82 | { 83 | echo "POP3 - Connect(): " . $result["error"] . "\n"; 84 | 85 | exit(); 86 | } 87 | 88 | $result = $pop3->GetMessageList(); 89 | if (!$result["success"]) 90 | { 91 | echo "POP3 - GetMessageList(): " . $result["error"] . "\n"; 92 | 93 | exit(); 94 | } 95 | 96 | $ids = $result["ids"]; 97 | foreach ($ids as $id => $size) 98 | { 99 | // Only retrieve messages under 1MB. 100 | if ($size < 1024768) 101 | { 102 | $result = $pop3->GetNextMessage($id); 103 | if (!$result["success"]) 104 | { 105 | echo "POP3 - GetNextMessage(): " . $result["error"] . "\n"; 106 | 107 | exit(); 108 | } 109 | 110 | $message = $result["message"]; 111 | 112 | // Process the message. 113 | $message = MIMEParser::Parse($result["message"]); 114 | var_dump($message); 115 | 116 | $content = MIMEParser::ExtractContent($message); 117 | var_dump($content); 118 | } 119 | 120 | // $result = $pop3->DeleteMessage($id); 121 | // if (!$result["success"]) 122 | // { 123 | // echo "POP3 - DeleteMessage(): " . $result["error"] . "\n"; 124 | // 125 | // exit(); 126 | // } 127 | } 128 | 129 | $pop3->Disconnect(); 130 | ?> 131 | ``` 132 | 133 | POP3::__construct() 134 | ------------------- 135 | 136 | Access: public 137 | 138 | Parameters: None. 139 | 140 | Returns: Nothing. 141 | 142 | This function initializes the class. 143 | 144 | POP3::GetSSLCiphers($type = "intermediate") 145 | ------------------------------------------- 146 | 147 | Access: public static 148 | 149 | Parameters: 150 | 151 | * $type - A string containing one of "modern", "intermediate", or "old" (Default is "intermediate"). 152 | 153 | Returns: A string containing the SSL cipher list to use. 154 | 155 | This static function returns SSL cipher lists extracted from the [Mozilla SSL configuration generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/). 156 | 157 | POP3::GetSafeSSLOpts($cafile = true, $cipherstype = "intermediate") 158 | ------------------------------------------------------------------- 159 | 160 | Access: public static 161 | 162 | Parameters: 163 | 164 | * $cafile - A boolean that indicates whether or not to use the internally defined CA file list or a string containing the full path and filename of a CA root certificate file (Default is true). 165 | * $cipherstype - A string containing one of "modern", "intermediate", or "old" (Default is "intermediate"). See GetSSLCiphers() above. 166 | 167 | Returns: An array of SSL context options. 168 | 169 | This static function is used to generate a default "sslopts" array if they are not provided when connecting to an associated secure POP3 server. 170 | 171 | POP3::ProcessSSLOptions(&$options, $key, $host) 172 | ----------------------------------------------- 173 | 174 | Access: private static 175 | 176 | Parameters: 177 | 178 | * $options - An array of options. 179 | * $key - A string specifying which SSL options to process. 180 | * $host - A string containing alternate hostname information. 181 | 182 | Returns: Nothing. 183 | 184 | This internal static function processes the "auto_cainfo", "auto_peer_name", "auto_cn_match", and "auto_sni" options for "sslopts" for SSL/TLS context purposes. 185 | 186 | POP3::GetIDNAHost($host) 187 | ------------------------ 188 | 189 | Access: protected static 190 | 191 | Parameters: 192 | 193 | * $host - A string containing a hostname to convert to IDNA if necessary. 194 | 195 | Returns: A string containing a converted hostname. 196 | 197 | This internal static function converts an input Unicode hostname to IDNA. If no Unicode characters are detected, this function just returns the input string. 198 | 199 | POP3::Connect($username, $password, $options = array()) 200 | ------------------------------------------------------- 201 | 202 | Access: public 203 | 204 | Parameters: 205 | 206 | * $username - A string containing the username to use. 207 | * $password - A string containing the password to use. 208 | * $options - An array of options containing connection and class information (Default is array()). 209 | 210 | Returns: A standard array of information. 211 | 212 | This function connects to a POP3 server using the specified username and password. 213 | 214 | The `$options` array can contain: 215 | 216 | * server - A string containing the POP3 server to connect to (Default is "localhost"). 217 | * port - An integer that specifies which port to connect to (Default is 110 when 'secure' is false, 995 when 'secure' is true). 218 | * secure - A boolean that determines whether or not to connect using SSL (Default is false). 219 | * protocol - A string containing the preferred low-level protocol. May be any supported protocol that the PHP stream_get_transports() function supports (e.g. "ssl", "tls", "tlsv1.2", "tcp"). 220 | * connecttimeout - An integer containing the amount of time to wait for the connection to the host to succeed in seconds (Default is 10). 221 | * sslopts - An array of valid SSL context options key-value pairs to use when connecting to a SSL-enabled host. Also supports "auto_cainfo", "auto_peer_name", "auto_cn_match", and "auto_sni" options to define several context options automatically. 222 | * sslhostname - A string containing an alternate hostname to match the certificate against. 223 | * debug - A boolean that specifies that every function in the class will return the raw POP3 conversation. 224 | * debug_callback - A string containing a function name of a debugging callback. The callback function must accept three parameters - callback($type, $data, $opts). 225 | * debug_callback_opts - Data to pass as the third parameter to the function specified by the 'debug_callback' option. 226 | 227 | While the `$options` array is optional, the best approach is to be as specific as possible. 228 | 229 | POP3::GetMessageList() 230 | ---------------------- 231 | 232 | Access: public 233 | 234 | Parameters: None. 235 | 236 | Returns: A standard array of information. 237 | 238 | This function retrieves the list of message IDs and message sizes on the server. This information is returned in key-value ID and size pairs. 239 | 240 | POP3::GetMessage($id) 241 | --------------------- 242 | 243 | Access: public 244 | 245 | Parameters: 246 | 247 | * $id - An integer containing a valid message ID on the server. 248 | 249 | Returns: A standard array of information. 250 | 251 | Retrieves a single message from the POP3 server that matches the specified message ID. Message IDs are retrieved with `POP3::GetMessageList()`. 252 | 253 | The message is typically processed with the `MIMEParser::Parse()` function and then text and HTML components are further extracted with the `MIMEParser::ExtractContent()` function. 254 | 255 | POP3::DeleteMessage($id) 256 | ------------------------ 257 | 258 | Access: public 259 | 260 | Parameters: 261 | 262 | * $id - An integer containing a valid message ID on the server. 263 | 264 | Returns: A standard array of information. 265 | 266 | This function deletes a single message from a POP3 server based on the message ID. Message IDs are retrieved with `POP3::GetMessageList()`. 267 | 268 | POP3::Disconnect() 269 | ------------------ 270 | 271 | Access: public 272 | 273 | Parameters: None. 274 | 275 | Returns: A boolean of true if the instance was successfully disconnected from the POP3 server, false otherwise. 276 | 277 | This function disconnects from a POP3 server and performs cleanup for a potential future connection with the same instance. 278 | 279 | POP3::POP3Request($command, &$rawsend, &$rawrecv, $multiline = false) 280 | --------------------------------------------------------------------- 281 | 282 | Access: public 283 | 284 | Parameters: 285 | 286 | * $command - A string containing the POP3 command to send. 287 | * $rawsend - A string to append debugging information to. 288 | * $rawrecv - A string to append debugging information to. 289 | * $multiline - A boolean that indicates that the response is expected to be multiline. 290 | 291 | Returns: A standard array of information. 292 | 293 | This internal function sends a command to the POP3 server and retrieves the response. Drastically simplifies and improves maintainability of the class. 294 | 295 | POP3::GetPOP3Response($multiline) 296 | --------------------------------- 297 | 298 | Access: public 299 | 300 | Parameters: 301 | 302 | * $multiline - A boolean indicating that the caller is expecting the POP3 server to respond with a multiline response. 303 | 304 | Returns: A standard array of information. 305 | 306 | This internal function retrieves a response from a POP3 server. 307 | 308 | POP3::POP3_Translate($format, ...) 309 | ---------------------------------- 310 | 311 | Access: _internal_ static 312 | 313 | Parameters: 314 | 315 | * $format - A string containing valid sprintf() format specifiers. 316 | 317 | Returns: A string containing a translation. 318 | 319 | This internal static function takes input strings and translates them from English to some other language if CS_TRANSLATE_FUNC is defined to be a valid PHP function name. 320 | -------------------------------------------------------------------------------- /docs/smtp.md: -------------------------------------------------------------------------------- 1 | SMTP Class: 'support/smtp.php' 2 | =============================== 3 | 4 | The SMTP class contains mail sending functionality, content cleanup, and e-mail address correction routines. The mail sending functions directly connect to a SMTP/ESMTP server (or use mail()) to send an e-mail and offer more functionality and flexibility than the built-in PHP mail() function. 5 | 6 | I'm not responsible with what you choose to do with these functions. These are incredibly powerful PHP routines that go far beyond what PHP mail() calls typically do. It is easy to create e-mails that look exactly like they came from a real e-mail client and will tend to get through spam filters. 7 | 8 | Due to spam proliferation across the Internet and the fact that each mail server is set up uniquely makes it nearly impossible to diagnose problems with these functions and YOUR mail server. Note that the included test suite in the Ultimate E-mail Toolkit may help with diagnosing mail sending issues. Other than that, you are on your own and, in the event of problems, you should contact your web/e-mail hosting provider. These functions are a good place to start though and offer significantly more functionality than anything you'll likely need. 9 | 10 | Full MIME, Quoted Printable, and binary transfer support. 11 | 12 | Example e-mail address validation: 13 | 14 | ```php 15 | 26 | ``` 27 | 28 | Example usage: 29 | 30 | ```php 31 | Your message goes here"; 37 | 38 | // Send the e-mail to the user. 39 | // Change the stuff in '[]' to your server settings. 40 | $smtpoptions = array( 41 | "headers" => SMTP::GetUserAgent("Thunderbird"), 42 | "htmlmessage" => $body, 43 | "textmessage" => SMTP::ConvertHTMLToText($body), 44 | "server" => "[smtp.yourhost.com]", 45 | "port" => [25 or 465], 46 | "secure" => [true or false], 47 | "username" => "[YOUR e-mail username]", 48 | "password" => "[YOUR e-mail password]" 49 | ); 50 | 51 | $pop3options = array( 52 | "server" => "[pop3.yourhost.com]", 53 | "port" => [110 or 995], 54 | "secure" => [true or false] 55 | ); 56 | 57 | $fromaddr = "[YOUR e-mail address]"; 58 | $subject = "Thanks for signing up!"; 59 | $result = SMTP::SendEmail($fromaddr, $toaddr, $subject, $smtpoptions); 60 | if (!$result["success"]) 61 | { 62 | // This is usually the correct thing 63 | // to do to implement POP-before-SMTP. 64 | if ($smtpoptions["username"] != "" && $smtpoptions["password"] != "") 65 | { 66 | $pop3 = new POP3; 67 | $result = $pop3->Connect($smtpoptions["username"], $smtpoptions["password"], $pop3options); 68 | if ($result["success"]) 69 | { 70 | $pop3->Disconnect(); 71 | 72 | $result = SMTP::SendEmail($fromaddr, $toaddr, $subject, $smtpoptions); 73 | } 74 | } 75 | 76 | if (!$result["success"]) 77 | { 78 | echo "Failed to send e-mail.\n"; 79 | 80 | exit(); 81 | } 82 | } 83 | ?> 84 | ``` 85 | 86 | SMTP::ConvertToRFC1341($data, $restrictmore = false) 87 | ---------------------------------------------------- 88 | 89 | Access: public static 90 | 91 | Parameters: 92 | 93 | * $data - A string containing the data to convert to RFC1341. 94 | * $restrictmore - A boolean that imposes additional character restrictions for EBCDIC transport (Default is false). 95 | 96 | Returns: A string containing the converted data. 97 | 98 | This static function takes an input string of data and converts it into a RFC1341-compliant string. RFC1341 is a hacky workaround to allow 8-bit data to be transmitted cleanly over 7-bit transports (SMTP is a 7-bit transport) in the body of a message. Also known as Quoted Printable. Typically used for e-mail in Latin-based languages (e.g. U.S. English). 99 | 100 | SMTP::ConvertEmailMessageToRFC1341($data, $restrictmore = false) 101 | ---------------------------------------------------------------- 102 | 103 | Access: public static 104 | 105 | Parameters: 106 | 107 | * $data - A string containing the data to convert to RFC1341. 108 | * $restrictmore - A boolean that imposes additional character restrictions for EBCDIC transport (Default is false). 109 | 110 | Returns: A string containing the converted data. 111 | 112 | This static function takes an input string of data, converts newlines, and then converts the data into a RFC1341-compliant string. See `SMTP::ConvertToRFC1341()` for details. 113 | 114 | SMTP::ConvertToRFC1342($data, $lang = "UTF-8", $encodeb64 = true) 115 | ----------------------------------------------------------------- 116 | 117 | Access: public static 118 | 119 | Parameters: 120 | 121 | * $data - A string containing the data to convert to RFC1342. 122 | * $lang - A string containing a valid character set (Default is "UTF-8"). 123 | * $encodeb64 - A boolean that encodes the output as Base64 (Default is true). 124 | 125 | Returns: A string containing the converted data. 126 | 127 | This static function takes an input string and converts it into a RFC1342-compliant string. RFC1342 is a hacky workaround to allow 8-bit data to be transmitted cleanly over 7-bit transports in an e-mail header. Used primarily to encode the name portion of an e-mail address. 128 | 129 | By default Base64 encoding is used but Quoted Printable can be used: `$encodeb64` can be set to false when `$lang` is "ISO-8859-1" or "US-ASCII". 130 | 131 | SMTP::MakeValidEmailAddress($email, $options = array()) 132 | ------------------------------------------------------- 133 | 134 | Access: public static 135 | 136 | Parameters: 137 | 138 | * $email - A string containing an e-mail address to clean up. 139 | * $options - An array containing options that affect the final output (Default is array()). 140 | 141 | Returns: An array containing whether or not conversion was successful, the cleaned up e-mail address, and whether or not the domain passes DNS checks. 142 | 143 | This static function takes an input e-mail address of the form 'local@domain', parses it one character at a time using a state engine, cleans it up of common mistakes, and validates that the resulting domain is valid. For example, "someone@hotmail,com" would become "someone@hotmail.com". This function allows all non-obsolete address formats. 144 | 145 | This function is not a validation routine but it can be used as such. If it successfully completes, just check the resulting e-mail address ('email') for a match with the original. If they match exactly, then the original is valid. This function, however, is much more desirable for its repairing capabilities. It becomes possible to use AJAX to send a query to the server to determine if the address is valid. Instead of saying, "Invalid e-mail address" to the user, it could say, "Did you mean ...?" with the corrected e-mail address. 146 | 147 | The `$options` array can contain the following options: 148 | 149 | * usedns - A boolean that specifies if the function should check for MX and A DNS records for the domain (Default is true). 150 | * nameservers - An array containing IP addresses of DNS servers to use as resolvers in order of preference or a boolean of true to autodetect (Default is true - local nameservers or Google DNS). 151 | 152 | Checking for the existence of a SMTP mail server via DNS is a great way to avoid bounced e-mail. DNS checking is done with a slightly modified PEAR::Net_DNS library, which is more versatile than the built-in PHP functions that aren't always available for all platforms. 153 | 154 | E-mail validation/clean up is hard. As of this writing, Dominic Sayers has a decent test suite. This function passes most tests with flying colors - everything except obsolete address formats - and even passes most of the tests that are supposed to "fail" because of the repairing capabilities, which only a state engine could accomplish. 155 | 156 | SMTP::UpdateDNSTTLCache() 157 | ------------------------- 158 | 159 | Access: public static 160 | 161 | Parameters: None. 162 | 163 | Returns: Nothing. 164 | 165 | This static function updates the static DNS cache that this class uses for resolving identical domains by removing outdated cache entries (Time-To-Live has expired). 166 | 167 | SMTP::GetDNSRecord($domain, $types, $nameservers, $cache = true) 168 | ---------------------------------------------------------------- 169 | 170 | Access: public static 171 | 172 | Parameters: 173 | 174 | * $domain - A string containing a domain name. 175 | * $types - An array of strings containing the DNS record types to look up (Default is array("MX", "A")). 176 | * $nameservers - An array of strings containing the IP addresses of DNS servers to use as resolvers in order of preference or a boolean of true to autodetect (Default is true - local nameservers or Google DNS). 177 | * $cache - A boolean indicating that the TTL of the domain is to be cached in the SMTP DNS TTL cache (Default is true). 178 | 179 | Returns: A standard array of information. 180 | 181 | This static function retrieves DNS record information for a specific domain and then optionally caches the results to avoid future DNS requests. 182 | 183 | SMTP::EmailAddressesToNamesAndEmail(&$destnames, &$destaddrs, $emailaddrs, $removenames = false, $options = array()) 184 | -------------------------------------------------------------------------------------------------------------------- 185 | 186 | Access: public static 187 | 188 | Parameters: 189 | 190 | * $destnames - An array that receives the names processed. 191 | * $destaddrs - An array that receives e-mail addresses processed. 192 | * $emailaddrs - A string containing names and e-mail addresses to filter and separate. 193 | * $removenames - A boolean that returns no names for $destnames (Default is false). 194 | * $options - An array to pass to `SMTP::MakeValidEmailAddress()` (Default is array()). 195 | 196 | Returns: A boolean of true if at least one valid e-mail address was successfully processed, false otherwise. 197 | 198 | This static function takes a string of '"name" ', 'name ', or 'emailaddr' and extracts each component. Multiple e-mail addresses can be separated with commas (',') [preferred] or semi-colons (';'). 199 | 200 | This function attempts to deal with malformed strings as best as possible but there are limits as to what it can do. `SMTP::MakeValidEmailAddress()` is called for each e-mail address to check its validity. 201 | 202 | SMTP::EmailAddressesToEmailHeaders($emailaddrs, $headername, $multiple = true, $removenames = false, $options = array()) 203 | ------------------------------------------------------------------------------------------------------------------------ 204 | 205 | Access: public static 206 | 207 | Parameters: 208 | 209 | * $emailaddrs - A string containing names and e-mail addresses to process. 210 | * $headername - A string containing the header name. 211 | * $multiple - A boolean that allows multiple e-mail addresses in the header (Default is true). 212 | * $removenames - A boolean that will not include names (Default is false). 213 | * $options - An array to pass to MakeValidEmailAddress() (Default is array()). 214 | 215 | Returns: A string containing an e-mail header if the e-mail addresses were successfully processed, an empty string otherwise. 216 | 217 | This static function generates a valid mail header from one or more e-mail addresses. If `$headername` is an empty string, e-mail addresses will be returned without a header name. 218 | 219 | SMTP::GetUserAgent($type) 220 | ------------------------- 221 | 222 | Access: public static 223 | 224 | Parameters: 225 | 226 | * $type - A string containing one of "Thunderbird", "Thunderbird2", "OutlookExpress", "Exchange", or "OfficeOutlook". 227 | 228 | Returns: A string containing a valid user agent for the specified e-mail client. 229 | 230 | This static function returns a popular user agent string. These aren't always up-to-date but are usually good enough to get the job done on servers that require a user agent and helps to get past most spam filters. If you feel a string is too out of date, post a message to the forums. 231 | 232 | SMTP::GetTimeLeft($start, $limit) 233 | --------------------------------- 234 | 235 | Access: _internal_ static 236 | 237 | Parameters: 238 | 239 | * $start - A numeric value containing a UNIX timestamp of a start time. 240 | * $limit - A boolean of false or a numeric value containing the maximum amount of time, in seconds, to take from $start. 241 | 242 | Returns: A boolean of false if $limit is false, 0 if the time limit has been reached/exceeded, or a numeric value representing the amount of time left in seconds. 243 | 244 | This internal static function is used to calculate whether an operation has taken too long and then terminate the connection. 245 | 246 | SMTP::ProcessRateLimit($size, $start, $limit, $async) 247 | ----------------------------------------------------- 248 | 249 | Access: private static 250 | 251 | Parameters: 252 | 253 | * $size - An integer containing the number of bytes transferred. 254 | * $start - A numeric value containing a UNIX timestamp of a start time. 255 | * $limit - An integer representing the maximum acceptable rate in bytes/sec. 256 | * $async - A boolean indicating whether or not the function should not sleep (async caller). 257 | 258 | Returns: An integer containing the amount of time to wait for (async only), -1 otherwise. 259 | 260 | This internal static function calculates the current rate at which bytes are being transferred over the network. If the rate exceeds the limit, it calculates exactly how long to wait and then sleeps for that amount of time so that the average transfer rate is within the limit. 261 | 262 | SMTP::StreamTimedOut($fp) 263 | ------------------------- 264 | 265 | Access: private static 266 | 267 | Parameters: 268 | 269 | * $fp - A valid socket handle. 270 | 271 | Returns: A boolean of true if the underlying socket has timed out, false otherwise. 272 | 273 | This internal static function calls `stream_get_meta_data()` to determine the validity of the socket. 274 | 275 | HTTP::ProcessState__InternalRead(&$state, $size, $endchar = false) 276 | ------------------------------------------------------------------ 277 | 278 | Access: private static 279 | 280 | Parameters: 281 | 282 | * $state - A valid SMTP state array. 283 | * $size - An integer containing the maximum length to read in. 284 | * $endchar - A boolean of false or a string containing a single character to stop reading after (Default is false). 285 | 286 | Returns: Normalized fread() output. 287 | 288 | This internal static function gets rid of the old fgets() line-by-line retrieval mechanism used by `ProcessState__ReadLine()` and standardizes on fread() with an internal cache. Doing this also helps to work around a number of bugs in PHP. 289 | 290 | SMTP::ProcessState__ReadLine(&$state) 291 | ------------------------------------- 292 | 293 | Access: private static 294 | 295 | Parameters: 296 | 297 | * $state - A valid SMTP state array. 298 | 299 | Returns: A standard array of information. 300 | 301 | This internal static function attempts to read in a single line of information and return to the caller. 302 | 303 | SMTP::ProcessState__WriteData(&$state) 304 | -------------------------------------- 305 | 306 | Access: private static 307 | 308 | Parameters: 309 | 310 | * $state - A valid SMTP state array. 311 | 312 | Returns: A standard array of information. 313 | 314 | This internal static function attempts to write waiting data out to the socket. 315 | 316 | SMTP::ForceClose(&$state) 317 | ------------------------- 318 | 319 | Access: _internal_ static 320 | 321 | Parameters: 322 | 323 | * $state - A valid SMTP state array. 324 | 325 | Returns: Nothing. 326 | 327 | This internal static function forces closes the underlying socket. Any future attempts to use the socket will fail. 328 | 329 | SMTP::CleanupErrorState(&$state, $result) 330 | ----------------------------------------- 331 | 332 | Access: private static 333 | 334 | Parameters: 335 | 336 | * $state - A valid SMTP state array. 337 | * $result - A standard array of information. 338 | 339 | Returns: $result unmodified. 340 | 341 | This internal static function looks at an error condition on a socket to determine if the socket should be closed immediately or not. When the state of the socket is in 'async' (non-blocking) mode, the "no_data" error code will be returned quite frequently due to the non-blocking nature of those sockets. 342 | 343 | SMTP::InitSMTPRequest(&$state, $command, $expectedcode, $nextstate, $expectederror) 344 | ----------------------------------------------------------------------------------- 345 | 346 | Access: private static 347 | 348 | Parameters: 349 | 350 | * $state - A valid SMTP state array. 351 | * $command - A string containing the command to send to the SMTP server. 352 | * $expectedcode - An integer containing the expected SMTP response code from the server. 353 | * $nextstate - A string containing the state to move to once the server responds with the expected code. 354 | * $expectederror - A string containing error message details to use if the expected code is not returned from the server. 355 | 356 | Returns: Nothing. 357 | 358 | This internal static function sets the internal SMTP state array to prepare for a new request to the server as part of the ongoing communication process. 359 | 360 | SMTP::WantRead(&$state) 361 | ----------------------- 362 | 363 | Access: _internal_ static 364 | 365 | Parameters: 366 | 367 | * $state - A valid SMTP state array. 368 | 369 | Returns: A boolean of true if the underlying socket is waiting to read data, false otherwise. 370 | 371 | This internal static function is used to identify if the underlying socket in this state should be used in the read array of a stream_select() call (or equivalent) when the socket is in 'async' (non-blocking) mode. 372 | 373 | SMTP::WantWrite(&$state) 374 | ------------------------ 375 | 376 | Access: _internal_ static 377 | 378 | Parameters: 379 | 380 | * $state - A valid SMTP state array. 381 | 382 | Returns: A boolean of true if the underlying socket is waiting to write data, false otherwise. 383 | 384 | This internal static function is used to identify if the underlying socket in this state should be used in the write array of a stream_select() call (or equivalent) when the socket is in 'async' (non-blocking) mode. 385 | 386 | SMTP::ProcessState(&$state) 387 | --------------------------- 388 | 389 | Access: public static 390 | 391 | Parameters: 392 | 393 | * $state - A valid SMTP state array. 394 | 395 | Returns: A standard array of information. 396 | 397 | This internal-ish static function runs the core state engine behind the scenes against the input state. This is the primary workhorse of the SMTP network communication routines. It supports running input states in client mode only. 398 | 399 | SMTP::SMTP_RandomHexString($length) 400 | ----------------------------------- 401 | 402 | Access: private static 403 | 404 | Parameters: 405 | 406 | * $length - An integer containing the length of the string to create. 407 | 408 | Returns: A randomly generated string containing hexadecimal letters and numbers (0-9, A-F). 409 | 410 | An internal static function to generate a unique string for the 'Message-ID' portion of an e-mail. 411 | 412 | SMTP::GetSSLCiphers($type = "intermediate") 413 | ------------------------------------------- 414 | 415 | Access: public static 416 | 417 | Parameters: 418 | 419 | * $type - A string containing one of "modern", "intermediate", or "old" (Default is "intermediate"). 420 | 421 | Returns: A string containing the SSL cipher list to use. 422 | 423 | This static function returns SSL cipher lists extracted from the [Mozilla SSL configuration generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/). 424 | 425 | SMTP::GetSafeSSLOpts($cafile = true, $cipherstype = "intermediate") 426 | ------------------------------------------------------------------- 427 | 428 | Access: public static 429 | 430 | Parameters: 431 | 432 | * $cafile - A boolean that indicates whether or not to use the internally defined CA file list or a string containing the full path and filename of a CA root certificate file (Default is true). 433 | * $cipherstype - A string containing one of "modern", "intermediate", or "old" (Default is "intermediate"). See GetSSLCiphers() above. 434 | 435 | Returns: An array of SSL context options. 436 | 437 | This static function is used to generate a default "sslopts" array if they are not provided when connecting to an associated secure POP3 server. 438 | 439 | SMTP::ProcessSSLOptions(&$options, $key, $host) 440 | ----------------------------------------------- 441 | 442 | Access: private static 443 | 444 | Parameters: 445 | 446 | * $options - An array of options. 447 | * $key - A string specifying which SSL options to process. 448 | * $host - A string containing alternate hostname information. 449 | 450 | Returns: Nothing. 451 | 452 | This internal static function processes the "auto_cainfo", "auto_peer_name", "auto_cn_match", and "auto_sni" options for "sslopts" for SSL/TLS context purposes. 453 | 454 | SMTP::GetIDNAHost($host) 455 | ------------------------ 456 | 457 | Access: protected static 458 | 459 | Parameters: 460 | 461 | * $host - A string containing a hostname to convert to IDNA if necessary. 462 | 463 | Returns: A string containing a converted hostname. 464 | 465 | This internal static function converts an input Unicode hostname to IDNA. If no Unicode characters are detected, this function just returns the input string. 466 | 467 | SMTP::SendSMTPEmail($toaddr, $fromaddr, $message, $options = array()) 468 | --------------------------------------------------------------------- 469 | 470 | Access: public static 471 | 472 | Parameters: 473 | 474 | * $toaddr - A string containing one or more e-mail addresses to send to. 475 | * $fromaddr - A string containing the e-mail address this is from. 476 | * $message - A string containing the message to send. 477 | * $options - An array containing various options (Default is array()). 478 | 479 | Returns: A standard array of information. 480 | 481 | This static function sends an e-mail message by directly connecting to a SMTP server. 482 | 483 | The `$options` array can contain all the `$options` for `SMTP::MakeValidEmailAddress()` plus: 484 | 485 | * server - A string containing the SMTP server to connect to (Default is "localhost"). 486 | * port - An integer containing the SMTP port to connect to (Default is 25). 487 | * secure - A boolean that determines whether or not to connect using SSL (Default is false). 488 | * protocol - A string containing the preferred low-level protocol. May be any supported protocol that the PHP stream_get_transports() function supports (e.g. "ssl", "tls", "tlsv1.2", "tcp"). 489 | * username - A string containing the username to log in to the SMTP server with (Default is ""). 490 | * password - A string containing the password to log in to the SMTP server with (Default is ""). 491 | * connecttimeout - An integer containing the amount of time to wait for the connection to the host to succeed in seconds (Default is 10). 492 | * sslopts - An array of valid SSL context options key-value pairs to use when connecting to a SSL-enabled host. Also supports "auto_cainfo", "auto_peer_name", "auto_cn_match", and "auto_sni" options to define several context options automatically. 493 | * sslhostname - A string containing an alternate hostname to match the certificate against. 494 | * debug - A boolean that determines whether or not the raw SMTP conversation will be returned (Default is false). 495 | * debug_callback - A string containing a function name of a debugging callback. The callback function must accept three parameters - callback($type, $data, $opts). 496 | * debug_callback_opts - Data to pass as the third parameter to the function specified by the 'debug_callback' option. 497 | * hostname - A string containing the hostname to send with the HELO/EHLO command (Default is the server's IP address). 498 | 499 | Some SMTP servers may use the HELO/EHLO 'hostname' option for blocking incoming messages via SPF records. 500 | 501 | While it might seem to make sense to connect to the target SMTP server (the "To" address), that isn't how e-mail works. The source SMTP server (the "From" address) is the correct server to connect to. On many web hosts, the mail server is "localhost" but not always. 502 | 503 | Some popular web hosts will block SMTP requests made with this function. PHP mail() also tends to not work on such hosts. The host isn't usually specifically blocking sending e-mail via the web server but rather generally blocking until a POP3 login occurs for the same "From" address. This is known as POP before SMTP. To successfully send e-mail in this situation, you should attempt to send an e-mail and, if it fails, then use the POP3 functions to connect to the mail server. Once the POP3 login is successful, attempt to send the same message again over SMTP. If the message is still blocked, then something else is the problem. 504 | 505 | SMTP::ConvertHTMLToText_TagCallback($stack, &$content, $open, $tagname, &$attrs, $options) 506 | ------------------------------------------------------------------------------------------ 507 | 508 | Access: _internal_ static 509 | 510 | Parameters: Standard TagFilterStream 'tag_callback' parameters. 511 | 512 | Returns: array("keep_tag" => false, "keep_interior" => false). 513 | 514 | This internal static function is a standard TagFilterStream 'tag_callback' callback that processes the input tag as `SMTP::ConvertHTMLToText()` converts the HTML to (mostly) visually-pleasing plain text. 515 | 516 | SMTP::ConvertHTMLToText_ContentCallback($stack, $result, &$content, $options) 517 | ----------------------------------------------------------------------------- 518 | 519 | Access: _internal_ static 520 | 521 | Parameters: Standard TagFilterStream 'content_callback' parameters. 522 | 523 | Returns: Nothing. 524 | 525 | This internal static function is a standard TagFilterStream 'content_callback' that processes incoming content as `SMTP::ConvertHTMLToText()` converts the HTML to (mostly) visually-pleasing plain text. 526 | 527 | SMTP::ConvertHTMLToText($data) 528 | ------------------------------ 529 | 530 | Access: public static 531 | 532 | Parameters: 533 | 534 | * $data - A string containing a HTML document to convert to text. 535 | 536 | Returns: A string containing the formatted text version of the HTML. 537 | 538 | This static function is intended to be used to generate a text version of a HTML document for sending via e-mail but is versatile enough for most other purposes. The function attempts to create a visually appealing version of the text found within the HTML suitable for sending in an e-mail. 539 | 540 | SMTP::MIME_RandomString($length) 541 | -------------------------------- 542 | 543 | Access: private static 544 | 545 | Parameters: 546 | 547 | * $length - An integer containing the target length of the string. 548 | 549 | Returns: A string containing alphanumeric characters suitable for MIME encoding. 550 | 551 | An internal static function to generate MIME headers for e-mails. Note that the output of this function is not actually random. 552 | 553 | SMTP::SendEmailAsync__Handler($mode, &$data, $key, &$info) 554 | ---------------------------------------------------------- 555 | 556 | Access: _internal_ static 557 | 558 | Parameters: 559 | 560 | * $mode - A string representing the mode/state to process. 561 | * $data - Mixed content the depends entirely on the $mode. 562 | * $key - A string representing the key associated with an object. 563 | * $info - The information associated with the key. 564 | 565 | Returns: Nothing. 566 | 567 | This internal static callback function is the internal handler for MultiAsyncHandler for the `SMTP::SendEmailAsync()` function. 568 | 569 | SMTP::SendEmailAsync($helper, $key, $callback, $fromaddr, $toaddr, $subject, $options = array()) 570 | ------------------------------------------------------------------------------------------------ 571 | 572 | Access: public static 573 | 574 | Parameters: 575 | 576 | * $helper - A MultiAsyncHelper instance. 577 | * $key - A string containing a key to uniquely identify this WebBrowser instance. 578 | * $callback - An optional callback function to receive regular status updates on the request (specify NULL if not needed). The callback function must accept three parameters - callback($key, $url, $result). 579 | * $fromaddr - A string containing the 'From' e-mail address. 580 | * $toaddr - A string containing one or more 'To' e-mail addresses. 581 | * $subject - A string containing the subject of the e-mail. 582 | * $options - An array containing various options for the e-mail. 583 | 584 | Returns: A standard array of information. 585 | 586 | This static function queues the request with the MultiAsyncHandler instance ($helper) for later async/non-blocking processing of the request. Note that this function always succeeds since request failure can't be detected until after processing begins. 587 | 588 | See MultiAsyncHelper for example usage. 589 | 590 | See `SMTP::SendEmail()` for details on the `$options` array. 591 | 592 | SMTP::CreateDeliveryStatusMessage($host, $fromaddr, $toaddr, $subject, $notificationmsg, $sender, $origrecipient, $finalrecipient, $action, $status, $diagcode, $origmsg = false, $options = array()) 593 | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 594 | 595 | Access: public static 596 | 597 | Parameters: 598 | 599 | * $host - A string containing a host. 600 | * $fromaddr - A string containing an email address. 601 | * $toaddr - A string containing an email address. 602 | * $subject - A string containing a subject line. 603 | * $notificationmsg - A string containing the notification message. 604 | * $sender - A string containing a sender header. 605 | * $origrecipient - A string containing the original recipient header. 606 | * $finalrecipient - A string containing the final recipient header. 607 | * $action - A string containing an action header. 608 | * $status - A string containing a status header. 609 | * $diagcode - A string containing a diagnostic code header. 610 | * $origmsg - A string containing the original message or a boolean of false (Default is false). 611 | * $options - An array containing various options (Default is array()). 612 | 613 | This static function generates a `multipart/report` compatible email (e.g. hard bounce messages). 614 | 615 | SMTP::SendEmail($fromaddr, $toaddr, $subject, $options = array()) 616 | ----------------------------------------------------------------- 617 | 618 | Access: public static 619 | 620 | Parameters: 621 | 622 | * $fromaddr - A string containing the 'From' e-mail address. 623 | * $toaddr - A string containing one or more 'To' e-mail addresses. 624 | * $subject - A string containing the subject of the e-mail. 625 | * $options - An array containing various options for the e-mail. 626 | 627 | Returns: An array of processed information, the result of the PHP `mail()` command, or the result of `SMTP::SendSMTPEmail()` depending on the various `$options`. The default behavior is to return the result of `SMTP::SendSMTPEmail()`. 628 | 629 | This static function sends an e-mail message or returns processed data intended to be sent via e-mail. In terms of capabilities, this function does it all: E-mail address validation and cleanup, DNS checking, direct SMTP server communication, plain-text and HTML e-mails, MIME, attachments, etc. It does its best to act like an actual e-mail client. 630 | 631 | The `$options` array can contain all the `$options` for `SMTP::SendSMTPEmail()` plus: 632 | 633 | * replytoaddr - A string containing an e-mail address to use as the 'Reply-To' address (Default is ""). 634 | * ccaddr - A string containing one or more 'CC' e-mail addresses (Default is ""). 635 | * bccaddr - A string containing one or more 'BCC' e-mail addresses (Default is ""). 636 | * headers - A string containing additional e-mail headers. Usually just the result of a call to SMTP::GetUserAgent() (Default is ""). 637 | * textmessage - A string containing the text version of the message (Default is ""). 638 | * htmlmessage - A string containing the HTML version of the message (Default is ""). 639 | * attachments - An array containing information about zero or more attachments (Default is array()). 640 | * usemail - A boolean that calls the built-in PHP mail() function instead of SendSMTPEmail() (Default is false). 641 | * returnresults - A boolean that causes the function to return an array of processed information instead of sending an e-mail (Default is false). 642 | 643 | The optional 'attachments' array can contain a number of different options: 644 | 645 | * type - A string containing the MIME 'Content-Type' of the attachment. Required for each attachment. 646 | * name - A string containing the filename of the attachment. 647 | * location - A string containing a URL contained within the HTML portion of the e-mail. Do not use 'name'. 648 | * cid - A string containing a 'Content-ID' contained within the HTML portion of the e-mail. Do not use 'name'. 649 | * data - A string containing the binary data to attach. 650 | 651 | When specifying an attachment that someone can open, only use 'name'. For inline attachments, such as embedded images, do not use 'name' but use either 'location' or 'cid' instead (avoid using both). Inline images are generally not blocked by e-mail clients. For most web developers who want to use inline images, 'location' is probably the easiest to use but 'cid' is more likely to get through spam filters since that is what most e-mail clients use. 652 | 653 | SMTP::FilenameSafe($filename) 654 | ----------------------------- 655 | 656 | Access: private static 657 | 658 | Parameters: 659 | 660 | * $filename - A string containing a filename. 661 | 662 | Returns: A string containing a safe filename prefix. 663 | 664 | This internal static function allows the characters A-Z, a-z, 0-9, '_' (underscore), '.' (period), and '-' (hyphen) through. All other characters are converted to hyphens. Multiple hyphens in a row are converted to one hyphen. So a filename like `index@$%*&^$+hacked?12.php` becomes `index-hacked-12.php`. 665 | 666 | Note that this function still allows file extensions through. You should always add your own file extension when calling this function. 667 | 668 | SMTP::ReplaceNewlines($replacewith, $data) 669 | ------------------------------------------ 670 | 671 | Access: private static 672 | 673 | Parameters: 674 | 675 | * $replacewith - A string to replace newlines with. 676 | * $data - A string to replace newlines in. 677 | 678 | Returns: A string with newlines replaced. 679 | 680 | This static function replaces any newline combination within the input data with the target newline. All known (DOS, Mac, *NIX) and unknown newline combinations are handled to normalize on the replacement newline string. 681 | 682 | SMTP::SMTP_Translate($format, ...) 683 | ---------------------------------- 684 | 685 | Access: _internal_ static 686 | 687 | Parameters: 688 | 689 | * $format - A string containing valid sprintf() format specifiers. 690 | 691 | Returns: A string containing a translation. 692 | 693 | This internal static function takes input strings and translates them from English to some other language if CS_TRANSLATE_FUNC is defined to be a valid PHP function name. 694 | -------------------------------------------------------------------------------- /support/Net/license.txt: -------------------------------------------------------------------------------- 1 | Net_DNS2 - DNS Library for handling lookups and updates. 2 | 3 | Copyright (c) 2010-2020, Mike Pultz . 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | * Neither the name of Mike Pultz nor the names of his contributors 19 | may be used to endorse or promote products derived from this 20 | software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /support/email_builder.php: -------------------------------------------------------------------------------- 1 | array(), "custom_type" => array(), "finalize" => array()); 12 | 13 | if (isset(self::$handlers[$mode])) self::$handlers[$mode][] = $callback; 14 | } 15 | 16 | protected static function InitContentOpts(&$contentopts) 17 | { 18 | // Let form handlers modify the array. 19 | foreach (self::$handlers["init"] as $callback) 20 | { 21 | if (is_callable($callback)) call_user_func_array($callback, array(&$contentopts)); 22 | } 23 | } 24 | 25 | protected static function AlterOption(&$html, &$inlineimages, &$option, $prefix) 26 | { 27 | // Let form handlers process custom, modified, and other field types. 28 | foreach (self::$handlers["custom_type"] as $callback) 29 | { 30 | if (is_callable($callback)) call_user_func_array($callback, array(&$html, &$inlineimages, &$option, $prefix)); 31 | } 32 | } 33 | 34 | protected static function Finalize(&$html, &$contentopts) 35 | { 36 | // Let form handlers process custom, modified, and other field types. 37 | foreach (self::$handlers["finalize"] as $callback) 38 | { 39 | if (is_callable($callback)) call_user_func_array($callback, array(&$html, &$contentopts)); 40 | } 41 | } 42 | 43 | public static function ProcessContentOpts(&$html, &$inlineimages, &$contentopts, $prefix) 44 | { 45 | self::InitContentOpts($contentopts); 46 | 47 | foreach ($contentopts as $option) 48 | { 49 | if (is_string($option)) $html .= $prefix . $option . "\n"; 50 | else if (!is_array($option) || !isset($option["type"])) continue; 51 | else 52 | { 53 | self::AlterOption($html, $inlineimages, $option, $prefix); 54 | 55 | switch ($option["type"]) 56 | { 57 | case "layout": 58 | { 59 | if (isset($option["padding"]) && !is_array($option["padding"])) $option["padding"] = array($option["padding"]); 60 | 61 | if (isset($option["padding"])) 62 | { 63 | if (count($option["padding"]) == 1) $option["padding"] = array($option["padding"][0], $option["padding"][0], $option["padding"][0], $option["padding"][0]); 64 | else if (count($option["padding"]) == 2) $option["padding"] = array($option["padding"][0], $option["padding"][1], $option["padding"][0], $option["padding"][1]); 65 | else if (count($option["padding"]) == 3) $option["padding"] = array($option["padding"][0], $option["padding"][1], $option["padding"][2], $option["padding"][1]); 66 | 67 | $numcols = ($option["padding"][3] > 0 ? 1 : 0) + 1 + ($option["padding"][1] > 0 ? 1 : 0); 68 | } 69 | 70 | // Set 'table-width' if both 'width' and 'padding' are specified. 71 | if (isset($option["width"]) && isset($option["padding"]) && is_array($option["padding"])) 72 | { 73 | $option["width"] = (int)$option["width"]; 74 | $option["table-width"] = $option["padding"][1] + $option["width"] + $option["padding"][3]; 75 | } 76 | 77 | if (!isset($option["table-width"])) $option["table-width"] = "100%"; 78 | $option["table-width"] = (string)$option["table-width"]; 79 | if (strpos($option["table-width"], "%") === false && strpos($option["table-width"], "px") === false) $option["table-width"] = (int)$option["table-width"] . "px"; 80 | 81 | $html .= $prefix . "\n"; 82 | 83 | if (isset($option["padding"]) && $option["padding"][0] > 0) 84 | { 85 | $html .= $prefix . "\t"; 86 | $html .= ""; 87 | $html .= "\n"; 88 | } 89 | 90 | $html .= $prefix . "\t\n"; 91 | if (isset($option["padding"])) 92 | { 93 | if ($option["padding"][3] > 0) $html .= $prefix . "\t\t\n"; 94 | } 95 | else if (isset($option["width"]) && (!isset($option["row-align"]) || $option["row-align"] !== "left")) 96 | { 97 | $html .= $prefix . "\t\t\n"; 98 | } 99 | 100 | $html .= $prefix . "\t\t\n"; 105 | 106 | if (isset($option["padding"])) 107 | { 108 | if ($option["padding"][1] > 0) $html .= $prefix . "\t\t\n"; 109 | } 110 | else if (isset($option["width"]) && (!isset($option["row-align"]) || $option["row-align"] !== "right")) 111 | { 112 | $html .= $prefix . "\t\t\n"; 113 | } 114 | 115 | $html .= $prefix . "\t\n"; 116 | 117 | if (isset($option["padding"]) && $option["padding"][2] > 0) 118 | { 119 | $html .= $prefix . "\t"; 120 | $html .= ""; 121 | $html .= "\n"; 122 | } 123 | 124 | $html .= $prefix . "
\n"; 101 | 102 | if (isset($option["content"]) && is_array($option["content"])) self::ProcessContentOpts($html, $inlineimages, $option["content"], $prefix . "\t\t\t"); 103 | 104 | $html .= $prefix . "\t\t
\n"; 125 | 126 | break; 127 | } 128 | case "space": 129 | case "split": 130 | { 131 | if (!isset($option["height"])) $option["height"] = ($option["type"] === "space" ? 5 : 2); 132 | 133 | $html .= $prefix . ""; 134 | $html .= ""; 135 | $html .= "
\n"; 136 | 137 | break; 138 | } 139 | case "button": 140 | { 141 | if (!isset($option["bgcolor"])) $option["bgcolor"] = "#4E88C2"; 142 | if (!isset($option["border-radius"])) $option["border-radius"] = 4; 143 | 144 | if (!isset($option["padding"])) $option["padding"] = array(12); 145 | if (!is_array($option["padding"])) $option["padding"] = array($option["padding"]); 146 | 147 | if (count($option["padding"]) == 1) $option["padding"] = array($option["padding"][0], $option["padding"][0], $option["padding"][0], $option["padding"][0]); 148 | else if (count($option["padding"]) == 2) $option["padding"] = array($option["padding"][0], $option["padding"][1], $option["padding"][0], $option["padding"][1]); 149 | else if (count($option["padding"]) == 3) $option["padding"] = array($option["padding"][0], $option["padding"][1], $option["padding"][2], $option["padding"][1]); 150 | 151 | $numcols = ($option["padding"][3] > 0 ? 1 : 0) + 1 + ($option["padding"][1] > 0 ? 1 : 0); 152 | 153 | $html .= $prefix . "\n"; 154 | 155 | if ($option["padding"][0] > 0) 156 | { 157 | $html .= $prefix . "\t"; 158 | $html .= ""; 159 | $html .= "\n"; 160 | } 161 | 162 | $html .= $prefix . "\t\n"; 163 | 164 | $style = "background-color: " . htmlspecialchars($option["bgcolor"]) . ";"; 165 | if ($option["border-radius"] > 0) $style .= " border-radius: " . (int)$option["border-radius"] . "px;"; 166 | $style .= " border-top: " . (int)$option["padding"][0] . "px solid " . htmlspecialchars($option["bgcolor"]) . ";"; 167 | $style .= " border-right: " . (int)$option["padding"][1] . "px solid " . htmlspecialchars($option["bgcolor"]) . ";"; 168 | $style .= " border-bottom: " . (int)$option["padding"][2] . "px solid " . htmlspecialchars($option["bgcolor"]) . ";"; 169 | $style .= " border-left: " . (int)$option["padding"][3] . "px solid " . htmlspecialchars($option["bgcolor"]) . ";"; 170 | 171 | $html .= $prefix . "\t\t\n"; 172 | 173 | $html .= $prefix . "\t\n"; 174 | 175 | if ($option["padding"][2] > 0) 176 | { 177 | $html .= $prefix . "\t"; 178 | $html .= ""; 179 | $html .= "\n"; 180 | } 181 | 182 | $html .= $prefix . "
" . (isset($option["text"]) ? htmlspecialchars($option["text"]) : "") . (isset($option["html"]) ? $option["html"] : "") . "
\n"; 183 | 184 | break; 185 | } 186 | case "image": 187 | { 188 | // Inline image. 189 | if (isset($option["file"])) 190 | { 191 | $filename = str_replace("\\", "/", $option["file"]); 192 | $data = file_get_contents($filename); 193 | 194 | $pos = strrpos($filename, "."); 195 | if ($pos === false) break; 196 | 197 | $fileext = strtolower(substr($filename, $pos + 1)); 198 | 199 | $fileextmap = array( 200 | "jpg" => array("image/jpeg", "jpg"), 201 | "jpeg" => array("image/jpeg", "jpg"), 202 | "png" => array("image/png", "png"), 203 | "gif" => array("image/gif", "gif"), 204 | ); 205 | 206 | if (!isset($fileextmap[$fileext])) break; 207 | 208 | $name = "image" . sprintf("%03d", count($inlineimages) + 1) . "." . $fileextmap[$fileext][1]; 209 | $cid = $name . "@" . strtoupper(dechex(time())) . "." . strtoupper(dechex(filemtime($filename))); 210 | 211 | $inlineimages[] = array( 212 | "type" => $fileextmap[$fileext][0], 213 | "name" => $name, 214 | "cid" => $cid, 215 | "data" => $data 216 | ); 217 | 218 | $option["src"] = "cid:" . $cid; 219 | } 220 | 221 | $html .= $prefix . "\""\n"; 222 | 223 | break; 224 | } 225 | } 226 | } 227 | } 228 | 229 | self::Finalize($html, $contentopts); 230 | } 231 | 232 | public static function Generate($styles, $contentopts) 233 | { 234 | if (!isset(self::$handlers) || !is_array(self::$handlers)) self::$handlers = array("init" => array(), "custom_type" => array(), "finalize" => array()); 235 | 236 | // Generate the HTML. 237 | $html = << 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | EOF; 249 | 250 | $inlineimages = array(); 251 | 252 | self::ProcessContentOpts($html, $inlineimages, $contentopts, ""); 253 | 254 | $html .= << 256 | 257 | EOF; 258 | 259 | // Inline styles. 260 | if (!class_exists("TagFilter", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/tag_filter.php"; 261 | 262 | $htmloptions = TagFilter::GetHTMLOptions(); 263 | $html2 = TagFilter::Explode($html, $htmloptions); 264 | $root = $html2->Get(); 265 | 266 | foreach ($styles as $key => $val) 267 | { 268 | $rows = $root->Find($key); 269 | //echo "Found " . count($rows) . " references to '" . $key . "'.\n"; 270 | foreach ($rows as $row) 271 | { 272 | if (isset($row->style)) $row->style = trim($row->style) . (substr(trim($row->style), -1) !== ";" ? "; " : " ") . $val; 273 | else $row->style = $val; 274 | } 275 | } 276 | 277 | // Remove 'id' and 'class' attributes. 278 | $rows = $root->Find('[id], [class]'); 279 | foreach ($rows as $row) 280 | { 281 | unset($row->id); 282 | unset($row->class); 283 | } 284 | 285 | $html = $root->GetOuterHTML(); 286 | 287 | return array("success" => true, "html" => $html, "inlineimages" => $inlineimages); 288 | } 289 | } 290 | ?> -------------------------------------------------------------------------------- /support/ipaddr.php: -------------------------------------------------------------------------------- 1 | $segment) 40 | { 41 | $segment = trim($segment); 42 | if ($segment != "") $ipaddr2[] = $segment; 43 | else if ($foundpos === false && count($ipaddr) > $num + 1 && $ipaddr[$num + 1] != "") 44 | { 45 | $foundpos = count($ipaddr2); 46 | $ipaddr2[] = "0000"; 47 | } 48 | } 49 | // Convert ::ffff:123.123.123.123 format. 50 | if (strpos($ipaddr2[count($ipaddr2) - 1], ".") !== false) 51 | { 52 | $x = count($ipaddr2) - 1; 53 | if ($ipaddr2[count($ipaddr2) - 2] != "ffff") $ipaddr2[$x] = "0"; 54 | else 55 | { 56 | $ipaddr = explode(".", $ipaddr2[$x]); 57 | if (count($ipaddr) != 4) $ipaddr2[$x] = "0"; 58 | else 59 | { 60 | $ipaddr2[$x] = str_pad(strtolower(dechex($ipaddr[0])), 2, "0", STR_PAD_LEFT) . str_pad(strtolower(dechex($ipaddr[1])), 2, "0", STR_PAD_LEFT); 61 | $ipaddr2[] = str_pad(strtolower(dechex($ipaddr[2])), 2, "0", STR_PAD_LEFT) . str_pad(strtolower(dechex($ipaddr[3])), 2, "0", STR_PAD_LEFT); 62 | } 63 | } 64 | } 65 | $ipaddr = array_slice($ipaddr2, 0, 8); 66 | if ($foundpos !== false && count($ipaddr) < 8) array_splice($ipaddr, $foundpos, 0, array_fill(0, 8 - count($ipaddr), "0000")); 67 | foreach ($ipaddr as $num => $segment) 68 | { 69 | $ipaddr[$num] = substr(str_pad(strtolower(dechex(hexdec($segment))), 4, "0", STR_PAD_LEFT), -4); 70 | } 71 | $ipv6addr = implode(":", $ipaddr); 72 | 73 | // Extract IPv4 address. 74 | if (substr($ipv6addr, 0, 30) == "0000:0000:0000:0000:0000:ffff:") $ipv4addr = hexdec(substr($ipv6addr, 30, 2)) . "." . hexdec(substr($ipv6addr, 32, 2)) . "." . hexdec(substr($ipv6addr, 35, 2)) . "." . hexdec(substr($ipv6addr, 37, 2)); 75 | 76 | // Make a short IPv6 address. 77 | $shortipv6 = $ipv6addr; 78 | $pattern = "0000:0000:0000:0000:0000:0000:0000"; 79 | do 80 | { 81 | $shortipv6 = str_replace($pattern, ":", $shortipv6); 82 | $pattern = substr($pattern, 5); 83 | } while (strlen($shortipv6) == 39 && $pattern != ""); 84 | $shortipv6 = explode(":", $shortipv6); 85 | foreach ($shortipv6 as $num => $segment) 86 | { 87 | if ($segment != "") $shortipv6[$num] = strtolower(dechex(hexdec($segment))); 88 | } 89 | $shortipv6 = implode(":", $shortipv6); 90 | 91 | return array("ipv6" => $ipv6addr, "shortipv6" => $shortipv6, "ipv4" => $ipv4addr); 92 | } 93 | 94 | public static function GetRemoteIP($proxies = array()) 95 | { 96 | $ipaddr = self::NormalizeIP(isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : "127.0.0.1"); 97 | 98 | // Check for trusted proxies. Stop at first untrusted IP in the chain. 99 | if (isset($proxies[$ipaddr["ipv6"]]) || ($ipaddr["ipv4"] != "" && isset($proxies[$ipaddr["ipv4"]]))) 100 | { 101 | $xforward = (isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ? explode(",", $_SERVER["HTTP_X_FORWARDED_FOR"]) : array()); 102 | $clientip = (isset($_SERVER["HTTP_CLIENT_IP"]) ? explode(",", $_SERVER["HTTP_CLIENT_IP"]) : array()); 103 | 104 | do 105 | { 106 | $found = false; 107 | 108 | if (isset($proxies[$ipaddr["ipv6"]])) $header = $proxies[$ipaddr["ipv6"]]; 109 | else $header = $proxies[$ipaddr["ipv4"]]; 110 | 111 | $header = strtolower($header); 112 | if ($header == "xforward" && count($xforward) > 0) 113 | { 114 | $ipaddr = self::NormalizeIP(array_pop($xforward)); 115 | $found = true; 116 | } 117 | else if ($header == "clientip" && count($clientip) > 0) 118 | { 119 | $ipaddr = self::NormalizeIP(array_pop($clientip)); 120 | $found = true; 121 | } 122 | } while ($found && (isset($proxies[$ipaddr["ipv6"]]) || ($ipaddr["ipv4"] != "" && isset($proxies[$ipaddr["ipv4"]])))); 123 | } 124 | 125 | return $ipaddr; 126 | } 127 | 128 | public static function IsMatch($pattern, $ipaddr) 129 | { 130 | if (is_string($ipaddr)) $ipaddr = self::NormalizeIP($ipaddr); 131 | 132 | if (strpos($pattern, ":") !== false) 133 | { 134 | // Pattern is IPv6. 135 | $pattern = explode(":", strtolower($pattern)); 136 | $ipaddr = explode(":", $ipaddr["ipv6"]); 137 | if (count($pattern) != 8 || count($ipaddr) != 8) return false; 138 | foreach ($pattern as $num => $segment) 139 | { 140 | $found = false; 141 | $pieces = explode(",", $segment); 142 | foreach ($pieces as $piece) 143 | { 144 | $piece = trim($piece); 145 | $piece = explode(".", $piece); 146 | if (count($piece) == 1) 147 | { 148 | $piece = $piece[0]; 149 | 150 | if ($piece == "*") $found = true; 151 | else if (strpos($piece, "-") !== false) 152 | { 153 | $range = explode("-", $piece); 154 | $range[0] = hexdec($range[0]); 155 | $range[1] = hexdec($range[1]); 156 | $val = hexdec($ipaddr[$num]); 157 | if ($range[0] > $range[1]) $range[0] = $range[1]; 158 | if ($val >= $range[0] && $val <= $range[1]) $found = true; 159 | } 160 | else if ($piece === $ipaddr[$num]) $found = true; 161 | } 162 | else if (count($piece) == 2) 163 | { 164 | // Special IPv4-like notation. 165 | $found2 = false; 166 | $found3 = false; 167 | $val = hexdec(substr($ipaddr[$num], 0, 2)); 168 | $val2 = hexdec(substr($ipaddr[$num], 2, 2)); 169 | 170 | if ($piece[0] == "*") $found2 = true; 171 | else if (strpos($piece[0], "-") !== false) 172 | { 173 | $range = explode("-", $piece[0]); 174 | if ($range[0] > $range[1]) $range[0] = $range[1]; 175 | if ($val >= $range[0] && $val <= $range[1]) $found2 = true; 176 | } 177 | else if ($piece[0] == $val) $found2 = true; 178 | 179 | if ($piece[1] == "*") $found3 = true; 180 | else if (strpos($piece[1], "-") !== false) 181 | { 182 | $range = explode("-", $piece[1]); 183 | if ($range[0] > $range[1]) $range[0] = $range[1]; 184 | if ($val >= $range[0] && $val <= $range[1]) $found3 = true; 185 | } 186 | else if ($piece[1] == $val2) $found3 = true; 187 | 188 | if ($found2 && $found3) $found = true; 189 | } 190 | 191 | if ($found) break; 192 | } 193 | 194 | if (!$found) return false; 195 | } 196 | } 197 | else 198 | { 199 | // Pattern is IPv4. 200 | $pattern = explode(".", strtolower($pattern)); 201 | $ipaddr = explode(".", $ipaddr["ipv4"]); 202 | if (count($pattern) != 4 || count($ipaddr) != 4) return false; 203 | foreach ($pattern as $num => $segment) 204 | { 205 | $found = false; 206 | $pieces = explode(",", $segment); 207 | foreach ($pieces as $piece) 208 | { 209 | $piece = trim($piece); 210 | 211 | if ($piece == "*") $found = true; 212 | else if (strpos($piece, "-") !== false) 213 | { 214 | $range = explode("-", $piece); 215 | if ($range[0] > $range[1]) $range[0] = $range[1]; 216 | if ($ipaddr[$num] >= $range[0] && $ipaddr[$num] <= $range[1]) $found = true; 217 | } 218 | else if ($piece == $ipaddr[$num]) $found = true; 219 | 220 | if ($found) break; 221 | } 222 | 223 | if (!$found) return false; 224 | } 225 | } 226 | 227 | return true; 228 | } 229 | } 230 | ?> -------------------------------------------------------------------------------- /support/mime_parser.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /support/pop3.php: -------------------------------------------------------------------------------- 1 | fp = false; 12 | $this->messagelist = array(); 13 | $this->debug = false; 14 | } 15 | 16 | public function __destruct() 17 | { 18 | $this->Disconnect(); 19 | } 20 | 21 | public static function GetSSLCiphers($type = "intermediate") 22 | { 23 | $type = strtolower($type); 24 | 25 | // Cipher list last updated May 3, 2017. 26 | if ($type == "modern") return "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; 27 | else if ($type == "old") return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP"; 28 | 29 | return "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; 30 | } 31 | 32 | public static function GetSafeSSLOpts($cafile = true, $cipherstype = "intermediate") 33 | { 34 | // Result array last updated May 3, 2017. 35 | $result = array( 36 | "ciphers" => self::GetSSLCiphers($cipherstype), 37 | "disable_compression" => true, 38 | "allow_self_signed" => false, 39 | "verify_peer" => true, 40 | "verify_depth" => 5 41 | ); 42 | 43 | if ($cafile === true) $result["auto_cainfo"] = true; 44 | else if ($cafile !== false) $result["cafile"] = $cafile; 45 | 46 | return $result; 47 | } 48 | 49 | private static function ProcessSSLOptions(&$options, $key, $host) 50 | { 51 | if (isset($options[$key]["auto_cainfo"])) 52 | { 53 | unset($options[$key]["auto_cainfo"]); 54 | 55 | $cainfo = ini_get("curl.cainfo"); 56 | if ($cainfo !== false && strlen($cainfo) > 0) $options[$key]["cafile"] = $cainfo; 57 | else if (file_exists(str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem")) $options[$key]["cafile"] = str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem"; 58 | } 59 | 60 | if (isset($options[$key]["auto_peer_name"])) 61 | { 62 | unset($options[$key]["auto_peer_name"]); 63 | 64 | $options[$key]["peer_name"] = $host; 65 | } 66 | 67 | if (isset($options[$key]["auto_cn_match"])) 68 | { 69 | unset($options[$key]["auto_cn_match"]); 70 | 71 | $options[$key]["CN_match"] = $host; 72 | } 73 | 74 | if (isset($options[$key]["auto_sni"])) 75 | { 76 | unset($options[$key]["auto_sni"]); 77 | 78 | $options[$key]["SNI_enabled"] = true; 79 | $options[$key]["SNI_server_name"] = $host; 80 | } 81 | } 82 | 83 | protected static function GetIDNAHost($host) 84 | { 85 | $y = strlen($host); 86 | for ($x = 0; $x < $y && ord($host[$x]) <= 0x7F; $x++); 87 | 88 | if ($x < $y) 89 | { 90 | if (!class_exists("UTFUtils", false)) require_once str_replace("\\", "/", dirname(__FILE__)) . "/utf_utils.php"; 91 | 92 | $host2 = UTFUtils::ConvertToPunycode($host); 93 | if ($host2 !== false) $host = $host2; 94 | } 95 | 96 | return $host; 97 | } 98 | 99 | public function Connect($username, $password, $options = array()) 100 | { 101 | if ($this->fp !== false) $this->Disconnect(); 102 | 103 | $server = (isset($options["server"]) ? self::GetIDNAHost(trim($options["server"])) : "localhost"); 104 | if ($server == "") return array("success" => false, "error" => self::POP3_Translate("Invalid server specified."), "errorcode" => "invalid_server"); 105 | $secure = (isset($options["secure"]) ? $options["secure"] : false); 106 | $protocol = ($secure ? (isset($options["protocol"]) ? strtolower($options["protocol"]) : "ssl") : "tcp"); 107 | if (function_exists("stream_get_transports") && !in_array($protocol, stream_get_transports())) return array("success" => false, "error" => self::POP3_Translate("The desired transport protocol '%s' is not installed.", $protocol), "errorcode" => "transport_not_installed"); 108 | $port = (isset($options["port"]) ? (int)$options["port"] : -1); 109 | if ($port < 0 || $port > 65535) $port = ($secure ? 995 : 110); 110 | 111 | $this->debug = (isset($options["debug"]) ? $options["debug"] : false); 112 | $errornum = 0; 113 | $errorstr = ""; 114 | if (!isset($options["connecttimeout"])) $options["connecttimeout"] = 10; 115 | if (!function_exists("stream_socket_client")) $this->fp = @fsockopen($protocol . "://" . $server, $port, $errornum, $errorstr, $options["connecttimeout"]); 116 | else 117 | { 118 | $context = @stream_context_create(); 119 | if (isset($options["source_ip"])) $context["socket"] = array("bindto" => $options["source_ip"] . ":0"); 120 | if ($secure) 121 | { 122 | if (!isset($options["sslopts"]) || !is_array($options["sslopts"])) 123 | { 124 | $options["sslopts"] = self::GetSafeSSLOpts(); 125 | $options["sslopts"]["auto_peer_name"] = true; 126 | } 127 | self::ProcessSSLOptions($options, "sslopts", (isset($options["sslhostname"]) ? self::GetIDNAHost($options["sslhostname"]) : $server)); 128 | foreach ($options["sslopts"] as $key => $val) @stream_context_set_option($context, "ssl", $key, $val); 129 | } 130 | $this->fp = @stream_socket_client($protocol . "://" . $server . ":" . $port, $errornum, $errorstr, $options["connecttimeout"], STREAM_CLIENT_CONNECT, $context); 131 | 132 | $contextopts = stream_context_get_options($context); 133 | if ($secure && isset($options["sslopts"]) && is_array($options["sslopts"]) && isset($contextopts["ssl"]["peer_certificate"])) 134 | { 135 | if (isset($options["debug_callback"])) $options["debug_callback"]("peercert", @openssl_x509_parse($contextopts["ssl"]["peer_certificate"]), $options["debug_callback_opts"]); 136 | } 137 | } 138 | 139 | if ($this->fp === false) return array("success" => false, "error" => self::POP3_Translate("Unable to establish a POP3 connection to '%s'.", $protocol . "://" . $server . ":" . $port), "errorcode" => "connection_failure", "info" => $errorstr . " (" . $errornum . ")"); 140 | 141 | // Get the initial connection data. 142 | $result = $this->GetPOP3Response(false); 143 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("Unable to get initial POP3 data."), "errorcode" => "no_response", "info" => $result); 144 | $rawrecv = $result["rawrecv"]; 145 | $rawsend = ""; 146 | 147 | // Extract APOP information (if any). 148 | $pos = strpos($result["response"], "<"); 149 | $pos2 = strpos($result["response"], ">", (int)$pos); 150 | if ($pos !== false && $pos2 !== false) $apop = substr($result["response"], $pos, $pos2 - $pos + 1); 151 | else $apop = ""; 152 | 153 | // // Determine authentication capabilities. 154 | // fwrite($this->fp, "CAPA\r\n"); 155 | 156 | if ($apop != "" && (!isset($options["use_apop"]) || $options["use_apop"])) 157 | { 158 | $result = $this->POP3Request("APOP " . $username . " " . md5($apop . $password), $rawsend, $rawrecv); 159 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login request failed (APOP failed)."), "errorcode" => "invalid_response", "info" => $result); 160 | } 161 | else 162 | { 163 | $result = $this->POP3Request("USER " . $username, $rawsend, $rawrecv); 164 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login username is invalid (USER failed)."), "errorcode" => "invalid_response", "info" => $result); 165 | 166 | $result = $this->POP3Request("PASS " . $password, $rawsend, $rawrecv); 167 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The POP3 login password is invalid (PASS failed)."), "errorcode" => "invalid_response", "info" => $result); 168 | } 169 | 170 | return array("success" => true, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 171 | } 172 | 173 | public function GetMessageList() 174 | { 175 | $rawrecv = ""; 176 | $rawsend = ""; 177 | $result = $this->POP3Request("LIST", $rawsend, $rawrecv, true); 178 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message list request failed (LIST failed)."), "errorcode" => "invalid_response", "info" => $result); 179 | 180 | $ids = array(); 181 | foreach ($result["data"] as $data) 182 | { 183 | $data = explode(" ", $data); 184 | if (count($data) > 1) $ids[(int)$data[0]] = (int)$data[1]; 185 | } 186 | 187 | return array("success" => true, "ids" => $ids, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 188 | } 189 | 190 | public function GetMessage($id) 191 | { 192 | $rawrecv = ""; 193 | $rawsend = ""; 194 | $result = $this->POP3Request("RETR " . (int)$id, $rawsend, $rawrecv, true); 195 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message retrieval request failed (RETR %d failed).", (int)$id), "errorcode" => "invalid_response", "info" => $result); 196 | 197 | return array("success" => true, "message" => implode("\r\n", $result["data"]) . "\r\n", "rawsend" => $rawsend, "rawrecv" => $rawrecv); 198 | } 199 | 200 | public function DeleteMessage($id) 201 | { 202 | $rawrecv = ""; 203 | $rawsend = ""; 204 | $result = $this->POP3Request("DELE " . (int)$id, $rawsend, $rawrecv); 205 | if (!$result["success"]) return array("success" => false, "error" => self::POP3_Translate("The message deletion request failed (DELE %d failed).", (int)$id), "errorcode" => "invalid_response", "info" => $result); 206 | 207 | return array("success" => true, "rawsend" => $rawsend, "rawrecv" => $rawrecv); 208 | } 209 | 210 | public function Disconnect() 211 | { 212 | if ($this->fp === false) return true; 213 | 214 | $rawrecv = ""; 215 | $rawsend = ""; 216 | $this->POP3Request("QUIT", $rawsend, $rawrecv); 217 | 218 | fclose($this->fp); 219 | $this->fp = false; 220 | 221 | return true; 222 | } 223 | 224 | private function POP3Request($command, &$rawsend, &$rawrecv, $multiline = false) 225 | { 226 | if ($this->fp === false) return array("success" => false, "error" => self::POP3_Translate("Not connected to a POP3 server."), "errorcode" => "not_connected"); 227 | 228 | fwrite($this->fp, $command . "\r\n"); 229 | if ($this->debug) $rawsend .= $command . "\r\n"; 230 | 231 | $result = $this->GetPOP3Response($multiline); 232 | if ($this->debug) $rawrecv .= $result["rawrecv"]; 233 | 234 | return $result; 235 | } 236 | 237 | private function GetPOP3Response($multiline) 238 | { 239 | $rawrecv = ""; 240 | $currline = fgets($this->fp); 241 | if ($currline === false) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "rawrecv" => $rawrecv); 242 | if ($this->debug) $rawrecv .= $currline; 243 | $currline = rtrim($currline); 244 | if (strtoupper(substr($currline, 0, 5)) == "-ERR ") 245 | { 246 | $data = substr($currline, 5); 247 | return array("success" => false, "error" => self::POP3_Translate("POP3 server returned an error."), "errorcode" => "error_response", "info" => $data, "rawrecv" => $rawrecv); 248 | } 249 | 250 | $response = substr($currline, 4); 251 | if (feof($this->fp)) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "info" => $response, "rawrecv" => $rawrecv); 252 | $data = array(); 253 | if ($multiline) 254 | { 255 | do 256 | { 257 | $currline = fgets($this->fp); 258 | if ($currline === false) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "rawrecv" => $rawrecv); 259 | if ($this->debug) $rawrecv .= $currline; 260 | $currline = rtrim($currline); 261 | if ($currline == ".") break; 262 | if ($currline == "..") $currline = "."; 263 | $data[] = $currline; 264 | } while (!feof($this->fp)); 265 | } 266 | 267 | if (feof($this->fp)) return array("success" => false, "error" => self::POP3_Translate("Connection terminated."), "errorcode" => "connection_terminated", "info" => $response, "rawrecv" => $rawrecv); 268 | 269 | return array("success" => true, "response" => $response, "data" => $data, "rawrecv" => $rawrecv); 270 | } 271 | 272 | private static function POP3_Translate() 273 | { 274 | $args = func_get_args(); 275 | if (!count($args)) return ""; 276 | 277 | return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); 278 | } 279 | } 280 | ?> -------------------------------------------------------------------------------- /support/utf8.php: -------------------------------------------------------------------------------- 1 | = 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) 20 | { 21 | // ASCII minus control and special characters. 22 | $result .= chr($tempchr); 23 | $x++; 24 | } 25 | else 26 | { 27 | if ($y - $x > 1) $tempchr2 = ord($data[$x + 1]); 28 | else $tempchr2 = 0x00; 29 | if ($y - $x > 2) $tempchr3 = ord($data[$x + 2]); 30 | else $tempchr3 = 0x00; 31 | if ($y - $x > 3) $tempchr4 = ord($data[$x + 3]); 32 | else $tempchr4 = 0x00; 33 | 34 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 35 | { 36 | // Non-overlong (2 bytes). 37 | $result .= chr($tempchr); 38 | $result .= chr($tempchr2); 39 | $x += 2; 40 | } 41 | else if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 42 | { 43 | // Non-overlong (3 bytes). 44 | $result .= chr($tempchr); 45 | $result .= chr($tempchr2); 46 | $result .= chr($tempchr3); 47 | $x += 3; 48 | } 49 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 50 | { 51 | // Normal/straight (3 bytes). 52 | $result .= chr($tempchr); 53 | $result .= chr($tempchr2); 54 | $result .= chr($tempchr3); 55 | $x += 3; 56 | } 57 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF)) 58 | { 59 | // Non-surrogates (3 bytes). 60 | $result .= chr($tempchr); 61 | $result .= chr($tempchr2); 62 | $result .= chr($tempchr3); 63 | $x += 3; 64 | } 65 | else if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 66 | { 67 | // Planes 1-3 (4 bytes). 68 | $result .= chr($tempchr); 69 | $result .= chr($tempchr2); 70 | $result .= chr($tempchr3); 71 | $result .= chr($tempchr4); 72 | $x += 4; 73 | } 74 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 75 | { 76 | // Planes 4-15 (4 bytes). 77 | $result .= chr($tempchr); 78 | $result .= chr($tempchr2); 79 | $result .= chr($tempchr3); 80 | $result .= chr($tempchr4); 81 | $x += 4; 82 | } 83 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F) && ($tempchr3 >= 0x80 && $tempchr3 <= 0xBF) && ($tempchr4 >= 0x80 && $tempchr4 <= 0xBF)) 84 | { 85 | // Plane 16 (4 bytes). 86 | $result .= chr($tempchr); 87 | $result .= chr($tempchr2); 88 | $result .= chr($tempchr3); 89 | $result .= chr($tempchr4); 90 | $x += 4; 91 | } 92 | else if ($open && $x + 4 > $y) break; 93 | else $x++; 94 | } 95 | } 96 | 97 | $prefix = substr($data, $x); 98 | 99 | return $result; 100 | } 101 | 102 | public static function MakeValid($data) 103 | { 104 | $prefix = ""; 105 | 106 | if (!is_string($data)) $data = (string)$data; 107 | 108 | return self::MakeValidStream($prefix, $data, false); 109 | } 110 | 111 | public static function IsValid($data) 112 | { 113 | $x = 0; 114 | $y = strlen($data); 115 | while ($x < $y) 116 | { 117 | $tempchr = ord($data[$x]); 118 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $x++; 119 | else if ($tempchr < 0xC2) return false; 120 | else 121 | { 122 | $left = $y - $x; 123 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 124 | else return false; 125 | 126 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 2; 127 | else 128 | { 129 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 130 | else return false; 131 | 132 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 133 | 134 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $x += 3; 135 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 3; 136 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $x += 3; 137 | else 138 | { 139 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 140 | else return false; 141 | 142 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 143 | 144 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $x += 4; 145 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $x += 4; 146 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $x += 4; 147 | else return false; 148 | } 149 | } 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | // Locates the next UTF8 character in a UTF8 string. 157 | // Set Pos and Size to 0 to start at the beginning. 158 | // Returns false at the end of the string or bad UTF8 character. Otherwise, returns true. 159 | public static function NextChrPos(&$data, $datalen, &$pos, &$size) 160 | { 161 | $pos += $size; 162 | $size = 0; 163 | $x = $pos; 164 | $y = $datalen; 165 | if ($x >= $y) return false; 166 | 167 | $tempchr = ord($data[$x]); 168 | if (($tempchr >= 0x20 && $tempchr <= 0x7E) || $tempchr == 0x09 || $tempchr == 0x0A || $tempchr == 0x0D) $size = 1; 169 | else if ($tempchr < 0xC2) return false; 170 | else 171 | { 172 | $left = $y - $x; 173 | if ($left > 1) $tempchr2 = ord($data[$x + 1]); 174 | else return false; 175 | 176 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 2; 177 | else 178 | { 179 | if ($left > 2) $tempchr3 = ord($data[$x + 2]); 180 | else return false; 181 | 182 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 183 | 184 | if ($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) $size = 3; 185 | else if ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 3; 186 | else if ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F)) $size = 3; 187 | else 188 | { 189 | if ($left > 3) $tempchr4 = ord($data[$x + 3]); 190 | else return false; 191 | 192 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 193 | 194 | if ($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) $size = 4; 195 | else if (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) $size = 4; 196 | else if ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F)) $size = 4; 197 | else return false; 198 | } 199 | } 200 | } 201 | 202 | return true; 203 | } 204 | 205 | // Converts a numeric value to a UTF8 character (code point). 206 | public static function chr($num) 207 | { 208 | if ($num < 0 || ($num >= 0xD800 && $num <= 0xDFFF) || ($num >= 0xFDD0 && $num <= 0xFDEF) || ($num & 0xFFFE) == 0xFFFE) return ""; 209 | 210 | if ($num <= 0x7F) $result = chr($num); 211 | else if ($num <= 0x7FF) $result = chr(0xC0 | ($num >> 6)) . chr(0x80 | ($num & 0x3F)); 212 | else if ($num <= 0xFFFF) $result = chr(0xE0 | ($num >> 12)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 213 | else if ($num <= 0x10FFFF) $result = chr(0xF0 | ($num >> 18)) . chr(0x80 | (($num >> 12) & 0x3F)) . chr(0x80 | (($num >> 6) & 0x3F)) . chr(0x80 | ($num & 0x3F)); 214 | else $result = ""; 215 | 216 | return $result; 217 | } 218 | 219 | public static function MakeChr($num) 220 | { 221 | return self::chr($num); 222 | } 223 | 224 | // Converts a UTF8 code point to a numeric value. 225 | public static function ord($str) 226 | { 227 | $tempchr = ord($str[0]); 228 | if ($tempchr <= 0x7F) return $tempchr; 229 | else if ($tempchr < 0xC2) return false; 230 | else 231 | { 232 | $y = strlen($str); 233 | if ($y > 1) $tempchr2 = ord($str[1]); 234 | else return false; 235 | 236 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) return (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 237 | else 238 | { 239 | if ($y > 2) $tempchr3 = ord($str[2]); 240 | else return false; 241 | 242 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) return false; 243 | 244 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 245 | { 246 | return (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 247 | } 248 | else 249 | { 250 | if ($y > 3) $tempchr4 = ord($str[3]); 251 | else return false; 252 | 253 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) return false; 254 | 255 | if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 256 | { 257 | return (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 258 | } 259 | } 260 | } 261 | } 262 | 263 | return false; 264 | } 265 | 266 | // Checks a numeric value to see if it is a combining code point. 267 | public static function IsCombiningCodePoint($val) 268 | { 269 | return (($val >= 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 270 | } 271 | 272 | // Determines if a UTF8 string can also be viewed as ASCII. 273 | public static function IsASCII($data) 274 | { 275 | $pos = 0; 276 | $size = 0; 277 | $y = strlen($data); 278 | while (self::NextChrPos($data, $y, $pos, $size) && $size == 1) {} 279 | if ($pos < $y || $size > 1) return false; 280 | 281 | return true; 282 | } 283 | 284 | // Returns the number of characters in a UTF8 string. 285 | public static function strlen($data) 286 | { 287 | $num = 0; 288 | $pos = 0; 289 | $size = 0; 290 | $y = strlen($data); 291 | while (self::NextChrPos($data, $y, $pos, $size)) $num++; 292 | 293 | return $num; 294 | } 295 | 296 | // Converts a UTF8 string to ASCII and drops bad UTF8 and non-ASCII characters in the process. 297 | public static function ConvertToASCII($data) 298 | { 299 | $result = ""; 300 | 301 | $pos = 0; 302 | $size = 0; 303 | $y = strlen($data); 304 | while ($pos < $y) 305 | { 306 | if (self::NextChrPos($data, $y, $pos, $size) && $size == 1) $result .= $data[$pos]; 307 | else if (!$size) $size = 1; 308 | } 309 | 310 | return $result; 311 | } 312 | 313 | // Converts UTF8 characters in a string to HTML entities. 314 | public static function ConvertToHTML($data) 315 | { 316 | return preg_replace_callback('/([\xC0-\xF7]{1,1}[\x80-\xBF]+)/', __CLASS__ . '::ConvertToHTML__Callback', $data); 317 | } 318 | 319 | protected static function ConvertToHTML__Callback($data) 320 | { 321 | $data = $data[1]; 322 | $num = 0; 323 | $data = str_split(strrev(chr((ord(substr($data, 0, 1)) % 252 % 248 % 240 % 224 % 192) + 128) . substr($data, 1))); 324 | foreach ($data as $k => $v) $num += (ord($v) % 128) * pow(64, $k); 325 | 326 | return "&#" . $num . ";"; 327 | } 328 | } 329 | ?> -------------------------------------------------------------------------------- /support/utf_utils.php: -------------------------------------------------------------------------------- 1 | = 0x0300 && $val <= 0x036F) || ($val >= 0x1DC0 && $val <= 0x1DFF) || ($val >= 0x20D0 && $val <= 0x20FF) || ($val >= 0xFE20 && $val <= 0xFE2F)); 21 | } 22 | 23 | public static function Convert($data, $srctype, $desttype) 24 | { 25 | $arr = is_array($data); 26 | if ($arr) $srctype = self::UTF32_ARRAY; 27 | $x = 0; 28 | $y = ($arr ? count($data) : strlen($data)); 29 | $result = ($desttype === self::UTF32_ARRAY ? array() : ""); 30 | if (!$arr && $srctype === self::UTF32_ARRAY) return $result; 31 | 32 | $first = true; 33 | 34 | if ($srctype === self::UTF8_BOM) 35 | { 36 | if (substr($data, 0, 3) === "\xEF\xBB\xBF") $x = 3; 37 | 38 | $srctype = self::UTF8; 39 | } 40 | 41 | if ($srctype === self::UTF16_BOM) 42 | { 43 | if (substr($data, 0, 2) === "\xFE\xFF") 44 | { 45 | $srctype = self::UTF16_BE; 46 | $x = 2; 47 | } 48 | else if (substr($data, 0, 2) === "\xFF\xFE") 49 | { 50 | $srctype = self::UTF16_LE; 51 | $x = 2; 52 | } 53 | else 54 | { 55 | $srctype = self::UTF16_LE; 56 | } 57 | } 58 | 59 | if ($srctype === self::UTF32_BOM) 60 | { 61 | if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") 62 | { 63 | $srctype = self::UTF32_BE; 64 | $x = 4; 65 | } 66 | else if (substr($data, 0, 4) === "\xFF\xFE\x00\x00") 67 | { 68 | $srctype = self::UTF32_LE; 69 | $x = 4; 70 | } 71 | else 72 | { 73 | $srctype = self::UTF32_LE; 74 | } 75 | } 76 | 77 | while ($x < $y) 78 | { 79 | // Read the next valid code point. 80 | $val = false; 81 | 82 | switch ($srctype) 83 | { 84 | case self::UTF8: 85 | { 86 | $tempchr = ord($data[$x]); 87 | if ($tempchr <= 0x7F) 88 | { 89 | $val = $tempchr; 90 | $x++; 91 | } 92 | else if ($tempchr < 0xC2) $x++; 93 | else 94 | { 95 | $left = $y - $x; 96 | if ($left < 2) $x++; 97 | else 98 | { 99 | $tempchr2 = ord($data[$x + 1]); 100 | 101 | if (($tempchr >= 0xC2 && $tempchr <= 0xDF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) 102 | { 103 | $val = (($tempchr & 0x1F) << 6) | ($tempchr2 & 0x3F); 104 | $x += 2; 105 | } 106 | else if ($left < 3) $x++; 107 | else 108 | { 109 | $tempchr3 = ord($data[$x + 2]); 110 | 111 | if ($tempchr3 < 0x80 || $tempchr3 > 0xBF) $x++; 112 | else 113 | { 114 | if (($tempchr == 0xE0 && ($tempchr2 >= 0xA0 && $tempchr2 <= 0xBF)) || ((($tempchr >= 0xE1 && $tempchr <= 0xEC) || $tempchr == 0xEE || $tempchr == 0xEF) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xED && ($tempchr2 >= 0x80 && $tempchr2 <= 0x9F))) 115 | { 116 | $val = (($tempchr & 0x0F) << 12) | (($tempchr2 & 0x3F) << 6) | ($tempchr3 & 0x3F); 117 | $x += 3; 118 | } 119 | else if ($left < 4) $x++; 120 | else 121 | { 122 | $tempchr4 = ord($data[$x + 3]); 123 | 124 | if ($tempchr4 < 0x80 || $tempchr4 > 0xBF) $x++; 125 | else if (($tempchr == 0xF0 && ($tempchr2 >= 0x90 && $tempchr2 <= 0xBF)) || (($tempchr >= 0xF1 && $tempchr <= 0xF3) && ($tempchr2 >= 0x80 && $tempchr2 <= 0xBF)) || ($tempchr == 0xF4 && ($tempchr2 >= 0x80 && $tempchr2 <= 0x8F))) 126 | { 127 | $val = (($tempchr & 0x07) << 18) | (($tempchr2 & 0x3F) << 12) | (($tempchr3 & 0x3F) << 6) | ($tempchr4 & 0x3F); 128 | $x += 4; 129 | } 130 | else 131 | { 132 | $x++; 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | break; 141 | } 142 | case self::UTF16_LE: 143 | { 144 | if ($x + 1 >= $y) $x = $y; 145 | else 146 | { 147 | $val = unpack("v", substr($data, $x, 2))[1]; 148 | $x += 2; 149 | 150 | if ($val >= 0xD800 && $val <= 0xDBFF) 151 | { 152 | if ($x + 1 >= $y) 153 | { 154 | $x = $y; 155 | $val = false; 156 | } 157 | else 158 | { 159 | $val2 = unpack("v", substr($data, $x, 2))[1]; 160 | 161 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 162 | else 163 | { 164 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 165 | $x += 2; 166 | } 167 | } 168 | } 169 | } 170 | 171 | break; 172 | } 173 | case self::UTF16_BE: 174 | { 175 | if ($x + 1 >= $y) $x = $y; 176 | else 177 | { 178 | $val = unpack("n", substr($data, $x, 2))[1]; 179 | $x += 2; 180 | 181 | if ($val >= 0xD800 && $val <= 0xDBFF) 182 | { 183 | if ($x + 1 >= $y) 184 | { 185 | $x = $y; 186 | $val = false; 187 | } 188 | else 189 | { 190 | $val2 = unpack("n", substr($data, $x, 2))[1]; 191 | 192 | if ($val2 < 0xDC00 || $val2 > 0xDFFF) $val = false; 193 | else 194 | { 195 | $val = ((($val - 0xD800) << 10) | ($val2 - 0xDC00)) + 0x10000; 196 | $x += 2; 197 | } 198 | } 199 | } 200 | } 201 | 202 | break; 203 | } 204 | case self::UTF32_LE: 205 | { 206 | if ($x + 3 >= $y) $x = $y; 207 | else 208 | { 209 | $val = unpack("V", substr($data, $x, 4))[1]; 210 | $x += 4; 211 | } 212 | 213 | break; 214 | } 215 | case self::UTF32_BE: 216 | { 217 | if ($x + 3 >= $y) $x = $y; 218 | else 219 | { 220 | $val = unpack("N", substr($data, $x, 4))[1]; 221 | $x += 4; 222 | } 223 | 224 | break; 225 | } 226 | case self::UTF32_ARRAY: 227 | { 228 | $val = (int)$data[$x]; 229 | $x++; 230 | 231 | break; 232 | } 233 | default: $x = $y; break; 234 | } 235 | 236 | // Make sure it is a valid Unicode value. 237 | // 0xD800-0xDFFF are for UTF-16 surrogate pairs. Invalid characters. 238 | // 0xFDD0-0xFDEF are non-characters. 239 | // 0x*FFFE and 0x*FFFF are reserved. 240 | // The largest possible character is 0x10FFFF. 241 | // First character can't be a combining code point. 242 | if ($val !== false && !($val < 0 || ($val >= 0xD800 && $val <= 0xDFFF) || ($val >= 0xFDD0 && $val <= 0xFDEF) || ($val & 0xFFFE) == 0xFFFE || $val > 0x10FFFF || ($first && self::IsCombiningCodePoint($val)))) 243 | { 244 | if ($first) 245 | { 246 | if ($desttype === self::UTF8_BOM) 247 | { 248 | $result .= "\xEF\xBB\xBF"; 249 | 250 | $desttype = self::UTF8; 251 | } 252 | 253 | if ($desttype === self::UTF16_BOM) 254 | { 255 | $result .= "\xFF\xFE"; 256 | 257 | $desttype = self::UTF16_LE; 258 | } 259 | 260 | if ($srctype === self::UTF32_BOM) 261 | { 262 | $result .= "\xFF\xFE\x00\x00"; 263 | 264 | $desttype = self::UTF32_LE; 265 | } 266 | 267 | $first = false; 268 | } 269 | 270 | switch ($desttype) 271 | { 272 | case self::UTF8: 273 | { 274 | if ($val <= 0x7F) $result .= chr($val); 275 | else if ($val <= 0x7FF) $result .= chr(0xC0 | ($val >> 6)) . chr(0x80 | ($val & 0x3F)); 276 | else if ($val <= 0xFFFF) $result .= chr(0xE0 | ($val >> 12)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 277 | else if ($val <= 0x10FFFF) $result .= chr(0xF0 | ($val >> 18)) . chr(0x80 | (($val >> 12) & 0x3F)) . chr(0x80 | (($val >> 6) & 0x3F)) . chr(0x80 | ($val & 0x3F)); 278 | 279 | break; 280 | } 281 | case self::UTF16_LE: 282 | { 283 | if ($val <= 0xFFFF) $result .= pack("v", $val); 284 | else 285 | { 286 | $val -= 0x10000; 287 | $result .= pack("v", ((($val >> 10) & 0x3FF) + 0xD800)); 288 | $result .= pack("v", (($val & 0x3FF) + 0xDC00)); 289 | } 290 | 291 | break; 292 | } 293 | case self::UTF16_BE: 294 | { 295 | if ($val <= 0xFFFF) $result .= pack("n", $val); 296 | else 297 | { 298 | $val -= 0x10000; 299 | $result .= pack("n", ((($val >> 10) & 0x3FF) + 0xD800)); 300 | $result .= pack("n", (($val & 0x3FF) + 0xDC00)); 301 | } 302 | 303 | break; 304 | } 305 | case self::UTF32_LE: 306 | { 307 | $result .= pack("V", $val); 308 | 309 | break; 310 | } 311 | case self::UTF32_BE: 312 | { 313 | $result .= pack("N", $val); 314 | 315 | break; 316 | } 317 | case self::UTF32_ARRAY: 318 | { 319 | $result[] = $val; 320 | 321 | break; 322 | } 323 | default: $x = $y; break; 324 | } 325 | } 326 | } 327 | 328 | return $result; 329 | } 330 | 331 | 332 | protected const PUNYCODE_BASE = 36; 333 | protected const PUNYCODE_TMIN = 1; 334 | protected const PUNYCODE_TMAX = 26; 335 | protected const PUNYCODE_SKEW = 38; 336 | protected const PUNYCODE_DAMP = 700; 337 | protected const PUNYCODE_INITIAL_BIAS = 72; 338 | protected const PUNYCODE_INITIAL_N = 0x80; 339 | protected const PUNYCODE_DIGIT_MAP = "abcdefghijklmnopqrstuvwxyz0123456789"; 340 | 341 | public static function ConvertToPunycode($domain) 342 | { 343 | // Reject invalid domain name lengths. 344 | if (strlen($domain) > 255) return false; 345 | 346 | $parts = explode(".", $domain); 347 | 348 | foreach ($parts as $num => $part) 349 | { 350 | // Reject invalid label lengths. 351 | $y = strlen($part); 352 | if ($y > 63) return false; 353 | 354 | // Skip already encoded portions. 355 | if (substr($part, 0, 4) === "xn--") continue; 356 | 357 | // Convert UTF-8 to UTF-32 code points. 358 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 359 | 360 | // Handle ASCII code points. 361 | $part2 = ""; 362 | foreach ($data as $cp) 363 | { 364 | if ($cp <= 0x7F) $part2 .= strtolower(chr($cp)); 365 | } 366 | 367 | $numhandled = strlen($part2); 368 | $y = count($data); 369 | 370 | if ($numhandled >= $y) 371 | { 372 | $parts[$num] = $part2; 373 | 374 | continue; 375 | } 376 | 377 | if ($numhandled) $part2 .= "-"; 378 | 379 | $part2 = "xn--" . $part2; 380 | 381 | if (strlen($part2) > 63) return false; 382 | 383 | $bias = self::PUNYCODE_INITIAL_BIAS; 384 | $n = self::PUNYCODE_INITIAL_N; 385 | $delta = 0; 386 | $first = true; 387 | 388 | while ($numhandled < $y) 389 | { 390 | // Find the next largest unhandled code point. 391 | $cp2 = 0x01000000; 392 | foreach ($data as $cp) 393 | { 394 | if ($cp >= $n && $cp2 > $cp) $cp2 = $cp; 395 | } 396 | 397 | // Increase delta but prevent overflow. 398 | $delta += ($cp2 - $n) * ($numhandled + 1); 399 | if ($delta < 0) return false; 400 | $n = $cp2; 401 | 402 | foreach ($data as $cp) 403 | { 404 | if ($cp < $n) 405 | { 406 | $delta++; 407 | 408 | if ($delta < 0) return false; 409 | } 410 | else if ($cp === $n) 411 | { 412 | // Calculate and encode a variable length integer from the delta. 413 | $q = $delta; 414 | $x = 0; 415 | do 416 | { 417 | $x += self::PUNYCODE_BASE; 418 | 419 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 420 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 421 | else $t = $x - $bias; 422 | 423 | if ($q < $t) break; 424 | 425 | $part2 .= self::PUNYCODE_DIGIT_MAP[$t + (($q - $t) % (self::PUNYCODE_BASE - $t))]; 426 | 427 | $q = (int)(($q - $t) / (self::PUNYCODE_BASE - $t)); 428 | 429 | if (strlen($part2) > 63) return false; 430 | } while (1); 431 | 432 | $part2 .= self::PUNYCODE_DIGIT_MAP[$q]; 433 | if (strlen($part2) > 63) return false; 434 | 435 | // Adapt bias. 436 | $numhandled++; 437 | $bias = self::InternalPunycodeAdapt($delta, $numhandled, $first); 438 | $delta = 0; 439 | $first = false; 440 | } 441 | } 442 | 443 | $delta++; 444 | $n++; 445 | } 446 | 447 | $parts[$num] = $part2; 448 | } 449 | 450 | return implode(".", $parts); 451 | } 452 | 453 | public static function ConvertFromPunycode($domain) 454 | { 455 | // Reject invalid domain name lengths. 456 | if (strlen($domain) > 255) return false; 457 | 458 | $parts = explode(".", $domain); 459 | 460 | foreach ($parts as $num => $part) 461 | { 462 | // Reject invalid label lengths. 463 | $y = strlen($part); 464 | if ($y > 63) return false; 465 | 466 | // Skip unencoded portions. 467 | if (substr($part, 0, 4) !== "xn--") continue; 468 | 469 | $part = substr($part, 4); 470 | 471 | // Convert UTF-8 to UTF-32 code points. 472 | $data = self::Convert($part, self::UTF8, self::UTF32_ARRAY); 473 | 474 | // Handle ASCII code points. 475 | $hyphen = ord("-"); 476 | for ($x = count($data); $x && $data[$x - 1] !== $hyphen; $x--); 477 | if (!$x) $data2 = array(); 478 | else 479 | { 480 | $data2 = array_splice($data, 0, $x - 1); 481 | 482 | array_shift($data); 483 | } 484 | 485 | $numhandled = count($data2); 486 | 487 | $bias = self::PUNYCODE_INITIAL_BIAS; 488 | $n = self::PUNYCODE_INITIAL_N; 489 | $delta = 0; 490 | $first = true; 491 | 492 | $pos = 0; 493 | $y = count($data); 494 | while ($pos < $y) 495 | { 496 | // Calculate and decode a delta from the variable length integer. 497 | $olddelta = $delta; 498 | $w = 1; 499 | $x = 0; 500 | do 501 | { 502 | $x += self::PUNYCODE_BASE; 503 | 504 | $cp = $data[$pos]; 505 | $pos++; 506 | 507 | if ($cp >= ord("a") && $cp <= ord("z")) $digit = $cp - ord("a"); 508 | else if ($cp >= ord("A") && $cp <= ord("Z")) $digit = $cp - ord("A"); 509 | else if ($cp >= ord("0") && $cp <= ord("9")) $digit = $cp - ord("0") + 26; 510 | else return false; 511 | 512 | $delta += $digit * $w; 513 | if ($delta < 0) return false; 514 | 515 | if ($x <= $bias) $t = self::PUNYCODE_TMIN; 516 | else if ($x >= $bias + self::PUNYCODE_TMAX) $t = self::PUNYCODE_TMAX; 517 | else $t = $x - $bias; 518 | 519 | if ($digit < $t) break; 520 | 521 | $w *= (self::PUNYCODE_BASE - $t); 522 | if ($w < 0) return false; 523 | } while (1); 524 | 525 | // Adapt bias. 526 | $numhandled++; 527 | $bias = self::InternalPunycodeAdapt($delta - $olddelta, $numhandled, $first); 528 | $first = false; 529 | 530 | // Delta was supposed to wrap around from $numhandled to 0, incrementing $n each time, so fix that now. 531 | $n += (int)($delta / $numhandled); 532 | $delta %= $numhandled; 533 | 534 | // Insert $n (the code point) at the delta position. 535 | array_splice($data2, $delta, 0, array($n)); 536 | $delta++; 537 | } 538 | 539 | $parts[$num] = self::Convert($data2, self::UTF32_ARRAY, self::UTF8); 540 | } 541 | 542 | return implode(".", $parts); 543 | } 544 | 545 | // RFC3492 adapt() function. 546 | protected static function InternalPunycodeAdapt($delta, $numpoints, $first) 547 | { 548 | $delta = ($first ? (int)($delta / self::PUNYCODE_DAMP) : $delta >> 1); 549 | $delta += (int)($delta / $numpoints); 550 | 551 | $y = self::PUNYCODE_BASE - self::PUNYCODE_TMIN; 552 | 553 | $condval = (int)(($y * self::PUNYCODE_TMAX) / 2); 554 | for ($x = 0; $delta > $condval; $x += self::PUNYCODE_BASE) $delta = (int)($delta / $y); 555 | 556 | return (int)($x + ((($y + 1) * $delta) / ($delta + self::PUNYCODE_SKEW))); 557 | } 558 | } 559 | ?> -------------------------------------------------------------------------------- /test_suite/run.php: -------------------------------------------------------------------------------- 1 | 2 ? $argv[2] : ""); 69 | if (substr($server, 0, 4) != "tls:") $secure = false; 70 | else 71 | { 72 | $secure = true; 73 | $server = substr($server, 4); 74 | } 75 | $pos = strpos($server, ":"); 76 | if ($pos === false) $port = -1; 77 | else 78 | { 79 | $port = (int)substr($server, $pos + 1); 80 | $server = substr($server, 0, $pos); 81 | } 82 | $username = ($argc > 3 ? $argv[3] : ""); 83 | $password = ($argc > 4 ? $argv[4] : ""); 84 | 85 | // All test messages bounce back to the "From" address as a ZIP file attachment. 86 | $options = array( 87 | "headers" => SMTP::GetUserAgent("Thunderbird"), 88 | "server" => $server, 89 | "secure" => $secure, 90 | "port" => $port, 91 | "username" => $username, 92 | "password" => $password, 93 | "usemail" => ($server == ""), 94 | "sslopts" => array( 95 | "auto_cainfo" => true, 96 | "auto_cn_match" => true, 97 | "auto_sni" => true, 98 | "verify_peer" => true, 99 | "verify_depth" => 6 100 | ) 101 | ); 102 | 103 | // Test plain-text message. 104 | $options["textmessage"] = file_get_contents($rootpath . "/test_1.txt"); 105 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] Plain-text test", $options); 106 | ProcessResult("Sending plain-text test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 107 | 108 | // Test HTML message. 109 | $options["htmlmessage"] = file_get_contents($rootpath . "/test_2.txt"); 110 | unset($options["textmessage"]); 111 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] HTML test", $options); 112 | ProcessResult("Sending HTML only test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 113 | 114 | // Test MIME (HTML + plain-text). 115 | $options["htmlmessage"] = file_get_contents($rootpath . "/test_3.txt"); 116 | $options["textmessage"] = SMTP::ConvertHTMLToText($options["htmlmessage"]); 117 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] MIME test - HTML and plain-text", $options); 118 | ProcessResult("Sending HTML and plain-text MIME test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 119 | 120 | // Test MIME (HTML + plain-text + linked image). 121 | $options["htmlmessage"] = file_get_contents($rootpath . "/test_4.txt"); 122 | $options["textmessage"] = SMTP::ConvertHTMLToText($options["htmlmessage"]); 123 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] MIME test - HTML, plain-text, linked image", $options); 124 | ProcessResult("Sending HTML and plain-text with linked image MIME test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 125 | 126 | // Test MIME (HTML + plain-text + linked image + attachment). 127 | $options["attachments"] = array( 128 | array( 129 | "type" => "image/png", 130 | "name" => "test.png", 131 | "data" => file_get_contents($rootpath . "/test_newsletter_header.png") 132 | ) 133 | ); 134 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] MIME test - HTML, plain-text, linked image, attachment", $options); 135 | ProcessResult("Sending HTML and plain-text with linked image and an attachment MIME test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 136 | 137 | // Test MIME (HTML + plain-text + inline image). 138 | $options["htmlmessage"] = file_get_contents($rootpath . "/test_5.txt"); 139 | $options["textmessage"] = SMTP::ConvertHTMLToText($options["htmlmessage"]); 140 | $options["attachments"] = array( 141 | array( 142 | "type" => "image/png", 143 | "cid" => "newsletter_header.png", 144 | "name" => "newsletter_header.png", 145 | "data" => file_get_contents($rootpath . "/test_newsletter_header.png") 146 | ) 147 | ); 148 | $result = SMTP::SendEmail($argv[1], "bouncetest@barebonescms.com", "[Ultimate E-mail Toolkit] MIME test - HTML, plain-text, inline image", $options); 149 | ProcessResult("Sending HTML and plain-text with inline image MIME test to 'bouncetest@barebonescms.com' from '" . $argv[1] . "'.", $result); 150 | 151 | // Output results. 152 | echo "\n-----\n"; 153 | if (!$failed && !$skipped) echo "All tests were successful.\n"; 154 | else echo "Results: " . $passed . " passed, " . $failed . " failed, " . $skipped . " skipped.\n"; 155 | ?> -------------------------------------------------------------------------------- /test_suite/test_1.txt: -------------------------------------------------------------------------------- 1 | Mary had a little lamb, its fleece was white as snow. 2 | Wherever Mary went, the lamb was sure to go. 3 | Then Mary had dinner. 4 | It was tasty. 5 | 6 | Lamb: The other, other, other white meat. 7 | -------------------------------------------------------------------------------- /test_suite/test_2.txt: -------------------------------------------------------------------------------- 1 | 2 | It was a dark and stormy night.
3 | Dark was cold and brooding.
4 | Stormy was kind and sensitive.
5 | They were the best of friends.
6 |
7 | One evening, they went and grabbed a chicken dinner...
8 |
9 | What's that you say?
10 | Well, they do happen to be real people.
11 | What else were you expecting?
12 | A boring cliché?
13 | -------------------------------------------------------------------------------- /test_suite/test_3.txt: -------------------------------------------------------------------------------- 1 | 2 | Once upon a time, there was this crazy guy named Thomas.
3 | And he ran a crazy company called CubicleSoft.
4 |
5 | He wrote a library that did e-mail that actually worked.
6 | In PHP.
7 |
8 | It was awesome.
9 | Eventually people decided it was so awesome, they parted with some money.
10 |
11 | Don't forget to donate.
12 |
13 | http://barebonescms.com/donate/
14 | 15 | -------------------------------------------------------------------------------- /test_suite/test_4.txt: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 23 |
8 |
9 |

Once upon a time, there was this crazy guy named Thomas.
10 | And he ran a crazy company called CubicleSoft.

11 | 12 |

He wrote a library that did e-mail that actually worked.
13 | In PHP.

14 | 15 |

It was awesome.
16 | Eventually people decided it was so awesome, they parted with some money.

17 | 18 |

Don't forget to donate.

19 | 20 |

http://barebonescms.com/donate/

21 |
22 |
24 | 25 | -------------------------------------------------------------------------------- /test_suite/test_5.txt: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 23 |
8 |
9 |

Once upon a time, there was this crazy guy named Thomas.
10 | And he ran a crazy company called CubicleSoft.

11 | 12 |

He wrote a library that did e-mail that actually worked.
13 | In PHP.

14 | 15 |

It was awesome.
16 | Eventually people decided it was so awesome, they parted with some money.

17 | 18 |

Don't forget to donate.

19 | 20 |

http://barebonescms.com/donate/

21 |
22 |
24 | 25 | -------------------------------------------------------------------------------- /test_suite/test_6.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Booya! 7 | 8 | 9 |
10 |

Hello,

11 | 12 | 13 |
 14 | 
 15 |   ***********
 16 |   * Awesome *
 17 |   *  Text   *
 18 |   ***********
 19 | 
 20 | 
21 | 22 |

Header 1

23 |

Header 2

24 |

Header 3

25 |

Header 4

26 |
Header 5
27 |
Header 6
28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 |
Table Header 1Table Header 2
R1, C1 35 | R1, C2 36 | 37 |
R2, C1R2, C2
41 | 42 | 43 | 44 |

Test. Italicz!

45 |

Should not show!

46 |

47 | 48 | 51 |

https://www.google.com/

52 | 53 | 56 | 57 |
    58 |
  • List element 1 59 |
    1. List element 1.1
    2. 60 |
    3. List element 1.2 61 |
      • List element 1.2.a
      • 62 |
      • List element 1.2.b
        Description line 2 63 |
      • List element 1.2.c
      64 |
    4. List element 1.3
    65 | 66 |
  • List element 2 67 |
  • List element 3 68 |
  • List element 4 69 |
70 | 71 |
72 | Blah d blah d blah! 73 | 74 | Har har har. 75 |
76 | 77 | This is awesome! 78 | 79 | 80 | 81 | 82 | 83 | 84 | Hello. 85 | 86 | 87 | 88 | 89 | 90 | 91 |

92 | Some 93 | Company
123 94 | Test 95 | Street
96 | Somewhere, 97 | NY 98 | 12345

99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /test_suite/test_newsletter_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cubiclesoft/ultimate-email/639499ca9544ceeebb38bda3a3e08d74f89504e5/test_suite/test_newsletter_header.png --------------------------------------------------------------------------------