├── README.md ├── application.php ├── class.php ├── config.php ├── mimeDecode.php └── mysql-structure.sql /README.md: -------------------------------------------------------------------------------- 1 | # PHP IMAP Fetcher 2 | PHP IMAP Fetcher is an open source PHP script that can fetch or pipe emails from a POP box, save the message to MySQL (both text-plain and text-html), and save attachments/images locally. 3 | 4 | # Compatibility 5 | The script has been tested on emails sent from Gmail, Outlook, Yahoo, Apple Mail, Mozilla Thunderbird, and others. 6 | 7 | # Installing 8 | Navigate to the folder on your server where you want to run the script. 9 | 10 | 1. Upload the files: 11 | * application.php 12 | * class.php 13 | * config.php 14 | * mimeDecode.php 15 | 16 | 2. Create a folder called "files" and make sure it's writable by the web server. 17 | 18 | 3. In your MySQL database, create the tables "emails" and "files" by importing the file: 19 | * mysql-structure.sql 20 | 21 | 4. Open the file config.php and add your MySQL and POP credentials. 22 | 23 | # Pipe vs Fetch 24 | You can either pipe an email address to the script to process each email as it arrives, or you can fetch emails one-by-one from a mailbox using a cron job (emails are deleted as they're processed.) We recommend using the fetch method. 25 | 26 | The script is set to "fetch" by default but you can change it to "pipe" in config.php 27 | 28 | # IMAP Flags 29 | There are flags set by default in config.php but you can change them by referencing: 30 | 31 | http://php.net/manual/en/function.imap-open.php 32 | 33 | # Running the script 34 | The file to either pipe to or run as a cron job is application.php 35 | 36 | # Referencing attachments/images 37 | When an email is processed, a unique ID will be generated for the MySQL record and if any attachments are present, a folder for that ID will be created in your "files" folder. Attachments will be saved there. 38 | 39 | Reference to images is only saved in the "text-html" part of the message, and not the "text-plain." 40 | 41 | Images are referenced like: 42 | 43 | [filePath]/dkvmbY14NZr4l4eb79Gs1513724817/mypicture.png 44 | 45 | When including the message in your own application you'll simply replace "[filePath]" with the relevant folder path, for example, if the path to your files is: 46 | 47 | [example.com]/application/files/dkvmbY14NZr4l4eb79Gs1513724817/mypicture.png 48 | 49 | then your code will be 50 | 51 | $str = str_replace("[filePath]","/application/files",$str); 52 | -------------------------------------------------------------------------------- /application.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php -q 2 | readEmail(); 28 | } 29 | 30 | /* Fetch */ 31 | if ($grab_type == "fetch") { 32 | 33 | $inbox = @imap_open("{".$imap_host.$imap_flags."}INBOX",$imap_user,$imap_pass); 34 | 35 | if ($inbox) { 36 | $emails = imap_search($inbox,"ALL"); 37 | 38 | if ($emails) { 39 | rsort($emails); 40 | 41 | if ($emails) { 42 | foreach($emails AS $n) { 43 | $source = imap_fetchbody($inbox, $n, ""); 44 | $uniqid = generateId(20).date("U"); 45 | $emailMessage = new EmailObject($mysql,$uniqid,$source,$file_store); 46 | $emailMessage->readEmail(); 47 | imap_delete($inbox, $n); 48 | } 49 | imap_expunge($inbox); 50 | } 51 | } 52 | /* imap_errors() is called to supress PHP errors, such as when a mailbox is empty */ 53 | $errors = imap_errors(); 54 | imap_close($inbox); 55 | } 56 | } 57 | 58 | function generateId($n) { 59 | 60 | mt_srand((double)microtime()*1000000); 61 | 62 | $id = ""; 63 | while(strlen($id)<$n){ 64 | switch(mt_rand(1,3)){ 65 | case 1: $id.=chr(mt_rand(48,57)); break; // 0-9 66 | case 2: $id.=chr(mt_rand(65,90)); break; // A-Z 67 | case 3: $id.=chr(mt_rand(97,122)); break; // a-z 68 | } 69 | } 70 | 71 | return $id; 72 | } 73 | 74 | mysql_close($mysql); 75 | -------------------------------------------------------------------------------- /class.php: -------------------------------------------------------------------------------- 1 | mysql = $mysql; 12 | $this->uniqid = $uniqid; 13 | $this->source = $source; 14 | $this->file_store = $file_store; 15 | } 16 | 17 | function readEmail(){ 18 | 19 | // Decode email message into parts 20 | $decoder = new Mail_mimeDecode($this->source); 21 | 22 | $this->decoded = $decoder->decode( 23 | Array( 24 | "decode_headers" => TRUE, 25 | "include_bodies" => TRUE, 26 | "decode_bodies" => TRUE, 27 | ) 28 | ); 29 | 30 | // Get from name and email 31 | $this->from = $this->decoded->headers["from"]; 32 | 33 | if (preg_match("/.* <.*@.*\..*>/i",$this->from,$matches)) { 34 | $this->name = preg_replace("/ <(.*)>$/", "", $this->from); 35 | $this->email = preg_replace("/.*<(.*)>.*/","$1",$this->from); 36 | } else { 37 | $this->email = $this->from; 38 | } 39 | 40 | // Get subject 41 | $this->subject = trim($this->decoded->headers["subject"]); 42 | 43 | // Get body & attachments (if available) 44 | if (is_array($this->decoded->parts)) { 45 | foreach($this->decoded->parts as $arItem => $body_part){ 46 | $this->decodePart($body_part); 47 | } 48 | } else { 49 | $this->bodyText = $this->decoded->body; 50 | } 51 | 52 | // Save Message to MySQL 53 | $this->saveToDb(); 54 | } 55 | 56 | // Decode body part 57 | private function decodePart($body_part){ 58 | 59 | // Get file and file name 60 | if (isset($body_part->d_parameters["filename"])) { 61 | 62 | // Set file name 63 | $filename = $body_part->d_parameters["filename"]; 64 | 65 | // Save the file 66 | $this->saveFile($filename,$body_part->body); 67 | 68 | // Get content ID for image (as some mailers use CID instead of file name) 69 | if (isset($body_part->headers["content-id"])) { 70 | $cid = $body_part->headers["content-id"]; 71 | $cid = str_replace("<","",$cid); 72 | $cid = str_replace(">","",$cid); 73 | 74 | // Replace the image src reference with the path to saved file in HTML 75 | $this->bodyHtml = preg_replace("/src=\"(cid:)?".$cid."\"/i", "src=\"[filePath]/".$this->uniqid."/".$filename."\"", $this->bodyHtml); 76 | 77 | // Replace the image CID reference in plain text 78 | $this->bodyText = preg_replace("/\[cid:".$cid."\]/i", "", $this->bodyText); 79 | } 80 | 81 | // Replace the image name reference in plain text 82 | $this->bodyText = preg_replace("/\[".$filename."\]/i", "", $this->bodyText); 83 | } 84 | 85 | $mimeType = "{$body_part->ctype_primary}/{$body_part->ctype_secondary}"; 86 | 87 | // Decode sub-parts 88 | if ($body_part->ctype_primary == "multipart") { 89 | if (is_array($body_part->parts)) { 90 | foreach($body_part->parts as $arItem => $sub_part) { 91 | $this->decodePart($sub_part); 92 | } 93 | } 94 | } 95 | 96 | // Get plain text version 97 | if ($mimeType == "text/plain") { 98 | if (!isset($body_part->disposition)) { 99 | $this->bodyText .= $body_part->body; 100 | } 101 | } 102 | 103 | // Get HTML version 104 | if ($mimeType == "text/html") { 105 | if (!isset($body_part->disposition)) { 106 | $this->bodyHtml .= $body_part->body; 107 | } 108 | } 109 | echo "

".$body_part->ctype_primary; 110 | if ($body_part->ctype_primary == "body") 111 | echo $body_part->body; 112 | } 113 | 114 | // Save file 115 | private function saveFile($filename,$contents) { 116 | 117 | // Check if uniqid folder exists 118 | if (!file_exists($this->file_store."/".$this->uniqid)) 119 | mkdir($this->file_store."/".$this->uniqid); 120 | 121 | // Save file 122 | file_put_contents($this->file_store."/".$this->uniqid."/".$filename, $contents); 123 | $this->saved_files[] = $filename; 124 | } 125 | 126 | // Save message & files to MySQL 127 | private function saveToDb() { 128 | 129 | $mysql = $this->mysql; 130 | $uniqid = $this->uniqid; 131 | 132 | if (isset($this->bodyText)) { 133 | $body_text = $this->bodyText; 134 | $body_text = mysql_real_escape_string(mb_convert_encoding(trim($body_text),'UTF-8','UTF-8'), $mysql); 135 | } else { 136 | $body_text = ""; 137 | } 138 | 139 | if (isset($this->bodyHtml)) { 140 | 141 | $body_html = $this->bodyHtml; 142 | 143 | // Strip header tag (some email clients) 144 | $body_html = preg_replace("/(\\r\\n)?/i","",$body_html); 145 | 146 | // Strip HTML tags (Yahoo, Mozilla) 147 | $body_html = preg_replace("/<\/?html(.*?)>(\\r\\n)?/i","",$body_html); 148 | $body_html = preg_replace("/<\/?head(.*?)>(\\r\\n)?/i","",$body_html); 149 | $body_html = preg_replace("/<\/?body(.*?)>(\\r\\n)?/i","",$body_html); 150 | $body_html = preg_replace("/(\\r\\n)?/i","",$body_html); 151 | $body_html = preg_replace("/(\\r\\n)?/i","",$body_html); 152 | 153 | // Replace superfluous inline image meta 154 | $body_html = preg_replace("/ id=\"(.*?)\"/i","",$body_html); 155 | $body_html = preg_replace("/ alt=\"(.*?)\"/i","",$body_html); 156 | $body_html = preg_replace("/ title=\"(.*?)\"/i","",$body_html); 157 | $body_html = preg_replace("/ class=\"(.*?)\"/i","",$body_html); 158 | $body_html = preg_replace("/ data-id=\"(.*?)\"/i","",$body_html); 159 | $body_html = preg_replace("/ apple-inline=\"yes\"/i","",$body_html); 160 | 161 | $body_html = mysql_real_escape_string(mb_convert_encoding(trim($body_html),'UTF-8','UTF-8'), $mysql); 162 | } else { 163 | $body_html = ""; 164 | } 165 | 166 | // Prepare data for MySql 167 | if (isset($this->name)) 168 | $name = mysql_real_escape_string(mb_convert_encoding($this->name,'UTF-8','UTF-8'), $mysql); 169 | else 170 | $name = ""; 171 | if (isset($this->email)) 172 | $email = mysql_real_escape_string(mb_convert_encoding($this->email,'UTF-8','UTF-8'), $mysql); 173 | else 174 | $email = ""; 175 | if (isset($this->subject)) 176 | $subject = mysql_real_escape_string(mb_convert_encoding($this->subject,'UTF-8','UTF-8'), $mysql); 177 | else 178 | $subject = ""; 179 | 180 | // Insert message to MySQL 181 | mysql_query("INSERT INTO emails (uniqid,time,name,email,subject,body_text,body_html) VALUES ('".$uniqid."',now(),'".$name."','".$email."','".$subject."','".$body_text."','".$body_html."')"); 182 | 183 | // Get the AI ID from MySQL 184 | $result = mysql_query ("SELECT id FROM emails WHERE uniqid='".$uniqid."'"); 185 | $row = mysql_fetch_array($result); 186 | $email_id = mysql_real_escape_string($row["id"], $mysql); 187 | 188 | // Insert all the attached file names to MySQL 189 | if (sizeof($this->saved_files) > 0) { 190 | foreach($this->saved_files as $filename){ 191 | $filename = mysql_real_escape_string(mb_convert_encoding($filename,'UTF-8','UTF-8'), $mysql); 192 | mysql_query("INSERT INTO files (email_id,filename) VALUES ('".$email_id."','".$filename."')"); 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 19 | * Copyright (c) 2003-2006, PEAR 20 | * All rights reserved. 21 | * 22 | * Redistribution and use in source and binary forms, with or 23 | * without modification, are permitted provided that the following 24 | * conditions are met: 25 | * 26 | * - Redistributions of source code must retain the above copyright 27 | * notice, this list of conditions and the following disclaimer. 28 | * - Redistributions in binary form must reproduce the above copyright 29 | * notice, this list of conditions and the following disclaimer in the 30 | * documentation and/or other materials provided with the distribution. 31 | * - Neither the name of the authors, nor the names of its contributors 32 | * may be used to endorse or promote products derived from this 33 | * software without specific prior written permission. 34 | * 35 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 36 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 37 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 38 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 39 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 40 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 41 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 42 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 43 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 44 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 45 | * THE POSSIBILITY OF SUCH DAMAGE. 46 | * 47 | * @category Mail 48 | * @package Mail_Mime 49 | * @author Richard Heyes 50 | * @author George Schlossnagle 51 | * @author Cipriano Groenendal 52 | * @author Sean Coates 53 | * @copyright 2003-2006 PEAR 54 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 55 | * @version CVS: $Id: mimeDecode.php 337165 2015-07-15 09:42:08Z alan_k $ 56 | * @link http://pear.php.net/package/Mail_mime 57 | */ 58 | 59 | 60 | /** 61 | * require PEAR 62 | * 63 | * This package depends on PEAR to raise errors. 64 | */ 65 | require_once 'PEAR.php'; 66 | 67 | 68 | /** 69 | * The Mail_mimeDecode class is used to decode mail/mime messages 70 | * 71 | * This class will parse a raw mime email and return the structure. 72 | * Returned structure is similar to that returned by imap_fetchstructure(). 73 | * 74 | * +----------------------------- IMPORTANT ------------------------------+ 75 | * | Usage of this class compared to native php extensions such as | 76 | * | mailparse or imap, is slow and may be feature deficient. If available| 77 | * | you are STRONGLY recommended to use the php extensions. | 78 | * +----------------------------------------------------------------------+ 79 | * 80 | * @category Mail 81 | * @package Mail_Mime 82 | * @author Richard Heyes 83 | * @author George Schlossnagle 84 | * @author Cipriano Groenendal 85 | * @author Sean Coates 86 | * @copyright 2003-2006 PEAR 87 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 88 | * @version Release: @package_version@ 89 | * @link http://pear.php.net/package/Mail_mime 90 | */ 91 | class Mail_mimeDecode extends PEAR 92 | { 93 | /** 94 | * The raw email to decode 95 | * 96 | * @var string 97 | * @access private 98 | */ 99 | var $_input; 100 | 101 | /** 102 | * The header part of the input 103 | * 104 | * @var string 105 | * @access private 106 | */ 107 | var $_header; 108 | 109 | /** 110 | * The body part of the input 111 | * 112 | * @var string 113 | * @access private 114 | */ 115 | var $_body; 116 | 117 | /** 118 | * If an error occurs, this is used to store the message 119 | * 120 | * @var string 121 | * @access private 122 | */ 123 | var $_error; 124 | 125 | /** 126 | * Flag to determine whether to include bodies in the 127 | * returned object. 128 | * 129 | * @var boolean 130 | * @access private 131 | */ 132 | var $_include_bodies; 133 | 134 | /** 135 | * Flag to determine whether to decode bodies 136 | * 137 | * @var boolean 138 | * @access private 139 | */ 140 | var $_decode_bodies; 141 | 142 | /** 143 | * Flag to determine whether to decode headers 144 | * (set to UTF8 to iconv convert headers) 145 | * @var mixed 146 | * @access private 147 | */ 148 | var $_decode_headers; 149 | 150 | 151 | /** 152 | * Flag to determine whether to include attached messages 153 | * as body in the returned object. Depends on $_include_bodies 154 | * 155 | * @var boolean 156 | * @access private 157 | */ 158 | var $_rfc822_bodies; 159 | 160 | /** 161 | * Constructor. 162 | * 163 | * Sets up the object, initialise the variables, and splits and 164 | * stores the header and body of the input. 165 | * 166 | * @param string The input to decode 167 | * @access public 168 | */ 169 | function __construct($input) 170 | { 171 | list($header, $body) = $this->_splitBodyHeader($input); 172 | 173 | $this->_input = $input; 174 | $this->_header = $header; 175 | $this->_body = $body; 176 | $this->_decode_bodies = false; 177 | $this->_include_bodies = true; 178 | $this->_rfc822_bodies = false; 179 | } 180 | // BC 181 | function Mail_mimeDecode($input) 182 | { 183 | $this->__construct($input); 184 | } 185 | 186 | 187 | /** 188 | * Begins the decoding process. If called statically 189 | * it will create an object and call the decode() method 190 | * of it. 191 | * 192 | * @param array An array of various parameters that determine 193 | * various things: 194 | * include_bodies - Whether to include the body in the returned 195 | * object. 196 | * decode_bodies - Whether to decode the bodies 197 | * of the parts. (Transfer encoding) 198 | * decode_headers - Whether to decode headers, 199 | * - use "UTF8//IGNORE" to convert charset. 200 | * 201 | * input - If called statically, this will be treated 202 | * as the input 203 | * @return object Decoded results 204 | * @access public 205 | */ 206 | function decode($params = null) 207 | { 208 | // determine if this method has been called statically 209 | $isStatic = empty($this) || !is_a($this, __CLASS__); 210 | 211 | // Have we been called statically? 212 | // If so, create an object and pass details to that. 213 | if ($isStatic AND isset($params['input'])) { 214 | 215 | $obj = new Mail_mimeDecode($params['input']); 216 | $structure = $obj->decode($params); 217 | 218 | // Called statically but no input 219 | } elseif ($isStatic) { 220 | return PEAR::raiseError('Called statically and no input given'); 221 | 222 | // Called via an object 223 | } else { 224 | $this->_include_bodies = isset($params['include_bodies']) ? 225 | $params['include_bodies'] : false; 226 | $this->_decode_bodies = isset($params['decode_bodies']) ? 227 | $params['decode_bodies'] : false; 228 | $this->_decode_headers = isset($params['decode_headers']) ? 229 | $params['decode_headers'] : false; 230 | $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? 231 | $params['rfc_822bodies'] : false; 232 | 233 | if (is_string($this->_decode_headers) && !function_exists('iconv')) { 234 | PEAR::raiseError('header decode conversion requested, however iconv is missing'); 235 | } 236 | 237 | $structure = $this->_decode($this->_header, $this->_body); 238 | if ($structure === false) { 239 | $structure = $this->raiseError($this->_error); 240 | } 241 | } 242 | 243 | return $structure; 244 | } 245 | 246 | /** 247 | * Performs the decoding. Decodes the body string passed to it 248 | * If it finds certain content-types it will call itself in a 249 | * recursive fashion 250 | * 251 | * @param string Header section 252 | * @param string Body section 253 | * @return object Results of decoding process 254 | * @access private 255 | */ 256 | function _decode($headers, $body, $default_ctype = 'text/plain') 257 | { 258 | $return = new stdClass; 259 | $return->headers = array(); 260 | $headers = $this->_parseHeaders($headers); 261 | 262 | foreach ($headers as $value) { 263 | $value['value'] = $this->_decodeHeader($value['value']); 264 | if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { 265 | $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); 266 | $return->headers[strtolower($value['name'])][] = $value['value']; 267 | 268 | } elseif (isset($return->headers[strtolower($value['name'])])) { 269 | $return->headers[strtolower($value['name'])][] = $value['value']; 270 | 271 | } else { 272 | $return->headers[strtolower($value['name'])] = $value['value']; 273 | } 274 | } 275 | 276 | 277 | foreach ($headers as $key => $value) { 278 | $headers[$key]['name'] = strtolower($headers[$key]['name']); 279 | switch ($headers[$key]['name']) { 280 | 281 | case 'content-type': 282 | $content_type = $this->_parseHeaderValue($headers[$key]['value']); 283 | 284 | if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) { 285 | $return->ctype_primary = $regs[1]; 286 | $return->ctype_secondary = $regs[2]; 287 | } 288 | 289 | if (isset($content_type['other'])) { 290 | foreach($content_type['other'] as $p_name => $p_value) { 291 | $return->ctype_parameters[$p_name] = $p_value; 292 | } 293 | } 294 | break; 295 | 296 | case 'content-disposition': 297 | $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); 298 | $return->disposition = $content_disposition['value']; 299 | if (isset($content_disposition['other'])) { 300 | foreach($content_disposition['other'] as $p_name => $p_value) { 301 | $return->d_parameters[$p_name] = $p_value; 302 | } 303 | } 304 | break; 305 | 306 | case 'content-transfer-encoding': 307 | $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']); 308 | break; 309 | } 310 | } 311 | 312 | if (isset($content_type)) { 313 | switch (strtolower($content_type['value'])) { 314 | case 'text/plain': 315 | $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 316 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; 317 | break; 318 | 319 | case 'text/html': 320 | $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 321 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null; 322 | break; 323 | 324 | case 'multipart/signed': // PGP 325 | $parts = $this->_boundarySplit($body, $content_type['other']['boundary'], true); 326 | $return->parts['msg_body'] = $parts[0]; 327 | list($part_header, $part_body) = $this->_splitBodyHeader($parts[1]); 328 | $return->parts['sig_hdr'] = $part_header; 329 | $return->parts['sig_body'] = $part_body; 330 | break; 331 | 332 | case 'multipart/parallel': 333 | case 'multipart/appledouble': // Appledouble mail 334 | case 'multipart/report': // RFC1892 335 | case 'multipart/signed': // PGP 336 | case 'multipart/digest': 337 | case 'multipart/alternative': 338 | case 'multipart/related': 339 | case 'multipart/relative': //#20431 - android 340 | case 'multipart/mixed': 341 | case 'application/vnd.wap.multipart.related': 342 | if(!isset($content_type['other']['boundary'])){ 343 | $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; 344 | return false; 345 | } 346 | 347 | $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain'; 348 | 349 | $parts = $this->_boundarySplit($body, $content_type['other']['boundary']); 350 | for ($i = 0; $i < count($parts); $i++) { 351 | list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]); 352 | $part = $this->_decode($part_header, $part_body, $default_ctype); 353 | if($part === false) 354 | $part = $this->raiseError($this->_error); 355 | $return->parts[] = $part; 356 | } 357 | break; 358 | 359 | case 'message/rfc822': 360 | case 'message/delivery-status': // #bug #18693 361 | if ($this->_rfc822_bodies) { 362 | $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; 363 | $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body); 364 | } 365 | $obj = new Mail_mimeDecode($body); 366 | $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, 367 | 'decode_bodies' => $this->_decode_bodies, 368 | 'decode_headers' => $this->_decode_headers)); 369 | unset($obj); 370 | break; 371 | 372 | default: 373 | if(!isset($content_transfer_encoding['value'])) 374 | $content_transfer_encoding['value'] = '7bit'; 375 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null; 376 | break; 377 | } 378 | 379 | } else { 380 | $ctype = explode('/', $default_ctype); 381 | $return->ctype_primary = $ctype[0]; 382 | $return->ctype_secondary = $ctype[1]; 383 | $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null; 384 | } 385 | 386 | return $return; 387 | } 388 | 389 | /** 390 | * Given the output of the above function, this will return an 391 | * array of references to the parts, indexed by mime number. 392 | * 393 | * @param object $structure The structure to go through 394 | * @param string $mime_number Internal use only. 395 | * @return array Mime numbers 396 | */ 397 | function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '') 398 | { 399 | $return = array(); 400 | if (!empty($structure->parts)) { 401 | if ($mime_number != '') { 402 | $structure->mime_id = $prepend . $mime_number; 403 | $return[$prepend . $mime_number] = &$structure; 404 | } 405 | for ($i = 0; $i < count($structure->parts); $i++) { 406 | 407 | 408 | if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') { 409 | $prepend = $prepend . $mime_number . '.'; 410 | $_mime_number = ''; 411 | } else { 412 | $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1)); 413 | } 414 | 415 | $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend); 416 | foreach ($arr as $key => $val) { 417 | $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key]; 418 | } 419 | } 420 | } else { 421 | if ($mime_number == '') { 422 | $mime_number = '1'; 423 | } 424 | $structure->mime_id = $prepend . $mime_number; 425 | $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure; 426 | } 427 | 428 | return $return; 429 | } 430 | 431 | /** 432 | * Given a string containing a header and body 433 | * section, this function will split them (at the first 434 | * blank line) and return them. 435 | * 436 | * @param string Input to split apart 437 | * @return array Contains header and body section 438 | * @access private 439 | */ 440 | function _splitBodyHeader($input) 441 | { 442 | if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { 443 | return array($match[1], $match[2]); 444 | } 445 | // bug #17325 - empty bodies are allowed. - we just check that at least one line 446 | // of headers exist.. 447 | if (count(explode("\n",$input))) { 448 | return array($input, ''); 449 | } 450 | $this->_error = 'Could not split header and body'; 451 | return false; 452 | } 453 | 454 | /** 455 | * Parse headers given in $input and return 456 | * as assoc array. 457 | * 458 | * @param string Headers to parse 459 | * @return array Contains parsed headers 460 | * @access private 461 | */ 462 | function _parseHeaders($input) 463 | { 464 | 465 | if ($input !== '') { 466 | // Unfold the input 467 | $input = preg_replace("/\r?\n/", "\r\n", $input); 468 | //#7065 - wrapping.. with encoded stuff.. - probably not needed, 469 | // wrapping space should only get removed if the trailing item on previous line is a 470 | // encoded character 471 | $input = preg_replace("/=\r\n(\t| )+/", '=', $input); 472 | $input = preg_replace("/\r\n(\t| )+/", ' ', $input); 473 | 474 | $headers = explode("\r\n", trim($input)); 475 | $got_start = false; 476 | foreach ($headers as $value) { 477 | if (!$got_start) { 478 | // munge headers for mbox style from 479 | if ($value[0] == '>') { 480 | $value = substring($value, 1); // remove mbox > 481 | } 482 | if (substr($value,0,5) == 'From ') { 483 | $value = 'Return-Path: ' . substr($value, 5); 484 | } else { 485 | $got_start = true; 486 | } 487 | } 488 | 489 | $hdr_name = substr($value, 0, $pos = strpos($value, ':')); 490 | $hdr_value = substr($value, $pos+1); 491 | if($hdr_value[0] == ' ') { 492 | $hdr_value = substr($hdr_value, 1); 493 | } 494 | 495 | $return[] = array( 496 | 'name' => $hdr_name, 497 | 'value' => $hdr_value 498 | ); 499 | } 500 | } else { 501 | $return = array(); 502 | } 503 | 504 | return $return; 505 | } 506 | 507 | /** 508 | * Function to parse a header value, 509 | * extract first part, and any secondary 510 | * parts (after ;) This function is not as 511 | * robust as it could be. Eg. header comments 512 | * in the wrong place will probably break it. 513 | * 514 | * Extra things this can handle 515 | * filename*0=...... 516 | * filename*1=...... 517 | * 518 | * This is where lines are broken in, and need merging. 519 | * 520 | * filename*0*=ENC'lang'urlencoded data. 521 | * filename*1*=ENC'lang'urlencoded data. 522 | * 523 | * 524 | * 525 | * @param string Header value to parse 526 | * @return array Contains parsed result 527 | * @access private 528 | */ 529 | function _parseHeaderValue($input) 530 | { 531 | if (($pos = strpos($input, ';')) === false) { 532 | $input = $this->_decodeHeader($input); 533 | $return['value'] = trim($input); 534 | return $return; 535 | } 536 | 537 | 538 | 539 | $value = substr($input, 0, $pos); 540 | $value = $this->_decodeHeader($value); 541 | $return['value'] = trim($value); 542 | $input = trim(substr($input, $pos+1)); 543 | 544 | if (!strlen($input) > 0) { 545 | return $return; 546 | } 547 | // at this point input contains xxxx=".....";zzzz="...." 548 | // since we are dealing with quoted strings, we need to handle this properly.. 549 | $i = 0; 550 | $l = strlen($input); 551 | $key = ''; 552 | $val = false; // our string - including quotes.. 553 | $q = false; // in quote.. 554 | $lq = ''; // last quote.. 555 | 556 | while ($i < $l) { 557 | 558 | $c = $input[$i]; 559 | //var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val)); 560 | 561 | $escaped = false; 562 | if ($c == '\\') { 563 | $i++; 564 | if ($i == $l-1) { // end of string. 565 | break; 566 | } 567 | $escaped = true; 568 | $c = $input[$i]; 569 | } 570 | 571 | 572 | // state - in key.. 573 | if ($val === false) { 574 | if (!$escaped && $c == '=') { 575 | $val = ''; 576 | $key = trim($key); 577 | $i++; 578 | continue; 579 | } 580 | if (!$escaped && $c == ';') { 581 | if ($key) { // a key without a value.. 582 | $key= trim($key); 583 | $return['other'][$key] = ''; 584 | } 585 | $key = ''; 586 | } 587 | $key .= $c; 588 | $i++; 589 | continue; 590 | } 591 | 592 | // state - in value.. (as $val is set..) 593 | 594 | if ($q === false) { 595 | // not in quote yet. 596 | if ((!strlen($val) || $lq !== false) && $c == ' ' || $c == "\t") { 597 | $i++; 598 | continue; // skip leading spaces after '=' or after '"' 599 | } 600 | 601 | // do not de-quote 'xxx*= itesm.. 602 | $key_is_trans = $key[strlen($key)-1] == '*'; 603 | 604 | if (!$key_is_trans && !$escaped && ($c == '"' || $c == "'")) { 605 | // start quoted area.. 606 | $q = $c; 607 | // in theory should not happen raw text in value part.. 608 | // but we will handle it as a merged part of the string.. 609 | $val = !strlen(trim($val)) ? '' : trim($val); 610 | $i++; 611 | continue; 612 | } 613 | // got end.... 614 | if (!$escaped && $c == ';') { 615 | 616 | $return['other'][$key] = trim($val); 617 | $val = false; 618 | $key = ''; 619 | $lq = false; 620 | $i++; 621 | continue; 622 | } 623 | 624 | $val .= $c; 625 | $i++; 626 | continue; 627 | } 628 | 629 | // state - in quote.. 630 | if (!$escaped && $c == $q) { // potential exit state.. 631 | 632 | // end of quoted string.. 633 | $lq = $q; 634 | $q = false; 635 | $i++; 636 | continue; 637 | } 638 | 639 | // normal char inside of quoted string.. 640 | $val.= $c; 641 | $i++; 642 | } 643 | 644 | // do we have anything left.. 645 | if (strlen(trim($key)) || $val !== false) { 646 | 647 | $val = trim($val); 648 | 649 | $return['other'][$key] = $val; 650 | } 651 | 652 | 653 | $clean_others = array(); 654 | // merge added values. eg. *1[*] 655 | foreach($return['other'] as $key =>$val) { 656 | if (preg_match('/\*[0-9]+\**$/', $key)) { 657 | $key = preg_replace('/(.*)\*[0-9]+(\**)$/', '\1\2', $key); 658 | if (isset($clean_others[$key])) { 659 | $clean_others[$key] .= $val; 660 | continue; 661 | } 662 | 663 | } 664 | $clean_others[$key] = $val; 665 | 666 | } 667 | 668 | // handle language translation of '*' ending others. 669 | foreach( $clean_others as $key =>$val) { 670 | if ( $key[strlen($key)-1] != '*') { 671 | $clean_others[strtolower($key)] = $val; 672 | continue; 673 | } 674 | unset($clean_others[$key]); 675 | $key = substr($key,0,-1); 676 | //extended-initial-value := [charset] "'" [language] "'" 677 | // extended-other-values 678 | $match = array(); 679 | $info = preg_match("/^([^']+)'([^']*)'(.*)$/", $val, $match); 680 | 681 | $clean_others[$key] = urldecode($match[3]); 682 | $clean_others[strtolower($key)] = $clean_others[$key]; 683 | $clean_others[strtolower($key).'-charset'] = $match[1]; 684 | $clean_others[strtolower($key).'-language'] = $match[2]; 685 | 686 | 687 | } 688 | 689 | 690 | $return['other'] = $clean_others; 691 | 692 | // decode values. 693 | foreach($return['other'] as $key =>$val) { 694 | $charset = isset($return['other'][$key . '-charset']) ? 695 | $return['other'][$key . '-charset'] : false; 696 | 697 | $return['other'][$key] = $this->_decodeHeader($val, $charset); 698 | } 699 | 700 | return $return; 701 | } 702 | 703 | /** 704 | * This function splits the input based 705 | * on the given boundary 706 | * 707 | * @param string Input to parse 708 | * @return array Contains array of resulting mime parts 709 | * @access private 710 | */ 711 | function _boundarySplit($input, $boundary, $eatline = false) 712 | { 713 | $parts = array(); 714 | 715 | $bs_possible = substr($boundary, 2, -2); 716 | $bs_check = '\"' . $bs_possible . '\"'; 717 | 718 | if ($boundary == $bs_check) { 719 | $boundary = $bs_possible; 720 | } 721 | // eatline is used by multipart/signed. 722 | $tmp = $eatline ? 723 | preg_split("/\r?\n--".preg_quote($boundary, '/')."(|--)\n/", $input) : 724 | preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); 725 | 726 | $len = count($tmp) -1; 727 | for ($i = 1; $i < $len; $i++) { 728 | if (strlen(trim($tmp[$i]))) { 729 | $parts[] = $tmp[$i]; 730 | } 731 | } 732 | 733 | // add the last part on if it does not end with the 'closing indicator' 734 | if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') { 735 | $parts[] = $tmp[$len]; 736 | } 737 | return $parts; 738 | } 739 | 740 | /** 741 | * Given a header, this function will decode it 742 | * according to RFC2047. Probably not *exactly* 743 | * conformant, but it does pass all the given 744 | * examples (in RFC2047). 745 | * 746 | * @param string Input header value to decode 747 | * @return string Decoded header value 748 | * @access private 749 | */ 750 | function _decodeHeader($input, $default_charset=false) 751 | { 752 | if (!$this->_decode_headers) { 753 | return $input; 754 | } 755 | // Remove white space between encoded-words 756 | $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); 757 | 758 | // For each encoded-word... 759 | while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { 760 | 761 | $encoded = $matches[1]; 762 | $charset = $matches[2]; 763 | $encoding = $matches[3]; 764 | $text = $matches[4]; 765 | 766 | switch (strtolower($encoding)) { 767 | case 'b': 768 | $text = base64_decode($text); 769 | break; 770 | 771 | case 'q': 772 | $text = str_replace('_', ' ', $text); 773 | preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); 774 | foreach($matches[1] as $value) 775 | $text = str_replace('='.$value, chr(hexdec($value)), $text); 776 | break; 777 | } 778 | if (is_string($this->_decode_headers)) { 779 | $conv = @iconv($charset, $this->_decode_headers, $text); 780 | $text = ($conv === false) ? $text : $conv; 781 | } 782 | $input = str_replace($encoded, $text, $input); 783 | } 784 | 785 | if ($default_charset && is_string($this->_decode_headers)) { 786 | $conv = @iconv($charset, $this->_decode_headers, $input); 787 | $input = ($conv === false) ? $input : $conv; 788 | } 789 | 790 | return $input; 791 | } 792 | 793 | /** 794 | * Given a body string and an encoding type, 795 | * this function will decode and return it. 796 | * 797 | * @param string Input body to decode 798 | * @param string Encoding type to use. 799 | * @return string Decoded body 800 | * @access private 801 | */ 802 | function _decodeBody($input, $encoding = '7bit') 803 | { 804 | switch (strtolower($encoding)) { 805 | case '7bit': 806 | return $input; 807 | break; 808 | 809 | case 'quoted-printable': 810 | return $this->_quotedPrintableDecode($input); 811 | break; 812 | 813 | case 'base64': 814 | return base64_decode($input); 815 | break; 816 | 817 | default: 818 | return $input; 819 | } 820 | } 821 | 822 | /** 823 | * Given a quoted-printable string, this 824 | * function will decode and return it. 825 | * 826 | * @param string Input body to decode 827 | * @return string Decoded body 828 | * @access private 829 | */ 830 | function _quotedPrintableDecode($input) 831 | { 832 | // Remove soft line breaks 833 | $input = preg_replace("/=\r?\n/", '', $input); 834 | 835 | // Replace encoded characters 836 | 837 | $cb = create_function('$matches', ' return chr(hexdec($matches[0]));'); 838 | 839 | $input = preg_replace_callback( '/=([a-f0-9]{2})/i', $cb, $input); 840 | 841 | return $input; 842 | } 843 | 844 | /** 845 | * Checks the input for uuencoded files and returns 846 | * an array of them. Can be called statically, eg: 847 | * 848 | * $files =& Mail_mimeDecode::uudecode($some_text); 849 | * 850 | * It will check for the begin 666 ... end syntax 851 | * however and won't just blindly decode whatever you 852 | * pass it. 853 | * 854 | * @param string Input body to look for attahcments in 855 | * @return array Decoded bodies, filenames and permissions 856 | * @access public 857 | * @author Unknown 858 | */ 859 | function &uudecode($input) 860 | { 861 | // Find all uuencoded sections 862 | preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches); 863 | 864 | for ($j = 0; $j < count($matches[3]); $j++) { 865 | 866 | $str = $matches[3][$j]; 867 | $filename = $matches[2][$j]; 868 | $fileperm = $matches[1][$j]; 869 | 870 | $file = ''; 871 | $str = preg_split("/\r?\n/", trim($str)); 872 | $strlen = count($str); 873 | 874 | for ($i = 0; $i < $strlen; $i++) { 875 | $pos = 1; 876 | $d = 0; 877 | $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077); 878 | 879 | while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) { 880 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 881 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 882 | $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 883 | $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20); 884 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 885 | 886 | $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 887 | 888 | $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077)); 889 | 890 | $pos += 4; 891 | $d += 3; 892 | } 893 | 894 | if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) { 895 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 896 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 897 | $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20); 898 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 899 | 900 | $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2)); 901 | 902 | $pos += 3; 903 | $d += 2; 904 | } 905 | 906 | if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) { 907 | $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20); 908 | $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20); 909 | $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4)); 910 | 911 | } 912 | } 913 | $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file); 914 | } 915 | 916 | return $files; 917 | } 918 | 919 | /** 920 | * getSendArray() returns the arguments required for Mail::send() 921 | * used to build the arguments for a mail::send() call 922 | * 923 | * Usage: 924 | * $mailtext = Full email (for example generated by a template) 925 | * $decoder = new Mail_mimeDecode($mailtext); 926 | * $parts = $decoder->getSendArray(); 927 | * if (!PEAR::isError($parts) { 928 | * list($recipents,$headers,$body) = $parts; 929 | * $mail = Mail::factory('smtp'); 930 | * $mail->send($recipents,$headers,$body); 931 | * } else { 932 | * echo $parts->message; 933 | * } 934 | * @return mixed array of recipeint, headers,body or Pear_Error 935 | * @access public 936 | * @author Alan Knowles 937 | */ 938 | function getSendArray() 939 | { 940 | // prevent warning if this is not set 941 | $this->_decode_headers = FALSE; 942 | $headerlist =$this->_parseHeaders($this->_header); 943 | $to = ""; 944 | if (!$headerlist) { 945 | return $this->raiseError("Message did not contain headers"); 946 | } 947 | foreach($headerlist as $item) { 948 | $header[$item['name']] = $item['value']; 949 | switch (strtolower($item['name'])) { 950 | case "to": 951 | case "cc": 952 | case "bcc": 953 | $to .= ",".$item['value']; 954 | default: 955 | break; 956 | } 957 | } 958 | if ($to == "") { 959 | return $this->raiseError("Message did not contain any recipents"); 960 | } 961 | $to = substr($to,1); 962 | return array($to,$header,$this->_body); 963 | } 964 | 965 | /** 966 | * Returns a xml copy of the output of 967 | * Mail_mimeDecode::decode. Pass the output in as the 968 | * argument. This function can be called statically. Eg: 969 | * 970 | * $output = $obj->decode(); 971 | * $xml = Mail_mimeDecode::getXML($output); 972 | * 973 | * The DTD used for this should have been in the package. Or 974 | * alternatively you can get it from cvs, or here: 975 | * http://www.phpguru.org/xmail/xmail.dtd. 976 | * 977 | * @param object Input to convert to xml. This should be the 978 | * output of the Mail_mimeDecode::decode function 979 | * @return string XML version of input 980 | * @access public 981 | */ 982 | function getXML($input) 983 | { 984 | $crlf = "\r\n"; 985 | $output = '' . $crlf . 986 | '' . $crlf . 987 | '' . $crlf . 988 | Mail_mimeDecode::_getXML($input) . 989 | ''; 990 | 991 | return $output; 992 | } 993 | 994 | /** 995 | * Function that does the actual conversion to xml. Does a single 996 | * mimepart at a time. 997 | * 998 | * @param object Input to convert to xml. This is a mimepart object. 999 | * It may or may not contain subparts. 1000 | * @param integer Number of tabs to indent 1001 | * @return string XML version of input 1002 | * @access private 1003 | */ 1004 | function _getXML($input, $indent = 1) 1005 | { 1006 | $htab = "\t"; 1007 | $crlf = "\r\n"; 1008 | $output = ''; 1009 | $headers = @(array)$input->headers; 1010 | 1011 | foreach ($headers as $hdr_name => $hdr_value) { 1012 | 1013 | // Multiple headers with this name 1014 | if (is_array($headers[$hdr_name])) { 1015 | for ($i = 0; $i < count($hdr_value); $i++) { 1016 | $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent); 1017 | } 1018 | 1019 | // Only one header of this sort 1020 | } else { 1021 | $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent); 1022 | } 1023 | } 1024 | 1025 | if (!empty($input->parts)) { 1026 | for ($i = 0; $i < count($input->parts); $i++) { 1027 | $output .= $crlf . str_repeat($htab, $indent) . '' . $crlf . 1028 | Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) . 1029 | str_repeat($htab, $indent) . '' . $crlf; 1030 | } 1031 | } elseif (isset($input->body)) { 1032 | $output .= $crlf . str_repeat($htab, $indent) . 'body . ']]>' . $crlf; 1034 | } 1035 | 1036 | return $output; 1037 | } 1038 | 1039 | /** 1040 | * Helper function to _getXML(). Returns xml of a header. 1041 | * 1042 | * @param string Name of header 1043 | * @param string Value of header 1044 | * @param integer Number of tabs to indent 1045 | * @return string XML version of input 1046 | * @access private 1047 | */ 1048 | function _getXML_helper($hdr_name, $hdr_value, $indent) 1049 | { 1050 | $htab = "\t"; 1051 | $crlf = "\r\n"; 1052 | $return = ''; 1053 | 1054 | $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value); 1055 | $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name))); 1056 | 1057 | // Sort out any parameters 1058 | if (!empty($new_hdr_value['other'])) { 1059 | foreach ($new_hdr_value['other'] as $paramname => $paramvalue) { 1060 | $params[] = str_repeat($htab, $indent) . $htab . '' . $crlf . 1061 | str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramname) . '' . $crlf . 1062 | str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramvalue) . '' . $crlf . 1063 | str_repeat($htab, $indent) . $htab . '' . $crlf; 1064 | } 1065 | 1066 | $params = implode('', $params); 1067 | } else { 1068 | $params = ''; 1069 | } 1070 | 1071 | $return = str_repeat($htab, $indent) . '

' . $crlf . 1072 | str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_name) . '' . $crlf . 1073 | str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_value['value']) . '' . $crlf . 1074 | $params . 1075 | str_repeat($htab, $indent) . '
' . $crlf; 1076 | 1077 | return $return; 1078 | } 1079 | 1080 | } // End of class 1081 | -------------------------------------------------------------------------------- /mysql-structure.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Table structure for table `emails` 3 | -- 4 | 5 | CREATE TABLE IF NOT EXISTS `emails` ( 6 | `id` int(11) NOT NULL AUTO_INCREMENT, 7 | `uniqid` varchar(255) NOT NULL, 8 | `time` varchar(255) NOT NULL, 9 | `name` varchar(255) NOT NULL, 10 | `email` varchar(255) NOT NULL, 11 | `subject` varchar(255) NOT NULL, 12 | `body_text` text NOT NULL, 13 | `body_html` text NOT NULL, 14 | PRIMARY KEY (`id`) 15 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=22 ; 16 | 17 | -- 18 | -- Table structure for table `files` 19 | -- 20 | 21 | CREATE TABLE IF NOT EXISTS `files` ( 22 | `id` int(11) NOT NULL AUTO_INCREMENT, 23 | `email_id` int(11) NOT NULL, 24 | `filename` varchar(255) NOT NULL, 25 | PRIMARY KEY (`id`) 26 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=120 ; 27 | --------------------------------------------------------------------------------